diff --git a/compose-customisable-ui-example/.idea/codeStyles/Project.xml b/compose-customisable-ui-example/.idea/codeStyles/Project.xml new file mode 100644 index 00000000..113c8620 --- /dev/null +++ b/compose-customisable-ui-example/.idea/codeStyles/Project.xml @@ -0,0 +1,161 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/compose-customisable-ui-example/.idea/codeStyles/codeStyleConfig.xml b/compose-customisable-ui-example/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 00000000..79ee123c --- /dev/null +++ b/compose-customisable-ui-example/.idea/codeStyles/codeStyleConfig.xml @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/compose-customisable-ui-example/Libraries.txt b/compose-customisable-ui-example/Libraries.txt new file mode 120000 index 00000000..9cc9caf9 --- /dev/null +++ b/compose-customisable-ui-example/Libraries.txt @@ -0,0 +1 @@ +../Libraries.txt \ No newline at end of file diff --git a/compose-customisable-ui-example/app/build.gradle b/compose-customisable-ui-example/app/build.gradle new file mode 100644 index 00000000..201819c7 --- /dev/null +++ b/compose-customisable-ui-example/app/build.gradle @@ -0,0 +1,105 @@ +plugins { + id("com.android.application") + id("org.jetbrains.kotlin.android") + id("org.jetbrains.kotlin.kapt") + id("org.jetbrains.kotlin.plugin.compose") +} + +android { + namespace = "io.scanbot.example.compose" + compileSdk = 36 + + defaultConfig { + applicationId = "io.scanbot.example.compose" + targetSdk = 36 + minSdk = 21 + versionCode = 1 + versionName = "1.0" + + ndk { + abiFilters "armeabi-v7a", "arm64-v8a" + // Please add "x86" and "x86_64" if you would like to test on an emulator + // or if you need to support some rare devices with the Intel Atom architecture. + } + } + + buildTypes { + named("debug") { + // set this to `false` to allow debugging and run a "non-release" build + minifyEnabled = false + debuggable = true + } + named("release") { + // set this to `false` to allow debugging and run a "non-release" build + minifyEnabled = true + debuggable = true + } + } + + kotlin { + jvmToolchain(17) + } + + buildFeatures { + buildConfig = true + compose = true + } + + packagingOptions { + exclude "META-INF/LICENSE.txt" + exclude "META-INF/LICENSE" + exclude "META-INF/NOTICE.txt" + exclude "META-INF/NOTICE" + exclude "META-INF/DEPENDENCIES" + } +} + +kapt { + generateStubs = true +} + +configurations { + compile.exclude group: "org.jetbrains", module: "annotations" +} + +dependencies { + implementation("androidx.appcompat:appcompat:1.7.1") + implementation("com.google.android.material:material:1.13.0") + // we are using compose dependencies that come transitively via Scanbot SDK. + // If you need additional compose dependencies, please make sure to use the same versions as Scanbot SDK to avoid version conflicts. + def coroutines_version = "1.10.2" + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version") + + def scanbotSdkVersion = "8.1.0.78-STAGING-SNAPSHOT" + + implementation("io.scanbot:sdk-package-4:$scanbotSdkVersion") + implementation("io.scanbot:rtu-ui-v2-bundle:$scanbotSdkVersion") + + // This dependency is only needed if you plan to use the Generic Document Recognizer feature + implementation("io.scanbot:sdk-documentdata-assets:$scanbotSdkVersion") + + // This dependency is only needed if you plan to use the Document Quality Analyzer feature + implementation("io.scanbot:sdk-multitasktext-assets:$scanbotSdkVersion") + + // This dependency is only needed if you plan to use data scanner feature + implementation("io.scanbot:sdk-textpattern-assets:$scanbotSdkVersion") + + // This dependency is only needed if you plan to use Medical Certificate scanner feature + implementation("io.scanbot:sdk-mc-assets:$scanbotSdkVersion") + + // This dependency is only needed if you plan to use Credit Card Scanner feature + implementation("io.scanbot:sdk-creditcard-assets:$scanbotSdkVersion") + + // This dependency is only needed if you plan to use MRZ scanner feature + implementation("io.scanbot:sdk-mrz-assets:$scanbotSdkVersion") + + // This dependency is only needed if you plan to use Check recognizer feature + implementation("io.scanbot:sdk-check-assets:$scanbotSdkVersion") + + // This dependency is only needed if you plan to use Pdfium processor for import export of pdfs. See comment in Application class. + implementation("io.scanbot:bundle-sdk-pdfium:$scanbotSdkVersion") + + // This dependency is only needed if you plan to use the encryption feature + implementation("io.scanbot:bundle-sdk-crypto-persistence:$scanbotSdkVersion") +} diff --git a/compose-customisable-ui-example/app/src/main/AndroidManifest.xml b/compose-customisable-ui-example/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..8ad7bb62 --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/AndroidManifest.xml @@ -0,0 +1,36 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/compose-customisable-ui-example/app/src/main/assets/ocr_blobs/deu.traineddata b/compose-customisable-ui-example/app/src/main/assets/ocr_blobs/deu.traineddata new file mode 100644 index 00000000..97ed7b2b Binary files /dev/null and b/compose-customisable-ui-example/app/src/main/assets/ocr_blobs/deu.traineddata differ diff --git a/compose-customisable-ui-example/app/src/main/assets/ocr_blobs/eng.traineddata b/compose-customisable-ui-example/app/src/main/assets/ocr_blobs/eng.traineddata new file mode 100755 index 00000000..bbef4675 Binary files /dev/null and b/compose-customisable-ui-example/app/src/main/assets/ocr_blobs/eng.traineddata differ diff --git a/compose-customisable-ui-example/app/src/main/assets/ocr_blobs/osd.traineddata b/compose-customisable-ui-example/app/src/main/assets/ocr_blobs/osd.traineddata new file mode 100644 index 00000000..183644aa Binary files /dev/null and b/compose-customisable-ui-example/app/src/main/assets/ocr_blobs/osd.traineddata differ diff --git a/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/Application.kt b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/Application.kt new file mode 100644 index 00000000..d269f3b2 --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/Application.kt @@ -0,0 +1,112 @@ +package io.scanbot.example.compose + +import android.app.Application +import android.util.Log +import android.widget.Toast +import io.scanbot.example.compose.BuildConfig +import io.scanbot.sap.IScanbotSDKLicenseErrorHandler +import io.scanbot.sdk.ScanbotSDK +import io.scanbot.sdk.ScanbotSDKInitializer +import io.scanbot.sdk.licensing.LicenseStatus +import io.scanbot.sdk.persistence.CameraImageFormat +import io.scanbot.sdk.persistence.page.PageStorageSettings +import io.scanbot.sdk.persistence.fileio.AESEncryptedFileIOProcessor +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job +import kotlinx.coroutines.launch +import java.io.File +import kotlin.coroutines.CoroutineContext + +class Application : Application(), CoroutineScope { + + private var job: Job = Job() + override val coroutineContext: CoroutineContext + get() = Dispatchers.IO + job + + companion object { + /* + * TODO Add the Scanbot SDK license key here. + * Please note: The Scanbot SDK will run without a license key for one minute per session! + * After the trial period is over all Scanbot SDK functions as well as the UI components will stop working. + * You can get an unrestricted "no-strings-attached" 30 day trial license key for free. + * Please submit the trial license form (https://scanbot.io/sdk/trial.html) on our website by using + * the app identifier "io.scanbot.example.sdk.rtu.android" of this example app. + */ + const val LICENSE_KEY = "" + + // TODO: you can enable encryption of all the image files and generated PDFs by changing this property + const val USE_ENCRYPTION = false + + // TODO: you should store a password in a secure place or let the user enter it manually + private const val ENCRYPTION_PASSWORD = "password" + + // TODO: you can select an encryption method + private val ENCRYPTION_METHOD = AESEncryptedFileIOProcessor.AESEncrypterMode.AES256 + } + + override fun onCreate() { + super.onCreate() + val sdkLicenseInfo = ScanbotSDKInitializer() + .withLogging(BuildConfig.DEBUG) + // Optional, custom SDK files directory. Please see the comments below! + .sdkFilesDirectory(this, customStorageDirectory()) + .usePageStorageSettings( + PageStorageSettings.Builder() + .imageFormat(CameraImageFormat.JPG) + .imageQuality(80) + .previewTargetMax(1500) + .build() + ) + .prepareOCRLanguagesBlobs(true) + .useFileEncryption(USE_ENCRYPTION, AESEncryptedFileIOProcessor(ENCRYPTION_PASSWORD, ENCRYPTION_METHOD)) + .licenseErrorHandler(IScanbotSDKLicenseErrorHandler { status, feature, statusMessage -> + // Optional license failure handler implementation. Handle license issues here. + // A license issue can either be an invalid or expired license key + // or missing SDK feature (see SDK feature packages on https://scanbot.io). + val errorMsg = if (status != LicenseStatus.OKAY && status != LicenseStatus.TRIAL) { + "License Error! License status: ${status.name}. $statusMessage" + } else { + "License Error! Missing SDK feature in license: ${feature.name}. $statusMessage" + } + Log.d("ScanbotSDKExample", errorMsg) + Toast.makeText(this@Application, errorMsg, Toast.LENGTH_LONG).show() + }) + + // Uncomment to switch back to the legacy camera approach in Ready-To-Use UI screens + // .useCameraXRtuUi(false) + .license(this, LICENSE_KEY) + .initialize(this) + + // Check the Scanbot SDK license status: + Log.d("ScanbotSDKExample", "Is license valid: " + sdkLicenseInfo.isValid) + Log.d("ScanbotSDKExample", "License status " + sdkLicenseInfo.status.name) + + launch { + // Leaving as is to clean end-users' storage for next several app updates. + ScanbotSDK(this@Application).documentApi.deleteAllDocuments() + } + } + + private fun customStorageDirectory(): File { + // !! Please note !! + // It is strongly recommended to use the default (secure) storage directory of the Scanbot SDK. + // However, for demo purposes we use a custom external(!) storage directory here, which is a public(!) folder. + // All image files and export files (PDF, TIFF, etc) created by the Scanbot SDK in this demo app will be stored + // in this public storage directory and will be accessible for every(!) app having external storage permissions! + // Again, this is only for demo purposes, which allows us to easily fetch and check the generated files + // via Android "adb" CLI tools, Android File Transfer app, Android Studio, etc. + // + // For more details about the storage system of the Scanbot SDK please see our docs: + // https://github.com/doo/scanbot-sdk-example-android/wiki/Storage + // + // For more details about the file system on Android we also highly recommend to check out: + // - https://developer.android.com/guide/topics/data/data-storage + // - https://developer.android.com/training/data-storage/files + + val customDir = + File(this.getExternalFilesDir(null) ?: this.filesDir, "my-custom-storage-folder") + customDir.mkdirs() + return customDir + } +} diff --git a/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/BarcodeDetailScreen.kt b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/BarcodeDetailScreen.kt new file mode 100644 index 00000000..0347a684 --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/BarcodeDetailScreen.kt @@ -0,0 +1,33 @@ +package io.scanbot.example.compose + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + + +@Composable +fun BarcodeDetailScreen(data: String, format: String) { + Surface(modifier = Modifier.fillMaxSize()) { + Column( + modifier = Modifier.padding(32.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text("Barcode Data:", style = MaterialTheme.typography.titleLarge) + Text( + data, + style = MaterialTheme.typography.bodyLarge, + modifier = Modifier.padding(vertical = 8.dp) + ) + Text("Format: $format", style = MaterialTheme.typography.bodyMedium) + } + } +} \ No newline at end of file diff --git a/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/BarcodeFindAndPick.kt b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/BarcodeFindAndPick.kt new file mode 100644 index 00000000..57cb1648 --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/BarcodeFindAndPick.kt @@ -0,0 +1,169 @@ +package io.scanbot.example.compose + +import android.util.Log +import androidx.annotation.OptIn +import androidx.camera.camera2.interop.ExperimentalCamera2Interop +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.material.Icon +import androidx.compose.material.TopAppBar +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.Button +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.layout +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import androidx.navigation.NavOptions +import io.scanbot.common.* +import io.scanbot.demo.composeui.ui.theme.sbBrandColor +import io.scanbot.example.compose.components.* +import io.scanbot.sdk.barcode.textWithExtension +import io.scanbot.sdk.geometry.* +import io.scanbot.sdk.ui_v2.barcode.* +import io.scanbot.sdk.ui_v2.barcode.components.ar_tracking.ScanbotBarcodesArOverlay +import io.scanbot.sdk.ui_v2.common.* +import io.scanbot.sdk.ui_v2.common.components.* +import kotlin.random.* + +@OptIn(ExperimentalCamera2Interop::class) +@Composable +fun BarcodeFindAndPick(navController: NavHostController) { + val density = LocalDensity.current + // Use these states to control camera, torch and zoom + val zoom = remember { mutableFloatStateOf(1.0f) } + val torchEnabled = remember { mutableStateOf(false) } + val cameraEnabled = remember { mutableStateOf(true) } + + // Unused in this example, but you may use it to + // enable/disable barcode scanning dynamically + val barcodeScanningEnabled = remember { mutableStateOf(true) } + val expectedBarcodeValue = "Scanbot" // Expected barcode value to find + Scaffold( + modifier = Modifier.systemBarsPadding(), + topBar = { + TopAppBar( + title = { + Text( + text = "Barcode Single Scan", + style = MaterialTheme.typography.titleLarge, + color = Color.White + ) + }, + backgroundColor = sbBrandColor, + navigationIcon = { + IconButton(onClick = { + navController.popBackStack() + }) { + Icon( + tint = Color.White, + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "" + ) + } + }) + }, + content = { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { + // @Tag("Find And Pick Single Barcode") + BarcodeScannerCustomUI( + // Modify Size here: + modifier = Modifier + .fillMaxWidth() + .weight(1.0f), + cameraEnabled = cameraEnabled.value, + barcodeScanningEnabled = barcodeScanningEnabled.value, + torchEnabled = torchEnabled.value, + zoomLevel = zoom.floatValue, + permissionView = { + // View that will be shown while camera permission is not granted + Box(modifier = Modifier.fillMaxSize()) { + Text( + "Camera permission is required to scan barcodes.", + modifier = Modifier.padding(16.dp), + color = Color.White + ) + } + }, + arPolygonView = { barcodesFlow -> + // Configure AR overlay polygon appearance inside CustomBarcodesArView if needed + ScanbotBarcodesArOverlay( + barcodesFlow, + getData = { barcodeItem -> barcodeItem.textWithExtension }, + getPolygonStyle = { defaultStyle, barcodeItem -> + // Customize polygon style here. + // You may use barcodeItem to apply different styles for different barcode types, etc. + defaultStyle.copy( + drawPolygon = true, + useFill = true, + useFillHighlighted = true, + cornerRadius = density.run { 20.dp.toPx() }, + cornerHighlightedRadius = density.run { 20.dp.toPx() }, + strokeWidth = density.run { 5.dp.toPx() }, + strokeHighlightedWidth = density.run { 5.dp.toPx() }, + strokeColor = Color.Red, + strokeHighlightedColor = Color.Green, + fillColor = Color.Red.copy(alpha = 0.3f), + fillHighlightedColor = Color.Green.copy(alpha = 0.3f), + shouldDrawShadows = false + ) + }, + shouldHighlight = { barcodeItem -> + // Here you can implement any custom logic. + barcodeItem.text == expectedBarcodeValue + }, + // Customize AR view for barcode data here if needed + view = { path, barcodeItem, data, shouldHighlight -> + // Implement custom view for barcode polygon if needed + // See CustomBarcodesArView.kt for details + }, + onClick = { + // Handle barcode click on barcode from AR overlay if needed + }, + ) + }, + onBarcodeScanningResult = { result -> + result.onSuccess { data -> + // Navigate to detail screen for the first barcode + val firstBarcode = data.barcodes.firstOrNull() + if(firstBarcode?.text == expectedBarcodeValue){ + // handle the found barcode if needed + } + }.onFailure { error -> + Log.e( + "BarcodeScannerScreen3", + "Barcode scanning error: ${error.message}", + error + ) + } + }, + ) + // @EndTag("Find And Pick Single Barcode") + } + } + ) +} \ No newline at end of file diff --git a/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/BarcodeScannerBatchScan.kt b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/BarcodeScannerBatchScan.kt new file mode 100644 index 00000000..06752f78 --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/BarcodeScannerBatchScan.kt @@ -0,0 +1,148 @@ +package io.scanbot.example.compose + +import android.util.Log +import androidx.annotation.OptIn +import androidx.camera.camera2.interop.ExperimentalCamera2Interop +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.Icon +import androidx.compose.material.TopAppBar +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import io.scanbot.common.* +import io.scanbot.demo.composeui.ui.theme.sbBrandColor +import io.scanbot.example.compose.components.* +import io.scanbot.sdk.barcode.* +import io.scanbot.sdk.geometry.* +import io.scanbot.sdk.ui_v2.barcode.* +import io.scanbot.sdk.ui_v2.common.components.* +import io.scanbot.sdk.util.snap.* +import kotlinx.coroutines.launch + +@OptIn(ExperimentalCamera2Interop::class) +@Composable +fun BarcodeScannerBatchScan(navController: NavHostController) { + val zoom = remember { mutableFloatStateOf(1.0f) } + val torchEnabled = remember { mutableStateOf(false) } + val cameraEnabled = remember { mutableStateOf(true) } + val barcodeScanningEnabled = remember { mutableStateOf(true) } + val scannedBarcodes = remember { mutableStateListOf() } + val context = LocalContext.current + val soundController = remember { + SoundControllerImpl(context).apply { + // Prepare Scanbot beep sound controller: + setUp() + setVibrationEnabled(true) + setBleepEnabled(true) + } + } + val scope = rememberCoroutineScope() + Scaffold( + modifier = Modifier.systemBarsPadding(), + topBar = { + TopAppBar( + title = { + Text( + text = "Batch Barcodes Scan", + style = MaterialTheme.typography.titleLarge, + color = Color.White + ) + }, + backgroundColor = sbBrandColor, + navigationIcon = { + IconButton(onClick = { + navController.popBackStack() + }) { + Icon( + tint = Color.White, + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "" + ) + } + }) + }, + content = { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { + // @Tag("Batch Scanning") + BarcodeScannerCustomUI( + modifier = Modifier.weight(1f), + cameraEnabled = cameraEnabled.value, + barcodeScanningEnabled = barcodeScanningEnabled.value, + torchEnabled = torchEnabled.value, + zoomLevel = zoom.floatValue, + finderConfiguration = FinderConfiguration( + aspectRatio = AspectRatio(2.0, 1.0), + overlayColor = Color(0x5500FF00), + strokeColor = Color.Transparent, + finderContent = { + CorneredFinder() + } + ), + permissionView = { + Box(modifier = Modifier.fillMaxSize()) { + Text( + "Camera permission is required to scan barcodes.", + modifier = Modifier.padding(16.dp), + color = Color.White + ) + } + }, + onBarcodeScanningResult = { result -> + result.onSuccess { result -> + result.barcodes.forEach { data -> + if (scannedBarcodes.none { it.text == data.text && it.format == data.format }) { + scope.launch { + // Provide sound and vibration feedback on scan: + soundController.playBleepSound() + } + scannedBarcodes.add(data) + } + } + }.onFailure { error -> + Log.e( + "BarcodeScannerScreen2", + "Barcode scanning error: ${error.message}", + error + ) + } + } + ) + // @EndTag("Batch Scanning") + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .padding(8.dp), + ) { + items(scannedBarcodes.reversed()) { barcode -> + BarcodeItem(barcode) + } + } + } + }) +} diff --git a/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/BarcodeScannerDistantScan.kt b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/BarcodeScannerDistantScan.kt new file mode 100644 index 00000000..87ea7d94 --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/BarcodeScannerDistantScan.kt @@ -0,0 +1,203 @@ +package io.scanbot.example.compose + +import android.util.Log +import androidx.annotation.OptIn +import androidx.camera.camera2.interop.ExperimentalCamera2Interop +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.material.Icon +import androidx.compose.material.TopAppBar +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.Button +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import androidx.navigation.NavOptions +import io.scanbot.common.* +import io.scanbot.demo.composeui.ui.theme.sbBrandColor +import io.scanbot.example.compose.components.* +import io.scanbot.sdk.geometry.* +import io.scanbot.sdk.ui_v2.barcode.* +import io.scanbot.sdk.ui_v2.common.* +import io.scanbot.sdk.ui_v2.common.components.* +import kotlin.random.* + +@OptIn(ExperimentalCamera2Interop::class) +@Composable +fun BarcodeScannerDistantScan(navController: NavHostController) { + // Use these states to control camera, torch and zoom + + // THIS IS IMPORTANT FOR DISTANT SCAN USECASE + val zoom = remember { mutableFloatStateOf(20.0f) } + val torchEnabled = remember { mutableStateOf(false) } + val cameraEnabled = remember { mutableStateOf(true) } + + // Unused in this example, but you may use it to + // enable/disable barcode scanning dynamically + val barcodeScanningEnabled = remember { mutableStateOf(true) } + + Scaffold( + modifier = Modifier.systemBarsPadding(), + topBar = { + TopAppBar( + title = { + Text( + text = "Distant Barcode Scan", + style = MaterialTheme.typography.titleLarge, + color = Color.White + ) + }, + backgroundColor = sbBrandColor, + navigationIcon = { + IconButton(onClick = { + navController.popBackStack() + }) { + Icon( + tint = Color.White, + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "" + ) + } + }) + }, + content = { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { + // @Tag("Scanning distant barcodes") + BarcodeScannerCustomUI( + // Modify Size here: + modifier = Modifier + .fillMaxWidth() + .weight(1.0f), + finderConfiguration = FinderConfiguration( + verticalAlignment = Alignment.Top, + previewInsets = PaddingValues( + top = 32.dp, + bottom = 32.dp, + start = 16.dp, + end = 16.dp + ), + // Modify aspect ratio of the viewfinder here: + aspectRatio = AspectRatio(1.0, 1.0), + // Change viewfinder overlay color here: + overlayColor = Color.Transparent, + // Change viewfinder stroke color here: + strokeColor = Color.Transparent, + + // Alternatively, it is possible to provide a completely custom viewfinder content: + finderContent = { + // Custom cornered viewfinder. Can be replaced with any custom Composable + CorneredFinder() + }, + topContent = { + androidx.compose.material.Text( + "Custom Top Content", + color = Color.White, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + ) + }, + bottomContent = { + // You may add custom buttons and other elements here: + androidx.compose.material.Text( + "Custom Bottom Content", + color = Color.White, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + ) + } + ), + cameraEnabled = cameraEnabled.value, + barcodeScanningEnabled = barcodeScanningEnabled.value, + torchEnabled = torchEnabled.value, + zoomLevel = zoom.floatValue, + permissionView = { + // View that will be shown while camera permission is not granted + Box(modifier = Modifier.fillMaxSize()) { + Text( + "Camera permission is required to scan barcodes.", + modifier = Modifier.padding(16.dp), + color = Color.White + ) + } + }, + arPolygonView = { barcodesFlow -> + // Configure AR overlay polygon appearance inside CustomBarcodesArView if needed + CustomBarcodesArView( + barcodesFlow = barcodesFlow, + onBarcodeClick = { + // Handle barcode click on barcode from AR overlay if needed + } + ) + }, + onBarcodeScanningResult = { result -> + result.onSuccess { data -> + // Navigate to detail screen for the first barcode + val firstBarcode = data.barcodes.firstOrNull() + if (firstBarcode != null) { + navController.navigate( + Screen.BarcodeDetail.createRoute( + firstBarcode.text, + firstBarcode.format.name + ), + navOptions = NavOptions.Builder().setLaunchSingleTop(true) + .build() + ) + } + }.onFailure { error -> + Log.e( + "BarcodeScannerScreen3", + "Barcode scanning error: ${error.message}", + error + ) + } + }, + ) + // @EndTag("Scanning distant barcodes") + Row { + androidx.compose.material.Button(modifier = Modifier.weight(1f), onClick = { + zoom.floatValue = 1.0f + Random.nextFloat() + }) { + Text("Zoom") + } + + Button(modifier = Modifier.weight(1f), onClick = { + torchEnabled.value = !torchEnabled.value + }) { + Text("Flash") + } + + Button(modifier = Modifier.weight(1f), onClick = { + cameraEnabled.value = !cameraEnabled.value + }) { + Text("Visibility") + } + } + + } + } + ) +} \ No newline at end of file diff --git a/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/BarcodeScannerMicroScan.kt b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/BarcodeScannerMicroScan.kt new file mode 100644 index 00000000..ad32f549 --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/BarcodeScannerMicroScan.kt @@ -0,0 +1,204 @@ +package io.scanbot.example.compose + +import android.util.Log +import androidx.annotation.OptIn +import androidx.camera.camera2.interop.ExperimentalCamera2Interop +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.material.Icon +import androidx.compose.material.TopAppBar +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.Button +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import androidx.navigation.NavOptions +import io.scanbot.common.* +import io.scanbot.demo.composeui.ui.theme.sbBrandColor +import io.scanbot.example.compose.components.* +import io.scanbot.sdk.geometry.* +import io.scanbot.sdk.ui_v2.barcode.* +import io.scanbot.sdk.ui_v2.common.* +import io.scanbot.sdk.ui_v2.common.components.* +import kotlin.random.* + +@OptIn(ExperimentalCamera2Interop::class) +@Composable +fun BarcodeScannerMicroScan(navController: NavHostController) { + + // Use these states to control camera, torch and zoom + val zoom = remember { mutableFloatStateOf(1.0f) } + val torchEnabled = remember { mutableStateOf(false) } + val cameraEnabled = remember { mutableStateOf(true) } + + // Unused in this example, but you may use it to + // enable/disable barcode scanning dynamically + val barcodeScanningEnabled = remember { mutableStateOf(true) } + + Scaffold( + modifier = Modifier.systemBarsPadding(), + topBar = { + TopAppBar( + title = { + Text( + text = "Micro Barcode Scan", + style = MaterialTheme.typography.titleLarge, + color = Color.White + ) + }, + backgroundColor = sbBrandColor, + navigationIcon = { + IconButton(onClick = { + navController.popBackStack() + }) { + Icon( + tint = Color.White, + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "" + ) + } + }) + }, + content = { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { + // @Tag("Scanning tiny barcodes") + BarcodeScannerCustomUI( + // Modify Size here: + modifier = Modifier + .fillMaxWidth() + .weight(1.0f), + // THIS IS IMPORTANT FOR MICR0 SCAN USECASE + minFocusDistanceLock = true, + finderConfiguration = FinderConfiguration( + verticalAlignment = Alignment.Top, + previewInsets = PaddingValues( + top = 32.dp, + bottom = 32.dp, + start = 16.dp, + end = 16.dp + ), + // Modify aspect ratio of the viewfinder here: + aspectRatio = AspectRatio(1.0, 1.0), + // Change viewfinder overlay color here: + overlayColor = Color.Transparent, + // Change viewfinder stroke color here: + strokeColor = Color.Transparent, + + // Alternatively, it is possible to provide a completely custom viewfinder content: + finderContent = { + // Custom cornered viewfinder. Can be replaced with any custom Composable + CorneredFinder() + }, + topContent = { + androidx.compose.material.Text( + "Custom Top Content", + color = Color.White, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + ) + }, + bottomContent = { + // You may add custom buttons and other elements here: + androidx.compose.material.Text( + "Custom Bottom Content", + color = Color.White, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + ) + } + ), + cameraEnabled = cameraEnabled.value, + barcodeScanningEnabled = barcodeScanningEnabled.value, + torchEnabled = torchEnabled.value, + zoomLevel = zoom.floatValue, + permissionView = { + // View that will be shown while camera permission is not granted + Box(modifier = Modifier.fillMaxSize()) { + Text( + "Camera permission is required to scan barcodes.", + modifier = Modifier.padding(16.dp), + color = Color.White + ) + } + }, + arPolygonView = { barcodesFlow -> + // Configure AR overlay polygon appearance inside CustomBarcodesArView if needed + CustomBarcodesArView( + barcodesFlow = barcodesFlow, + onBarcodeClick = { + // Handle barcode click on barcode from AR overlay if needed + } + ) + }, + onBarcodeScanningResult = { result -> + result.onSuccess { data -> + // Navigate to detail screen for the first barcode + val firstBarcode = data.barcodes.firstOrNull() + if (firstBarcode != null) { + navController.navigate( + Screen.BarcodeDetail.createRoute( + firstBarcode.text, + firstBarcode.format.name + ), + navOptions = NavOptions.Builder().setLaunchSingleTop(true) + .build() + ) + } + }.onFailure { error -> + Log.e( + "BarcodeScannerScreen3", + "Barcode scanning error: ${error.message}", + error + ) + } + }, + ) + // @EndTag("Scanning tiny barcodes") + Row { + androidx.compose.material.Button(modifier = Modifier.weight(1f), onClick = { + zoom.floatValue = 1.0f + Random.nextFloat() + }) { + Text("Zoom") + } + + Button(modifier = Modifier.weight(1f), onClick = { + torchEnabled.value = !torchEnabled.value + }) { + Text("Flash") + } + + Button(modifier = Modifier.weight(1f), onClick = { + cameraEnabled.value = !cameraEnabled.value + }) { + Text("Visibility") + } + } + + } + } + ) +} \ No newline at end of file diff --git a/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/BarcodeScannerMultiScan.kt b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/BarcodeScannerMultiScan.kt new file mode 100644 index 00000000..e366e9b3 --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/BarcodeScannerMultiScan.kt @@ -0,0 +1,152 @@ +package io.scanbot.example.compose + +import android.util.Log +import androidx.annotation.OptIn +import androidx.camera.camera2.interop.ExperimentalCamera2Interop +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.Icon +import androidx.compose.material.TopAppBar +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import io.scanbot.common.* +import io.scanbot.demo.composeui.ui.theme.* +import io.scanbot.example.compose.components.BarcodeItem +import io.scanbot.sdk.barcode.* +import io.scanbot.sdk.ui_v2.barcode.* +import io.scanbot.sdk.util.snap.* +import kotlinx.coroutines.launch + +@OptIn(ExperimentalCamera2Interop::class) +@Composable +fun BarcodeScannerMultiScan(navController: NavHostController) { + val zoom = remember { mutableFloatStateOf(1.0f) } + val torchEnabled = remember { mutableStateOf(false) } + val cameraEnabled = remember { mutableStateOf(true) } + val barcodeScanningEnabled = remember { mutableStateOf(true) } + val scannedBarcodes = remember { mutableStateListOf() } + val context = LocalContext.current + val soundController = remember { + SoundControllerImpl(context).apply { + // Prepare Scanbot beep sound controller: + setUp() + setVibrationEnabled(true) + setBleepEnabled(true) + } + } + val scope = rememberCoroutineScope() + Scaffold( + modifier = Modifier.systemBarsPadding(), + topBar = { + TopAppBar( + title = { + Text( + text = "Multiple Barcodes Scan", + style = MaterialTheme.typography.titleLarge, + color = Color.White + ) + }, + backgroundColor = sbBrandColor, + navigationIcon = { + IconButton(onClick = { + navController.popBackStack() + }) { + Icon( + tint = Color.White, + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "" + ) + } + }) + }, + content = { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { + // @Tag("Scanning multiple barcodes") + BarcodeScannerCustomUI( + modifier = Modifier.weight(1f), + cameraEnabled = cameraEnabled.value, + barcodeScanningEnabled = barcodeScanningEnabled.value, + torchEnabled = torchEnabled.value, + zoomLevel = zoom.floatValue, + finderConfiguration = null, + arPolygonView = { dataFlow -> + CustomBarcodesArView(dataFlow, { barcode -> + if (scannedBarcodes.none { it.textWithExtension == barcode.textWithExtension }) { + scannedBarcodes.add(barcode) + } else { + scannedBarcodes.removeAll { it.textWithExtension == barcode.textWithExtension } + } + }, onShouldHighlight = { barcode -> + scannedBarcodes.any { + it.textWithExtension == barcode.textWithExtension + } + }) + }, + permissionView = { + Box(modifier = Modifier.fillMaxSize()) { + Text( + "Camera permission is required to scan barcodes.", + modifier = Modifier.padding(16.dp), + color = Color.White + ) + } + }, + onBarcodeScanningResult = { result -> + result.onSuccess { result -> + result.barcodes.forEach { data -> + if (scannedBarcodes.none { it.text == data.text && it.format == data.format }) { + scope.launch { + // Provide sound and vibration feedback on scan: + soundController.playBleepSound() + } + scannedBarcodes.add(data) + } + } + }.onFailure { error -> + Log.e( + "BarcodeScannerScreen2", + "Barcode scanning error: ${error.message}", + error + ) + } + } + ) + // @EndTag("Scanning multiple barcodes") + LazyColumn( + modifier = Modifier + .fillMaxWidth() + .weight(1f) + .padding(8.dp), + ) { + items(scannedBarcodes.reversed()) { barcode -> + BarcodeItem(barcode) + } + } + } + }) +} diff --git a/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/BarcodeScannerSingleScan.kt b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/BarcodeScannerSingleScan.kt new file mode 100644 index 00000000..f25b6f20 --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/BarcodeScannerSingleScan.kt @@ -0,0 +1,202 @@ +package io.scanbot.example.compose + +import android.util.Log +import androidx.annotation.OptIn +import androidx.camera.camera2.interop.ExperimentalCamera2Interop +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.material.Icon +import androidx.compose.material.TopAppBar +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.Button +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import androidx.navigation.NavOptions +import io.scanbot.common.* +import io.scanbot.demo.composeui.ui.theme.sbBrandColor +import io.scanbot.example.compose.components.* +import io.scanbot.sdk.geometry.* +import io.scanbot.sdk.ui_v2.barcode.* +import io.scanbot.sdk.ui_v2.common.* +import io.scanbot.sdk.ui_v2.common.components.* +import kotlin.random.* + +@OptIn(ExperimentalCamera2Interop::class) +@Composable +fun BarcodeScannerSingleScan(navController: NavHostController) { + // Use these states to control camera, torch and zoom + val zoom = remember { mutableFloatStateOf(1.0f) } + val torchEnabled = remember { mutableStateOf(false) } + val cameraEnabled = remember { mutableStateOf(true) } + + // Unused in this example, but you may use it to + // enable/disable barcode scanning dynamically + val barcodeScanningEnabled = remember { mutableStateOf(true) } + + Scaffold( + modifier = Modifier.systemBarsPadding(), + topBar = { + TopAppBar( + title = { + Text( + text = "Barcode Single Scan", + style = MaterialTheme.typography.titleLarge, + color = Color.White + ) + }, + backgroundColor = sbBrandColor, + navigationIcon = { + IconButton(onClick = { + navController.popBackStack() + }) { + Icon( + tint = Color.White, + imageVector = Icons.AutoMirrored.Filled.ArrowBack, + contentDescription = "" + ) + } + }) + }, + content = { paddingValues -> + Column( + modifier = Modifier + .fillMaxSize() + .padding(paddingValues) + ) { + // @Tag("Scanning single barcode") + BarcodeScannerCustomUI( + // Modify Size here: + modifier = Modifier + .fillMaxWidth() + .weight(1.0f), + finderConfiguration = FinderConfiguration( + verticalAlignment = Alignment.Top, + previewInsets = PaddingValues( + top = 32.dp, + bottom = 32.dp, + start = 16.dp, + end = 16.dp + ), + // Modify aspect ratio of the viewfinder here: + aspectRatio = AspectRatio(1.0, 1.0), + // Change viewfinder overlay color here: + overlayColor = Color.Transparent, + // Change viewfinder stroke color here: + strokeColor = Color.Transparent, + + // Alternatively, it is possible to provide a completely custom viewfinder content: + finderContent = { + // Custom cornered viewfinder. Can be replaced with any custom Composable + CorneredFinder() + }, + topContent = { + androidx.compose.material.Text( + "Custom Top Content", + color = Color.White, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + ) + }, + bottomContent = { + // You may add custom buttons and other elements here: + androidx.compose.material.Text( + "Custom Bottom Content", + color = Color.White, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + ) + } + ), + cameraEnabled = cameraEnabled.value, + barcodeScanningEnabled = barcodeScanningEnabled.value, + torchEnabled = torchEnabled.value, + zoomLevel = zoom.floatValue, + permissionView = { + // View that will be shown while camera permission is not granted + Box(modifier = Modifier.fillMaxSize()) { + Text( + "Camera permission is required to scan barcodes.", + modifier = Modifier.padding(16.dp), + color = Color.White + ) + } + }, + arPolygonView = { barcodesFlow -> + // Configure AR overlay polygon appearance inside CustomBarcodesArView if needed + CustomBarcodesArView( + barcodesFlow = barcodesFlow, + onBarcodeClick = { + // Handle barcode click on barcode from AR overlay if needed + } + ) + }, + onBarcodeScanningResult = { result -> + result.onSuccess { data -> + // Navigate to detail screen for the first barcode + val firstBarcode = data.barcodes.firstOrNull() + if (firstBarcode != null) { + navController.navigate( + Screen.BarcodeDetail.createRoute( + firstBarcode.text, + firstBarcode.format.name + ), + navOptions = NavOptions.Builder().setLaunchSingleTop(true) + .build() + ) + } + }.onFailure { error -> + Log.e( + "BarcodeScannerScreen3", + "Barcode scanning error: ${error.message}", + error + ) + } + }, + ) + // @EndTag("Scanning single barcode") + + Row { + androidx.compose.material.Button(modifier = Modifier.weight(1f), onClick = { + zoom.floatValue = 1.0f + Random.nextFloat() + }) { + Text("Zoom") + } + + Button(modifier = Modifier.weight(1f), onClick = { + torchEnabled.value = !torchEnabled.value + }) { + Text("Flash") + } + + Button(modifier = Modifier.weight(1f), onClick = { + cameraEnabled.value = !cameraEnabled.value + }) { + Text("Visibility") + } + } + + } + } + ) +} \ No newline at end of file diff --git a/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/CustomBarcodesArView.kt b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/CustomBarcodesArView.kt new file mode 100644 index 00000000..810e4707 --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/CustomBarcodesArView.kt @@ -0,0 +1,86 @@ +package io.scanbot.example.compose + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.layout +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.dp +import io.scanbot.sdk.barcode.BarcodeItem +import io.scanbot.sdk.barcode.BarcodeScannerResult +import io.scanbot.sdk.barcode.textWithExtension +import io.scanbot.sdk.camera.FrameHandler +import io.scanbot.sdk.ui_v2.barcode.components.ar_tracking.ScanbotBarcodesArOverlay +import kotlinx.coroutines.flow.SharedFlow + +@Composable +fun CustomBarcodesArView( + barcodesFlow: SharedFlow?>, + onBarcodeClick: (BarcodeItem) -> Unit = {}, + onShouldHighlight: (BarcodeItem) -> Boolean = { false }, +) { + val density = LocalDensity.current + + ScanbotBarcodesArOverlay( + barcodesFlow, + getData = { barcodeItem -> barcodeItem.textWithExtension }, + getPolygonStyle = { defaultStyle, barcodeItem -> + // Customize polygon style here. + // You may use barcodeItem to apply different styles for different barcode types, etc. + defaultStyle.copy( + drawPolygon = true, + useFill = true, + useFillHighlighted = true, + cornerRadius = density.run { 20.dp.toPx() }, + cornerHighlightedRadius = density.run { 20.dp.toPx() }, + strokeWidth = density.run { 5.dp.toPx() }, + strokeHighlightedWidth = density.run { 5.dp.toPx() }, + strokeColor = Color.Green, + strokeHighlightedColor = Color.Red, + fillColor = Color.Green.copy(alpha = 0.3f), + fillHighlightedColor = Color.Red.copy(alpha = 0.3f), + shouldDrawShadows = false + ) + }, + shouldHighlight = { barcodeItem -> + // Here you can implement any custom logic. + // Return true to highlight a barcode with different style. + onShouldHighlight(barcodeItem) + }, + // Customize AR view for barcode data here if needed + view = { path, barcodeItem, data, shouldHighlight -> + // Implement custom view for barcode polygon if needed + Box(modifier = Modifier.layout { measurable, constraints -> + val placeable = measurable.measure(constraints); + + var rectF: Rect + path.getBounds().also { rectF = it } + + val width = placeable.width + val height = placeable.height + val x = rectF.center.x - width / 2 + val y = rectF.center.y + rectF.height / 2 + 10.dp.toPx() // place below the polygon + layout(width, height) { + placeable.placeRelative(x.toInt(), y.toInt()) + } + }) { + Text( + text = data, + color = if (shouldHighlight) Color.Red else Color.Green, + style = MaterialTheme.typography.body2, + modifier = Modifier + .background(Color.Black.copy(alpha = 0.5f)) + .padding(4.dp) + ) + } + }, + onClick = onBarcodeClick, + ) +} diff --git a/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/DocumentTestScreens.kt b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/DocumentTestScreens.kt new file mode 100644 index 00000000..7ce3d679 --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/DocumentTestScreens.kt @@ -0,0 +1,279 @@ +package io.scanbot.example.compose + +import android.graphics.Bitmap +import android.util.Log +import androidx.annotation.OptIn +import androidx.camera.camera2.interop.ExperimentalCamera2Interop +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.material3.Button +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import coil.compose.AsyncImage +import io.scanbot.common.onFailure +import io.scanbot.common.onSuccess +import io.scanbot.sdk.ScanbotSDK +import io.scanbot.sdk.documentscanner.DocumentDetectionStatus +import io.scanbot.sdk.documentscanner.DocumentScannerConfiguration +import io.scanbot.sdk.documentscanner.DocumentScannerParameters +import io.scanbot.sdk.geometry.AspectRatio +import io.scanbot.sdk.imagemanipulation.ScanbotSdkImageManipulator +import io.scanbot.sdk.imageprocessing.ScanbotSdkImageProcessor +import io.scanbot.sdk.ui_v2.common.CameraPermissionScreen +import io.scanbot.sdk.ui_v2.common.camera.TakePictureActionController +import io.scanbot.sdk.ui_v2.common.components.FinderConfiguration +import io.scanbot.sdk.ui_v2.common.components.ScanbotCameraPermissionView +import io.scanbot.sdk.ui_v2.common.components.ScanbotSnapButton +import io.scanbot.sdk.ui_v2.document.DocumentScannerCustomUI +import io.scanbot.sdk.ui_v2.document.components.camera.ScanbotDocumentArOverlay +import io.scanbot.sdk.ui_v2.document.screen.AutoSnappingConfiguration +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import kotlin.random.Random + +@Composable +@OptIn(ExperimentalCamera2Interop::class) +fun DocumentScannerScreen1(navController: NavHostController) { + val density = LocalDensity.current + Column(modifier = Modifier.systemBarsPadding()) { + val context = LocalContext.current + val sdk = remember { ScanbotSDK(context) } + val imageProcessor = remember { ScanbotSdkImageProcessor.create() } + val documentScanner = remember { sdk.createDocumentScanner().getOrNull() } + // Use these states to control camera, torch and zoom + val zoom = remember { mutableFloatStateOf(1.0f) } + val torchEnabled = remember { mutableStateOf(false) } + val cameraEnabled = remember { mutableStateOf(true) } + val scope = rememberCoroutineScope() + // Unused in this example, but you may use it to + // enable/disable barcode scanning dynamically + val scanningEnabled = remember { mutableStateOf(true) } + val autosnappingEnabled = remember { mutableStateOf(true) } + val cameraInProcessingState = remember { mutableStateOf(false) } + val scannedImage = remember { mutableStateOf(null) } + val takePictureActionController = + remember { mutableStateOf(null) } + val documentScanningStatus = + remember { mutableStateOf(DocumentDetectionStatus.ERROR_NOTHING_DETECTED) } + Box( + modifier = Modifier + .fillMaxWidth() + .weight(1.0f), + ) { + DocumentScannerCustomUI( + // Modify Size here: + modifier = Modifier.fillMaxSize(), + cameraEnabled = cameraEnabled.value, + documentScanningEnabled = scanningEnabled.value, + autoSnappingConfiguration = AutoSnappingConfiguration( + enabled = autosnappingEnabled.value + ), + torchEnabled = torchEnabled.value, + zoomLevel = zoom.floatValue, + documentScannerConfiguration = DocumentScannerConfiguration( + parameters = DocumentScannerParameters( + ignoreOrientationMismatch = true + ) + ), + /* finderConfiguration = FinderConfiguration( + //strokeColor = Color.Cyan, + verticalAlignment = Alignment.Top, + aspectRatio = AspectRatio( + 21.0, + 29.0 + ) // Use default aspect ratio matching document size + ),*/ + permissionView = { + // View that will be shown while camera permission is not granted + ScanbotCameraPermissionView( + modifier = Modifier.fillMaxSize(), + bottomContentPadding = 0.dp, + permissionConfig = CameraPermissionScreen(), + onClose = { + // Handle permission screen close if needed + }) + }, + arPolygonView = { dataFlow -> + ScanbotDocumentArOverlay( + dataFlow = dataFlow, + getProgressPolygonStyle = { defaultStyle -> + // Customize polygon style if needed + defaultStyle.copy(strokeWidth = 8f, strokeColor = Color.Green) + }) + }, + onTakePictureCalled = { + Log.d("DocumentScannerScreen1", "Take picture called") + cameraInProcessingState.value = true + }, + onTakePictureCanceled = { + Log.d("DocumentScannerScreen1", "Take picture canceled") + cameraInProcessingState.value = false + }, + onPictureSnapped = { imageRef, captureInfo -> + // WARNING: move all processing operation to view model with proper coroutine scope in real apps to avoid data loss during recompositions + scope.launch(Dispatchers.Default) { + // See https://docs.scanbot.io/android/data-capture-modules/detailed-setup-guide/result-api/ for details of result handling + // run detection and cropping on the captured image + documentScanner?.run(imageRef)?.onSuccess { documentData -> + val croppedImage = + imageProcessor.crop(imageRef, documentData.pointsNormalized) + .getOrReturn() // get the result of cropping operation or leave onSuccess if cropping failed + imageRef.close() // clear image ref resources + scannedImage.value = + imageProcessor.resize(croppedImage, 300).getOrReturn().toBitmap() + .getOrReturn() // get the result of cropping operation or leave onSuccess if cropping failed + croppedImage.close() // clear image ref resources + }?.onFailure { error -> + Log.e( + "DocumentScannerScreen", + "Document scanning error: ${error.message}" + ) + } + delay(1000) + cameraInProcessingState.value = + false // Picture is received, allow auto-snapping again or proceed further and allow image snap after some additional processing + } + }, + onTakePictureControllerCreated = { + takePictureActionController.value = it + }, + onAutoSnapping = { + // return true if auto-snapping should be consumed and not proceed to take picture + cameraInProcessingState.value // Disable auto-snapping while awaiting picture result after snap is triggered + }, + onDocumentScanningResult = { result -> + // Update document scanning status to show feedback in the UI if needed + documentScanningStatus.value = + result.getOrNull()?.status ?: DocumentDetectionStatus.ERROR_NOTHING_DETECTED + result.onSuccess { data -> + Log.d( + "BarcodeComposeClassic", + "Scanned polygon: ${ + data.pointsNormalized.map { + with(density) { + "(${it.x.toDp().value.toInt()}, ${it.y.toDp().value.toInt()})" + } + } + }", + ) + } + + }, + ) + ScanbotSnapButton( + modifier = Modifier + .height(100.dp) + .align(Alignment.BottomCenter), + // Disable button when scanning or auto-snapping is disabled + clickable = scanningEnabled.value && !cameraInProcessingState.value, + // Show indicator when camera is processing the last taken picture + autoCapture = autosnappingEnabled.value, + // animate progress when camera is processing the last taken picture + animateProgress = cameraInProcessingState.value, + // rotation speed of the outer big arc in auto-capture mode + bigArcSpeed = 3000, + // speed of the progress arc during processing + progressSpeed = 500, + // color of buttons inner component + innerColor = Color.Red, + // outer color of buttons outer component + outerColor = Color.White, + // outer circle line width + lineWidth = 1.dp, + // size of the empty space between inner and outer components + emptyLineWidth = 10.dp, + // initial angle of the 360 degrees rotating big arc + bigArcInitialAngle = 200f + ) { + takePictureActionController.value?.invoke() + } + + Box( + modifier = Modifier + .align(Alignment.Center) + ) { + Surface(color = Color.Green.copy(alpha = 0.3f)) { + Text( + text = instructionString(documentScanningStatus.value), + color = Color.White + ) + } + } + + if (scannedImage.value != null) { + AsyncImage( + model = scannedImage.value, + contentDescription = "ScannedImage", + modifier = Modifier + .height(100.dp) + .align(Alignment.BottomEnd) + ) + } + } + Column() { + Row { + androidx.compose.material.Button(modifier = Modifier.weight(1f), onClick = { + zoom.floatValue = 1.0f + Random.nextFloat() + }) { + Text("Zoom") + } + + Button(modifier = Modifier.weight(1f), onClick = { + torchEnabled.value = !torchEnabled.value + }) { + Text("Flash") + } + + Button(modifier = Modifier.weight(1f), onClick = { + cameraEnabled.value = !cameraEnabled.value + }) { + Text("Visibility") + } + } + Row { + androidx.compose.material.Button(modifier = Modifier.weight(1f), onClick = { + autosnappingEnabled.value = !autosnappingEnabled.value + }) { + Text("Autosnapping: ${autosnappingEnabled.value}") + } + } + } + } +} + +@Composable +private fun instructionString(status: DocumentDetectionStatus): String = when (status) { + DocumentDetectionStatus.NOT_ACQUIRED -> "Point the camera at a document" + DocumentDetectionStatus.OK -> "Hold still..." + DocumentDetectionStatus.OK_BUT_TOO_SMALL -> "Please move closer" + DocumentDetectionStatus.OK_BUT_BAD_ANGLES -> "Please align document with the preview edges" + DocumentDetectionStatus.OK_BUT_BAD_ASPECT_RATIO -> "Document aspect ratio mismatch" + DocumentDetectionStatus.OK_BUT_ORIENTATION_MISMATCH -> "Please rotate the device" + DocumentDetectionStatus.OK_BUT_OFF_CENTER -> "Please center the document in the camera preview" + DocumentDetectionStatus.OK_BUT_TOO_DARK -> " Please turn on more light" + DocumentDetectionStatus.ERROR_NOTHING_DETECTED -> "Document not detected" + DocumentDetectionStatus.ERROR_PARTIALLY_VISIBLE -> "Please fit the document fully in the preview" + DocumentDetectionStatus.ERROR_PARTIALLY_VISIBLE_TOO_CLOSE -> "Please move the device away from the document" + DocumentDetectionStatus.ERROR_TOO_DARK -> "Please turn on more light" + DocumentDetectionStatus.ERROR_TOO_NOISY -> "Image is too noisy" +} diff --git a/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/MainActivity.kt b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/MainActivity.kt new file mode 100644 index 00000000..b9927da8 --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/MainActivity.kt @@ -0,0 +1,234 @@ +package io.scanbot.example.compose + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.material.Divider +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.semantics.contentDescription +import androidx.compose.ui.semantics.semantics +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument +import io.scanbot.demo.composeui.ui.theme.ScanbotsdkandroidTheme +import io.scanbot.demo.composeui.ui.theme.sbBrandColor +import io.scanbot.sdk.ScanbotSDK +import io.scanbot.sdk.licensing.LicenseStatus +import java.net.URLDecoder +import java.net.URLEncoder +import java.nio.charset.StandardCharsets + +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + setContent { + MainApp() + } + } + + @Composable + fun MainApp() { + ScanbotsdkandroidTheme { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + val navController = rememberNavController() + AppNavHost(navController) + } + } + } +} + +sealed class Screen(val route: String) { + object Menu : Screen("menu") + object BarcodeScannerSingle : Screen("BarcodeScannerSingle") + object BarcodeScannerMulti : Screen("BarcodeScannerMulti") + object BarcodeScannerBatch : Screen("BarcodeScannerBatch") + object BarcodeScannerMicro : Screen("BarcodeScannerMicro") + object BarcodeScannerDistant : Screen("BarcodeScannerDistant") + object BarcodeFindAndPick : Screen("BarcodeFindAndPick") + + object DocumentScanner1 : Screen("DocumentScanner1") + object MrzScanner1 : Screen("MrzScanner1") + data class BarcodeDetail(val data: String, val format: String) : + Screen("barcodeDetail/{data}/{format}") { + companion object { + fun createRoute(data: String, format: String): String { + val encodedData = URLEncoder.encode(data, StandardCharsets.UTF_8.toString()) + val encodedFormat = URLEncoder.encode(format, StandardCharsets.UTF_8.toString()) + return "barcodeDetail/$encodedData/$encodedFormat" + } + } + } +} + +@Composable +fun AppNavHost(navController: NavHostController) { + NavHost(navController = navController, startDestination = Screen.Menu.route) { + composable(Screen.Menu.route) { MenuScreen(navController) } + composable(Screen.BarcodeScannerSingle.route) { BarcodeScannerSingleScan(navController) } + composable(Screen.BarcodeScannerMulti.route) { BarcodeScannerMultiScan(navController) } + composable(Screen.BarcodeScannerBatch.route) { BarcodeScannerBatchScan(navController) } + composable(Screen.BarcodeScannerMicro.route) { BarcodeScannerMicroScan(navController) } + composable(Screen.BarcodeScannerDistant.route) { BarcodeScannerDistantScan(navController) } + composable(Screen.BarcodeFindAndPick.route) { BarcodeFindAndPick(navController) } + composable(Screen.DocumentScanner1.route) { DocumentScannerScreen1(navController) } + composable(Screen.MrzScanner1.route) { MrzScannerScreen1(navController) } + composable( + route = "barcodeDetail/{data}/{format}", + arguments = listOf( + navArgument("data") { type = NavType.StringType }, + navArgument("format") { type = NavType.StringType } + ) + ) { backStackEntry -> + val data = + backStackEntry.arguments?.getString("data") + ?.let { URLDecoder.decode(it, StandardCharsets.UTF_8.toString()) } + ?: "" + val format = + backStackEntry.arguments?.getString("format") + ?.let { URLDecoder.decode(it, StandardCharsets.UTF_8.toString()) } + ?: "" + BarcodeDetailScreen(data, format) + } + } +} + +@Composable +fun MenuScreen(navController: NavHostController) { + val context = navController.context + val scanbotSdk: ScanbotSDK = remember { ScanbotSDK(context) } + + val menuItems = listOf( + Triple( + "Barcode Single Mode", + Screen.BarcodeScannerSingle.route, + "Barcode scanner with AR overlay" + ), + Triple( + "Barcodes Multi Mode", + Screen.BarcodeScannerMulti.route, + "Barcode multi scan mode without finder" + ), + Triple( + "Barcodes Batch Mode", + Screen.BarcodeScannerBatch.route, + "Barcode batch scan mode" + ), + Triple( + "Barcodes Micro Barcode Mode", + Screen.BarcodeScannerMicro.route, + "Barcode micro barcode scan mode" + ), + Triple( + "Barcodes Distant Barcode Mode", + Screen.BarcodeScannerDistant.route, + "Barcode distant barcode scan mode" + ), Triple( + "Barcodes Find and Pick Mode", + Screen.BarcodeFindAndPick.route, + "Find Specific barcode and pick it" + ), + Triple( + "Document Default Scanner", + Screen.DocumentScanner1.route, + "" + ), + Triple( + "Mrz Default Scanner", + Screen.MrzScanner1.route, + "" + ) + ) + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp) + .systemBarsPadding(), + verticalArrangement = Arrangement.Top, + horizontalAlignment = Alignment.Start + ) { + item() { + Text( + "Scanbot SDK Compose Customisable UI Demo", + style = MaterialTheme.typography.headlineMedium, + modifier = Modifier.padding(bottom = 24.dp) + ) + } + if (scanbotSdk.licenseInfo.status != LicenseStatus.OKAY) { + item() { + Surface( + modifier = Modifier.padding(bottom = 24.dp), + color = sbBrandColor, + shape = MaterialTheme.shapes.medium + ) { + Text( + "Warning: Scanbot SDK License is not valid! Current status: ${scanbotSdk.licenseInfo.status}", + style = MaterialTheme.typography.titleLarge.copy(color = Color.White), + modifier = Modifier.padding(horizontal = 16.dp, vertical = 8.dp) + ) + } + } + } + + item() { + Text( + "Scanners Examples".uppercase(), + textAlign = TextAlign.Start, + style = MaterialTheme.typography.titleLarge.copy(fontFamily = FontFamily.Monospace), + modifier = Modifier.padding(bottom = 16.dp) + ) + } + items(menuItems) { (title, route, description) -> + Box( + modifier = Modifier + .fillMaxWidth() + .clickable { navController.navigate(route) } + .semantics { + this.contentDescription = description + }, + contentAlignment = Alignment.CenterStart + ) { + Text( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 24.dp), + text = title, + style = MaterialTheme.typography.titleMedium + ) + Divider( + Modifier + .fillMaxWidth() + .align(Alignment.BottomCenter), + color = Color.LightGray.copy(alpha = 0.3f) + ) + } + } + } +} + diff --git a/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/MrzTestScreens.kt b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/MrzTestScreens.kt new file mode 100644 index 00000000..3b246b01 --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/MrzTestScreens.kt @@ -0,0 +1,135 @@ +package io.scanbot.example.compose + +import android.util.Log +import androidx.annotation.OptIn +import androidx.camera.camera2.interop.ExperimentalCamera2Interop +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.systemBarsPadding +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.navigation.NavHostController +import io.scanbot.common.onSuccess +import io.scanbot.sdk.ui_v2.common.CameraPermissionScreen +import io.scanbot.sdk.ui_v2.common.components.ScanbotCameraPermissionView +import io.scanbot.sdk.ui_v2.mrz.MrzScannerCustomUI +import kotlin.random.Random + +@OptIn(ExperimentalCamera2Interop::class) +@Composable +fun MrzScannerScreen1(navController: NavHostController) { + + Column(modifier = Modifier.systemBarsPadding()) { + // Use these states to control camera, torch and zoom + val zoom = remember { mutableFloatStateOf(1.0f) } + val torchEnabled = remember { mutableStateOf(false) } + val cameraEnabled = remember { mutableStateOf(true) } + + // Unused in this example, but you may use it to + // enable/disable barcode scanning dynamically + val scanningEnabled = remember { mutableStateOf(true) } + + MrzScannerCustomUI( + // Modify Size here: + modifier = Modifier + .fillMaxWidth() + .weight(1.0f), + /* finderConfiguration = FinderConfiguration( + verticalAlignment = Alignment.Top, + // Modify aspect ratio of the viewfinder here: + aspectRatio = AspectRatio(adjustedMrzThreeLinedFinderAspectRatio, 1.0), + // Alternatively, it is possible to provide a completely custom viewfinder content: + finderContent = { + // Box with border stroke color as an example of custom viewfinder content + Box( + modifier = Modifier + .fillMaxSize() + .background(Color.Transparent) + // Same but with rounded corners + .border( + 4.dp, + Color.Cyan, + shape = RoundedCornerShape( + 16.dp + ) + ) + ) { + + } + }, + topContent = { + androidx.compose.material.Text( + "Custom Top Content", + color = Color.White, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + ) + }, + bottomContent = { + // You may add custom buttons and other elements here: + androidx.compose.material.Text( + "Custom Bottom Content", + color = Color.White, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + ) + } + ),*/ + cameraEnabled = cameraEnabled.value, + mrzScanningEnabled = scanningEnabled.value, + torchEnabled = torchEnabled.value, + zoomLevel = zoom.floatValue, + permissionView = { + // View that will be shown while camera permission is not granted + ScanbotCameraPermissionView( + modifier = Modifier.fillMaxSize(), + bottomContentPadding = 0.dp, + permissionConfig = CameraPermissionScreen(), + onClose = { + // Handle permission screen close if needed + }) + }, + onMrzScanningResult = { result -> + result.onSuccess { data -> + // Apply feedback, sound, vibration here if needed + // ... + + // Handle scanned barcodes here (for example, show a dialog) + Log.d( + "MrzScannerScreen", "Scanned mrz: ${data.rawMRZ}" + ) + } + + }, + ) + Row { + androidx.compose.material.Button(modifier = Modifier.weight(1f), onClick = { + zoom.floatValue = 1.0f + Random.nextFloat() + }) { + Text("Zoom") + } + + Button(modifier = Modifier.weight(1f), onClick = { + torchEnabled.value = !torchEnabled.value + }) { + Text("Flash") + } + + Button(modifier = Modifier.weight(1f), onClick = { + cameraEnabled.value = !cameraEnabled.value + }) { + Text("Visibility") + } + } + } +} diff --git a/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/components/BarcodeItem.kt b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/components/BarcodeItem.kt new file mode 100644 index 00000000..214518d3 --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/components/BarcodeItem.kt @@ -0,0 +1,57 @@ +package io.scanbot.example.compose.components + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Surface +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.asImageBitmap +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.dp +import io.scanbot.sdk.barcode.BarcodeItem + +@Composable +fun BarcodeItem(barcode: BarcodeItem) { + Surface(color = MaterialTheme.colorScheme.surface) { + Row() { + Box( + modifier = Modifier + .padding(8.dp) + .size(64.dp) + ) { + val image = barcode.sourceImage?.toBitmap()?.getOrNull()?.asImageBitmap() + image?.let { + Image( + modifier = Modifier.size(48.dp), + bitmap = it, + contentDescription = "Barcode Thumbnail", + contentScale = ContentScale.Inside + ) + } + Column( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 4.dp) + ) { + Text( + "Data: ${barcode.text}", + style = MaterialTheme.typography.bodyLarge.copy(color = MaterialTheme.colorScheme.onSurface) + ) + Text( + "Format: ${barcode.format}", + style = MaterialTheme.typography.bodySmall.copy( + color = MaterialTheme.colorScheme.onSurface + ) + ) + } + } + } + } +} \ No newline at end of file diff --git a/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/components/FinderCornered.kt b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/components/FinderCornered.kt new file mode 100644 index 00000000..5ea04196 --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/components/FinderCornered.kt @@ -0,0 +1,86 @@ +package io.scanbot.example.compose.components + +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.CornerRadius +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.BlendMode +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Paint +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.StrokeCap +import androidx.compose.ui.graphics.drawscope.Stroke +import androidx.compose.ui.graphics.withSaveLayer +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import kotlin.math.min + +@Composable +fun CorneredFinder( + cornerRadius: Dp = 16.dp, + strokeWidth: Dp = 4.dp, + strokeColor: Color = Color.White, +) { + Canvas(modifier = Modifier.fillMaxSize(), onDraw = { + + val maxWidth = this.size.width + val maxHeight = this.size.height + val cornerRadiusPx = cornerRadius.toPx() + val strokeWidthPx = strokeWidth.toPx() + val cornerSize = min( + cornerRadiusPx + cornerRadiusPx / 2 + strokeWidthPx / 2, + min(maxWidth, maxHeight) / 2f + ) + + + val clearPath = Path().apply { + moveTo(cornerSize, 0f) + + lineTo(maxWidth - cornerSize, 0f) + moveTo(maxWidth, 0f) + moveTo(maxWidth, cornerSize) + + lineTo(maxWidth, maxHeight - cornerSize) + moveTo(maxWidth, maxHeight) + moveTo(maxWidth - cornerSize, maxHeight) + + lineTo(cornerSize, maxHeight) + moveTo(0f, maxHeight) + moveTo(0f, maxHeight - cornerSize) + lineTo(0f, cornerSize) + } + + this.drawContext.canvas.withSaveLayer( + bounds = Rect( + 0f, + 0f, + maxWidth, + maxHeight + ), + paint = Paint(), + ) { + drawRoundRect( + color = strokeColor, + topLeft = Offset(strokeWidthPx / 2, strokeWidthPx / 2), + size = Size(maxWidth - strokeWidthPx, maxHeight - strokeWidthPx), + style = Stroke( + width = strokeWidthPx + ), + cornerRadius = CornerRadius(cornerRadiusPx), + ) + + drawPath( + path = clearPath, color = Color.Black, style = Stroke( + width = strokeWidthPx * 4, + cap = StrokeCap.Butt, + ), + blendMode = BlendMode.Clear + ) + } + + }) +} diff --git a/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/doc_code_snippet/barcode/BarcodeArOverlaySnippet.kt b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/doc_code_snippet/barcode/BarcodeArOverlaySnippet.kt new file mode 100644 index 00000000..eb6c8a4a --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/doc_code_snippet/barcode/BarcodeArOverlaySnippet.kt @@ -0,0 +1,95 @@ +package io.scanbot.example.compose.doc_code_snippet.barcode + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.padding +import androidx.compose.material.MaterialTheme +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.layout +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.dp +import io.scanbot.sdk.barcode.* +import io.scanbot.sdk.camera.* +import io.scanbot.sdk.ui_v2.barcode.components.ar_tracking.* +import kotlinx.coroutines.flow.SharedFlow + + +// @Tag("Customisable barcode AR view") +@Composable +fun BarcodeArOverlaySnippet( + barcodesFlow: SharedFlow?>, +) { + val density = LocalDensity.current + + ScanbotBarcodesArOverlay( + barcodesFlow, + getData = { barcodeItem -> barcodeItem.textWithExtension }, + shouldHighlight = { barcodeItem -> + // Here you can implement any custom logic to decide whether to highlight a barcode with second style or not. + false + }, + getPolygonStyle = { defaultStyle, barcodeItem -> + // Customize polygon style here. + // You may use barcodeItem to apply different styles for different barcode types, etc. + defaultStyle.copy( + drawPolygon = true, + // Control whether to fill the polygon with fill color + useFill = true, + // Control whether to fill the polygon with fill color when highlighted + useFillHighlighted = true, + // Radius of the polygon corners in px + cornerRadius = density.run { 20.dp.toPx() }, + cornerHighlightedRadius = density.run { 20.dp.toPx() }, + // Width of the polygon stroke in px + strokeWidth = density.run { 5.dp.toPx() }, + // Width of the polygon stroke when highlighted in px + strokeHighlightedWidth = density.run { 5.dp.toPx() }, + // Color of the polygon stroke + strokeColor = Color.Green, + // Color of the polygon stroke when highlighted + strokeHighlightedColor = Color.Red, + // Fill color of the polygon + fillColor = Color.Green.copy(alpha = 0.3f), + // Fill color of the polygon when highlighted + fillHighlightedColor = Color.Red.copy(alpha = 0.3f), + shouldDrawShadows = false + ) + }, + // Customize AR view for barcode polygon here if needed + // For example, show barcode data below the polygon or display an icon, image etc. + view = { path, barcodeItem, data, shouldHighlight -> + // Implement custom view for barcode polygon if needed + Box(modifier = Modifier.layout { measurable, constraints -> + val placeable = measurable.measure(constraints); + + var rectF: Rect + path.getBounds().also { rectF = it } + + val width = placeable.width + val height = placeable.height + val x = rectF.center.x - width / 2 + val y = rectF.center.y + rectF.height / 2 + 10.dp.toPx() // place below the polygon + layout(width, height) { + placeable.placeRelative(x.toInt(), y.toInt()) + } + }) { + Text( + text = data, + color = if (shouldHighlight) Color.Red else Color.Green, + style = MaterialTheme.typography.body2, + modifier = Modifier + .background(Color.Black.copy(alpha = 0.5f)) + .padding(4.dp) + ) + } + }, + onClick = { + //handle click on polygon area representing a barcode + }, + ) +} +// @EndTag("Customisable barcode AR view") \ No newline at end of file diff --git a/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/doc_code_snippet/barcode/BarcodeScannerSnippet.kt b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/doc_code_snippet/barcode/BarcodeScannerSnippet.kt new file mode 100644 index 00000000..eefa913e --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/doc_code_snippet/barcode/BarcodeScannerSnippet.kt @@ -0,0 +1,100 @@ +package io.scanbot.example.compose.doc_code_snippet.barcode + +import android.util.Log +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import io.scanbot.common.* +import io.scanbot.example.compose.CustomBarcodesArView +import io.scanbot.sdk.barcode.textWithExtension +import io.scanbot.sdk.geometry.AspectRatio +import io.scanbot.sdk.ui_v2.barcode.BarcodeScannerCustomUI +import io.scanbot.sdk.ui_v2.common.CameraModule +import io.scanbot.sdk.ui_v2.common.CameraPermissionScreen +import io.scanbot.sdk.ui_v2.common.CameraPreviewMode +import io.scanbot.sdk.ui_v2.common.components.FinderConfiguration +import io.scanbot.sdk.ui_v2.common.components.ScanbotCameraPermissionView + +// @Tag("Detailed Barcode Scanner Composable") +@Composable +fun BarcodeScannerSnippet() { + // @Tag("Mutable states for camera control") + // Use these states to control camera, torch and zoom + val zoom = remember { mutableFloatStateOf(1.0f) } + val torchEnabled = remember { mutableStateOf(false) } + val cameraEnabled = remember { mutableStateOf(true) } + // Unused in this example, but you may use it to + // enable/disable barcode scanning dynamically + val scanningEnabled = remember { mutableStateOf(true) } + // @EndTag("Mutable states for camera control") + BarcodeScannerCustomUI( + // Modify Size here: + modifier = Modifier + .fillMaxSize(), + // See more details about FinderConfiguration in the FinderConfigurationSnippet.kt + finderConfiguration = FinderConfiguration( + verticalAlignment = Alignment.Top, + // Modify aspect ratio of the viewfinder here: + aspectRatio = AspectRatio(1.0, 1.0), + ), + // Enable or disable camera view here: + cameraEnabled = cameraEnabled.value, + // Select front or back camera here: + cameraModule = CameraModule.BACK, + // Set camera preview mode here. Possible values: FIT_IN, FILL_IN + cameraPreviewMode = CameraPreviewMode.FILL_IN, + // Enable or disable Barcode scanning here: + barcodeScanningEnabled = scanningEnabled.value, + // Enable or disable torch here: + torchEnabled = torchEnabled.value, + // Set zoom level. Range from 1.0 to 5.0: + zoomLevel = zoom.floatValue, + // Permission view that will be shown if camera permission is not granted + arPolygonView = { dataFlow-> + CustomBarcodesArView(dataFlow,{ + //handle click on barcode in AR + }) + }, + permissionView = { + // View that will be shown while camera permission is not granted + // Use custom layout of camera permission handling view here: + ScanbotCameraPermissionView( + modifier = Modifier.fillMaxSize(), + bottomContentPadding = 0.dp, + permissionConfig = CameraPermissionScreen(), + onClose = { + // Handle permission screen close if needed + }) + }, + onBarcodeScanningResult = { result -> + // See https://docs.scanbot.io/android/data-capture-modules/detailed-setup-guide/result-api/ for details of result handling + result.onSuccess { data -> + // Handle scanned barcodes here (for example, show a dialog or navigate to another screen) + Log.d( + "BarcodeScannerScreen", + "Scanned Barcodes: ${data.barcodes.joinToString { it.textWithExtension }}" + ) + }.onFailure { error -> + when (error) { + is Result.InvalidLicenseError -> { + Log.e( + "BarcodeScannerScreen", + "Barcodes scanning license error: ${error.message}" + ) + } + + else -> { + // Handle error here + Log.e("BarcodeScannerScreen", "Barcodes scanning error: ${error.message}") + } + } + } + } + ) +} +// @EndTag("Detailed Barcode Scanner Composable") diff --git a/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/doc_code_snippet/document/DocumentScannerSnippet.kt b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/doc_code_snippet/document/DocumentScannerSnippet.kt new file mode 100644 index 00000000..74694862 --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/doc_code_snippet/document/DocumentScannerSnippet.kt @@ -0,0 +1,333 @@ +package io.scanbot.example.compose.doc_code_snippet.document + +import android.graphics.Bitmap +import android.util.Log +import androidx.annotation.OptIn +import androidx.camera.camera2.interop.ExperimentalCamera2Interop +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import coil.compose.AsyncImage +import io.scanbot.common.mapSuccess +import io.scanbot.common.onFailure +import io.scanbot.common.onSuccess +import io.scanbot.sdk.ScanbotSDK +import io.scanbot.sdk.camera.FrameHandler +import io.scanbot.sdk.documentscanner.DocumentDetectionResult +import io.scanbot.sdk.documentscanner.DocumentDetectionStatus +import io.scanbot.sdk.documentscanner.DocumentScanner +import io.scanbot.sdk.documentscanner.DocumentScannerConfiguration +import io.scanbot.sdk.documentscanner.DocumentScannerParameters +import io.scanbot.sdk.image.ImageRef +import io.scanbot.sdk.imageprocessing.ScanbotSdkImageProcessor +import io.scanbot.sdk.ui_v2.common.CameraModule +import io.scanbot.sdk.ui_v2.common.CameraPermissionScreen +import io.scanbot.sdk.ui_v2.common.CameraPreviewMode +import io.scanbot.sdk.ui_v2.common.camera.TakePictureActionController +import io.scanbot.sdk.ui_v2.common.components.ScanbotCameraPermissionView +import io.scanbot.sdk.ui_v2.common.components.ScanbotSnapButton +import io.scanbot.sdk.ui_v2.document.DocumentScannerCustomUI +import io.scanbot.sdk.ui_v2.document.components.camera.ScanbotDocumentArOverlay +import io.scanbot.sdk.ui_v2.document.screen.AutoSnappingConfiguration +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.launch + +// @Tag("Detailed Document Scanner Composable") +@Composable +@OptIn(ExperimentalCamera2Interop::class) +fun DocumentScannerSnippet() { + val context = LocalContext.current + val sdk = remember { ScanbotSDK(context) } + val imageProcessor = remember { ScanbotSdkImageProcessor.create() } + val documentScanner = remember { sdk.createDocumentScanner().getOrNull() } + // @Tag("Mutable states for camera control") + // Use these states to control camera, torch and zoom + val zoom = remember { mutableFloatStateOf(1.0f) } + val torchEnabled = remember { mutableStateOf(false) } + val cameraEnabled = remember { mutableStateOf(true) } + val scope = rememberCoroutineScope() + // Unused in this example, but you may use it to + // enable/disable barcode scanning dynamically + val scanningEnabled = remember { mutableStateOf(true) } + val autosnappingEnabled = remember { mutableStateOf(false) } + val cameraInProcessingState = remember { mutableStateOf(false) } + val scannedImage = remember { mutableStateOf(null) } + val takePictureActionController = + remember { mutableStateOf(null) } + // @EndTag("Mutable states for camera control") + Box( + modifier = Modifier + .fillMaxWidth(), + ) { + DocumentScannerCustomUI( + // Modify Size here: + modifier = Modifier.fillMaxSize(), + // Enable or disable camera view. + cameraEnabled = cameraEnabled.value, + // Select front or back camera here: + cameraModule = CameraModule.BACK, + // Set camera preview mode here. Possible values: FIT_IN, FILL_IN + cameraPreviewMode = CameraPreviewMode.FILL_IN, + // Enable or disable document scanning process here: + documentScanningEnabled = scanningEnabled.value, + // Auto-snapping configuration + autoSnappingConfiguration = AutoSnappingConfiguration( + enabled = autosnappingEnabled.value, + //Changes sensitivity of auto-snapping. That is: the more sensitive it is the faster it shoots. + // Sensitivity must be within `[0..1]` range. A value of 1.0 triggers automatic capturing immediately, + // a value of 0.0 delays the automatic by 3 seconds. + sensitivity = 0.66f, + // Enable or disable shake detection to stop auto-snapping when device is shaken + shakeDetectionEnabled = true, + ), + // Enable or disable torch here: + torchEnabled = torchEnabled.value, + // Set zoom level. Range from 1.0 to 5.0: + zoomLevel = zoom.floatValue, + // Document scanner configuration to customize scanning parameters . Eg document apect ratios, size ratios, etc. + documentScannerConfiguration = DocumentScannerConfiguration( + parameters = DocumentScannerParameters( + ignoreOrientationMismatch = true + ) + ), + //uncomment to turn on the finder view with custom aspect ratio + // See more details about FinderConfiguration in the FinderConfigurationSnippet.kt + /*finderConfiguration = FinderConfiguration( + verticalAlignment = Alignment.CenterVertically, + // Modify aspect ratio of the viewfinder here: + aspectRatio = AspectRatio(21.0, 29.0), + ),*/ + // Permission view that will be shown if camera permission is not granted + permissionView = { + // View that will be shown while camera permission is not granted + ScanbotCameraPermissionView( + modifier = Modifier.fillMaxSize(), + bottomContentPadding = 0.dp, + permissionConfig = CameraPermissionScreen(), + onClose = { + // Handle permission screen close if needed + }) + }, + // AR Polygon overlay configuration + arPolygonView = { dataFlow -> + // AR Overlay composable that will be drawn over the camera preview + // remove this block to disable AR overlay + ScanbotDocumentArOverlay( + dataFlow = dataFlow, + // Enable or disable all polygons drawing here + drawPolygon = true, + isPolygonOk = { status -> + // Define which detection statuses are considered for AR polygon as OK + status == DocumentDetectionStatus.OK + }, + // lambda to customize polygon style when document is shown as OK + getOkPolygonStyle = { defaultStyle -> + // Customize polygon style if needed + defaultStyle.copy( + strokeWidth = 8f, + strokeColor = Color.Blue, + fillColor = Color.Blue.copy(alpha = 0.15f) + ) + }, + // lambda to customize polygon style when document is shown as Not OK + getNotOkPolygonStyle = { defaultStyle -> + // Customize polygon style if needed + defaultStyle.copy( + strokeWidth = 8f, + strokeColor = Color.Red, + fillColor = Color.Red.copy(alpha = 0.15f) + ) + }, + // lambda to customize polygon that shown on top of the OK polygon during auto-snapping countdown + getProgressPolygonStyle = { defaultStyle -> + // Customize polygon style if needed + defaultStyle.copy( + strokeWidth = 8f, + strokeColor = Color.Green, + fillColor = Color.Transparent + ) + }, + ) + }, + // Triggered when take picture is called (auto or manual) + onTakePictureCalled = { + Log.d("DocumentScannerScreen1", "Take picture called") + cameraInProcessingState.value = true + }, + // Triggered when take picture is canceled (auto or manual) + onTakePictureCanceled = { + Log.d("DocumentScannerScreen1", "Take picture canceled") + cameraInProcessingState.value = false + }, + // Triggered when picture is taken successfully and provides image for further processing + onPictureSnapped = { imageRef, captureInfo -> + // WARNING: move all processing operation to view model with proper coroutine scope in real apps to avoid data loss during recompositions + scope.launch(Dispatchers.Default) { + scannedImage.value = createPreview(documentScanner, imageRef, imageProcessor) + // Picture is received, allow auto-snapping again or proceed further and allow image snap after some additional processing + cameraInProcessingState.value = false + } + }, + // Provides TakePictureActionController to use for triggering picture taking manually + onTakePictureControllerCreated = { + takePictureActionController.value = it + }, + // Callback that is called right before auto-snapping should be triggered. + // Return false to allow auto-snapping, true to prevent it. + onAutoSnapping = { + // return true if auto-snapping should be consumed and not proceed to take picture + cameraInProcessingState.value // Disable auto-snapping while awaiting picture result after snap is triggered + }, + // Callback invoked after each frame with document scanning result. + onDocumentScanningResult = { result -> + result.onSuccess { data -> + // Handle scanned barcodes here (for example, show a dialog) + val points = data.pointsNormalized + val status = data.status + } + }, + ) + ScanbotSnapButton( + modifier = Modifier + .height(100.dp) + .align(Alignment.BottomCenter), + // Disable button when scanning or auto-snapping is disabled + clickable = scanningEnabled.value && !cameraInProcessingState.value, + // Show indicator that camera in auto-snapping mode + autoCapture = autosnappingEnabled.value, + // Animate progress when camera is processing the last taken picture + animateProgress = cameraInProcessingState.value, + ) { + takePictureActionController.value?.invoke() + } + + // Simple view that shows scanned image preview + if (scannedImage.value != null) { + AsyncImage( + model = scannedImage.value, + contentDescription = "ScannedImage", + modifier = Modifier + .height(100.dp) + .align(Alignment.BottomEnd) + ) + } + } +} + +private fun createPreview( + documentScanner: DocumentScanner?, + imageRef: ImageRef, + imageProcessor: ScanbotSdkImageProcessor, +): Bitmap? { + // See https://docs.scanbot.io/android/data-capture-modules/detailed-setup-guide/result-api/ for details of result handling + // run detection and cropping on the captured image + return documentScanner?.run(imageRef)?.mapSuccess { documentData -> + val croppedImage = + imageProcessor.crop(imageRef, documentData.pointsNormalized) + .getOrReturn() // get the result of cropping operation or leave onSuccess if cropping failed + imageRef.close() // clear image ref resources + + val downscaledCrop = imageProcessor.resize(croppedImage, 300).getOrReturn().toBitmap() + .getOrReturn() // get the result of cropping operation or leave onSuccess if cropping failed + croppedImage.close() // clear image ref resources + downscaledCrop + }?.onFailure { error -> + Log.e( + "DocumentScannerScreen", + "Document scanning error: ${error.message}" + ) + }?.getOrNull() +} +// @EndTag("Detailed Document Scanner Composable") + +// @Tag("Detailed Snap Button Composable") +@Composable +fun SnapButtonSnippet() { + ScanbotSnapButton( + modifier = Modifier + .height(100.dp), + // Disable button when scanning or auto-snapping is disabled + clickable = true, + // Show indicator that camera in auto-snapping mode + autoCapture = false, + // Animate progress when camera is processing the last taken picture + // Need to be true to show progress animation. Use mutable state to control it. + animateProgress = false, + // Rotation speed of the outer big arc in auto-capture mode + bigArcSpeed = 3000, + // Speed of the progress arc during processing + progressSpeed = 400, + // Color of buttons inner component + innerColor = Color.White, + // Outer color of buttons outer component + outerColor = Color.Red, + // Color of the outer Arc + lineWidth = 5.dp, + // Size of the empty space between inner and outer components + emptyLineWidth = 10.dp, + // Sweep angle of the outer arc + arcSweepAngle = 270f, + // Initial angle of the 360 degrees rotating big arc + bigArcInitialAngle = 200f, + // Scale factor applied to the button when pressed + snapButtonPressedSize = 1.2f + ) { + // Handle button click here + } +} +// @EndTag("Detailed Snap Button Composable") + +// @Tag(" Document AR overlay Snippet") +@Composable +fun DocumentArOverlaySnippet(dataFlow: SharedFlow>) { + // AR Overlay composable that will be drawn over the camera preview + // remove this block to disable AR overlay + ScanbotDocumentArOverlay( + dataFlow = dataFlow, + // Enable or disable all polygons drawing here + drawPolygon = true, + isPolygonOk = { status -> + // Define which detection statuses are considered for AR polygon as OK + status == DocumentDetectionStatus.OK + }, + // lambda to customize polygon style when document is shown as OK + getOkPolygonStyle = { defaultStyle -> + // Customize polygon style if needed + defaultStyle.copy( + strokeWidth = 8f, + strokeColor = Color.Blue, + fillColor = Color.Blue.copy(alpha = 0.15f) + ) + }, + // lambda to customize polygon style when document is shown as Not OK + getNotOkPolygonStyle = { defaultStyle -> + // Customize polygon style if needed + defaultStyle.copy( + strokeWidth = 8f, + strokeColor = Color.Red, + fillColor = Color.Red.copy(alpha = 0.15f) + ) + }, + // lambda to customize polygon that shown on top of the OK polygon during auto-snapping countdown + getProgressPolygonStyle = { defaultStyle -> + // Customize polygon style if needed + defaultStyle.copy( + strokeWidth = 8f, + strokeColor = Color.Green, + fillColor = Color.Transparent + ) + }, + ) +} \ No newline at end of file diff --git a/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/doc_code_snippet/finder/FinderConfigurationSnippet.kt b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/doc_code_snippet/finder/FinderConfigurationSnippet.kt new file mode 100644 index 00000000..6f8d89d0 --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/doc_code_snippet/finder/FinderConfigurationSnippet.kt @@ -0,0 +1,98 @@ +package io.scanbot.example.compose.doc_code_snippet.finder + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import io.scanbot.sdk.geometry.AspectRatio +import io.scanbot.sdk.ui_v2.common.components.FinderConfiguration + + +// @Tag("Detailed Finder Configuration") +@Composable +fun FinderConfigurationSnippet() { + FinderConfiguration( + // align the viewfinder to the top free space of the Camera Preview. Means that it will be close to the preview edge minus the previewInsets + verticalAlignment = Alignment.Top, + // align the viewfinder to the free horizontal space of the Camera Preview + horizontalAlignment = Alignment.CenterHorizontally, + // Insets from the edges of the camera preview to the viewfinder. + previewInsets = PaddingValues( + top = 32.dp, + bottom = 32.dp, + start = 16.dp, + end = 16.dp + ), + // Aspect ratio of the viewfinder window: + aspectRatio = AspectRatio(1.0, 1.0), + // Change viewfinder overlay color here: + overlayColor = Color.Black.copy(alpha = 0.3f), + // Viewfinder stroke color here, default is Transparent: + strokeColor = Color.White, + // Viewfinder stroke width here: + strokeWidth = 2.dp, + // radius for rounded corners of the viewfinder window: + cornerRadius = 8.dp, + // Limit the maximum width of the viewfinder window on the preview. This parameter work with aspect ratio to define the final size of the viewfinder. + //preferredMaxWidth = 300.dp, + // Limit the maximum height of the viewfinder window on the preview. This parameter work with aspect ratio to define the final size of the viewfinder. + //preferredMaxHeight = 52.dp, + // Composable area that is inserted inside the viewfinder window: + finderContent = { + // Box with border stroke color as an example of custom viewfinder content + Box( + modifier = Modifier + .fillMaxSize() + .background(Color.Transparent) + // Same but with rounded corners + .border( + 4.dp, + Color.Cyan, + shape = RoundedCornerShape( + 16.dp + ) + ) + ) { + + } + }, + // Composable area that are inserted between the viewfinder window and the top edge of the camera view: + topContent = { + Text( + "Custom Top Content", + color = Color.White, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + ) + }, + // Composable area that are inserted between the viewfinder window and the bottom edge of the camera view: + bottomContent = { + // You may add custom components and other elements here: + Text( + "Custom Bottom Content", + color = Color.White, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + ) + }, + bottomLayer = { + // Draw something between the viewfinder and camera preview layers + }, + topLayer = { + // Draw something above the viewfinder layer if needed + } + ) +} +// @EndTag("Detailed Finder Configuration") \ No newline at end of file diff --git a/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/doc_code_snippet/mrz/MrzScannerSnippet.kt b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/doc_code_snippet/mrz/MrzScannerSnippet.kt new file mode 100644 index 00000000..ec9bdc5a --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/doc_code_snippet/mrz/MrzScannerSnippet.kt @@ -0,0 +1,85 @@ +package io.scanbot.example.compose.doc_code_snippet.mrz + +import android.util.Log +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.runtime.mutableFloatStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import io.scanbot.common.* +import io.scanbot.sdk.geometry.* +import io.scanbot.sdk.ui_v2.common.* +import io.scanbot.sdk.ui_v2.common.components.* +import io.scanbot.sdk.ui_v2.mrz.* + +// @Tag("Detailed MRZ Scanner Composable") +@Composable +fun MrzScannerSnippet() { + //@Tag("Mutable states for camera control") + // Use these states to control camera, torch and zoom + val zoom = remember { mutableFloatStateOf(1.0f) } + val torchEnabled = remember { mutableStateOf(false) } + val cameraEnabled = remember { mutableStateOf(true) } + // Unused in this example, but you may use it to + // enable/disable barcode scanning dynamically + val scanningEnabled = remember { mutableStateOf(true) } + // @EndTag("Mutable states for camera control") + MrzScannerCustomUI( + // Modify Size here: + modifier = Modifier + .fillMaxSize(), + // See more details about FinderConfiguration in the FinderConfigurationSnippet.kt + finderConfiguration = FinderConfiguration( + verticalAlignment = Alignment.CenterVertically, + // Modify aspect ratio of the viewfinder here: + aspectRatio = AspectRatio(adjustedMrzThreeLinedFinderAspectRatio, 1.0), + ), + cameraEnabled = cameraEnabled.value, + // Select front or back camera here: + cameraModule = CameraModule.BACK, + // Set camera preview mode here. Possible values: FIT_IN, FILL_IN + cameraPreviewMode = CameraPreviewMode.FILL_IN, + // Enable or disable MRZ scanning here: + mrzScanningEnabled = scanningEnabled.value, + // Enable or disable torch here: + torchEnabled = torchEnabled.value, + // Set zoom level here: + zoomLevel = zoom.floatValue, + // Permission view that will be shown if camera permission is not granted + permissionView = { + // View that will be shown while camera permission is not granted + // Use custom layout of camera permission handling view here: + ScanbotCameraPermissionView( + modifier = Modifier.fillMaxSize(), + bottomContentPadding = 0.dp, + permissionConfig = CameraPermissionScreen(), + onClose = { + // Handle permission screen close if needed + }) + }, + onMrzScanningResult = { result -> + // See https://docs.scanbot.io/android/data-capture-modules/detailed-setup-guide/result-api/ for details of result handling + result.onSuccess { data -> + // Handle scanned barcodes here (for example, show a dialog or navigate to another screen) + Log.d( + "MrzScannerScreen", "Scanned mrz: ${data.rawMRZ}" + ) + }.onFailure { error -> + when (error) { + is Result.InvalidLicenseError -> { + Log.e("MrzScannerScreen", "MRZ scanning license error: ${error.message}") + } + + else -> { + // Handle error here + Log.e("MrzScannerScreen", "MRZ scanning error: ${error.message}") + } + } + } + } + ) +} +// @EndTag("Detailed MRZ Scanner Composable") diff --git a/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/ui/theme/Color.kt b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/ui/theme/Color.kt new file mode 100644 index 00000000..00e53801 --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/ui/theme/Color.kt @@ -0,0 +1,11 @@ +package io.scanbot.demo.composeui.ui.theme + +import androidx.compose.ui.graphics.Color + +val sbPrimary = Color(0xFF4b4f52) +val colorPrimaryDark = Color(0xFF000000) +val colorAccent = Color(0xFF00b92e) +val backgroundColor = Color(0xFF4b4f52) +val sbBrandColor = Color(0xFFc8193c) +val sheetColor = Color(0xFF7f8487) + diff --git a/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/ui/theme/Theme.kt b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/ui/theme/Theme.kt new file mode 100644 index 00000000..037aa728 --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/ui/theme/Theme.kt @@ -0,0 +1,55 @@ +package io.scanbot.demo.composeui.ui.theme + +import android.os.Build +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.dynamicDarkColorScheme +import androidx.compose.material3.dynamicLightColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext + +private val DarkColorScheme = darkColorScheme( + primary = sbPrimary, + secondary = sbPrimary, + tertiary = sbPrimary +) + +private val LightColorScheme = lightColorScheme( + primary = sbPrimary, + secondary = sbPrimary, + tertiary = sbPrimary, + background = backgroundColor, + surface = sheetColor, + onPrimary = Color.White, + onSecondary = Color.White, + onTertiary = Color.White, + onBackground = Color(0xFF1C1B1F), + onSurface = Color(0xFF1C1B1F), + ) + +@Composable +fun ScanbotsdkandroidTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + // Dynamic color is available on Android 12+ + dynamicColor: Boolean = true, + content: @Composable () -> Unit, +) { + val colorScheme = when { + dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> { + val context = LocalContext.current + if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context) + } + + darkTheme -> DarkColorScheme + else -> LightColorScheme + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} diff --git a/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/ui/theme/Type.kt b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/ui/theme/Type.kt new file mode 100644 index 00000000..a99a2fe4 --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/java/io/scanbot/example/compose/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package io.scanbot.demo.composeui.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) + /* Other default text styles to override + titleLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 22.sp, + lineHeight = 28.sp, + letterSpacing = 0.sp + ), + labelSmall = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Medium, + fontSize = 11.sp, + lineHeight = 16.sp, + letterSpacing = 0.5.sp + ) + */ +) diff --git a/compose-customisable-ui-example/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/compose-customisable-ui-example/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 00000000..1f6bb290 --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/compose-customisable-ui-example/app/src/main/res/drawable/divider_menu_items.xml b/compose-customisable-ui-example/app/src/main/res/drawable/divider_menu_items.xml new file mode 100644 index 00000000..32ee0d2f --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/res/drawable/divider_menu_items.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/compose-customisable-ui-example/app/src/main/res/drawable/ic_broken_image_white_24dp.xml b/compose-customisable-ui-example/app/src/main/res/drawable/ic_broken_image_white_24dp.xml new file mode 100644 index 00000000..32316e1e --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/res/drawable/ic_broken_image_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/compose-customisable-ui-example/app/src/main/res/drawable/ic_launcher_background.xml b/compose-customisable-ui-example/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 00000000..0d025f9b --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/compose-customisable-ui-example/app/src/main/res/drawable/warning_shape.xml b/compose-customisable-ui-example/app/src/main/res/drawable/warning_shape.xml new file mode 100644 index 00000000..e01bd4fa --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/res/drawable/warning_shape.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/compose-customisable-ui-example/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/compose-customisable-ui-example/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 00000000..4ae7d123 --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/compose-customisable-ui-example/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/compose-customisable-ui-example/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 00000000..4ae7d123 --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/compose-customisable-ui-example/app/src/main/res/mipmap-hdpi/ic_launcher.png b/compose-customisable-ui-example/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..2cd5ef66 Binary files /dev/null and b/compose-customisable-ui-example/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/compose-customisable-ui-example/app/src/main/res/mipmap-hdpi/ic_launcher_background.png b/compose-customisable-ui-example/app/src/main/res/mipmap-hdpi/ic_launcher_background.png new file mode 100644 index 00000000..b883b839 Binary files /dev/null and b/compose-customisable-ui-example/app/src/main/res/mipmap-hdpi/ic_launcher_background.png differ diff --git a/compose-customisable-ui-example/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/compose-customisable-ui-example/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..529f470d Binary files /dev/null and b/compose-customisable-ui-example/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/compose-customisable-ui-example/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/compose-customisable-ui-example/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 00000000..d46c178c Binary files /dev/null and b/compose-customisable-ui-example/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/compose-customisable-ui-example/app/src/main/res/mipmap-mdpi/ic_launcher.png b/compose-customisable-ui-example/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..6db5e0f2 Binary files /dev/null and b/compose-customisable-ui-example/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/compose-customisable-ui-example/app/src/main/res/mipmap-mdpi/ic_launcher_background.png b/compose-customisable-ui-example/app/src/main/res/mipmap-mdpi/ic_launcher_background.png new file mode 100644 index 00000000..a65a22b7 Binary files /dev/null and b/compose-customisable-ui-example/app/src/main/res/mipmap-mdpi/ic_launcher_background.png differ diff --git a/compose-customisable-ui-example/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/compose-customisable-ui-example/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..4ab3e5e7 Binary files /dev/null and b/compose-customisable-ui-example/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/compose-customisable-ui-example/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/compose-customisable-ui-example/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 00000000..4409f93e Binary files /dev/null and b/compose-customisable-ui-example/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/compose-customisable-ui-example/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/compose-customisable-ui-example/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..7ea2827c Binary files /dev/null and b/compose-customisable-ui-example/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/compose-customisable-ui-example/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png b/compose-customisable-ui-example/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png new file mode 100644 index 00000000..4f1aeb6e Binary files /dev/null and b/compose-customisable-ui-example/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png differ diff --git a/compose-customisable-ui-example/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/compose-customisable-ui-example/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..c66d6b35 Binary files /dev/null and b/compose-customisable-ui-example/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/compose-customisable-ui-example/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/compose-customisable-ui-example/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 00000000..644134cb Binary files /dev/null and b/compose-customisable-ui-example/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/compose-customisable-ui-example/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/compose-customisable-ui-example/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..7322aa33 Binary files /dev/null and b/compose-customisable-ui-example/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/compose-customisable-ui-example/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png b/compose-customisable-ui-example/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png new file mode 100644 index 00000000..9295e62e Binary files /dev/null and b/compose-customisable-ui-example/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png differ diff --git a/compose-customisable-ui-example/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/compose-customisable-ui-example/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..eda5f168 Binary files /dev/null and b/compose-customisable-ui-example/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/compose-customisable-ui-example/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/compose-customisable-ui-example/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..01f709ec Binary files /dev/null and b/compose-customisable-ui-example/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/compose-customisable-ui-example/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/compose-customisable-ui-example/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..566a5866 Binary files /dev/null and b/compose-customisable-ui-example/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/compose-customisable-ui-example/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png b/compose-customisable-ui-example/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png new file mode 100644 index 00000000..8c3de908 Binary files /dev/null and b/compose-customisable-ui-example/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png differ diff --git a/compose-customisable-ui-example/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/compose-customisable-ui-example/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 00000000..1ef923fc Binary files /dev/null and b/compose-customisable-ui-example/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/compose-customisable-ui-example/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/compose-customisable-ui-example/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 00000000..b7c965dd Binary files /dev/null and b/compose-customisable-ui-example/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/compose-customisable-ui-example/app/src/main/res/values-v21/styles.xml b/compose-customisable-ui-example/app/src/main/res/values-v21/styles.xml new file mode 100644 index 00000000..a15bb1a6 --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/res/values-v21/styles.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + diff --git a/compose-customisable-ui-example/app/src/main/res/values/colors.xml b/compose-customisable-ui-example/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..ff8d61bd --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/res/values/colors.xml @@ -0,0 +1,11 @@ + + + #4b4f52 + #000000 + #00b92e + #4b4f52 + #c4c4c4 + #7f8487 + #c8193c + #1effffff + diff --git a/compose-customisable-ui-example/app/src/main/res/values/dimens.xml b/compose-customisable-ui-example/app/src/main/res/values/dimens.xml new file mode 100644 index 00000000..ae5c3551 --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/res/values/dimens.xml @@ -0,0 +1,4 @@ + + + 400dp + diff --git a/compose-customisable-ui-example/app/src/main/res/values/snippets_res.xml b/compose-customisable-ui-example/app/src/main/res/values/snippets_res.xml new file mode 100644 index 00000000..279e51ea --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/res/values/snippets_res.xml @@ -0,0 +1,11 @@ + + + + + + + + + Clear + Submit + \ No newline at end of file diff --git a/compose-customisable-ui-example/app/src/main/res/values/strings.xml b/compose-customisable-ui-example/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..234f70fb --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/res/values/strings.xml @@ -0,0 +1,4 @@ + + Scanbot Compose UI Example + Scanbot Compose UI Example + diff --git a/compose-customisable-ui-example/app/src/main/res/values/styles.xml b/compose-customisable-ui-example/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..a15bb1a6 --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/res/values/styles.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + diff --git a/compose-customisable-ui-example/app/src/main/res/xml/provider_paths.xml b/compose-customisable-ui-example/app/src/main/res/xml/provider_paths.xml new file mode 100644 index 00000000..ffa74ab5 --- /dev/null +++ b/compose-customisable-ui-example/app/src/main/res/xml/provider_paths.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/compose-customisable-ui-example/applyMinifyEnabledPatch.sh b/compose-customisable-ui-example/applyMinifyEnabledPatch.sh new file mode 100755 index 00000000..8ff6bee4 --- /dev/null +++ b/compose-customisable-ui-example/applyMinifyEnabledPatch.sh @@ -0,0 +1,4 @@ +#!/bin/sh + +cd .. +sh applyMinifyEnabledPatch.sh \ No newline at end of file diff --git a/compose-customisable-ui-example/build.gradle b/compose-customisable-ui-example/build.gradle new file mode 100644 index 00000000..16f7a52d --- /dev/null +++ b/compose-customisable-ui-example/build.gradle @@ -0,0 +1,13 @@ + +plugins { + id "com.android.application" version '8.9.0' apply false + id "org.jetbrains.kotlin.android" version "2.2.20" apply false + id "org.jetbrains.kotlin.plugin.compose" version "2.2.20" apply false +} + +allprojects { + configurations.all { + // Hack to let Gradle pickup latest SNAPSHOTS + resolutionStrategy.cacheChangingModulesFor 0, 'seconds' + } +} diff --git a/compose-customisable-ui-example/gradle.properties b/compose-customisable-ui-example/gradle.properties new file mode 100644 index 00000000..2d817b8f --- /dev/null +++ b/compose-customisable-ui-example/gradle.properties @@ -0,0 +1,21 @@ +# 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. +# Default value: -Xmx10248m -XX:MaxPermSize=256m +# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 + +# 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 +android.enableJetifier=true +android.useAndroidX=true +org.gradle.jvmargs=-Xmx3072M -Dkotlin.daemon.jvm.options="-Xmx3072M" diff --git a/compose-customisable-ui-example/gradle/wrapper/gradle-wrapper.jar b/compose-customisable-ui-example/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..8c0fb64a Binary files /dev/null and b/compose-customisable-ui-example/gradle/wrapper/gradle-wrapper.jar differ diff --git a/compose-customisable-ui-example/gradle/wrapper/gradle-wrapper.properties b/compose-customisable-ui-example/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..c190f9a7 --- /dev/null +++ b/compose-customisable-ui-example/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Apr 24 08:45:12 CEST 2019 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip diff --git a/compose-customisable-ui-example/gradlew b/compose-customisable-ui-example/gradlew new file mode 100755 index 00000000..91a7e269 --- /dev/null +++ b/compose-customisable-ui-example/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/compose-customisable-ui-example/gradlew.bat b/compose-customisable-ui-example/gradlew.bat new file mode 100644 index 00000000..8a0b282a --- /dev/null +++ b/compose-customisable-ui-example/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/compose-customisable-ui-example/settings.gradle.kts b/compose-customisable-ui-example/settings.gradle.kts new file mode 100644 index 00000000..e67bcf5f --- /dev/null +++ b/compose-customisable-ui-example/settings.gradle.kts @@ -0,0 +1,20 @@ +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + mavenCentral() + google() + maven(url = "https://nexus.scanbot.io/nexus/content/repositories/releases/") + maven(url = "https://nexus.scanbot.io/nexus/content/repositories/snapshots/") + } +} + +include(":app") +rootProject.name = "Scanbot Compose Customisable UI example"