From a39537420a032c1e713b9b2c27a54e42fdb1b939 Mon Sep 17 00:00:00 2001 From: dogmania Date: Mon, 9 Feb 2026 18:05:31 +0900 Subject: [PATCH 01/48] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20ktlint?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/twix/goal_editor/GoalEditorViewModel.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorViewModel.kt b/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorViewModel.kt index 3f18818d..8958f0e4 100644 --- a/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorViewModel.kt +++ b/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorViewModel.kt @@ -7,9 +7,9 @@ import com.twix.domain.model.enums.GoalIconType import com.twix.domain.model.enums.GoalReactionType import com.twix.domain.model.enums.RepeatCycle import com.twix.domain.model.goal.CreateGoalParam -import com.twix.domain.repository.GoalRepository import com.twix.domain.model.goal.Goal import com.twix.domain.model.goal.GoalVerification +import com.twix.domain.repository.GoalRepository import com.twix.goal_editor.model.GoalEditorUiState import com.twix.ui.base.BaseViewModel import com.twix.util.bus.GoalRefreshBus From 2719d815afdd996cbe398d78ff1188da3fb56564 Mon Sep 17 00:00:00 2001 From: dogmania Date: Mon, 9 Feb 2026 18:26:23 +0900 Subject: [PATCH 02/48] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=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 --- .../twix/network/model/response/goal/mapper/GoalMapper.kt | 6 +++--- .../java/com/twix/data/repository/DefaultGoalRepository.kt | 4 ++-- .../domain/model/goal/{CreatedGoal.kt => GoalDetail.kt} | 2 +- .../main/java/com/twix/domain/repository/GoalRepository.kt | 4 ++-- 4 files changed, 8 insertions(+), 8 deletions(-) rename domain/src/main/java/com/twix/domain/model/goal/{CreatedGoal.kt => GoalDetail.kt} (94%) diff --git a/core/network/src/main/java/com/twix/network/model/response/goal/mapper/GoalMapper.kt b/core/network/src/main/java/com/twix/network/model/response/goal/mapper/GoalMapper.kt index 6c62c5de..65339ea5 100644 --- a/core/network/src/main/java/com/twix/network/model/response/goal/mapper/GoalMapper.kt +++ b/core/network/src/main/java/com/twix/network/model/response/goal/mapper/GoalMapper.kt @@ -3,7 +3,7 @@ package com.twix.network.model.response.goal.mapper import com.twix.domain.model.enums.GoalIconType import com.twix.domain.model.enums.GoalReactionType import com.twix.domain.model.enums.RepeatCycle -import com.twix.domain.model.goal.CreatedGoal +import com.twix.domain.model.goal.GoalDetail import com.twix.domain.model.goal.Goal import com.twix.domain.model.goal.GoalList import com.twix.domain.model.goal.GoalVerification @@ -41,8 +41,8 @@ fun VerificationResponse.toDomainOrNull(): GoalVerification? = uploadedAt = uploadedAt, ) -fun CreateGoalResponse.toDomain(): CreatedGoal = - CreatedGoal( +fun CreateGoalResponse.toDomain(): GoalDetail = + GoalDetail( goalId = goalId, name = name, icon = GoalIconType.fromApi(icon), diff --git a/data/src/main/java/com/twix/data/repository/DefaultGoalRepository.kt b/data/src/main/java/com/twix/data/repository/DefaultGoalRepository.kt index 1cf57d01..c5a33c83 100644 --- a/data/src/main/java/com/twix/data/repository/DefaultGoalRepository.kt +++ b/data/src/main/java/com/twix/data/repository/DefaultGoalRepository.kt @@ -1,7 +1,7 @@ package com.twix.data.repository import com.twix.domain.model.goal.CreateGoalParam -import com.twix.domain.model.goal.CreatedGoal +import com.twix.domain.model.goal.GoalDetail import com.twix.domain.model.goal.GoalList import com.twix.domain.repository.GoalRepository import com.twix.network.execute.safeApiCall @@ -15,7 +15,7 @@ class DefaultGoalRepository( ) : GoalRepository { override suspend fun fetchGoalList(date: String): AppResult = safeApiCall { service.fetchGoals(date).toDomain() } - override suspend fun createGoal(param: CreateGoalParam): AppResult = + override suspend fun createGoal(param: CreateGoalParam): AppResult = safeApiCall { service.createGoal(param.toRequest()).toDomain() } diff --git a/domain/src/main/java/com/twix/domain/model/goal/CreatedGoal.kt b/domain/src/main/java/com/twix/domain/model/goal/GoalDetail.kt similarity index 94% rename from domain/src/main/java/com/twix/domain/model/goal/CreatedGoal.kt rename to domain/src/main/java/com/twix/domain/model/goal/GoalDetail.kt index fdb95343..b01c2e2b 100644 --- a/domain/src/main/java/com/twix/domain/model/goal/CreatedGoal.kt +++ b/domain/src/main/java/com/twix/domain/model/goal/GoalDetail.kt @@ -4,7 +4,7 @@ import com.twix.domain.model.enums.GoalIconType import com.twix.domain.model.enums.RepeatCycle import java.time.LocalDate -data class CreatedGoal( +data class GoalDetail( val goalId: Long, val name: String, val icon: GoalIconType, diff --git a/domain/src/main/java/com/twix/domain/repository/GoalRepository.kt b/domain/src/main/java/com/twix/domain/repository/GoalRepository.kt index c79b7732..c1a55e66 100644 --- a/domain/src/main/java/com/twix/domain/repository/GoalRepository.kt +++ b/domain/src/main/java/com/twix/domain/repository/GoalRepository.kt @@ -1,12 +1,12 @@ package com.twix.domain.repository import com.twix.domain.model.goal.CreateGoalParam -import com.twix.domain.model.goal.CreatedGoal +import com.twix.domain.model.goal.GoalDetail import com.twix.domain.model.goal.GoalList import com.twix.result.AppResult interface GoalRepository { suspend fun fetchGoalList(date: String): AppResult - suspend fun createGoal(param: CreateGoalParam): AppResult + suspend fun createGoal(param: CreateGoalParam): AppResult } From 4d5543b03db3ac4dee21f7d141f67fee918e28f0 Mon Sep 17 00:00:00 2001 From: dogmania Date: Mon, 9 Feb 2026 18:40:32 +0900 Subject: [PATCH 03/48] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=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 --- .../goal/model/{CreateGoalResponse.kt => GoalDetailResponse.kt} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename core/network/src/main/java/com/twix/network/model/response/goal/model/{CreateGoalResponse.kt => GoalDetailResponse.kt} (95%) diff --git a/core/network/src/main/java/com/twix/network/model/response/goal/model/CreateGoalResponse.kt b/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalDetailResponse.kt similarity index 95% rename from core/network/src/main/java/com/twix/network/model/response/goal/model/CreateGoalResponse.kt rename to core/network/src/main/java/com/twix/network/model/response/goal/model/GoalDetailResponse.kt index 7150b5a6..521f671f 100644 --- a/core/network/src/main/java/com/twix/network/model/response/goal/model/CreateGoalResponse.kt +++ b/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalDetailResponse.kt @@ -4,7 +4,7 @@ import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class CreateGoalResponse( +data class GoalDetailResponse( @SerialName("goalId") val goalId: Long, @SerialName("name") val name: String, @SerialName("icon") val icon: String, From 441554ef3e8393265a6a82821a216ea9b48628f8 Mon Sep 17 00:00:00 2001 From: dogmania Date: Mon, 9 Feb 2026 18:41:01 +0900 Subject: [PATCH 04/48] =?UTF-8?q?=E2=9C=A8=20Feat:=20UpdateGoalParam=20->?= =?UTF-8?q?=20UpdateGoalRequest=20Mapper=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../network/model/request/goal/mapper/GoalMapper.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/core/network/src/main/java/com/twix/network/model/request/goal/mapper/GoalMapper.kt b/core/network/src/main/java/com/twix/network/model/request/goal/mapper/GoalMapper.kt index c93d9a12..c5afe713 100644 --- a/core/network/src/main/java/com/twix/network/model/request/goal/mapper/GoalMapper.kt +++ b/core/network/src/main/java/com/twix/network/model/request/goal/mapper/GoalMapper.kt @@ -1,7 +1,9 @@ package com.twix.network.model.request.goal.mapper import com.twix.domain.model.goal.CreateGoalParam +import com.twix.domain.model.goal.UpdateGoalParam import com.twix.network.model.request.goal.model.CreateGoalRequest +import com.twix.network.model.request.goal.model.UpdateGoalRequest fun CreateGoalParam.toRequest(): CreateGoalRequest = CreateGoalRequest( @@ -12,3 +14,12 @@ fun CreateGoalParam.toRequest(): CreateGoalRequest = startDate = startDate.toString(), endDate = endDate?.toString(), ) + +fun UpdateGoalParam.toRequest(): UpdateGoalRequest = + UpdateGoalRequest( + name = name, + icon = icon.toApi(), + repeatCycle = repeatCycle.toApi(), + repeatCount = repeatCount, + endDate = endDate?.toString(), + ) From faad6c28c05fe4e77f477b2d4c24788ddbe82bea Mon Sep 17 00:00:00 2001 From: dogmania Date: Mon, 9 Feb 2026 18:41:15 +0900 Subject: [PATCH 05/48] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EB=AA=A9=ED=91=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EC=9A=94=EC=B2=AD=20DTO=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/request/goal/model/UpdateGoalRequest.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 core/network/src/main/java/com/twix/network/model/request/goal/model/UpdateGoalRequest.kt diff --git a/core/network/src/main/java/com/twix/network/model/request/goal/model/UpdateGoalRequest.kt b/core/network/src/main/java/com/twix/network/model/request/goal/model/UpdateGoalRequest.kt new file mode 100644 index 00000000..7a466555 --- /dev/null +++ b/core/network/src/main/java/com/twix/network/model/request/goal/model/UpdateGoalRequest.kt @@ -0,0 +1,13 @@ +package com.twix.network.model.request.goal.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class UpdateGoalRequest( + @SerialName("name") val name: String, + @SerialName("icon") val icon: String, + @SerialName("repeatCycle") val repeatCycle: String, + @SerialName("repeatCount") val repeatCount: Int, + @SerialName("endDate") val endDate: String? = null, +) From 6bb8b073be1392d38cfaa9549cbd4ac690283a36 Mon Sep 17 00:00:00 2001 From: dogmania Date: Mon, 9 Feb 2026 18:41:28 +0900 Subject: [PATCH 06/48] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EB=AA=A9=ED=91=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20Domain=20Param=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/twix/domain/model/goal/UpdateGoalParam.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 domain/src/main/java/com/twix/domain/model/goal/UpdateGoalParam.kt diff --git a/domain/src/main/java/com/twix/domain/model/goal/UpdateGoalParam.kt b/domain/src/main/java/com/twix/domain/model/goal/UpdateGoalParam.kt new file mode 100644 index 00000000..e2c281e3 --- /dev/null +++ b/domain/src/main/java/com/twix/domain/model/goal/UpdateGoalParam.kt @@ -0,0 +1,14 @@ +package com.twix.domain.model.goal + +import com.twix.domain.model.enums.GoalIconType +import com.twix.domain.model.enums.RepeatCycle +import java.time.LocalDate + +data class UpdateGoalParam( + val goalId: Long, + val name: String, + val icon: GoalIconType, + val repeatCycle: RepeatCycle, + val repeatCount: Int, + val endDate: LocalDate?, +) From bd7fe6acfac5e7332227e4d52cb2f4de6824ec31 Mon Sep 17 00:00:00 2001 From: dogmania Date: Mon, 9 Feb 2026 18:41:42 +0900 Subject: [PATCH 07/48] =?UTF-8?q?=F0=9F=8D=B1=20Chore:=20string=20?= =?UTF-8?q?=EB=A6=AC=EC=86=8C=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/design-system/src/main/res/values/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/core/design-system/src/main/res/values/strings.xml b/core/design-system/src/main/res/values/strings.xml index 3325a37f..2a44e7dd 100644 --- a/core/design-system/src/main/res/values/strings.xml +++ b/core/design-system/src/main/res/values/strings.xml @@ -60,6 +60,7 @@ 종료 날짜가 시작 날짜보다 이전입니다. 목표 조회에 실패했습니다. 목표 생성에 실패했습니다. + 목표 수정에 실패했습니다. %s\n목표를 이루셨나요? From ea45cf5e68b0ba183f1dd56059fb175ca86a44af Mon Sep 17 00:00:00 2001 From: dogmania Date: Mon, 9 Feb 2026 18:41:59 +0900 Subject: [PATCH 08/48] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=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 --- .../twix/network/model/response/goal/mapper/GoalMapper.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/network/src/main/java/com/twix/network/model/response/goal/mapper/GoalMapper.kt b/core/network/src/main/java/com/twix/network/model/response/goal/mapper/GoalMapper.kt index 65339ea5..d4cedb73 100644 --- a/core/network/src/main/java/com/twix/network/model/response/goal/mapper/GoalMapper.kt +++ b/core/network/src/main/java/com/twix/network/model/response/goal/mapper/GoalMapper.kt @@ -3,11 +3,11 @@ package com.twix.network.model.response.goal.mapper import com.twix.domain.model.enums.GoalIconType import com.twix.domain.model.enums.GoalReactionType import com.twix.domain.model.enums.RepeatCycle -import com.twix.domain.model.goal.GoalDetail import com.twix.domain.model.goal.Goal +import com.twix.domain.model.goal.GoalDetail import com.twix.domain.model.goal.GoalList import com.twix.domain.model.goal.GoalVerification -import com.twix.network.model.response.goal.model.CreateGoalResponse +import com.twix.network.model.response.goal.model.GoalDetailResponse import com.twix.network.model.response.goal.model.GoalListResponse import com.twix.network.model.response.goal.model.GoalResponse import com.twix.network.model.response.goal.model.VerificationResponse @@ -41,7 +41,7 @@ fun VerificationResponse.toDomainOrNull(): GoalVerification? = uploadedAt = uploadedAt, ) -fun CreateGoalResponse.toDomain(): GoalDetail = +fun GoalDetailResponse.toDomain(): GoalDetail = GoalDetail( goalId = goalId, name = name, From 4dcde0f1b5db9564dc827977c47c568d9f546836 Mon Sep 17 00:00:00 2001 From: dogmania Date: Mon, 9 Feb 2026 18:42:19 +0900 Subject: [PATCH 09/48] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EB=AA=A9=ED=91=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20API=20=ED=86=B5=EC=8B=A0=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/twix/network/service/GoalService.kt | 13 +++++++++++-- .../twix/data/repository/DefaultGoalRepository.kt | 6 ++++++ .../com/twix/domain/repository/GoalRepository.kt | 3 +++ 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/core/network/src/main/java/com/twix/network/service/GoalService.kt b/core/network/src/main/java/com/twix/network/service/GoalService.kt index 3a46205d..fd404b4b 100644 --- a/core/network/src/main/java/com/twix/network/service/GoalService.kt +++ b/core/network/src/main/java/com/twix/network/service/GoalService.kt @@ -1,11 +1,14 @@ package com.twix.network.service import com.twix.network.model.request.goal.model.CreateGoalRequest -import com.twix.network.model.response.goal.model.CreateGoalResponse +import com.twix.network.model.request.goal.model.UpdateGoalRequest +import com.twix.network.model.response.goal.model.GoalDetailResponse import com.twix.network.model.response.goal.model.GoalListResponse import de.jensklingenberg.ktorfit.http.Body import de.jensklingenberg.ktorfit.http.GET import de.jensklingenberg.ktorfit.http.POST +import de.jensklingenberg.ktorfit.http.PUT +import de.jensklingenberg.ktorfit.http.Path import de.jensklingenberg.ktorfit.http.Query interface GoalService { @@ -17,5 +20,11 @@ interface GoalService { @POST("api/v1/goals") suspend fun createGoal( @Body body: CreateGoalRequest, - ): CreateGoalResponse + ): GoalDetailResponse + + @PUT("api/v1/goals/{goalId}") + suspend fun updateGoal( + @Body body: UpdateGoalRequest, + @Path("goalId") goalId: Long, + ): GoalDetailResponse } diff --git a/data/src/main/java/com/twix/data/repository/DefaultGoalRepository.kt b/data/src/main/java/com/twix/data/repository/DefaultGoalRepository.kt index c5a33c83..450b9088 100644 --- a/data/src/main/java/com/twix/data/repository/DefaultGoalRepository.kt +++ b/data/src/main/java/com/twix/data/repository/DefaultGoalRepository.kt @@ -3,6 +3,7 @@ package com.twix.data.repository import com.twix.domain.model.goal.CreateGoalParam import com.twix.domain.model.goal.GoalDetail import com.twix.domain.model.goal.GoalList +import com.twix.domain.model.goal.UpdateGoalParam import com.twix.domain.repository.GoalRepository import com.twix.network.execute.safeApiCall import com.twix.network.model.request.goal.mapper.toRequest @@ -19,4 +20,9 @@ class DefaultGoalRepository( safeApiCall { service.createGoal(param.toRequest()).toDomain() } + + override suspend fun updateGoal(param: UpdateGoalParam): AppResult = + safeApiCall { + service.updateGoal(body = param.toRequest(), goalId = param.goalId).toDomain() + } } diff --git a/domain/src/main/java/com/twix/domain/repository/GoalRepository.kt b/domain/src/main/java/com/twix/domain/repository/GoalRepository.kt index c1a55e66..e342805c 100644 --- a/domain/src/main/java/com/twix/domain/repository/GoalRepository.kt +++ b/domain/src/main/java/com/twix/domain/repository/GoalRepository.kt @@ -3,10 +3,13 @@ package com.twix.domain.repository import com.twix.domain.model.goal.CreateGoalParam import com.twix.domain.model.goal.GoalDetail import com.twix.domain.model.goal.GoalList +import com.twix.domain.model.goal.UpdateGoalParam import com.twix.result.AppResult interface GoalRepository { suspend fun fetchGoalList(date: String): AppResult suspend fun createGoal(param: CreateGoalParam): AppResult + + suspend fun updateGoal(param: UpdateGoalParam): AppResult } From f86be537c217183b5f5be2c77cffaff46b4c900c Mon Sep 17 00:00:00 2001 From: dogmania Date: Mon, 9 Feb 2026 18:42:42 +0900 Subject: [PATCH 10/48] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20Save=20I?= =?UTF-8?q?ntent=EC=97=90=20id=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/twix/goal_editor/GoalEditorIntent.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorIntent.kt b/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorIntent.kt index 872db8e9..31a55405 100644 --- a/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorIntent.kt +++ b/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorIntent.kt @@ -34,7 +34,9 @@ sealed interface GoalEditorIntent : Intent { val enabled: Boolean, ) : GoalEditorIntent - data object Save : GoalEditorIntent + data class Save( + val id: Long, + ) : GoalEditorIntent data class InitGoal( val id: Long, From aa001afc2a8dceafad345b71282440312c4742a4 Mon Sep 17 00:00:00 2001 From: dogmania Date: Mon, 9 Feb 2026 18:43:00 +0900 Subject: [PATCH 11/48] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20Save=20I?= =?UTF-8?q?ntent=EC=97=90=20id=20=ED=8C=8C=EB=9D=BC=EB=AF=B8=ED=84=B0=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/twix/goal_editor/GoalEditorScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorScreen.kt b/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorScreen.kt index 89e116e4..15a1678f 100644 --- a/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorScreen.kt +++ b/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorScreen.kt @@ -105,7 +105,7 @@ fun GoalEditorRoute( onCommitStartDate = { viewModel.dispatch(GoalEditorIntent.SetStartDate(it)) }, onCommitRepeatCount = { viewModel.dispatch(GoalEditorIntent.SetRepeatCount(it)) }, onToggleEndDateEnabled = { viewModel.dispatch(GoalEditorIntent.SetEndDateEnabled(it)) }, - onComplete = { viewModel.dispatch(GoalEditorIntent.Save) }, + onComplete = { viewModel.dispatch(GoalEditorIntent.Save(goalId)) }, ) } From dedee73814efb61459feef196ce35fbc33a51d36 Mon Sep 17 00:00:00 2001 From: dogmania Date: Mon, 9 Feb 2026 18:43:19 +0900 Subject: [PATCH 12/48] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EB=AA=A9=ED=91=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95=20=EB=B9=84=EC=A6=88=EB=8B=88=EC=8A=A4=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../twix/goal_editor/GoalEditorViewModel.kt | 44 ++++++++++++++----- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorViewModel.kt b/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorViewModel.kt index 8958f0e4..a59f84bf 100644 --- a/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorViewModel.kt +++ b/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorViewModel.kt @@ -9,6 +9,7 @@ import com.twix.domain.model.enums.RepeatCycle import com.twix.domain.model.goal.CreateGoalParam import com.twix.domain.model.goal.Goal import com.twix.domain.model.goal.GoalVerification +import com.twix.domain.model.goal.UpdateGoalParam import com.twix.domain.repository.GoalRepository import com.twix.goal_editor.model.GoalEditorUiState import com.twix.ui.base.BaseViewModel @@ -24,7 +25,7 @@ class GoalEditorViewModel( ) { override suspend fun handleIntent(intent: GoalEditorIntent) { when (intent) { - GoalEditorIntent.Save -> save() + is GoalEditorIntent.Save -> save(intent.id) is GoalEditorIntent.SetIcon -> setIcon(intent.icon) is GoalEditorIntent.SetEndDate -> setEndDate(intent.endDate) is GoalEditorIntent.SetRepeatCount -> setRepeatCount(intent.repeatCount) @@ -68,7 +69,7 @@ class GoalEditorViewModel( reduce { copy(endDateEnabled = enabled) } } - private fun save() { + private fun save(id: Long) { if (!currentState.isEnabled) return if (currentState.endDateEnabled && currentState.endDate.isBefore(currentState.startDate)) { @@ -78,14 +79,25 @@ class GoalEditorViewModel( return } - launchResult( - block = { goalRepository.createGoal(currentState.toCreateParam()) }, - onSuccess = { - goalRefreshBus.notifyChanged() - tryEmitSideEffect(GoalEditorSideEffect.NavigateToHome) - }, - onError = { emitSideEffect(GoalEditorSideEffect.ShowToast(R.string.toast_create_goal_failed, ToastType.ERROR)) }, - ) + if (id == -1L) { + launchResult( + block = { goalRepository.createGoal(currentState.toCreateParam()) }, + onSuccess = { + goalRefreshBus.notifyChanged() + tryEmitSideEffect(GoalEditorSideEffect.NavigateToHome) + }, + onError = { emitSideEffect(GoalEditorSideEffect.ShowToast(R.string.toast_create_goal_failed, ToastType.ERROR)) }, + ) + } else { + launchResult( + block = { goalRepository.updateGoal(currentState.toUpdateParam(id)) }, + onSuccess = { + goalRefreshBus.notifyChanged() + tryEmitSideEffect(GoalEditorSideEffect.NavigateToHome) + }, + onError = { emitSideEffect(GoalEditorSideEffect.ShowToast(R.string.toast_update_goal_failed, ToastType.ERROR)) }, + ) + } } private fun initGoal(id: Long) { @@ -123,7 +135,7 @@ class GoalEditorViewModel( repeatCount = 4, startDate = LocalDate.now(), endDate = LocalDate.now().plusWeeks(1), - endDateEnabled = true, + endDateEnabled = false, ) } } @@ -137,4 +149,14 @@ class GoalEditorViewModel( startDate = startDate, endDate = if (endDateEnabled) endDate else null, ) + + private fun GoalEditorUiState.toUpdateParam(id: Long): UpdateGoalParam = + UpdateGoalParam( + goalId = id, + name = goalTitle.trim(), + icon = selectedIcon, + repeatCycle = selectedRepeatCycle, + repeatCount = repeatCount, + endDate = if (endDateEnabled) endDate else null, + ) } From e9b34f7556679b440eddeba642d449e99bfac000 Mon Sep 17 00:00:00 2001 From: dogmania Date: Mon, 9 Feb 2026 18:43:35 +0900 Subject: [PATCH 13/48] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20Horizont?= =?UTF-8?q?alDivider=20=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/twix/goal_editor/component/GoalInfoCard.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/feature/goal-editor/src/main/java/com/twix/goal_editor/component/GoalInfoCard.kt b/feature/goal-editor/src/main/java/com/twix/goal_editor/component/GoalInfoCard.kt index c7116a3e..80070668 100644 --- a/feature/goal-editor/src/main/java/com/twix/goal_editor/component/GoalInfoCard.kt +++ b/feature/goal-editor/src/main/java/com/twix/goal_editor/component/GoalInfoCard.kt @@ -65,9 +65,9 @@ fun GoalInfoCard( onShowRepeatCountBottomSheet = onShowRepeatCountBottomSheet, ) - HorizontalDivider(thickness = 1.dp, color = GrayColor.C500) - if (!isEdit) { + HorizontalDivider(thickness = 1.dp, color = GrayColor.C500) + DateSettings( date = startDate, onShowCalendarBottomSheet = { onShowCalendarBottomSheet(false) }, From 662dffe66e8dac604449179cc04cb42f8934378e Mon Sep 17 00:00:00 2001 From: dogmania Date: Mon, 9 Feb 2026 18:52:05 +0900 Subject: [PATCH 14/48] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EB=AA=A9=ED=91=9C=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20API=20=ED=86=B5?= =?UTF-8?q?=EC=8B=A0=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/twix/network/service/GoalService.kt | 5 +++++ .../java/com/twix/data/repository/DefaultGoalRepository.kt | 5 +++++ .../main/java/com/twix/domain/repository/GoalRepository.kt | 2 ++ 3 files changed, 12 insertions(+) diff --git a/core/network/src/main/java/com/twix/network/service/GoalService.kt b/core/network/src/main/java/com/twix/network/service/GoalService.kt index fd404b4b..75eac5f4 100644 --- a/core/network/src/main/java/com/twix/network/service/GoalService.kt +++ b/core/network/src/main/java/com/twix/network/service/GoalService.kt @@ -27,4 +27,9 @@ interface GoalService { @Body body: UpdateGoalRequest, @Path("goalId") goalId: Long, ): GoalDetailResponse + + @GET("api/v1/goals/{goalId}") + suspend fun fetchGoalDetail( + @Path("goalId") goalId: Long, + ): GoalDetailResponse } diff --git a/data/src/main/java/com/twix/data/repository/DefaultGoalRepository.kt b/data/src/main/java/com/twix/data/repository/DefaultGoalRepository.kt index 450b9088..ddbe85d3 100644 --- a/data/src/main/java/com/twix/data/repository/DefaultGoalRepository.kt +++ b/data/src/main/java/com/twix/data/repository/DefaultGoalRepository.kt @@ -25,4 +25,9 @@ class DefaultGoalRepository( safeApiCall { service.updateGoal(body = param.toRequest(), goalId = param.goalId).toDomain() } + + override suspend fun fetchGoalDetail(goalId: Long): AppResult = + safeApiCall { + service.fetchGoalDetail(goalId).toDomain() + } } diff --git a/domain/src/main/java/com/twix/domain/repository/GoalRepository.kt b/domain/src/main/java/com/twix/domain/repository/GoalRepository.kt index e342805c..5daf532f 100644 --- a/domain/src/main/java/com/twix/domain/repository/GoalRepository.kt +++ b/domain/src/main/java/com/twix/domain/repository/GoalRepository.kt @@ -12,4 +12,6 @@ interface GoalRepository { suspend fun createGoal(param: CreateGoalParam): AppResult suspend fun updateGoal(param: UpdateGoalParam): AppResult + + suspend fun fetchGoalDetail(goalId: Long): AppResult } From 2d9d81b2a88b625faf53219ef8efb2e68f43ccb9 Mon Sep 17 00:00:00 2001 From: dogmania Date: Mon, 9 Feb 2026 18:52:20 +0900 Subject: [PATCH 15/48] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EB=AA=A9=ED=91=9C=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C=20=EB=B9=84=EC=A6=88?= =?UTF-8?q?=EB=8B=88=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../twix/goal_editor/GoalEditorViewModel.kt | 59 ++++++------------- 1 file changed, 19 insertions(+), 40 deletions(-) diff --git a/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorViewModel.kt b/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorViewModel.kt index a59f84bf..5fcb06ce 100644 --- a/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorViewModel.kt +++ b/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorViewModel.kt @@ -4,11 +4,9 @@ import androidx.lifecycle.viewModelScope import com.twix.designsystem.R import com.twix.designsystem.components.toast.model.ToastType import com.twix.domain.model.enums.GoalIconType -import com.twix.domain.model.enums.GoalReactionType import com.twix.domain.model.enums.RepeatCycle import com.twix.domain.model.goal.CreateGoalParam -import com.twix.domain.model.goal.Goal -import com.twix.domain.model.goal.GoalVerification +import com.twix.domain.model.goal.GoalDetail import com.twix.domain.model.goal.UpdateGoalParam import com.twix.domain.repository.GoalRepository import com.twix.goal_editor.model.GoalEditorUiState @@ -69,6 +67,19 @@ class GoalEditorViewModel( reduce { copy(endDateEnabled = enabled) } } + private fun setGoal(goal: GoalDetail) { + reduce { + copy( + goalTitle = goal.name, + selectedIcon = goal.icon, + selectedRepeatCycle = goal.repeatCycle, + repeatCount = goal.repeatCount, + endDate = goal.endDate ?: LocalDate.now(), + endDateEnabled = goal.endDate != null, + ) + } + } + private fun save(id: Long) { if (!currentState.isEnabled) return @@ -101,43 +112,11 @@ class GoalEditorViewModel( } private fun initGoal(id: Long) { - val goal = - Goal( - goalId = 4, - name = "밥무라", - icon = GoalIconType.DEFAULT, - repeatCycle = RepeatCycle.WEEKLY, - myCompleted = true, - partnerCompleted = true, - myVerification = - GoalVerification( - photologId = 1, - imageUrl = "https://picsum.photos/400/300", - comment = null, - reaction = GoalReactionType.LOVE, - uploadedAt = "2023-05-05", - ), - partnerVerification = - GoalVerification( - photologId = 1, - imageUrl = "https://picsum.photos/400/300", - comment = null, - reaction = GoalReactionType.LOVE, - uploadedAt = "2023-05-05", - ), - ) - - reduce { - copy( - goalTitle = goal.name, - selectedIcon = goal.icon, - selectedRepeatCycle = goal.repeatCycle, - repeatCount = 4, - startDate = LocalDate.now(), - endDate = LocalDate.now().plusWeeks(1), - endDateEnabled = false, - ) - } + launchResult( + block = { goalRepository.fetchGoalDetail(id) }, + onSuccess = { setGoal(it) }, + onError = { emitSideEffect(GoalEditorSideEffect.ShowToast(R.string.toast_goal_fetch_failed, ToastType.ERROR)) }, + ) } private fun GoalEditorUiState.toCreateParam(): CreateGoalParam = From 5efa27e42ec0cbd4ab9fd5479f54a796f804a0d0 Mon Sep 17 00:00:00 2001 From: dogmania Date: Mon, 9 Feb 2026 19:59:22 +0900 Subject: [PATCH 16/48] =?UTF-8?q?=E2=9C=A8=20Feat:=20ShowToast=20SideEffec?= =?UTF-8?q?t=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/twix/goal_manage/GoalManageSideEffect.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageSideEffect.kt diff --git a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageSideEffect.kt b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageSideEffect.kt new file mode 100644 index 00000000..df243fc9 --- /dev/null +++ b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageSideEffect.kt @@ -0,0 +1,12 @@ +package com.twix.goal_manage + +import androidx.annotation.StringRes +import com.twix.designsystem.components.toast.model.ToastType +import com.twix.ui.base.SideEffect + +sealed interface GoalManageSideEffect : SideEffect { + data class ShowToast( + @param:StringRes val resId: Int, + val type: ToastType, + ) : GoalManageSideEffect +} From e51acba28ad093c35c588e240667ab95b7a20ff2 Mon Sep 17 00:00:00 2001 From: dogmania Date: Mon, 9 Feb 2026 19:59:33 +0900 Subject: [PATCH 17/48] =?UTF-8?q?=F0=9F=8D=B1=20Chore:=20string=20?= =?UTF-8?q?=EB=A6=AC=EC=86=8C=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/design-system/src/main/res/values/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/core/design-system/src/main/res/values/strings.xml b/core/design-system/src/main/res/values/strings.xml index 2a44e7dd..fcd2d746 100644 --- a/core/design-system/src/main/res/values/strings.xml +++ b/core/design-system/src/main/res/values/strings.xml @@ -61,6 +61,7 @@ 목표 조회에 실패했습니다. 목표 생성에 실패했습니다. 목표 수정에 실패했습니다. + 목표 삭제에 실패했습니다. %s\n목표를 이루셨나요? From d260d8f739a3e3d1f6508b9baccc127f2cb30b62 Mon Sep 17 00:00:00 2001 From: dogmania Date: Mon, 9 Feb 2026 19:59:52 +0900 Subject: [PATCH 18/48] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EB=AA=A9=ED=91=9C=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20API=20=ED=86=B5=EC=8B=A0=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/twix/network/service/GoalService.kt | 6 ++++++ .../java/com/twix/data/repository/DefaultGoalRepository.kt | 2 ++ .../main/java/com/twix/domain/repository/GoalRepository.kt | 2 ++ 3 files changed, 10 insertions(+) diff --git a/core/network/src/main/java/com/twix/network/service/GoalService.kt b/core/network/src/main/java/com/twix/network/service/GoalService.kt index 75eac5f4..554bd3e0 100644 --- a/core/network/src/main/java/com/twix/network/service/GoalService.kt +++ b/core/network/src/main/java/com/twix/network/service/GoalService.kt @@ -5,6 +5,7 @@ import com.twix.network.model.request.goal.model.UpdateGoalRequest import com.twix.network.model.response.goal.model.GoalDetailResponse import com.twix.network.model.response.goal.model.GoalListResponse import de.jensklingenberg.ktorfit.http.Body +import de.jensklingenberg.ktorfit.http.DELETE import de.jensklingenberg.ktorfit.http.GET import de.jensklingenberg.ktorfit.http.POST import de.jensklingenberg.ktorfit.http.PUT @@ -32,4 +33,9 @@ interface GoalService { suspend fun fetchGoalDetail( @Path("goalId") goalId: Long, ): GoalDetailResponse + + @DELETE("api/v1/goals/{goalId}") + suspend fun deleteGoal( + @Path("goalId") goalId: Long, + ) } diff --git a/data/src/main/java/com/twix/data/repository/DefaultGoalRepository.kt b/data/src/main/java/com/twix/data/repository/DefaultGoalRepository.kt index ddbe85d3..fc0b21d8 100644 --- a/data/src/main/java/com/twix/data/repository/DefaultGoalRepository.kt +++ b/data/src/main/java/com/twix/data/repository/DefaultGoalRepository.kt @@ -30,4 +30,6 @@ class DefaultGoalRepository( safeApiCall { service.fetchGoalDetail(goalId).toDomain() } + + override suspend fun deleteGoal(goalId: Long): AppResult = safeApiCall { service.deleteGoal(goalId) } } diff --git a/domain/src/main/java/com/twix/domain/repository/GoalRepository.kt b/domain/src/main/java/com/twix/domain/repository/GoalRepository.kt index 5daf532f..0a9358fa 100644 --- a/domain/src/main/java/com/twix/domain/repository/GoalRepository.kt +++ b/domain/src/main/java/com/twix/domain/repository/GoalRepository.kt @@ -14,4 +14,6 @@ interface GoalRepository { suspend fun updateGoal(param: UpdateGoalParam): AppResult suspend fun fetchGoalDetail(goalId: Long): AppResult + + suspend fun deleteGoal(goalId: Long): AppResult } From 1a4fed234a1139797f711fa4c3d2a1a2b55a003b Mon Sep 17 00:00:00 2001 From: dogmania Date: Mon, 9 Feb 2026 20:00:07 +0900 Subject: [PATCH 19/48] =?UTF-8?q?=E2=9C=A8=20Feat:=20SideEffect=20?= =?UTF-8?q?=ED=95=B8=EB=93=A4=EB=A7=81=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/twix/goal_manage/GoalManageScreen.kt | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageScreen.kt b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageScreen.kt index 7649de79..b3619bc8 100644 --- a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageScreen.kt +++ b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageScreen.kt @@ -25,9 +25,11 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign @@ -42,6 +44,8 @@ import com.twix.designsystem.components.popup.CommonPopup import com.twix.designsystem.components.popup.CommonPopupDivider import com.twix.designsystem.components.popup.CommonPopupItem import com.twix.designsystem.components.text.AppText +import com.twix.designsystem.components.toast.ToastManager +import com.twix.designsystem.components.toast.model.ToastData import com.twix.designsystem.components.topbar.CommonTopBar import com.twix.designsystem.extension.label import com.twix.designsystem.extension.toRes @@ -51,23 +55,34 @@ import com.twix.domain.model.enums.AppTextStyle import com.twix.domain.model.enums.GoalIconType import com.twix.domain.model.goal.GoalSummary import com.twix.goal_manage.model.GoalManageUiState +import com.twix.ui.base.ObserveAsEvents import com.twix.ui.extension.noRippleClickable import org.koin.androidx.compose.koinViewModel +import org.koin.compose.koinInject import java.time.LocalDate @Composable fun GoalManageRoute( selectedDate: LocalDate, + toastManager: ToastManager = koinInject(), viewModel: GoalManageViewModel = koinViewModel(), popBackStack: () -> Unit, navigateToGoalEditor: (Long) -> Unit, ) { val uiState by viewModel.uiState.collectAsStateWithLifecycle() + val context = LocalContext.current + val currentContext by rememberUpdatedState(context) LaunchedEffect(selectedDate) { viewModel.dispatch(GoalManageIntent.SetSelectedDate(selectedDate)) } + ObserveAsEvents(viewModel.sideEffect) { effect -> + when (effect) { + is GoalManageSideEffect.ShowToast -> toastManager.tryShow(ToastData(currentContext.getString(effect.resId), effect.type)) + } + } + GoalManageScreen( uiState = uiState, onBack = popBackStack, From 9687a03492ec34884b0d02301cb55c64fbec7ba2 Mon Sep 17 00:00:00 2001 From: dogmania Date: Mon, 9 Feb 2026 20:00:18 +0900 Subject: [PATCH 20/48] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EB=AA=A9=ED=91=9C=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EB=B9=84=EC=A6=88=EB=8B=88=EC=8A=A4=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/twix/goal_manage/GoalManageViewModel.kt | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt index b42c0254..18c78b6b 100644 --- a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt +++ b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt @@ -1,15 +1,16 @@ package com.twix.goal_manage +import com.twix.designsystem.R +import com.twix.designsystem.components.toast.model.ToastType import com.twix.domain.model.enums.WeekNavigation import com.twix.domain.repository.GoalRepository import com.twix.goal_manage.model.GoalManageUiState import com.twix.ui.base.BaseViewModel -import com.twix.ui.base.NoSideEffect import java.time.LocalDate class GoalManageViewModel( private val goalRepository: GoalRepository, -) : BaseViewModel(GoalManageUiState()) { +) : BaseViewModel(GoalManageUiState()) { override suspend fun handleIntent(intent: GoalManageIntent) { when (intent) { is GoalManageIntent.EndGoal -> endGoal(intent.id) @@ -25,9 +26,18 @@ class GoalManageViewModel( } private fun deleteGoal(id: Long) { + val previousSummaries = currentState.goalSummaries val newSummaries = currentState.goalSummaries.filter { it.goalId != id } reduce { copy(goalSummaries = newSummaries) } - // TODO: 서버 통신 로직 추가 + + launchResult( + block = { goalRepository.deleteGoal(id) }, + onSuccess = {}, + onError = { + reduce { copy(goalSummaries = previousSummaries) } + emitSideEffect(GoalManageSideEffect.ShowToast(R.string.toast_delete_goal_failed, ToastType.ERROR)) + }, + ) } private fun setSelectedDate(date: LocalDate) { From 0882bbd51ed962d059d05934e30a5855ab312417 Mon Sep 17 00:00:00 2001 From: dogmania Date: Mon, 9 Feb 2026 21:06:16 +0900 Subject: [PATCH 21/48] =?UTF-8?q?=E2=9C=A8=20Feat:=20DialogState=20?= =?UTF-8?q?=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/twix/goal_manage/model/GoalDialogState.kt | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 feature/goal-manage/src/main/java/com/twix/goal_manage/model/GoalDialogState.kt diff --git a/feature/goal-manage/src/main/java/com/twix/goal_manage/model/GoalDialogState.kt b/feature/goal-manage/src/main/java/com/twix/goal_manage/model/GoalDialogState.kt new file mode 100644 index 00000000..455b7bbd --- /dev/null +++ b/feature/goal-manage/src/main/java/com/twix/goal_manage/model/GoalDialogState.kt @@ -0,0 +1,11 @@ +package com.twix.goal_manage.model + +import androidx.compose.runtime.Immutable +import com.twix.domain.model.enums.GoalIconType + +@Immutable +data class GoalDialogState( + val goalId: Long, + val name: String, + val icon: GoalIconType, +) From b14d33b0b490677a32eb63c1163e130dfc28c4e4 Mon Sep 17 00:00:00 2001 From: dogmania Date: Mon, 9 Feb 2026 21:06:38 +0900 Subject: [PATCH 22/48] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EC=82=AD=EC=A0=9C?= =?UTF-8?q?=EB=90=9C=20=EB=AA=A9=ED=91=9C=20=EC=83=81=ED=83=9C=20=EC=A0=95?= =?UTF-8?q?=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/twix/goal_manage/model/RemovedGoal.kt | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 feature/goal-manage/src/main/java/com/twix/goal_manage/model/RemovedGoal.kt diff --git a/feature/goal-manage/src/main/java/com/twix/goal_manage/model/RemovedGoal.kt b/feature/goal-manage/src/main/java/com/twix/goal_manage/model/RemovedGoal.kt new file mode 100644 index 00000000..8b39cadf --- /dev/null +++ b/feature/goal-manage/src/main/java/com/twix/goal_manage/model/RemovedGoal.kt @@ -0,0 +1,8 @@ +package com.twix.goal_manage.model + +import com.twix.domain.model.goal.GoalSummary + +data class RemovedGoal( + val index: Int, + val item: GoalSummary, +) From c491b59918eed7f945d26b429b4b803338cbded0 Mon Sep 17 00:00:00 2001 From: dogmania Date: Mon, 9 Feb 2026 21:06:51 +0900 Subject: [PATCH 23/48] =?UTF-8?q?=F0=9F=8D=B1=20Chore:=20string=20?= =?UTF-8?q?=EB=A6=AC=EC=86=8C=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/design-system/src/main/res/values/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/core/design-system/src/main/res/values/strings.xml b/core/design-system/src/main/res/values/strings.xml index fcd2d746..3d1cb70c 100644 --- a/core/design-system/src/main/res/values/strings.xml +++ b/core/design-system/src/main/res/values/strings.xml @@ -62,6 +62,7 @@ 목표 생성에 실패했습니다. 목표 수정에 실패했습니다. 목표 삭제에 실패했습니다. + 목표 완료에 실패했습니다. %s\n목표를 이루셨나요? From b4644ae1d88d4a15a0f0a30eb052681483fc6481 Mon Sep 17 00:00:00 2001 From: dogmania Date: Mon, 9 Feb 2026 21:07:14 +0900 Subject: [PATCH 24/48] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EB=AA=A9=ED=91=9C=20?= =?UTF-8?q?=EB=81=9D=EB=82=B4=EA=B8=B0=20API=20=ED=86=B5=EC=8B=A0=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/twix/network/service/GoalService.kt | 5 +++++ .../java/com/twix/data/repository/DefaultGoalRepository.kt | 2 ++ .../main/java/com/twix/domain/repository/GoalRepository.kt | 2 ++ 3 files changed, 9 insertions(+) diff --git a/core/network/src/main/java/com/twix/network/service/GoalService.kt b/core/network/src/main/java/com/twix/network/service/GoalService.kt index 554bd3e0..705a2788 100644 --- a/core/network/src/main/java/com/twix/network/service/GoalService.kt +++ b/core/network/src/main/java/com/twix/network/service/GoalService.kt @@ -38,4 +38,9 @@ interface GoalService { suspend fun deleteGoal( @Path("goalId") goalId: Long, ) + + @PUT("api/v1/goals/{goalId}/complete") + suspend fun completeGoal( + @Path("goalId") goalId: Long, + ) } diff --git a/data/src/main/java/com/twix/data/repository/DefaultGoalRepository.kt b/data/src/main/java/com/twix/data/repository/DefaultGoalRepository.kt index fc0b21d8..0e13dcb4 100644 --- a/data/src/main/java/com/twix/data/repository/DefaultGoalRepository.kt +++ b/data/src/main/java/com/twix/data/repository/DefaultGoalRepository.kt @@ -32,4 +32,6 @@ class DefaultGoalRepository( } override suspend fun deleteGoal(goalId: Long): AppResult = safeApiCall { service.deleteGoal(goalId) } + + override suspend fun completeGoal(goalId: Long): AppResult = safeApiCall { service.completeGoal(goalId) } } diff --git a/domain/src/main/java/com/twix/domain/repository/GoalRepository.kt b/domain/src/main/java/com/twix/domain/repository/GoalRepository.kt index 0a9358fa..14499924 100644 --- a/domain/src/main/java/com/twix/domain/repository/GoalRepository.kt +++ b/domain/src/main/java/com/twix/domain/repository/GoalRepository.kt @@ -16,4 +16,6 @@ interface GoalRepository { suspend fun fetchGoalDetail(goalId: Long): AppResult suspend fun deleteGoal(goalId: Long): AppResult + + suspend fun completeGoal(goalId: Long): AppResult } From 9fd57bbf298c71c5db8be5180ecc02370c0235c0 Mon Sep 17 00:00:00 2001 From: dogmania Date: Mon, 9 Feb 2026 21:07:31 +0900 Subject: [PATCH 25/48] =?UTF-8?q?=E2=9C=A8=20Feat:=20UI=20=EC=A0=9C?= =?UTF-8?q?=EC=96=B4=20Intent=20=EC=A0=95=EC=9D=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/twix/goal_manage/GoalManageIntent.kt | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageIntent.kt b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageIntent.kt index 8203abea..d17f9635 100644 --- a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageIntent.kt +++ b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageIntent.kt @@ -19,4 +19,23 @@ sealed interface GoalManageIntent : Intent { data object PreviousWeek : GoalManageIntent data object NextWeek : GoalManageIntent + + // UI 제어 + data class OpenMenu( + val goalId: Long, + ) : GoalManageIntent + + data object CloseMenu : GoalManageIntent + + data class ShowEndDialog( + val goalId: Long, + ) : GoalManageIntent + + data object DismissEndDialog : GoalManageIntent + + data class ShowDeleteDialog( + val goalId: Long, + ) : GoalManageIntent + + data object DismissDeleteDialog : GoalManageIntent } From 7514269ee835305f788bdeb266afc1d72e120aad Mon Sep 17 00:00:00 2001 From: dogmania Date: Mon, 9 Feb 2026 21:08:08 +0900 Subject: [PATCH 26/48] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EB=8B=A4=EC=9D=B4=EC=96=BC=EB=A1=9C=EA=B7=B8=20=EB=A0=8C?= =?UTF-8?q?=EB=8D=94=EB=A7=81=20=EB=A1=9C=EC=A7=81=EC=9D=84=20ID=20?= =?UTF-8?q?=EA=B8=B0=EB=B0=98=EC=9C=BC=EB=A1=9C=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/twix/goal_manage/GoalManageScreen.kt | 153 +++++++++++------- .../twix/goal_manage/GoalManageViewModel.kt | 96 ++++++++++- .../goal_manage/model/GoalManageUiState.kt | 8 +- 3 files changed, 191 insertions(+), 66 deletions(-) diff --git a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageScreen.kt b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageScreen.kt index b3619bc8..79e3e869 100644 --- a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageScreen.kt +++ b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageScreen.kt @@ -85,30 +85,60 @@ fun GoalManageRoute( GoalManageScreen( uiState = uiState, + openedMenuGoalId = uiState.openedMenuGoalId, + pendingIds = uiState.pendingGoalIds, onBack = popBackStack, onSelectDate = { viewModel.dispatch(GoalManageIntent.SetSelectedDate(it)) }, onPreviousWeek = { viewModel.dispatch(GoalManageIntent.PreviousWeek) }, onNextWeek = { viewModel.dispatch(GoalManageIntent.NextWeek) }, + // 팝업 메뉴 + onOpenMenu = { viewModel.dispatch(GoalManageIntent.OpenMenu(it)) }, + onCloseMenu = { viewModel.dispatch(GoalManageIntent.CloseMenu) }, + // 팝업 아이템에서 요청 + onRequestEnd = { viewModel.dispatch(GoalManageIntent.ShowEndDialog(it)) }, + onRequestDelete = { viewModel.dispatch(GoalManageIntent.ShowDeleteDialog(it)) }, + // 다이얼로그 confirm + onConfirmEnd = { viewModel.dispatch(GoalManageIntent.EndGoal(it)) }, + onConfirmDelete = { viewModel.dispatch(GoalManageIntent.DeleteGoal(it)) }, + // 다이얼로그 dismiss + onDismissEndDialog = { viewModel.dispatch(GoalManageIntent.DismissEndDialog) }, + onDismissDeleteDialog = { viewModel.dispatch(GoalManageIntent.DismissDeleteDialog) }, + // 수정 onEdit = navigateToGoalEditor, - onDelete = { viewModel.dispatch(GoalManageIntent.DeleteGoal(it)) }, - onEnd = { viewModel.dispatch(GoalManageIntent.EndGoal(it)) }, ) } @Composable private fun GoalManageScreen( uiState: GoalManageUiState, + openedMenuGoalId: Long?, onBack: () -> Unit, onSelectDate: (LocalDate) -> Unit, onPreviousWeek: () -> Unit, onNextWeek: () -> Unit, - onEdit: (Long) -> Unit = {}, - onDelete: (Long) -> Unit = {}, - onEnd: (Long) -> Unit = {}, + onEdit: (Long) -> Unit, + onRequestDelete: (Long) -> Unit, + onRequestEnd: (Long) -> Unit, + onConfirmDelete: (Long) -> Unit, + onConfirmEnd: (Long) -> Unit, + onDismissDeleteDialog: () -> Unit, + onDismissEndDialog: () -> Unit, + onOpenMenu: (Long) -> Unit, + onCloseMenu: () -> Unit, + pendingIds: Set, ) { - var showEndGoalDialog by remember { mutableStateOf(false) } - var showDeleteGoalDialog by remember { mutableStateOf(false) } - var selectedGoal: GoalSummary? by remember { mutableStateOf(null) } + val endDialog = uiState.endDialog + val deleteDialog = uiState.deleteDialog + + var endDialogSnapshot by remember { mutableStateOf(endDialog) } + var deleteDialogSnapshot by remember { mutableStateOf(deleteDialog) } + + LaunchedEffect(endDialog) { + if (endDialog != null) endDialogSnapshot = endDialog + } + LaunchedEffect(deleteDialog) { + if (deleteDialog != null) deleteDialogSnapshot = deleteDialog + } Box(modifier = Modifier.fillMaxSize()) { Column( @@ -148,57 +178,55 @@ private fun GoalManageScreen( .padding(horizontal = 20.dp) .weight(1f), summaryList = uiState.goalSummaries, + openedMenuGoalId = openedMenuGoalId, + pendingIds = pendingIds, + onOpenMenu = onOpenMenu, + onCloseMenu = onCloseMenu, onEdit = onEdit, - onDelete = { - showDeleteGoalDialog = true - selectedGoal = it - }, - onEnd = { - showEndGoalDialog = true - selectedGoal = it - }, + onRequestDelete = onRequestDelete, + onRequestEnd = onRequestEnd, ) } CommonDialog( - visible = showEndGoalDialog, + visible = endDialog != null, confirmText = stringResource(R.string.action_complete_goal), dismissText = stringResource(R.string.word_cancel), - onDismissRequest = { showEndGoalDialog = false }, + onDismissRequest = onDismissEndDialog, onConfirm = { - showEndGoalDialog = false - selectedGoal?.let { onEnd(it.goalId) } + val id = endDialog?.goalId + onDismissEndDialog() + id?.let(onConfirmEnd) }, - onDismiss = { showEndGoalDialog = false }, + onDismiss = onDismissEndDialog, content = { - selectedGoal?.let { - GoalSummaryDialogContent( - title = stringResource(R.string.dialog_end_goal_title, it.name), - content = stringResource(R.string.dialog_end_goal_content), - icon = it.icon, - ) - } + val dialog = endDialogSnapshot ?: return@CommonDialog + GoalSummaryDialogContent( + title = stringResource(R.string.dialog_end_goal_title, dialog.name), + content = stringResource(R.string.dialog_end_goal_content), + icon = dialog.icon, + ) }, ) CommonDialog( - visible = showDeleteGoalDialog, + visible = deleteDialog != null, confirmText = stringResource(R.string.word_delete), dismissText = stringResource(R.string.word_cancel), - onDismissRequest = { showDeleteGoalDialog = false }, + onDismissRequest = onDismissDeleteDialog, onConfirm = { - showDeleteGoalDialog = false - selectedGoal?.let { onDelete(it.goalId) } + val id = deleteDialog?.goalId + onDismissDeleteDialog() + id?.let(onConfirmDelete) }, - onDismiss = { showDeleteGoalDialog = false }, + onDismiss = onDismissDeleteDialog, content = { - selectedGoal?.let { - GoalSummaryDialogContent( - title = stringResource(R.string.dialog_delete_goal_title, it.name), - content = stringResource(R.string.dialog_delete_goal_content), - icon = it.icon, - ) - } + val dialog = deleteDialogSnapshot ?: return@CommonDialog + GoalSummaryDialogContent( + title = stringResource(R.string.dialog_delete_goal_title, dialog.name), + content = stringResource(R.string.dialog_delete_goal_content), + icon = dialog.icon, + ) }, ) } @@ -208,9 +236,13 @@ private fun GoalManageScreen( private fun GoalSummaryList( modifier: Modifier = Modifier, summaryList: List, + openedMenuGoalId: Long?, + pendingIds: Set, + onOpenMenu: (Long) -> Unit, + onCloseMenu: () -> Unit, onEdit: (Long) -> Unit = {}, - onDelete: (GoalSummary) -> Unit = {}, - onEnd: (GoalSummary) -> Unit = {}, + onRequestDelete: (Long) -> Unit = {}, + onRequestEnd: (Long) -> Unit = {}, ) { LazyColumn( modifier = modifier, @@ -220,9 +252,13 @@ private fun GoalSummaryList( items(summaryList, key = { it.goalId }) { item -> GoalSummaryItem( item = item, + openedMenuGoalId = openedMenuGoalId, + onOpenMenu = onOpenMenu, + onCloseMenu = onCloseMenu, onEdit = onEdit, - onDelete = onDelete, - onEnd = onEnd, + onShowDeleteDialog = onRequestDelete, + onShowEndDialog = onRequestEnd, + isPending = item.goalId in pendingIds, ) } } @@ -231,12 +267,15 @@ private fun GoalSummaryList( @Composable private fun GoalSummaryItem( item: GoalSummary, - onEdit: (Long) -> Unit = {}, - onDelete: (GoalSummary) -> Unit = {}, - onEnd: (GoalSummary) -> Unit = {}, + openedMenuGoalId: Long?, + onOpenMenu: (Long) -> Unit, + onCloseMenu: () -> Unit, + onEdit: (Long) -> Unit, + onShowEndDialog: (Long) -> Unit, + onShowDeleteDialog: (Long) -> Unit, + isPending: Boolean, ) { - var menuExpanded by remember { mutableStateOf(false) } - var anchorOffset by remember { mutableStateOf(IntOffset(x = -180, y = 68)) } + val menuVisible = openedMenuGoalId == item.goalId GoalCardFrame( goalName = item.name, @@ -251,13 +290,13 @@ private fun GoalSummaryItem( modifier = Modifier .size(24.dp) - .noRippleClickable(onClick = { menuExpanded = true }), + .noRippleClickable(enabled = !isPending, onClick = { onOpenMenu(item.goalId) }), ) CommonPopup( - visible = menuExpanded, - anchorOffset = anchorOffset, - onDismiss = { menuExpanded = false }, + visible = menuVisible, + anchorOffset = IntOffset(x = -180, y = 68), + onDismiss = onCloseMenu, ) { Column( modifier = @@ -270,23 +309,21 @@ private fun GoalSummaryItem( text = stringResource(R.string.action_edit), onClick = { onEdit(item.goalId) - menuExpanded = false + onCloseMenu() }, ) CommonPopupDivider() CommonPopupItem( text = stringResource(R.string.action_finish), onClick = { - onEnd(item) - menuExpanded = false + onShowEndDialog(item.goalId) }, ) CommonPopupDivider() CommonPopupItem( text = stringResource(R.string.action_delete), onClick = { - onDelete(item) - menuExpanded = false + onShowDeleteDialog(item.goalId) }, ) } diff --git a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt index 18c78b6b..09905519 100644 --- a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt +++ b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt @@ -4,7 +4,9 @@ import com.twix.designsystem.R import com.twix.designsystem.components.toast.model.ToastType import com.twix.domain.model.enums.WeekNavigation import com.twix.domain.repository.GoalRepository +import com.twix.goal_manage.model.GoalDialogState import com.twix.goal_manage.model.GoalManageUiState +import com.twix.goal_manage.model.RemovedGoal import com.twix.ui.base.BaseViewModel import java.time.LocalDate @@ -18,30 +20,112 @@ class GoalManageViewModel( is GoalManageIntent.SetSelectedDate -> setSelectedDate(intent.date) GoalManageIntent.NextWeek -> shiftWeek(WeekNavigation.NEXT) GoalManageIntent.PreviousWeek -> shiftWeek(WeekNavigation.PREVIOUS) + is GoalManageIntent.OpenMenu -> reduce { copy(openedMenuGoalId = intent.goalId) } + GoalManageIntent.CloseMenu -> reduce { copy(openedMenuGoalId = null) } + is GoalManageIntent.ShowEndDialog -> { + if (intent.goalId in currentState.pendingGoalIds) return + val goal = currentState.goalSummaries.firstOrNull { it.goalId == intent.goalId } ?: return + reduce { + copy( + endDialog = GoalDialogState(goal.goalId, goal.name, goal.icon), + openedMenuGoalId = null, + ) + } + } + GoalManageIntent.DismissEndDialog -> reduce { copy(endDialog = null) } + is GoalManageIntent.ShowDeleteDialog -> { + if (intent.goalId in currentState.pendingGoalIds) return + val goal = currentState.goalSummaries.firstOrNull { it.goalId == intent.goalId } ?: return + reduce { + copy( + deleteDialog = GoalDialogState(goal.goalId, goal.name, goal.icon), + openedMenuGoalId = null, + ) + } + } + GoalManageIntent.DismissDeleteDialog -> reduce { copy(deleteDialog = null) } } } private fun endGoal(id: Long) { - // TODO: 서버 통신 로직 추가 + if (id in currentState.pendingGoalIds) return + + val removed = removeGoalOptimistically(id) ?: return + + markPending(id, true) + + launchResult( + block = { goalRepository.completeGoal(id) }, + onSuccess = { + markPending(id, false) + }, + onError = { + markPending(id, false) + restoreGoal(removed) + emitSideEffect(GoalManageSideEffect.ShowToast(R.string.toast_complete_goal_failed, ToastType.ERROR)) + }, + ) } private fun deleteGoal(id: Long) { - val previousSummaries = currentState.goalSummaries - val newSummaries = currentState.goalSummaries.filter { it.goalId != id } - reduce { copy(goalSummaries = newSummaries) } + if (id in currentState.pendingGoalIds) return + + val removed = removeGoalOptimistically(id) ?: return + + markPending(id, true) launchResult( block = { goalRepository.deleteGoal(id) }, - onSuccess = {}, + onSuccess = { + markPending(id, false) + }, onError = { - reduce { copy(goalSummaries = previousSummaries) } + markPending(id, false) + restoreGoal(removed) emitSideEffect(GoalManageSideEffect.ShowToast(R.string.toast_delete_goal_failed, ToastType.ERROR)) }, ) } + private fun removeGoalOptimistically(id: Long): RemovedGoal? { + val index = currentState.goalSummaries.indexOfFirst { it.goalId == id } + if (index == -1) return null + val item = currentState.goalSummaries[index] + + reduce { + copy( + goalSummaries = goalSummaries.filterNot { it.goalId == id }, + openedMenuGoalId = if (openedMenuGoalId == id) null else openedMenuGoalId, + ) + } + return RemovedGoal(index, item) + } + private fun setSelectedDate(date: LocalDate) { + if (currentState.selectedDate == date) return reduce { copy(selectedDate = date) } + // TODO: 새로운 리스트 조회 + } + + private fun restoreGoal(removed: RemovedGoal) { + reduce { + if (goalSummaries.any { it.goalId == removed.item.goalId }) return@reduce this + val list = goalSummaries.toMutableList() + val safeIndex = removed.index.coerceIn(0, list.size) + list.add(safeIndex, removed.item) + copy(goalSummaries = list) + } + } + + private fun markPending( + id: Long, + pending: Boolean, + ) { + reduce { + copy( + pendingGoalIds = if (pending) pendingGoalIds + id else pendingGoalIds - id, + ) + } } private fun shiftWeek(action: WeekNavigation) { diff --git a/feature/goal-manage/src/main/java/com/twix/goal_manage/model/GoalManageUiState.kt b/feature/goal-manage/src/main/java/com/twix/goal_manage/model/GoalManageUiState.kt index 41526b28..e8ee58a8 100644 --- a/feature/goal-manage/src/main/java/com/twix/goal_manage/model/GoalManageUiState.kt +++ b/feature/goal-manage/src/main/java/com/twix/goal_manage/model/GoalManageUiState.kt @@ -23,7 +23,7 @@ data class GoalManageUiState( ), GoalSummary( goalId = 2, - name = "운동", + name = "물 마시기", icon = GoalIconType.EXERCISE, repeatCycle = RepeatCycle.DAILY, startDate = LocalDate.now(), @@ -31,7 +31,7 @@ data class GoalManageUiState( ), GoalSummary( goalId = 3, - name = "운동", + name = "잠자기", icon = GoalIconType.EXERCISE, repeatCycle = RepeatCycle.DAILY, startDate = LocalDate.now(), @@ -46,4 +46,8 @@ data class GoalManageUiState( endDate = LocalDate.now(), ), ), + val pendingGoalIds: Set = emptySet(), // 중복 요청 방지 + val openedMenuGoalId: Long? = null, // 팝업 현재 열린 goalId + val endDialog: GoalDialogState? = null, + val deleteDialog: GoalDialogState? = null, ) : State From bf20b1eb3e7a04c78bdefb6f64a9c3f9c27a641d Mon Sep 17 00:00:00 2001 From: dogmania Date: Tue, 10 Feb 2026 02:56:23 +0900 Subject: [PATCH 27/48] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EB=AA=A9=ED=91=9C=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20api=20=ED=86=B5=EC=8B=A0=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/twix/network/service/GoalService.kt | 6 ++++++ .../java/com/twix/data/repository/DefaultGoalRepository.kt | 6 ++++++ .../main/java/com/twix/domain/repository/GoalRepository.kt | 3 +++ 3 files changed, 15 insertions(+) diff --git a/core/network/src/main/java/com/twix/network/service/GoalService.kt b/core/network/src/main/java/com/twix/network/service/GoalService.kt index 705a2788..0dd2e910 100644 --- a/core/network/src/main/java/com/twix/network/service/GoalService.kt +++ b/core/network/src/main/java/com/twix/network/service/GoalService.kt @@ -4,6 +4,7 @@ import com.twix.network.model.request.goal.model.CreateGoalRequest import com.twix.network.model.request.goal.model.UpdateGoalRequest import com.twix.network.model.response.goal.model.GoalDetailResponse import com.twix.network.model.response.goal.model.GoalListResponse +import com.twix.network.model.response.goal.model.GoalSummaryListResponse import de.jensklingenberg.ktorfit.http.Body import de.jensklingenberg.ktorfit.http.DELETE import de.jensklingenberg.ktorfit.http.GET @@ -43,4 +44,9 @@ interface GoalService { suspend fun completeGoal( @Path("goalId") goalId: Long, ) + + @GET("api/v1/goals/detail") + suspend fun fetchGoalSummaryList( + @Query("date") date: String, + ): GoalSummaryListResponse } diff --git a/data/src/main/java/com/twix/data/repository/DefaultGoalRepository.kt b/data/src/main/java/com/twix/data/repository/DefaultGoalRepository.kt index 0e13dcb4..e25b193d 100644 --- a/data/src/main/java/com/twix/data/repository/DefaultGoalRepository.kt +++ b/data/src/main/java/com/twix/data/repository/DefaultGoalRepository.kt @@ -3,6 +3,7 @@ package com.twix.data.repository import com.twix.domain.model.goal.CreateGoalParam import com.twix.domain.model.goal.GoalDetail import com.twix.domain.model.goal.GoalList +import com.twix.domain.model.goal.GoalSummary import com.twix.domain.model.goal.UpdateGoalParam import com.twix.domain.repository.GoalRepository import com.twix.network.execute.safeApiCall @@ -34,4 +35,9 @@ class DefaultGoalRepository( override suspend fun deleteGoal(goalId: Long): AppResult = safeApiCall { service.deleteGoal(goalId) } override suspend fun completeGoal(goalId: Long): AppResult = safeApiCall { service.completeGoal(goalId) } + + override suspend fun fetchGoalSummaryList(date: String): AppResult> = + safeApiCall { + service.fetchGoalSummaryList(date).toDomain() + } } diff --git a/domain/src/main/java/com/twix/domain/repository/GoalRepository.kt b/domain/src/main/java/com/twix/domain/repository/GoalRepository.kt index 14499924..40e7a2d4 100644 --- a/domain/src/main/java/com/twix/domain/repository/GoalRepository.kt +++ b/domain/src/main/java/com/twix/domain/repository/GoalRepository.kt @@ -3,6 +3,7 @@ package com.twix.domain.repository import com.twix.domain.model.goal.CreateGoalParam import com.twix.domain.model.goal.GoalDetail import com.twix.domain.model.goal.GoalList +import com.twix.domain.model.goal.GoalSummary import com.twix.domain.model.goal.UpdateGoalParam import com.twix.result.AppResult @@ -18,4 +19,6 @@ interface GoalRepository { suspend fun deleteGoal(goalId: Long): AppResult suspend fun completeGoal(goalId: Long): AppResult + + suspend fun fetchGoalSummaryList(date: String): AppResult> } From c3138d8e34c0e922d8a6407b3484ea5b2cfe171a Mon Sep 17 00:00:00 2001 From: dogmania Date: Tue, 10 Feb 2026 02:56:45 +0900 Subject: [PATCH 28/48] =?UTF-8?q?=E2=9C=A8=20Feat:=20GoalSummaryListRespon?= =?UTF-8?q?se=20DTO=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/response/goal/model/GoalSummaryListResponse.kt | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 core/network/src/main/java/com/twix/network/model/response/goal/model/GoalSummaryListResponse.kt diff --git a/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalSummaryListResponse.kt b/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalSummaryListResponse.kt new file mode 100644 index 00000000..8623ca80 --- /dev/null +++ b/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalSummaryListResponse.kt @@ -0,0 +1,9 @@ +package com.twix.network.model.response.goal.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class GoalSummaryListResponse( + @SerialName("goals") val goals: List +) From ee46d30d9a5845c9e4d7980263574519462631a6 Mon Sep 17 00:00:00 2001 From: dogmania Date: Tue, 10 Feb 2026 02:57:04 +0900 Subject: [PATCH 29/48] =?UTF-8?q?=E2=9C=A8=20Feat:=20GoalSummaryResponse?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../response/goal/model/GoalSummaryResponse.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 core/network/src/main/java/com/twix/network/model/response/goal/model/GoalSummaryResponse.kt diff --git a/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalSummaryResponse.kt b/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalSummaryResponse.kt new file mode 100644 index 00000000..c08fe359 --- /dev/null +++ b/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalSummaryResponse.kt @@ -0,0 +1,14 @@ +package com.twix.network.model.response.goal.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class GoalSummaryResponse( + @SerialName("goalId") val goalId: Long, + @SerialName("name") val name: String, + @SerialName("icon") val icon: String, + @SerialName("repeatCycle") val repeatCycle: String, + @SerialName("startDate") val startDate: String, + @SerialName("endDate") val endDate: String?, +) From 3c5a9981ccf89d1008ae5cd47e10a411ed6795ca Mon Sep 17 00:00:00 2001 From: dogmania Date: Tue, 10 Feb 2026 02:57:36 +0900 Subject: [PATCH 30/48] =?UTF-8?q?=E2=9C=A8=20Feat:=20GoalSummaryListRespon?= =?UTF-8?q?se=20->=20List=20Mapper=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../model/response/goal/mapper/GoalMapper.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/core/network/src/main/java/com/twix/network/model/response/goal/mapper/GoalMapper.kt b/core/network/src/main/java/com/twix/network/model/response/goal/mapper/GoalMapper.kt index d4cedb73..8f10364c 100644 --- a/core/network/src/main/java/com/twix/network/model/response/goal/mapper/GoalMapper.kt +++ b/core/network/src/main/java/com/twix/network/model/response/goal/mapper/GoalMapper.kt @@ -6,10 +6,12 @@ import com.twix.domain.model.enums.RepeatCycle import com.twix.domain.model.goal.Goal import com.twix.domain.model.goal.GoalDetail import com.twix.domain.model.goal.GoalList +import com.twix.domain.model.goal.GoalSummary import com.twix.domain.model.goal.GoalVerification import com.twix.network.model.response.goal.model.GoalDetailResponse import com.twix.network.model.response.goal.model.GoalListResponse import com.twix.network.model.response.goal.model.GoalResponse +import com.twix.network.model.response.goal.model.GoalSummaryListResponse import com.twix.network.model.response.goal.model.VerificationResponse import java.time.LocalDate @@ -52,3 +54,15 @@ fun GoalDetailResponse.toDomain(): GoalDetail = endDate = endDate?.let(LocalDate::parse), createdAt = createdAt, ) + +fun GoalSummaryListResponse.toDomain(): List = + this.goals.map { + GoalSummary( + goalId = it.goalId, + name = it.name, + icon = GoalIconType.fromApi(it.icon), + repeatCycle = RepeatCycle.fromApi(it.repeatCycle), + startDate = LocalDate.parse(it.startDate), + it.endDate?.let(LocalDate::parse) + ) + } From 37b345006cddafa290ad20f3c1eddae86b329f85 Mon Sep 17 00:00:00 2001 From: dogmania Date: Tue, 10 Feb 2026 02:57:58 +0900 Subject: [PATCH 31/48] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EB=AA=A9=ED=91=9C=20?= =?UTF-8?q?=EC=83=81=EC=84=B8=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EB=B9=84=EC=A6=88=EB=8B=88=EC=8A=A4=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81=20=EC=97=B0=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../twix/goal_manage/GoalManageViewModel.kt | 17 ++++++-- .../goal_manage/model/GoalManageUiState.kt | 39 +------------------ 2 files changed, 16 insertions(+), 40 deletions(-) diff --git a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt index 09905519..d95398a4 100644 --- a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt +++ b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt @@ -102,9 +102,20 @@ class GoalManageViewModel( } private fun setSelectedDate(date: LocalDate) { - if (currentState.selectedDate == date) return - reduce { copy(selectedDate = date) } - // TODO: 새로운 리스트 조회 + if (currentState.selectedDate == date && currentState.isInitialized) return + reduce { copy(selectedDate = date, isInitialized = true) } + + fetchGoalSummaryList(date) + } + + private fun fetchGoalSummaryList(date: LocalDate) { + launchResult( + block = { goalRepository.fetchGoalSummaryList(date.toString()) }, + onSuccess = { + reduce { copy(goalSummaries = it) } + }, + onError = { emitSideEffect(GoalManageSideEffect.ShowToast(R.string.toast_goal_fetch_failed, ToastType.ERROR)) } + ) } private fun restoreGoal(removed: RemovedGoal) { diff --git a/feature/goal-manage/src/main/java/com/twix/goal_manage/model/GoalManageUiState.kt b/feature/goal-manage/src/main/java/com/twix/goal_manage/model/GoalManageUiState.kt index e8ee58a8..d001c522 100644 --- a/feature/goal-manage/src/main/java/com/twix/goal_manage/model/GoalManageUiState.kt +++ b/feature/goal-manage/src/main/java/com/twix/goal_manage/model/GoalManageUiState.kt @@ -1,51 +1,16 @@ package com.twix.goal_manage.model import androidx.compose.runtime.Immutable -import com.twix.domain.model.enums.GoalIconType -import com.twix.domain.model.enums.RepeatCycle import com.twix.domain.model.goal.GoalSummary import com.twix.ui.base.State import java.time.LocalDate @Immutable data class GoalManageUiState( + val isInitialized: Boolean = false, val selectedDate: LocalDate = LocalDate.now(), val referenceDate: LocalDate = LocalDate.now(), // 7일 달력을 생성하기 위한 레퍼런스 날짜 - val goalSummaries: List = - listOf( - GoalSummary( - goalId = 1, - name = "운동", - icon = GoalIconType.EXERCISE, - repeatCycle = RepeatCycle.DAILY, - startDate = LocalDate.now(), - endDate = LocalDate.now(), - ), - GoalSummary( - goalId = 2, - name = "물 마시기", - icon = GoalIconType.EXERCISE, - repeatCycle = RepeatCycle.DAILY, - startDate = LocalDate.now(), - endDate = null, - ), - GoalSummary( - goalId = 3, - name = "잠자기", - icon = GoalIconType.EXERCISE, - repeatCycle = RepeatCycle.DAILY, - startDate = LocalDate.now(), - endDate = LocalDate.now(), - ), - GoalSummary( - goalId = 4, - name = "운동", - icon = GoalIconType.EXERCISE, - repeatCycle = RepeatCycle.DAILY, - startDate = LocalDate.now(), - endDate = LocalDate.now(), - ), - ), + val goalSummaries: List = emptyList(), val pendingGoalIds: Set = emptySet(), // 중복 요청 방지 val openedMenuGoalId: Long? = null, // 팝업 현재 열린 goalId val endDialog: GoalDialogState? = null, From 4bc6dc8fa72d596550d8f1c556bfcac79bf68c46 Mon Sep 17 00:00:00 2001 From: dogmania Date: Tue, 10 Feb 2026 03:05:40 +0900 Subject: [PATCH 32/48] =?UTF-8?q?=F0=9F=8D=B1=20Chore:=20=EB=A6=AC?= =?UTF-8?q?=EC=86=8C=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/res/drawable/ic_empty_goal_arrow.xml | 28 ++- .../main/res/drawable/ic_empty_goal_home.xml | 176 ++++++++++++++++++ .../src/main/res/values/strings.xml | 1 + 3 files changed, 198 insertions(+), 7 deletions(-) create mode 100644 core/design-system/src/main/res/drawable/ic_empty_goal_home.xml diff --git a/core/design-system/src/main/res/drawable/ic_empty_goal_arrow.xml b/core/design-system/src/main/res/drawable/ic_empty_goal_arrow.xml index 865123a1..6eaf7f7d 100644 --- a/core/design-system/src/main/res/drawable/ic_empty_goal_arrow.xml +++ b/core/design-system/src/main/res/drawable/ic_empty_goal_arrow.xml @@ -1,9 +1,23 @@ - + android:width="120dp" + android:height="120dp" + android:viewportWidth="120" + android:viewportHeight="120"> + + + + + diff --git a/core/design-system/src/main/res/drawable/ic_empty_goal_home.xml b/core/design-system/src/main/res/drawable/ic_empty_goal_home.xml new file mode 100644 index 00000000..c1d34faf --- /dev/null +++ b/core/design-system/src/main/res/drawable/ic_empty_goal_home.xml @@ -0,0 +1,176 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/design-system/src/main/res/values/strings.xml b/core/design-system/src/main/res/values/strings.xml index 3d1cb70c..03939c10 100644 --- a/core/design-system/src/main/res/values/strings.xml +++ b/core/design-system/src/main/res/values/strings.xml @@ -35,6 +35,7 @@ 오늘 우리 목표 첫 목표를 세워볼까요? + + 버튼을 눌러 목표를 추가해보세요 코멘트추가 From 78cdd7858cdb58bcd7aec540631f2698a39e61f1 Mon Sep 17 00:00:00 2001 From: dogmania Date: Tue, 10 Feb 2026 03:05:49 +0900 Subject: [PATCH 33/48] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=95=84=EC=9D=B4=EC=BD=98=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/twix/home/component/EmptyGoalGuide.kt | 21 ++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/feature/main/src/main/java/com/twix/home/component/EmptyGoalGuide.kt b/feature/main/src/main/java/com/twix/home/component/EmptyGoalGuide.kt index 16b116ca..713e4ac3 100644 --- a/feature/main/src/main/java/com/twix/home/component/EmptyGoalGuide.kt +++ b/feature/main/src/main/java/com/twix/home/component/EmptyGoalGuide.kt @@ -30,30 +30,37 @@ fun EmptyGoalGuide(modifier: Modifier = Modifier) { verticalArrangement = Arrangement.Center, ) { Image( - painter = painterResource(R.drawable.ic_empty_face), + painter = painterResource(R.drawable.ic_empty_goal_home), contentDescription = "empty face", modifier = Modifier - .padding(horizontal = 9.dp, vertical = 6.dp) - .size(width = 34.dp, height = 40.dp), + .size(width = 181.dp, height = 111.dp), ) - Spacer(Modifier.height(10.dp)) + Spacer(Modifier.height(21.5.dp)) AppText( text = stringResource(R.string.home_empty_goal_guide), style = AppTextStyle.T2, - color = GrayColor.C200, + color = GrayColor.C400, ) - Spacer(Modifier.height(12.dp)) + Spacer(Modifier.height(5.dp)) + + AppText( + text = stringResource(R.string.home_empty_goal_content), + style = AppTextStyle.C1, + color = GrayColor.C300, + ) + + Spacer(Modifier.height(50.dp)) Image( painter = painterResource(R.drawable.ic_empty_goal_arrow), contentDescription = "empty goal arrow", modifier = Modifier - .offset(x = 32.dp), + .offset(x = 60.dp), ) } } From 01facf04890c510c3d1dfe5e0b70eef6e9a965d5 Mon Sep 17 00:00:00 2001 From: dogmania Date: Tue, 10 Feb 2026 03:06:56 +0900 Subject: [PATCH 34/48] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20ktlint?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/twix/network/model/response/goal/mapper/GoalMapper.kt | 2 +- .../model/response/goal/model/GoalSummaryListResponse.kt | 2 +- .../src/main/java/com/twix/goal_manage/GoalManageViewModel.kt | 2 +- .../src/main/java/com/twix/home/component/EmptyGoalGuide.kt | 1 - 4 files changed, 3 insertions(+), 4 deletions(-) diff --git a/core/network/src/main/java/com/twix/network/model/response/goal/mapper/GoalMapper.kt b/core/network/src/main/java/com/twix/network/model/response/goal/mapper/GoalMapper.kt index 8f10364c..1b1702dc 100644 --- a/core/network/src/main/java/com/twix/network/model/response/goal/mapper/GoalMapper.kt +++ b/core/network/src/main/java/com/twix/network/model/response/goal/mapper/GoalMapper.kt @@ -63,6 +63,6 @@ fun GoalSummaryListResponse.toDomain(): List = icon = GoalIconType.fromApi(it.icon), repeatCycle = RepeatCycle.fromApi(it.repeatCycle), startDate = LocalDate.parse(it.startDate), - it.endDate?.let(LocalDate::parse) + it.endDate?.let(LocalDate::parse), ) } diff --git a/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalSummaryListResponse.kt b/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalSummaryListResponse.kt index 8623ca80..222e76ab 100644 --- a/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalSummaryListResponse.kt +++ b/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalSummaryListResponse.kt @@ -5,5 +5,5 @@ import kotlinx.serialization.Serializable @Serializable data class GoalSummaryListResponse( - @SerialName("goals") val goals: List + @SerialName("goals") val goals: List, ) diff --git a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt index d95398a4..3d176c32 100644 --- a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt +++ b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt @@ -114,7 +114,7 @@ class GoalManageViewModel( onSuccess = { reduce { copy(goalSummaries = it) } }, - onError = { emitSideEffect(GoalManageSideEffect.ShowToast(R.string.toast_goal_fetch_failed, ToastType.ERROR)) } + onError = { emitSideEffect(GoalManageSideEffect.ShowToast(R.string.toast_goal_fetch_failed, ToastType.ERROR)) }, ) } diff --git a/feature/main/src/main/java/com/twix/home/component/EmptyGoalGuide.kt b/feature/main/src/main/java/com/twix/home/component/EmptyGoalGuide.kt index 713e4ac3..b283ff4c 100644 --- a/feature/main/src/main/java/com/twix/home/component/EmptyGoalGuide.kt +++ b/feature/main/src/main/java/com/twix/home/component/EmptyGoalGuide.kt @@ -7,7 +7,6 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment From 2fc3b7669c55497bab0a356dbd8f071a4177d2cc Mon Sep 17 00:00:00 2001 From: dogmania Date: Tue, 10 Feb 2026 03:22:53 +0900 Subject: [PATCH 35/48] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20PUT=20->?= =?UTF-8?q?=20PATCH=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 --- .../src/main/java/com/twix/network/service/GoalService.kt | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/network/src/main/java/com/twix/network/service/GoalService.kt b/core/network/src/main/java/com/twix/network/service/GoalService.kt index 0dd2e910..951cd333 100644 --- a/core/network/src/main/java/com/twix/network/service/GoalService.kt +++ b/core/network/src/main/java/com/twix/network/service/GoalService.kt @@ -8,6 +8,7 @@ import com.twix.network.model.response.goal.model.GoalSummaryListResponse import de.jensklingenberg.ktorfit.http.Body import de.jensklingenberg.ktorfit.http.DELETE import de.jensklingenberg.ktorfit.http.GET +import de.jensklingenberg.ktorfit.http.PATCH import de.jensklingenberg.ktorfit.http.POST import de.jensklingenberg.ktorfit.http.PUT import de.jensklingenberg.ktorfit.http.Path @@ -40,7 +41,7 @@ interface GoalService { @Path("goalId") goalId: Long, ) - @PUT("api/v1/goals/{goalId}/complete") + @PATCH("api/v1/goals/{goalId}/complete") suspend fun completeGoal( @Path("goalId") goalId: Long, ) From 997b513552f823699e31297fd00c05ad051a4056 Mon Sep 17 00:00:00 2001 From: dogmania Date: Tue, 10 Feb 2026 03:23:23 +0900 Subject: [PATCH 36/48] =?UTF-8?q?=E2=9C=A8=20Feat:=20GoalRefreshBus=20?= =?UTF-8?q?=EC=9D=B4=EB=B2=A4=ED=8A=B8=20=EB=B0=A9=EC=B6=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/twix/goal_manage/GoalManageViewModel.kt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt index 3d176c32..afc9af54 100644 --- a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt +++ b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt @@ -8,10 +8,12 @@ import com.twix.goal_manage.model.GoalDialogState import com.twix.goal_manage.model.GoalManageUiState import com.twix.goal_manage.model.RemovedGoal import com.twix.ui.base.BaseViewModel +import com.twix.util.bus.GoalRefreshBus import java.time.LocalDate class GoalManageViewModel( private val goalRepository: GoalRepository, + private val goalRefreshBus: GoalRefreshBus, ) : BaseViewModel(GoalManageUiState()) { override suspend fun handleIntent(intent: GoalManageIntent) { when (intent) { @@ -57,6 +59,7 @@ class GoalManageViewModel( launchResult( block = { goalRepository.completeGoal(id) }, onSuccess = { + goalRefreshBus.notifyChanged() markPending(id, false) }, onError = { @@ -77,6 +80,7 @@ class GoalManageViewModel( launchResult( block = { goalRepository.deleteGoal(id) }, onSuccess = { + goalRefreshBus.notifyChanged() markPending(id, false) }, onError = { From b309e8bbf1c3d7d3292ad926f0b7c14da72a4a99 Mon Sep 17 00:00:00 2001 From: dogmania Date: Tue, 10 Feb 2026 15:59:27 +0900 Subject: [PATCH 37/48] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EB=AA=A9=ED=91=9C=20=EC=88=98=EC=A0=95=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?Intent=20=EA=B8=B0=EB=B0=98=EC=9C=BC=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/twix/goal_manage/GoalManageIntent.kt | 4 ++++ .../src/main/java/com/twix/goal_manage/GoalManageScreen.kt | 3 ++- .../main/java/com/twix/goal_manage/GoalManageSideEffect.kt | 4 ++++ .../src/main/java/com/twix/goal_manage/GoalManageViewModel.kt | 4 ++++ 4 files changed, 14 insertions(+), 1 deletion(-) diff --git a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageIntent.kt b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageIntent.kt index d17f9635..86e8f6f7 100644 --- a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageIntent.kt +++ b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageIntent.kt @@ -38,4 +38,8 @@ sealed interface GoalManageIntent : Intent { ) : GoalManageIntent data object DismissDeleteDialog : GoalManageIntent + + data class EditGoal( + val goalId: Long, + ) : GoalManageIntent } diff --git a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageScreen.kt b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageScreen.kt index 79e3e869..a481b267 100644 --- a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageScreen.kt +++ b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageScreen.kt @@ -80,6 +80,7 @@ fun GoalManageRoute( ObserveAsEvents(viewModel.sideEffect) { effect -> when (effect) { is GoalManageSideEffect.ShowToast -> toastManager.tryShow(ToastData(currentContext.getString(effect.resId), effect.type)) + is GoalManageSideEffect.NavigateToGoalEditor -> navigateToGoalEditor(effect.goalId) } } @@ -104,7 +105,7 @@ fun GoalManageRoute( onDismissEndDialog = { viewModel.dispatch(GoalManageIntent.DismissEndDialog) }, onDismissDeleteDialog = { viewModel.dispatch(GoalManageIntent.DismissDeleteDialog) }, // 수정 - onEdit = navigateToGoalEditor, + onEdit = { viewModel.dispatch(GoalManageIntent.EditGoal(it)) }, ) } diff --git a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageSideEffect.kt b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageSideEffect.kt index df243fc9..3420815d 100644 --- a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageSideEffect.kt +++ b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageSideEffect.kt @@ -9,4 +9,8 @@ sealed interface GoalManageSideEffect : SideEffect { @param:StringRes val resId: Int, val type: ToastType, ) : GoalManageSideEffect + + data class NavigateToGoalEditor( + val goalId: Long, + ) : GoalManageSideEffect } diff --git a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt index afc9af54..e87ae2e8 100644 --- a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt +++ b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt @@ -46,6 +46,10 @@ class GoalManageViewModel( } } GoalManageIntent.DismissDeleteDialog -> reduce { copy(deleteDialog = null) } + is GoalManageIntent.EditGoal -> { + reduce { copy(openedMenuGoalId = null) } + emitSideEffect(GoalManageSideEffect.NavigateToGoalEditor(intent.goalId)) + } } } From b13dea1c4a6802d5c8f86b8ecad6660380c9a527 Mon Sep 17 00:00:00 2001 From: dogmania Date: Tue, 10 Feb 2026 17:10:45 +0900 Subject: [PATCH 38/48] =?UTF-8?q?=F0=9F=8D=B1=20Chore:=20string=20?= =?UTF-8?q?=EB=A6=AC=EC=86=8C=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/design-system/src/main/res/values/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/core/design-system/src/main/res/values/strings.xml b/core/design-system/src/main/res/values/strings.xml index 03939c10..5c055ff5 100644 --- a/core/design-system/src/main/res/values/strings.xml +++ b/core/design-system/src/main/res/values/strings.xml @@ -64,6 +64,7 @@ 목표 수정에 실패했습니다. 목표 삭제에 실패했습니다. 목표 완료에 실패했습니다. + 목표를 입력해주세요. %s\n목표를 이루셨나요? From 4b5485a87147a0052e1963f11d0f5f9e8211f13f Mon Sep 17 00:00:00 2001 From: dogmania Date: Tue, 10 Feb 2026 17:11:15 +0900 Subject: [PATCH 39/48] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EB=AA=A9=ED=91=9C=20?= =?UTF-8?q?=EB=AF=B8=EC=9E=85=EB=A0=A5=20=EC=8B=9C=20=ED=86=A0=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EB=A0=8C=EB=8D=94=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/twix/goal_editor/GoalEditorViewModel.kt | 13 ++++++------- .../com/twix/goal_editor/model/GoalEditorUiState.kt | 2 +- 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorViewModel.kt b/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorViewModel.kt index 5fcb06ce..3c63c53d 100644 --- a/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorViewModel.kt +++ b/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorViewModel.kt @@ -1,6 +1,5 @@ package com.twix.goal_editor -import androidx.lifecycle.viewModelScope import com.twix.designsystem.R import com.twix.designsystem.components.toast.model.ToastType import com.twix.domain.model.enums.GoalIconType @@ -12,7 +11,6 @@ import com.twix.domain.repository.GoalRepository import com.twix.goal_editor.model.GoalEditorUiState import com.twix.ui.base.BaseViewModel import com.twix.util.bus.GoalRefreshBus -import kotlinx.coroutines.launch import java.time.LocalDate class GoalEditorViewModel( @@ -80,13 +78,14 @@ class GoalEditorViewModel( } } - private fun save(id: Long) { - if (!currentState.isEnabled) return + private suspend fun save(id: Long) { + if (!currentState.isEnabled) { + emitSideEffect(GoalEditorSideEffect.ShowToast(R.string.toast_input_goal_title, ToastType.ERROR)) + return + } if (currentState.endDateEnabled && currentState.endDate.isBefore(currentState.startDate)) { - viewModelScope.launch { - emitSideEffect(GoalEditorSideEffect.ShowToast(R.string.toast_end_date_before_start_date, ToastType.ERROR)) - } + emitSideEffect(GoalEditorSideEffect.ShowToast(R.string.toast_end_date_before_start_date, ToastType.ERROR)) return } diff --git a/feature/goal-editor/src/main/java/com/twix/goal_editor/model/GoalEditorUiState.kt b/feature/goal-editor/src/main/java/com/twix/goal_editor/model/GoalEditorUiState.kt index 8add180f..21841e59 100644 --- a/feature/goal-editor/src/main/java/com/twix/goal_editor/model/GoalEditorUiState.kt +++ b/feature/goal-editor/src/main/java/com/twix/goal_editor/model/GoalEditorUiState.kt @@ -17,5 +17,5 @@ data class GoalEditorUiState( val endDate: LocalDate = LocalDate.now(), ) : State { val isEnabled: Boolean - get() = goalTitle.isNotBlank() && (selectedRepeatCycle == RepeatCycle.DAILY || repeatCount > 0) + get() = goalTitle.isNotBlank() } From 1cacb7f88f192fbfa713c04787e445772845c0e5 Mon Sep 17 00:00:00 2001 From: dogmania Date: Tue, 10 Feb 2026 17:39:02 +0900 Subject: [PATCH 40/48] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=ED=82=A4=EB=B3=B4=EB=93=9C=20=EA=B0=90=EC=A7=80=EB=A5=BC=20?= =?UTF-8?q?=ED=86=B5=ED=95=B4=EC=84=9C=20=EB=AA=A9=ED=91=9C=EB=AA=85=20com?= =?UTF-8?q?mit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../goal_editor/component/GoalTextField.kt | 52 ++++++++++++++++--- 1 file changed, 45 insertions(+), 7 deletions(-) diff --git a/feature/goal-editor/src/main/java/com/twix/goal_editor/component/GoalTextField.kt b/feature/goal-editor/src/main/java/com/twix/goal_editor/component/GoalTextField.kt index 48669b98..f49883a3 100644 --- a/feature/goal-editor/src/main/java/com/twix/goal_editor/component/GoalTextField.kt +++ b/feature/goal-editor/src/main/java/com/twix/goal_editor/component/GoalTextField.kt @@ -1,17 +1,23 @@ package com.twix.goal_editor.component +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.ime import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier import androidx.compose.ui.focus.onFocusChanged +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.ImeAction @@ -19,15 +25,43 @@ import androidx.compose.ui.unit.dp import com.twix.designsystem.R import com.twix.designsystem.components.text_field.UnderlineTextField +@OptIn(ExperimentalLayoutApi::class) @Composable fun GoalTextField( value: String, onCommitTitle: (String) -> Unit, ) { val focusManager = LocalFocusManager.current + val density = LocalDensity.current var internalValue by rememberSaveable(value) { mutableStateOf(value) } - // 초기에 무의미하게 commit 되는 것을 방지하는 상태 변수 - var wasFocused by remember { mutableStateOf(false) } + var isFocused by remember { mutableStateOf(false) } + var lastCommitted by remember(value) { mutableStateOf(value.trim()) } + + fun commitIfChanged() { + val trimmed = internalValue.trim() + if (trimmed != lastCommitted) { + lastCommitted = trimmed + onCommitTitle(trimmed) + } + } + + val imeVisibleState = + remember { + mutableStateOf(false) + } + + imeVisibleState.value = WindowInsets.ime.getBottom(density) > 0 + LaunchedEffect(isFocused) { + var prev = imeVisibleState.value + snapshotFlow { imeVisibleState.value } + .collect { now -> + if (prev && !now && isFocused) { + commitIfChanged() + focusManager.clearFocus(force = true) + } + prev = now + } + } UnderlineTextField( modifier = @@ -35,15 +69,19 @@ fun GoalTextField( .padding(horizontal = 20.dp) .fillMaxWidth() .onFocusChanged { state -> - if (wasFocused && !state.isFocused) { - onCommitTitle(internalValue.trim()) - } - wasFocused = state.isFocused + isFocused = state.isFocused + if (!state.isFocused) commitIfChanged() }, value = internalValue, placeHolder = stringResource(R.string.goal_editor_text_field_placeholder), onValueChange = { internalValue = it }, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), - keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() }), + keyboardActions = + KeyboardActions( + onDone = { + commitIfChanged() + focusManager.clearFocus(force = true) + }, + ), ) } From c0b6d4cbbfa56dc4b5e401752266d1791f769db7 Mon Sep 17 00:00:00 2001 From: dogmania Date: Tue, 10 Feb 2026 17:59:45 +0900 Subject: [PATCH 41/48] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20EmptyGoa?= =?UTF-8?q?lGuide=20=EA=B3=B5=ED=86=B5=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=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 --- .../components/goal}/EmptyGoalGuide.kt | 42 +++++++++++-------- .../src/main/java/com/twix/home/HomeScreen.kt | 7 +++- 2 files changed, 29 insertions(+), 20 deletions(-) rename {feature/main/src/main/java/com/twix/home/component => core/design-system/src/main/java/com/twix/designsystem/components/goal}/EmptyGoalGuide.kt (63%) diff --git a/feature/main/src/main/java/com/twix/home/component/EmptyGoalGuide.kt b/core/design-system/src/main/java/com/twix/designsystem/components/goal/EmptyGoalGuide.kt similarity index 63% rename from feature/main/src/main/java/com/twix/home/component/EmptyGoalGuide.kt rename to core/design-system/src/main/java/com/twix/designsystem/components/goal/EmptyGoalGuide.kt index b283ff4c..a844a399 100644 --- a/feature/main/src/main/java/com/twix/home/component/EmptyGoalGuide.kt +++ b/core/design-system/src/main/java/com/twix/designsystem/components/goal/EmptyGoalGuide.kt @@ -1,4 +1,4 @@ -package com.twix.home.component +package com.twix.designsystem.components.goal import androidx.compose.foundation.Image import androidx.compose.foundation.layout.Arrangement @@ -20,7 +20,11 @@ import com.twix.designsystem.theme.GrayColor import com.twix.domain.model.enums.AppTextStyle @Composable -fun EmptyGoalGuide(modifier: Modifier = Modifier) { +fun EmptyGoalGuide( + modifier: Modifier = Modifier, + text: String, + isDetail: Boolean = false, +) { Column( modifier = modifier @@ -36,30 +40,32 @@ fun EmptyGoalGuide(modifier: Modifier = Modifier) { .size(width = 181.dp, height = 111.dp), ) - Spacer(Modifier.height(21.5.dp)) + Spacer(Modifier.height(22.dp)) AppText( - text = stringResource(R.string.home_empty_goal_guide), + text = text, style = AppTextStyle.T2, color = GrayColor.C400, ) - Spacer(Modifier.height(5.dp)) + if (!isDetail) { + Spacer(Modifier.height(5.dp)) - AppText( - text = stringResource(R.string.home_empty_goal_content), - style = AppTextStyle.C1, - color = GrayColor.C300, - ) + AppText( + text = stringResource(R.string.home_empty_goal_content), + style = AppTextStyle.C1, + color = GrayColor.C300, + ) - Spacer(Modifier.height(50.dp)) + Spacer(Modifier.height(50.dp)) - Image( - painter = painterResource(R.drawable.ic_empty_goal_arrow), - contentDescription = "empty goal arrow", - modifier = - Modifier - .offset(x = 60.dp), - ) + Image( + painter = painterResource(R.drawable.ic_empty_goal_arrow), + contentDescription = "empty goal arrow", + modifier = + Modifier + .offset(x = 60.dp), + ) + } } } diff --git a/feature/main/src/main/java/com/twix/home/HomeScreen.kt b/feature/main/src/main/java/com/twix/home/HomeScreen.kt index d583af7f..dd954f13 100644 --- a/feature/main/src/main/java/com/twix/home/HomeScreen.kt +++ b/feature/main/src/main/java/com/twix/home/HomeScreen.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.twix.designsystem.R import com.twix.designsystem.components.calendar.WeeklyCalendar +import com.twix.designsystem.components.goal.EmptyGoalGuide import com.twix.designsystem.components.goal.GoalCardFrame import com.twix.designsystem.components.goal.GoalCheckIndicator import com.twix.designsystem.components.text.AppText @@ -37,7 +38,6 @@ import com.twix.designsystem.theme.GrayColor import com.twix.domain.model.enums.AppTextStyle import com.twix.domain.model.goal.Goal import com.twix.domain.model.goal.checkState -import com.twix.home.component.EmptyGoalGuide import com.twix.home.component.GoalVerifications import com.twix.home.component.HomeTopBar import com.twix.home.model.HomeUiState @@ -109,7 +109,10 @@ fun HomeScreen( Spacer(Modifier.height(12.dp)) if (uiState.goalList.goals.isEmpty()) { - EmptyGoalGuide(modifier = Modifier.weight(1f)) + EmptyGoalGuide( + modifier = Modifier.weight(1f), + text = stringResource(R.string.home_empty_goal_guide), + ) } else { GoalList( modifier = From df9db8d3c733f725d4084a66f027a44221ab3b66 Mon Sep 17 00:00:00 2001 From: dogmania Date: Tue, 10 Feb 2026 17:59:57 +0900 Subject: [PATCH 42/48] =?UTF-8?q?=F0=9F=8D=B1=20Chore:=20string=20?= =?UTF-8?q?=EB=A6=AC=EC=86=8C=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/design-system/src/main/res/values/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/core/design-system/src/main/res/values/strings.xml b/core/design-system/src/main/res/values/strings.xml index 5c055ff5..ceedeffc 100644 --- a/core/design-system/src/main/res/values/strings.xml +++ b/core/design-system/src/main/res/values/strings.xml @@ -56,6 +56,7 @@ 오늘 우리 목표 다음 우리 목표 %s년 %02d월 %02d일 + 아직 목표가 없어요! 종료 날짜가 시작 날짜보다 이전입니다. From 74dc8db86956c281829e51a3dc50aaaaeafbe144 Mon Sep 17 00:00:00 2001 From: dogmania Date: Tue, 10 Feb 2026 18:00:13 +0900 Subject: [PATCH 43/48] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EA=B0=80=20=EB=B9=84=EC=96=B4=EC=9E=88=EB=8A=94=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=20EmptyGoalGuide=20=EB=A0=8C=EB=8D=94?= =?UTF-8?q?=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/twix/goal_manage/GoalManageScreen.kt | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageScreen.kt b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageScreen.kt index a481b267..07954b6d 100644 --- a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageScreen.kt +++ b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageScreen.kt @@ -39,6 +39,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle import com.twix.designsystem.R import com.twix.designsystem.components.calendar.WeeklyCalendar import com.twix.designsystem.components.dialog.CommonDialog +import com.twix.designsystem.components.goal.EmptyGoalGuide import com.twix.designsystem.components.goal.GoalCardFrame import com.twix.designsystem.components.popup.CommonPopup import com.twix.designsystem.components.popup.CommonPopupDivider @@ -173,20 +174,30 @@ private fun GoalManageScreen( onNextWeek = onNextWeek, ) - GoalSummaryList( - modifier = - Modifier - .padding(horizontal = 20.dp) - .weight(1f), - summaryList = uiState.goalSummaries, - openedMenuGoalId = openedMenuGoalId, - pendingIds = pendingIds, - onOpenMenu = onOpenMenu, - onCloseMenu = onCloseMenu, - onEdit = onEdit, - onRequestDelete = onRequestDelete, - onRequestEnd = onRequestEnd, - ) + if (uiState.goalSummaries.isEmpty()) { + EmptyGoalGuide( + modifier = + Modifier + .weight(1f), + text = stringResource(R.string.goal_detail_empty_goal_guide), + isDetail = true, + ) + } else { + GoalSummaryList( + modifier = + Modifier + .padding(horizontal = 20.dp) + .weight(1f), + summaryList = uiState.goalSummaries, + openedMenuGoalId = openedMenuGoalId, + pendingIds = pendingIds, + onOpenMenu = onOpenMenu, + onCloseMenu = onCloseMenu, + onEdit = onEdit, + onRequestDelete = onRequestDelete, + onRequestEnd = onRequestEnd, + ) + } } CommonDialog( From df64bcbb79b709b424c71347c32d449dc3e2b45c Mon Sep 17 00:00:00 2001 From: dogmania Date: Tue, 10 Feb 2026 20:37:40 +0900 Subject: [PATCH 44/48] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=9C=84=EC=B9=98=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/twix/goal_manage/GoalManageScreen.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageScreen.kt b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageScreen.kt index 07954b6d..4ff3c63c 100644 --- a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageScreen.kt +++ b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageScreen.kt @@ -178,7 +178,7 @@ private fun GoalManageScreen( EmptyGoalGuide( modifier = Modifier - .weight(1f), + .padding(top = 128.dp), text = stringResource(R.string.goal_detail_empty_goal_guide), isDetail = true, ) From 615e04dac6aa332ac45055fe056d06a9b5d6be3f Mon Sep 17 00:00:00 2001 From: dogmania Date: Wed, 11 Feb 2026 22:45:24 +0900 Subject: [PATCH 45/48] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=EC=84=9C=EB=B2=84=20=EC=9D=91=EB=8B=B5=20=ED=95=84=EB=93=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../network/model/response/goal/model/GoalDetailResponse.kt | 2 +- .../com/twix/network/model/response/goal/model/GoalResponse.kt | 2 +- .../network/model/response/goal/model/GoalSummaryResponse.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalDetailResponse.kt b/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalDetailResponse.kt index 521f671f..00c8a7c1 100644 --- a/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalDetailResponse.kt +++ b/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalDetailResponse.kt @@ -6,7 +6,7 @@ import kotlinx.serialization.Serializable @Serializable data class GoalDetailResponse( @SerialName("goalId") val goalId: Long, - @SerialName("name") val name: String, + @SerialName("goalName") val name: String, @SerialName("icon") val icon: String, @SerialName("repeatCycle") val repeatCycle: String, @SerialName("repeatCount") val repeatCount: Int, diff --git a/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalResponse.kt b/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalResponse.kt index e35d9047..a8a269b4 100644 --- a/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalResponse.kt +++ b/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalResponse.kt @@ -6,7 +6,7 @@ import kotlinx.serialization.Serializable @Serializable data class GoalResponse( @SerialName("goalId") val goalId: Long, - @SerialName("name") val name: String, + @SerialName("goalName") val name: String, @SerialName("icon") val icon: String, @SerialName("repeatCycle") val repeatCycle: String, @SerialName("myCompleted") val myCompleted: Boolean, diff --git a/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalSummaryResponse.kt b/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalSummaryResponse.kt index c08fe359..9fd0a11b 100644 --- a/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalSummaryResponse.kt +++ b/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalSummaryResponse.kt @@ -6,7 +6,7 @@ import kotlinx.serialization.Serializable @Serializable data class GoalSummaryResponse( @SerialName("goalId") val goalId: Long, - @SerialName("name") val name: String, + @SerialName("goalName") val name: String, @SerialName("icon") val icon: String, @SerialName("repeatCycle") val repeatCycle: String, @SerialName("startDate") val startDate: String, From eb7079877432dd0fde69bdd454f394afd31c8856 Mon Sep 17 00:00:00 2001 From: dogmania Date: Wed, 11 Feb 2026 23:05:52 +0900 Subject: [PATCH 46/48] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=EB=AA=85=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../twix/network/model/request/goal/model/CreateGoalRequest.kt | 2 +- .../twix/network/model/request/goal/model/UpdateGoalRequest.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/network/src/main/java/com/twix/network/model/request/goal/model/CreateGoalRequest.kt b/core/network/src/main/java/com/twix/network/model/request/goal/model/CreateGoalRequest.kt index f5c8c602..703ef36e 100644 --- a/core/network/src/main/java/com/twix/network/model/request/goal/model/CreateGoalRequest.kt +++ b/core/network/src/main/java/com/twix/network/model/request/goal/model/CreateGoalRequest.kt @@ -5,7 +5,7 @@ import kotlinx.serialization.Serializable @Serializable data class CreateGoalRequest( - @SerialName("name") val name: String, + @SerialName("goalName") val name: String, @SerialName("icon") val icon: String, @SerialName("repeatCycle") val repeatCycle: String, @SerialName("repeatCount") val repeatCount: Int, diff --git a/core/network/src/main/java/com/twix/network/model/request/goal/model/UpdateGoalRequest.kt b/core/network/src/main/java/com/twix/network/model/request/goal/model/UpdateGoalRequest.kt index 7a466555..db3fd8be 100644 --- a/core/network/src/main/java/com/twix/network/model/request/goal/model/UpdateGoalRequest.kt +++ b/core/network/src/main/java/com/twix/network/model/request/goal/model/UpdateGoalRequest.kt @@ -5,7 +5,7 @@ import kotlinx.serialization.Serializable @Serializable data class UpdateGoalRequest( - @SerialName("name") val name: String, + @SerialName("goalName") val name: String, @SerialName("icon") val icon: String, @SerialName("repeatCycle") val repeatCycle: String, @SerialName("repeatCount") val repeatCount: Int, From d451302bdd3db750b01f3f62569501cfee17011d Mon Sep 17 00:00:00 2001 From: dogmania Date: Wed, 11 Feb 2026 23:06:16 +0900 Subject: [PATCH 47/48] =?UTF-8?q?=E2=9C=A8=20Feat:=20=EB=AA=A9=ED=91=9C=20?= =?UTF-8?q?=ED=8E=B8=EC=A7=91=20=ED=99=94=EB=A9=B4=20=EC=A0=84=EC=9A=A9=20?= =?UTF-8?q?=EB=A6=AC=ED=94=84=EB=A0=88=EC=89=AC=20=EC=9D=B4=EB=B2=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/com/twix/util/bus/GoalRefreshBus.kt | 15 ++++++++++++--- .../com/twix/goal_editor/GoalEditorViewModel.kt | 5 +++-- .../com/twix/goal_manage/GoalManageViewModel.kt | 14 ++++++++++++-- .../src/main/java/com/twix/home/HomeViewModel.kt | 2 +- 4 files changed, 28 insertions(+), 8 deletions(-) diff --git a/core/util/src/main/java/com/twix/util/bus/GoalRefreshBus.kt b/core/util/src/main/java/com/twix/util/bus/GoalRefreshBus.kt index c2fd72a6..de87e53c 100644 --- a/core/util/src/main/java/com/twix/util/bus/GoalRefreshBus.kt +++ b/core/util/src/main/java/com/twix/util/bus/GoalRefreshBus.kt @@ -4,12 +4,21 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.SharedFlow class GoalRefreshBus { - private val _events = + // 홈 화면 목표 리프레쉬 이벤트 + private val _goalEvents = MutableSharedFlow( replay = 0, extraBufferCapacity = 1, ) - val events: SharedFlow = _events + // 편집 화면 목표 리프레쉬 이벤트 + private val _goalSummariesEvents = + MutableSharedFlow( + replay = 0, + extraBufferCapacity = 1, + ) + val goalEvents: SharedFlow = _goalEvents + val goalSummariesEvents: SharedFlow = _goalSummariesEvents - fun notifyChanged() = _events.tryEmit(Unit) + fun notifyGoalListChanged() = _goalEvents.tryEmit(Unit) + fun notifyGoalSummariesChanged() = _goalSummariesEvents.tryEmit(Unit) } diff --git a/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorViewModel.kt b/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorViewModel.kt index 3c63c53d..39710943 100644 --- a/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorViewModel.kt +++ b/feature/goal-editor/src/main/java/com/twix/goal_editor/GoalEditorViewModel.kt @@ -93,7 +93,7 @@ class GoalEditorViewModel( launchResult( block = { goalRepository.createGoal(currentState.toCreateParam()) }, onSuccess = { - goalRefreshBus.notifyChanged() + goalRefreshBus.notifyGoalListChanged() tryEmitSideEffect(GoalEditorSideEffect.NavigateToHome) }, onError = { emitSideEffect(GoalEditorSideEffect.ShowToast(R.string.toast_create_goal_failed, ToastType.ERROR)) }, @@ -102,7 +102,8 @@ class GoalEditorViewModel( launchResult( block = { goalRepository.updateGoal(currentState.toUpdateParam(id)) }, onSuccess = { - goalRefreshBus.notifyChanged() + goalRefreshBus.notifyGoalListChanged() + goalRefreshBus.notifyGoalSummariesChanged() tryEmitSideEffect(GoalEditorSideEffect.NavigateToHome) }, onError = { emitSideEffect(GoalEditorSideEffect.ShowToast(R.string.toast_update_goal_failed, ToastType.ERROR)) }, diff --git a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt index e87ae2e8..ad8fa410 100644 --- a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt +++ b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt @@ -1,5 +1,6 @@ package com.twix.goal_manage +import androidx.lifecycle.viewModelScope import com.twix.designsystem.R import com.twix.designsystem.components.toast.model.ToastType import com.twix.domain.model.enums.WeekNavigation @@ -9,12 +10,21 @@ import com.twix.goal_manage.model.GoalManageUiState import com.twix.goal_manage.model.RemovedGoal import com.twix.ui.base.BaseViewModel import com.twix.util.bus.GoalRefreshBus +import kotlinx.coroutines.launch import java.time.LocalDate class GoalManageViewModel( private val goalRepository: GoalRepository, private val goalRefreshBus: GoalRefreshBus, ) : BaseViewModel(GoalManageUiState()) { + init { + viewModelScope.launch { + goalRefreshBus.goalSummariesEvents.collect { + fetchGoalSummaryList(currentState.selectedDate) + } + } + } + override suspend fun handleIntent(intent: GoalManageIntent) { when (intent) { is GoalManageIntent.EndGoal -> endGoal(intent.id) @@ -63,7 +73,7 @@ class GoalManageViewModel( launchResult( block = { goalRepository.completeGoal(id) }, onSuccess = { - goalRefreshBus.notifyChanged() + goalRefreshBus.notifyGoalListChanged() markPending(id, false) }, onError = { @@ -84,7 +94,7 @@ class GoalManageViewModel( launchResult( block = { goalRepository.deleteGoal(id) }, onSuccess = { - goalRefreshBus.notifyChanged() + goalRefreshBus.notifyGoalListChanged() markPending(id, false) }, onError = { diff --git a/feature/main/src/main/java/com/twix/home/HomeViewModel.kt b/feature/main/src/main/java/com/twix/home/HomeViewModel.kt index db68185d..dec304d2 100644 --- a/feature/main/src/main/java/com/twix/home/HomeViewModel.kt +++ b/feature/main/src/main/java/com/twix/home/HomeViewModel.kt @@ -27,7 +27,7 @@ class HomeViewModel( fetchGoalList() viewModelScope.launch { - goalRefreshBus.events.collect { + goalRefreshBus.goalEvents.collect { fetchGoalList() } } From b572e973cd869ec8ca9b752018e30cff544a39d9 Mon Sep 17 00:00:00 2001 From: dogmania Date: Wed, 11 Feb 2026 23:10:19 +0900 Subject: [PATCH 48/48] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor:=20ktlint?= =?UTF-8?q?=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/util/src/main/java/com/twix/util/bus/GoalRefreshBus.kt | 2 ++ .../src/main/java/com/twix/goal_manage/GoalManageViewModel.kt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/core/util/src/main/java/com/twix/util/bus/GoalRefreshBus.kt b/core/util/src/main/java/com/twix/util/bus/GoalRefreshBus.kt index de87e53c..59630e06 100644 --- a/core/util/src/main/java/com/twix/util/bus/GoalRefreshBus.kt +++ b/core/util/src/main/java/com/twix/util/bus/GoalRefreshBus.kt @@ -10,6 +10,7 @@ class GoalRefreshBus { replay = 0, extraBufferCapacity = 1, ) + // 편집 화면 목표 리프레쉬 이벤트 private val _goalSummariesEvents = MutableSharedFlow( @@ -20,5 +21,6 @@ class GoalRefreshBus { val goalSummariesEvents: SharedFlow = _goalSummariesEvents fun notifyGoalListChanged() = _goalEvents.tryEmit(Unit) + fun notifyGoalSummariesChanged() = _goalSummariesEvents.tryEmit(Unit) } diff --git a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt index ad8fa410..e086c040 100644 --- a/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt +++ b/feature/goal-manage/src/main/java/com/twix/goal_manage/GoalManageViewModel.kt @@ -24,7 +24,7 @@ class GoalManageViewModel( } } } - + override suspend fun handleIntent(intent: GoalManageIntent) { when (intent) { is GoalManageIntent.EndGoal -> endGoal(intent.id)