diff --git a/CameraView/.gitignore b/CameraView/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/CameraView/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/CameraView/.idea/.gitignore b/CameraView/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/CameraView/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/CameraView/.idea/compiler.xml b/CameraView/.idea/compiler.xml new file mode 100644 index 0000000..b589d56 --- /dev/null +++ b/CameraView/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/CameraView/.idea/deploymentTargetSelector.xml b/CameraView/.idea/deploymentTargetSelector.xml new file mode 100644 index 0000000..b268ef3 --- /dev/null +++ b/CameraView/.idea/deploymentTargetSelector.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/CameraView/.idea/gradle.xml b/CameraView/.idea/gradle.xml new file mode 100644 index 0000000..0897082 --- /dev/null +++ b/CameraView/.idea/gradle.xml @@ -0,0 +1,19 @@ + + + + + + + \ No newline at end of file diff --git a/CameraView/.idea/inspectionProfiles/Project_Default.xml b/CameraView/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..44ca2d9 --- /dev/null +++ b/CameraView/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,41 @@ + + + + \ No newline at end of file diff --git a/CameraView/.idea/kotlinc.xml b/CameraView/.idea/kotlinc.xml new file mode 100644 index 0000000..fdf8d99 --- /dev/null +++ b/CameraView/.idea/kotlinc.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/CameraView/.idea/migrations.xml b/CameraView/.idea/migrations.xml new file mode 100644 index 0000000..f8051a6 --- /dev/null +++ b/CameraView/.idea/migrations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/CameraView/.idea/misc.xml b/CameraView/.idea/misc.xml new file mode 100644 index 0000000..0ad17cb --- /dev/null +++ b/CameraView/.idea/misc.xml @@ -0,0 +1,10 @@ + + + + + + + + + \ No newline at end of file diff --git a/CameraView/app/.gitignore b/CameraView/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/CameraView/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/CameraView/app/build.gradle.kts b/CameraView/app/build.gradle.kts new file mode 100644 index 0000000..f52e307 --- /dev/null +++ b/CameraView/app/build.gradle.kts @@ -0,0 +1,96 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.jetbrains.kotlin.android) + id("kotlin-kapt") + id("com.google.dagger.hilt.android") +} + +android { + namespace = "com.example.cameraview" + compileSdk = 34 + + defaultConfig { + applicationId = "com.example.cameraview" + minSdk = 24 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary = true + } + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.5.1" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } +} + +kapt { + correctErrorTypes = true +} + +dependencies { + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.activity.compose) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.ui) + implementation(libs.androidx.ui.graphics) + implementation(libs.androidx.ui.tooling.preview) + implementation(libs.androidx.material3) + implementation(libs.androidx.camera.mlkit.vision) + testImplementation(libs.junit) + + val navVersion = "2.7.7" + implementation("androidx.navigation:navigation-compose:$navVersion") + + val cameraxVersion = "1.3.3" + implementation("androidx.camera:camera-core:$cameraxVersion") + implementation("androidx.camera:camera-camera2:$cameraxVersion") + implementation("androidx.camera:camera-lifecycle:$cameraxVersion") + implementation("androidx.camera:camera-view:$cameraxVersion") + implementation("androidx.camera:camera-extensions:$cameraxVersion") + + // Bar Code Scanning + implementation("com.google.mlkit:barcode-scanning:17.2.0") + + // DI: Hilt + implementation("com.google.dagger:hilt-android:2.48") + kapt("com.google.dagger:hilt-android-compiler:2.48") + kapt ("androidx.hilt:hilt-compiler:1.2.0") + implementation ("androidx.hilt:hilt-navigation-compose:1.2.0") + + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(platform(libs.androidx.compose.bom)) + androidTestImplementation(libs.androidx.ui.test.junit4) + debugImplementation(libs.androidx.ui.tooling) + debugImplementation(libs.androidx.ui.test.manifest) +} \ No newline at end of file diff --git a/CameraView/app/proguard-rules.pro b/CameraView/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/CameraView/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/CameraView/app/src/androidTest/java/com/example/cameraview/ExampleInstrumentedTest.kt b/CameraView/app/src/androidTest/java/com/example/cameraview/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..160d8a7 --- /dev/null +++ b/CameraView/app/src/androidTest/java/com/example/cameraview/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package com.example.cameraview + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.example.cameraview", appContext.packageName) + } +} \ No newline at end of file diff --git a/CameraView/app/src/main/AndroidManifest.xml b/CameraView/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..51cfae8 --- /dev/null +++ b/CameraView/app/src/main/AndroidManifest.xml @@ -0,0 +1,34 @@ + + + + + \ + + + + + + + + + + + + \ No newline at end of file diff --git a/CameraView/app/src/main/java/com/example/cameraview/MyApp.kt b/CameraView/app/src/main/java/com/example/cameraview/MyApp.kt new file mode 100644 index 0000000..1805ba3 --- /dev/null +++ b/CameraView/app/src/main/java/com/example/cameraview/MyApp.kt @@ -0,0 +1,12 @@ +package com.example.cameraview + +import android.app.Application +import dagger.hilt.android.HiltAndroidApp + +@HiltAndroidApp +class MyApp : Application() { + + override fun onCreate() { + super.onCreate() + } +} \ No newline at end of file diff --git a/CameraView/app/src/main/java/com/example/cameraview/activity/MainActivity.kt b/CameraView/app/src/main/java/com/example/cameraview/activity/MainActivity.kt new file mode 100644 index 0000000..84492f1 --- /dev/null +++ b/CameraView/app/src/main/java/com/example/cameraview/activity/MainActivity.kt @@ -0,0 +1,68 @@ +package com.example.cameraview.activity + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.navigation.NavType +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import androidx.navigation.compose.rememberNavController +import androidx.navigation.navArgument +import com.example.cameraview.composables.HomeScreen +import com.example.cameraview.composables.QRScannerScreen +import com.example.cameraview.composables.RegistrationScreen +import com.example.cameraview.ui.theme.CameraViewTheme +import dagger.hilt.android.AndroidEntryPoint + +@AndroidEntryPoint +class MainActivity : ComponentActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + setContent { + CameraViewTheme { + val navController = rememberNavController() + NavHost( + navController = navController, + startDestination = Destinations.HOME_SCREEN.name + ) { + composable(Destinations.HOME_SCREEN.name) { + HomeScreen { + navController.navigate(Destinations.QR_SCAN_SCREEN.name) + } + } + + composable(Destinations.QR_SCAN_SCREEN.name) { + QRScannerScreen( + onSuccessBarCodeAnalysis = { uniqueId -> + val id = uniqueId.toString() + navController.navigate("${Destinations.REGISTRATION_SCREEN.name}/${id}") + }, applicationContext = application + ) + } + + composable( + route = Destinations.REGISTRATION_SCREEN.name + "/{userId}", + arguments = listOf( + navArgument(name = "userId") { + type = NavType.StringType + defaultValue = "default_value" + } + ) + ) { + RegistrationScreen(uniqueId = it.arguments?.getString("userId") ?: "") + } + } + } + } + } +} + +enum class Destinations { + HOME_SCREEN, + QR_SCAN_SCREEN, + REGISTRATION_SCREEN +} + + diff --git a/CameraView/app/src/main/java/com/example/cameraview/composables/CameraScreen.kt b/CameraView/app/src/main/java/com/example/cameraview/composables/CameraScreen.kt new file mode 100644 index 0000000..e89e218 --- /dev/null +++ b/CameraView/app/src/main/java/com/example/cameraview/composables/CameraScreen.kt @@ -0,0 +1,201 @@ +package com.example.cameraview.composables + +import android.content.Context +import android.graphics.Color +import android.graphics.Rect +import android.net.Uri +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.Toast +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts +import androidx.camera.core.AspectRatio +import androidx.camera.core.CameraSelector +import androidx.camera.core.ImageAnalysis +import androidx.camera.core.Preview +import androidx.camera.mlkit.vision.MlKitAnalyzer +import androidx.camera.view.CameraController +import androidx.camera.view.LifecycleCameraController +import androidx.camera.view.PreviewView +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.foundation.Canvas +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +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.padding +import androidx.compose.material3.Button +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.platform.LocalLifecycleOwner +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.content.ContextCompat +import androidx.core.graphics.component1 +import androidx.core.graphics.component2 +import androidx.core.graphics.component3 +import androidx.core.graphics.component4 +import androidx.lifecycle.LifecycleOwner +import com.google.mlkit.vision.common.InputImage +import com.google.mlkit.vision.text.Text +import com.google.mlkit.vision.text.TextRecognition +import com.google.mlkit.vision.text.TextRecognizer +import com.google.mlkit.vision.text.latin.TextRecognizerOptions + +@Composable +fun CameraScreen( + modifier: Modifier = Modifier, + applicationContext: Context, + onResultFound: (String) -> Unit, +) { + val cameraController = remember { + LifecycleCameraController(applicationContext).apply { + setEnabledUseCases( + CameraController.IMAGE_ANALYSIS + ) + cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA + imageAnalysisBackpressureStrategy = ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST + } + } + + val textRecognizer: TextRecognizer = TextRecognition.getClient( + TextRecognizerOptions.Builder().build() + ) + + var selectedImageUri by rememberSaveable { mutableStateOf(null) } + var inputImage by remember { mutableStateOf(null) } + + // Launcher to get Image Uri from Storage + val photoPickerLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.PickVisualMedia(), + onResult = { uri -> + selectedImageUri = uri + } + ) + + val lifecycleOwner = LocalLifecycleOwner.current + var recognizedText by rememberSaveable { mutableStateOf(null) } + var blockFrame by remember { mutableStateOf(null) } + + Scaffold(modifier = modifier.fillMaxSize()) { innerPadding -> + Box( + modifier = modifier + .fillMaxSize() + .padding(innerPadding), + contentAlignment = Alignment.BottomCenter + ) { + AndroidView( + factory = { context -> + PreviewView(context).apply { + layoutParams = LinearLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT + ) + implementationMode = PreviewView.ImplementationMode.COMPATIBLE + scaleType = PreviewView.ScaleType.FILL_START + }.also { previewView -> + cameraController.setImageAnalysisAnalyzer( + ContextCompat.getMainExecutor(context), + MyTextRecognizer( + textRecognizer = textRecognizer, + onResultFound = { + it.textBlocks.forEach { block -> + blockFrame = block.boundingBox + } + recognizedText = it.formatRecognizedText() + }, + ) + ) + cameraController.bindToLifecycle(lifecycleOwner) + previewView.controller = cameraController + } + }, + modifier = modifier + .fillMaxSize(), + ) + + Canvas(modifier = modifier.fillMaxSize()) { + blockFrame?.let { rect -> + val topLeft = Offset(rect.left.toFloat(), rect.top.toFloat()) + val size = Size(rect.width().toFloat(), rect.height().toFloat()) + + // Draw the rectangle + drawRect( + color = androidx.compose.ui.graphics.Color.Red, + topLeft = topLeft, + size = size + ) + } + } + + AnimatedVisibility(visible = recognizedText.isNullOrEmpty().not()) { + Column( + modifier + .fillMaxWidth() + .background(color = androidx.compose.ui.graphics.Color.White) + .padding(24.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text(text = "OutPut Text : $recognizedText") + Row( + modifier + .fillMaxWidth(0.9f) + .padding(8.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Button(onClick = { + photoPickerLauncher.launch( + PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) + ) + }) { + Text(text = "Pick a Photo") + } + + Button(onClick = { + photoPickerLauncher.launch( + PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) + ) + }) { + Text(text = "Save to DB") + } + } + } + } + } + } +} + +fun Text.formatRecognizedText(): String { + val stringBuilder = buildString { + textBlocks.forEach { blockText -> + append("\n") + append("Block : ${blockText.text}") + blockText.lines.forEach { lineText -> + append("\n") + append("Line : ${lineText.text}") + lineText.elements.forEach { + append("\n") + append("Element : ${it.text}") + } + } + } + } + return stringBuilder +} \ No newline at end of file diff --git a/CameraView/app/src/main/java/com/example/cameraview/composables/HomeScreen.kt b/CameraView/app/src/main/java/com/example/cameraview/composables/HomeScreen.kt new file mode 100644 index 0000000..8b1d34c --- /dev/null +++ b/CameraView/app/src/main/java/com/example/cameraview/composables/HomeScreen.kt @@ -0,0 +1,43 @@ +package com.example.cameraview.composables + +import android.Manifest +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Button +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier + +@Composable +fun HomeScreen( + modifier: Modifier = Modifier, + navigateToQrScannerScreen: () -> Unit, +) { + val cameraPermissionLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.RequestPermission(), + onResult = { granted -> + if (granted) { + navigateToQrScannerScreen() + } + } + ) + + Column( + modifier.fillMaxSize(), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + + Button(onClick = { + cameraPermissionLauncher.launch( + Manifest.permission.CAMERA + ) + }) { + Text(text = "Open QR Scanner") + } + } +} \ No newline at end of file diff --git a/CameraView/app/src/main/java/com/example/cameraview/composables/QrScannerScreen.kt b/CameraView/app/src/main/java/com/example/cameraview/composables/QrScannerScreen.kt new file mode 100644 index 0000000..bab2fd1 --- /dev/null +++ b/CameraView/app/src/main/java/com/example/cameraview/composables/QrScannerScreen.kt @@ -0,0 +1,150 @@ +package com.example.cameraview.composables + +import android.app.Application +import android.net.Uri +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.PickVisualMediaRequest +import androidx.activity.result.contract.ActivityResultContracts +import androidx.annotation.OptIn +import androidx.camera.core.CameraSelector +import androidx.camera.core.ExperimentalGetImage +import androidx.camera.core.ImageAnalysis +import androidx.camera.core.ImageAnalysis.COORDINATE_SYSTEM_VIEW_REFERENCED +import androidx.camera.core.Preview +import androidx.camera.mlkit.vision.MlKitAnalyzer +import androidx.camera.view.CameraController +import androidx.camera.view.LifecycleCameraController +import androidx.camera.view.PreviewView +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +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.material3.Button +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +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.hilt.navigation.compose.hiltViewModel +import com.google.mlkit.vision.common.InputImage +import java.io.IOException + + +@OptIn(ExperimentalGetImage::class) +@Composable +fun QRScannerScreen( + modifier: Modifier = Modifier, + qrScreenViewModel: QrScreenViewModel = hiltViewModel(), + onSuccessBarCodeAnalysis: (String) -> Unit, + applicationContext: Application +) { + + val cameraController = remember { + LifecycleCameraController(applicationContext).apply { + setEnabledUseCases( + CameraController.IMAGE_ANALYSIS + ) + cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA + imageAnalysisBackpressureStrategy = ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST + } + } + + val context = LocalContext.current + val lifecycleOwner = LocalLifecycleOwner.current + var selectedImageUri by rememberSaveable { mutableStateOf(null) } + var inputImage by remember { mutableStateOf(null) } + + // Launcher to get Image Uri from Storage + val photoPickerLauncher = rememberLauncherForActivityResult( + contract = ActivityResultContracts.PickVisualMedia(), + onResult = { uri -> + selectedImageUri = uri + } + ) + + LaunchedEffect(selectedImageUri) { + selectedImageUri?.let { uri -> + try { + inputImage = InputImage.fromFilePath(context, uri) + } catch (e: IOException) { + e.printStackTrace() + } + + qrScreenViewModel.processPhotoImageProxy( + mediaImage = inputImage, + onSuccess = { barcodes -> + onSuccessBarCodeAnalysis(barcodes.first().rawValue.toString()) + } + ) + } + + selectedImageUri = null + } + + Scaffold(modifier = modifier.fillMaxSize()) { innerPadding -> + Box( + modifier = modifier + .fillMaxSize() + .padding(innerPadding), + contentAlignment = Alignment.BottomCenter + ) { + AndroidView( + factory = { context -> + PreviewView(context).apply { + // Preview : Set Surface for Process + val preview: Preview = Preview.Builder().build() + preview.setSurfaceProvider(this.surfaceProvider) + this.controller = cameraController + cameraController.bindToLifecycle(lifecycleOwner) + + cameraController.setImageAnalysisAnalyzer( + ContextCompat.getMainExecutor(context), + MlKitAnalyzer( + listOf(qrScreenViewModel.getBarCodeClient()), + COORDINATE_SYSTEM_VIEW_REFERENCED, + ContextCompat.getMainExecutor(context) + ) { result: MlKitAnalyzer.Result? -> + qrScreenViewModel.processBarCodeImgAnalysisResult( + result = result, + noResultFound = { + + }, + successResult = { uniqueId -> + onSuccessBarCodeAnalysis(uniqueId) + } + ) + } + ) + } + }, + modifier = modifier + .fillMaxSize(), + ) + + Row( + modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.Absolute.SpaceBetween + ) { + Button(onClick = { + photoPickerLauncher.launch( + PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly) + ) + }) { + Text(text = "Gallery") + } + } + } + } +} \ No newline at end of file diff --git a/CameraView/app/src/main/java/com/example/cameraview/composables/QrScreenViewModel.kt b/CameraView/app/src/main/java/com/example/cameraview/composables/QrScreenViewModel.kt new file mode 100644 index 0000000..10716d4 --- /dev/null +++ b/CameraView/app/src/main/java/com/example/cameraview/composables/QrScreenViewModel.kt @@ -0,0 +1,95 @@ +package com.example.cameraview.composables + +import androidx.annotation.OptIn +import androidx.camera.core.ExperimentalGetImage +import androidx.camera.core.ImageProxy +import androidx.camera.mlkit.vision.MlKitAnalyzer +import androidx.compose.runtime.mutableStateOf +import androidx.lifecycle.ViewModel +import com.example.cameraview.data.Repository +import com.google.mlkit.vision.barcode.BarcodeScanner +import com.google.mlkit.vision.barcode.common.Barcode +import com.google.mlkit.vision.common.InputImage +import dagger.hilt.android.lifecycle.HiltViewModel +import javax.inject.Inject + +@HiltViewModel +class QrScreenViewModel @Inject constructor( + private val repository: Repository, + private val barcodeScanner: BarcodeScanner +) : ViewModel() { + + val fetchedId = mutableStateOf("ID_FROM_QR_CODE") + + init { + repository.saveIdAndShowMessage() + repository.retrieveDataAndCloseConnection(fetchedId.value) + } + + fun getBarCodeClient(): BarcodeScanner { + return barcodeScanner + } + + @OptIn(ExperimentalGetImage::class) + fun processAnalysisImageProxy( + imageProxy: ImageProxy, + onSuccess: (List) -> Unit + ) { + // Convert from ImageProxy to MediaImage for Analysis + val inputMediaImage = imageProxy.image?.let { imageCaptured -> + InputImage.fromMediaImage( + imageCaptured, + imageProxy.imageInfo.rotationDegrees, + ) + } + + try { + inputMediaImage?.let { safeMediaImage -> + barcodeScanner.process(safeMediaImage) + .addOnSuccessListener { + onSuccess(it) + }.addOnFailureListener { + it.printStackTrace() + } + } + } catch (e: Exception) { + e.printStackTrace() + } finally { + imageProxy.close() + } + } + + fun processPhotoImageProxy( + mediaImage: InputImage?, + onSuccess: (List) -> Unit + ) { + try { + mediaImage?.let { safeMediaImage -> + barcodeScanner.process(safeMediaImage) + .addOnSuccessListener { barcodeList -> + onSuccess(barcodeList) + }.addOnFailureListener { + it.printStackTrace() + } + } + } catch (e: Exception) { + e.printStackTrace() + } + } + + fun processBarCodeImgAnalysisResult( + result: MlKitAnalyzer.Result?, + noResultFound: () -> Unit, + successResult: (String) -> Unit, + ) { + val barcodeResults = result?.getValue(barcodeScanner) + if ((barcodeResults == null) || + (barcodeResults.size == 0) || + (barcodeResults.first() == null) + ) { + noResultFound() + } else { + successResult(barcodeResults.first().rawValue.toString()) + } + } +} \ No newline at end of file diff --git a/CameraView/app/src/main/java/com/example/cameraview/composables/RegistrationScreen.kt b/CameraView/app/src/main/java/com/example/cameraview/composables/RegistrationScreen.kt new file mode 100644 index 0000000..67193cc --- /dev/null +++ b/CameraView/app/src/main/java/com/example/cameraview/composables/RegistrationScreen.kt @@ -0,0 +1,21 @@ +package com.example.cameraview.composables + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier + +@Composable +fun RegistrationScreen( + modifier: Modifier = Modifier, + uniqueId: String? +) { + Box( + modifier = modifier.fillMaxSize(), + contentAlignment = Alignment.Center + ) { + Text(text = "Here is the Unique Id \n $uniqueId") + } +} \ No newline at end of file diff --git a/CameraView/app/src/main/java/com/example/cameraview/data/Repository.kt b/CameraView/app/src/main/java/com/example/cameraview/data/Repository.kt new file mode 100644 index 0000000..abdd92e --- /dev/null +++ b/CameraView/app/src/main/java/com/example/cameraview/data/Repository.kt @@ -0,0 +1,24 @@ +package com.example.cameraview.data + +import android.util.Log +import javax.inject.Inject + +interface Repository { + fun saveIdAndShowMessage() + fun retrieveDataAndCloseConnection(id: String) +} + +class RepositoryImpl @Inject constructor( + private val storageService: StorageService +) : Repository { + + override fun saveIdAndShowMessage() { + storageService.saveId() + Log.d("TAG", "Showing Message to user") + } + + override fun retrieveDataAndCloseConnection(id: String) { + storageService.retrieveDataFromId(id) + Log.d("TAG", "Closing Connection") + } +} \ No newline at end of file diff --git a/CameraView/app/src/main/java/com/example/cameraview/data/StorageService.kt b/CameraView/app/src/main/java/com/example/cameraview/data/StorageService.kt new file mode 100644 index 0000000..d7e2d45 --- /dev/null +++ b/CameraView/app/src/main/java/com/example/cameraview/data/StorageService.kt @@ -0,0 +1,29 @@ +package com.example.cameraview.data + +import android.util.Log +import javax.inject.Inject + +interface StorageService { + fun saveId() + fun retrieveDataFromId(id: String) +} + +class FirebaseStorageService @Inject constructor() : StorageService { + override fun saveId() { + Log.d("TAG", "Id Saved") + } + + override fun retrieveDataFromId(id: String) { + Log.d("TAG", "Data Retrieved") + } +} + +class LocalDatabaseStorageService @Inject constructor() : StorageService { + override fun saveId() { + Log.d("TAG", "Id Saved") + } + + override fun retrieveDataFromId(id: String) { + Log.d("TAG", "Data Retrieved") + } +} \ No newline at end of file diff --git a/CameraView/app/src/main/java/com/example/cameraview/di/BarcodeScannerModule.kt b/CameraView/app/src/main/java/com/example/cameraview/di/BarcodeScannerModule.kt new file mode 100644 index 0000000..85cb044 --- /dev/null +++ b/CameraView/app/src/main/java/com/example/cameraview/di/BarcodeScannerModule.kt @@ -0,0 +1,24 @@ +package com.example.cameraview.di + +import com.google.mlkit.vision.barcode.BarcodeScannerOptions +import com.google.mlkit.vision.barcode.BarcodeScanning +import com.google.mlkit.vision.barcode.common.Barcode +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent + +@Module +@InstallIn(SingletonComponent::class) +object BarcodeScannerModule { + + @Provides + fun provideBarCodeScannerOption() = BarcodeScannerOptions.Builder() + .setBarcodeFormats(Barcode.FORMAT_QR_CODE) + .build() + + @Provides + fun provideBarCodeClient( + barCodeScannerOptions: BarcodeScannerOptions + ) = BarcodeScanning.getClient(barCodeScannerOptions) +} \ No newline at end of file diff --git a/CameraView/app/src/main/java/com/example/cameraview/di/RepositoryModule.kt b/CameraView/app/src/main/java/com/example/cameraview/di/RepositoryModule.kt new file mode 100644 index 0000000..6c3b7b8 --- /dev/null +++ b/CameraView/app/src/main/java/com/example/cameraview/di/RepositoryModule.kt @@ -0,0 +1,18 @@ +package com.example.cameraview.di + +import com.example.cameraview.data.Repository +import com.example.cameraview.data.RepositoryImpl +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +abstract class RepositoryModule { + + @Singleton + @Binds + abstract fun bindRepository(repositoryImpl: RepositoryImpl): Repository +} \ No newline at end of file diff --git a/CameraView/app/src/main/java/com/example/cameraview/di/StorageServiceModule.kt b/CameraView/app/src/main/java/com/example/cameraview/di/StorageServiceModule.kt new file mode 100644 index 0000000..f56ba3f --- /dev/null +++ b/CameraView/app/src/main/java/com/example/cameraview/di/StorageServiceModule.kt @@ -0,0 +1,20 @@ +package com.example.cameraview.di + +import com.example.cameraview.data.FirebaseStorageService +import com.example.cameraview.data.StorageService +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +abstract class StorageServiceModule { + + @Singleton + @Binds + abstract fun bindFirebaseStorageService( + firebaseStorageService: FirebaseStorageService + ): StorageService +} \ No newline at end of file diff --git a/CameraView/app/src/main/java/com/example/cameraview/ui/theme/Color.kt b/CameraView/app/src/main/java/com/example/cameraview/ui/theme/Color.kt new file mode 100644 index 0000000..333a43a --- /dev/null +++ b/CameraView/app/src/main/java/com/example/cameraview/ui/theme/Color.kt @@ -0,0 +1,11 @@ +package com.example.cameraview.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple80 = Color(0xFFD0BCFF) +val PurpleGrey80 = Color(0xFFCCC2DC) +val Pink80 = Color(0xFFEFB8C8) + +val Purple40 = Color(0xFF6650a4) +val PurpleGrey40 = Color(0xFF625b71) +val Pink40 = Color(0xFF7D5260) \ No newline at end of file diff --git a/CameraView/app/src/main/java/com/example/cameraview/ui/theme/Theme.kt b/CameraView/app/src/main/java/com/example/cameraview/ui/theme/Theme.kt new file mode 100644 index 0000000..b5d5d8b --- /dev/null +++ b/CameraView/app/src/main/java/com/example/cameraview/ui/theme/Theme.kt @@ -0,0 +1,58 @@ +package com.example.cameraview.ui.theme + +import android.app.Activity +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.platform.LocalContext + +private val DarkColorScheme = darkColorScheme( + primary = Purple80, + secondary = PurpleGrey80, + tertiary = Pink80 +) + +private val LightColorScheme = lightColorScheme( + primary = Purple40, + secondary = PurpleGrey40, + tertiary = Pink40 + + /* Other default colors to override + background = Color(0xFFFFFBFE), + surface = Color(0xFFFFFBFE), + onPrimary = Color.White, + onSecondary = Color.White, + onTertiary = Color.White, + onBackground = Color(0xFF1C1B1F), + onSurface = Color(0xFF1C1B1F), + */ +) + +@Composable +fun CameraViewTheme( + 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 + ) +} \ No newline at end of file diff --git a/CameraView/app/src/main/java/com/example/cameraview/ui/theme/Type.kt b/CameraView/app/src/main/java/com/example/cameraview/ui/theme/Type.kt new file mode 100644 index 0000000..f1afe90 --- /dev/null +++ b/CameraView/app/src/main/java/com/example/cameraview/ui/theme/Type.kt @@ -0,0 +1,34 @@ +package com.example.cameraview.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 + ) + */ +) \ No newline at end of file diff --git a/CameraView/app/src/main/res/drawable/ic_launcher_background.xml b/CameraView/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/CameraView/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/CameraView/app/src/main/res/drawable/ic_launcher_foreground.xml b/CameraView/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/CameraView/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/CameraView/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/CameraView/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/CameraView/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/CameraView/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/CameraView/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..6f3b755 --- /dev/null +++ b/CameraView/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/CameraView/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/CameraView/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/CameraView/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/CameraView/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/CameraView/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/CameraView/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/CameraView/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/CameraView/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/CameraView/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/CameraView/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/CameraView/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/CameraView/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/CameraView/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/CameraView/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/CameraView/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/CameraView/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/CameraView/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/CameraView/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/CameraView/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/CameraView/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/CameraView/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/CameraView/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/CameraView/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/CameraView/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/CameraView/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/CameraView/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/CameraView/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/CameraView/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/CameraView/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/CameraView/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/CameraView/app/src/main/res/values/colors.xml b/CameraView/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/CameraView/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/CameraView/app/src/main/res/values/strings.xml b/CameraView/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..2eb2af5 --- /dev/null +++ b/CameraView/app/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + CameraView + \ No newline at end of file diff --git a/CameraView/app/src/main/res/values/themes.xml b/CameraView/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..e437120 --- /dev/null +++ b/CameraView/app/src/main/res/values/themes.xml @@ -0,0 +1,5 @@ + + + +