diff --git a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrUi.kt b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrUi.kt index fa2362ba..2f24a26e 100644 --- a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrUi.kt +++ b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/ocr/OcrUi.kt @@ -34,7 +34,10 @@ import androidx.compose.material3.Text 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.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -89,11 +92,19 @@ private fun CameraPreview( val lifecycleOwner = LocalLifecycleOwner.current val permission = android.Manifest.permission.CAMERA - // UI에서 항상 권한 최신 상태 확인 - val isGranted = ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED - val launcher = rememberLauncherForActivityResult( + /** + * Camera Permission Request + */ + var isGranted by remember { + mutableStateOf( + ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED, + ) + } + val permissionLauncher = rememberLauncherForActivityResult( contract = ActivityResultContracts.RequestPermission(), ) { granted -> + isGranted = granted + if (!granted) { state.eventSink(OcrUiEvent.OnShowPermissionDialog) } @@ -102,45 +113,20 @@ private fun CameraPreview( contract = ActivityResultContracts.StartActivityForResult(), ) { _ -> } - val cameraController = remember { LifecycleCameraController(context) } - val imageAnalyzer = remember { - ImageAnalysis.Analyzer { imageProxy -> - state.eventSink(OcrUiEvent.OnFrameReceived(imageProxy)) - } - } - - val systemUiController = rememberSystemUiController() - - DisposableEffect(systemUiController) { - systemUiController.setSystemBarsColor( - color = Color.Transparent, - darkIcons = false, - isNavigationBarContrastEnforced = false, - ) - - onDispose { - systemUiController.setSystemBarsColor( - color = Color.Transparent, - darkIcons = true, - isNavigationBarContrastEnforced = false, - ) - } - } - // 최초 진입 시 권한 요청 LaunchedEffect(Unit) { if (!isGranted) { state.eventSink(OcrUiEvent.OnHidePermissionDialog) - launcher.launch(permission) + permissionLauncher.launch(permission) } } - // 앱이 포그라운드로 북귀할 때 OS 권한 체크 + // 앱이 포그라운드로 북귀할 때 OS 권한 동기화 DisposableEffect(Unit) { val observer = LifecycleEventObserver { _, event -> if (event == Lifecycle.Event.ON_RESUME) { - val currentGrant = ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED - if (currentGrant) { + isGranted = ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED + if (isGranted) { state.eventSink(OcrUiEvent.OnHidePermissionDialog) } else { state.eventSink(OcrUiEvent.OnShowPermissionDialog) @@ -151,18 +137,52 @@ private fun CameraPreview( onDispose { lifecycleOwner.lifecycle.removeObserver(observer) } } - DisposableEffect(lifecycleOwner, cameraController) { - cameraController.bindToLifecycle(lifecycleOwner) - cameraController.setImageAnalysisAnalyzer( - ContextCompat.getMainExecutor(context), - imageAnalyzer, - ) + /** + * Camera Controller & ImageAnalyzer + */ + val cameraController = remember { LifecycleCameraController(context) } + val imageAnalyzer = remember { + ImageAnalysis.Analyzer { imageProxy -> + state.eventSink(OcrUiEvent.OnFrameReceived(imageProxy)) + } + } + + DisposableEffect(isGranted, lifecycleOwner, cameraController) { + if (isGranted) { + cameraController.bindToLifecycle(lifecycleOwner) + cameraController.setImageAnalysisAnalyzer( + ContextCompat.getMainExecutor(context), + imageAnalyzer, + ) + } + onDispose { cameraController.unbind() cameraController.clearImageAnalysisAnalyzer() } } + /** + * SystemStatusBar Color + */ + val systemUiController = rememberSystemUiController() + + DisposableEffect(systemUiController) { + systemUiController.setSystemBarsColor( + color = Color.Transparent, + darkIcons = false, + isNavigationBarContrastEnforced = false, + ) + + onDispose { + systemUiController.setSystemBarsColor( + color = Color.Transparent, + darkIcons = true, + isNavigationBarContrastEnforced = false, + ) + } + } + ReedScaffold( modifier = modifier.fillMaxSize(), containerColor = Neutral950,