diff --git a/data-capture-ready-to-use-ui-example/app/build.gradle b/data-capture-ready-to-use-ui-example/app/build.gradle
index 46a5fec7..1762bffa 100644
--- a/data-capture-ready-to-use-ui-example/app/build.gradle
+++ b/data-capture-ready-to-use-ui-example/app/build.gradle
@@ -43,6 +43,11 @@ android {
buildFeatures {
viewBinding = true
buildConfig = true
+ compose = true
+ }
+
+ composeOptions {
+ kotlinCompilerExtensionVersion = "1.5.13"
}
packagingOptions {
diff --git a/data-capture-ready-to-use-ui-example/app/src/main/AndroidManifest.xml b/data-capture-ready-to-use-ui-example/app/src/main/AndroidManifest.xml
index eb9bdb20..0a7ae8f9 100644
--- a/data-capture-ready-to-use-ui-example/app/src/main/AndroidManifest.xml
+++ b/data-capture-ready-to-use-ui-example/app/src/main/AndroidManifest.xml
@@ -21,7 +21,7 @@
-
+
(R.id.mrz_camera_compose_ui).setOnClickListener {
+ val intent = Intent(this, MrzScannerComposeActivity::class.java)
+ startActivity(intent)
+ }
+
findViewById(R.id.vin_scanner_default_ui).setOnClickListener {
val vinScannerConfiguration = VinScannerConfiguration()
diff --git a/data-capture-ready-to-use-ui-example/app/src/main/java/io/scanbot/example/MrzScannerComposeActivity.kt b/data-capture-ready-to-use-ui-example/app/src/main/java/io/scanbot/example/MrzScannerComposeActivity.kt
new file mode 100644
index 00000000..c8cbe615
--- /dev/null
+++ b/data-capture-ready-to-use-ui-example/app/src/main/java/io/scanbot/example/MrzScannerComposeActivity.kt
@@ -0,0 +1,47 @@
+package io.scanbot.example
+
+import android.content.Intent
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import androidx.camera.camera2.interop.ExperimentalCamera2Interop
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.statusBarsPadding
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.ComposeView
+import androidx.core.view.WindowCompat
+import io.scanbot.example.compose.MrzScannerViewClassic
+import io.scanbot.example.compose.MrzViewModel
+import io.scanbot.example.fragments.MRZDialogFragment
+import io.scanbot.sdk.ScanbotSDK
+import io.scanbot.sdk.mrz.MrzScannerResult
+import io.scanbot.sdk.ui_v2.common.CameraConfiguration
+
+
+class MrzScannerComposeActivity : AppCompatActivity() {
+ @ExperimentalCamera2Interop
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(ComposeView(this).apply {
+ setContent {
+ MrzScannerViewClassic(
+ modifier = Modifier.fillMaxSize().statusBarsPadding(),
+ viewModel = MrzViewModel(CameraConfiguration(), ScanbotSDK(this@MrzScannerComposeActivity)),
+ onMrz = { mrzRecognitionResult ->
+ showMrzDialog(mrzRecognitionResult)
+ },
+ onScannerClosed = {
+ finish()
+ },
+ )
+ }
+ })
+
+ }
+
+ private fun showMrzDialog(mrzRecognitionResult: MrzScannerResult) {
+ val dialogFragment = MRZDialogFragment.newInstance(mrzRecognitionResult.document)
+ dialogFragment.show(supportFragmentManager, MRZDialogFragment.NAME)
+ }
+
+}
+
diff --git a/data-capture-ready-to-use-ui-example/app/src/main/java/io/scanbot/example/compose/MrzComposeCamera.kt b/data-capture-ready-to-use-ui-example/app/src/main/java/io/scanbot/example/compose/MrzComposeCamera.kt
new file mode 100644
index 00000000..b971f337
--- /dev/null
+++ b/data-capture-ready-to-use-ui-example/app/src/main/java/io/scanbot/example/compose/MrzComposeCamera.kt
@@ -0,0 +1,247 @@
+package io.scanbot.example.compose
+
+import android.Manifest
+import androidx.activity.compose.BackHandler
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.calculateEndPadding
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Scaffold
+import androidx.compose.material.Text
+import androidx.compose.material.TopAppBar
+import androidx.compose.material.rememberScaffoldState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalInspectionMode
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.dp
+import com.google.accompanist.permissions.ExperimentalPermissionsApi
+import com.google.accompanist.permissions.PermissionState
+import com.google.accompanist.permissions.PermissionStatus
+import com.google.accompanist.permissions.isGranted
+import com.google.accompanist.permissions.rememberPermissionState
+import io.scanbot.sdk.ScanbotSDK
+import io.scanbot.sdk.camera.FrameHandlerResult
+import io.scanbot.sdk.common.AspectRatio
+import io.scanbot.sdk.mrz.MrzScannerFrameHandler
+import io.scanbot.sdk.mrz.MrzScannerResult
+import io.scanbot.sdk.ui_v2.common.ActionBarConfiguration
+import io.scanbot.sdk.ui_v2.common.CameraConfiguration
+import io.scanbot.sdk.ui_v2.common.CameraPermissionScreen
+import io.scanbot.sdk.ui_v2.common.Constants
+import io.scanbot.sdk.ui_v2.common.activity.CanceledByUser
+import io.scanbot.sdk.ui_v2.common.activity.CloseReason
+import io.scanbot.sdk.ui_v2.common.camera.FinderConfiguration
+import io.scanbot.sdk.ui_v2.common.camera.ScanbotComposeCamera
+import io.scanbot.sdk.ui_v2.common.camera.ScanbotComposeCameraViewModel
+import io.scanbot.sdk.ui_v2.common.components.ScanbotCameraActionBar
+import io.scanbot.sdk.ui_v2.common.components.ScanbotCameraPermissionView
+import io.scanbot.sdk.ui_v2.common.components.ScanbotSystemBar
+import io.scanbot.sdk.ui_v2.common.theme.LocalScanbotTheme
+import io.scanbot.sdk.ui_v2.common.theme.Localization
+import io.scanbot.sdk.ui_v2.common.theme.ScanbotTheme
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.MutableSharedFlow
+
+@Composable
+@OptIn(ExperimentalPermissionsApi::class, ExperimentalMaterialApi::class)
+@androidx.camera.camera2.interop.ExperimentalCamera2Interop
+fun MrzScannerViewClassic(
+ modifier: Modifier = Modifier.fillMaxSize(),
+ enableBackNavigation: Boolean = true,
+ onScannerClosed: (CloseReason) -> Unit = {},
+ onMrz: (MrzScannerResult) -> Unit = {},
+ viewModel: MrzViewModel,
+) {
+
+ LaunchedEffect(key1 = Unit) {
+ viewModel.result.collect { mrzDocument ->
+ mrzDocument?.let { onMrz(it) }
+ }
+ }
+ CompositionLocalProvider(
+ LocalScanbotTheme provides ScanbotTheme(
+ localization = Localization(mutableMapOf())
+ )
+ ) {
+ val context = LocalContext.current
+ val previewMode = LocalInspectionMode.current
+
+ BoxWithConstraints(modifier = modifier) {
+ this.maxWidth
+ val density = LocalDensity.current
+
+ val scope = rememberCoroutineScope()
+ BackHandler(enableBackNavigation, onBack = {
+ onScannerClosed(CanceledByUser)
+ })
+
+ val backgroundColor = Color.Black
+
+ val permissionGranted = if (!previewMode) {
+ val cameraPermissionState =
+ rememberPermissionState(permission = Manifest.permission.CAMERA)
+
+ // start scanning delay dialog counter if camera permissions are granted
+
+ checkPermissionStatus(cameraPermissionState) {
+ viewModel.permissionEnabled.value = cameraPermissionState.status.isGranted
+ }
+ cameraPermissionState.status.isGranted
+ } else {
+ true
+ }
+ if (permissionGranted) {
+
+ val scaffoldState = rememberScaffoldState()
+
+ Scaffold(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(backgroundColor),
+ topBar = {
+ TopAppBar(title = { Text(text = "Scan Mrz") })
+ },
+ backgroundColor = backgroundColor,
+ scaffoldState = scaffoldState,
+ content = { paddingValues ->
+ BoxWithConstraints(modifier = modifier) {
+ this.maxWidth
+ val cornerRadius = with(LocalDensity.current) {
+ MaterialTheme.shapes.large.topEnd.toPx(
+ Size(
+ maxWidth.toPx(), maxHeight.toPx()
+ ), LocalDensity.current
+ ).toDp()
+ }
+ ScanbotComposeCamera(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(
+ bottom = paddingValues.calculateBottomPadding(),
+ end = paddingValues.calculateEndPadding(LocalLayoutDirection.current)
+ ),
+ viewModel = viewModel,
+ cameraBackgroundColor = backgroundColor,
+ onViewCreated = { camera ->
+ if (!previewMode) {
+ camera.removeFrameHandler(viewModel.frameHandler)
+ camera.addFrameHandler(viewModel.frameHandler)
+ }
+ },
+ finderConfiguration = FinderConfiguration(
+ aspectRatio = AspectRatio(
+ 5.0,
+ 2.0
+ ),
+ overlayColor = Color.Black.copy(alpha = 0.5f),
+ strokeColor = Color.White,
+ ),
+ )
+
+ }
+ },
+ // action bar max scroll position from the bottom consists of sheet pick height, safe area(action bar with its padding itself) and the area from it to hint bottom
+ floatingActionButton = {
+ Column(
+ Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+
+ Box(Modifier.height(Constants.Ui.actionBarHeight)) {
+ ScanbotCameraActionBar(
+ config = ActionBarConfiguration(),
+ flashEnabled = viewModel.flashEnabled.collectAsState(),
+ flashButtonEnabled = viewModel.flashButtonEnabled.collectAsState(),
+ zoomState = viewModel.zoomFactorUi.collectAsState(),
+ cameraModule = viewModel.cameraModule.collectAsState(),
+ onAction = { viewModel.onAction(it) },
+ )
+ }
+ }
+ },
+ )
+
+ } else {
+ ScanbotSystemBar(
+ systemBarColor = Color.Transparent,
+ statusBarDarkIcons = true
+ ) {
+ ScanbotCameraPermissionView(
+ modifier = modifier,
+ bottomContentPadding = 0.dp,
+ permissionConfig = CameraPermissionScreen(),
+ onClose = { onScannerClosed(CanceledByUser) })
+ }
+ }
+ }
+ }
+}
+
+@Composable
+@OptIn(ExperimentalPermissionsApi::class)
+inline fun checkPermissionStatus(
+ cameraPermissionState: PermissionState,
+ noinline permissionGrantedBlock: CoroutineScope.() -> Unit,
+) {
+ LaunchedEffect(key1 = cameraPermissionState.status.isGranted, block = permissionGrantedBlock)
+ if (cameraPermissionState.status == PermissionStatus.Denied(false)) {
+ if (!LocalInspectionMode.current) {
+ SideEffect {
+ cameraPermissionState.launchPermissionRequest()
+ }
+ }
+ }
+}
+
+class MrzViewModel(
+ val cameraConfiguration: CameraConfiguration,
+ val sdk: ScanbotSDK,
+ val flashAvailable: Boolean = true,
+) : ScanbotComposeCameraViewModel(
+ cameraConfiguration.cameraModule,
+ cameraConfiguration.zoomSteps,
+ cameraConfiguration.defaultZoomFactor,
+ cameraConfiguration.flashEnabled,
+ cameraConfiguration.minFocusDistanceLock,
+ cameraConfiguration.touchToFocusEnabled,
+ cameraConfiguration.pinchToZoomEnabled,
+ false,
+ cameraConfiguration.orientationLockMode,
+ cameraConfiguration.cameraPreviewMode,
+ flashAvailable
+) {
+ val frameHandler: MrzScannerFrameHandler = MrzScannerFrameHandler(sdk.createMrzScanner())
+ val result: MutableSharedFlow = MutableSharedFlow(1)
+ val handler = MrzScannerFrameHandler.ResultHandler { result ->
+ if (result is FrameHandlerResult.Success) {
+ val mrzDocument = result.value as MrzScannerResult
+ // handle mrz document
+ if (mrzDocument.success) {
+ this.result.tryEmit(mrzDocument)
+ }
+ }
+ false
+ }
+ init {
+ frameHandler.addResultHandler(handler)
+ }
+}
\ No newline at end of file
diff --git a/data-capture-ready-to-use-ui-example/app/src/main/java/io/scanbot/example/fragments/MRZDialogFragment.kt b/data-capture-ready-to-use-ui-example/app/src/main/java/io/scanbot/example/fragments/MRZDialogFragment.kt
index 64dba1d4..0b14b2c6 100644
--- a/data-capture-ready-to-use-ui-example/app/src/main/java/io/scanbot/example/fragments/MRZDialogFragment.kt
+++ b/data-capture-ready-to-use-ui-example/app/src/main/java/io/scanbot/example/fragments/MRZDialogFragment.kt
@@ -13,7 +13,6 @@ import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import io.scanbot.example.R
import io.scanbot.sdk.genericdocument.entity.*
-import io.scanbot.sdk.mrz.*
class MRZDialogFragment : androidx.fragment.app.DialogFragment() {
@@ -23,7 +22,7 @@ class MRZDialogFragment : androidx.fragment.app.DialogFragment() {
const val NAME = "MRZDialogFragment"
@JvmStatic
- fun newInstance(data: GenericDocument): MRZDialogFragment {
+ fun newInstance(data: GenericDocument?): MRZDialogFragment {
val frag = MRZDialogFragment()
val args = Bundle()
args.putParcelable(MRZ_DATA, data)
diff --git a/data-capture-ready-to-use-ui-example/app/src/main/res/layout/activity_main.xml b/data-capture-ready-to-use-ui-example/app/src/main/res/layout/activity_main.xml
index b757cb52..e0d63021 100644
--- a/data-capture-ready-to-use-ui-example/app/src/main/res/layout/activity_main.xml
+++ b/data-capture-ready-to-use-ui-example/app/src/main/res/layout/activity_main.xml
@@ -79,6 +79,14 @@
android:layout_marginEnd="8dp"
android:text="@string/scan_mrz" />
+
+