diff --git a/build-logic/convention/src/main/kotlin/com/twix/convention/FeatureConventionPlugin.kt b/build-logic/convention/src/main/kotlin/com/twix/convention/FeatureConventionPlugin.kt index 8bf351be..8597f405 100644 --- a/build-logic/convention/src/main/kotlin/com/twix/convention/FeatureConventionPlugin.kt +++ b/build-logic/convention/src/main/kotlin/com/twix/convention/FeatureConventionPlugin.kt @@ -15,6 +15,7 @@ class FeatureConventionPlugin : BuildLogicConventionPlugin({ implementation(project(":core:design-system")) implementation(project(":core:navigation")) implementation(project(":core:ui")) + implementation(project(":core:result")) implementation(project(":domain")) } }) diff --git a/core/design-system/src/main/java/com/twix/designsystem/extension/GoalIconType.kt b/core/design-system/src/main/java/com/twix/designsystem/extension/GoalIconType.kt new file mode 100644 index 00000000..65e04aa3 --- /dev/null +++ b/core/design-system/src/main/java/com/twix/designsystem/extension/GoalIconType.kt @@ -0,0 +1,16 @@ +package com.twix.designsystem.extension + +import com.twix.designsystem.R +import com.twix.domain.model.enums.GoalIconType + +fun GoalIconType.toRes(): Int = + when (this) { + GoalIconType.DEFAULT -> R.drawable.ic_default + GoalIconType.CLEAN -> R.drawable.ic_clean + GoalIconType.EXERCISE -> R.drawable.ic_exercise + GoalIconType.BOOK -> R.drawable.ic_book + GoalIconType.PENCIL -> R.drawable.ic_pencil + GoalIconType.HEALTH -> R.drawable.ic_health + GoalIconType.HEART -> R.drawable.ic_heart + GoalIconType.LAPTOP -> R.drawable.ic_laptop + } diff --git a/core/design-system/src/main/res/drawable/ic_checked_me.xml b/core/design-system/src/main/res/drawable/ic_checked_me.xml new file mode 100644 index 00000000..992974fe --- /dev/null +++ b/core/design-system/src/main/res/drawable/ic_checked_me.xml @@ -0,0 +1,18 @@ + + + + diff --git a/core/design-system/src/main/res/drawable/ic_checked_you.xml b/core/design-system/src/main/res/drawable/ic_checked_you.xml new file mode 100644 index 00000000..495fd1ab --- /dev/null +++ b/core/design-system/src/main/res/drawable/ic_checked_you.xml @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/core/design-system/src/main/res/drawable/ic_unchecked_me.xml b/core/design-system/src/main/res/drawable/ic_unchecked_me.xml new file mode 100644 index 00000000..215461a0 --- /dev/null +++ b/core/design-system/src/main/res/drawable/ic_unchecked_me.xml @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/core/design-system/src/main/res/drawable/ic_unchecked_you.xml b/core/design-system/src/main/res/drawable/ic_unchecked_you.xml new file mode 100644 index 00000000..4b9defc5 --- /dev/null +++ b/core/design-system/src/main/res/drawable/ic_unchecked_you.xml @@ -0,0 +1,18 @@ + + + + diff --git a/core/design-system/src/main/res/values/strings.xml b/core/design-system/src/main/res/values/strings.xml index 657bea88..c9901cfa 100644 --- a/core/design-system/src/main/res/values/strings.xml +++ b/core/design-system/src/main/res/values/strings.xml @@ -37,5 +37,6 @@ 종료 날짜가 시작 날짜보다 이전입니다. + 목표 조회에 실패했습니다. \ No newline at end of file diff --git a/core/network/build.gradle.kts b/core/network/build.gradle.kts index 5a301264..ae951421 100644 --- a/core/network/build.gradle.kts +++ b/core/network/build.gradle.kts @@ -42,6 +42,7 @@ android { dependencies { implementation(projects.core.token) implementation(projects.core.result) + implementation(projects.domain) implementation(libs.bundles.ktor) implementation(libs.ktorfit.lib) diff --git a/core/network/src/main/java/com/twix/network/di/ApiServiceModule.kt b/core/network/src/main/java/com/twix/network/di/ApiServiceModule.kt index f911f84f..254a0387 100644 --- a/core/network/src/main/java/com/twix/network/di/ApiServiceModule.kt +++ b/core/network/src/main/java/com/twix/network/di/ApiServiceModule.kt @@ -1,8 +1,10 @@ package com.twix.network.di import com.twix.network.service.AuthService +import com.twix.network.service.GoalService import com.twix.network.service.OnboardingService import com.twix.network.service.createAuthService +import com.twix.network.service.createGoalService import com.twix.network.service.createOnboardingService import de.jensklingenberg.ktorfit.Ktorfit import org.koin.dsl.module @@ -15,4 +17,7 @@ internal val apiServiceModule = single { get().createAuthService() } + single { + get().createGoalService() + } } 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 new file mode 100644 index 00000000..715c671d --- /dev/null +++ b/core/network/src/main/java/com/twix/network/model/response/goal/mapper/GoalMapper.kt @@ -0,0 +1,38 @@ +package com.twix.network.model.response.goal.mapper + +import com.twix.domain.model.enums.GoalIconType +import com.twix.domain.model.enums.RepeatCycle +import com.twix.domain.model.goal.Goal +import com.twix.domain.model.goal.GoalList +import com.twix.domain.model.goal.GoalVerification +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 + +fun GoalListResponse.toDomain(): GoalList = + GoalList( + completedCount = completedCount, + totalCount = totalCount, + goals = goals.map { it.toDomain() }, + ) + +fun GoalResponse.toDomain(): Goal = + Goal( + goalId = goalId, + name = name, + icon = GoalIconType.fromApi(icon), + repeatCycle = RepeatCycle.fromApi(repeatCycle), + myCompleted = myCompleted, + partnerCompleted = partnerCompleted, + myVerification = myVerification?.toDomainOrNull(), + partnerVerification = partnerVerification?.toDomainOrNull(), + ) + +fun VerificationResponse.toDomainOrNull(): GoalVerification? = + GoalVerification( + photologId = photologId, + imageUrl = imageUrl, + comment = comment, + reaction = reaction, + uploadedAt = uploadedAt, + ) diff --git a/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalListResponse.kt b/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalListResponse.kt new file mode 100644 index 00000000..caa4f0aa --- /dev/null +++ b/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalListResponse.kt @@ -0,0 +1,11 @@ +package com.twix.network.model.response.goal.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class GoalListResponse( + @SerialName("completedCount") val completedCount: Int, + @SerialName("totalCount") val totalCount: Int, + @SerialName("goals") val goals: List, +) 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 new file mode 100644 index 00000000..e35d9047 --- /dev/null +++ b/core/network/src/main/java/com/twix/network/model/response/goal/model/GoalResponse.kt @@ -0,0 +1,16 @@ +package com.twix.network.model.response.goal.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class GoalResponse( + @SerialName("goalId") val goalId: Long, + @SerialName("name") val name: String, + @SerialName("icon") val icon: String, + @SerialName("repeatCycle") val repeatCycle: String, + @SerialName("myCompleted") val myCompleted: Boolean, + @SerialName("partnerCompleted") val partnerCompleted: Boolean, + @SerialName("myVerification") val myVerification: VerificationResponse? = null, + @SerialName("partnerVerification") val partnerVerification: VerificationResponse? = null, +) diff --git a/core/network/src/main/java/com/twix/network/model/response/goal/model/VerificationResponse.kt b/core/network/src/main/java/com/twix/network/model/response/goal/model/VerificationResponse.kt new file mode 100644 index 00000000..385048dc --- /dev/null +++ b/core/network/src/main/java/com/twix/network/model/response/goal/model/VerificationResponse.kt @@ -0,0 +1,13 @@ +package com.twix.network.model.response.goal.model + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class VerificationResponse( + @SerialName("photologId") val photologId: Long, + @SerialName("imageUrl") val imageUrl: String, + @SerialName("comment") val comment: String? = null, + @SerialName("reaction") val reaction: String? = null, + @SerialName("uploadedAt") val uploadedAt: String, // yyyy-mm-dd +) diff --git a/core/network/src/main/java/com/twix/network/service/AuthService.kt b/core/network/src/main/java/com/twix/network/service/AuthService.kt index 6f87570b..d3981030 100644 --- a/core/network/src/main/java/com/twix/network/service/AuthService.kt +++ b/core/network/src/main/java/com/twix/network/service/AuthService.kt @@ -8,12 +8,12 @@ import de.jensklingenberg.ktorfit.http.Body import de.jensklingenberg.ktorfit.http.POST interface AuthService { - @POST("auth/google/token") + @POST("api/v1/auth/google/token") suspend fun googleLogin( @Body request: LoginRequest, ): LoginResponse - @POST("auth/refresh") + @POST("api/v1/auth/refresh") suspend fun refresh( @Body request: RefreshRequest, ): RefreshResponse 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 new file mode 100644 index 00000000..8ff116e7 --- /dev/null +++ b/core/network/src/main/java/com/twix/network/service/GoalService.kt @@ -0,0 +1,12 @@ +package com.twix.network.service + +import com.twix.network.model.response.goal.model.GoalListResponse +import de.jensklingenberg.ktorfit.http.GET +import de.jensklingenberg.ktorfit.http.Query + +interface GoalService { + @GET("api/v1/goals") + suspend fun fetchGoals( + @Query("date") date: String, + ): GoalListResponse +} diff --git a/core/result/build.gradle.kts b/core/result/build.gradle.kts index 2711e586..ba7b83cc 100644 --- a/core/result/build.gradle.kts +++ b/core/result/build.gradle.kts @@ -1,7 +1,3 @@ plugins { - alias(libs.plugins.twix.android.library) -} - -android { - namespace = "com.twix.result" + alias(libs.plugins.twix.java.library) } diff --git a/core/ui/src/main/java/com/twix/ui/base/BaseViewModel.kt b/core/ui/src/main/java/com/twix/ui/base/BaseViewModel.kt index f240411a..09b49864 100644 --- a/core/ui/src/main/java/com/twix/ui/base/BaseViewModel.kt +++ b/core/ui/src/main/java/com/twix/ui/base/BaseViewModel.kt @@ -81,7 +81,7 @@ abstract class BaseViewModel( onStart: (() -> Unit)? = null, // 비동기 시작 전 처리해야 할 로직 ex) 로딩 onFinally: (() -> Unit)? = null, // 비동기 종료 후 리소스 정리 onSuccess: (D) -> Unit, // 비동기 메서드 호출이 성공했을 때 처리해야 할 로직 - onError: ((AppError) -> Unit)? = null, // 비동기 메서드 호출에 실패했을 때 처리해야 할 로직 + onError: (suspend (AppError) -> Unit)? = null, // 비동기 메서드 호출에 실패했을 때 처리해야 할 로직 block: suspend () -> AppResult, // 비동기 메서드 ex) 서버 통신 메서드 ): Job = viewModelScope.launch { diff --git a/data/build.gradle.kts b/data/build.gradle.kts index a2d22455..06467ea1 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -8,5 +8,6 @@ android { dependencies { implementation(projects.core.datastore) + implementation(projects.core.result) implementation(projects.core.token) } diff --git a/data/src/main/java/com/twix/data/di/RepositoryModule.kt b/data/src/main/java/com/twix/data/di/RepositoryModule.kt index a3d7bd3c..6fb9e27c 100644 --- a/data/src/main/java/com/twix/data/di/RepositoryModule.kt +++ b/data/src/main/java/com/twix/data/di/RepositoryModule.kt @@ -1,8 +1,10 @@ package com.twix.data.di import com.twix.data.repository.DefaultAuthRepository +import com.twix.data.repository.DefaultGoalRepository import com.twix.data.repository.DefaultOnboardingRepository import com.twix.domain.repository.AuthRepository +import com.twix.domain.repository.GoalRepository import com.twix.domain.repository.OnBoardingRepository import org.koin.dsl.module @@ -11,6 +13,9 @@ internal val repositoryModule = single { DefaultOnboardingRepository(get()) } + single { + DefaultGoalRepository(get()) + } single { DefaultAuthRepository(get(), get()) } diff --git a/data/src/main/java/com/twix/data/repository/DefaultGoalRepository.kt b/data/src/main/java/com/twix/data/repository/DefaultGoalRepository.kt new file mode 100644 index 00000000..f145b393 --- /dev/null +++ b/data/src/main/java/com/twix/data/repository/DefaultGoalRepository.kt @@ -0,0 +1,14 @@ +package com.twix.data.repository + +import com.twix.domain.model.goal.GoalList +import com.twix.domain.repository.GoalRepository +import com.twix.network.execute.safeApiCall +import com.twix.network.model.response.goal.mapper.toDomain +import com.twix.network.service.GoalService +import com.twix.result.AppResult + +class DefaultGoalRepository( + private val service: GoalService, +) : GoalRepository { + override suspend fun fetchGoalList(date: String): AppResult = safeApiCall { service.fetchGoals(date).toDomain() } +} diff --git a/domain/build.gradle.kts b/domain/build.gradle.kts index ba7b83cc..e39ea89d 100644 --- a/domain/build.gradle.kts +++ b/domain/build.gradle.kts @@ -1,3 +1,7 @@ plugins { alias(libs.plugins.twix.java.library) } + +dependencies { + implementation(projects.core.result) +} diff --git a/domain/src/main/java/com/twix/domain/model/enums/GoalIconType.kt b/domain/src/main/java/com/twix/domain/model/enums/GoalIconType.kt index 79abb906..0f4870e4 100644 --- a/domain/src/main/java/com/twix/domain/model/enums/GoalIconType.kt +++ b/domain/src/main/java/com/twix/domain/model/enums/GoalIconType.kt @@ -9,4 +9,20 @@ enum class GoalIconType { HEALTH, HEART, LAPTOP, + ; + + companion object { + fun fromApi(icon: String): GoalIconType = + when (icon) { + "ICON_DEFAULT" -> DEFAULT + "ICON_CLEAN" -> CLEAN + "ICON_EXERCISE" -> EXERCISE + "ICON_BOOK" -> BOOK + "ICON_PENCIL" -> PENCIL + "ICON_HEALTH" -> HEALTH + "ICON_HEART" -> HEART + "ICON_LAPTOP" -> LAPTOP + else -> DEFAULT + } + } } diff --git a/domain/src/main/java/com/twix/domain/model/enums/RepeatCycle.kt b/domain/src/main/java/com/twix/domain/model/enums/RepeatCycle.kt new file mode 100644 index 00000000..2931eaf9 --- /dev/null +++ b/domain/src/main/java/com/twix/domain/model/enums/RepeatCycle.kt @@ -0,0 +1,12 @@ +package com.twix.domain.model.enums + +enum class RepeatCycle { + DAILY, + WEEKLY, + MONTHLY, + ; + + companion object { + fun fromApi(value: String): RepeatCycle = runCatching { valueOf(value) }.getOrElse { DAILY } + } +} diff --git a/domain/src/main/java/com/twix/domain/model/enums/RepeatType.kt b/domain/src/main/java/com/twix/domain/model/enums/RepeatType.kt deleted file mode 100644 index 61a44e25..00000000 --- a/domain/src/main/java/com/twix/domain/model/enums/RepeatType.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.twix.domain.model.enums - -enum class RepeatType { - DAILY, - WEEKLY, - MONTHLY, -} diff --git a/domain/src/main/java/com/twix/domain/model/goal/Goal.kt b/domain/src/main/java/com/twix/domain/model/goal/Goal.kt new file mode 100644 index 00000000..a4d03f14 --- /dev/null +++ b/domain/src/main/java/com/twix/domain/model/goal/Goal.kt @@ -0,0 +1,15 @@ +package com.twix.domain.model.goal + +import com.twix.domain.model.enums.GoalIconType +import com.twix.domain.model.enums.RepeatCycle + +data class Goal( + val goalId: Long, + val name: String, + val icon: GoalIconType, + val repeatCycle: RepeatCycle, + val myCompleted: Boolean, + val partnerCompleted: Boolean, + val myVerification: GoalVerification?, + val partnerVerification: GoalVerification?, +) diff --git a/domain/src/main/java/com/twix/domain/model/goal/GoalList.kt b/domain/src/main/java/com/twix/domain/model/goal/GoalList.kt new file mode 100644 index 00000000..2ff8a336 --- /dev/null +++ b/domain/src/main/java/com/twix/domain/model/goal/GoalList.kt @@ -0,0 +1,7 @@ +package com.twix.domain.model.goal + +data class GoalList( + val completedCount: Int = 0, + val totalCount: Int = 0, + val goals: List = emptyList(), +) diff --git a/domain/src/main/java/com/twix/domain/model/goal/GoalVerification.kt b/domain/src/main/java/com/twix/domain/model/goal/GoalVerification.kt new file mode 100644 index 00000000..c9367ad7 --- /dev/null +++ b/domain/src/main/java/com/twix/domain/model/goal/GoalVerification.kt @@ -0,0 +1,9 @@ +package com.twix.domain.model.goal + +data class GoalVerification( + val photologId: Long, + val imageUrl: String, + val comment: String?, + val reaction: String?, + val uploadedAt: String, +) diff --git a/domain/src/main/java/com/twix/domain/repository/GoalRepository.kt b/domain/src/main/java/com/twix/domain/repository/GoalRepository.kt new file mode 100644 index 00000000..be0958f9 --- /dev/null +++ b/domain/src/main/java/com/twix/domain/repository/GoalRepository.kt @@ -0,0 +1,8 @@ +package com.twix.domain.repository + +import com.twix.domain.model.goal.GoalList +import com.twix.result.AppResult + +interface GoalRepository { + suspend fun fetchGoalList(date: String): AppResult +} 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 0ac5656b..c6cb00f2 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 @@ -1,7 +1,7 @@ package com.twix.goal_editor import com.twix.domain.model.enums.GoalIconType -import com.twix.domain.model.enums.RepeatType +import com.twix.domain.model.enums.RepeatCycle import com.twix.ui.base.Intent import java.time.LocalDate @@ -15,7 +15,7 @@ sealed interface GoalEditorIntent : Intent { ) : GoalEditorIntent data class SetRepeatType( - val repeatType: RepeatType, + val repeatCycle: RepeatCycle, ) : GoalEditorIntent data class SetRepeatCount( 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 e3ec4af9..13fce06a 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 @@ -46,18 +46,18 @@ import com.twix.designsystem.components.dialog.CommonDialog 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.extension.toRes import com.twix.designsystem.theme.CommonColor import com.twix.designsystem.theme.GrayColor import com.twix.designsystem.theme.TwixTheme import com.twix.domain.model.enums.AppTextStyle import com.twix.domain.model.enums.GoalIconType -import com.twix.domain.model.enums.RepeatType +import com.twix.domain.model.enums.RepeatCycle import com.twix.goal_editor.component.EmojiPicker import com.twix.goal_editor.component.GoalEditorTopBar import com.twix.goal_editor.component.GoalInfoCard import com.twix.goal_editor.component.GoalTextField import com.twix.goal_editor.component.label -import com.twix.goal_editor.component.toRes import com.twix.goal_editor.model.GoalEditorUiState import com.twix.ui.extension.dismissKeyboardOnTap import com.twix.ui.extension.noRippleClickable @@ -110,7 +110,7 @@ fun GoalEditorScreen( isEdit: Boolean = false, onBack: () -> Unit, onCommitTitle: (String) -> Unit, - onSelectRepeatType: (RepeatType) -> Unit, + onSelectRepeatType: (RepeatCycle) -> Unit, onCommitIcon: (GoalIconType) -> Unit, onCommitEndDate: (LocalDate) -> Unit, onCommitStartDate: (LocalDate) -> Unit, @@ -155,7 +155,7 @@ fun GoalEditorScreen( Spacer(Modifier.height(44.dp)) GoalInfoCard( - selectedRepeatType = uiState.selectedRepeatType, + selectedRepeatCycle = uiState.selectedRepeatCycle, repeatCount = uiState.repeatCount, startDate = uiState.startDate, endDateEnabled = uiState.endDateEnabled, @@ -204,7 +204,7 @@ fun GoalEditorScreen( content = { RepeatCountBottomSheetContent( repeatCount = uiState.repeatCount, - selectedRepeatType = uiState.selectedRepeatType, + selectedRepeatCycle = uiState.selectedRepeatCycle, onCommit = { repeatType, repeatCount -> onSelectRepeatType(repeatType) onCommitRepeatCount(repeatCount) @@ -289,12 +289,12 @@ private fun IconEditorDialogContent( @Composable private fun RepeatCountBottomSheetContent( repeatCount: Int, - selectedRepeatType: RepeatType, - onCommit: (RepeatType, Int) -> Unit, + selectedRepeatCycle: RepeatCycle, + onCommit: (RepeatCycle, Int) -> Unit, ) { var internalRepeatCount by remember { mutableIntStateOf(repeatCount) } - var internalSelectedRepeatType by remember { mutableStateOf(selectedRepeatType) } - val maxCount = if (internalSelectedRepeatType == RepeatType.WEEKLY) 6 else 25 + var internalSelectedRepeatType by remember { mutableStateOf(selectedRepeatCycle) } + val maxCount = if (internalSelectedRepeatType == RepeatCycle.WEEKLY) 6 else 25 Column( horizontalAlignment = Alignment.CenterHorizontally, @@ -306,33 +306,33 @@ private fun RepeatCountBottomSheetContent( horizontalArrangement = Arrangement.spacedBy(8.dp), ) { AppText( - text = RepeatType.WEEKLY.label(), + text = RepeatCycle.WEEKLY.label(), style = AppTextStyle.B2, - color = if (internalSelectedRepeatType == RepeatType.WEEKLY) CommonColor.White else GrayColor.C500, + color = if (internalSelectedRepeatType == RepeatCycle.WEEKLY) CommonColor.White else GrayColor.C500, modifier = Modifier .clip(RoundedCornerShape(8.dp)) - .background(if (internalSelectedRepeatType == RepeatType.WEEKLY) GrayColor.C500 else CommonColor.White) + .background(if (internalSelectedRepeatType == RepeatCycle.WEEKLY) GrayColor.C500 else CommonColor.White) .border(1.dp, GrayColor.C500, RoundedCornerShape(8.dp)) .padding(horizontal = 12.dp, vertical = 5.5.dp) .noRippleClickable(onClick = { - internalSelectedRepeatType = RepeatType.WEEKLY + internalSelectedRepeatType = RepeatCycle.WEEKLY internalRepeatCount = 0 }), ) AppText( - text = RepeatType.MONTHLY.label(), + text = RepeatCycle.MONTHLY.label(), style = AppTextStyle.B2, - color = if (internalSelectedRepeatType == RepeatType.MONTHLY) CommonColor.White else GrayColor.C500, + color = if (internalSelectedRepeatType == RepeatCycle.MONTHLY) CommonColor.White else GrayColor.C500, modifier = Modifier .clip(RoundedCornerShape(8.dp)) - .background(if (internalSelectedRepeatType == RepeatType.MONTHLY) GrayColor.C500 else CommonColor.White) + .background(if (internalSelectedRepeatType == RepeatCycle.MONTHLY) GrayColor.C500 else CommonColor.White) .border(1.dp, GrayColor.C500, RoundedCornerShape(8.dp)) .padding(horizontal = 12.dp, vertical = 5.5.dp) .noRippleClickable(onClick = { - internalSelectedRepeatType = RepeatType.MONTHLY + internalSelectedRepeatType = RepeatCycle.MONTHLY internalRepeatCount = 0 }), ) 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 c99766e8..ce841bac 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,7 +4,7 @@ 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.RepeatType +import com.twix.domain.model.enums.RepeatCycle import com.twix.goal_editor.model.GoalEditorUiState import com.twix.ui.base.BaseViewModel import kotlinx.coroutines.launch @@ -20,7 +20,7 @@ class GoalEditorViewModel : is GoalEditorIntent.SetIcon -> setIcon(intent.icon) is GoalEditorIntent.SetEndDate -> setEndDate(intent.endDate) is GoalEditorIntent.SetRepeatCount -> setRepeatCount(intent.repeatCount) - is GoalEditorIntent.SetRepeatType -> setRepeatType(intent.repeatType) + is GoalEditorIntent.SetRepeatType -> setRepeatType(intent.repeatCycle) is GoalEditorIntent.SetStartDate -> setStartDate(intent.startDate) is GoalEditorIntent.SetTitle -> setTitle(intent.title) is GoalEditorIntent.SetEndDateEnabled -> setEndDateEnabled(intent.enabled) @@ -37,8 +37,8 @@ class GoalEditorViewModel : reduce { copy(goalTitle = title) } } - private fun setRepeatType(repeatType: RepeatType) { - reduce { copy(selectedRepeatType = repeatType) } + private fun setRepeatType(repeatCycle: RepeatCycle) { + reduce { copy(selectedRepeatCycle = repeatCycle) } } private fun setRepeatCount(repeatCount: Int) { diff --git a/feature/goal-editor/src/main/java/com/twix/goal_editor/component/EmojiPicker.kt b/feature/goal-editor/src/main/java/com/twix/goal_editor/component/EmojiPicker.kt index d3aa81c8..c0640244 100644 --- a/feature/goal-editor/src/main/java/com/twix/goal_editor/component/EmojiPicker.kt +++ b/feature/goal-editor/src/main/java/com/twix/goal_editor/component/EmojiPicker.kt @@ -12,7 +12,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp -import com.twix.designsystem.R +import com.twix.designsystem.extension.toRes import com.twix.designsystem.theme.GrayColor import com.twix.domain.model.enums.GoalIconType import com.twix.ui.extension.noRippleClickable @@ -40,16 +40,3 @@ fun EmojiPicker( ) } } - -@Composable -fun GoalIconType.toRes(): Int = - when (this) { - GoalIconType.DEFAULT -> R.drawable.ic_default - GoalIconType.CLEAN -> R.drawable.ic_clean - GoalIconType.EXERCISE -> R.drawable.ic_exercise - GoalIconType.BOOK -> R.drawable.ic_book - GoalIconType.PENCIL -> R.drawable.ic_pencil - GoalIconType.HEALTH -> R.drawable.ic_health - GoalIconType.HEART -> R.drawable.ic_heart - GoalIconType.LAPTOP -> R.drawable.ic_laptop - } 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 c6d95d47..f5b739fc 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 @@ -33,18 +33,18 @@ import com.twix.designsystem.components.text.AppText import com.twix.designsystem.theme.CommonColor import com.twix.designsystem.theme.GrayColor import com.twix.domain.model.enums.AppTextStyle -import com.twix.domain.model.enums.RepeatType +import com.twix.domain.model.enums.RepeatCycle import com.twix.ui.extension.noRippleClickable import java.time.LocalDate @Composable fun GoalInfoCard( - selectedRepeatType: RepeatType, + selectedRepeatCycle: RepeatCycle, repeatCount: Int, startDate: LocalDate, endDateEnabled: Boolean, endDate: LocalDate, - onSelectedRepeatType: (RepeatType) -> Unit, + onSelectedRepeatType: (RepeatCycle) -> Unit, onShowRepeatCountBottomSheet: () -> Unit, onShowCalendarBottomSheet: (Boolean) -> Unit, // true면 endDate onToggleEndDateEnabled: (Boolean) -> Unit, @@ -57,7 +57,7 @@ fun GoalInfoCard( .border(1.dp, GrayColor.C500, RoundedCornerShape(12.dp)), ) { RepeatTypeSettings( - selectedRepeatType = selectedRepeatType, + selectedRepeatCycle = selectedRepeatCycle, repeatCount = repeatCount, onSelectedRepeatType = onSelectedRepeatType, onShowRepeatCountBottomSheet = onShowRepeatCountBottomSheet, @@ -97,9 +97,9 @@ fun GoalInfoCard( @Composable private fun RepeatTypeSettings( - selectedRepeatType: RepeatType, + selectedRepeatCycle: RepeatCycle, repeatCount: Int, - onSelectedRepeatType: (RepeatType) -> Unit, + onSelectedRepeatType: (RepeatCycle) -> Unit, onShowRepeatCountBottomSheet: () -> Unit, ) { val animationDuration = 160 @@ -119,8 +119,8 @@ private fun RepeatTypeSettings( .fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, ) { - RepeatType.entries.forEachIndexed { index, type -> - val isSelected = selectedRepeatType == type + RepeatCycle.entries.forEachIndexed { index, type -> + val isSelected = selectedRepeatCycle == type AppText( text = type.label(), @@ -135,13 +135,13 @@ private fun RepeatTypeSettings( .noRippleClickable(onClick = { onSelectedRepeatType(type) }), ) - if (index != RepeatType.entries.lastIndex) Spacer(Modifier.width(8.dp)) + if (index != RepeatCycle.entries.lastIndex) Spacer(Modifier.width(8.dp)) } Spacer(Modifier.weight(1f)) AnimatedVisibility( - visible = selectedRepeatType != RepeatType.DAILY, + visible = selectedRepeatCycle != RepeatCycle.DAILY, enter = fadeIn(animationSpec = tween(durationMillis = animationDuration)), exit = fadeOut(animationSpec = tween(durationMillis = animationDuration)), ) { @@ -153,7 +153,7 @@ private fun RepeatTypeSettings( verticalAlignment = Alignment.CenterVertically, ) { AppText( - text = stringResource(R.string.repeat_count, selectedRepeatType.label(), repeatCount), + text = stringResource(R.string.repeat_count, selectedRepeatCycle.label(), repeatCount), style = AppTextStyle.B2, color = GrayColor.C500, ) @@ -245,9 +245,9 @@ private fun HeaderText(text: String) { } @Composable -fun RepeatType.label(): String = +fun RepeatCycle.label(): String = when (this) { - RepeatType.DAILY -> stringResource(R.string.word_daily) - RepeatType.WEEKLY -> stringResource(R.string.word_weekly) - RepeatType.MONTHLY -> stringResource(R.string.word_monthly) + RepeatCycle.DAILY -> stringResource(R.string.word_daily) + RepeatCycle.WEEKLY -> stringResource(R.string.word_weekly) + RepeatCycle.MONTHLY -> stringResource(R.string.word_monthly) } 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 feee3572..7cc8ca7b 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 @@ -2,7 +2,7 @@ package com.twix.goal_editor.model import androidx.compose.runtime.Immutable import com.twix.domain.model.enums.GoalIconType -import com.twix.domain.model.enums.RepeatType +import com.twix.domain.model.enums.RepeatCycle import com.twix.ui.base.State import java.time.LocalDate @@ -10,7 +10,7 @@ import java.time.LocalDate data class GoalEditorUiState( val selectedIcon: GoalIconType = GoalIconType.DEFAULT, val goalTitle: String = "", - val selectedRepeatType: RepeatType = RepeatType.DAILY, + val selectedRepeatCycle: RepeatCycle = RepeatCycle.DAILY, val repeatCount: Int = 0, val startDate: LocalDate = LocalDate.now(), val endDateEnabled: Boolean = false, diff --git a/feature/main/src/main/java/com/twix/home/HomeSideEffect.kt b/feature/main/src/main/java/com/twix/home/HomeSideEffect.kt index 5b1046d5..5f5ee5a2 100644 --- a/feature/main/src/main/java/com/twix/home/HomeSideEffect.kt +++ b/feature/main/src/main/java/com/twix/home/HomeSideEffect.kt @@ -1,7 +1,14 @@ package com.twix.home +import androidx.annotation.StringRes +import com.twix.designsystem.components.toast.model.ToastType import com.twix.ui.base.SideEffect interface HomeSideEffect : SideEffect { data object ShowMonthPickerBottomSheet : HomeSideEffect + + data class ShowToast( + @param:StringRes val resId: Int, + val type: ToastType, + ) : HomeSideEffect } 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 ec9a25b7..66465446 100644 --- a/feature/main/src/main/java/com/twix/home/HomeViewModel.kt +++ b/feature/main/src/main/java/com/twix/home/HomeViewModel.kt @@ -1,8 +1,10 @@ package com.twix.home import androidx.lifecycle.viewModelScope -import com.twix.designsystem.components.toast.ToastManager +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.home.model.CalendarState import com.twix.home.model.HomeUiState import com.twix.ui.base.BaseViewModel @@ -14,7 +16,7 @@ import kotlinx.coroutines.flow.stateIn import java.time.LocalDate class HomeViewModel( - private val toastManager: ToastManager, + private val goalRepository: GoalRepository, ) : BaseViewModel( HomeUiState(), ) { @@ -38,6 +40,10 @@ class HomeViewModel( ), ) + init { + fetchGoalList() + } + override suspend fun handleIntent(intent: HomeIntent) { when (intent) { is HomeIntent.SelectDate -> updateDate(intent.date) @@ -54,6 +60,8 @@ class HomeViewModel( if (date.month != currentState.visibleDate.month) updateVisibleDate(date) reduce { copy(selectedDate = date, referenceDate = date) } + + fetchGoalList() } private fun shiftWeek(action: WeekNavigation) { @@ -64,7 +72,10 @@ class HomeViewModel( WeekNavigation.TODAY -> LocalDate.now() } if (currentState.referenceDate == newReference) return - if (action == WeekNavigation.TODAY) updateDate(newReference) + if (action == WeekNavigation.TODAY) { + updateDate(newReference) + return + } reduce { copy(referenceDate = newReference) } } @@ -73,4 +84,19 @@ class HomeViewModel( reduce { copy(visibleDate = date) } } + + /** + * 서버에서 데이터를 가져오는 부분 + * */ + private fun fetchGoalList() { + val date = currentState.selectedDate.toString() + + launchResult( + block = { goalRepository.fetchGoalList(date = date) }, + onSuccess = { goalList -> reduce { copy(goalList = goalList) } }, + onError = { + emitSideEffect(HomeSideEffect.ShowToast(R.string.toast_goal_fetch_failed, ToastType.ERROR)) + }, + ) + } } diff --git a/feature/main/src/main/java/com/twix/home/model/HomeUiState.kt b/feature/main/src/main/java/com/twix/home/model/HomeUiState.kt index 0eab724c..a411ad27 100644 --- a/feature/main/src/main/java/com/twix/home/model/HomeUiState.kt +++ b/feature/main/src/main/java/com/twix/home/model/HomeUiState.kt @@ -1,6 +1,7 @@ package com.twix.home.model import androidx.compose.runtime.Immutable +import com.twix.domain.model.goal.GoalList import com.twix.ui.base.State import java.time.LocalDate import java.time.YearMonth @@ -11,6 +12,7 @@ data class HomeUiState( val visibleDate: LocalDate = LocalDate.now(), // 홈 화면 상단에 존재하는 월, 년 텍스트를 위한 상태 변수 val selectedDate: LocalDate = LocalDate.now(), val referenceDate: LocalDate = LocalDate.now(), // 7일 달력을 생성하기 위한 레퍼런스 날짜 + val goalList: GoalList = GoalList(), ) : State { val monthYear: String get() = "${visibleDate.month.value}월 ${visibleDate.year}"