diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..be35543
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+MyScan
\ No newline at end of file
diff --git a/.idea/AndroidProjectSystem.xml b/.idea/AndroidProjectSystem.xml
new file mode 100644
index 0000000..4a53bee
--- /dev/null
+++ b/.idea/AndroidProjectSystem.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deploymentTargetSelector.xml b/.idea/deploymentTargetSelector.xml
new file mode 100644
index 0000000..b268ef3
--- /dev/null
+++ b/.idea/deploymentTargetSelector.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
new file mode 100644
index 0000000..639c779
--- /dev/null
+++ b/.idea/gradle.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..7061a0d
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
new file mode 100644
index 0000000..c224ad5
--- /dev/null
+++ b/.idea/kotlinc.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/migrations.xml b/.idea/migrations.xml
new file mode 100644
index 0000000..f8051a6
--- /dev/null
+++ b/.idea/migrations.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..74dd639
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml
new file mode 100644
index 0000000..16660f1
--- /dev/null
+++ b/.idea/runConfigurations.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index d5b52c4..e462101 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -49,6 +49,11 @@ dependencies {
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
+ implementation(libs.androidx.camera.core)
+ implementation(libs.androidx.camera.camera2)
+ implementation(libs.androidx.camera.lifecycle)
+ implementation(libs.androidx.camera.view)
+
testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index f6cab60..eae1911 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -2,6 +2,11 @@
+
+
+
- Greeting(
- name = "Android",
- modifier = Modifier.padding(innerPadding)
- )
+ Scaffold(/*modifier = Modifier.fillMaxSize()*/) { innerPadding ->
+ Column {
+ Greeting(modifier = Modifier.padding(innerPadding))
+ Box(/*modifier = Modifier.width(300.dp)*/) {
+ CameraScreen { }
+ }
+ }
}
}
}
@@ -31,9 +38,9 @@ class MainActivity : ComponentActivity() {
}
@Composable
-fun Greeting(name: String, modifier: Modifier = Modifier) {
+fun Greeting(modifier: Modifier = Modifier) {
Text(
- text = "Hello $name!",
+ text = "Scan your document",
modifier = modifier
)
}
@@ -42,6 +49,6 @@ fun Greeting(name: String, modifier: Modifier = Modifier) {
@Composable
fun GreetingPreview() {
MyScanTheme {
- Greeting("Android")
+ Greeting()
}
}
\ No newline at end of file
diff --git a/app/src/main/java/org/mydomain/myscan/view/Camera.kt b/app/src/main/java/org/mydomain/myscan/view/Camera.kt
new file mode 100644
index 0000000..caf58f6
--- /dev/null
+++ b/app/src/main/java/org/mydomain/myscan/view/Camera.kt
@@ -0,0 +1,114 @@
+package org.mydomain.myscan.view
+
+import android.content.pm.PackageManager.PERMISSION_GRANTED
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.widget.LinearLayout
+import android.widget.Toast
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.camera.core.AspectRatio.RATIO_4_3
+import androidx.camera.core.CameraSelector
+import androidx.camera.core.ImageAnalysis
+import androidx.camera.core.ImageProxy
+import androidx.camera.core.Preview
+import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.camera.view.PreviewView
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.content.ContextCompat
+import androidx.lifecycle.LifecycleOwner
+import com.google.common.util.concurrent.ListenableFuture
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+
+@Composable
+fun CameraScreen(
+ onImageAnalyzed: (ImageProxy) -> Unit,
+) {
+ val context = LocalContext.current
+ val requestPermissionLauncher = rememberLauncherForActivityResult(
+ ActivityResultContracts.RequestPermission()
+ ) { isGranted: Boolean ->
+ if (!isGranted) {
+ Toast.makeText(context, "Camera permission was denied", Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ LaunchedEffect(Unit) {
+ val camera = android.Manifest.permission.CAMERA
+ if (ContextCompat.checkSelfPermission(context, camera) != PERMISSION_GRANTED) {
+ requestPermissionLauncher.launch(camera)
+ }
+ }
+
+ CameraPreview(onImageAnalyzed = { imageProxy -> onImageAnalyzed(imageProxy) })
+}
+
+@Composable
+fun CameraPreview(
+ modifier: Modifier = Modifier,
+ onImageAnalyzed: (ImageProxy) -> Unit,
+) {
+ val context = LocalContext.current
+ val lifecycleOwner = LocalLifecycleOwner.current
+ val cameraProviderFuture by remember {
+ mutableStateOf(ProcessCameraProvider.getInstance(context))
+ }
+
+ DisposableEffect(lifecycleOwner) {
+ onDispose {
+ cameraProviderFuture.get().unbindAll()
+ }
+ }
+
+ AndroidView(modifier = modifier, factory = {
+ val previewView = PreviewView(it).apply {
+ layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT)
+ scaleType = PreviewView.ScaleType.FIT_CENTER
+ }
+
+ val executor = Executors.newSingleThreadExecutor()
+ cameraProviderFuture.addListener({
+ bindCameraUseCases(
+ lifecycleOwner = lifecycleOwner,
+ cameraProviderFuture = cameraProviderFuture,
+ executor = executor,
+ previewView = previewView,
+ onImageAnalyzed = onImageAnalyzed
+ )
+ }, ContextCompat.getMainExecutor(context))
+
+ previewView
+ })
+}
+
+fun bindCameraUseCases(
+ lifecycleOwner: LifecycleOwner,
+ cameraProviderFuture: ListenableFuture,
+ executor: ExecutorService,
+ previewView: PreviewView,
+ onImageAnalyzed: (ImageProxy) -> Unit,
+) {
+ val preview: Preview = Preview.Builder().setTargetAspectRatio(RATIO_4_3).build()
+
+ preview.surfaceProvider = previewView.surfaceProvider
+
+ val cameraSelector: CameraSelector =
+ CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build()
+ val imageAnalysis = ImageAnalysis.Builder().setTargetAspectRatio(RATIO_4_3)
+ .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST)
+ .setOutputImageFormat(ImageAnalysis.OUTPUT_IMAGE_FORMAT_RGBA_8888).build()
+ imageAnalysis.setAnalyzer(executor, onImageAnalyzed)
+
+ val cameraProvider = cameraProviderFuture.get()
+ cameraProvider.bindToLifecycle(lifecycleOwner, cameraSelector, imageAnalysis, preview)
+}
+
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 916e792..87e83da 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,13 +1,14 @@
[versions]
-agp = "8.9.1"
-kotlin = "2.0.21"
+agp = "8.9.2"
+kotlin = "2.1.0"
coreKtx = "1.16.0"
junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
-lifecycleRuntimeKtx = "2.8.7"
+lifecycleRuntimeKtx = "2.9.0"
activityCompose = "1.10.1"
-composeBom = "2024.09.00"
+composeBom = "2025.05.00"
+camerax = "1.4.2"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -25,6 +26,11 @@ androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-man
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-material3 = { group = "androidx.compose.material3", name = "material3" }
+androidx-camera-core = { group = "androidx.camera", name = "camera-core", version.ref = "camerax" }
+androidx-camera-camera2 = { group = "androidx.camera", name = "camera-camera2", version.ref = "camerax" }
+androidx-camera-lifecycle = { group = "androidx.camera", name = "camera-lifecycle", version.ref = "camerax" }
+androidx-camera-view = { group = "androidx.camera", name = "camera-view", version.ref = "camerax" }
+
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }
kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }