From e401a09bcc421b20c57d605ece72034cdef2fe55 Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Wed, 5 Nov 2025 02:24:12 +0900 Subject: [PATCH 01/12] =?UTF-8?q?Refactor:=20=EB=A3=A8=ED=8B=B4=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=EB=AA=A8=EB=8D=B8=20=ED=94=84=EB=A1=9C=ED=8D=BC?= =?UTF-8?q?=ED=8B=B0=20=EB=B0=8F=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EB=84=A4?= =?UTF-8?q?=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Routines -> RoutineSchedule --- .../routine/model/response/DayRoutinesDto.kt | 4 +- .../data/routine/model/response/RoutineDto.kt | 12 ++--- .../model/response/RoutinesResponseDto.kt | 6 +-- .../repositoryImpl/RoutineRepositoryImpl.kt | 4 +- .../domain/routine/model/DayRoutines.kt | 11 +++- .../bitnagil/domain/routine/model/Routine.kt | 12 ++--- .../domain/routine/model/RoutineSchedule.kt | 11 ++++ .../bitnagil/domain/routine/model/Routines.kt | 5 -- .../routine/repository/RoutineRepository.kt | 4 +- .../usecase/FetchWeeklyRoutinesUseCase.kt | 4 +- .../bitnagil/presentation/home/HomeScreen.kt | 6 +-- .../presentation/home/HomeViewModel.kt | 54 +++++++++---------- .../home/component/block/RoutineItem.kt | 28 +++++----- .../component/template/WeeklyDatePicker.kt | 8 +-- .../home/model/DayRoutinesUiModel.kt | 8 +-- .../presentation/home/model/HomeIntent.kt | 2 +- .../presentation/home/model/HomeState.kt | 4 +- .../home/model/RoutineScheduleUiModel.kt | 17 ++++++ .../presentation/home/model/RoutineUiModel.kt | 20 +++---- .../home/model/RoutinesUiModel.kt | 17 ------ .../routinelist/model/DayRoutinesUiModel.kt | 2 +- .../routinelist/model/RoutineUiModel.kt | 8 +-- .../routinelist/model/RoutinesUiModel.kt | 6 +-- .../writeroutine/WriteRoutineViewModel.kt | 4 +- 24 files changed, 135 insertions(+), 122 deletions(-) create mode 100644 domain/src/main/java/com/threegap/bitnagil/domain/routine/model/RoutineSchedule.kt delete mode 100644 domain/src/main/java/com/threegap/bitnagil/domain/routine/model/Routines.kt create mode 100644 presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/RoutineScheduleUiModel.kt delete mode 100644 presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/RoutinesUiModel.kt diff --git a/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/DayRoutinesDto.kt b/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/DayRoutinesDto.kt index 6ceff985..d6f8ce6e 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/DayRoutinesDto.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/DayRoutinesDto.kt @@ -14,6 +14,6 @@ data class DayRoutinesDto( fun DayRoutinesDto.toDomain(): DayRoutines = DayRoutines( - routineList = routineList.map { it.toDomain() }, - allCompleted = allCompleted, + routines = routineList.map { it.toDomain() }, + isAllCompleted = allCompleted, ) diff --git a/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutineDto.kt b/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutineDto.kt index d78c6a20..cd6e279e 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutineDto.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutineDto.kt @@ -36,16 +36,16 @@ data class RoutineDto( fun RoutineDto.toDomain(): Routine = Routine( - routineId = this.routineId, - routineName = this.routineName, - repeatDay = this.repeatDay.map { DayOfWeek.fromString(it) }, + id = this.routineId, + name = this.routineName, + repeatDays = this.repeatDay.map { DayOfWeek.fromString(it) }, executionTime = this.executionTime, routineDate = this.routineDate, - routineCompleteYn = this.routineCompleteYn, + isComplete = this.routineCompleteYn, subRoutineNames = this.subRoutineNames, - subRoutineCompleteYn = this.subRoutineCompleteYn, + subRoutineIsCompleted = this.subRoutineCompleteYn, recommendedRoutineType = RecommendedRoutineType.fromString(this.recommendedRoutineType), - routineDeletedYn = routineDeletedYn, + isDeleted = routineDeletedYn, startDate = this.routineStartDate, endDate = this.routineEndDate, ) diff --git a/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutinesResponseDto.kt b/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutinesResponseDto.kt index bbf5f92f..5f9fd500 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutinesResponseDto.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutinesResponseDto.kt @@ -1,6 +1,6 @@ package com.threegap.bitnagil.data.routine.model.response -import com.threegap.bitnagil.domain.routine.model.Routines +import com.threegap.bitnagil.domain.routine.model.RoutineSchedule import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -11,8 +11,8 @@ data class RoutinesResponseDto( ) fun RoutinesResponseDto.toDomain() = - Routines( - routines = this.routines.mapValues { (_, dayRoutinesDto) -> + RoutineSchedule( + dailyRoutines = this.routines.mapValues { (_, dayRoutinesDto) -> dayRoutinesDto.toDomain() }, ) diff --git a/data/src/main/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImpl.kt b/data/src/main/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImpl.kt index 2e1542b0..2c76fe7a 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImpl.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/routine/repositoryImpl/RoutineRepositoryImpl.kt @@ -5,14 +5,14 @@ import com.threegap.bitnagil.data.routine.model.request.toDto import com.threegap.bitnagil.data.routine.model.response.toDomain import com.threegap.bitnagil.domain.routine.model.Routine import com.threegap.bitnagil.domain.routine.model.RoutineCompletionInfos -import com.threegap.bitnagil.domain.routine.model.Routines +import com.threegap.bitnagil.domain.routine.model.RoutineSchedule import com.threegap.bitnagil.domain.routine.repository.RoutineRepository import javax.inject.Inject class RoutineRepositoryImpl @Inject constructor( private val routineRemoteDataSource: RoutineRemoteDataSource, ) : RoutineRepository { - override suspend fun fetchWeeklyRoutines(startDate: String, endDate: String): Result = + override suspend fun fetchWeeklyRoutines(startDate: String, endDate: String): Result = routineRemoteDataSource.fetchWeeklyRoutines(startDate, endDate) .map { it.toDomain() } diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/DayRoutines.kt b/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/DayRoutines.kt index 2fb1abdd..f7e39d8e 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/DayRoutines.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/DayRoutines.kt @@ -1,6 +1,13 @@ package com.threegap.bitnagil.domain.routine.model +/** + * 특정 하루("일간")의 루틴 정보. + * [RoutineSchedule] Map의 '값(value)'으로 사용됩니다. + * + * @property routines 해당 날짜에 포함된 [Routine](개별 루틴)의 목록. + * @property isAllCompleted 해당 날짜의 모든 루틴이 완료되었는지 여부. + */ data class DayRoutines( - val routineList: List, - val allCompleted: Boolean, + val routines: List, + val isAllCompleted: Boolean, ) diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/Routine.kt b/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/Routine.kt index 3f9eee0d..dd74a4da 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/Routine.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/Routine.kt @@ -1,16 +1,16 @@ package com.threegap.bitnagil.domain.routine.model data class Routine( - val routineId: String, - val routineName: String, - val repeatDay: List, + val id: String, + val name: String, + val repeatDays: List, val executionTime: String, val startDate: String, val endDate: String, val routineDate: String, - val routineCompleteYn: Boolean, - val routineDeletedYn: Boolean, + val isCompleted: Boolean, + val isDeleted: Boolean, val subRoutineNames: List, - val subRoutineCompleteYn: List, + val subRoutineIsCompleted: List, val recommendedRoutineType: RecommendedRoutineType?, ) diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/RoutineSchedule.kt b/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/RoutineSchedule.kt new file mode 100644 index 00000000..c335889b --- /dev/null +++ b/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/RoutineSchedule.kt @@ -0,0 +1,11 @@ +package com.threegap.bitnagil.domain.routine.model + +/** + * 특정 기간(예: 주간, 월간) 동안의 루틴 일정표. + * + * @property dailyRoutines 날짜(String, "YYYY-MM-DD")를 key로, + * 해당 날짜의 루틴 정보([DayRoutines])를 value로 가지는 Map. + */ +data class RoutineSchedule( + val dailyRoutines: Map, +) diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/Routines.kt b/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/Routines.kt deleted file mode 100644 index 93a895b8..00000000 --- a/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/Routines.kt +++ /dev/null @@ -1,5 +0,0 @@ -package com.threegap.bitnagil.domain.routine.model - -data class Routines( - val routines: Map, -) diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/routine/repository/RoutineRepository.kt b/domain/src/main/java/com/threegap/bitnagil/domain/routine/repository/RoutineRepository.kt index d978d967..cc2625d3 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/routine/repository/RoutineRepository.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/routine/repository/RoutineRepository.kt @@ -2,10 +2,10 @@ package com.threegap.bitnagil.domain.routine.repository import com.threegap.bitnagil.domain.routine.model.Routine import com.threegap.bitnagil.domain.routine.model.RoutineCompletionInfos -import com.threegap.bitnagil.domain.routine.model.Routines +import com.threegap.bitnagil.domain.routine.model.RoutineSchedule interface RoutineRepository { - suspend fun fetchWeeklyRoutines(startDate: String, endDate: String): Result + suspend fun fetchWeeklyRoutines(startDate: String, endDate: String): Result suspend fun syncRoutineCompletion(routineCompletionInfos: RoutineCompletionInfos): Result suspend fun getRoutine(routineId: String): Result suspend fun deleteRoutine(routineId: String): Result diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/FetchWeeklyRoutinesUseCase.kt b/domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/FetchWeeklyRoutinesUseCase.kt index 0876e3fe..6ff39b5e 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/FetchWeeklyRoutinesUseCase.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/FetchWeeklyRoutinesUseCase.kt @@ -1,12 +1,12 @@ package com.threegap.bitnagil.domain.routine.usecase -import com.threegap.bitnagil.domain.routine.model.Routines +import com.threegap.bitnagil.domain.routine.model.RoutineSchedule import com.threegap.bitnagil.domain.routine.repository.RoutineRepository import javax.inject.Inject class FetchWeeklyRoutinesUseCase @Inject constructor( private val routineRepository: RoutineRepository, ) { - suspend operator fun invoke(startDate: String, endDate: String): Result = + suspend operator fun invoke(startDate: String, endDate: String): Result = routineRepository.fetchWeeklyRoutines(startDate, endDate) } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeScreen.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeScreen.kt index 3ac06c15..41e8c8fc 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeScreen.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeScreen.kt @@ -183,15 +183,15 @@ private fun HomeScreen( ) { items( items = uiState.selectedDateRoutines, - key = { routine -> "${routine.routineId}_${uiState.selectedDate}" }, + key = { routine -> "${routine.id}_${uiState.selectedDate}" }, ) { routine -> RoutineSection( routine = routine, onRoutineToggle = { isCompleted -> - onRoutineCompletionToggle(routine.routineId, isCompleted) + onRoutineCompletionToggle(routine.id, isCompleted) }, onSubRoutineToggle = { subRoutineIndex, isCompleted -> - onSubRoutineCompletionToggle(routine.routineId, subRoutineIndex, isCompleted) + onSubRoutineCompletionToggle(routine.id, subRoutineIndex, isCompleted) }, ) } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt index 6439515b..8e7b91e3 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt @@ -16,8 +16,8 @@ import com.threegap.bitnagil.presentation.common.mviviewmodel.MviViewModel import com.threegap.bitnagil.presentation.home.model.HomeIntent import com.threegap.bitnagil.presentation.home.model.HomeSideEffect import com.threegap.bitnagil.presentation.home.model.HomeState +import com.threegap.bitnagil.presentation.home.model.RoutineScheduleUiModel import com.threegap.bitnagil.presentation.home.model.RoutineUiModel -import com.threegap.bitnagil.presentation.home.model.RoutinesUiModel import com.threegap.bitnagil.presentation.home.model.toUiModel import com.threegap.bitnagil.presentation.home.util.getCurrentWeekDays import dagger.hilt.android.lifecycle.HiltViewModel @@ -47,7 +47,7 @@ class HomeViewModel @Inject constructor( savedStateHandle = savedStateHandle, ) { private val pendingChangesByDate = mutableMapOf>() - private val backupStatesByDate = mutableMapOf() + private val backupStatesByDate = mutableMapOf() private val routineSyncTrigger = MutableSharedFlow() init { @@ -284,13 +284,13 @@ class HomeViewModel @Inject constructor( isCompleted: Boolean, ): HomeState { return updateRoutinesForDate(state) { routinesForDate -> - val routineIndex = routinesForDate.indexOfFirst { it.routineId == routineId } + val routineIndex = routinesForDate.indexOfFirst { it.id == routineId } if (routineIndex == -1) return@updateRoutinesForDate false val routine = routinesForDate[routineIndex] val updatedRoutine = routine.copy( - routineCompleteYn = isCompleted, - subRoutineCompleteYn = routine.subRoutineCompleteYn.map { isCompleted }, + isCompleted = isCompleted, + subRoutineIsCompleted = routine.subRoutineIsCompleted.map { isCompleted }, ) routinesForDate[routineIndex] = updatedRoutine @@ -305,24 +305,24 @@ class HomeViewModel @Inject constructor( isCompleted: Boolean, ): HomeState { return updateRoutinesForDate(state) { routinesForDate -> - val routineIndex = routinesForDate.indexOfFirst { it.routineId == routineId } + val routineIndex = routinesForDate.indexOfFirst { it.id == routineId } if (routineIndex == -1) return@updateRoutinesForDate false val routine = routinesForDate[routineIndex] - if (subRoutineIndex !in routine.subRoutineCompleteYn.indices) { + if (subRoutineIndex !in routine.subRoutineIsCompleted.indices) { return@updateRoutinesForDate false } - val updatedSubRoutineCompleteYn = routine.subRoutineCompleteYn.toMutableList().also { + val updatedSubRoutineCompleteYn = routine.subRoutineIsCompleted.toMutableList().also { it[subRoutineIndex] = isCompleted } val routineCompleted = updatedSubRoutineCompleteYn.all { it } val updatedRoutine = routine.copy( - subRoutineCompleteYn = updatedSubRoutineCompleteYn, - routineCompleteYn = routineCompleted, + subRoutineIsCompleted = updatedSubRoutineCompleteYn, + isCompleted = routineCompleted, ) routinesForDate[routineIndex] = updatedRoutine @@ -335,44 +335,44 @@ class HomeViewModel @Inject constructor( updateLogic: (MutableList) -> Boolean, ): HomeState { val dateKey = state.selectedDate.toString() - val dayRoutines = state.routines.routines[dateKey] ?: return state - val routinesForDate = dayRoutines.routineList.toMutableList() + val dayRoutines = state.routines.dailyRoutines[dateKey] ?: return state + val routinesForDate = dayRoutines.routines.toMutableList() if (!updateLogic(routinesForDate)) return state - val allCompleted = routinesForDate.all { it.routineCompleteYn } + val allCompleted = routinesForDate.all { it.isCompleted } - val updatedRoutinesByDate = state.routines.routines.toMutableMap() + val updatedRoutinesByDate = state.routines.dailyRoutines.toMutableMap() updatedRoutinesByDate[dateKey] = dayRoutines.copy( - routineList = routinesForDate, - allCompleted = allCompleted, + routines = routinesForDate, + isAllCompleted = allCompleted, ) - return state.copy(routines = RoutinesUiModel(updatedRoutinesByDate)) + return state.copy(routines = RoutineScheduleUiModel(updatedRoutinesByDate)) } private fun calculateStateChanges( - originalRoutines: RoutinesUiModel, - updatedRoutines: RoutinesUiModel, + originalRoutines: RoutineScheduleUiModel, + updatedRoutines: RoutineScheduleUiModel, date: LocalDate, ): List { val dateKey = date.toString() - val originalRoutineList = originalRoutines.routines[dateKey]?.routineList ?: emptyList() - val updatedRoutineList = updatedRoutines.routines[dateKey]?.routineList ?: emptyList() + val originalRoutineList = originalRoutines.dailyRoutines[dateKey]?.routines ?: emptyList() + val updatedRoutineList = updatedRoutines.dailyRoutines[dateKey]?.routines ?: emptyList() return buildList { updatedRoutineList.forEach { updatedRoutine -> - val originalRoutine = originalRoutineList.find { it.routineId == updatedRoutine.routineId } + val originalRoutine = originalRoutineList.find { it.id == updatedRoutine.id } - val hasMainRoutineChanged = originalRoutine?.routineCompleteYn != updatedRoutine.routineCompleteYn - val hasSubRoutinesChanged = originalRoutine?.subRoutineCompleteYn != updatedRoutine.subRoutineCompleteYn + val hasMainRoutineChanged = originalRoutine?.isCompleted != updatedRoutine.isCompleted + val hasSubRoutinesChanged = originalRoutine?.subRoutineIsCompleted != updatedRoutine.subRoutineIsCompleted if (hasMainRoutineChanged || hasSubRoutinesChanged) { add( RoutineCompletionInfo( - routineId = updatedRoutine.routineId, - routineCompleteYn = updatedRoutine.routineCompleteYn, - subRoutineCompleteYn = updatedRoutine.subRoutineCompleteYn, + routineId = updatedRoutine.id, + routineCompleteYn = updatedRoutine.isCompleted, + subRoutineCompleteYn = updatedRoutine.subRoutineIsCompleted, ), ) } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/block/RoutineItem.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/block/RoutineItem.kt index acadbc38..fde2f94f 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/block/RoutineItem.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/block/RoutineItem.kt @@ -43,19 +43,19 @@ fun RoutineItem( Row( modifier = Modifier .fillMaxWidth() - .clickableWithoutRipple { onRoutineToggle(!routine.routineCompleteYn) }, + .clickableWithoutRipple { onRoutineToggle(!routine.isCompleted) }, horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { Text( - text = routine.routineName, + text = routine.name, style = BitnagilTheme.typography.body1SemiBold, color = BitnagilTheme.colors.coolGray10, modifier = Modifier.weight(1f), ) BitnagilIcon( - id = if (routine.routineCompleteYn) R.drawable.ic_check_circle else R.drawable.ic_check_default, + id = if (routine.isCompleted) R.drawable.ic_check_circle else R.drawable.ic_check_default, tint = null, modifier = Modifier .padding(start = 10.dp) @@ -72,7 +72,7 @@ fun RoutineItem( SubRoutinesItem( subRoutineNames = routine.subRoutineNames, - subRoutineCompleteYn = routine.subRoutineCompleteYn, + subRoutineCompleteYn = routine.subRoutineIsCompleted, onSubRoutineToggle = { index, isCompleted -> onSubRoutineToggle(index, isCompleted) }, @@ -86,14 +86,14 @@ fun RoutineItem( private fun RoutineItemPreview() { RoutineItem( routine = RoutineUiModel( - routineId = "uuid1", - routineName = "개운하게 일어나기", - repeatDay = emptyList(), + id = "uuid1", + name = "개운하게 일어나기", + repeatDays = emptyList(), executionTime = "20:30:00", routineDate = "2025-08-15", - routineCompleteYn = false, + isCompleted = false, subRoutineNames = listOf("물 마시기", "스트레칭하기", "심호흡하기"), - subRoutineCompleteYn = listOf(true, false, true), + subRoutineIsCompleted = listOf(true, false, true), recommendedRoutineType = null, ), onRoutineToggle = { }, @@ -106,14 +106,14 @@ private fun RoutineItemPreview() { private fun NoneSubRoutineRoutineItemPreview() { RoutineItem( routine = RoutineUiModel( - routineId = "uuid1", - routineName = "개운하게 일어나기", - repeatDay = emptyList(), + id = "uuid1", + name = "개운하게 일어나기", + repeatDays = emptyList(), executionTime = "20:30:00", routineDate = "2025-08-15", - routineCompleteYn = false, + isCompleted = false, subRoutineNames = emptyList(), - subRoutineCompleteYn = emptyList(), + subRoutineIsCompleted = emptyList(), recommendedRoutineType = null, ), onRoutineToggle = {}, diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/WeeklyDatePicker.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/WeeklyDatePicker.kt index 45eed46a..0ee23625 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/WeeklyDatePicker.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/WeeklyDatePicker.kt @@ -37,7 +37,7 @@ import com.threegap.bitnagil.designsystem.R import com.threegap.bitnagil.designsystem.component.atom.BitnagilIcon import com.threegap.bitnagil.designsystem.component.atom.BitnagilIconButton import com.threegap.bitnagil.designsystem.modifier.clickableWithoutRipple -import com.threegap.bitnagil.presentation.home.model.RoutinesUiModel +import com.threegap.bitnagil.presentation.home.model.RoutineScheduleUiModel import com.threegap.bitnagil.presentation.home.util.formatDayOfMonth import com.threegap.bitnagil.presentation.home.util.formatDayOfWeekShort import com.threegap.bitnagil.presentation.home.util.formatMonthYear @@ -48,7 +48,7 @@ import java.time.LocalDate fun WeeklyDatePicker( selectedDate: LocalDate, weeklyDates: List, - routines: RoutinesUiModel, + routines: RoutineScheduleUiModel, onDateSelect: (LocalDate) -> Unit, onPreviousWeekClick: () -> Unit, onNextWeekClick: () -> Unit, @@ -57,7 +57,7 @@ fun WeeklyDatePicker( val today = remember { LocalDate.now() } val completionStates = remember(weeklyDates, routines) { weeklyDates.associateWith { date -> - routines.routines[date.toString()]?.allCompleted ?: false + routines.dailyRoutines[date.toString()]?.isAllCompleted ?: false } } @@ -197,7 +197,7 @@ private fun WeeklyDatePickerPreview() { WeeklyDatePicker( selectedDate = selectedDate, weeklyDates = selectedDate.getCurrentWeekDays(), - routines = RoutinesUiModel(), + routines = RoutineScheduleUiModel(), onDateSelect = { selectedDate = it }, onPreviousWeekClick = { selectedDate = selectedDate.minusWeeks(1) }, onNextWeekClick = { selectedDate = selectedDate.plusWeeks(1) }, diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/DayRoutinesUiModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/DayRoutinesUiModel.kt index a17726fa..41fa80a7 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/DayRoutinesUiModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/DayRoutinesUiModel.kt @@ -6,12 +6,12 @@ import kotlinx.parcelize.Parcelize @Parcelize data class DayRoutinesUiModel( - val routineList: List = emptyList(), - val allCompleted: Boolean = false, + val routines: List = emptyList(), + val isAllCompleted: Boolean = false, ) : Parcelable fun DayRoutines.toUiModel(): DayRoutinesUiModel = DayRoutinesUiModel( - routineList = routineList.map { it.toUiModel() }, - allCompleted = allCompleted, + routines = routines.map { it.toUiModel() }, + isAllCompleted = isAllCompleted, ) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeIntent.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeIntent.kt index 4d4a6e68..876f165d 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeIntent.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeIntent.kt @@ -7,7 +7,7 @@ sealed class HomeIntent : MviIntent { data class UpdateLoading(val isLoading: Boolean) : HomeIntent() data class LoadUserProfile(val nickname: String) : HomeIntent() data class LoadTodayEmotion(val emotion: TodayEmotionUiModel?) : HomeIntent() - data class LoadWeeklyRoutines(val routines: RoutinesUiModel) : HomeIntent() + data class LoadWeeklyRoutines(val routines: RoutineScheduleUiModel) : HomeIntent() data class OnDateSelect(val date: LocalDate) : HomeIntent() data class OnRoutineCompletionToggle(val routineId: String, val isCompleted: Boolean) : HomeIntent() data class OnSubRoutineCompletionToggle(val routineId: String, val subRoutineIndex: Int, val isCompleted: Boolean) : HomeIntent() diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeState.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeState.kt index 63b7fc66..8a34d2db 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeState.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeState.kt @@ -12,8 +12,8 @@ data class HomeState( val todayEmotion: TodayEmotionUiModel? = null, val selectedDate: LocalDate = LocalDate.now(), val currentWeeks: List = LocalDate.now().getCurrentWeekDays(), - val routines: RoutinesUiModel = RoutinesUiModel(), + val routines: RoutineScheduleUiModel = RoutineScheduleUiModel(), ) : MviState { val selectedDateRoutines: List - get() = routines.routines[selectedDate.toString()]?.routineList ?: emptyList() + get() = routines.dailyRoutines[selectedDate.toString()]?.routines ?: emptyList() } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/RoutineScheduleUiModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/RoutineScheduleUiModel.kt new file mode 100644 index 00000000..9b8aee3b --- /dev/null +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/RoutineScheduleUiModel.kt @@ -0,0 +1,17 @@ +package com.threegap.bitnagil.presentation.home.model + +import android.os.Parcelable +import com.threegap.bitnagil.domain.routine.model.RoutineSchedule +import kotlinx.parcelize.Parcelize + +@Parcelize +data class RoutineScheduleUiModel( + val dailyRoutines: Map = emptyMap(), +) : Parcelable + +fun RoutineSchedule.toUiModel(): RoutineScheduleUiModel = + RoutineScheduleUiModel( + dailyRoutines = this.dailyRoutines.mapValues { (_, dayRoutines) -> + dayRoutines.toUiModel() + }, + ) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/RoutineUiModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/RoutineUiModel.kt index b1059f56..935999e3 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/RoutineUiModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/RoutineUiModel.kt @@ -8,26 +8,26 @@ import kotlinx.parcelize.Parcelize @Parcelize data class RoutineUiModel( - val routineId: String, - val routineName: String, - val repeatDay: List, + val id: String, + val name: String, + val repeatDays: List, val executionTime: String, val routineDate: String, - val routineCompleteYn: Boolean, + val isCompleted: Boolean, val subRoutineNames: List, - val subRoutineCompleteYn: List, + val subRoutineIsCompleted: List, val recommendedRoutineType: RecommendedRoutineType?, ) : Parcelable fun Routine.toUiModel(): RoutineUiModel = RoutineUiModel( - routineId = this.routineId, - routineName = this.routineName, - repeatDay = this.repeatDay, + id = this.id, + name = this.name, + repeatDays = this.repeatDays, executionTime = this.executionTime, routineDate = this.routineDate, - routineCompleteYn = this.routineCompleteYn, + isCompleted = this.isCompleted, subRoutineNames = this.subRoutineNames, - subRoutineCompleteYn = this.subRoutineCompleteYn, + subRoutineIsCompleted = this.subRoutineIsCompleted, recommendedRoutineType = this.recommendedRoutineType, ) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/RoutinesUiModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/RoutinesUiModel.kt deleted file mode 100644 index 928c7f63..00000000 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/RoutinesUiModel.kt +++ /dev/null @@ -1,17 +0,0 @@ -package com.threegap.bitnagil.presentation.home.model - -import android.os.Parcelable -import com.threegap.bitnagil.domain.routine.model.Routines -import kotlinx.parcelize.Parcelize - -@Parcelize -data class RoutinesUiModel( - val routines: Map = emptyMap(), -) : Parcelable - -fun Routines.toUiModel(): RoutinesUiModel = - RoutinesUiModel( - routines = this.routines.mapValues { (_, dayRoutines) -> - dayRoutines.toUiModel() - }, - ) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/DayRoutinesUiModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/DayRoutinesUiModel.kt index b3dc638f..05d6db48 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/DayRoutinesUiModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/DayRoutinesUiModel.kt @@ -11,5 +11,5 @@ data class DayRoutinesUiModel( fun DayRoutines.toUiModel(): DayRoutinesUiModel = DayRoutinesUiModel( - routineList = routineList.map { it.toUiModel() }, + routineList = routines.map { it.toUiModel() }, ) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/RoutineUiModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/RoutineUiModel.kt index 0796f915..41a1b9eb 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/RoutineUiModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/RoutineUiModel.kt @@ -31,14 +31,14 @@ data class RoutineUiModel( fun Routine.toUiModel(): RoutineUiModel = RoutineUiModel( - routineId = this.routineId, - routineName = this.routineName, - repeatDay = this.repeatDay, + routineId = this.id, + routineName = this.name, + repeatDay = this.repeatDays, executionTime = this.executionTime.formatExecutionTime12Hour(), routineDate = this.routineDate, startDate = this.startDate, endDate = this.endDate, - routineDeletedYn = this.routineDeletedYn, + routineDeletedYn = this.isDeleted, subRoutineNames = this.subRoutineNames, recommendedRoutineType = this.recommendedRoutineType, ) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/RoutinesUiModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/RoutinesUiModel.kt index e9d5ffb4..21fd81c7 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/RoutinesUiModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/RoutinesUiModel.kt @@ -1,7 +1,7 @@ package com.threegap.bitnagil.presentation.routinelist.model import android.os.Parcelable -import com.threegap.bitnagil.domain.routine.model.Routines +import com.threegap.bitnagil.domain.routine.model.RoutineSchedule import kotlinx.parcelize.Parcelize @Parcelize @@ -9,9 +9,9 @@ data class RoutinesUiModel( val routines: Map = emptyMap(), ) : Parcelable -fun Routines.toUiModel(): RoutinesUiModel = +fun RoutineSchedule.toUiModel(): RoutinesUiModel = RoutinesUiModel( - routines = this.routines.mapValues { (_, dayRoutines) -> + routines = this.dailyRoutines.mapValues { (_, dayRoutines) -> dayRoutines.toUiModel() }, ) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt index 5f8f869e..ad4227ba 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/writeroutine/WriteRoutineViewModel.kt @@ -83,8 +83,8 @@ class WriteRoutineViewModel @AssistedInject constructor( onSuccess = { routine -> sendIntent( WriteRoutineIntent.SetRoutine( - name = routine.routineName, - repeatDays = routine.repeatDay.map { Day.fromDayOfWeek(it) }, + name = routine.name, + repeatDays = routine.repeatDays.map { Day.fromDayOfWeek(it) }, startTime = Time.fromDomainTimeString(routine.executionTime), subRoutines = listOf( routine.subRoutineNames.getOrNull(0) ?: "", From 02db0f7e170aa3695ef64136e63fd03f4fef4b23 Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Wed, 5 Nov 2025 02:47:53 +0900 Subject: [PATCH 02/12] =?UTF-8?q?Refactor:=20=EB=A3=A8=ED=8B=B4=20?= =?UTF-8?q?=EC=99=84=EB=A3=8C=20=ED=86=A0=EA=B8=80=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=ED=98=B8=EC=9D=B4=EC=8A=A4=ED=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 루틴/서브루틴 완료 여부를 토글할 때, 기존의 isCompleted Boolean 값을 직접 전달하는 방식에서 파라미터 없이 호출하는 방식으로 변경 --- .../data/routine/model/response/RoutineDto.kt | 2 +- .../bitnagil/presentation/home/HomeScreen.kt | 24 ++++++++--------- .../presentation/home/HomeViewModel.kt | 27 ++++++++++--------- .../home/component/block/RoutineItem.kt | 14 +++++----- .../home/component/block/SubRoutinesItem.kt | 6 ++--- .../home/component/template/RoutineSection.kt | 4 +-- .../presentation/home/model/HomeIntent.kt | 4 +-- 7 files changed, 39 insertions(+), 42 deletions(-) diff --git a/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutineDto.kt b/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutineDto.kt index cd6e279e..f5d91d72 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutineDto.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutineDto.kt @@ -41,7 +41,7 @@ fun RoutineDto.toDomain(): Routine = repeatDays = this.repeatDay.map { DayOfWeek.fromString(it) }, executionTime = this.executionTime, routineDate = this.routineDate, - isComplete = this.routineCompleteYn, + isCompleted = this.routineCompleteYn, subRoutineNames = this.subRoutineNames, subRoutineIsCompleted = this.subRoutineCompleteYn, recommendedRoutineType = RecommendedRoutineType.fromString(this.recommendedRoutineType), diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeScreen.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeScreen.kt index 41e8c8fc..52aecba5 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeScreen.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeScreen.kt @@ -77,11 +77,11 @@ fun HomeScreenContainer( onNextWeekClick = { viewModel.sendIntent(HomeIntent.OnNextWeekClick) }, - onRoutineCompletionToggle = { routineId, isCompleted -> - viewModel.toggleRoutineCompletion(routineId, isCompleted) + onRoutineCompletionToggle = { routineId -> + viewModel.toggleRoutineCompletion(routineId) }, - onSubRoutineCompletionToggle = { routineId, subRoutineIndex, isCompleted -> - viewModel.toggleSubRoutineCompletion(routineId, subRoutineIndex, isCompleted) + onSubRoutineCompletionToggle = { routineId, subRoutineIndex -> + viewModel.toggleSubRoutineCompletion(routineId, subRoutineIndex) }, onHelpClick = { viewModel.sendIntent(HomeIntent.OnHelpClick) @@ -104,8 +104,8 @@ private fun HomeScreen( onDateSelect: (LocalDate) -> Unit, onPreviousWeekClick: () -> Unit, onNextWeekClick: () -> Unit, - onRoutineCompletionToggle: (String, Boolean) -> Unit, - onSubRoutineCompletionToggle: (String, Int, Boolean) -> Unit, + onRoutineCompletionToggle: (String) -> Unit, + onSubRoutineCompletionToggle: (String, Int) -> Unit, onHelpClick: () -> Unit, onRegisterRoutineClick: () -> Unit, onRegisterEmotionClick: () -> Unit, @@ -187,11 +187,9 @@ private fun HomeScreen( ) { routine -> RoutineSection( routine = routine, - onRoutineToggle = { isCompleted -> - onRoutineCompletionToggle(routine.id, isCompleted) - }, - onSubRoutineToggle = { subRoutineIndex, isCompleted -> - onSubRoutineCompletionToggle(routine.id, subRoutineIndex, isCompleted) + onRoutineToggle = { onRoutineCompletionToggle(routine.id) }, + onSubRoutineToggle = { subRoutineIndex -> + onSubRoutineCompletionToggle(routine.id, subRoutineIndex) }, ) } @@ -217,8 +215,8 @@ private fun HomeScreenPreview() { onDateSelect = {}, onPreviousWeekClick = {}, onNextWeekClick = {}, - onRoutineCompletionToggle = { _, _ -> }, - onSubRoutineCompletionToggle = { _, _, _ -> }, + onRoutineCompletionToggle = { _ -> }, + onSubRoutineCompletionToggle = { _, _ -> }, onHelpClick = {}, onRegisterRoutineClick = {}, onRegisterEmotionClick = {}, diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt index 8e7b91e3..7c2e8f5f 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt @@ -99,11 +99,11 @@ class HomeViewModel @Inject constructor( } is HomeIntent.OnRoutineCompletionToggle -> { - updateMainRoutine(state, intent.routineId, intent.isCompleted) + updateMainRoutine(state, intent.routineId) } is HomeIntent.OnSubRoutineCompletionToggle -> { - updateSubRoutine(state, intent.routineId, intent.subRoutineIndex, intent.isCompleted) + updateSubRoutine(state, intent.routineId, intent.subRoutineIndex) } is HomeIntent.LoadTodayEmotion -> { @@ -238,19 +238,19 @@ class HomeViewModel @Inject constructor( } } - fun toggleRoutineCompletion(routineId: String, isCompleted: Boolean) { + fun toggleRoutineCompletion(routineId: String) { val originalState = container.stateFlow.value - sendIntent(HomeIntent.OnRoutineCompletionToggle(routineId, isCompleted)) + sendIntent(HomeIntent.OnRoutineCompletionToggle(routineId)) - val predictedUpdatedState = updateMainRoutine(originalState, routineId, isCompleted) + val predictedUpdatedState = updateMainRoutine(originalState, routineId) processRoutineToggleChanges(originalState, predictedUpdatedState) } - fun toggleSubRoutineCompletion(routineId: String, subRoutineIndex: Int, isCompleted: Boolean) { + fun toggleSubRoutineCompletion(routineId: String, subRoutineIndex: Int) { val originalState = container.stateFlow.value - sendIntent(HomeIntent.OnSubRoutineCompletionToggle(routineId, subRoutineIndex, isCompleted)) + sendIntent(HomeIntent.OnSubRoutineCompletionToggle(routineId, subRoutineIndex)) - val predictedUpdatedState = updateSubRoutine(originalState, routineId, subRoutineIndex, isCompleted) + val predictedUpdatedState = updateSubRoutine(originalState, routineId, subRoutineIndex) processRoutineToggleChanges(originalState, predictedUpdatedState) } @@ -281,16 +281,17 @@ class HomeViewModel @Inject constructor( private fun updateMainRoutine( state: HomeState, routineId: String, - isCompleted: Boolean, ): HomeState { return updateRoutinesForDate(state) { routinesForDate -> val routineIndex = routinesForDate.indexOfFirst { it.id == routineId } if (routineIndex == -1) return@updateRoutinesForDate false val routine = routinesForDate[routineIndex] + val newIsCompleted = !routine.isCompleted + val updatedRoutine = routine.copy( - isCompleted = isCompleted, - subRoutineIsCompleted = routine.subRoutineIsCompleted.map { isCompleted }, + isCompleted = newIsCompleted, + subRoutineIsCompleted = routine.subRoutineIsCompleted.map { newIsCompleted }, ) routinesForDate[routineIndex] = updatedRoutine @@ -302,20 +303,20 @@ class HomeViewModel @Inject constructor( state: HomeState, routineId: String, subRoutineIndex: Int, - isCompleted: Boolean, ): HomeState { return updateRoutinesForDate(state) { routinesForDate -> val routineIndex = routinesForDate.indexOfFirst { it.id == routineId } if (routineIndex == -1) return@updateRoutinesForDate false val routine = routinesForDate[routineIndex] + val newIsCompleted = !routine.subRoutineIsCompleted[subRoutineIndex] if (subRoutineIndex !in routine.subRoutineIsCompleted.indices) { return@updateRoutinesForDate false } val updatedSubRoutineCompleteYn = routine.subRoutineIsCompleted.toMutableList().also { - it[subRoutineIndex] = isCompleted + it[subRoutineIndex] = newIsCompleted } val routineCompleted = updatedSubRoutineCompleteYn.all { it } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/block/RoutineItem.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/block/RoutineItem.kt index fde2f94f..51bdb6b7 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/block/RoutineItem.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/block/RoutineItem.kt @@ -24,8 +24,8 @@ import com.threegap.bitnagil.presentation.home.model.RoutineUiModel @Composable fun RoutineItem( routine: RoutineUiModel, - onRoutineToggle: (Boolean) -> Unit, - onSubRoutineToggle: (Int, Boolean) -> Unit, + onRoutineToggle: () -> Unit, + onSubRoutineToggle: (Int) -> Unit, modifier: Modifier = Modifier, ) { Column( @@ -43,7 +43,7 @@ fun RoutineItem( Row( modifier = Modifier .fillMaxWidth() - .clickableWithoutRipple { onRoutineToggle(!routine.isCompleted) }, + .clickableWithoutRipple { onRoutineToggle() }, horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { @@ -73,9 +73,7 @@ fun RoutineItem( SubRoutinesItem( subRoutineNames = routine.subRoutineNames, subRoutineCompleteYn = routine.subRoutineIsCompleted, - onSubRoutineToggle = { index, isCompleted -> - onSubRoutineToggle(index, isCompleted) - }, + onSubRoutineToggle = { index -> onSubRoutineToggle(index) }, ) } } @@ -97,7 +95,7 @@ private fun RoutineItemPreview() { recommendedRoutineType = null, ), onRoutineToggle = { }, - onSubRoutineToggle = { _, _ -> }, + onSubRoutineToggle = { _ -> }, ) } @@ -117,6 +115,6 @@ private fun NoneSubRoutineRoutineItemPreview() { recommendedRoutineType = null, ), onRoutineToggle = {}, - onSubRoutineToggle = { _, _ -> }, + onSubRoutineToggle = { _ -> }, ) } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/block/SubRoutinesItem.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/block/SubRoutinesItem.kt index 01651f28..b713b466 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/block/SubRoutinesItem.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/block/SubRoutinesItem.kt @@ -20,7 +20,7 @@ import com.threegap.bitnagil.designsystem.modifier.clickableWithoutRipple fun SubRoutinesItem( subRoutineNames: List, subRoutineCompleteYn: List, - onSubRoutineToggle: (Int, Boolean) -> Unit, + onSubRoutineToggle: (Int) -> Unit, modifier: Modifier = Modifier, ) { val minSize = minOf(subRoutineNames.size, subRoutineCompleteYn.size) @@ -38,7 +38,7 @@ fun SubRoutinesItem( horizontalArrangement = Arrangement.spacedBy(10.dp), modifier = Modifier .fillMaxWidth() - .clickableWithoutRipple { onSubRoutineToggle(index, !isCompleted) }, + .clickableWithoutRipple { onSubRoutineToggle(index) }, ) { BitnagilIcon( id = if (isCompleted) R.drawable.ic_check_circle else R.drawable.ic_check_default, @@ -63,6 +63,6 @@ private fun SubRoutinesItemPreview() { SubRoutinesItem( subRoutineNames = listOf("물 마시기", "스트레칭하기", "심호흡하기"), subRoutineCompleteYn = listOf(true, false, true), - onSubRoutineToggle = { _, _ -> }, + onSubRoutineToggle = { _ -> }, ) } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/RoutineSection.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/RoutineSection.kt index 86c8c2f4..4bfaceac 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/RoutineSection.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/RoutineSection.kt @@ -17,8 +17,8 @@ import com.threegap.bitnagil.presentation.home.util.formatExecutionTime24Hour @Composable fun RoutineSection( routine: RoutineUiModel, - onRoutineToggle: (Boolean) -> Unit, - onSubRoutineToggle: (Int, Boolean) -> Unit, + onRoutineToggle: () -> Unit, + onSubRoutineToggle: (Int) -> Unit, modifier: Modifier = Modifier, ) { Row( diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeIntent.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeIntent.kt index 876f165d..6fab98ec 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeIntent.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeIntent.kt @@ -9,8 +9,8 @@ sealed class HomeIntent : MviIntent { data class LoadTodayEmotion(val emotion: TodayEmotionUiModel?) : HomeIntent() data class LoadWeeklyRoutines(val routines: RoutineScheduleUiModel) : HomeIntent() data class OnDateSelect(val date: LocalDate) : HomeIntent() - data class OnRoutineCompletionToggle(val routineId: String, val isCompleted: Boolean) : HomeIntent() - data class OnSubRoutineCompletionToggle(val routineId: String, val subRoutineIndex: Int, val isCompleted: Boolean) : HomeIntent() + data class OnRoutineCompletionToggle(val routineId: String) : HomeIntent() + data class OnSubRoutineCompletionToggle(val routineId: String, val subRoutineIndex: Int) : HomeIntent() data object RoutineToggleCompletionFailure : HomeIntent() data object OnHelpClick : HomeIntent() data object OnRegisterEmotionClick : HomeIntent() From 8d12ab7672547aa66ed650dfa0d32b0e2859dece Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Wed, 5 Nov 2025 09:35:59 +0900 Subject: [PATCH 03/12] =?UTF-8?q?Refactor:=20RoutineItem=20=ED=8C=8C?= =?UTF-8?q?=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존 RoutineUiModel에서 사용하는 프로퍼티만을 전달하도록 변경 --- .../home/component/block/RoutineItem.kt | 46 +++++-------- .../home/component/template/RoutineSection.kt | 68 ++++++------------- 2 files changed, 37 insertions(+), 77 deletions(-) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/block/RoutineItem.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/block/RoutineItem.kt index 51bdb6b7..7ecb619d 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/block/RoutineItem.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/block/RoutineItem.kt @@ -19,11 +19,13 @@ import com.threegap.bitnagil.designsystem.BitnagilTheme import com.threegap.bitnagil.designsystem.R import com.threegap.bitnagil.designsystem.component.atom.BitnagilIcon import com.threegap.bitnagil.designsystem.modifier.clickableWithoutRipple -import com.threegap.bitnagil.presentation.home.model.RoutineUiModel @Composable fun RoutineItem( - routine: RoutineUiModel, + name: String, + isCompleted: Boolean, + subRoutineNames: List, + subRoutineIsCompleted: List, onRoutineToggle: () -> Unit, onSubRoutineToggle: (Int) -> Unit, modifier: Modifier = Modifier, @@ -48,14 +50,14 @@ fun RoutineItem( verticalAlignment = Alignment.CenterVertically, ) { Text( - text = routine.name, + text = name, style = BitnagilTheme.typography.body1SemiBold, color = BitnagilTheme.colors.coolGray10, modifier = Modifier.weight(1f), ) BitnagilIcon( - id = if (routine.isCompleted) R.drawable.ic_check_circle else R.drawable.ic_check_default, + id = if (isCompleted) R.drawable.ic_check_circle else R.drawable.ic_check_default, tint = null, modifier = Modifier .padding(start = 10.dp) @@ -63,7 +65,7 @@ fun RoutineItem( ) } - if (routine.subRoutineNames.isNotEmpty()) { + if (subRoutineNames.isNotEmpty()) { HorizontalDivider( thickness = 1.dp, color = BitnagilTheme.colors.coolGray97, @@ -71,8 +73,8 @@ fun RoutineItem( ) SubRoutinesItem( - subRoutineNames = routine.subRoutineNames, - subRoutineCompleteYn = routine.subRoutineIsCompleted, + subRoutineNames = subRoutineNames, + subRoutineCompleteYn = subRoutineIsCompleted, onSubRoutineToggle = { index -> onSubRoutineToggle(index) }, ) } @@ -83,17 +85,10 @@ fun RoutineItem( @Composable private fun RoutineItemPreview() { RoutineItem( - routine = RoutineUiModel( - id = "uuid1", - name = "개운하게 일어나기", - repeatDays = emptyList(), - executionTime = "20:30:00", - routineDate = "2025-08-15", - isCompleted = false, - subRoutineNames = listOf("물 마시기", "스트레칭하기", "심호흡하기"), - subRoutineIsCompleted = listOf(true, false, true), - recommendedRoutineType = null, - ), + name = "개운하게 일어나기", + isCompleted = false, + subRoutineNames = listOf("물 마시기", "스트레칭하기", "심호흡하기"), + subRoutineIsCompleted = listOf(true, false, true), onRoutineToggle = { }, onSubRoutineToggle = { _ -> }, ) @@ -103,17 +98,10 @@ private fun RoutineItemPreview() { @Composable private fun NoneSubRoutineRoutineItemPreview() { RoutineItem( - routine = RoutineUiModel( - id = "uuid1", - name = "개운하게 일어나기", - repeatDays = emptyList(), - executionTime = "20:30:00", - routineDate = "2025-08-15", - isCompleted = false, - subRoutineNames = emptyList(), - subRoutineIsCompleted = emptyList(), - recommendedRoutineType = null, - ), + name = "개운하게 일어나기", + isCompleted = false, + subRoutineNames = emptyList(), + subRoutineIsCompleted = emptyList(), onRoutineToggle = {}, onSubRoutineToggle = { _ -> }, ) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/RoutineSection.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/RoutineSection.kt index 4bfaceac..b6c8bdee 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/RoutineSection.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/RoutineSection.kt @@ -10,6 +10,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.threegap.bitnagil.designsystem.BitnagilTheme +import com.threegap.bitnagil.domain.routine.model.DayOfWeek import com.threegap.bitnagil.presentation.home.component.block.RoutineItem import com.threegap.bitnagil.presentation.home.model.RoutineUiModel import com.threegap.bitnagil.presentation.home.util.formatExecutionTime24Hour @@ -33,7 +34,10 @@ fun RoutineSection( ) RoutineItem( - routine = routine, + name = routine.name, + isCompleted = routine.isCompleted, + subRoutineNames = routine.subRoutineNames, + subRoutineIsCompleted = routine.subRoutineIsCompleted, onRoutineToggle = onRoutineToggle, onSubRoutineToggle = onSubRoutineToggle, modifier = Modifier.fillMaxWidth(), @@ -44,51 +48,19 @@ fun RoutineSection( @Preview(showBackground = true) @Composable private fun RoutineSectionPreview() { -// RoutineSection( -// routine = RoutineUiModel( -// routineId = "uuid1", -// routineName = "개운하게 일어나기", -// executionTime = "20:30:00", -// routineCompletionId = 1, -// isCompleted = false, -// isModified = false, -// subRoutines = listOf( -// SubRoutineUiModel( -// subRoutineId = "uuid1", -// historySeq = 1, -// subRoutineName = "물 마시기", -// sortOrder = 1, -// routineCompletionId = 1, -// isCompleted = false, -// isModified = false, -// routineType = RoutineType.SUB_ROUTINE, -// ), -// SubRoutineUiModel( -// subRoutineId = "uuid2", -// historySeq = 1, -// subRoutineName = "스트레칭하기", -// sortOrder = 1, -// routineCompletionId = 1, -// isCompleted = false, -// isModified = false, -// routineType = RoutineType.SUB_ROUTINE, -// ), -// SubRoutineUiModel( -// subRoutineId = "uuid3", -// historySeq = 1, -// subRoutineName = "심호흡하기", -// sortOrder = 1, -// routineCompletionId = 1, -// isCompleted = false, -// isModified = false, -// routineType = RoutineType.SUB_ROUTINE, -// ), -// ), -// historySeq = 1, -// repeatDay = listOf(), -// routineType = RoutineType.ROUTINE, -// ), -// onRoutineToggle = {}, -// onSubRoutineToggle = { _, _ -> }, -// ) + RoutineSection( + routine = RoutineUiModel( + id = "1", + name = "개운하게 일어나기", + repeatDays = listOf(DayOfWeek.MONDAY, DayOfWeek.WEDNESDAY), + executionTime = "08:00", + routineDate = "2023-10-27", + isCompleted = false, + subRoutineNames = listOf("Make bed", "Brush teeth", "Meditate"), + subRoutineIsCompleted = listOf(true, false, false), + recommendedRoutineType = null + ), + onRoutineToggle = {}, + onSubRoutineToggle = {} + ) } From a94e1d50fb3d11d9617648a725f577c58b77ee7e Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Wed, 5 Nov 2025 15:41:55 +0900 Subject: [PATCH 04/12] =?UTF-8?q?Chore:=20DayRoutines=20->=20DailyRoutines?= =?UTF-8?q?=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../data/routine/model/response/DayRoutinesDto.kt | 6 +++--- .../bitnagil/data/routine/model/response/RoutineDto.kt | 2 +- .../routine/model/{DayRoutines.kt => DailyRoutines.kt} | 2 +- .../com/threegap/bitnagil/domain/routine/model/Routine.kt | 2 +- .../bitnagil/domain/routine/model/RoutineSchedule.kt | 4 ++-- .../home/component/template/RoutineSection.kt | 8 ++++---- .../{DayRoutinesUiModel.kt => DailyRoutinesUiModel.kt} | 8 ++++---- .../bitnagil/presentation/home/model/HomeIntent.kt | 4 ++-- .../bitnagil/presentation/home/model/HomeState.kt | 4 ++-- .../presentation/home/model/RoutineScheduleUiModel.kt | 2 +- .../bitnagil/presentation/home/model/RoutineUiModel.kt | 4 ++-- .../presentation/routinelist/model/DayRoutinesUiModel.kt | 4 ++-- 12 files changed, 25 insertions(+), 25 deletions(-) rename domain/src/main/java/com/threegap/bitnagil/domain/routine/model/{DayRoutines.kt => DailyRoutines.kt} (94%) rename presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/{DayRoutinesUiModel.kt => DailyRoutinesUiModel.kt} (65%) diff --git a/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/DayRoutinesDto.kt b/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/DayRoutinesDto.kt index d6f8ce6e..28bbdfcb 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/DayRoutinesDto.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/DayRoutinesDto.kt @@ -1,6 +1,6 @@ package com.threegap.bitnagil.data.routine.model.response -import com.threegap.bitnagil.domain.routine.model.DayRoutines +import com.threegap.bitnagil.domain.routine.model.DailyRoutines import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -12,8 +12,8 @@ data class DayRoutinesDto( val allCompleted: Boolean, ) -fun DayRoutinesDto.toDomain(): DayRoutines = - DayRoutines( +fun DayRoutinesDto.toDomain(): DailyRoutines = + DailyRoutines( routines = routineList.map { it.toDomain() }, isAllCompleted = allCompleted, ) diff --git a/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutineDto.kt b/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutineDto.kt index f5d91d72..e63d5756 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutineDto.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/routine/model/response/RoutineDto.kt @@ -43,7 +43,7 @@ fun RoutineDto.toDomain(): Routine = routineDate = this.routineDate, isCompleted = this.routineCompleteYn, subRoutineNames = this.subRoutineNames, - subRoutineIsCompleted = this.subRoutineCompleteYn, + subRoutineCompletionStates = this.subRoutineCompleteYn, recommendedRoutineType = RecommendedRoutineType.fromString(this.recommendedRoutineType), isDeleted = routineDeletedYn, startDate = this.routineStartDate, diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/DayRoutines.kt b/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/DailyRoutines.kt similarity index 94% rename from domain/src/main/java/com/threegap/bitnagil/domain/routine/model/DayRoutines.kt rename to domain/src/main/java/com/threegap/bitnagil/domain/routine/model/DailyRoutines.kt index f7e39d8e..894c2c92 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/DayRoutines.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/DailyRoutines.kt @@ -7,7 +7,7 @@ package com.threegap.bitnagil.domain.routine.model * @property routines 해당 날짜에 포함된 [Routine](개별 루틴)의 목록. * @property isAllCompleted 해당 날짜의 모든 루틴이 완료되었는지 여부. */ -data class DayRoutines( +data class DailyRoutines( val routines: List, val isAllCompleted: Boolean, ) diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/Routine.kt b/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/Routine.kt index dd74a4da..d275d11f 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/Routine.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/Routine.kt @@ -11,6 +11,6 @@ data class Routine( val isCompleted: Boolean, val isDeleted: Boolean, val subRoutineNames: List, - val subRoutineIsCompleted: List, + val subRoutineCompletionStates: List, val recommendedRoutineType: RecommendedRoutineType?, ) diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/RoutineSchedule.kt b/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/RoutineSchedule.kt index c335889b..99736fab 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/RoutineSchedule.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/RoutineSchedule.kt @@ -4,8 +4,8 @@ package com.threegap.bitnagil.domain.routine.model * 특정 기간(예: 주간, 월간) 동안의 루틴 일정표. * * @property dailyRoutines 날짜(String, "YYYY-MM-DD")를 key로, - * 해당 날짜의 루틴 정보([DayRoutines])를 value로 가지는 Map. + * 해당 날짜의 루틴 정보([DailyRoutines])를 value로 가지는 Map. */ data class RoutineSchedule( - val dailyRoutines: Map, + val dailyRoutines: Map, ) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/RoutineSection.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/RoutineSection.kt index b6c8bdee..5256e69b 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/RoutineSection.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/component/template/RoutineSection.kt @@ -37,7 +37,7 @@ fun RoutineSection( name = routine.name, isCompleted = routine.isCompleted, subRoutineNames = routine.subRoutineNames, - subRoutineIsCompleted = routine.subRoutineIsCompleted, + subRoutineIsCompleted = routine.subRoutineCompletionStates, onRoutineToggle = onRoutineToggle, onSubRoutineToggle = onSubRoutineToggle, modifier = Modifier.fillMaxWidth(), @@ -57,10 +57,10 @@ private fun RoutineSectionPreview() { routineDate = "2023-10-27", isCompleted = false, subRoutineNames = listOf("Make bed", "Brush teeth", "Meditate"), - subRoutineIsCompleted = listOf(true, false, false), - recommendedRoutineType = null + subRoutineCompletionStates = listOf(true, false, false), + recommendedRoutineType = null, ), onRoutineToggle = {}, - onSubRoutineToggle = {} + onSubRoutineToggle = {}, ) } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/DayRoutinesUiModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/DailyRoutinesUiModel.kt similarity index 65% rename from presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/DayRoutinesUiModel.kt rename to presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/DailyRoutinesUiModel.kt index 41fa80a7..295415b8 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/DayRoutinesUiModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/DailyRoutinesUiModel.kt @@ -1,17 +1,17 @@ package com.threegap.bitnagil.presentation.home.model import android.os.Parcelable -import com.threegap.bitnagil.domain.routine.model.DayRoutines +import com.threegap.bitnagil.domain.routine.model.DailyRoutines import kotlinx.parcelize.Parcelize @Parcelize -data class DayRoutinesUiModel( +data class DailyRoutinesUiModel( val routines: List = emptyList(), val isAllCompleted: Boolean = false, ) : Parcelable -fun DayRoutines.toUiModel(): DayRoutinesUiModel = - DayRoutinesUiModel( +fun DailyRoutines.toUiModel(): DailyRoutinesUiModel = + DailyRoutinesUiModel( routines = routines.map { it.toUiModel() }, isAllCompleted = isAllCompleted, ) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeIntent.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeIntent.kt index 6fab98ec..d089c2a4 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeIntent.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeIntent.kt @@ -9,8 +9,8 @@ sealed class HomeIntent : MviIntent { data class LoadTodayEmotion(val emotion: TodayEmotionUiModel?) : HomeIntent() data class LoadWeeklyRoutines(val routines: RoutineScheduleUiModel) : HomeIntent() data class OnDateSelect(val date: LocalDate) : HomeIntent() - data class OnRoutineCompletionToggle(val routineId: String) : HomeIntent() - data class OnSubRoutineCompletionToggle(val routineId: String, val subRoutineIndex: Int) : HomeIntent() + data class ToggleRoutine(val routineId: String) : HomeIntent() + data class ToggleSubRoutine(val routineId: String, val subRoutineIndex: Int) : HomeIntent() data object RoutineToggleCompletionFailure : HomeIntent() data object OnHelpClick : HomeIntent() data object OnRegisterEmotionClick : HomeIntent() diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeState.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeState.kt index 8a34d2db..784a5adb 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeState.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeState.kt @@ -12,8 +12,8 @@ data class HomeState( val todayEmotion: TodayEmotionUiModel? = null, val selectedDate: LocalDate = LocalDate.now(), val currentWeeks: List = LocalDate.now().getCurrentWeekDays(), - val routines: RoutineScheduleUiModel = RoutineScheduleUiModel(), + val routineSchedule: RoutineScheduleUiModel = RoutineScheduleUiModel(), ) : MviState { val selectedDateRoutines: List - get() = routines.dailyRoutines[selectedDate.toString()]?.routines ?: emptyList() + get() = routineSchedule.dailyRoutines[selectedDate.toString()]?.routines ?: emptyList() } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/RoutineScheduleUiModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/RoutineScheduleUiModel.kt index 9b8aee3b..3304cfda 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/RoutineScheduleUiModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/RoutineScheduleUiModel.kt @@ -6,7 +6,7 @@ import kotlinx.parcelize.Parcelize @Parcelize data class RoutineScheduleUiModel( - val dailyRoutines: Map = emptyMap(), + val dailyRoutines: Map = emptyMap(), ) : Parcelable fun RoutineSchedule.toUiModel(): RoutineScheduleUiModel = diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/RoutineUiModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/RoutineUiModel.kt index 935999e3..6b8e73c4 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/RoutineUiModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/RoutineUiModel.kt @@ -15,7 +15,7 @@ data class RoutineUiModel( val routineDate: String, val isCompleted: Boolean, val subRoutineNames: List, - val subRoutineIsCompleted: List, + val subRoutineCompletionStates: List, val recommendedRoutineType: RecommendedRoutineType?, ) : Parcelable @@ -28,6 +28,6 @@ fun Routine.toUiModel(): RoutineUiModel = routineDate = this.routineDate, isCompleted = this.isCompleted, subRoutineNames = this.subRoutineNames, - subRoutineIsCompleted = this.subRoutineIsCompleted, + subRoutineCompletionStates = this.subRoutineCompletionStates, recommendedRoutineType = this.recommendedRoutineType, ) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/DayRoutinesUiModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/DayRoutinesUiModel.kt index 05d6db48..7516e137 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/DayRoutinesUiModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/model/DayRoutinesUiModel.kt @@ -1,7 +1,7 @@ package com.threegap.bitnagil.presentation.routinelist.model import android.os.Parcelable -import com.threegap.bitnagil.domain.routine.model.DayRoutines +import com.threegap.bitnagil.domain.routine.model.DailyRoutines import kotlinx.parcelize.Parcelize @Parcelize @@ -9,7 +9,7 @@ data class DayRoutinesUiModel( val routineList: List = emptyList(), ) : Parcelable -fun DayRoutines.toUiModel(): DayRoutinesUiModel = +fun DailyRoutines.toUiModel(): DayRoutinesUiModel = DayRoutinesUiModel( routineList = routines.map { it.toUiModel() }, ) From b0d72a7e06415e51c0e91e667d9f4828068b26f9 Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Fri, 7 Nov 2025 17:59:58 +0900 Subject: [PATCH 05/12] =?UTF-8?q?Refactor:=20HomeViewModel=20=EC=97=90?= =?UTF-8?q?=EC=84=9C=20MviViewModel=20=EA=B5=AC=ED=98=84=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - homeState, homeSideEffect 수정 - homeIntent 제거 --- .../bitnagil/presentation/home/HomeScreen.kt | 80 ++++------ .../presentation/home/HomeViewModel.kt | 140 +++++++----------- .../presentation/home/model/HomeIntent.kt | 21 --- .../presentation/home/model/HomeSideEffect.kt | 4 +- .../presentation/home/model/HomeState.kt | 28 ++-- 5 files changed, 94 insertions(+), 179 deletions(-) delete mode 100644 presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeIntent.kt diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeScreen.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeScreen.kt index 52aecba5..8256ccc1 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeScreen.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeScreen.kt @@ -22,18 +22,17 @@ import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel -import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.threegap.bitnagil.designsystem.BitnagilTheme import com.threegap.bitnagil.designsystem.modifier.clickableWithoutRipple -import com.threegap.bitnagil.presentation.common.flow.collectAsEffect import com.threegap.bitnagil.presentation.home.component.template.CollapsibleHomeHeader import com.threegap.bitnagil.presentation.home.component.template.EmptyRoutineView import com.threegap.bitnagil.presentation.home.component.template.RoutineSection import com.threegap.bitnagil.presentation.home.component.template.WeeklyDatePicker -import com.threegap.bitnagil.presentation.home.model.HomeIntent import com.threegap.bitnagil.presentation.home.model.HomeSideEffect import com.threegap.bitnagil.presentation.home.model.HomeState import com.threegap.bitnagil.presentation.home.util.rememberCollapsibleHeaderState +import org.orbitmvi.orbit.compose.collectAsState +import org.orbitmvi.orbit.compose.collectSideEffect import java.time.LocalDate @Composable @@ -44,57 +43,28 @@ fun HomeScreenContainer( navigateToRoutineList: (String) -> Unit, viewModel: HomeViewModel = hiltViewModel(), ) { - val uiState by viewModel.stateFlow.collectAsStateWithLifecycle() + val uiState by viewModel.collectAsState() - viewModel.sideEffectFlow.collectAsEffect { sideEffect -> + viewModel.collectSideEffect { sideEffect -> when (sideEffect) { - is HomeSideEffect.NavigateToGuide -> { - navigateToGuide() - } - - is HomeSideEffect.NavigateToRegisterRoutine -> { - navigateToRegisterRoutine() - } - - is HomeSideEffect.NavigateToEmotion -> { - navigateToEmotion() - } - - is HomeSideEffect.NavigateToRoutineList -> { - navigateToRoutineList(sideEffect.selectedDate) - } + is HomeSideEffect.NavigateToGuide -> navigateToGuide() + is HomeSideEffect.NavigateToRegisterRoutine -> navigateToRegisterRoutine() + is HomeSideEffect.NavigateToEmotion -> navigateToEmotion() + is HomeSideEffect.NavigateToRoutineList -> navigateToRoutineList(sideEffect.selectedDate) } } HomeScreen( uiState = uiState, - onDateSelect = { date -> - viewModel.sendIntent(HomeIntent.OnDateSelect(date)) - }, - onPreviousWeekClick = { - viewModel.sendIntent(HomeIntent.OnPreviousWeekClick) - }, - onNextWeekClick = { - viewModel.sendIntent(HomeIntent.OnNextWeekClick) - }, - onRoutineCompletionToggle = { routineId -> - viewModel.toggleRoutineCompletion(routineId) - }, - onSubRoutineCompletionToggle = { routineId, subRoutineIndex -> - viewModel.toggleSubRoutineCompletion(routineId, subRoutineIndex) - }, - onHelpClick = { - viewModel.sendIntent(HomeIntent.OnHelpClick) - }, - onRegisterRoutineClick = { - viewModel.sendIntent(HomeIntent.OnRegisterRoutineClick) - }, - onRegisterEmotionClick = { - viewModel.sendIntent(HomeIntent.OnRegisterEmotionClick) - }, - onShowMoreRoutinesClick = { - viewModel.sendIntent((HomeIntent.OnShowMoreRoutinesClick)) - }, + onDateSelect = viewModel::selectDate, + onPreviousWeekClick = viewModel::getPreviousWeek, + onNextWeekClick = viewModel::getNextWeek, + onRoutineToggle = viewModel::toggleRoutine, + onSubRoutineToggle = viewModel::toggleSubRoutine, + onHelpClick = viewModel::navigateToGuide, + onRegisterRoutineClick = viewModel::navigateToRegisterRoutine, + onRegisterEmotionClick = viewModel::navigateToEmotion, + onShowMoreRoutinesClick = viewModel::navigateToRoutineList ) } @@ -104,8 +74,8 @@ private fun HomeScreen( onDateSelect: (LocalDate) -> Unit, onPreviousWeekClick: () -> Unit, onNextWeekClick: () -> Unit, - onRoutineCompletionToggle: (String) -> Unit, - onSubRoutineCompletionToggle: (String, Int) -> Unit, + onRoutineToggle: (String) -> Unit, + onSubRoutineToggle: (String, Int) -> Unit, onHelpClick: () -> Unit, onRegisterRoutineClick: () -> Unit, onRegisterEmotionClick: () -> Unit, @@ -125,7 +95,7 @@ private fun HomeScreen( WeeklyDatePicker( selectedDate = uiState.selectedDate, weeklyDates = uiState.currentWeeks, - routines = uiState.routines, + routines = uiState.routineSchedule, onDateSelect = onDateSelect, onPreviousWeekClick = onPreviousWeekClick, onNextWeekClick = onNextWeekClick, @@ -187,9 +157,9 @@ private fun HomeScreen( ) { routine -> RoutineSection( routine = routine, - onRoutineToggle = { onRoutineCompletionToggle(routine.id) }, + onRoutineToggle = { onRoutineToggle(routine.id) }, onSubRoutineToggle = { subRoutineIndex -> - onSubRoutineCompletionToggle(routine.id, subRoutineIndex) + onSubRoutineToggle(routine.id, subRoutineIndex) }, ) } @@ -211,12 +181,12 @@ private fun HomeScreen( @Composable private fun HomeScreenPreview() { HomeScreen( - uiState = HomeState(), + uiState = HomeState.INIT, onDateSelect = {}, onPreviousWeekClick = {}, onNextWeekClick = {}, - onRoutineCompletionToggle = { _ -> }, - onSubRoutineCompletionToggle = { _, _ -> }, + onRoutineToggle = { _ -> }, + onSubRoutineToggle = { _, _ -> }, onHelpClick = {}, onRegisterRoutineClick = {}, onRegisterEmotionClick = {}, diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt index 7c2e8f5f..631c790c 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt @@ -2,6 +2,7 @@ package com.threegap.bitnagil.presentation.home import android.util.Log import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.threegap.bitnagil.domain.emotion.usecase.FetchTodayEmotionUseCase import com.threegap.bitnagil.domain.emotion.usecase.GetEmotionChangeEventFlowUseCase @@ -13,7 +14,6 @@ import com.threegap.bitnagil.domain.routine.usecase.RoutineCompletionUseCase import com.threegap.bitnagil.domain.user.usecase.FetchUserProfileUseCase import com.threegap.bitnagil.domain.writeroutine.usecase.GetWriteRoutineEventFlowUseCase import com.threegap.bitnagil.presentation.common.mviviewmodel.MviViewModel -import com.threegap.bitnagil.presentation.home.model.HomeIntent import com.threegap.bitnagil.presentation.home.model.HomeSideEffect import com.threegap.bitnagil.presentation.home.model.HomeState import com.threegap.bitnagil.presentation.home.model.RoutineScheduleUiModel @@ -28,13 +28,15 @@ import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch +import org.orbitmvi.orbit.Container +import org.orbitmvi.orbit.ContainerHost import org.orbitmvi.orbit.syntax.Syntax +import org.orbitmvi.orbit.viewmodel.container import java.time.LocalDate import javax.inject.Inject @HiltViewModel class HomeViewModel @Inject constructor( - savedStateHandle: SavedStateHandle, private val fetchWeeklyRoutinesUseCase: FetchWeeklyRoutinesUseCase, private val fetchUserProfileUseCase: FetchUserProfileUseCase, private val fetchTodayEmotionUseCase: FetchTodayEmotionUseCase, @@ -42,10 +44,11 @@ class HomeViewModel @Inject constructor( private val getWriteRoutineEventFlowUseCase: GetWriteRoutineEventFlowUseCase, private val getEmotionChangeEventFlowUseCase: GetEmotionChangeEventFlowUseCase, private val getOnBoardingRecommendRoutineEventFlowUseCase: GetOnBoardingRecommendRoutineEventFlowUseCase, -) : MviViewModel( - initState = HomeState(), - savedStateHandle = savedStateHandle, -) { + private val toggleRoutineUseCase: ToggleRoutineUseCase, +) : ContainerHost, ViewModel() { + + override val container: Container = container(initialState = HomeState.INIT) + private val pendingChangesByDate = mutableMapOf>() private val backupStatesByDate = mutableMapOf() private val routineSyncTrigger = MutableSharedFlow() @@ -56,92 +59,54 @@ class HomeViewModel @Inject constructor( observeRecommendRoutineEvent() observeWeekChanges() observeRoutineUpdates() - fetchWeeklyRoutines(stateFlow.value.currentWeeks) + fetchWeeklyRoutines(container.stateFlow.value.currentWeeks) fetchUserProfile() fetchTodayEmotion(LocalDate.now()) } - override suspend fun Syntax.reduceState( - intent: HomeIntent, - state: HomeState, - ): HomeState? { - val newState = when (intent) { - is HomeIntent.UpdateLoading -> { - state.copy(isLoading = intent.isLoading) - } + fun selectDate(data: LocalDate) { + intent { + reduce { state.copy(selectedDate = data) } + } + } - is HomeIntent.LoadUserProfile -> { - state.copy(userNickname = intent.nickname) - } + fun getNextWeek() { + intent { + val newWeek = state.selectedDate.plusWeeks(1).getCurrentWeekDays() + reduce { state.copy(currentWeeks = newWeek, selectedDate = newWeek.first()) } + } + } - is HomeIntent.LoadWeeklyRoutines -> { - state.copy(routines = intent.routines) - } + fun getPreviousWeek() { + intent { + val newWeek = state.selectedDate.minusWeeks(1).getCurrentWeekDays() + reduce { state.copy(currentWeeks = newWeek, selectedDate = newWeek.first()) } + } + } - is HomeIntent.OnDateSelect -> { - state.copy(selectedDate = intent.date) - } - is HomeIntent.OnNextWeekClick -> { - val newWeek = state.selectedDate.plusWeeks(1).getCurrentWeekDays() - state.copy( - currentWeeks = newWeek, - selectedDate = newWeek.first(), - ) - } - is HomeIntent.OnPreviousWeekClick -> { - val newWeek = state.selectedDate.minusWeeks(1).getCurrentWeekDays() - state.copy( - currentWeeks = newWeek, - selectedDate = newWeek.first(), - ) - } - is HomeIntent.OnRoutineCompletionToggle -> { - updateMainRoutine(state, intent.routineId) - } - is HomeIntent.OnSubRoutineCompletionToggle -> { - updateSubRoutine(state, intent.routineId, intent.subRoutineIndex) } - is HomeIntent.LoadTodayEmotion -> { - state.copy(todayEmotion = intent.emotion) - } - is HomeIntent.OnHelpClick -> { - sendSideEffect(HomeSideEffect.NavigateToGuide) - null } - is HomeIntent.OnRegisterEmotionClick -> { - sendSideEffect(HomeSideEffect.NavigateToEmotion) - null } - is HomeIntent.OnRegisterRoutineClick -> { - sendSideEffect(HomeSideEffect.NavigateToRegisterRoutine) - null } - is HomeIntent.OnShowMoreRoutinesClick -> { - val selectedDate = stateFlow.value.selectedDate.toString() - sendSideEffect(HomeSideEffect.NavigateToRoutineList(selectedDate)) - null } - is HomeIntent.RoutineToggleCompletionFailure -> { - null } } - return newState } private fun observeWriteRoutineEvent() { viewModelScope.launch { getWriteRoutineEventFlowUseCase().collect { - fetchWeeklyRoutines(stateFlow.value.currentWeeks) + fetchWeeklyRoutines(container.stateFlow.value.currentWeeks) } } } @@ -158,7 +123,7 @@ class HomeViewModel @Inject constructor( private fun observeRecommendRoutineEvent() { viewModelScope.launch { getOnBoardingRecommendRoutineEventFlowUseCase().collect { - fetchWeeklyRoutines(stateFlow.value.currentWeeks) + fetchWeeklyRoutines(container.stateFlow.value.currentWeeks) } } } @@ -189,50 +154,45 @@ class HomeViewModel @Inject constructor( } private fun fetchUserProfile() { - sendIntent(HomeIntent.UpdateLoading(true)) - viewModelScope.launch { + intent { + reduce { state.copy(isLoading = true) } fetchUserProfileUseCase().fold( onSuccess = { - sendIntent(HomeIntent.LoadUserProfile(it.nickname)) - sendIntent(HomeIntent.UpdateLoading(false)) + reduce { state.copy(userNickname = it.nickname, isLoading = false) } }, - onFailure = { error -> - Log.e("HomeViewModel", "유저 정보 가져오기 실패: ${error.message}") - sendIntent(HomeIntent.UpdateLoading(false)) + onFailure = { + Log.e("HomeViewModel", "유저 정보 가져오기 실패: ${it.message}") + reduce { state.copy(isLoading = false) } }, ) } } private fun fetchWeeklyRoutines(currentWeeks: List) { - sendIntent(HomeIntent.UpdateLoading(true)) - val startDate = currentWeeks.first().toString() - val endDate = currentWeeks.last().toString() - viewModelScope.launch { - fetchWeeklyRoutinesUseCase(startDate, endDate).fold( - onSuccess = { routines -> - sendIntent(HomeIntent.LoadWeeklyRoutines(routines.toUiModel())) - sendIntent(HomeIntent.UpdateLoading(false)) + intent { + reduce { state.copy(isLoading = true) } + fetchWeeklyRoutinesUseCase(currentWeeks).fold( + onSuccess = { + reduce { state.copy(isLoading = false, routineSchedule = it.toUiModel()) } }, - onFailure = { error -> - Log.e("HomeViewModel", "루틴 가져오기 실패: ${error.message}") - sendIntent(HomeIntent.UpdateLoading(false)) + onFailure = { + Log.e("HomeViewModel", "루틴 가져오기 실패: ${it.message}") + reduce { state.copy(isLoading = false) } }, ) } } private fun fetchTodayEmotion(currentDate: LocalDate) { - sendIntent(HomeIntent.UpdateLoading(true)) - viewModelScope.launch { + intent { + reduce { state.copy(isLoading = true) } fetchTodayEmotionUseCase(currentDate.toString()).fold( - onSuccess = { todayEmotion -> - sendIntent(HomeIntent.LoadTodayEmotion(todayEmotion?.toUiModel())) - sendIntent(HomeIntent.UpdateLoading(false)) + onSuccess = { + reduce { state.copy(isLoading = false, todayEmotion = it?.toUiModel()) } }, - onFailure = { error -> - Log.e("HomeViewModel", "나의 감정 실패: ${error.message}") - sendIntent(HomeIntent.UpdateLoading(false)) + onFailure = { + Log.e("HomeViewModel", "나의 감정 실패: ${it.message}") + reduce { state.copy(isLoading = false) } }, ) } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeIntent.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeIntent.kt deleted file mode 100644 index d089c2a4..00000000 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeIntent.kt +++ /dev/null @@ -1,21 +0,0 @@ -package com.threegap.bitnagil.presentation.home.model - -import com.threegap.bitnagil.presentation.common.mviviewmodel.MviIntent -import java.time.LocalDate - -sealed class HomeIntent : MviIntent { - data class UpdateLoading(val isLoading: Boolean) : HomeIntent() - data class LoadUserProfile(val nickname: String) : HomeIntent() - data class LoadTodayEmotion(val emotion: TodayEmotionUiModel?) : HomeIntent() - data class LoadWeeklyRoutines(val routines: RoutineScheduleUiModel) : HomeIntent() - data class OnDateSelect(val date: LocalDate) : HomeIntent() - data class ToggleRoutine(val routineId: String) : HomeIntent() - data class ToggleSubRoutine(val routineId: String, val subRoutineIndex: Int) : HomeIntent() - data object RoutineToggleCompletionFailure : HomeIntent() - data object OnHelpClick : HomeIntent() - data object OnRegisterEmotionClick : HomeIntent() - data object OnRegisterRoutineClick : HomeIntent() - data object OnPreviousWeekClick : HomeIntent() - data object OnNextWeekClick : HomeIntent() - data object OnShowMoreRoutinesClick : HomeIntent() -} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeSideEffect.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeSideEffect.kt index 6e727ef5..9f55ba02 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeSideEffect.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeSideEffect.kt @@ -1,8 +1,6 @@ package com.threegap.bitnagil.presentation.home.model -import com.threegap.bitnagil.presentation.common.mviviewmodel.MviSideEffect - -sealed interface HomeSideEffect : MviSideEffect { +sealed interface HomeSideEffect { data object NavigateToGuide : HomeSideEffect data object NavigateToRegisterRoutine : HomeSideEffect data object NavigateToEmotion : HomeSideEffect diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeState.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeState.kt index 784a5adb..0366654c 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeState.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeState.kt @@ -1,19 +1,27 @@ package com.threegap.bitnagil.presentation.home.model -import com.threegap.bitnagil.presentation.common.mviviewmodel.MviState import com.threegap.bitnagil.presentation.home.util.getCurrentWeekDays -import kotlinx.parcelize.Parcelize import java.time.LocalDate -@Parcelize data class HomeState( - val isLoading: Boolean = false, - val userNickname: String = "", - val todayEmotion: TodayEmotionUiModel? = null, - val selectedDate: LocalDate = LocalDate.now(), - val currentWeeks: List = LocalDate.now().getCurrentWeekDays(), - val routineSchedule: RoutineScheduleUiModel = RoutineScheduleUiModel(), -) : MviState { + val isLoading: Boolean, + val userNickname: String, + val todayEmotion: TodayEmotionUiModel?, + val selectedDate: LocalDate, + val currentWeeks: List, + val routineSchedule: RoutineScheduleUiModel, +) { val selectedDateRoutines: List get() = routineSchedule.dailyRoutines[selectedDate.toString()]?.routines ?: emptyList() + + companion object { + val INIT = HomeState( + isLoading = false, + userNickname = "", + todayEmotion = null, + selectedDate = LocalDate.now(), + currentWeeks = LocalDate.now().getCurrentWeekDays(), + routineSchedule = RoutineScheduleUiModel(), + ) + } } From d877e07be2bba27795c9dcf12e850b919036c127 Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Fri, 7 Nov 2025 18:02:12 +0900 Subject: [PATCH 06/12] =?UTF-8?q?Feat:=20=ED=99=88=20=ED=99=94=EB=A9=B4=20?= =?UTF-8?q?=EB=84=A4=EB=B9=84=EA=B2=8C=EC=9D=B4=EC=85=98=20=EB=A9=94?= =?UTF-8?q?=EC=86=8C=EB=93=9C=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/home/HomeViewModel.kt | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt index 631c790c..95715949 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt @@ -103,6 +103,31 @@ class HomeViewModel @Inject constructor( } } + fun navigateToGuide() { + intent { + postSideEffect(HomeSideEffect.NavigateToGuide) + } + } + + fun navigateToEmotion() { + intent { + postSideEffect(HomeSideEffect.NavigateToEmotion) + } + } + + fun navigateToRegisterRoutine() { + intent { + postSideEffect(HomeSideEffect.NavigateToRegisterRoutine) + } + } + + fun navigateToRoutineList() { + intent { + val selectedDate = state.selectedDate.toString() + postSideEffect(HomeSideEffect.NavigateToRoutineList(selectedDate)) + } + } + private fun observeWriteRoutineEvent() { viewModelScope.launch { getWriteRoutineEventFlowUseCase().collect { From cffd981736cfc2c61fbd4bde13e51b8afb98cf5a Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Fri, 7 Nov 2025 18:04:17 +0900 Subject: [PATCH 07/12] =?UTF-8?q?Refactor:=20FetchWeeklyRoutinesUseCase=20?= =?UTF-8?q?=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/routine/usecase/FetchWeeklyRoutinesUseCase.kt | 8 ++++++-- .../presentation/routinelist/RoutineListViewModel.kt | 4 +--- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/FetchWeeklyRoutinesUseCase.kt b/domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/FetchWeeklyRoutinesUseCase.kt index 6ff39b5e..999e3658 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/FetchWeeklyRoutinesUseCase.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/FetchWeeklyRoutinesUseCase.kt @@ -2,11 +2,15 @@ package com.threegap.bitnagil.domain.routine.usecase import com.threegap.bitnagil.domain.routine.model.RoutineSchedule import com.threegap.bitnagil.domain.routine.repository.RoutineRepository +import java.time.LocalDate import javax.inject.Inject class FetchWeeklyRoutinesUseCase @Inject constructor( private val routineRepository: RoutineRepository, ) { - suspend operator fun invoke(startDate: String, endDate: String): Result = - routineRepository.fetchWeeklyRoutines(startDate, endDate) + suspend operator fun invoke(currentWeeks: List): Result { + val startDate = currentWeeks.first().toString() + val endDate = currentWeeks.last().toString() + return routineRepository.fetchWeeklyRoutines(startDate, endDate) + } } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/RoutineListViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/RoutineListViewModel.kt index e5113f80..7542a6c0 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/RoutineListViewModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/RoutineListViewModel.kt @@ -128,10 +128,8 @@ class RoutineListViewModel @Inject constructor( private fun fetchRoutines() { sendIntent(RoutineListIntent.UpdateLoading(true)) val currentWeek = stateFlow.value.selectedDate.getCurrentWeekDays() - val startDate = currentWeek.first().toString() - val endDate = currentWeek.last().toString() viewModelScope.launch { - fetchWeeklyRoutinesUseCase(startDate, endDate).fold( + fetchWeeklyRoutinesUseCase(currentWeek).fold( onSuccess = { routines -> sendIntent(RoutineListIntent.LoadRoutines(routines.toUiModel())) sendIntent(RoutineListIntent.UpdateLoading(false)) From 0d93d5f0a219d161acd750c18b359161d1328df9 Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Sat, 8 Nov 2025 18:50:36 +0900 Subject: [PATCH 08/12] =?UTF-8?q?Feat:=20=EB=A3=A8=ED=8B=B4=20=ED=86=A0?= =?UTF-8?q?=EA=B8=80=20usecase=20=EB=A1=9C=EC=A7=81=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - RoutineToggleState 정의 - 기존 viewmodel에서 비즈니스 로직 domain으로 변경 --- .../routine/model/RoutineToggleState.kt | 6 ++ .../routine/usecase/ToggleRoutineUseCase.kt | 57 +++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 domain/src/main/java/com/threegap/bitnagil/domain/routine/model/RoutineToggleState.kt create mode 100644 domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/ToggleRoutineUseCase.kt diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/RoutineToggleState.kt b/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/RoutineToggleState.kt new file mode 100644 index 00000000..0b3a5e79 --- /dev/null +++ b/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/RoutineToggleState.kt @@ -0,0 +1,6 @@ +package com.threegap.bitnagil.domain.routine.model + +data class RoutineToggleState( + val isCompleted: Boolean, + val subRoutinesIsCompleted: List, +) diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/ToggleRoutineUseCase.kt b/domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/ToggleRoutineUseCase.kt new file mode 100644 index 00000000..6f30b47c --- /dev/null +++ b/domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/ToggleRoutineUseCase.kt @@ -0,0 +1,57 @@ +package com.threegap.bitnagil.domain.routine.usecase + +import com.threegap.bitnagil.domain.routine.model.RoutineToggleState +import javax.inject.Inject + +class ToggleRoutineUseCase @Inject constructor() { + + /** + * 메인 루틴을 토글합니다. + * 메인 루틴이 토글되면 모든 서브 루틴도 같은 상태로 변경됩니다. + * + * @param isCompleted 현재 메인 루틴 완료 상태 + * @param subRoutineStates 현재 서브 루틴 완료 상태 리스트 + * @return 토글된 루틴 상태 + */ + fun toggleMainRoutine( + isCompleted: Boolean, + subRoutineStates: List, + ): RoutineToggleState { + val newIsCompleted = !isCompleted + val newSubRoutineStates = subRoutineStates.map { newIsCompleted } + + return RoutineToggleState( + isCompleted = newIsCompleted, + subRoutinesIsCompleted = newSubRoutineStates, + ) + } + + /** + * 특정 서브 루틴을 토글합니다. + * 모든 서브 루틴이 완료되면 메인 루틴도 자동으로 완료됩니다. + * + * @param index 토글할 서브 루틴의 인덱스 + * @param subRoutineStates 현재 서브 루틴 완료 상태 리스트 + * @return 토글된 루틴 상태, 잘못된 인덱스인 경우 null + */ + fun toggleSubRoutine( + index: Int, + subRoutineStates: List, + ): RoutineToggleState? { + if (index !in subRoutineStates.indices) { + return null + } + + val newState = !subRoutineStates[index] + val newSubRoutineStates = subRoutineStates.toMutableList().apply { + this[index] = newState + } + + val allCompleted = newSubRoutineStates.all { it } + + return RoutineToggleState( + isCompleted = allCompleted, + subRoutinesIsCompleted = newSubRoutineStates, + ) + } +} From 26efa55a3081e83890b8bf7abd5e5a30cce1408a Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Sat, 8 Nov 2025 19:04:22 +0900 Subject: [PATCH 09/12] =?UTF-8?q?Refactor:=20=ED=99=88=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F?= =?UTF-8?q?=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 루틴 완료 로직 ToggleStrategy을 통해 main/sub 분리 - 초기화 메소드 initialize로 통합 --- .../usecase/FetchTodayEmotionUseCase.kt | 7 +- .../bitnagil/presentation/home/HomeScreen.kt | 2 +- .../presentation/home/HomeViewModel.kt | 353 +++++++----------- .../presentation/home/model/HomeState.kt | 7 +- .../presentation/home/model/ToggleStrategy.kt | 6 + 5 files changed, 151 insertions(+), 224 deletions(-) create mode 100644 presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/ToggleStrategy.kt diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/emotion/usecase/FetchTodayEmotionUseCase.kt b/domain/src/main/java/com/threegap/bitnagil/domain/emotion/usecase/FetchTodayEmotionUseCase.kt index 35750da6..b98453e4 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/emotion/usecase/FetchTodayEmotionUseCase.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/emotion/usecase/FetchTodayEmotionUseCase.kt @@ -2,11 +2,14 @@ package com.threegap.bitnagil.domain.emotion.usecase import com.threegap.bitnagil.domain.emotion.model.TodayEmotion import com.threegap.bitnagil.domain.emotion.repository.EmotionRepository +import java.time.LocalDate import javax.inject.Inject class FetchTodayEmotionUseCase @Inject constructor( private val emotionRepository: EmotionRepository, ) { - suspend operator fun invoke(currentDate: String): Result = - emotionRepository.fetchTodayEmotion(currentDate) + suspend operator fun invoke(): Result { + val currentDate = LocalDate.now().toString() + return emotionRepository.fetchTodayEmotion(currentDate) + } } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeScreen.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeScreen.kt index 8256ccc1..8a8639b9 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeScreen.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeScreen.kt @@ -64,7 +64,7 @@ fun HomeScreenContainer( onHelpClick = viewModel::navigateToGuide, onRegisterRoutineClick = viewModel::navigateToRegisterRoutine, onRegisterEmotionClick = viewModel::navigateToEmotion, - onShowMoreRoutinesClick = viewModel::navigateToRoutineList + onShowMoreRoutinesClick = viewModel::navigateToRoutineList, ) } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt index 95715949..f53739e7 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt @@ -1,9 +1,7 @@ package com.threegap.bitnagil.presentation.home import android.util.Log -import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope import com.threegap.bitnagil.domain.emotion.usecase.FetchTodayEmotionUseCase import com.threegap.bitnagil.domain.emotion.usecase.GetEmotionChangeEventFlowUseCase import com.threegap.bitnagil.domain.onboarding.usecase.GetOnBoardingRecommendRoutineEventFlowUseCase @@ -11,17 +9,17 @@ import com.threegap.bitnagil.domain.routine.model.RoutineCompletionInfo import com.threegap.bitnagil.domain.routine.model.RoutineCompletionInfos import com.threegap.bitnagil.domain.routine.usecase.FetchWeeklyRoutinesUseCase import com.threegap.bitnagil.domain.routine.usecase.RoutineCompletionUseCase +import com.threegap.bitnagil.domain.routine.usecase.ToggleRoutineUseCase import com.threegap.bitnagil.domain.user.usecase.FetchUserProfileUseCase import com.threegap.bitnagil.domain.writeroutine.usecase.GetWriteRoutineEventFlowUseCase -import com.threegap.bitnagil.presentation.common.mviviewmodel.MviViewModel import com.threegap.bitnagil.presentation.home.model.HomeSideEffect import com.threegap.bitnagil.presentation.home.model.HomeState -import com.threegap.bitnagil.presentation.home.model.RoutineScheduleUiModel -import com.threegap.bitnagil.presentation.home.model.RoutineUiModel +import com.threegap.bitnagil.presentation.home.model.ToggleStrategy import com.threegap.bitnagil.presentation.home.model.toUiModel import com.threegap.bitnagil.presentation.home.util.getCurrentWeekDays import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.distinctUntilChanged @@ -30,7 +28,6 @@ import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch import org.orbitmvi.orbit.Container import org.orbitmvi.orbit.ContainerHost -import org.orbitmvi.orbit.syntax.Syntax import org.orbitmvi.orbit.viewmodel.container import java.time.LocalDate import javax.inject.Inject @@ -49,29 +46,31 @@ class HomeViewModel @Inject constructor( override val container: Container = container(initialState = HomeState.INIT) - private val pendingChangesByDate = mutableMapOf>() - private val backupStatesByDate = mutableMapOf() - private val routineSyncTrigger = MutableSharedFlow() + private val pendingChangesByDate = mutableMapOf>() + private val routineSyncTrigger = MutableSharedFlow(extraBufferCapacity = 64) init { - observeWriteRoutineEvent() - observeEmotionChangeEvent() - observeRecommendRoutineEvent() - observeWeekChanges() - observeRoutineUpdates() - fetchWeeklyRoutines(container.stateFlow.value.currentWeeks) - fetchUserProfile() - fetchTodayEmotion(LocalDate.now()) + initialize() } fun selectDate(data: LocalDate) { intent { + val previousDateKey = state.selectedDate.toString() + if (pendingChangesByDate.containsKey(previousDateKey)) { + syncRoutineChangesForDate(previousDateKey) + } + reduce { state.copy(selectedDate = data) } } } fun getNextWeek() { intent { + val currentDateKey = state.selectedDate.toString() + if (pendingChangesByDate.containsKey(currentDateKey)) { + syncRoutineChangesForDate(currentDateKey) + } + val newWeek = state.selectedDate.plusWeeks(1).getCurrentWeekDays() reduce { state.copy(currentWeeks = newWeek, selectedDate = newWeek.first()) } } @@ -79,27 +78,82 @@ class HomeViewModel @Inject constructor( fun getPreviousWeek() { intent { + val currentDateKey = state.selectedDate.toString() + if (pendingChangesByDate.containsKey(currentDateKey)) { + syncRoutineChangesForDate(currentDateKey) + } + val newWeek = state.selectedDate.minusWeeks(1).getCurrentWeekDays() reduce { state.copy(currentWeeks = newWeek, selectedDate = newWeek.first()) } } } + fun toggleRoutine(routineId: String) { + intent { + updateRoutineState(routineId, ToggleStrategy.Main) + } + } + fun toggleSubRoutine(routineId: String, subRoutineIndex: Int) { + intent { + updateRoutineState(routineId, ToggleStrategy.Sub(subRoutineIndex)) + } + } + private suspend fun updateRoutineState(routineId: String, strategy: ToggleStrategy) { + subIntent { + val dateKey = state.selectedDate.toString() + val dailySchedule = state.routineSchedule.dailyRoutines[dateKey] ?: return@subIntent + val routine = dailySchedule.routines.find { it.id == routineId } ?: return@subIntent + + val toggledState = when (strategy) { + is ToggleStrategy.Main -> { + toggleRoutineUseCase.toggleMainRoutine( + isCompleted = routine.isCompleted, + subRoutineStates = routine.subRoutineCompletionStates, + ) + } + is ToggleStrategy.Sub -> { + toggleRoutineUseCase.toggleSubRoutine( + index = strategy.index, + subRoutineStates = routine.subRoutineCompletionStates, + ) + } + } ?: return@subIntent + val updatedRoutines = dailySchedule.routines.map { routine -> + if (routine.id == routineId) { + routine.copy( + isCompleted = toggledState.isCompleted, + subRoutineCompletionStates = toggledState.subRoutinesIsCompleted, + ) + } else { + routine + } } + val updatedDailySchedule = dailySchedule.copy( + routines = updatedRoutines, + isAllCompleted = updatedRoutines.all { it.isCompleted }, + ) - } + val newSchedule = state.routineSchedule.copy( + dailyRoutines = state.routineSchedule.dailyRoutines + (dateKey to updatedDailySchedule), + ) - } + reduce { state.copy(routineSchedule = newSchedule) } - } + val change = RoutineCompletionInfo( + routineId = routineId, + routineCompleteYn = toggledState.isCompleted, + subRoutineCompleteYn = toggledState.subRoutinesIsCompleted, + ) - } + val dateChanges = pendingChangesByDate.getOrPut(dateKey) { mutableMapOf() } + dateChanges[routineId] = change - } + routineSyncTrigger.emit(dateKey) } } @@ -128,34 +182,48 @@ class HomeViewModel @Inject constructor( } } - private fun observeWriteRoutineEvent() { - viewModelScope.launch { + private fun initialize() { + intent { + coroutineScope { + launch { fetchUserProfile() } + launch { fetchTodayEmotion() } + launch { fetchWeeklyRoutines(state.currentWeeks) } + launch { observeWriteRoutineEvent() } + launch { observeEmotionChangeEvent() } + launch { observeRecommendRoutineEvent() } + launch { observeWeekChanges() } + launch { observeRoutineUpdates() } + } + } + } + + private suspend fun observeWriteRoutineEvent() { + subIntent { getWriteRoutineEventFlowUseCase().collect { - fetchWeeklyRoutines(container.stateFlow.value.currentWeeks) + fetchWeeklyRoutines(state.currentWeeks) } } } - private fun observeEmotionChangeEvent() { - viewModelScope.launch { + private suspend fun observeEmotionChangeEvent() { + subIntent { getEmotionChangeEventFlowUseCase().collect { - val currentDate = LocalDate.now() - fetchTodayEmotion(currentDate) + fetchTodayEmotion() } } } - private fun observeRecommendRoutineEvent() { - viewModelScope.launch { + private suspend fun observeRecommendRoutineEvent() { + subIntent { getOnBoardingRecommendRoutineEventFlowUseCase().collect { - fetchWeeklyRoutines(container.stateFlow.value.currentWeeks) + fetchWeeklyRoutines(state.currentWeeks) } } } @OptIn(FlowPreview::class) - private fun observeWeekChanges() { - viewModelScope.launch { + private suspend fun observeWeekChanges() { + subIntent { container.stateFlow .map { it.currentWeeks } .distinctUntilChanged() @@ -168,228 +236,75 @@ class HomeViewModel @Inject constructor( } @OptIn(FlowPreview::class) - private fun observeRoutineUpdates() { - viewModelScope.launch { + private suspend fun observeRoutineUpdates() { + subIntent { routineSyncTrigger - .debounce(2000L) - .collect { date -> - syncRoutineChangesForDate(date) + .debounce(500L) + .collect { dateKey -> + syncRoutineChangesForDate(dateKey) } } } - private fun fetchUserProfile() { - intent { - reduce { state.copy(isLoading = true) } + private suspend fun fetchUserProfile() { + subIntent { + reduce { state.copy(loadingCount = state.loadingCount + 1) } fetchUserProfileUseCase().fold( onSuccess = { - reduce { state.copy(userNickname = it.nickname, isLoading = false) } + reduce { state.copy(userNickname = it.nickname, loadingCount = state.loadingCount - 1) } }, onFailure = { Log.e("HomeViewModel", "유저 정보 가져오기 실패: ${it.message}") - reduce { state.copy(isLoading = false) } + reduce { state.copy(loadingCount = state.loadingCount - 1) } }, ) } } - private fun fetchWeeklyRoutines(currentWeeks: List) { - intent { - reduce { state.copy(isLoading = true) } + private suspend fun fetchWeeklyRoutines(currentWeeks: List) { + subIntent { + reduce { state.copy(loadingCount = state.loadingCount + 1) } fetchWeeklyRoutinesUseCase(currentWeeks).fold( onSuccess = { - reduce { state.copy(isLoading = false, routineSchedule = it.toUiModel()) } + reduce { state.copy(routineSchedule = it.toUiModel(), loadingCount = state.loadingCount - 1) } }, onFailure = { Log.e("HomeViewModel", "루틴 가져오기 실패: ${it.message}") - reduce { state.copy(isLoading = false) } + reduce { state.copy(loadingCount = state.loadingCount - 1) } }, ) } } - private fun fetchTodayEmotion(currentDate: LocalDate) { - intent { - reduce { state.copy(isLoading = true) } - fetchTodayEmotionUseCase(currentDate.toString()).fold( + private suspend fun fetchTodayEmotion() { + subIntent { + reduce { state.copy(loadingCount = state.loadingCount + 1) } + fetchTodayEmotionUseCase().fold( onSuccess = { - reduce { state.copy(isLoading = false, todayEmotion = it?.toUiModel()) } + reduce { state.copy(todayEmotion = it?.toUiModel(), loadingCount = state.loadingCount - 1) } }, onFailure = { Log.e("HomeViewModel", "나의 감정 실패: ${it.message}") - reduce { state.copy(isLoading = false) } + reduce { state.copy(loadingCount = state.loadingCount - 1) } }, ) } } - fun toggleRoutineCompletion(routineId: String) { - val originalState = container.stateFlow.value - sendIntent(HomeIntent.OnRoutineCompletionToggle(routineId)) - - val predictedUpdatedState = updateMainRoutine(originalState, routineId) - processRoutineToggleChanges(originalState, predictedUpdatedState) - } - - fun toggleSubRoutineCompletion(routineId: String, subRoutineIndex: Int) { - val originalState = container.stateFlow.value - sendIntent(HomeIntent.OnSubRoutineCompletionToggle(routineId, subRoutineIndex)) - - val predictedUpdatedState = updateSubRoutine(originalState, routineId, subRoutineIndex) - processRoutineToggleChanges(originalState, predictedUpdatedState) - } - - private fun processRoutineToggleChanges( - originalState: HomeState, - updatedState: HomeState, - ) { - val selectedDate = originalState.selectedDate - val dateKey = selectedDate.toString() - - if (!backupStatesByDate.containsKey(dateKey)) { - backupStatesByDate[dateKey] = originalState.routines - } - - val originalRoutines = backupStatesByDate[dateKey] ?: originalState.routines - val changes = calculateStateChanges(originalRoutines, updatedState.routines, selectedDate) - - if (changes.isNotEmpty()) { - pendingChangesByDate[dateKey] = changes.toMutableList() - viewModelScope.launch { - routineSyncTrigger.emit(selectedDate) - } - } else { - pendingChangesByDate.remove(dateKey) - } - } - - private fun updateMainRoutine( - state: HomeState, - routineId: String, - ): HomeState { - return updateRoutinesForDate(state) { routinesForDate -> - val routineIndex = routinesForDate.indexOfFirst { it.id == routineId } - if (routineIndex == -1) return@updateRoutinesForDate false - - val routine = routinesForDate[routineIndex] - val newIsCompleted = !routine.isCompleted - - val updatedRoutine = routine.copy( - isCompleted = newIsCompleted, - subRoutineIsCompleted = routine.subRoutineIsCompleted.map { newIsCompleted }, - ) - - routinesForDate[routineIndex] = updatedRoutine - true - } - } - - private fun updateSubRoutine( - state: HomeState, - routineId: String, - subRoutineIndex: Int, - ): HomeState { - return updateRoutinesForDate(state) { routinesForDate -> - val routineIndex = routinesForDate.indexOfFirst { it.id == routineId } - if (routineIndex == -1) return@updateRoutinesForDate false - - val routine = routinesForDate[routineIndex] - val newIsCompleted = !routine.subRoutineIsCompleted[subRoutineIndex] - - if (subRoutineIndex !in routine.subRoutineIsCompleted.indices) { - return@updateRoutinesForDate false - } - - val updatedSubRoutineCompleteYn = routine.subRoutineIsCompleted.toMutableList().also { - it[subRoutineIndex] = newIsCompleted - } + private fun syncRoutineChangesForDate(dateKey: String) { + intent { + val dateChanges = pendingChangesByDate.remove(dateKey) + if (dateChanges.isNullOrEmpty()) return@intent - val routineCompleted = updatedSubRoutineCompleteYn.all { it } + val changesToSync = dateChanges.values.toList() + val syncRequest = RoutineCompletionInfos(routineCompletionInfos = changesToSync) - val updatedRoutine = routine.copy( - subRoutineIsCompleted = updatedSubRoutineCompleteYn, - isCompleted = routineCompleted, + routineCompletionUseCase(syncRequest).fold( + onSuccess = {}, + onFailure = { error -> + fetchWeeklyRoutines(state.currentWeeks) + }, ) - - routinesForDate[routineIndex] = updatedRoutine - true } } - - private fun updateRoutinesForDate( - state: HomeState, - updateLogic: (MutableList) -> Boolean, - ): HomeState { - val dateKey = state.selectedDate.toString() - val dayRoutines = state.routines.dailyRoutines[dateKey] ?: return state - val routinesForDate = dayRoutines.routines.toMutableList() - - if (!updateLogic(routinesForDate)) return state - - val allCompleted = routinesForDate.all { it.isCompleted } - - val updatedRoutinesByDate = state.routines.dailyRoutines.toMutableMap() - updatedRoutinesByDate[dateKey] = dayRoutines.copy( - routines = routinesForDate, - isAllCompleted = allCompleted, - ) - - return state.copy(routines = RoutineScheduleUiModel(updatedRoutinesByDate)) - } - - private fun calculateStateChanges( - originalRoutines: RoutineScheduleUiModel, - updatedRoutines: RoutineScheduleUiModel, - date: LocalDate, - ): List { - val dateKey = date.toString() - val originalRoutineList = originalRoutines.dailyRoutines[dateKey]?.routines ?: emptyList() - val updatedRoutineList = updatedRoutines.dailyRoutines[dateKey]?.routines ?: emptyList() - - return buildList { - updatedRoutineList.forEach { updatedRoutine -> - val originalRoutine = originalRoutineList.find { it.id == updatedRoutine.id } - - val hasMainRoutineChanged = originalRoutine?.isCompleted != updatedRoutine.isCompleted - val hasSubRoutinesChanged = originalRoutine?.subRoutineIsCompleted != updatedRoutine.subRoutineIsCompleted - - if (hasMainRoutineChanged || hasSubRoutinesChanged) { - add( - RoutineCompletionInfo( - routineId = updatedRoutine.id, - routineCompleteYn = updatedRoutine.isCompleted, - subRoutineCompleteYn = updatedRoutine.subRoutineIsCompleted, - ), - ) - } - } - } - } - - private suspend fun syncRoutineChangesForDate(date: LocalDate) { - val dateKey = date.toString() - val unsyncedChanges = pendingChangesByDate[dateKey] ?: return - - if (unsyncedChanges.isEmpty()) return - - val syncRequest = RoutineCompletionInfos( - routineCompletionInfos = unsyncedChanges.toList(), - ) - - routineCompletionUseCase(syncRequest).fold( - onSuccess = { - pendingChangesByDate.remove(dateKey) - backupStatesByDate.remove(dateKey) - }, - onFailure = { error -> - Log.e("HomeViewModel", "루틴 동기화 실패: ${error.message}") - val backupState = backupStatesByDate[dateKey] ?: return - sendIntent(HomeIntent.RoutineToggleCompletionFailure) - sendIntent(HomeIntent.LoadWeeklyRoutines(backupState)) - pendingChangesByDate.remove(dateKey) - backupStatesByDate.remove(dateKey) - sendIntent(HomeIntent.UpdateLoading(false)) - }, - ) - } } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeState.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeState.kt index 0366654c..a54c49a4 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeState.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/HomeState.kt @@ -4,19 +4,22 @@ import com.threegap.bitnagil.presentation.home.util.getCurrentWeekDays import java.time.LocalDate data class HomeState( - val isLoading: Boolean, + val loadingCount: Int, val userNickname: String, val todayEmotion: TodayEmotionUiModel?, val selectedDate: LocalDate, val currentWeeks: List, val routineSchedule: RoutineScheduleUiModel, ) { + val isLoading: Boolean + get() = loadingCount > 0 + val selectedDateRoutines: List get() = routineSchedule.dailyRoutines[selectedDate.toString()]?.routines ?: emptyList() companion object { val INIT = HomeState( - isLoading = false, + loadingCount = 0, userNickname = "", todayEmotion = null, selectedDate = LocalDate.now(), diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/ToggleStrategy.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/ToggleStrategy.kt new file mode 100644 index 00000000..29dbb0d8 --- /dev/null +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/ToggleStrategy.kt @@ -0,0 +1,6 @@ +package com.threegap.bitnagil.presentation.home.model + +sealed interface ToggleStrategy { + data object Main : ToggleStrategy + data class Sub(val index: Int) : ToggleStrategy +} From cba9a67a44a85f90305e00f8752d7fc014e1bea8 Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Sun, 9 Nov 2025 10:19:06 +0900 Subject: [PATCH 10/12] =?UTF-8?q?Refactor:=20isCompleted=EB=A5=BC=20comput?= =?UTF-8?q?ed=20property=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bitnagil/domain/routine/model/RoutineToggleState.kt | 6 ++++-- .../bitnagil/domain/routine/usecase/ToggleRoutineUseCase.kt | 4 ---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/RoutineToggleState.kt b/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/RoutineToggleState.kt index 0b3a5e79..c7305c29 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/RoutineToggleState.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/RoutineToggleState.kt @@ -1,6 +1,8 @@ package com.threegap.bitnagil.domain.routine.model data class RoutineToggleState( - val isCompleted: Boolean, val subRoutinesIsCompleted: List, -) +) { + val isCompleted: Boolean + get() = subRoutinesIsCompleted.all { it } +} diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/ToggleRoutineUseCase.kt b/domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/ToggleRoutineUseCase.kt index 6f30b47c..587dbc74 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/ToggleRoutineUseCase.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/ToggleRoutineUseCase.kt @@ -21,7 +21,6 @@ class ToggleRoutineUseCase @Inject constructor() { val newSubRoutineStates = subRoutineStates.map { newIsCompleted } return RoutineToggleState( - isCompleted = newIsCompleted, subRoutinesIsCompleted = newSubRoutineStates, ) } @@ -47,10 +46,7 @@ class ToggleRoutineUseCase @Inject constructor() { this[index] = newState } - val allCompleted = newSubRoutineStates.all { it } - return RoutineToggleState( - isCompleted = allCompleted, subRoutinesIsCompleted = newSubRoutineStates, ) } From 18aaba13872b667933cf897da0bc90aacc501780 Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Sun, 9 Nov 2025 11:31:54 +0900 Subject: [PATCH 11/12] =?UTF-8?q?Refactor:=20isCompleted=20=EB=A1=A4?= =?UTF-8?q?=EB=B0=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bitnagil/domain/routine/model/RoutineToggleState.kt | 6 ++---- .../domain/routine/usecase/ToggleRoutineUseCase.kt | 8 +++++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/RoutineToggleState.kt b/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/RoutineToggleState.kt index c7305c29..0b3a5e79 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/RoutineToggleState.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/routine/model/RoutineToggleState.kt @@ -1,8 +1,6 @@ package com.threegap.bitnagil.domain.routine.model data class RoutineToggleState( + val isCompleted: Boolean, val subRoutinesIsCompleted: List, -) { - val isCompleted: Boolean - get() = subRoutinesIsCompleted.all { it } -} +) diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/ToggleRoutineUseCase.kt b/domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/ToggleRoutineUseCase.kt index 587dbc74..a0a68ebb 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/ToggleRoutineUseCase.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/ToggleRoutineUseCase.kt @@ -21,6 +21,7 @@ class ToggleRoutineUseCase @Inject constructor() { val newSubRoutineStates = subRoutineStates.map { newIsCompleted } return RoutineToggleState( + isCompleted = newIsCompleted, subRoutinesIsCompleted = newSubRoutineStates, ) } @@ -37,16 +38,17 @@ class ToggleRoutineUseCase @Inject constructor() { index: Int, subRoutineStates: List, ): RoutineToggleState? { - if (index !in subRoutineStates.indices) { - return null - } + if (index !in subRoutineStates.indices) return null val newState = !subRoutineStates[index] val newSubRoutineStates = subRoutineStates.toMutableList().apply { this[index] = newState } + val allCompleted = newSubRoutineStates.all { it } + return RoutineToggleState( + isCompleted = allCompleted, subRoutinesIsCompleted = newSubRoutineStates, ) } From 2e44846c14d2cfc612afc1820fcdc33cdbe3375e Mon Sep 17 00:00:00 2001 From: wjdrjs00 Date: Sun, 9 Nov 2025 13:52:00 +0900 Subject: [PATCH 12/12] =?UTF-8?q?Refactor:=20FetchWeeklyRoutinesUseCase=20?= =?UTF-8?q?=EC=9D=B8=EC=9E=90=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/routine/usecase/FetchWeeklyRoutinesUseCase.kt | 5 +---- .../com/threegap/bitnagil/presentation/home/HomeViewModel.kt | 4 +++- .../presentation/routinelist/RoutineListViewModel.kt | 4 +++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/FetchWeeklyRoutinesUseCase.kt b/domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/FetchWeeklyRoutinesUseCase.kt index 999e3658..084b81e5 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/FetchWeeklyRoutinesUseCase.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/routine/usecase/FetchWeeklyRoutinesUseCase.kt @@ -2,15 +2,12 @@ package com.threegap.bitnagil.domain.routine.usecase import com.threegap.bitnagil.domain.routine.model.RoutineSchedule import com.threegap.bitnagil.domain.routine.repository.RoutineRepository -import java.time.LocalDate import javax.inject.Inject class FetchWeeklyRoutinesUseCase @Inject constructor( private val routineRepository: RoutineRepository, ) { - suspend operator fun invoke(currentWeeks: List): Result { - val startDate = currentWeeks.first().toString() - val endDate = currentWeeks.last().toString() + suspend operator fun invoke(startDate: String, endDate: String): Result { return routineRepository.fetchWeeklyRoutines(startDate, endDate) } } diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt index f53739e7..ea9a12f8 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt @@ -264,7 +264,9 @@ class HomeViewModel @Inject constructor( private suspend fun fetchWeeklyRoutines(currentWeeks: List) { subIntent { reduce { state.copy(loadingCount = state.loadingCount + 1) } - fetchWeeklyRoutinesUseCase(currentWeeks).fold( + val startDate = currentWeeks.first().toString() + val endDate = currentWeeks.last().toString() + fetchWeeklyRoutinesUseCase(startDate, endDate).fold( onSuccess = { reduce { state.copy(routineSchedule = it.toUiModel(), loadingCount = state.loadingCount - 1) } }, diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/RoutineListViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/RoutineListViewModel.kt index 7542a6c0..e5113f80 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/RoutineListViewModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/routinelist/RoutineListViewModel.kt @@ -128,8 +128,10 @@ class RoutineListViewModel @Inject constructor( private fun fetchRoutines() { sendIntent(RoutineListIntent.UpdateLoading(true)) val currentWeek = stateFlow.value.selectedDate.getCurrentWeekDays() + val startDate = currentWeek.first().toString() + val endDate = currentWeek.last().toString() viewModelScope.launch { - fetchWeeklyRoutinesUseCase(currentWeek).fold( + fetchWeeklyRoutinesUseCase(startDate, endDate).fold( onSuccess = { routines -> sendIntent(RoutineListIntent.LoadRoutines(routines.toUiModel())) sendIntent(RoutineListIntent.UpdateLoading(false))