diff --git a/core/design-system/src/main/res/drawable/ic_big_plane.xml b/core/design-system/src/main/res/drawable/ic_big_plane.xml
new file mode 100644
index 00000000..7b8f6601
--- /dev/null
+++ b/core/design-system/src/main/res/drawable/ic_big_plane.xml
@@ -0,0 +1,47 @@
+
+
+
+
+
+
+
+
+
diff --git a/core/design-system/src/main/res/values/strings.xml b/core/design-system/src/main/res/values/strings.xml
index b9a55913..f246b2b7 100644
--- a/core/design-system/src/main/res/values/strings.xml
+++ b/core/design-system/src/main/res/values/strings.xml
@@ -104,6 +104,8 @@
인증샷 찍기
인증샷 등록에 실패했습니다.
이미지 변환에 실패했습니다.
+ 인증샷 업로드 중...
+ 잠시만 기다려 주세요.
코멘트는 5글자로 입력해주세요!
diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/certification/TaskCertificationScreen.kt b/feature/task-certification/src/main/java/com/twix/task_certification/certification/TaskCertificationScreen.kt
index 7f4bbfaa..bf990d55 100644
--- a/feature/task-certification/src/main/java/com/twix/task_certification/certification/TaskCertificationScreen.kt
+++ b/feature/task-certification/src/main/java/com/twix/task_certification/certification/TaskCertificationScreen.kt
@@ -39,6 +39,7 @@ import com.twix.domain.model.enums.AppTextStyle
import com.twix.task_certification.certification.component.CameraControlBar
import com.twix.task_certification.certification.component.CameraPreviewBox
import com.twix.task_certification.certification.component.CommentErrorText
+import com.twix.task_certification.certification.component.LoadingContent
import com.twix.task_certification.certification.component.TaskCertificationTopBar
import com.twix.task_certification.certification.contract.TaskCertificationIntent
import com.twix.task_certification.certification.contract.TaskCertificationSideEffect
@@ -95,48 +96,53 @@ fun TaskCertificationRoute(
),
)
}
+
TaskCertificationSideEffect.NavigateToBack -> navigateToBack()
TaskCertificationSideEffect.NavigateToDetail -> navigateToDetail()
}
}
- TaskCertificationScreen(
- uiState = uiState,
- cameraPreview = cameraPreview,
- onClickClose = navigateToBack,
- onCaptureClick = {
- coroutineScope.launch {
- camera
- .takePicture()
- .onSuccess {
- viewModel.dispatch(TaskCertificationIntent.TakePicture(it))
- }.onFailure {
- viewModel.dispatch(TaskCertificationIntent.TakePicture(null))
- }
- }
- },
- onToggleCameraClick = {
- viewModel.dispatch(TaskCertificationIntent.ToggleLens)
- },
- onClickFlash = {
- viewModel.dispatch(TaskCertificationIntent.ToggleTorch)
- },
- onClickGallery = {
- pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
- },
- onClickRefresh = {
- viewModel.dispatch(TaskCertificationIntent.RetakePicture)
- },
- onCommentChanged = {
- viewModel.dispatch(TaskCertificationIntent.UpdateComment(it))
- },
- onFocusChanged = {
- viewModel.dispatch(TaskCertificationIntent.CommentFocusChanged(it))
- },
- onClickUpload = {
- viewModel.dispatch(TaskCertificationIntent.TryUpload)
- },
- )
+ if (uiState.isLoading) {
+ LoadingContent()
+ } else {
+ TaskCertificationScreen(
+ uiState = uiState,
+ cameraPreview = cameraPreview,
+ onClickClose = navigateToBack,
+ onCaptureClick = {
+ coroutineScope.launch {
+ camera
+ .takePicture()
+ .onSuccess {
+ viewModel.dispatch(TaskCertificationIntent.TakePicture(it))
+ }.onFailure {
+ viewModel.dispatch(TaskCertificationIntent.TakePicture(null))
+ }
+ }
+ },
+ onToggleCameraClick = {
+ viewModel.dispatch(TaskCertificationIntent.ToggleLens)
+ },
+ onClickFlash = {
+ viewModel.dispatch(TaskCertificationIntent.ToggleTorch)
+ },
+ onClickGallery = {
+ pickMedia.launch(PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly))
+ },
+ onClickRefresh = {
+ viewModel.dispatch(TaskCertificationIntent.RetakePicture)
+ },
+ onCommentChanged = {
+ viewModel.dispatch(TaskCertificationIntent.UpdateComment(it))
+ },
+ onFocusChanged = {
+ viewModel.dispatch(TaskCertificationIntent.CommentFocusChanged(it))
+ },
+ onClickUpload = {
+ viewModel.dispatch(TaskCertificationIntent.TryUpload)
+ },
+ )
+ }
}
@Composable
diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/certification/TaskCertificationViewModel.kt b/feature/task-certification/src/main/java/com/twix/task_certification/certification/TaskCertificationViewModel.kt
index bc079ba3..7d895c24 100644
--- a/feature/task-certification/src/main/java/com/twix/task_certification/certification/TaskCertificationViewModel.kt
+++ b/feature/task-certification/src/main/java/com/twix/task_certification/certification/TaskCertificationViewModel.kt
@@ -125,7 +125,9 @@ class TaskCertificationViewModel(
}
private fun upload(image: ByteArray) {
+ reduce { copy(isLoading = true) }
launchResult(
+ onStart = { reduce { copy(isLoading = true) } },
block = {
photologRepository.uploadPhotologImage(
goalId = navArgs.goalId,
@@ -145,6 +147,7 @@ class TaskCertificationViewModel(
onError = {
showToast(R.string.task_certification_upload_fail, ToastType.ERROR)
},
+ onFinally = { reduce { copy(isLoading = false) } },
)
}
@@ -171,8 +174,10 @@ class TaskCertificationViewModel(
when (navArgs.from) {
NavRoutes.TaskCertificationRoute.From.HOME ->
goalRefreshBus.notifyGoalListChanged()
+
NavRoutes.TaskCertificationRoute.From.DETAIL ->
detailRefreshBus.notifyChanged(TaskCertificationRefreshBus.Publisher.PHOTOLOG)
+
NavRoutes.TaskCertificationRoute.From.EDITOR -> Unit
}
tryEmitSideEffect(TaskCertificationSideEffect.NavigateToBack)
diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/certification/component/LoadingContent.kt b/feature/task-certification/src/main/java/com/twix/task_certification/certification/component/LoadingContent.kt
new file mode 100644
index 00000000..a1d5e576
--- /dev/null
+++ b/feature/task-certification/src/main/java/com/twix/task_certification/certification/component/LoadingContent.kt
@@ -0,0 +1,115 @@
+package com.twix.task_certification.certification.component
+
+import androidx.compose.animation.core.EaseInOut
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.RepeatMode
+import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.infiniteRepeatable
+import androidx.compose.animation.core.rememberInfiniteTransition
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+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.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.twix.designsystem.R
+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
+internal fun LoadingContent() {
+ Column(
+ modifier =
+ Modifier
+ .fillMaxSize()
+ .background(CommonColor.White),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center,
+ ) {
+ FloatingPlane()
+
+ Spacer(Modifier.height(6.dp))
+
+ AppText(
+ text = stringResource(R.string.task_certification_loading),
+ style = AppTextStyle.H1,
+ color = GrayColor.C500,
+ )
+
+ Spacer(Modifier.height(10.dp))
+
+ AppText(
+ text = stringResource(R.string.task_certification_plz_waiting),
+ style = AppTextStyle.T2,
+ color = GrayColor.C300,
+ )
+ }
+}
+
+@Composable
+private fun FloatingPlane() {
+ val planeXOffset = 6f
+ val planeYOffset = 8f
+ val planeRotation = 6f
+ val transition = rememberInfiniteTransition(label = "plane")
+
+ val offsetY by transition.animateFloat(
+ -planeYOffset,
+ planeYOffset,
+ infiniteRepeatable(
+ tween(1500, easing = EaseInOut),
+ RepeatMode.Reverse,
+ ),
+ )
+
+ val offsetX by transition.animateFloat(
+ -planeXOffset,
+ planeXOffset,
+ infiniteRepeatable(
+ tween(2200, easing = LinearEasing),
+ RepeatMode.Reverse,
+ ),
+ )
+
+ val rotation by transition.animateFloat(
+ -planeRotation,
+ planeRotation,
+ infiniteRepeatable(
+ tween(1300, easing = EaseInOut),
+ RepeatMode.Reverse,
+ ),
+ )
+
+ Image(
+ painter = painterResource(R.drawable.ic_big_plane),
+ contentDescription = null,
+ modifier =
+ Modifier.graphicsLayer {
+ translationX = offsetX
+ translationY = offsetY
+ rotationZ = rotation
+ },
+ )
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun LoadingContentPreview() {
+ TwixTheme {
+ LoadingContent()
+ }
+}
diff --git a/feature/task-certification/src/main/java/com/twix/task_certification/certification/contract/TaskCertificationUiState.kt b/feature/task-certification/src/main/java/com/twix/task_certification/certification/contract/TaskCertificationUiState.kt
index 2e523cbd..54aa4eed 100644
--- a/feature/task-certification/src/main/java/com/twix/task_certification/certification/contract/TaskCertificationUiState.kt
+++ b/feature/task-certification/src/main/java/com/twix/task_certification/certification/contract/TaskCertificationUiState.kt
@@ -17,6 +17,7 @@ data class TaskCertificationUiState(
val preview: CameraPreview? = null,
val comment: CommentUiModel = CommentUiModel(),
val showCommentError: Boolean = false,
+ val isLoading: Boolean = false,
) : State {
val hasMaxCommentLength: Boolean
get() = comment.hasMaxCommentLength