diff --git a/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/data/repository/CameraRepository.kt b/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/data/repository/CameraRepository.kt new file mode 100644 index 0000000..d1eb262 --- /dev/null +++ b/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/data/repository/CameraRepository.kt @@ -0,0 +1,13 @@ +package com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.repository + +import android.graphics.Bitmap +import androidx.camera.view.LifecycleCameraController +import java.util.concurrent.Executor + +interface CameraRepository { + fun takePicture( + controller: LifecycleCameraController, + executor: Executor, + onPictureTaken: (Bitmap) -> Unit + ) +} \ No newline at end of file diff --git a/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/data/repository/CameraRepositoryImpl.kt b/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/data/repository/CameraRepositoryImpl.kt new file mode 100644 index 0000000..1c869c9 --- /dev/null +++ b/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/data/repository/CameraRepositoryImpl.kt @@ -0,0 +1,64 @@ +package com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.repository + +import android.graphics.Bitmap +import android.graphics.Matrix +import android.util.Log +import androidx.camera.core.ImageCapture.OnImageCapturedCallback +import androidx.camera.core.ImageCaptureException +import androidx.camera.core.ImageProxy +import androidx.camera.view.LifecycleCameraController +import java.util.concurrent.Executor + +class CameraRepositoryImpl : CameraRepository { + + override fun takePicture( + controller: LifecycleCameraController, + executor: Executor, + onPictureTaken: (Bitmap) -> Unit + ) { + Log.d("CameraRepository", "Starting takePicture") + + controller.takePicture( + executor, + object : OnImageCapturedCallback() { + override fun onCaptureSuccess(image: ImageProxy) { + Log.d("CameraRepository", "Capture success") + + try { + val rotationDegrees = image.imageInfo.rotationDegrees + Log.d("CameraRepository", "Rotation: $rotationDegrees") + + val matrix = Matrix().apply { + postRotate(rotationDegrees.toFloat()) + } + + val originalBitmap = image.toBitmap() + Log.d("CameraRepository", "Original bitmap size: ${originalBitmap.width} x ${originalBitmap.height}") + + val rotatedBitmap = Bitmap.createBitmap( + originalBitmap, + 0, 0, + image.width, + image.height, + matrix, + true + ) + + Log.d("CameraRepository", "Rotated bitmap size: ${rotatedBitmap.width} x ${rotatedBitmap.height}") + + onPictureTaken(rotatedBitmap) + + } catch (e: Exception) { + Log.e("CameraRepository", "Error while processing image: ${e.message}", e) + } finally { + image.close() + } + } + + override fun onError(exception: ImageCaptureException) { + Log.e("CameraRepository", "Couldn't take photo: ${exception.message}", exception) + } + } + ) + } +} \ No newline at end of file diff --git a/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/di/CameraModule.kt b/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/di/CameraModule.kt new file mode 100644 index 0000000..94746e6 --- /dev/null +++ b/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/di/CameraModule.kt @@ -0,0 +1,12 @@ +package com.syaroful.agrilinkvocpro.plant_disease_detection_feature.di + +import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.repository.CameraRepository +import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.repository.CameraRepositoryImpl +import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.presentation.camera.CameraViewModel +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val cameraModule = module { + single { CameraRepositoryImpl() } + viewModel { CameraViewModel(get(), get())} +} \ No newline at end of file diff --git a/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/presentation/camera/CameraScreen.kt b/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/presentation/camera/CameraScreen.kt new file mode 100644 index 0000000..17b5c7d --- /dev/null +++ b/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/presentation/camera/CameraScreen.kt @@ -0,0 +1,136 @@ +package com.syaroful.agrilinkvocpro.plant_disease_detection_feature.presentation.camera + +import androidx.camera.view.CameraController +import androidx.camera.view.LifecycleCameraController +import androidx.camera.view.PreviewView +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.aspectRatio +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +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.res.painterResource +import androidx.compose.ui.unit.dp +import androidx.compose.ui.viewinterop.AndroidView +import androidx.core.content.ContextCompat +import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.navigation.NavHostController +import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.R +import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.presentation.camera.component.CustomCameraShutter + + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun CameraScreen( + navController: NavHostController, + viewModel: CameraViewModel +) { + val context = LocalContext.current + val lifecycleOwner = LocalLifecycleOwner.current + + + val uiState by viewModel.uiState.collectAsState() +// val bitmaps by viewModel.bitmaps.collectAsState() + val bitmap by viewModel.bitmap.collectAsState() + val scope = rememberCoroutineScope() +// val scaffoldState = rememberBottomSheetScaffoldState() + + val controller = remember { + LifecycleCameraController(context).apply { + setEnabledUseCases( + CameraController.IMAGE_CAPTURE + ) + } + } + + Scaffold( + topBar = { + TopAppBar( + title = { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.fillMaxWidth() + ) { + Text( + "Deteksi Penyakit Tanaman", style = MaterialTheme.typography.titleMedium + ) + } + }, + navigationIcon = { + IconButton(onClick = { navController.popBackStack() }) { + Icon(Icons.AutoMirrored.Filled.ArrowBack, contentDescription = "Back") + } + }, + ) + }, + ) { innerPadding -> + Box( + modifier = Modifier + .fillMaxSize() + .padding(innerPadding) + .padding(top = 80.dp) + ) { + AndroidView( + modifier = Modifier + .align(Alignment.TopCenter) + .fillMaxWidth() + .aspectRatio(1f), + factory = { + PreviewView(it).apply { + this.controller = controller + controller.bindToLifecycle(lifecycleOwner) + this.scaleType = PreviewView.ScaleType.FILL_CENTER + } + } + ) + CustomCameraShutter( + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(bottom = 16.dp, start = 20.dp, end = 20.dp), + onShutterClick = { + if (!uiState.isLoading) + viewModel.takePicture( + controller, + ContextCompat.getMainExecutor(context) + ) { + navController.navigate("detail_screen") + } + + }, + ) + Image( + modifier = Modifier + .padding(24.dp) + .align(Alignment.TopCenter), + painter = painterResource(id = R.drawable.scan_frame), + contentDescription = "scan frame" + ) + if (uiState.isLoading) { + CircularProgressIndicator( + modifier = Modifier.align(Alignment.Center), + color = Color.White + ) + } + } + } +} \ No newline at end of file diff --git a/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/presentation/camera/CameraUIState.kt b/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/presentation/camera/CameraUIState.kt new file mode 100644 index 0000000..2ee5925 --- /dev/null +++ b/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/presentation/camera/CameraUIState.kt @@ -0,0 +1,5 @@ +package com.syaroful.agrilinkvocpro.plant_disease_detection_feature.presentation.camera + +data class CameraUIState( + val isLoading: Boolean = false +) \ No newline at end of file diff --git a/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/presentation/camera/CameraViewModel.kt b/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/presentation/camera/CameraViewModel.kt new file mode 100644 index 0000000..fccc0f9 --- /dev/null +++ b/agrilinkvocpro/plant_disease_detection_feature/src/main/java/com/syaroful/agrilinkvocpro/plant_disease_detection_feature/presentation/camera/CameraViewModel.kt @@ -0,0 +1,52 @@ +package com.syaroful.agrilinkvocpro.plant_disease_detection_feature.presentation.camera + +import android.graphics.Bitmap +import android.util.Log +import androidx.camera.view.LifecycleCameraController +import androidx.lifecycle.ViewModel +import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.core.AppConstant +import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.core.extention.cropToSquare +import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.data.repository.CameraRepository +import com.syaroful.agrilinkvocpro.plant_disease_detection_feature.presentation.detail.PlantDiagnosisViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import java.util.concurrent.Executor + +private const val TAG = "CameraViewModel" + +class CameraViewModel( + private val cameraRepository: CameraRepository, + private val diagnosisViewModel: PlantDiagnosisViewModel +) : ViewModel() { + + + private val _uiState = MutableStateFlow(CameraUIState()) + val uiState: StateFlow = _uiState + + private val _bitmap = MutableStateFlow(null) + val bitmap: StateFlow = _bitmap + + val prompt = AppConstant().prompt + + fun takePicture( + controller: LifecycleCameraController, + executor: Executor, + onFinish: () -> Unit + ) { + Log.d(TAG, "takePicture() called") + _uiState.value = _uiState.value.copy(isLoading = true) + + cameraRepository.takePicture(controller, executor) { bitmap -> + Log.d(TAG, "Picture taken, updating state") + val croppedImage = cropToSquare(bitmap) + _bitmap.value = croppedImage +// croppedImage.let { +// diagnosisViewModel.analyzePlant(bitmap = it, prompt = prompt) +// Log.d(TAG, "analyzePlant() called") +// } + _uiState.value = _uiState.value.copy(isLoading = false) + + onFinish() + } + } +} \ No newline at end of file