From 9a44a047b19c95595f641c3244fdd82289558c59 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Tue, 27 Jan 2026 22:56:48 +0900 Subject: [PATCH 01/21] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EA=B0=A4=EB=9F=AC?= =?UTF-8?q?=EB=A6=AC=20=EC=82=AC=EC=A7=84=20=EC=84=A0=ED=83=9D=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yapp/twix/main/MainActivity.kt | 5 ++-- .../TaskCertificationScreen.kt | 12 ++++++++ .../TaskCertificationViewModel.kt | 28 +++++++++++++------ .../component/CameraControlBar.kt | 5 +++- .../model/TaskCertificationIntent.kt | 4 +++ .../model/TaskCertificationSideEffect.kt | 2 ++ 6 files changed, 44 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/com/yapp/twix/main/MainActivity.kt b/app/src/main/java/com/yapp/twix/main/MainActivity.kt index 4de9cb22..2e5557f3 100644 --- a/app/src/main/java/com/yapp/twix/main/MainActivity.kt +++ b/app/src/main/java/com/yapp/twix/main/MainActivity.kt @@ -9,7 +9,7 @@ import androidx.compose.foundation.layout.safeContentPadding import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import com.twix.designsystem.theme.TwixTheme -import com.twix.navigation.AppNavHost +import com.twix.task_certification.TaskCertificationRoute import com.twix.ui.toast.ToastHost import com.twix.ui.toast.ToastManager import org.koin.android.ext.android.inject @@ -28,7 +28,8 @@ class MainActivity : ComponentActivity() { .safeContentPadding() .fillMaxSize(), ) { - AppNavHost() + // AppNavHost() + TaskCertificationRoute { } ToastHost( toastManager = toastManager, diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index 3990bb5c..dbe268ab 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -3,6 +3,7 @@ package com.twix.task_certification import android.Manifest import android.content.pm.PackageManager import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.PickVisualMediaRequest import androidx.activity.result.contract.ActivityResultContracts import androidx.compose.foundation.background import androidx.compose.foundation.layout.Column @@ -68,6 +69,11 @@ fun TaskCertificationRoute( hasPermission = granted } + val pickMedia = + rememberLauncherForActivityResult(ActivityResultContracts.PickVisualMedia()) { uri -> + viewModel.dispatch(TaskCertificationIntent.PickPicture(uri)) + } + LaunchedEffect(Unit) { if (!hasPermission) { permissionLauncher.launch(Manifest.permission.CAMERA) @@ -107,6 +113,9 @@ fun TaskCertificationRoute( onClickFlash = { viewModel.dispatch(TaskCertificationIntent.ToggleFlash) }, + onClickGallery = { + pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) + }, ) } @@ -118,6 +127,7 @@ private fun TaskCertificationScreen( onCaptureClick: () -> Unit, onToggleCameraClick: () -> Unit, onClickFlash: () -> Unit, + onClickGallery: () -> Unit, ) { Column( Modifier @@ -151,6 +161,7 @@ private fun TaskCertificationScreen( CameraControlBar( onCaptureClick = onCaptureClick, onToggleCameraClick = onToggleCameraClick, + onClickGallery = onClickGallery, ) } } @@ -166,6 +177,7 @@ fun TaskCertificationScreenPreview() { onCaptureClick = {}, onToggleCameraClick = {}, onClickFlash = {}, + onClickGallery = {}, ) } } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt index dd97dba4..5fb56820 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt @@ -19,6 +19,10 @@ class TaskCertificationViewModel : takePicture(intent.uri) } + is TaskCertificationIntent.PickPicture -> { + pickPicture(intent.uri) + } + is TaskCertificationIntent.ToggleLens -> { toggleLens() } @@ -30,17 +34,23 @@ class TaskCertificationViewModel : } private fun takePicture(uri: Uri?) { - uri?.let { - reduce { updateCapturedImage(uri) } - if (uiState.value.torch == TorchStatus.On) { - reduce { toggleTorch() } - } - } ?: run { onFailureCapture() } + uri?.let { updatePickPicture(it) } ?: viewModelScope.launch { + emitSideEffect( + TaskCertificationSideEffect.ImageCaptureFailException, + ) + } + } + + private fun pickPicture(uri: Uri?) { + uri?.let { updatePickPicture(uri) } ?: viewModelScope.launch { + emitSideEffect(TaskCertificationSideEffect.ImagePickFailException) + } } - private fun onFailureCapture() { - viewModelScope.launch { - emitSideEffect(TaskCertificationSideEffect.ImageCaptureFailException) + private fun updatePickPicture(uri: Uri) { + reduce { updateCapturedImage(uri) } + if (uiState.value.torch == TorchStatus.On) { + reduce { toggleTorch() } } } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt index 02e66d83..aa8bbfe7 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt @@ -26,6 +26,7 @@ internal fun CameraControlBar( modifier: Modifier = Modifier, onCaptureClick: () -> Unit, onToggleCameraClick: () -> Unit, + onClickGallery: () -> Unit, ) { Row( modifier = @@ -43,7 +44,8 @@ internal fun CameraControlBar( modifier = Modifier .size(52.dp) - .clip(RoundedCornerShape(3.dp)), + .clip(RoundedCornerShape(3.dp)) + .noRippleClickable(onClickGallery), ) Image( @@ -69,5 +71,6 @@ private fun CameraControlBarPreview() { CameraControlBar( onCaptureClick = {}, onToggleCameraClick = {}, + onClickGallery = {}, ) } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt index cc714833..054e4c52 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt @@ -8,6 +8,10 @@ sealed interface TaskCertificationIntent : Intent { val uri: Uri?, ) : TaskCertificationIntent + data class PickPicture( + val uri: Uri?, + ) : TaskCertificationIntent + data object ToggleLens : TaskCertificationIntent data object ToggleFlash : TaskCertificationIntent diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationSideEffect.kt b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationSideEffect.kt index e9188a6c..0e099502 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationSideEffect.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationSideEffect.kt @@ -4,4 +4,6 @@ import com.twix.ui.base.SideEffect sealed interface TaskCertificationSideEffect : SideEffect { data object ImageCaptureFailException : TaskCertificationSideEffect + + data object ImagePickFailException : TaskCertificationSideEffect } From 634cf7a3a9052e23323d0c12bc79799f87552686 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Wed, 28 Jan 2026 00:57:50 +0900 Subject: [PATCH 02/21] =?UTF-8?q?=E2=9C=A8=20Feat:=20ObserveAsEvents=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EC=A0=80=EB=B8=94=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/twix/ui/base/ObserveAsEvents.kt | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 core/ui/src/main/java/com/twix/ui/base/ObserveAsEvents.kt diff --git a/core/ui/src/main/java/com/twix/ui/base/ObserveAsEvents.kt b/core/ui/src/main/java/com/twix/ui/base/ObserveAsEvents.kt new file mode 100644 index 00000000..76c729d1 --- /dev/null +++ b/core/ui/src/main/java/com/twix/ui/base/ObserveAsEvents.kt @@ -0,0 +1,38 @@ +package com.twix.ui.base + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.lifecycle.repeatOnLifecycle +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext + +/** + * [Flow]를 통해 전달되는 일회성 이벤트(Side Effect)를 Lifecycle에 안전하게 관찰하기 위한 Composable 함수입니다. + * * 주로 ViewModel의 Channel이나 SharedFlow를 통해 전달되는 네비게이션, 스낵바 표시 등의 + * UI 이벤트를 처리할 때 사용됩니다. + * + * @param T 이벤트의 타입. + * @param flow 관찰할 [Flow] 인스턴스 (예: ViewModel의 events). + * @param onEvent 이벤트가 발생했을 때 실행할 suspend 콜백 함수. + * + */ +@Composable +fun ObserveAsEvents( + flow: Flow, + event: suspend (T) -> Unit, +) { + val lifecycleOwner = LocalLifecycleOwner.current + LaunchedEffect(flow, lifecycleOwner) { + // lifecycleOwner가 STARTED 상태일 때만 수집을 시작 + // STOPPED 상태가 되면 수집 코루틴을 자동으로 취소 + lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + // UI 변경 작업은 즉시 메인 스레드에서 실행되도록 보장 + withContext(Dispatchers.Main.immediate) { + flow.collect(event) + } + } + } +} From c98e7bb6ae7e1a0cb68b33f2518c55085b56dd21 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Fri, 30 Jan 2026 22:24:46 +0900 Subject: [PATCH 03/21] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EC=82=AC=EC=A7=84=20?= =?UTF-8?q?=EC=B4=AC=EC=98=81,=20=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=8B=A4?= =?UTF-8?q?=ED=8C=A8=EC=8B=9C=20=EC=97=90=EB=9F=AC=20=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=20=EC=B6=9C=EB=A0=A5=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../TaskCertificationScreen.kt | 28 +++++++++++++++++++ .../src/main/res/values/strings.xml | 2 ++ 2 files changed, 30 insertions(+) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index dbe268ab..da9325ef 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -35,13 +35,19 @@ import com.twix.task_certification.component.CameraPreviewBox import com.twix.task_certification.component.TaskCertificationTopBar import com.twix.task_certification.model.CameraPreview import com.twix.task_certification.model.TaskCertificationIntent +import com.twix.task_certification.model.TaskCertificationSideEffect import com.twix.task_certification.model.TaskCertificationUiState +import com.twix.ui.base.ObserveAsEvents +import com.twix.ui.toast.ToastManager +import com.twix.ui.toast.model.ToastData +import com.twix.ui.toast.model.ToastType import kotlinx.coroutines.launch import org.koin.androidx.compose.koinViewModel import org.koin.compose.koinInject @Composable fun TaskCertificationRoute( + toastManager: ToastManager = koinInject(), camera: Camera = koinInject(), viewModel: TaskCertificationViewModel = koinViewModel(), navigateToBack: () -> Unit, @@ -92,6 +98,28 @@ fun TaskCertificationRoute( } } + ObserveAsEvents(viewModel.sideEffect) { event -> + when (event) { + TaskCertificationSideEffect.ImageCaptureFailException -> { + toastManager.tryShow( + ToastData( + message = context.getString(R.string.task_certification_image_capture_fail), + type = ToastType.ERROR, + ), + ) + } + + TaskCertificationSideEffect.ImagePickFailException -> { + toastManager.tryShow( + ToastData( + message = context.getString(R.string.task_certification_image_pick_fail), + type = ToastType.ERROR, + ), + ) + } + } + } + TaskCertificationScreen( uiState = uiState, cameraPreview = cameraPreview, diff --git a/feature/task-certification/src/main/res/values/strings.xml b/feature/task-certification/src/main/res/values/strings.xml index 88875eed..7e10708c 100644 --- a/feature/task-certification/src/main/res/values/strings.xml +++ b/feature/task-certification/src/main/res/values/strings.xml @@ -1,3 +1,5 @@ 인증샷 찍기 + 이미지 캡처에 실패했습니다. 다시 시도해 주세요. + 이미지를 불러오는 데 실패했습니다. 다시 시도해 주세요. From 3993ff403baccb5a56e55957a85a4177e7baa74f Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Fri, 30 Jan 2026 23:48:48 +0900 Subject: [PATCH 04/21] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EA=B3=B5=EC=9A=A9=20?= =?UTF-8?q?Round=20Button=20=EC=BB=B4=ED=8F=AC=EC=A0=80=EB=B8=94=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../designsystem/components/AppRoundButton.kt | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 core/design-system/src/main/java/com/twix/designsystem/components/AppRoundButton.kt diff --git a/core/design-system/src/main/java/com/twix/designsystem/components/AppRoundButton.kt b/core/design-system/src/main/java/com/twix/designsystem/components/AppRoundButton.kt new file mode 100644 index 00000000..39b72325 --- /dev/null +++ b/core/design-system/src/main/java/com/twix/designsystem/components/AppRoundButton.kt @@ -0,0 +1,99 @@ +package com.twix.designsystem.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.twix.designsystem.components.text.AppText +import com.twix.designsystem.theme.CommonColor +import com.twix.designsystem.theme.GrayColor +import com.twix.designsystem.theme.TwixTheme +import com.twix.domain.model.enums.AppTextStyle + +@Composable +fun AppRoundButton( + modifier: Modifier = Modifier, + borderColor: Color = GrayColor.C500, + backgroundColor: Color = CommonColor.White, + content: @Composable () -> Unit, +) { + Box( + modifier = modifier, + ) { + Box( + modifier = + Modifier + .fillMaxSize() + .offset(y = 4.dp) + .background( + color = borderColor, + shape = RoundedCornerShape(100), + ), + ) + + Box( + modifier = + Modifier + .fillMaxSize() + .background( + color = backgroundColor, + shape = RoundedCornerShape(100), + ).border( + width = 1.6.dp, + color = borderColor, + shape = RoundedCornerShape(100), + ), + contentAlignment = Alignment.Center, + ) { + content() + } + } +} + +@Preview(showBackground = true) +@Composable +fun AppRoundButtonPreview() { + TwixTheme { + Column { + AppRoundButton( + modifier = + Modifier + .width(330.dp) + .height(68.dp), + ) { + AppText( + style = AppTextStyle.T2, + color = GrayColor.C500, + text = "버튼임니다", + ) + } + Spacer(modifier = Modifier.height(10.dp)) + AppRoundButton( + backgroundColor = GrayColor.C500, + modifier = + Modifier + .width(330.dp) + .height(68.dp), + ) { + AppText( + style = AppTextStyle.T2, + color = CommonColor.White, + text = "버튼임니다", + ) + } + Spacer(modifier = Modifier.height(10.dp)) + } + } +} From 60c6028d5f30bd6609df7ef5e4435cfa367ed326 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Fri, 30 Jan 2026 23:49:32 +0900 Subject: [PATCH 05/21] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EC=82=AC=EC=A7=84=20?= =?UTF-8?q?=EB=8B=A4=EC=8B=9C=EC=B0=8D=EA=B8=B0,=20=EA=B0=A4=EB=9F=AC?= =?UTF-8?q?=EB=A6=AC=20=EB=B2=84=ED=8A=BC=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/res/drawable/ic_camera_refresh.xml | 13 +++++++++++++ .../src/main/res/drawable/ic_gallery.xml | 19 +++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 feature/task-certification/src/main/res/drawable/ic_camera_refresh.xml create mode 100644 feature/task-certification/src/main/res/drawable/ic_gallery.xml diff --git a/feature/task-certification/src/main/res/drawable/ic_camera_refresh.xml b/feature/task-certification/src/main/res/drawable/ic_camera_refresh.xml new file mode 100644 index 00000000..98b8f423 --- /dev/null +++ b/feature/task-certification/src/main/res/drawable/ic_camera_refresh.xml @@ -0,0 +1,13 @@ + + + diff --git a/feature/task-certification/src/main/res/drawable/ic_gallery.xml b/feature/task-certification/src/main/res/drawable/ic_gallery.xml new file mode 100644 index 00000000..e57d49ca --- /dev/null +++ b/feature/task-certification/src/main/res/drawable/ic_gallery.xml @@ -0,0 +1,19 @@ + + + + + From 41f034d045ed747614f9e881bb388ec702c17b7c Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Fri, 30 Jan 2026 23:51:00 +0900 Subject: [PATCH 06/21] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EC=82=AC=EC=A7=84=20?= =?UTF-8?q?=EB=8B=A4=EC=8B=9C=EC=B0=8D=EA=B8=B0=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/yapp/twix/main/MainActivity.kt | 5 +- .../TaskCertificationScreen.kt | 11 ++ .../TaskCertificationViewModel.kt | 8 ++ .../component/CameraControlBar.kt | 126 +++++++++++++++--- .../model/TaskCertificationIntent.kt | 2 + .../model/TaskCertificationUiState.kt | 2 + 6 files changed, 136 insertions(+), 18 deletions(-) diff --git a/app/src/main/java/com/yapp/twix/main/MainActivity.kt b/app/src/main/java/com/yapp/twix/main/MainActivity.kt index 2e5557f3..4de9cb22 100644 --- a/app/src/main/java/com/yapp/twix/main/MainActivity.kt +++ b/app/src/main/java/com/yapp/twix/main/MainActivity.kt @@ -9,7 +9,7 @@ import androidx.compose.foundation.layout.safeContentPadding import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import com.twix.designsystem.theme.TwixTheme -import com.twix.task_certification.TaskCertificationRoute +import com.twix.navigation.AppNavHost import com.twix.ui.toast.ToastHost import com.twix.ui.toast.ToastManager import org.koin.android.ext.android.inject @@ -28,8 +28,7 @@ class MainActivity : ComponentActivity() { .safeContentPadding() .fillMaxSize(), ) { - // AppNavHost() - TaskCertificationRoute { } + AppNavHost() ToastHost( toastManager = toastManager, diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index da9325ef..a0448ef0 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -144,6 +144,10 @@ fun TaskCertificationRoute( onClickGallery = { pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)) }, + onClickRefresh = { + viewModel.dispatch(TaskCertificationIntent.RetakePicture) + }, + onClickUpload = { }, ) } @@ -156,6 +160,8 @@ private fun TaskCertificationScreen( onToggleCameraClick: () -> Unit, onClickFlash: () -> Unit, onClickGallery: () -> Unit, + onClickRefresh: () -> Unit, + onClickUpload: () -> Unit, ) { Column( Modifier @@ -187,9 +193,12 @@ private fun TaskCertificationScreen( Spacer(modifier = Modifier.height(52.dp)) CameraControlBar( + capture = uiState.capture, onCaptureClick = onCaptureClick, onToggleCameraClick = onToggleCameraClick, onClickGallery = onClickGallery, + onClickRefresh = onClickRefresh, + onClickUpload = onClickUpload, ) } } @@ -206,6 +215,8 @@ fun TaskCertificationScreenPreview() { onToggleCameraClick = {}, onClickFlash = {}, onClickGallery = {}, + onClickRefresh = {}, + onClickUpload = {}, ) } } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt index 5fb56820..9690fb8d 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt @@ -30,6 +30,10 @@ class TaskCertificationViewModel : is TaskCertificationIntent.ToggleFlash -> { toggleTorch() } + + is TaskCertificationIntent.RetakePicture -> { + setupRetake() + } } } @@ -61,4 +65,8 @@ class TaskCertificationViewModel : private fun toggleTorch() { reduce { toggleTorch() } } + + private fun setupRetake() { + reduce { removePicture() } + } } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt index aa8bbfe7..fadc4ac9 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt @@ -2,49 +2,85 @@ package com.twix.task_certification.component import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.core.net.toUri +import com.twix.designsystem.components.AppRoundButton +import com.twix.designsystem.components.text.AppText +import com.twix.designsystem.theme.CommonColor +import com.twix.designsystem.theme.GrayColor +import com.twix.designsystem.theme.TwixTheme +import com.twix.domain.model.enums.AppTextStyle import com.twix.task_certification.R +import com.twix.task_certification.model.CaptureStatus import com.twix.ui.extension.noRippleClickable @Composable internal fun CameraControlBar( + capture: CaptureStatus, + onCaptureClick: () -> Unit, + onToggleCameraClick: () -> Unit, + onClickGallery: () -> Unit, + onClickRefresh: () -> Unit, + onClickUpload: () -> Unit, modifier: Modifier = Modifier, +) { + when (capture) { + CaptureStatus.NotCaptured -> { + ImageNotCapturedBar( + onCaptureClick = onCaptureClick, + onToggleCameraClick = onToggleCameraClick, + onClickGallery = onClickGallery, + modifier = modifier, + ) + } + + is CaptureStatus.Captured -> { + ImageCapturedBar( + onClickRefresh = onClickRefresh, + onClickUpload = onClickUpload, + modifier = modifier, + ) + } + } +} + +@Composable +private fun ImageNotCapturedBar( onCaptureClick: () -> Unit, onToggleCameraClick: () -> Unit, onClickGallery: () -> Unit, + modifier: Modifier = Modifier, ) { Row( modifier = modifier .fillMaxWidth() .wrapContentHeight() - .padding(horizontal = 41.dp), + .padding(horizontal = 45.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { Image( - painter = painterResource(R.drawable.btn), + imageVector = ImageVector.vectorResource(R.drawable.ic_gallery), contentDescription = null, - contentScale = ContentScale.Crop, modifier = Modifier - .size(52.dp) - .clip(RoundedCornerShape(3.dp)) + .size(56.dp) .noRippleClickable(onClickGallery), ) @@ -55,22 +91,82 @@ internal fun CameraControlBar( Modifier .noRippleClickable(onCaptureClick), ) + Image( imageVector = ImageVector.vectorResource(R.drawable.ic_camera_toggle), contentDescription = null, modifier = Modifier + .size(56.dp) .noRippleClickable(onToggleCameraClick), ) } } -@Preview(showBackground = true) +@Composable +private fun ImageCapturedBar( + onClickRefresh: () -> Unit, + onClickUpload: () -> Unit, + modifier: Modifier = Modifier, +) { + Row( + modifier = + modifier + .padding(horizontal = 58.dp), + horizontalArrangement = Arrangement.Center, + verticalAlignment = Alignment.CenterVertically, + ) { + Image( + imageVector = ImageVector.vectorResource(R.drawable.ic_camera_refresh), + contentDescription = null, + modifier = + Modifier + .size(50.dp) + .noRippleClickable(onClickRefresh), + ) + + Spacer(modifier = modifier.width(12.dp)) + + AppRoundButton( + borderColor = CommonColor.White, + backgroundColor = GrayColor.C500, + modifier = + Modifier + .width(150.dp) + .height(74.dp) + .noRippleClickable(onClickUpload), + ) { + AppText( + text = "업로드", + style = AppTextStyle.T2, + color = CommonColor.White, + ) + } + } +} + +@Preview(showBackground = true, backgroundColor = 0xFF000000) @Composable private fun CameraControlBarPreview() { - CameraControlBar( - onCaptureClick = {}, - onToggleCameraClick = {}, - onClickGallery = {}, - ) + TwixTheme { + Column { + CameraControlBar( + capture = CaptureStatus.NotCaptured, + onCaptureClick = {}, + onToggleCameraClick = {}, + onClickGallery = {}, + onClickRefresh = {}, + onClickUpload = {}, + ) + + CameraControlBar( + capture = CaptureStatus.Captured("".toUri()), + onCaptureClick = {}, + onToggleCameraClick = {}, + onClickGallery = {}, + onClickRefresh = {}, + onClickUpload = {}, + ) + } + } } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt index 054e4c52..6e34a235 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationIntent.kt @@ -15,4 +15,6 @@ sealed interface TaskCertificationIntent : Intent { data object ToggleLens : TaskCertificationIntent data object ToggleFlash : TaskCertificationIntent + + data object RetakePicture : TaskCertificationIntent } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationUiState.kt b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationUiState.kt index cfb0e58d..4cc38a42 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationUiState.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationUiState.kt @@ -28,4 +28,6 @@ data class TaskCertificationUiState( } fun updateCapturedImage(uri: Uri) = copy(capture = CaptureStatus.Captured(uri)) + + fun removePicture(): TaskCertificationUiState = copy(capture = CaptureStatus.NotCaptured) } From 822a20096f910e344cb0e27bf379a9b52cac1cb4 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Sat, 31 Jan 2026 00:01:47 +0900 Subject: [PATCH 07/21] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=82=AC=EC=A7=84=20=EC=B4=AC=EC=98=81=20=EC=8B=A4=ED=8C=A8?= =?UTF-8?q?=EC=8B=9C=20=EC=97=90=EB=9F=AC=EC=B2=98=EB=A6=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/twix/task_certification/TaskCertificationScreen.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index a0448ef0..7644e5eb 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -132,6 +132,8 @@ fun TaskCertificationRoute( .takePicture() .onSuccess { viewModel.dispatch(TaskCertificationIntent.TakePicture(it)) + }.onFailure { + viewModel.dispatch(TaskCertificationIntent.TakePicture(null)) } } }, From 59f7be2353795da26a413d7160f03f80153390fc Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Sat, 31 Jan 2026 01:31:16 +0900 Subject: [PATCH 08/21] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EB=A6=AC=ED=94=8C=20?= =?UTF-8?q?=ED=9A=A8=EA=B3=BC=20=EC=A0=9C=EA=B1=B0=20=ED=95=A8=EC=88=98?= =?UTF-8?q?=EC=97=90=20enable=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/twix/ui/extension/NoRippleClickable.kt | 6 +++++- .../twix/task_certification/component/CameraControlBar.kt | 2 +- .../twix/task_certification/component/CameraPreviewBox.kt | 2 +- .../task_certification/component/TaskCertificationTopBar.kt | 2 +- 4 files changed, 8 insertions(+), 4 deletions(-) diff --git a/core/ui/src/main/java/com/twix/ui/extension/NoRippleClickable.kt b/core/ui/src/main/java/com/twix/ui/extension/NoRippleClickable.kt index a6186e54..b8ce2ebc 100644 --- a/core/ui/src/main/java/com/twix/ui/extension/NoRippleClickable.kt +++ b/core/ui/src/main/java/com/twix/ui/extension/NoRippleClickable.kt @@ -7,9 +7,13 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Modifier @Composable -fun Modifier.noRippleClickable(onClick: () -> Unit): Modifier = +fun Modifier.noRippleClickable( + enabled: Boolean = true, + onClick: () -> Unit, +): Modifier = clickable( indication = null, interactionSource = remember { MutableInteractionSource() }, onClick = onClick, + enabled = enabled, ) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt index fadc4ac9..cdd2d626 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt @@ -134,7 +134,7 @@ private fun ImageCapturedBar( Modifier .width(150.dp) .height(74.dp) - .noRippleClickable(onClickUpload), + .noRippleClickable(onClick = onClickUpload), ) { AppText( text = "업로드", diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraPreviewBox.kt b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraPreviewBox.kt index 96597674..677ff074 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraPreviewBox.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraPreviewBox.kt @@ -96,7 +96,7 @@ private fun TorchIcon( contentDescription = null, modifier = Modifier - .noRippleClickable(onClickFlash) + .noRippleClickable(onClick = onClickFlash) .padding(start = 30.33.dp, top = 31.82.dp), ) } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/component/TaskCertificationTopBar.kt b/feature/task-certification/src/main/java/com/twix/task_certification/component/TaskCertificationTopBar.kt index 32c917c3..e070201c 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/component/TaskCertificationTopBar.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/component/TaskCertificationTopBar.kt @@ -34,7 +34,7 @@ internal fun TaskCertificationTopBar( Modifier .padding(24.dp) .align(Alignment.CenterEnd) - .noRippleClickable(onClickClose), + .noRippleClickable(onClick = onClickClose), ) } } From afadfd8d70efbee2cd9b99f83575e974dab4fafc Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Sat, 31 Jan 2026 01:31:49 +0900 Subject: [PATCH 09/21] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EC=82=AC=EC=A7=84=20?= =?UTF-8?q?=EC=B4=AC=EC=98=81=EC=8B=9C=20=EC=B4=AC=EC=98=81,=EA=B0=A4?= =?UTF-8?q?=EB=9F=AC=EB=A6=AC,=20=EB=A0=8C=EC=A6=88=20=ED=86=A0=EA=B8=80?= =?UTF-8?q?=20=EB=B2=84=ED=8A=BC=20=EB=B9=84=ED=99=9C=EC=84=B1=ED=99=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/CameraControlBar.kt | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt index cdd2d626..37a1a57f 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt @@ -12,6 +12,10 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +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.graphics.vector.ImageVector @@ -66,6 +70,8 @@ private fun ImageNotCapturedBar( onClickGallery: () -> Unit, modifier: Modifier = Modifier, ) { + var enabled by rememberSaveable { mutableStateOf(true) } + Row( modifier = modifier @@ -81,7 +87,7 @@ private fun ImageNotCapturedBar( modifier = Modifier .size(56.dp) - .noRippleClickable(onClickGallery), + .noRippleClickable(enabled = enabled, onClick = onClickGallery), ) Image( @@ -89,7 +95,10 @@ private fun ImageNotCapturedBar( contentDescription = null, modifier = Modifier - .noRippleClickable(onCaptureClick), + .noRippleClickable(enabled = enabled) { + onCaptureClick() + enabled = false + }, ) Image( @@ -98,7 +107,7 @@ private fun ImageNotCapturedBar( modifier = Modifier .size(56.dp) - .noRippleClickable(onToggleCameraClick), + .noRippleClickable(enabled = enabled, onClick = onToggleCameraClick), ) } } @@ -122,7 +131,7 @@ private fun ImageCapturedBar( modifier = Modifier .size(50.dp) - .noRippleClickable(onClickRefresh), + .noRippleClickable(onClick = onClickRefresh), ) Spacer(modifier = modifier.width(12.dp)) From 131e0dc324b5d9ebc44531a217530642b5ef3aa4 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Sat, 31 Jan 2026 01:38:23 +0900 Subject: [PATCH 10/21] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EA=B0=A4=EB=9F=AC=EB=A6=AC=EC=97=90=EC=84=9C=20=EC=84=A0?= =?UTF-8?q?=ED=83=9D=EB=90=9C=20=EC=9D=B4=EB=AF=B8=EC=A7=80=EA=B0=80=20nul?= =?UTF-8?q?l=EC=9D=BC=EC=8B=9C=20=EC=97=90=EB=9F=AC=EC=B2=98=EB=A6=AC=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../twix/task_certification/TaskCertificationScreen.kt | 9 --------- .../task_certification/TaskCertificationViewModel.kt | 4 +--- .../model/TaskCertificationSideEffect.kt | 2 -- 3 files changed, 1 insertion(+), 14 deletions(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index 7644e5eb..52d194dc 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -108,15 +108,6 @@ fun TaskCertificationRoute( ), ) } - - TaskCertificationSideEffect.ImagePickFailException -> { - toastManager.tryShow( - ToastData( - message = context.getString(R.string.task_certification_image_pick_fail), - type = ToastType.ERROR, - ), - ) - } } } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt index 9690fb8d..6114cf17 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationViewModel.kt @@ -46,9 +46,7 @@ class TaskCertificationViewModel : } private fun pickPicture(uri: Uri?) { - uri?.let { updatePickPicture(uri) } ?: viewModelScope.launch { - emitSideEffect(TaskCertificationSideEffect.ImagePickFailException) - } + uri?.let { updatePickPicture(uri) } } private fun updatePickPicture(uri: Uri) { diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationSideEffect.kt b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationSideEffect.kt index 0e099502..e9188a6c 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationSideEffect.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/model/TaskCertificationSideEffect.kt @@ -4,6 +4,4 @@ import com.twix.ui.base.SideEffect sealed interface TaskCertificationSideEffect : SideEffect { data object ImageCaptureFailException : TaskCertificationSideEffect - - data object ImagePickFailException : TaskCertificationSideEffect } From bff39f520c40b0c45ee7d2c447513c03cb3e9abb Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Sat, 31 Jan 2026 12:15:25 +0900 Subject: [PATCH 11/21] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=ED=95=98=EC=9C=84=20=EC=BB=B4=ED=8F=AC=EC=A0=80=EB=B8=94?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EC=82=AC=EC=9A=A9=EC=A4=91=EC=9D=B8=20?= =?UTF-8?q?=EB=B6=80=EB=AA=A8=20=EC=BB=B4=ED=8F=AC=EC=A0=80=EB=B8=94?= =?UTF-8?q?=EC=9D=98=20modifier=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/twix/task_certification/component/CameraControlBar.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt index 37a1a57f..ad87f96b 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt @@ -134,7 +134,7 @@ private fun ImageCapturedBar( .noRippleClickable(onClick = onClickRefresh), ) - Spacer(modifier = modifier.width(12.dp)) + Spacer(modifier = Modifier.width(12.dp)) AppRoundButton( borderColor = CommonColor.White, From 45ae310d1ed4897473828919f05318b2f6bb9020 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Sat, 31 Jan 2026 12:19:12 +0900 Subject: [PATCH 12/21] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EA=B3=B5=EC=9A=A9=20round=20=EB=B2=84=ED=8A=BC=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../twix/designsystem/components/{ => button}/AppRoundButton.kt | 2 +- .../com/twix/task_certification/component/CameraControlBar.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename core/design-system/src/main/java/com/twix/designsystem/components/{ => button}/AppRoundButton.kt (98%) diff --git a/core/design-system/src/main/java/com/twix/designsystem/components/AppRoundButton.kt b/core/design-system/src/main/java/com/twix/designsystem/components/button/AppRoundButton.kt similarity index 98% rename from core/design-system/src/main/java/com/twix/designsystem/components/AppRoundButton.kt rename to core/design-system/src/main/java/com/twix/designsystem/components/button/AppRoundButton.kt index 39b72325..933dd70e 100644 --- a/core/design-system/src/main/java/com/twix/designsystem/components/AppRoundButton.kt +++ b/core/design-system/src/main/java/com/twix/designsystem/components/button/AppRoundButton.kt @@ -1,4 +1,4 @@ -package com.twix.designsystem.components +package com.twix.designsystem.components.button import androidx.compose.foundation.background import androidx.compose.foundation.border diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt index ad87f96b..4bcc4806 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt @@ -23,7 +23,7 @@ import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.net.toUri -import com.twix.designsystem.components.AppRoundButton +import com.twix.designsystem.components.button.AppRoundButton import com.twix.designsystem.components.text.AppText import com.twix.designsystem.theme.CommonColor import com.twix.designsystem.theme.GrayColor From 213270aacb03f78c6a0ed0a462e352e2eeade3a4 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Sat, 31 Jan 2026 14:31:23 +0900 Subject: [PATCH 13/21] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=9E=AC=EC=B4=AC=EC=98=81=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/CameraControlBar.kt | 1 + .../src/main/res/drawable/ic_camera_refresh.xml | 13 ------------- .../src/main/res/drawable/ic_camera_retake.xml | 13 +++++++++++++ 3 files changed, 14 insertions(+), 13 deletions(-) delete mode 100644 feature/task-certification/src/main/res/drawable/ic_camera_refresh.xml create mode 100644 feature/task-certification/src/main/res/drawable/ic_camera_retake.xml diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt index 4bcc4806..446ddf2d 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt @@ -127,6 +127,7 @@ private fun ImageCapturedBar( ) { Image( imageVector = ImageVector.vectorResource(R.drawable.ic_camera_refresh), + imageVector = ImageVector.vectorResource(R.drawable.ic_camera_retake), contentDescription = null, modifier = Modifier diff --git a/feature/task-certification/src/main/res/drawable/ic_camera_refresh.xml b/feature/task-certification/src/main/res/drawable/ic_camera_refresh.xml deleted file mode 100644 index 98b8f423..00000000 --- a/feature/task-certification/src/main/res/drawable/ic_camera_refresh.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - diff --git a/feature/task-certification/src/main/res/drawable/ic_camera_retake.xml b/feature/task-certification/src/main/res/drawable/ic_camera_retake.xml new file mode 100644 index 00000000..2b9675f4 --- /dev/null +++ b/feature/task-certification/src/main/res/drawable/ic_camera_retake.xml @@ -0,0 +1,13 @@ + + + From 7a33d2e26f68587a45def8b794cde206545b13fd Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Sat, 31 Jan 2026 14:31:40 +0900 Subject: [PATCH 14/21] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EB=B2=84=ED=8A=BC=20=EC=A4=91?= =?UTF-8?q?=EC=95=99=20=EC=A0=95=EB=A0=AC=EC=9D=B4=20=EB=A7=9E=EC=A7=80=20?= =?UTF-8?q?=EC=95=8A=EB=8A=94=20=EB=AC=B8=EC=A0=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/CameraControlBar.kt | 42 +++++++++++-------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt index 446ddf2d..6ad2ccf2 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt @@ -2,6 +2,7 @@ package com.twix.task_certification.component import androidx.compose.foundation.Image 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.Spacer @@ -118,39 +119,44 @@ private fun ImageCapturedBar( onClickUpload: () -> Unit, modifier: Modifier = Modifier, ) { - Row( + Box( modifier = modifier + .fillMaxWidth() .padding(horizontal = 58.dp), - horizontalArrangement = Arrangement.Center, - verticalAlignment = Alignment.CenterVertically, ) { Image( - imageVector = ImageVector.vectorResource(R.drawable.ic_camera_refresh), imageVector = ImageVector.vectorResource(R.drawable.ic_camera_retake), contentDescription = null, modifier = Modifier - .size(50.dp) + .size(52.dp) + .align(Alignment.CenterStart) .noRippleClickable(onClick = onClickRefresh), ) - Spacer(modifier = Modifier.width(12.dp)) - - AppRoundButton( - borderColor = CommonColor.White, - backgroundColor = GrayColor.C500, + Row( modifier = Modifier - .width(150.dp) - .height(74.dp) - .noRippleClickable(onClick = onClickUpload), + .align(Alignment.Center), ) { - AppText( - text = "업로드", - style = AppTextStyle.T2, - color = CommonColor.White, - ) + Spacer(modifier = Modifier.width(12.dp)) + + AppRoundButton( + borderColor = CommonColor.White, + backgroundColor = GrayColor.C500, + modifier = + Modifier + .width(150.dp) + .height(74.dp) + .noRippleClickable(onClick = onClickUpload), + ) { + AppText( + text = "업로드", + style = AppTextStyle.T2, + color = CommonColor.White, + ) + } } } } From 2fab411862ef46afc14dd080af28c56c97dde016 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Tue, 3 Feb 2026 21:48:52 +0900 Subject: [PATCH 15/21] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20ObserveA?= =?UTF-8?q?sEvents=EC=9D=98=20=EC=9D=B8=EC=9E=90=EB=AA=85=20=EC=88=98?= =?UTF-8?q?=EC=A0=95,=20withContext=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/twix/ui/base/ObserveAsEvents.kt | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/core/ui/src/main/java/com/twix/ui/base/ObserveAsEvents.kt b/core/ui/src/main/java/com/twix/ui/base/ObserveAsEvents.kt index 76c729d1..33a1c601 100644 --- a/core/ui/src/main/java/com/twix/ui/base/ObserveAsEvents.kt +++ b/core/ui/src/main/java/com/twix/ui/base/ObserveAsEvents.kt @@ -5,9 +5,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.LocalLifecycleOwner import androidx.lifecycle.repeatOnLifecycle -import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.Flow -import kotlinx.coroutines.withContext /** * [Flow]를 통해 전달되는 일회성 이벤트(Side Effect)를 Lifecycle에 안전하게 관찰하기 위한 Composable 함수입니다. @@ -21,18 +19,15 @@ import kotlinx.coroutines.withContext */ @Composable fun ObserveAsEvents( - flow: Flow, - event: suspend (T) -> Unit, + event: Flow, + onEvent: suspend (T) -> Unit, ) { val lifecycleOwner = LocalLifecycleOwner.current - LaunchedEffect(flow, lifecycleOwner) { + LaunchedEffect(event, lifecycleOwner) { // lifecycleOwner가 STARTED 상태일 때만 수집을 시작 // STOPPED 상태가 되면 수집 코루틴을 자동으로 취소 lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { - // UI 변경 작업은 즉시 메인 스레드에서 실행되도록 보장 - withContext(Dispatchers.Main.immediate) { - flow.collect(event) - } + event.collect(onEvent) } } } From 12297dd4cb38c917bdec1c609ce7167c0bd07c17 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Tue, 3 Feb 2026 22:02:20 +0900 Subject: [PATCH 16/21] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20AppRound?= =?UTF-8?q?Button=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../components/button/AppRoundButton.kt | 73 +++++++++++-------- .../component/CameraControlBar.kt | 12 +-- 2 files changed, 45 insertions(+), 40 deletions(-) diff --git a/core/design-system/src/main/java/com/twix/designsystem/components/button/AppRoundButton.kt b/core/design-system/src/main/java/com/twix/designsystem/components/button/AppRoundButton.kt index 933dd70e..d2df0a12 100644 --- a/core/design-system/src/main/java/com/twix/designsystem/components/button/AppRoundButton.kt +++ b/core/design-system/src/main/java/com/twix/designsystem/components/button/AppRoundButton.kt @@ -24,24 +24,29 @@ import com.twix.domain.model.enums.AppTextStyle @Composable fun AppRoundButton( + text: String, + textColor: Color, modifier: Modifier = Modifier, + textStyle: AppTextStyle = AppTextStyle.T2, + backgroundColor: Color, borderColor: Color = GrayColor.C500, - backgroundColor: Color = CommonColor.White, - content: @Composable () -> Unit, + hasBorder: Boolean = true, ) { Box( modifier = modifier, ) { - Box( - modifier = - Modifier - .fillMaxSize() - .offset(y = 4.dp) - .background( - color = borderColor, - shape = RoundedCornerShape(100), - ), - ) + if (hasBorder) { + Box( + modifier = + Modifier + .fillMaxSize() + .offset(y = 4.dp) + .background( + color = borderColor, + shape = RoundedCornerShape(100), + ), + ) + } Box( modifier = @@ -50,14 +55,24 @@ fun AppRoundButton( .background( color = backgroundColor, shape = RoundedCornerShape(100), - ).border( - width = 1.6.dp, - color = borderColor, - shape = RoundedCornerShape(100), + ).then( + if (hasBorder) { + Modifier.border( + width = 1.6.dp, + color = borderColor, + shape = RoundedCornerShape(100), + ) + } else { + Modifier + }, ), contentAlignment = Alignment.Center, ) { - content() + AppText( + style = textStyle, + color = textColor, + text = text, + ) } } } @@ -72,27 +87,21 @@ fun AppRoundButtonPreview() { Modifier .width(330.dp) .height(68.dp), - ) { - AppText( - style = AppTextStyle.T2, - color = GrayColor.C500, - text = "버튼임니다", - ) - } + text = "버튼임니다", + textColor = GrayColor.C500, + backgroundColor = CommonColor.White, + ) Spacer(modifier = Modifier.height(10.dp)) AppRoundButton( - backgroundColor = GrayColor.C500, modifier = Modifier .width(330.dp) .height(68.dp), - ) { - AppText( - style = AppTextStyle.T2, - color = CommonColor.White, - text = "버튼임니다", - ) - } + text = "버튼임니다", + textColor = CommonColor.White, + backgroundColor = GrayColor.C500, + hasBorder = false, + ) Spacer(modifier = Modifier.height(10.dp)) } } diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt index 6ad2ccf2..52855543 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt @@ -25,7 +25,6 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.net.toUri import com.twix.designsystem.components.button.AppRoundButton -import com.twix.designsystem.components.text.AppText import com.twix.designsystem.theme.CommonColor import com.twix.designsystem.theme.GrayColor import com.twix.designsystem.theme.TwixTheme @@ -145,18 +144,15 @@ private fun ImageCapturedBar( AppRoundButton( borderColor = CommonColor.White, backgroundColor = GrayColor.C500, + text = "업로드", + textStyle = AppTextStyle.T2, + textColor = CommonColor.White, modifier = Modifier .width(150.dp) .height(74.dp) .noRippleClickable(onClick = onClickUpload), - ) { - AppText( - text = "업로드", - style = AppTextStyle.T2, - color = CommonColor.White, - ) - } + ) } } } From 87440bcb27f567558d2f4a504edfbbf762f14f1d Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Tue, 3 Feb 2026 22:19:21 +0900 Subject: [PATCH 17/21] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=ED=95=98=EB=93=9C=EC=BD=94=EB=94=A9=EB=90=9C=20=EC=97=85?= =?UTF-8?q?=EB=A1=9C=EB=93=9C=20=EB=A6=AC=EC=86=8C=EC=8A=A4=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/twix/task_certification/component/CameraControlBar.kt | 3 ++- feature/task-certification/src/main/res/values/strings.xml | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt index 52855543..2f097c0f 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt @@ -20,6 +20,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -144,7 +145,7 @@ private fun ImageCapturedBar( AppRoundButton( borderColor = CommonColor.White, backgroundColor = GrayColor.C500, - text = "업로드", + text = stringResource(R.string.task_certification_image_upload), textStyle = AppTextStyle.T2, textColor = CommonColor.White, modifier = diff --git a/feature/task-certification/src/main/res/values/strings.xml b/feature/task-certification/src/main/res/values/strings.xml index 7e10708c..4f8a8edb 100644 --- a/feature/task-certification/src/main/res/values/strings.xml +++ b/feature/task-certification/src/main/res/values/strings.xml @@ -2,4 +2,5 @@ 인증샷 찍기 이미지 캡처에 실패했습니다. 다시 시도해 주세요. 이미지를 불러오는 데 실패했습니다. 다시 시도해 주세요. + 업로드 From a24210423c5027dfe0023dac336d32f920ec78f6 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Tue, 3 Feb 2026 22:20:18 +0900 Subject: [PATCH 18/21] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=B4=AC=EC=98=81=20=EB=B2=84=ED=8A=BC=20=EB=B9=84=ED=99=9C?= =?UTF-8?q?=EC=84=B1=ED=99=94=20=EC=8B=9C=EC=A0=90=EC=9D=84=20=EC=BD=9C?= =?UTF-8?q?=EB=B0=B1=EB=B3=B4=EB=8B=A4=20=EB=A8=BC=EC=A0=80=20=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/twix/task_certification/component/CameraControlBar.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt index 2f097c0f..83d05fc3 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt @@ -97,8 +97,8 @@ private fun ImageNotCapturedBar( modifier = Modifier .noRippleClickable(enabled = enabled) { - onCaptureClick() enabled = false + onCaptureClick() }, ) From ca84073ea1a2e26f5700d71ec9fbad4e78156a30 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Tue, 3 Feb 2026 22:21:34 +0900 Subject: [PATCH 19/21] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=B4=AC=EC=98=81=20=EB=B2=84=ED=8A=BC=20=ED=99=9C=EC=84=B1?= =?UTF-8?q?=ED=99=94=20=ED=94=8C=EB=9E=98=EA=B7=B8=20=EB=B3=80=EC=88=98=20?= =?UTF-8?q?remember=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/twix/task_certification/component/CameraControlBar.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt index 83d05fc3..550060f9 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt @@ -15,6 +15,7 @@ import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.runtime.Composable 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 @@ -71,7 +72,7 @@ private fun ImageNotCapturedBar( onClickGallery: () -> Unit, modifier: Modifier = Modifier, ) { - var enabled by rememberSaveable { mutableStateOf(true) } + var enabled by remember { mutableStateOf(true) } Row( modifier = From 8f12e1204679f96ba03b143d03c48a8d8b7dccb7 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Tue, 3 Feb 2026 22:26:08 +0900 Subject: [PATCH 20/21] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20stringRe?= =?UTF-8?q?source=EB=A5=BC=20=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/twix/task_certification/TaskCertificationScreen.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt index a5ca0fdc..7e00b6ee 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/TaskCertificationScreen.kt @@ -105,12 +105,13 @@ fun TaskCertificationRoute( } } + val imageCaptureFailMessage = stringResource(R.string.task_certification_image_capture_fail) ObserveAsEvents(viewModel.sideEffect) { event -> when (event) { TaskCertificationSideEffect.ShowImageCaptureFailToast -> { toastManager.tryShow( ToastData( - message = context.getString(R.string.task_certification_image_capture_fail), + message = imageCaptureFailMessage, type = ToastType.ERROR, ), ) From f8090318bd943d132ed039679899af7a027d2a74 Mon Sep 17 00:00:00 2001 From: chanho0908 Date: Tue, 3 Feb 2026 22:26:27 +0900 Subject: [PATCH 21/21] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20import?= =?UTF-8?q?=20=EB=88=84=EB=9D=BD=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/twix/task_certification/component/CameraControlBar.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt index 550060f9..a3d1a46f 100644 --- a/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt +++ b/feature/task-certification/src/main/java/com/twix/task_certification/component/CameraControlBar.kt @@ -16,7 +16,6 @@ import androidx.compose.runtime.Composable 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