diff --git a/data/src/main/java/com/threegap/bitnagil/data/emotion/datasource/EmotionDataSource.kt b/data/src/main/java/com/threegap/bitnagil/data/emotion/datasource/EmotionDataSource.kt index 0171fe2f..5e21227d 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/emotion/datasource/EmotionDataSource.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/emotion/datasource/EmotionDataSource.kt @@ -1,11 +1,11 @@ package com.threegap.bitnagil.data.emotion.datasource import com.threegap.bitnagil.data.emotion.model.dto.EmotionDto -import com.threegap.bitnagil.data.emotion.model.response.MyEmotionResponseDto +import com.threegap.bitnagil.data.emotion.model.response.GetEmotionResponse import com.threegap.bitnagil.data.emotion.model.response.RegisterEmotionResponse interface EmotionDataSource { suspend fun getEmotions(): Result> suspend fun registerEmotion(emotion: String): Result - suspend fun getMyEmotionMarble(currentDate: String): Result + suspend fun getEmotionMarble(currentDate: String): Result } diff --git a/data/src/main/java/com/threegap/bitnagil/data/emotion/datasourceImpl/EmotionDataSourceImpl.kt b/data/src/main/java/com/threegap/bitnagil/data/emotion/datasourceImpl/EmotionDataSourceImpl.kt index 16cd3a1b..b8893258 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/emotion/datasourceImpl/EmotionDataSourceImpl.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/emotion/datasourceImpl/EmotionDataSourceImpl.kt @@ -4,7 +4,7 @@ import com.threegap.bitnagil.data.common.safeApiCall import com.threegap.bitnagil.data.emotion.datasource.EmotionDataSource import com.threegap.bitnagil.data.emotion.model.dto.EmotionDto import com.threegap.bitnagil.data.emotion.model.request.RegisterEmotionRequest -import com.threegap.bitnagil.data.emotion.model.response.MyEmotionResponseDto +import com.threegap.bitnagil.data.emotion.model.response.GetEmotionResponse import com.threegap.bitnagil.data.emotion.model.response.RegisterEmotionResponse import com.threegap.bitnagil.data.emotion.service.EmotionService import javax.inject.Inject @@ -25,8 +25,8 @@ class EmotionDataSourceImpl @Inject constructor( } } - override suspend fun getMyEmotionMarble(currentDate: String): Result = + override suspend fun getEmotionMarble(currentDate: String): Result = safeApiCall { - emotionService.getMyEmotionMarble(currentDate) + emotionService.getEmotionMarble(currentDate) } } diff --git a/data/src/main/java/com/threegap/bitnagil/data/emotion/model/dto/EmotionDto.kt b/data/src/main/java/com/threegap/bitnagil/data/emotion/model/dto/EmotionDto.kt index c9f8e652..b6bbf455 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/emotion/model/dto/EmotionDto.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/emotion/model/dto/EmotionDto.kt @@ -1,5 +1,6 @@ package com.threegap.bitnagil.data.emotion.model.dto +import com.threegap.bitnagil.domain.emotion.model.Emotion import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @@ -11,4 +12,11 @@ data class EmotionDto( val emotionMarbleName: String, @SerialName("imageUrl") val imageUrl: String, -) +) { + fun toDomain(): Emotion = + Emotion( + emotionType = emotionMarbleType, + emotionMarbleName = emotionMarbleName, + imageUrl = imageUrl, + ) +} diff --git a/data/src/main/java/com/threegap/bitnagil/data/emotion/model/response/MyEmotionResponseDto.kt b/data/src/main/java/com/threegap/bitnagil/data/emotion/model/response/GetEmotionResponse.kt similarity index 53% rename from data/src/main/java/com/threegap/bitnagil/data/emotion/model/response/MyEmotionResponseDto.kt rename to data/src/main/java/com/threegap/bitnagil/data/emotion/model/response/GetEmotionResponse.kt index 77243076..01e75c86 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/emotion/model/response/MyEmotionResponseDto.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/emotion/model/response/GetEmotionResponse.kt @@ -1,12 +1,11 @@ package com.threegap.bitnagil.data.emotion.model.response import com.threegap.bitnagil.domain.emotion.model.Emotion -import com.threegap.bitnagil.domain.emotion.model.MyEmotion import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable @Serializable -data class MyEmotionResponseDto( +data class GetEmotionResponse( @SerialName("emotionMarbleType") val emotionMarbleType: String?, @SerialName("emotionMarbleName") @@ -15,9 +14,14 @@ data class MyEmotionResponseDto( val imageUrl: String?, ) -fun MyEmotionResponseDto.toDomain(): MyEmotion = - MyEmotion( - emotionMarbleType = emotionMarbleType?.let { Emotion.valueOf(it) }, - emotionMarbleName = emotionMarbleName, - imageUrl = imageUrl, - ) +fun GetEmotionResponse.toDomain(): Emotion? { + return if (emotionMarbleType != null && emotionMarbleName != null && imageUrl != null) { + Emotion( + emotionType = emotionMarbleType, + emotionMarbleName = emotionMarbleName, + imageUrl = imageUrl, + ) + } else { + null + } +} diff --git a/data/src/main/java/com/threegap/bitnagil/data/emotion/repositoryImpl/EmotionRepositoryImpl.kt b/data/src/main/java/com/threegap/bitnagil/data/emotion/repositoryImpl/EmotionRepositoryImpl.kt index 6c08bf65..b5748332 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/emotion/repositoryImpl/EmotionRepositoryImpl.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/emotion/repositoryImpl/EmotionRepositoryImpl.kt @@ -3,9 +3,12 @@ package com.threegap.bitnagil.data.emotion.repositoryImpl import com.threegap.bitnagil.data.emotion.datasource.EmotionDataSource import com.threegap.bitnagil.data.emotion.model.response.toDomain import com.threegap.bitnagil.domain.emotion.model.Emotion +import com.threegap.bitnagil.domain.emotion.model.EmotionChangeEvent import com.threegap.bitnagil.domain.emotion.model.EmotionRecommendRoutine -import com.threegap.bitnagil.domain.emotion.model.MyEmotion import com.threegap.bitnagil.domain.emotion.repository.EmotionRepository +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow import javax.inject.Inject class EmotionRepositoryImpl @Inject constructor( @@ -13,38 +16,26 @@ class EmotionRepositoryImpl @Inject constructor( ) : EmotionRepository { override suspend fun getEmotions(): Result> { return emotionDataSource.getEmotions().map { response -> - response.mapNotNull { - when (it.emotionMarbleType) { - "CALM" -> Emotion.CALM - "VITALITY" -> Emotion.VITALITY - "LETHARGY" -> Emotion.LETHARGY - "ANXIETY" -> Emotion.ANXIETY - "SATISFACTION" -> Emotion.SATISFACTION - "FATIGUE" -> Emotion.FATIGUE - else -> null - } - } + response.map { it.toDomain() } } } - override suspend fun registerEmotion(emotion: Emotion): Result> { - val selectedEmotion = when (emotion) { - Emotion.CALM -> "CALM" - Emotion.VITALITY -> "VITALITY" - Emotion.LETHARGY -> "LETHARGY" - Emotion.ANXIETY -> "ANXIETY" - Emotion.SATISFACTION -> "SATISFACTION" - Emotion.FATIGUE -> "FATIGUE" - } - - return emotionDataSource.registerEmotion(selectedEmotion).map { + override suspend fun registerEmotion(emotionMarbleType: String): Result> { + return emotionDataSource.registerEmotion(emotionMarbleType).map { it.recommendedRoutines.map { emotionRecommendedRoutineDto -> emotionRecommendedRoutineDto.toEmotionRecommendRoutine() } + }.also { + if (it.isSuccess) { + _emotionChangeEventFlow.emit(EmotionChangeEvent.ChangeEmotion(emotionMarbleType)) + } } } - override suspend fun getMyEmotionMarble(currentDate: String): Result = - emotionDataSource.getMyEmotionMarble(currentDate).map { it.toDomain() } + override suspend fun getEmotionMarble(currentDate: String): Result = + emotionDataSource.getEmotionMarble(currentDate).map { it.toDomain() } + + private val _emotionChangeEventFlow = MutableSharedFlow() + override suspend fun getEmotionChangeEventFlow(): Flow = _emotionChangeEventFlow.asSharedFlow() } diff --git a/data/src/main/java/com/threegap/bitnagil/data/emotion/service/EmotionService.kt b/data/src/main/java/com/threegap/bitnagil/data/emotion/service/EmotionService.kt index df8c4e9a..0752f695 100644 --- a/data/src/main/java/com/threegap/bitnagil/data/emotion/service/EmotionService.kt +++ b/data/src/main/java/com/threegap/bitnagil/data/emotion/service/EmotionService.kt @@ -2,7 +2,7 @@ package com.threegap.bitnagil.data.emotion.service import com.threegap.bitnagil.data.emotion.model.dto.EmotionDto import com.threegap.bitnagil.data.emotion.model.request.RegisterEmotionRequest -import com.threegap.bitnagil.data.emotion.model.response.MyEmotionResponseDto +import com.threegap.bitnagil.data.emotion.model.response.GetEmotionResponse import com.threegap.bitnagil.data.emotion.model.response.RegisterEmotionResponse import com.threegap.bitnagil.network.model.BaseResponse import retrofit2.http.Body @@ -20,7 +20,7 @@ interface EmotionService { ): BaseResponse @GET("/api/v1/emotion-marbles/{searchDate}") - suspend fun getMyEmotionMarble( + suspend fun getEmotionMarble( @Path("searchDate") date: String, - ): BaseResponse + ): BaseResponse } diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/emotion/model/Emotion.kt b/domain/src/main/java/com/threegap/bitnagil/domain/emotion/model/Emotion.kt index daf69bae..f6aa4ede 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/emotion/model/Emotion.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/emotion/model/Emotion.kt @@ -1,10 +1,7 @@ package com.threegap.bitnagil.domain.emotion.model -enum class Emotion { - CALM, - VITALITY, - LETHARGY, - ANXIETY, - SATISFACTION, - FATIGUE, -} +data class Emotion( + val emotionType: String, + val emotionMarbleName: String, + val imageUrl: String, +) diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/emotion/model/EmotionChangeEvent.kt b/domain/src/main/java/com/threegap/bitnagil/domain/emotion/model/EmotionChangeEvent.kt new file mode 100644 index 00000000..f848a647 --- /dev/null +++ b/domain/src/main/java/com/threegap/bitnagil/domain/emotion/model/EmotionChangeEvent.kt @@ -0,0 +1,5 @@ +package com.threegap.bitnagil.domain.emotion.model + +sealed interface EmotionChangeEvent { + data class ChangeEmotion(val emotionType: String) : EmotionChangeEvent +} diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/emotion/model/MyEmotion.kt b/domain/src/main/java/com/threegap/bitnagil/domain/emotion/model/MyEmotion.kt deleted file mode 100644 index 36e226a9..00000000 --- a/domain/src/main/java/com/threegap/bitnagil/domain/emotion/model/MyEmotion.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.threegap.bitnagil.domain.emotion.model - -data class MyEmotion( - val emotionMarbleType: Emotion?, - val emotionMarbleName: String?, - val imageUrl: String?, -) diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/emotion/repository/EmotionRepository.kt b/domain/src/main/java/com/threegap/bitnagil/domain/emotion/repository/EmotionRepository.kt index 1cb68de7..56367614 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/emotion/repository/EmotionRepository.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/emotion/repository/EmotionRepository.kt @@ -1,11 +1,13 @@ package com.threegap.bitnagil.domain.emotion.repository import com.threegap.bitnagil.domain.emotion.model.Emotion +import com.threegap.bitnagil.domain.emotion.model.EmotionChangeEvent import com.threegap.bitnagil.domain.emotion.model.EmotionRecommendRoutine -import com.threegap.bitnagil.domain.emotion.model.MyEmotion +import kotlinx.coroutines.flow.Flow interface EmotionRepository { suspend fun getEmotions(): Result> - suspend fun registerEmotion(emotion: Emotion): Result> - suspend fun getMyEmotionMarble(currentDate: String): Result + suspend fun registerEmotion(emotionMarbleType: String): Result> + suspend fun getEmotionMarble(currentDate: String): Result + suspend fun getEmotionChangeEventFlow(): Flow } diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/emotion/usecase/GetEmotionChangeEventFlowUseCase.kt b/domain/src/main/java/com/threegap/bitnagil/domain/emotion/usecase/GetEmotionChangeEventFlowUseCase.kt new file mode 100644 index 00000000..d30147c4 --- /dev/null +++ b/domain/src/main/java/com/threegap/bitnagil/domain/emotion/usecase/GetEmotionChangeEventFlowUseCase.kt @@ -0,0 +1,12 @@ +package com.threegap.bitnagil.domain.emotion.usecase + +import com.threegap.bitnagil.domain.emotion.model.EmotionChangeEvent +import com.threegap.bitnagil.domain.emotion.repository.EmotionRepository +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject + +class GetEmotionChangeEventFlowUseCase @Inject constructor( + private val repository: EmotionRepository, +) { + suspend operator fun invoke(): Flow = repository.getEmotionChangeEventFlow() +} diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/emotion/usecase/GetEmotionUseCase.kt b/domain/src/main/java/com/threegap/bitnagil/domain/emotion/usecase/GetEmotionUseCase.kt new file mode 100644 index 00000000..16d886d6 --- /dev/null +++ b/domain/src/main/java/com/threegap/bitnagil/domain/emotion/usecase/GetEmotionUseCase.kt @@ -0,0 +1,12 @@ +package com.threegap.bitnagil.domain.emotion.usecase + +import com.threegap.bitnagil.domain.emotion.model.Emotion +import com.threegap.bitnagil.domain.emotion.repository.EmotionRepository +import javax.inject.Inject + +class GetEmotionUseCase @Inject constructor( + private val emotionRepository: EmotionRepository, +) { + suspend operator fun invoke(currentDate: String): Result = + emotionRepository.getEmotionMarble(currentDate) +} diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/emotion/usecase/GetMyEmotionUseCase.kt b/domain/src/main/java/com/threegap/bitnagil/domain/emotion/usecase/GetMyEmotionUseCase.kt deleted file mode 100644 index a72157c9..00000000 --- a/domain/src/main/java/com/threegap/bitnagil/domain/emotion/usecase/GetMyEmotionUseCase.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.threegap.bitnagil.domain.emotion.usecase - -import com.threegap.bitnagil.domain.emotion.model.MyEmotion -import com.threegap.bitnagil.domain.emotion.repository.EmotionRepository -import javax.inject.Inject - -class GetMyEmotionUseCase @Inject constructor( - private val emotionRepository: EmotionRepository, -) { - suspend operator fun invoke(currentDate: String): Result = - emotionRepository.getMyEmotionMarble(currentDate) -} diff --git a/domain/src/main/java/com/threegap/bitnagil/domain/emotion/usecase/RegisterEmotionUseCase.kt b/domain/src/main/java/com/threegap/bitnagil/domain/emotion/usecase/RegisterEmotionUseCase.kt index 6c695312..3e51863e 100644 --- a/domain/src/main/java/com/threegap/bitnagil/domain/emotion/usecase/RegisterEmotionUseCase.kt +++ b/domain/src/main/java/com/threegap/bitnagil/domain/emotion/usecase/RegisterEmotionUseCase.kt @@ -1,6 +1,5 @@ package com.threegap.bitnagil.domain.emotion.usecase -import com.threegap.bitnagil.domain.emotion.model.Emotion import com.threegap.bitnagil.domain.emotion.model.EmotionRecommendRoutine import com.threegap.bitnagil.domain.emotion.repository.EmotionRepository import javax.inject.Inject @@ -8,7 +7,7 @@ import javax.inject.Inject class RegisterEmotionUseCase @Inject constructor( private val emotionRepository: EmotionRepository, ) { - suspend operator fun invoke(emotion: Emotion): Result> { - return emotionRepository.registerEmotion(emotion) + suspend operator fun invoke(emotionType: String): Result> { + return emotionRepository.registerEmotion(emotionType) } } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 47973da5..59488ed8 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -52,6 +52,7 @@ orbit = "6.1.0" javax = "1" kakaoLogin = "2.21.4" lottie-compose = "6.6.0" +coil = "3.3.0" [libraries] ## Android Gradle Plugin @@ -115,6 +116,10 @@ androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "j androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } kotlin-coroutines-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" } +## coil +coil-compose = { group = "io.coil-kt.coil3", name = "coil-compose", version.ref = "coil" } +coil-network = { group = "io.coil-kt.coil3", name = "coil-network-okhttp", version.ref = "coil" } + ## Other material = { group = "com.google.android.material", name = "material", version.ref = "material" } kakao-v2-user = { group = "com.kakao.sdk", name = "v2-user", version.ref = "kakaoLogin" } @@ -171,6 +176,11 @@ orbit = [ "orbit-viewmodel" ] +coil = [ + "coil-compose", + "coil-network" +] + [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" } diff --git a/presentation/build.gradle.kts b/presentation/build.gradle.kts index d0716ce7..6f8d0b1b 100644 --- a/presentation/build.gradle.kts +++ b/presentation/build.gradle.kts @@ -19,6 +19,7 @@ dependencies { implementation(libs.kakao.v2.user) implementation(libs.kotlinx.serialization.json) implementation(libs.lottie.compose) + implementation(libs.bundles.coil) testImplementation(libs.junit) testImplementation(libs.kotlin.coroutines.test) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/EmotionScreen.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/EmotionScreen.kt index 48eaf1c4..bcdfb249 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/EmotionScreen.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/EmotionScreen.kt @@ -1,11 +1,11 @@ package com.threegap.bitnagil.presentation.emotion import androidx.activity.compose.BackHandler -import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding @@ -22,23 +22,26 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import coil3.compose.AsyncImage +import coil3.request.ImageRequest +import coil3.request.crossfade import com.threegap.bitnagil.designsystem.BitnagilTheme import com.threegap.bitnagil.designsystem.component.atom.BitnagilSelectButton import com.threegap.bitnagil.designsystem.component.atom.BitnagilTextButton import com.threegap.bitnagil.designsystem.component.atom.BitnagilTextButtonColor -import com.threegap.bitnagil.designsystem.component.block.BitnagilProgressTopBar import com.threegap.bitnagil.designsystem.component.block.BitnagilTopBar import com.threegap.bitnagil.designsystem.modifier.clickableWithoutRipple import com.threegap.bitnagil.presentation.common.flow.collectAsEffect -import com.threegap.bitnagil.presentation.emotion.model.Emotion import com.threegap.bitnagil.presentation.emotion.model.EmotionRecommendRoutineUiModel import com.threegap.bitnagil.presentation.emotion.model.EmotionScreenStep +import com.threegap.bitnagil.presentation.emotion.model.EmotionUiModel import com.threegap.bitnagil.presentation.emotion.model.mvi.EmotionSideEffect import com.threegap.bitnagil.presentation.emotion.model.mvi.EmotionState @@ -67,7 +70,6 @@ fun EmotionScreenContainer( ) EmotionScreenStep.RecommendRoutines -> EmotionRecommendRoutineScreen( state = state, - onClickPreviousButton = viewModel::moveToPrev, onClickRoutine = viewModel::selectRecommendRoutine, onClickRegisterRecommendRoutines = viewModel::registerRecommendRoutines, onClickSkip = navigateToBack, @@ -79,7 +81,7 @@ fun EmotionScreenContainer( private fun EmotionScreen( state: EmotionState, onClickPreviousButton: () -> Unit, - onClickEmotion: (Emotion) -> Unit, + onClickEmotion: (String) -> Unit, ) { Column( modifier = Modifier @@ -117,18 +119,24 @@ private fun EmotionScreen( horizontalArrangement = Arrangement.spacedBy(12.dp), verticalArrangement = Arrangement.spacedBy(28.dp), ) { - items(state.emotions) { emotion -> + items(state.emotionTypeUiModels) { emotion -> Column( modifier = Modifier - .clickableWithoutRipple { onClickEmotion(emotion) }, + .clickableWithoutRipple { onClickEmotion(emotion.emotionType) }, horizontalAlignment = Alignment.CenterHorizontally, ) { - Image( - painter = painterResource(id = emotion.imageResourceId), + AsyncImage( + model = ImageRequest.Builder(LocalContext.current) + .data(emotion.imageUrl) + .crossfade(true) + .build(), + modifier = Modifier.aspectRatio(1f), contentDescription = null, + error = emotion.offlineBackupImageResourceId?.let { painterResource(it) }, ) + Text( - text = emotion.emotionName, + text = emotion.emotionMarbleName, style = BitnagilTheme.typography.body1Regular.copy(color = BitnagilTheme.colors.coolGray20), ) } @@ -140,7 +148,6 @@ private fun EmotionScreen( @Composable private fun EmotionRecommendRoutineScreen( state: EmotionState, - onClickPreviousButton: () -> Unit, onClickRoutine: (String) -> Unit, onClickRegisterRecommendRoutines: () -> Unit, onClickSkip: () -> Unit, @@ -149,73 +156,65 @@ private fun EmotionRecommendRoutineScreen( modifier = Modifier .fillMaxSize() .background(color = BitnagilTheme.colors.coolGray99) - .statusBarsPadding(), + .statusBarsPadding() + .padding(start = 16.dp, end = 16.dp, bottom = 20.dp, top = 32.dp), ) { - BitnagilProgressTopBar( - onBackClick = onClickPreviousButton, - progress = 1f, + Spacer(modifier = Modifier.height(54.dp)) + + Text( + text = "오늘 감정에 따른\n루틴을 추천드릴께요!", + color = BitnagilTheme.colors.navy500, + style = BitnagilTheme.typography.title2Bold, ) + Spacer(modifier = Modifier.height(10.dp)) + + Text( + text = "오늘 당신의 감정 상태에 맞춰 구성된 맞춤 루틴이에요.\n원하는 루틴을 선택해서 가볍게 시작해보세요.", + color = BitnagilTheme.colors.coolGray50, + style = BitnagilTheme.typography.body2Medium, + ) + + Spacer(modifier = Modifier.height(28.dp)) + + val scrollState = rememberScrollState() Column( modifier = Modifier .weight(1f) - .padding(start = 16.dp, end = 16.dp, bottom = 20.dp, top = 32.dp), + .verticalScroll(state = scrollState), ) { - Text( - text = "오늘 감정에 따른\n루틴을 추천드릴께요!", - color = BitnagilTheme.colors.navy500, - style = BitnagilTheme.typography.title2Bold, - ) - - Spacer(modifier = Modifier.height(10.dp)) - - Text( - text = "오늘 당신의 감정 상태에 맞춰 구성된 맞춤 루틴이에요.\n원하는 루틴을 선택해서 가볍게 시작해보세요.", - color = BitnagilTheme.colors.coolGray50, - style = BitnagilTheme.typography.body2Medium, - ) - - Spacer(modifier = Modifier.height(28.dp)) - - val scrollState = rememberScrollState() - Column( - modifier = Modifier - .weight(1f) - .verticalScroll(state = scrollState), - ) { - for (recommendRoutine in state.recommendRoutines) { - BitnagilSelectButton( - title = recommendRoutine.name, - description = recommendRoutine.description, - onClick = { onClickRoutine(recommendRoutine.id) }, - selected = recommendRoutine.selected, - modifier = Modifier.padding(bottom = 12.dp), - ) - } + for (recommendRoutine in state.recommendRoutines) { + BitnagilSelectButton( + title = recommendRoutine.name, + description = recommendRoutine.description, + onClick = { onClickRoutine(recommendRoutine.id) }, + selected = recommendRoutine.selected, + modifier = Modifier.padding(bottom = 12.dp), + ) } + } - Spacer(modifier = Modifier.height(12.dp)) + Spacer(modifier = Modifier.height(12.dp)) - BitnagilTextButton( - text = "변경하기", - onClick = onClickRegisterRecommendRoutines, - enabled = state.registerRecommendRoutinesButtonEnabled, - ) + BitnagilTextButton( + text = "변경하기", + onClick = onClickRegisterRecommendRoutines, + enabled = state.registerRecommendRoutinesButtonEnabled, + ) - Spacer(modifier = Modifier.height(10.dp)) + Spacer(modifier = Modifier.height(10.dp)) - BitnagilTextButton( - text = "건너뛰기", - onClick = onClickSkip, - colors = BitnagilTextButtonColor.skip().copy( - defaultBackgroundColor = Color.Transparent, - pressedBackgroundColor = Color.Transparent, - disabledBackgroundColor = Color.Transparent, - ), - textStyle = BitnagilTheme.typography.body2Regular, - textDecoration = TextDecoration.Underline, - ) - } + BitnagilTextButton( + text = "건너뛰기", + onClick = onClickSkip, + colors = BitnagilTextButtonColor.skip().copy( + defaultBackgroundColor = Color.Transparent, + pressedBackgroundColor = Color.Transparent, + disabledBackgroundColor = Color.Transparent, + ), + textStyle = BitnagilTheme.typography.body2Regular, + textDecoration = TextDecoration.Underline, + ) } } @@ -225,7 +224,20 @@ private fun EmotionScreenPreview() { BitnagilTheme { EmotionScreen( state = EmotionState( - emotions = Emotion.entries, + emotionTypeUiModels = listOf( + EmotionUiModel( + emotionType = "emotionType", + imageUrl = "https://bitnagil-s3.s3.ap-northeast-2.amazonaws.com/home_satisfaction.png", + emotionMarbleName = "emotionMarbleName", + offlineBackupImageResourceId = null, + ), + EmotionUiModel( + emotionType = "emotionType", + imageUrl = "https://bitnagil-s3.s3.ap-northeast-2.amazonaws.com/home_satisfaction.png", + emotionMarbleName = "emotionMarbleName", + offlineBackupImageResourceId = null, + ), + ), isLoading = false, step = EmotionScreenStep.Emotion, recommendRoutines = listOf(), @@ -242,7 +254,14 @@ private fun EmotionRecommendRoutineScreenPreview() { BitnagilTheme { EmotionRecommendRoutineScreen( state = EmotionState( - emotions = Emotion.entries, + emotionTypeUiModels = listOf( + EmotionUiModel( + emotionType = "emotionType", + imageUrl = "https://bitnagil-s3.s3.ap-northeast-2.amazonaws.com/home_satisfaction.png", + emotionMarbleName = "emotionMarbleName", + offlineBackupImageResourceId = null, + ), + ), isLoading = false, step = EmotionScreenStep.RecommendRoutines, recommendRoutines = listOf( @@ -254,7 +273,6 @@ private fun EmotionRecommendRoutineScreenPreview() { ), ), ), - onClickPreviousButton = {}, onClickRoutine = {}, onClickRegisterRecommendRoutines = {}, onClickSkip = {}, diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/EmotionViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/EmotionViewModel.kt index 9729b1ec..5e95dd86 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/EmotionViewModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/EmotionViewModel.kt @@ -6,9 +6,9 @@ import com.threegap.bitnagil.domain.emotion.usecase.GetEmotionsUseCase import com.threegap.bitnagil.domain.emotion.usecase.RegisterEmotionUseCase import com.threegap.bitnagil.domain.onboarding.usecase.RegisterRecommendOnBoardingRoutinesUseCase import com.threegap.bitnagil.presentation.common.mviviewmodel.MviViewModel -import com.threegap.bitnagil.presentation.emotion.model.Emotion import com.threegap.bitnagil.presentation.emotion.model.EmotionRecommendRoutineUiModel import com.threegap.bitnagil.presentation.emotion.model.EmotionScreenStep +import com.threegap.bitnagil.presentation.emotion.model.EmotionUiModel import com.threegap.bitnagil.presentation.emotion.model.mvi.EmotionIntent import com.threegap.bitnagil.presentation.emotion.model.mvi.EmotionSideEffect import com.threegap.bitnagil.presentation.emotion.model.mvi.EmotionState @@ -36,7 +36,7 @@ class EmotionViewModel @Inject constructor( getEmotionsUseCase().fold( onSuccess = { emotions -> sendIntent( - EmotionIntent.EmotionListLoadSuccess(emotions = emotions.map { Emotion.fromDomain(it) }), + EmotionIntent.EmotionListLoadSuccess(emotionTypeUiModels = emotions.map { EmotionUiModel.fromDomain(it) }), ) }, onFailure = { @@ -50,7 +50,7 @@ class EmotionViewModel @Inject constructor( when (intent) { is EmotionIntent.EmotionListLoadSuccess -> { return state.copy( - emotions = intent.emotions, + emotionTypeUiModels = intent.emotionTypeUiModels, isLoading = false, ) } @@ -106,13 +106,13 @@ class EmotionViewModel @Inject constructor( } } - fun selectEmotion(emotion: Emotion) { + fun selectEmotion(emotionType: String) { val isLoading = stateFlow.value.isLoading if (isLoading) return viewModelScope.launch { sendIntent(EmotionIntent.RegisterEmotionLoading) - registerEmotionUseCase(emotion = emotion.toDomain()).fold( + registerEmotionUseCase(emotionType = emotionType).fold( onSuccess = { emotionRecommendRoutines -> val recommendRoutines = emotionRecommendRoutines.map { EmotionRecommendRoutineUiModel.fromEmotionRecommendRoutine(it) } sendIntent(EmotionIntent.RegisterEmotionSuccess(recommendRoutines)) diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/Emotion.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/Emotion.kt deleted file mode 100644 index 415935a5..00000000 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/Emotion.kt +++ /dev/null @@ -1,41 +0,0 @@ -package com.threegap.bitnagil.presentation.emotion.model - -import com.threegap.bitnagil.designsystem.R -import com.threegap.bitnagil.domain.emotion.model.Emotion as DomainEmotion - -enum class Emotion( - val emotionName: String, - val imageResourceId: Int, -) { - CALM(emotionName = "평온함", imageResourceId = R.drawable.calm), - VITALITY(emotionName = "활기참", imageResourceId = R.drawable.vitality), - LETHARGY(emotionName = "무기력함", imageResourceId = R.drawable.lethargy), - ANXIETY(emotionName = "불안함", imageResourceId = R.drawable.anxiety), - SATISFACTION(emotionName = "만족함", imageResourceId = R.drawable.satisfaction), - FATIGUE(emotionName = "피로함", imageResourceId = R.drawable.fatigue), - ; - - companion object { - fun fromDomain(domain: DomainEmotion): Emotion { - return when (domain) { - DomainEmotion.CALM -> CALM - DomainEmotion.VITALITY -> VITALITY - DomainEmotion.LETHARGY -> LETHARGY - DomainEmotion.ANXIETY -> ANXIETY - DomainEmotion.SATISFACTION -> SATISFACTION - DomainEmotion.FATIGUE -> FATIGUE - } - } - } - - fun toDomain(): DomainEmotion { - return when (this) { - CALM -> DomainEmotion.CALM - VITALITY -> DomainEmotion.VITALITY - LETHARGY -> DomainEmotion.LETHARGY - ANXIETY -> DomainEmotion.ANXIETY - SATISFACTION -> DomainEmotion.SATISFACTION - FATIGUE -> DomainEmotion.FATIGUE - } - } -} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/EmotionUiModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/EmotionUiModel.kt new file mode 100644 index 00000000..4f575688 --- /dev/null +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/EmotionUiModel.kt @@ -0,0 +1,35 @@ +package com.threegap.bitnagil.presentation.emotion.model + +import android.os.Parcelable +import com.threegap.bitnagil.designsystem.R +import com.threegap.bitnagil.domain.emotion.model.Emotion +import kotlinx.parcelize.Parcelize + +@Parcelize +data class EmotionUiModel( + val emotionType: String, + val emotionMarbleName: String, + val imageUrl: String, + val offlineBackupImageResourceId: Int?, +) : Parcelable { + companion object { + fun fromDomain(emotion: Emotion) = EmotionUiModel( + emotionType = emotion.emotionType, + emotionMarbleName = emotion.emotionMarbleName, + imageUrl = emotion.imageUrl, + offlineBackupImageResourceId = getOfflineBackupImageResourceId(emotion.emotionType), + ) + + private fun getOfflineBackupImageResourceId(emotionType: String): Int? { + return when (emotionType) { + "CALM" -> R.drawable.calm + "VITALITY" -> R.drawable.vitality + "LETHARGY" -> R.drawable.lethargy + "ANXIETY" -> R.drawable.anxiety + "SATISFACTION" -> R.drawable.satisfaction + "FATIGUE" -> R.drawable.fatigue + else -> null + } + } + } +} diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/mvi/EmotionIntent.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/mvi/EmotionIntent.kt index 33e2b74f..49b62216 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/mvi/EmotionIntent.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/mvi/EmotionIntent.kt @@ -1,11 +1,11 @@ package com.threegap.bitnagil.presentation.emotion.model.mvi import com.threegap.bitnagil.presentation.common.mviviewmodel.MviIntent -import com.threegap.bitnagil.presentation.emotion.model.Emotion import com.threegap.bitnagil.presentation.emotion.model.EmotionRecommendRoutineUiModel +import com.threegap.bitnagil.presentation.emotion.model.EmotionUiModel sealed class EmotionIntent : MviIntent { - data class EmotionListLoadSuccess(val emotions: List) : EmotionIntent() + data class EmotionListLoadSuccess(val emotionTypeUiModels: List) : EmotionIntent() data class RegisterEmotionSuccess(val recommendRoutines: List) : EmotionIntent() data object RegisterEmotionLoading : EmotionIntent() data object RegisterRecommendRoutinesLoading : EmotionIntent() diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/mvi/EmotionState.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/mvi/EmotionState.kt index 511c1b76..3e016ddb 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/mvi/EmotionState.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/mvi/EmotionState.kt @@ -2,22 +2,22 @@ package com.threegap.bitnagil.presentation.emotion.model.mvi import androidx.compose.runtime.Immutable import com.threegap.bitnagil.presentation.common.mviviewmodel.MviState -import com.threegap.bitnagil.presentation.emotion.model.Emotion import com.threegap.bitnagil.presentation.emotion.model.EmotionRecommendRoutineUiModel import com.threegap.bitnagil.presentation.emotion.model.EmotionScreenStep +import com.threegap.bitnagil.presentation.emotion.model.EmotionUiModel import kotlinx.parcelize.Parcelize @Parcelize @Immutable data class EmotionState( - val emotions: List, + val emotionTypeUiModels: List, val isLoading: Boolean, val recommendRoutines: List, val step: EmotionScreenStep, ) : MviState { companion object { val Init = EmotionState( - emotions = emptyList(), + emotionTypeUiModels = emptyList(), isLoading = true, recommendRoutines = emptyList(), step = EmotionScreenStep.Emotion, diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt index 4d560b72..0a0a3fc8 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/HomeViewModel.kt @@ -3,7 +3,8 @@ package com.threegap.bitnagil.presentation.home import android.util.Log import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewModelScope -import com.threegap.bitnagil.domain.emotion.usecase.GetMyEmotionUseCase +import com.threegap.bitnagil.domain.emotion.usecase.GetEmotionChangeEventFlowUseCase +import com.threegap.bitnagil.domain.emotion.usecase.GetEmotionUseCase import com.threegap.bitnagil.domain.routine.model.RoutineCompletion import com.threegap.bitnagil.domain.routine.model.RoutineCompletionInfo import com.threegap.bitnagil.domain.routine.usecase.DeleteRoutineByDayUseCase @@ -40,11 +41,12 @@ class HomeViewModel @Inject constructor( savedStateHandle: SavedStateHandle, private val fetchWeeklyRoutinesUseCase: FetchWeeklyRoutinesUseCase, private val fetchUserProfileUseCase: FetchUserProfileUseCase, - private val getMyEmotionUseCase: GetMyEmotionUseCase, + private val getEmotionUseCase: GetEmotionUseCase, private val routineCompletionUseCase: RoutineCompletionUseCase, private val deleteRoutineUseCase: DeleteRoutineUseCase, private val deleteRoutineByDayUseCase: DeleteRoutineByDayUseCase, private val getWriteRoutineEventFlowUseCase: GetWriteRoutineEventFlowUseCase, + private val getEmotionChangeEventFlowUseCase: GetEmotionChangeEventFlowUseCase, ) : MviViewModel( initState = HomeState(), savedStateHandle = savedStateHandle, @@ -55,6 +57,7 @@ class HomeViewModel @Inject constructor( init { observeWriteRoutineEvent() + observeEmotionChangeEvent() observeWeekChanges() observeRoutineUpdates() fetchWeeklyRoutines(container.stateFlow.value.currentWeeks) @@ -234,7 +237,16 @@ class HomeViewModel @Inject constructor( private fun observeWriteRoutineEvent() { viewModelScope.launch { getWriteRoutineEventFlowUseCase().collect { - fetchWeeklyRoutines(container.stateFlow.value.currentWeeks) + fetchWeeklyRoutines(stateFlow.value.currentWeeks) + } + } + } + + private fun observeEmotionChangeEvent() { + viewModelScope.launch { + getEmotionChangeEventFlowUseCase().collect { + val currentDate = LocalDate.now() + getMyEmotion(currentDate) } } } @@ -302,9 +314,9 @@ class HomeViewModel @Inject constructor( private fun getMyEmotion(currentDate: LocalDate) { sendIntent(HomeIntent.UpdateLoading(true)) viewModelScope.launch { - getMyEmotionUseCase(currentDate.toString()).fold( + getEmotionUseCase(currentDate.toString()).fold( onSuccess = { emotion -> - val ballType = EmotionBallType.fromDomainEmotion(emotion.emotionMarbleType) + val ballType = EmotionBallType.fromDomainEmotion(emotion?.emotionType) sendIntent(HomeIntent.LoadMyEmotion(ballType)) sendIntent(HomeIntent.UpdateLoading(false)) }, diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/EmotionBallType.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/EmotionBallType.kt index 6e4fbfd0..a829f0c0 100644 --- a/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/EmotionBallType.kt +++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/home/model/EmotionBallType.kt @@ -3,7 +3,6 @@ package com.threegap.bitnagil.presentation.home.model import androidx.annotation.DrawableRes import androidx.compose.ui.graphics.Color import com.threegap.bitnagil.designsystem.R -import com.threegap.bitnagil.domain.emotion.model.Emotion enum class EmotionBallType( @DrawableRes val drawableId: Int, @@ -43,7 +42,7 @@ enum class EmotionBallType( ; companion object { - fun fromDomainEmotion(emotion: Emotion?): EmotionBallType? = - emotion?.let { valueOf(it.name) } + fun fromDomainEmotion(emotionMarbleType: String?): EmotionBallType? = + emotionMarbleType?.let { valueOf(it) } } }