From 3ff4b35df5aca4ebef0e0a213d8c6ab0f7cb0635 Mon Sep 17 00:00:00 2001 From: hoyahozz <85336456+hoyahozz@users.noreply.github.com> Date: Sat, 9 Aug 2025 17:09:48 +0900 Subject: [PATCH 1/2] =?UTF-8?q?[TNT-307]=20feat:=20=ED=8A=B8=EB=A0=88?= =?UTF-8?q?=EC=9D=B4=EB=84=88=20=EC=BA=98=EB=A6=B0=EB=8D=94=20=EC=A0=84?= =?UTF-8?q?=ED=99=98=20(=EC=9B=94=EA=B0=84=20->=20=EC=A3=BC=EA=B0=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kr/tnt/trainer/home/TrainerHomeScreen.kt | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/feature/trainer/home/src/main/java/co/kr/tnt/trainer/home/TrainerHomeScreen.kt b/feature/trainer/home/src/main/java/co/kr/tnt/trainer/home/TrainerHomeScreen.kt index 2c6fa741..f0ffea43 100644 --- a/feature/trainer/home/src/main/java/co/kr/tnt/trainer/home/TrainerHomeScreen.kt +++ b/feature/trainer/home/src/main/java/co/kr/tnt/trainer/home/TrainerHomeScreen.kt @@ -47,10 +47,10 @@ import co.kr.tnt.core.ui.R.string.core_do_not_see_for_three_days import co.kr.tnt.core.ui.R.string.core_next_time import co.kr.tnt.designsystem.component.TnTPopupDialog import co.kr.tnt.designsystem.component.button.TnTFabButton -import co.kr.tnt.designsystem.component.calendar.TnTIndicatorMonthCalendar +import co.kr.tnt.designsystem.component.calendar.TnTIndicatorWeekCalendar import co.kr.tnt.designsystem.component.calendar.model.DayIndicatorState import co.kr.tnt.designsystem.component.calendar.model.DayState -import co.kr.tnt.designsystem.component.calendar.utils.rememberMostVisibleMonth +import co.kr.tnt.designsystem.component.calendar.utils.rememberMostVisibleYearMonth import co.kr.tnt.designsystem.component.card.TnTSessionRecordCard import co.kr.tnt.designsystem.snackbar.LocalSnackbar import co.kr.tnt.designsystem.theme.TnTTheme @@ -66,8 +66,8 @@ import co.kr.tnt.ui.component.TnTHomeTopBar import co.kr.tnt.ui.utils.throttled import coil.compose.rememberAsyncImagePainter import coil.request.ImageRequest -import com.kizitonwose.calendar.compose.CalendarState -import com.kizitonwose.calendar.compose.rememberCalendarState +import com.kizitonwose.calendar.compose.weekcalendar.WeekCalendarState +import com.kizitonwose.calendar.compose.weekcalendar.rememberWeekCalendarState import kotlinx.coroutines.launch import java.time.DayOfWeek import java.time.LocalDate @@ -153,15 +153,14 @@ private fun TrainerHomeScreen( onClickAddPtSession: () -> Unit, onClickPtSessionComplete: (PtSession) -> Unit, ) { - val now = remember { YearMonth.now() } - val calendarState = rememberCalendarState( - firstVisibleMonth = now, + val weekCalendarState = rememberWeekCalendarState( firstDayOfWeek = DayOfWeek.SUNDAY, - startMonth = now.minusYears(10), - endMonth = now.plusYears(10), + firstVisibleWeekDate = state.selectedDay, + startDate = state.selectedDay.minusYears(10), + endDate = state.selectedDay.plusYears(10), ) val coroutineScope = rememberCoroutineScope() - val visibleMonth = rememberMostVisibleMonth(calendarState) + var visibleMonth = rememberMostVisibleYearMonth(weekCalendarState) val dateFormatter = remember { DateFormatter() } Box(modifier = Modifier.padding(padding)) { @@ -181,27 +180,32 @@ private fun TrainerHomeScreen( onClickNotification = onClickNotification, onClickSelectorPrevious = { coroutineScope.launch { - calendarState.animateScrollToMonth(visibleMonth.minusMonths(1)) + val previousWeek = weekCalendarState.firstVisibleWeek.days.first().date.minusWeeks(1) + visibleMonth = YearMonth.from(previousWeek) + weekCalendarState.animateScrollToWeek(previousWeek) } }, onClickSelectorNext = { coroutineScope.launch { - calendarState.animateScrollToMonth(visibleMonth.plusMonths(1)) + val nextWeek = + weekCalendarState.firstVisibleWeek.days.first().date.plusWeeks(1) + visibleMonth = YearMonth.from(nextWeek) + weekCalendarState.animateScrollToWeek(nextWeek) } }, ) Spacer(modifier = Modifier.height(16.dp)) Calendar( modifier = Modifier.background(color = TnTTheme.colors.commonColors.Common0), - calendarState = calendarState, + weekCalendarState = weekCalendarState, selectedDay = state.selectedDay, dailyPtSessionCount = state.dailyPtSessionCount, onClickDay = onClickDay, ) - Spacer(modifier = Modifier.height(24.dp)) + Spacer(modifier = Modifier.height(12.dp)) } Column { - Spacer(modifier = Modifier.height(24.dp)) + Spacer(modifier = Modifier.height(20.dp)) DailyPtSessionTitle( day = state.selectedDay, sessionCount = state.selectedDayPtSessions?.size ?: 0, @@ -253,16 +257,14 @@ private fun TrainerHomeScreen( @Composable private fun Calendar( - calendarState: CalendarState, + weekCalendarState: WeekCalendarState, selectedDay: LocalDate, dailyPtSessionCount: Map, onClickDay: (date: LocalDate) -> Unit, modifier: Modifier = Modifier, ) { - TnTIndicatorMonthCalendar( - modifier = modifier, - state = calendarState, - onClickDay = onClickDay, + TnTIndicatorWeekCalendar( + state = weekCalendarState, dayState = { day -> DayState(isSelected = day == selectedDay) }, indicatorState = { day -> val count = dailyPtSessionCount[day] ?: 0 @@ -273,6 +275,8 @@ private fun Calendar( showText = count != 0, ) }, + onClickDay = onClickDay, + modifier = modifier.background(TnTTheme.colors.commonColors.Common0), ) } From 5f251e4cb72fc2fa59a79c27a0e42cb723a3a2f2 Mon Sep 17 00:00:00 2001 From: hoyahozz <85336456+hoyahozz@users.noreply.github.com> Date: Sat, 9 Aug 2025 18:53:04 +0900 Subject: [PATCH 2/2] =?UTF-8?q?[TNT-307]=20feat:=20=EC=88=98=EC=97=85=20?= =?UTF-8?q?=EC=8B=9C=EC=9E=91=20=EC=8B=9C=EA=B0=84=20=EC=A0=84=20'?= =?UTF-8?q?=EC=88=98=EC=97=85=20=EC=99=84=EB=A3=8C'=20=EB=B2=84=ED=8A=BC?= =?UTF-8?q?=EC=9D=84=20=EB=88=84=EB=A5=B8=20=EA=B2=BD=EC=9A=B0,=20?= =?UTF-8?q?=EA=B2=BD=EA=B3=A0=20=EB=8B=A4=EC=9D=B4=EC=96=BC=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=20=EC=B6=9C=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tnt/trainer/home/TrainerHomeContract.kt | 18 +++++----- .../kr/tnt/trainer/home/TrainerHomeScreen.kt | 28 ++++++++++++--- .../tnt/trainer/home/TrainerHomeViewModel.kt | 35 +++++++++++++------ .../home/src/main/res/values/strings.xml | 2 ++ 4 files changed, 61 insertions(+), 22 deletions(-) diff --git a/feature/trainer/home/src/main/java/co/kr/tnt/trainer/home/TrainerHomeContract.kt b/feature/trainer/home/src/main/java/co/kr/tnt/trainer/home/TrainerHomeContract.kt index 15de08cf..64fc63ba 100644 --- a/feature/trainer/home/src/main/java/co/kr/tnt/trainer/home/TrainerHomeContract.kt +++ b/feature/trainer/home/src/main/java/co/kr/tnt/trainer/home/TrainerHomeContract.kt @@ -14,15 +14,9 @@ internal class TrainerHomeContract { val selectedDay: LocalDate = LocalDate.now(), val dailyPtSessionCount: Map = mapOf(), val selectedDayPtSessions: List? = null, - val dialogState: DialogState = DialogState.NONE, + val dialogState: TrainerHomeDialog = TrainerHomeDialog.None, val isDialogHiddenForThreeDays: Boolean = false, - ) : UiState { - enum class DialogState { - NONE, - HOME_CONNECT, - ADD_PT_CONNECT, - } - } + ) : UiState sealed interface TrainerHomeUiEvent : UiEvent { data object OnScreen : TrainerHomeUiEvent @@ -32,6 +26,7 @@ internal class TrainerHomeContract { data object OnClickAddPtSession : TrainerHomeUiEvent data class OnClickPtSessionComplete(val ptSession: PtSession) : TrainerHomeUiEvent data object OnConfirmConnectDialog : TrainerHomeUiEvent + data class OnConfirmEarlyPtCompletion(val ptSession: PtSession) : TrainerHomeUiEvent data object OnChangeHideDialogOption : TrainerHomeUiEvent data object OnDismissDialog : TrainerHomeUiEvent } @@ -45,4 +40,11 @@ internal class TrainerHomeContract { val type: SnackbarType = SnackbarType.WARNING, ) : TrainerHomeSideEffect } + + sealed interface TrainerHomeDialog { + data object None : TrainerHomeDialog + data object HomeConnect : TrainerHomeDialog + data object AddPtConnect : TrainerHomeDialog + data class EarlyPtCompletion(val ptSession: PtSession) : TrainerHomeDialog + } } diff --git a/feature/trainer/home/src/main/java/co/kr/tnt/trainer/home/TrainerHomeScreen.kt b/feature/trainer/home/src/main/java/co/kr/tnt/trainer/home/TrainerHomeScreen.kt index f0ffea43..a88fd462 100644 --- a/feature/trainer/home/src/main/java/co/kr/tnt/trainer/home/TrainerHomeScreen.kt +++ b/feature/trainer/home/src/main/java/co/kr/tnt/trainer/home/TrainerHomeScreen.kt @@ -42,9 +42,12 @@ import co.kr.tnt.core.designsystem.R.drawable.ic_add import co.kr.tnt.core.designsystem.R.drawable.ic_fill_check_false import co.kr.tnt.core.designsystem.R.drawable.ic_fill_check_true import co.kr.tnt.core.designsystem.R.drawable.img_default +import co.kr.tnt.core.ui.R.string.core_cancel import co.kr.tnt.core.ui.R.string.core_connect import co.kr.tnt.core.ui.R.string.core_do_not_see_for_three_days import co.kr.tnt.core.ui.R.string.core_next_time +import co.kr.tnt.core.ui.R.string.core_ok +import co.kr.tnt.designsystem.component.TnTIconPopupDialog import co.kr.tnt.designsystem.component.TnTPopupDialog import co.kr.tnt.designsystem.component.button.TnTFabButton import co.kr.tnt.designsystem.component.calendar.TnTIndicatorWeekCalendar @@ -58,6 +61,7 @@ import co.kr.tnt.domain.model.PtSession import co.kr.tnt.domain.utils.DateFormatter import co.kr.tnt.feature.trainer.home.R import co.kr.tnt.navigation.model.ScreenMode +import co.kr.tnt.trainer.home.TrainerHomeContract.TrainerHomeDialog import co.kr.tnt.trainer.home.TrainerHomeContract.TrainerHomeSideEffect import co.kr.tnt.trainer.home.TrainerHomeContract.TrainerHomeUiEvent import co.kr.tnt.trainer.home.TrainerHomeContract.TrainerHomeUiState @@ -109,9 +113,9 @@ internal fun TrainerHomeRoute( } } - when (state.dialogState) { - TrainerHomeUiState.DialogState.NONE -> Unit - TrainerHomeUiState.DialogState.HOME_CONNECT -> { + when (val dialogState = state.dialogState) { + TrainerHomeDialog.None -> Unit + TrainerHomeDialog.HomeConnect -> { TnTCheckToggleDialog( title = stringResource(R.string.please_connect_member), content = stringResource(R.string.cannot_add_pt_without_connection), @@ -126,7 +130,7 @@ internal fun TrainerHomeRoute( ) } - TrainerHomeUiState.DialogState.ADD_PT_CONNECT -> { + TrainerHomeDialog.AddPtConnect -> { TnTPopupDialog( title = stringResource(R.string.please_connect_member), content = stringResource(R.string.cannot_add_pt_without_connection), @@ -137,6 +141,22 @@ internal fun TrainerHomeRoute( onDismiss = { viewModel.setEvent(TrainerHomeUiEvent.OnDismissDialog) }, ) } + + is TrainerHomeDialog.EarlyPtCompletion -> { + TnTIconPopupDialog( + title = stringResource(R.string.pt_early_completion_title), + content = stringResource(R.string.pt_early_completion_message), + leftButtonText = stringResource(core_cancel), + rightButtonText = stringResource(core_ok), + onLeftButtonClick = { viewModel.setEvent(TrainerHomeUiEvent.OnDismissDialog) }, + onRightButtonClick = { + viewModel.setEvent( + TrainerHomeUiEvent.OnConfirmEarlyPtCompletion(dialogState.ptSession), + ) + }, + onDismiss = { viewModel.setEvent(TrainerHomeUiEvent.OnDismissDialog) }, + ) + } } // TODO 홈 화면 진입 시마다 데이터 조회 재고 필요 diff --git a/feature/trainer/home/src/main/java/co/kr/tnt/trainer/home/TrainerHomeViewModel.kt b/feature/trainer/home/src/main/java/co/kr/tnt/trainer/home/TrainerHomeViewModel.kt index 86099954..8f643d51 100644 --- a/feature/trainer/home/src/main/java/co/kr/tnt/trainer/home/TrainerHomeViewModel.kt +++ b/feature/trainer/home/src/main/java/co/kr/tnt/trainer/home/TrainerHomeViewModel.kt @@ -7,10 +7,10 @@ import co.kr.tnt.domain.model.trainer.TrainerDailyPtSessionCount import co.kr.tnt.domain.repository.ConnectRepository import co.kr.tnt.domain.repository.TrainerRepository import co.kr.tnt.feature.trainer.home.R +import co.kr.tnt.trainer.home.TrainerHomeContract.TrainerHomeDialog import co.kr.tnt.trainer.home.TrainerHomeContract.TrainerHomeSideEffect import co.kr.tnt.trainer.home.TrainerHomeContract.TrainerHomeUiEvent import co.kr.tnt.trainer.home.TrainerHomeContract.TrainerHomeUiState -import co.kr.tnt.trainer.home.TrainerHomeContract.TrainerHomeUiState.DialogState import co.kr.tnt.ui.base.BaseViewModel import co.kr.tnt.ui.model.SnackbarType import co.kr.tnt.ui.resource.DisplayText @@ -51,8 +51,11 @@ internal class TrainerHomeViewModel @Inject constructor( is TrainerHomeUiEvent.OnChangeVisibleMonth -> getMonthlySessionCounts(event.yearMonth) is TrainerHomeUiEvent.OnClickDay -> selectDay(event.day) TrainerHomeUiEvent.OnClickAddPtSession -> showConnectDialog(false) - - is TrainerHomeUiEvent.OnClickPtSessionComplete -> completePtSession(event.ptSession) + is TrainerHomeUiEvent.OnClickPtSessionComplete -> completePtSessionIfStartTimeBeforeNow(event.ptSession) + is TrainerHomeUiEvent.OnConfirmEarlyPtCompletion -> { + completePtSession(event.ptSession) + updateState { copy(dialogState = TrainerHomeDialog.None) } + } TrainerHomeUiEvent.OnChangeHideDialogOption -> toggleDialogHiddenState() TrainerHomeUiEvent.OnConfirmConnectDialog -> handleDialogConfirm() TrainerHomeUiEvent.OnDismissDialog -> dismissDialog() @@ -123,6 +126,15 @@ internal class TrainerHomeViewModel @Inject constructor( updateState { copy(dailyPtSessionCount = updatedDailyPtSessionCount) } } + private fun completePtSessionIfStartTimeBeforeNow(ptSession: PtSession) { + if (LocalDateTime.now().isBefore(ptSession.startTime)) { + updateState { copy(dialogState = TrainerHomeDialog.EarlyPtCompletion(ptSession)) } + return + } + + completePtSession(ptSession) + } + private fun completePtSession(ptSession: PtSession) { viewModelScope.launch { runCatching { @@ -178,7 +190,7 @@ internal class TrainerHomeViewModel @Inject constructor( trainerRepository.getActiveMembers() }.onSuccess { result -> if (result.isNotEmpty()) { - updateState { copy(dialogState = DialogState.NONE) } + updateState { copy(dialogState = TrainerHomeDialog.None) } if (triggeredByHome.not()) { sendEffect(TrainerHomeSideEffect.NavigateToAddPtSession) } @@ -191,9 +203,9 @@ internal class TrainerHomeViewModel @Inject constructor( Duration.between(lastHiddenDate, currentDateTime).toHours() < DIALOG_HIDE_DURATION_HOURS if (isHidden.not() && triggeredByHome) { - updateState { copy(dialogState = DialogState.HOME_CONNECT) } + updateState { copy(dialogState = TrainerHomeDialog.HomeConnect) } } else if (triggeredByHome.not()) { - updateState { copy(dialogState = DialogState.ADD_PT_CONNECT) } + updateState { copy(dialogState = TrainerHomeDialog.AddPtConnect) } } } } @@ -207,12 +219,15 @@ internal class TrainerHomeViewModel @Inject constructor( updateCurrentDateTime() } val effect = when (currentState.dialogState) { - DialogState.HOME_CONNECT -> TrainerHomeSideEffect.NavigateToInvite - DialogState.ADD_PT_CONNECT -> TrainerHomeSideEffect.NavigateToInvite + TrainerHomeDialog.HomeConnect -> TrainerHomeSideEffect.NavigateToInvite + TrainerHomeDialog.AddPtConnect -> TrainerHomeSideEffect.NavigateToInvite else -> return } updateState { - copy(dialogState = DialogState.NONE, isDialogHiddenForThreeDays = false) + copy( + dialogState = TrainerHomeDialog.None, + isDialogHiddenForThreeDays = false, + ) } sendEffect(effect) } @@ -230,7 +245,7 @@ internal class TrainerHomeViewModel @Inject constructor( } updateState { copy( - dialogState = DialogState.NONE, + dialogState = TrainerHomeDialog.None, isDialogHiddenForThreeDays = false, ) } diff --git a/feature/trainer/home/src/main/res/values/strings.xml b/feature/trainer/home/src/main/res/values/strings.xml index f9c25c95..e41e5cd7 100644 --- a/feature/trainer/home/src/main/res/values/strings.xml +++ b/feature/trainer/home/src/main/res/values/strings.xml @@ -8,4 +8,6 @@ 아직 등록된 수업이 없어요. 추가 버튼을 눌러 PT 수업 일정을 추가해 보세요 %1$s회차 수업 + 수업을 기록하시겠어요? + 아직 수업 시간이 지나지 않았어요