diff --git a/feature/appjamtamp/src/main/java/org/sopt/official/feature/appjamtamp/component/AppjamtampButton.kt b/feature/appjamtamp/src/main/java/org/sopt/official/feature/appjamtamp/component/AppjamtampButton.kt index ab402b473..d9d35ef47 100644 --- a/feature/appjamtamp/src/main/java/org/sopt/official/feature/appjamtamp/component/AppjamtampButton.kt +++ b/feature/appjamtamp/src/main/java/org/sopt/official/feature/appjamtamp/component/AppjamtampButton.kt @@ -25,7 +25,6 @@ package org.sopt.official.feature.appjamtamp.component import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding @@ -35,6 +34,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import org.sopt.official.common.util.noRippleClickable import org.sopt.official.designsystem.SoptTheme @Composable @@ -42,15 +42,16 @@ internal fun AppjamtampButton( text: String, onClicked: () -> Unit, modifier: Modifier = Modifier, + isEnabled: Boolean = true, ) { Box( modifier = modifier .fillMaxWidth() .background( - color = SoptTheme.colors.primary, + color = if (isEnabled) SoptTheme.colors.primary else SoptTheme.colors.onSurface300, shape = RoundedCornerShape(9.dp), ) - .clickable(onClick = onClicked), + .noRippleClickable(onClick = { if (isEnabled) onClicked() }), contentAlignment = Alignment.Center, ) { Text( diff --git a/feature/appjamtamp/src/main/java/org/sopt/official/feature/appjamtamp/component/BackButtonHeader.kt b/feature/appjamtamp/src/main/java/org/sopt/official/feature/appjamtamp/component/BackButtonHeader.kt index 78af8bdc9..5b9c367a8 100644 --- a/feature/appjamtamp/src/main/java/org/sopt/official/feature/appjamtamp/component/BackButtonHeader.kt +++ b/feature/appjamtamp/src/main/java/org/sopt/official/feature/appjamtamp/component/BackButtonHeader.kt @@ -38,6 +38,7 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.vectorResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import org.sopt.official.common.util.throttledNoRippleClickable import org.sopt.official.designsystem.SoptTheme import org.sopt.official.feature.appjamtamp.R @@ -57,7 +58,7 @@ internal fun BackButtonHeader( imageVector = ImageVector.vectorResource(R.drawable.ic_back_32), contentDescription = null, tint = SoptTheme.colors.onSurface10, - modifier = Modifier.clickable(onClick = onBackButtonClick) + modifier = Modifier.throttledNoRippleClickable(onClick = onBackButtonClick), ) Text( diff --git a/feature/appjamtamp/src/main/java/org/sopt/official/feature/appjamtamp/missiondetail/MissionDetailRoute.kt b/feature/appjamtamp/src/main/java/org/sopt/official/feature/appjamtamp/missiondetail/MissionDetailRoute.kt index 2a2e51e2b..650ba6e81 100644 --- a/feature/appjamtamp/src/main/java/org/sopt/official/feature/appjamtamp/missiondetail/MissionDetailRoute.kt +++ b/feature/appjamtamp/src/main/java/org/sopt/official/feature/appjamtamp/missiondetail/MissionDetailRoute.kt @@ -130,10 +130,11 @@ internal fun MissionDetailRoute( } } - LaunchedEffect(!uiState.isLoading, progress) { - if (progress >= 0.99f && !uiState.isLoading) { + LaunchedEffect(showPostSubmissionBadge, !uiState.isLoading, progress) { + if (showPostSubmissionBadge && progress >= 0.99f && !uiState.isLoading) { delay(500L) viewModel.updateShowPostSubmissionBadge() + navigateUp() } } @@ -148,7 +149,8 @@ internal fun MissionDetailRoute( }, onDatePickerClick = { isDatePickerVisible = true }, onMemoChange = viewModel::updateContent, - onCompleteButtonClick = viewModel::handleSubmit + onCompleteButtonClick = viewModel::handleSubmit, + isSubmitEnabled = uiState.isSubmitEnabled, ) } else { MissionDetailScreen( @@ -185,7 +187,8 @@ internal fun MissionDetailRoute( DetailViewType.EDIT -> viewModel.handleSubmit() } - } + }, + isSubmitEnabled = uiState.isSubmitEnabled, ) } @@ -249,7 +252,8 @@ private fun MyEmptyMissionDetailScreen( onClickZoomIn: (String) -> Unit, onDatePickerClick: () -> Unit, onMemoChange: (String) -> Unit, - onCompleteButtonClick: () -> Unit + onCompleteButtonClick: () -> Unit, + isSubmitEnabled: Boolean, ) { val scrollState = rememberScrollState() @@ -306,6 +310,7 @@ private fun MyEmptyMissionDetailScreen( AppjamtampButton( text = "미션 완료", onClicked = onCompleteButtonClick, + isEnabled = isSubmitEnabled, modifier = Modifier .fillMaxWidth() .padding(bottom = 20.dp) @@ -322,7 +327,8 @@ private fun MissionDetailScreen( onClickZoomIn: (String) -> Unit, onDatePickerClick: () -> Unit, onMemoChange: (String) -> Unit, - onActionButtonClick: () -> Unit + onActionButtonClick: () -> Unit, + isSubmitEnabled: Boolean, ) { val scrollState = rememberScrollState() var isEditable by remember(uiState.viewType) { mutableStateOf(uiState.viewType == DetailViewType.EDIT) } @@ -436,6 +442,7 @@ private fun MissionDetailScreen( AppjamtampButton( text = "미션 완료", onClicked = onActionButtonClick, + isEnabled = isSubmitEnabled, modifier = Modifier .fillMaxWidth() .padding(bottom = 20.dp) @@ -456,7 +463,8 @@ private fun MyEmptyMissionDetailScreenPreview() { onClickZoomIn = {}, onDatePickerClick = {}, onMemoChange = {}, - onCompleteButtonClick = {} + onCompleteButtonClick = {}, + isSubmitEnabled = true, ) } } @@ -473,7 +481,8 @@ private fun MyMissionDetailScreenPreview() { onDatePickerClick = {}, onMemoChange = {}, onActionButtonClick = {}, - onToolbarIconClick = {} + onToolbarIconClick = {}, + isSubmitEnabled = true, ) } } diff --git a/feature/appjamtamp/src/main/java/org/sopt/official/feature/appjamtamp/missiondetail/MissionDetailState.kt b/feature/appjamtamp/src/main/java/org/sopt/official/feature/appjamtamp/missiondetail/MissionDetailState.kt index a5465076e..c333b9279 100644 --- a/feature/appjamtamp/src/main/java/org/sopt/official/feature/appjamtamp/missiondetail/MissionDetailState.kt +++ b/feature/appjamtamp/src/main/java/org/sopt/official/feature/appjamtamp/missiondetail/MissionDetailState.kt @@ -51,5 +51,22 @@ internal data class MissionDetailState( val viewCount: Int = 0, val clappers: ImmutableList = persistentListOf(), - val showPostSubmissionBadge: Boolean = false -) + val showPostSubmissionBadge: Boolean = false, + + val initSnapshotImageModel: ImageModel = ImageModel.Empty, + val initSnapshotDate: String = "", + val initSnapshotContent: String = "", +) { + val isSubmitEnabled: Boolean + get() { + val commonGuard = !isLoading && content.isNotBlank() && date.isNotBlank() && !imageModel.isEmpty() + + if (!commonGuard) return false + + return when (viewType) { + DetailViewType.WRITE -> true + DetailViewType.EDIT -> content != initSnapshotContent || date != initSnapshotDate || imageModel != initSnapshotImageModel + DetailViewType.READ_ONLY, DetailViewType.COMPLETE -> false + } + } +} diff --git a/feature/appjamtamp/src/main/java/org/sopt/official/feature/appjamtamp/missiondetail/MissionDetailViewModel.kt b/feature/appjamtamp/src/main/java/org/sopt/official/feature/appjamtamp/missiondetail/MissionDetailViewModel.kt index 8cf7dba61..a86d15327 100644 --- a/feature/appjamtamp/src/main/java/org/sopt/official/feature/appjamtamp/missiondetail/MissionDetailViewModel.kt +++ b/feature/appjamtamp/src/main/java/org/sopt/official/feature/appjamtamp/missiondetail/MissionDetailViewModel.kt @@ -129,6 +129,9 @@ internal class MissionDetailViewModel @Inject constructor( imageModel = ImageModel.Remote(stamp.images), date = stamp.activityDate, content = stamp.contents, + initSnapshotImageModel = ImageModel.Remote(stamp.images), + initSnapshotDate = stamp.activityDate, + initSnapshotContent = stamp.contents, teamName = stamp.teamName, stampId = stamp.stampId, writer = User( @@ -260,7 +263,10 @@ internal class MissionDetailViewModel @Inject constructor( _missionDetailState.update { it.copy( isLoading = false, - viewType = DetailViewType.COMPLETE + viewType = DetailViewType.COMPLETE, + initSnapshotImageModel = ImageModel.Remote(imageModel.url), + initSnapshotDate = date, + initSnapshotContent = content, ) } }.onFailure { e -> diff --git a/feature/appjamtamp/src/main/java/org/sopt/official/feature/appjamtamp/ranking/component/Top3RecentRankingMission.kt b/feature/appjamtamp/src/main/java/org/sopt/official/feature/appjamtamp/ranking/component/Top3RecentRankingMission.kt index c144f4454..7ce306e3b 100644 --- a/feature/appjamtamp/src/main/java/org/sopt/official/feature/appjamtamp/ranking/component/Top3RecentRankingMission.kt +++ b/feature/appjamtamp/src/main/java/org/sopt/official/feature/appjamtamp/ranking/component/Top3RecentRankingMission.kt @@ -157,24 +157,27 @@ private fun String?.toRelativeTime(): String { val dateFormat = SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.KOREA) dateFormat.timeZone = TimeZone.getTimeZone("Asia/Seoul") + val monthDayFormat = SimpleDateFormat("M월d일", Locale.KOREA).apply { + timeZone = TimeZone.getTimeZone("Asia/Seoul") + } - val date = dateFormat.parse(this) ?: return "" + val date = runCatching { dateFormat.parse(this) }.getOrNull() ?: return "" val currentDate = Date() val diffMillis = currentDate.time - date.time - if (diffMillis < 0) return "1분 전" + if (diffMillis < 0) return "방금 전" val minutes = TimeUnit.MILLISECONDS.toMinutes(diffMillis) val hours = TimeUnit.MILLISECONDS.toHours(diffMillis) + val days = TimeUnit.MILLISECONDS.toDays(diffMillis) return when { - minutes == 0L -> "1분 전" - minutes in 1..59 -> "${minutes}분 전" - hours in 1..24 -> "${hours}시간 전" - else -> { - val days = TimeUnit.MILLISECONDS.toDays(diffMillis) - "${days}일 전" - } + minutes < 10L -> "방금 전" + minutes < 60L -> "${minutes}분 전" + hours < 25L -> "${hours}시간 전" + days < 7L -> "${days}일 전" + days < 35L -> "${days / 7}주 전" + else -> monthDayFormat.format(date) } }