From a8f35882dfd74b4b013861cc31b899af9c1ad663 Mon Sep 17 00:00:00 2001 From: seungjun Date: Sat, 30 May 2026 16:26:16 +0900 Subject: [PATCH 1/4] =?UTF-8?q?[FIX/#1591]=20=EC=95=B1=EC=9E=BC=20?= =?UTF-8?q?=EB=92=A4=EB=A1=9C=EA=B0=80=EA=B8=B0=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=ED=81=B4=EB=A6=AD=20=EC=B2=98=EB=A6=AC=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../official/feature/appjamtamp/component/BackButtonHeader.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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( From a1e5e24f7d401e926ad4513bab6d8ff761d84fd0 Mon Sep 17 00:00:00 2001 From: seungjun Date: Sat, 30 May 2026 16:26:28 +0900 Subject: [PATCH 2/4] =?UTF-8?q?[FIX/#1591]=20=EB=AF=B8=EC=85=98=20?= =?UTF-8?q?=EC=A0=9C=EC=B6=9C=20=ED=9B=84=20=EC=83=81=EC=84=B8=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EC=9E=90=EB=8F=99=20=EB=B3=B5=EA=B7=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../feature/appjamtamp/missiondetail/MissionDetailRoute.kt | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) 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..90c09933e 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() } } From 15468b712ec5572ccb4394ec7be0397674381b84 Mon Sep 17 00:00:00 2001 From: seungjun Date: Sat, 30 May 2026 16:26:37 +0900 Subject: [PATCH 3/4] =?UTF-8?q?[FEAT/#1591]=20=EB=AF=B8=EC=85=98=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EC=99=84=EB=A3=8C=20=EB=B2=84=ED=8A=BC=20?= =?UTF-8?q?=ED=99=9C=EC=84=B1=ED=99=94=20=EC=A1=B0=EA=B1=B4=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../appjamtamp/component/AppjamtampButton.kt | 7 ++++--- .../missiondetail/MissionDetailRoute.kt | 20 ++++++++++++------ .../missiondetail/MissionDetailState.kt | 21 +++++++++++++++++-- .../missiondetail/MissionDetailViewModel.kt | 8 ++++++- 4 files changed, 44 insertions(+), 12 deletions(-) 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/missiondetail/MissionDetailRoute.kt b/feature/appjamtamp/src/main/java/org/sopt/official/feature/appjamtamp/missiondetail/MissionDetailRoute.kt index 90c09933e..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 @@ -149,7 +149,8 @@ internal fun MissionDetailRoute( }, onDatePickerClick = { isDatePickerVisible = true }, onMemoChange = viewModel::updateContent, - onCompleteButtonClick = viewModel::handleSubmit + onCompleteButtonClick = viewModel::handleSubmit, + isSubmitEnabled = uiState.isSubmitEnabled, ) } else { MissionDetailScreen( @@ -186,7 +187,8 @@ internal fun MissionDetailRoute( DetailViewType.EDIT -> viewModel.handleSubmit() } - } + }, + isSubmitEnabled = uiState.isSubmitEnabled, ) } @@ -250,7 +252,8 @@ private fun MyEmptyMissionDetailScreen( onClickZoomIn: (String) -> Unit, onDatePickerClick: () -> Unit, onMemoChange: (String) -> Unit, - onCompleteButtonClick: () -> Unit + onCompleteButtonClick: () -> Unit, + isSubmitEnabled: Boolean, ) { val scrollState = rememberScrollState() @@ -307,6 +310,7 @@ private fun MyEmptyMissionDetailScreen( AppjamtampButton( text = "미션 완료", onClicked = onCompleteButtonClick, + isEnabled = isSubmitEnabled, modifier = Modifier .fillMaxWidth() .padding(bottom = 20.dp) @@ -323,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) } @@ -437,6 +442,7 @@ private fun MissionDetailScreen( AppjamtampButton( text = "미션 완료", onClicked = onActionButtonClick, + isEnabled = isSubmitEnabled, modifier = Modifier .fillMaxWidth() .padding(bottom = 20.dp) @@ -457,7 +463,8 @@ private fun MyEmptyMissionDetailScreenPreview() { onClickZoomIn = {}, onDatePickerClick = {}, onMemoChange = {}, - onCompleteButtonClick = {} + onCompleteButtonClick = {}, + isSubmitEnabled = true, ) } } @@ -474,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 -> From 9e3753e8a6319185f06f7c9b38ea22827b68b8db Mon Sep 17 00:00:00 2001 From: seungjun Date: Sat, 30 May 2026 16:26:47 +0900 Subject: [PATCH 4/4] =?UTF-8?q?[FIX/#1591]=20=EC=95=B1=EC=9E=BC=20?= =?UTF-8?q?=ED=98=84=ED=99=A9=20=EC=8B=9C=EA=B0=84=20=ED=91=9C=EA=B8=B0=20?= =?UTF-8?q?QA=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/Top3RecentRankingMission.kt | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) 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) } }