diff --git a/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/typography/Type.kt b/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/typography/Type.kt
index fb304e13..b6fae062 100644
--- a/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/typography/Type.kt
+++ b/core/designsystem/src/main/java/com/threegap/bitnagil/designsystem/typography/Type.kt
@@ -101,6 +101,13 @@ class BitnagilTypography internal constructor(
lineHeight = 30,
letterSpacing = (-0.5f),
),
+ private val _cafe24SsurroundAir2: BitnagilTextStyle = BitnagilTextStyle(
+ fontFamily = cafe24SsurroundAir,
+ fontWeight = FontWeight.Light,
+ fontSize = 16,
+ lineHeight = 24,
+ letterSpacing = (-0.5f),
+ ),
) {
private val _headline1Bold = _headline1.copy(fontWeight = FontWeight.Bold)
private val _headline1Medium = _headline1.copy(fontWeight = FontWeight.Medium)
@@ -163,6 +170,7 @@ class BitnagilTypography internal constructor(
val button1: TextStyle @Composable get() = _button1.toDpTextStyle
val button2: TextStyle @Composable get() = _button2.toDpTextStyle
val cafe24SsurroundAir: TextStyle @Composable get() = _cafe24SsurroundAir.toDpTextStyle
+ val cafe24SsurroundAir2: TextStyle @Composable get() = _cafe24SsurroundAir2.toDpTextStyle
}
internal val LocalBitnagilTypography = staticCompositionLocalOf { BitnagilTypography() }
diff --git a/core/designsystem/src/main/res/drawable-hdpi/img_ground.png b/core/designsystem/src/main/res/drawable-hdpi/img_ground.png
new file mode 100644
index 00000000..dfa97bde
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-hdpi/img_ground.png differ
diff --git a/core/designsystem/src/main/res/drawable-hdpi/img_marble_pomo.png b/core/designsystem/src/main/res/drawable-hdpi/img_marble_pomo.png
new file mode 100644
index 00000000..792cb974
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-hdpi/img_marble_pomo.png differ
diff --git a/core/designsystem/src/main/res/drawable-hdpi/img_marble_pomo_left_hand.png b/core/designsystem/src/main/res/drawable-hdpi/img_marble_pomo_left_hand.png
new file mode 100644
index 00000000..75b024b0
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-hdpi/img_marble_pomo_left_hand.png differ
diff --git a/core/designsystem/src/main/res/drawable-hdpi/img_marble_pomo_right_hand.png b/core/designsystem/src/main/res/drawable-hdpi/img_marble_pomo_right_hand.png
new file mode 100644
index 00000000..c919fde2
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-hdpi/img_marble_pomo_right_hand.png differ
diff --git a/core/designsystem/src/main/res/drawable-hdpi/img_pomo_hand.png b/core/designsystem/src/main/res/drawable-hdpi/img_pomo_hand.png
new file mode 100644
index 00000000..0ade69b2
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-hdpi/img_pomo_hand.png differ
diff --git a/core/designsystem/src/main/res/drawable-hdpi/img_pomo_thumb.png b/core/designsystem/src/main/res/drawable-hdpi/img_pomo_thumb.png
new file mode 100644
index 00000000..bb860b73
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-hdpi/img_pomo_thumb.png differ
diff --git a/core/designsystem/src/main/res/drawable-mdpi/img_ground.png b/core/designsystem/src/main/res/drawable-mdpi/img_ground.png
new file mode 100644
index 00000000..1b902aeb
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-mdpi/img_ground.png differ
diff --git a/core/designsystem/src/main/res/drawable-mdpi/img_marble_pomo.png b/core/designsystem/src/main/res/drawable-mdpi/img_marble_pomo.png
new file mode 100644
index 00000000..b41eaf33
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-mdpi/img_marble_pomo.png differ
diff --git a/core/designsystem/src/main/res/drawable-mdpi/img_marble_pomo_left_hand.png b/core/designsystem/src/main/res/drawable-mdpi/img_marble_pomo_left_hand.png
new file mode 100644
index 00000000..86e9b387
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-mdpi/img_marble_pomo_left_hand.png differ
diff --git a/core/designsystem/src/main/res/drawable-mdpi/img_marble_pomo_right_hand.png b/core/designsystem/src/main/res/drawable-mdpi/img_marble_pomo_right_hand.png
new file mode 100644
index 00000000..98dc0131
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-mdpi/img_marble_pomo_right_hand.png differ
diff --git a/core/designsystem/src/main/res/drawable-mdpi/img_pomo_hand.png b/core/designsystem/src/main/res/drawable-mdpi/img_pomo_hand.png
new file mode 100644
index 00000000..bd6f21ae
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-mdpi/img_pomo_hand.png differ
diff --git a/core/designsystem/src/main/res/drawable-mdpi/img_pomo_thumb.png b/core/designsystem/src/main/res/drawable-mdpi/img_pomo_thumb.png
new file mode 100644
index 00000000..ce53a107
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-mdpi/img_pomo_thumb.png differ
diff --git a/core/designsystem/src/main/res/drawable-xhdpi/img_ground.png b/core/designsystem/src/main/res/drawable-xhdpi/img_ground.png
new file mode 100644
index 00000000..edfb908d
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xhdpi/img_ground.png differ
diff --git a/core/designsystem/src/main/res/drawable-xhdpi/img_marble_pomo.png b/core/designsystem/src/main/res/drawable-xhdpi/img_marble_pomo.png
new file mode 100644
index 00000000..6a6f004f
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xhdpi/img_marble_pomo.png differ
diff --git a/core/designsystem/src/main/res/drawable-xhdpi/img_marble_pomo_left_hand.png b/core/designsystem/src/main/res/drawable-xhdpi/img_marble_pomo_left_hand.png
new file mode 100644
index 00000000..dd7235a3
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xhdpi/img_marble_pomo_left_hand.png differ
diff --git a/core/designsystem/src/main/res/drawable-xhdpi/img_marble_pomo_right_hand.png b/core/designsystem/src/main/res/drawable-xhdpi/img_marble_pomo_right_hand.png
new file mode 100644
index 00000000..52e9805d
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xhdpi/img_marble_pomo_right_hand.png differ
diff --git a/core/designsystem/src/main/res/drawable-xhdpi/img_pomo_hand.png b/core/designsystem/src/main/res/drawable-xhdpi/img_pomo_hand.png
new file mode 100644
index 00000000..463aea54
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xhdpi/img_pomo_hand.png differ
diff --git a/core/designsystem/src/main/res/drawable-xhdpi/img_pomo_thumb.png b/core/designsystem/src/main/res/drawable-xhdpi/img_pomo_thumb.png
new file mode 100644
index 00000000..cdc7a32c
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xhdpi/img_pomo_thumb.png differ
diff --git a/core/designsystem/src/main/res/drawable-xxhdpi/img_ground.png b/core/designsystem/src/main/res/drawable-xxhdpi/img_ground.png
new file mode 100644
index 00000000..4913c427
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxhdpi/img_ground.png differ
diff --git a/core/designsystem/src/main/res/drawable-xxhdpi/img_marble_pomo.png b/core/designsystem/src/main/res/drawable-xxhdpi/img_marble_pomo.png
new file mode 100644
index 00000000..96074d79
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxhdpi/img_marble_pomo.png differ
diff --git a/core/designsystem/src/main/res/drawable-xxhdpi/img_marble_pomo_left_hand.png b/core/designsystem/src/main/res/drawable-xxhdpi/img_marble_pomo_left_hand.png
new file mode 100644
index 00000000..19fbf123
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxhdpi/img_marble_pomo_left_hand.png differ
diff --git a/core/designsystem/src/main/res/drawable-xxhdpi/img_marble_pomo_right_hand.png b/core/designsystem/src/main/res/drawable-xxhdpi/img_marble_pomo_right_hand.png
new file mode 100644
index 00000000..abd3c12a
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxhdpi/img_marble_pomo_right_hand.png differ
diff --git a/core/designsystem/src/main/res/drawable-xxhdpi/img_pomo_hand.png b/core/designsystem/src/main/res/drawable-xxhdpi/img_pomo_hand.png
new file mode 100644
index 00000000..fa2d6c0f
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxhdpi/img_pomo_hand.png differ
diff --git a/core/designsystem/src/main/res/drawable-xxhdpi/img_pomo_thumb.png b/core/designsystem/src/main/res/drawable-xxhdpi/img_pomo_thumb.png
new file mode 100644
index 00000000..f193a641
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxhdpi/img_pomo_thumb.png differ
diff --git a/core/designsystem/src/main/res/drawable-xxxhdpi/img_ground.png b/core/designsystem/src/main/res/drawable-xxxhdpi/img_ground.png
new file mode 100644
index 00000000..29223375
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxxhdpi/img_ground.png differ
diff --git a/core/designsystem/src/main/res/drawable-xxxhdpi/img_marble_pomo.png b/core/designsystem/src/main/res/drawable-xxxhdpi/img_marble_pomo.png
new file mode 100644
index 00000000..ba23b024
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxxhdpi/img_marble_pomo.png differ
diff --git a/core/designsystem/src/main/res/drawable-xxxhdpi/img_marble_pomo_left_hand.png b/core/designsystem/src/main/res/drawable-xxxhdpi/img_marble_pomo_left_hand.png
new file mode 100644
index 00000000..872ef5be
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxxhdpi/img_marble_pomo_left_hand.png differ
diff --git a/core/designsystem/src/main/res/drawable-xxxhdpi/img_marble_pomo_right_hand.png b/core/designsystem/src/main/res/drawable-xxxhdpi/img_marble_pomo_right_hand.png
new file mode 100644
index 00000000..418d664c
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxxhdpi/img_marble_pomo_right_hand.png differ
diff --git a/core/designsystem/src/main/res/drawable-xxxhdpi/img_pomo_hand.png b/core/designsystem/src/main/res/drawable-xxxhdpi/img_pomo_hand.png
new file mode 100644
index 00000000..7f66eee4
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxxhdpi/img_pomo_hand.png differ
diff --git a/core/designsystem/src/main/res/drawable-xxxhdpi/img_pomo_thumb.png b/core/designsystem/src/main/res/drawable-xxxhdpi/img_pomo_thumb.png
new file mode 100644
index 00000000..6be2bfd2
Binary files /dev/null and b/core/designsystem/src/main/res/drawable-xxxhdpi/img_pomo_thumb.png differ
diff --git a/core/designsystem/src/main/res/drawable/ic_double_down_arrow_24.xml b/core/designsystem/src/main/res/drawable/ic_double_down_arrow_24.xml
new file mode 100644
index 00000000..03621063
--- /dev/null
+++ b/core/designsystem/src/main/res/drawable/ic_double_down_arrow_24.xml
@@ -0,0 +1,20 @@
+
+
+
+
diff --git a/core/designsystem/src/main/res/drawable/ic_double_left_arrow_24.xml b/core/designsystem/src/main/res/drawable/ic_double_left_arrow_24.xml
new file mode 100644
index 00000000..e52330f7
--- /dev/null
+++ b/core/designsystem/src/main/res/drawable/ic_double_left_arrow_24.xml
@@ -0,0 +1,20 @@
+
+
+
+
diff --git a/core/designsystem/src/main/res/drawable/ic_double_right_arrow_24.xml b/core/designsystem/src/main/res/drawable/ic_double_right_arrow_24.xml
new file mode 100644
index 00000000..23398e2f
--- /dev/null
+++ b/core/designsystem/src/main/res/drawable/ic_double_right_arrow_24.xml
@@ -0,0 +1,20 @@
+
+
+
+
diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/common/dimension/DpToPx.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/common/dimension/DpToPx.kt
new file mode 100644
index 00000000..bd006739
--- /dev/null
+++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/common/dimension/DpToPx.kt
@@ -0,0 +1,8 @@
+package com.threegap.bitnagil.presentation.common.dimension
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Dp
+
+@Composable
+fun Dp.dpToPx() = with(LocalDensity.current) { this@dpToPx.toPx() }
diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/common/dimension/PxToDp.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/common/dimension/PxToDp.kt
new file mode 100644
index 00000000..9ef3348d
--- /dev/null
+++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/common/dimension/PxToDp.kt
@@ -0,0 +1,10 @@
+package com.threegap.bitnagil.presentation.common.dimension
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalDensity
+
+@Composable
+fun Float.pxToDp() = with(LocalDensity.current) { this@pxToDp.toDp() }
+
+@Composable
+fun Int.pxToDp() = with(LocalDensity.current) { this@pxToDp.toDp() }
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 f1f4bd68..c8e09327 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,50 +1,23 @@
package com.threegap.bitnagil.presentation.emotion
import androidx.activity.compose.BackHandler
-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.BoxWithConstraints
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.statusBarsPadding
-import androidx.compose.foundation.lazy.grid.GridCells
-import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
-import androidx.compose.foundation.lazy.grid.items
-import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.verticalScroll
-import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
-import androidx.compose.ui.Alignment
+import androidx.compose.runtime.remember
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.BitnagilTopBar
-import com.threegap.bitnagil.designsystem.modifier.clickableWithoutRipple
+import com.threegap.bitnagil.presentation.common.dimension.pxToDp
import com.threegap.bitnagil.presentation.common.flow.collectAsEffect
-import com.threegap.bitnagil.presentation.emotion.model.EmotionRecommendRoutineUiModel
+import com.threegap.bitnagil.presentation.common.toast.GlobalBitnagilToast
+import com.threegap.bitnagil.presentation.emotion.component.template.EmotionRecommendRoutineScreen
+import com.threegap.bitnagil.presentation.emotion.component.template.SimpleEmotionSelectionScreen
+import com.threegap.bitnagil.presentation.emotion.component.template.SwipeEmotionSelectionScreen
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
@Composable
fun EmotionScreenContainer(
@@ -60,15 +33,32 @@ fun EmotionScreenContainer(
viewModel.sideEffectFlow.collectAsEffect { sideEffect ->
when (sideEffect) {
EmotionSideEffect.NavigateToBack -> navigateToBack()
+ is EmotionSideEffect.ShowToast -> GlobalBitnagilToast.showWarning(sideEffect.message)
}
}
when (state.step) {
- EmotionScreenStep.Emotion -> EmotionScreen(
- state = state,
- onClickPreviousButton = navigateToBack,
- onClickEmotion = viewModel::selectEmotion,
- )
+ EmotionScreenStep.Emotion -> BoxWithConstraints(modifier = Modifier.fillMaxWidth()) {
+ val height = constraints.maxHeight.pxToDp()
+
+ if (height > 600.dp) {
+ SwipeEmotionSelectionScreen(
+ state = state,
+ onClickPreviousButton = navigateToBack,
+ onSelectEmotion = remember {
+ { emotionType ->
+ viewModel.selectEmotion(emotionType = emotionType, minimumDelay = 1000)
+ }
+ },
+ )
+ } else {
+ SimpleEmotionSelectionScreen(
+ state = state,
+ onClickPreviousButton = navigateToBack,
+ onClickEmotion = viewModel::selectEmotion,
+ )
+ }
+ }
EmotionScreenStep.RecommendRoutines -> EmotionRecommendRoutineScreen(
state = state,
onClickRoutine = viewModel::selectRecommendRoutine,
@@ -77,208 +67,3 @@ fun EmotionScreenContainer(
)
}
}
-
-@Composable
-private fun EmotionScreen(
- state: EmotionState,
- onClickPreviousButton: () -> Unit,
- onClickEmotion: (String) -> Unit,
-) {
- Column(
- modifier = Modifier
- .fillMaxSize()
- .background(color = BitnagilTheme.colors.white)
- .statusBarsPadding(),
- horizontalAlignment = Alignment.CenterHorizontally,
- ) {
- BitnagilTopBar(
- showBackButton = true,
- onBackClick = onClickPreviousButton,
- )
-
- Spacer(modifier = Modifier.height(32.dp))
-
- Text(
- "오늘의 감정구슬을 골라보세요",
- style = BitnagilTheme.typography.title2Bold.copy(color = BitnagilTheme.colors.navy500),
- textAlign = TextAlign.Center,
- )
-
- Spacer(modifier = Modifier.height(6.dp))
-
- Text(
- "감정구슬을 등록하면 루틴을 추천받아요!",
- style = BitnagilTheme.typography.subtitle1Regular.copy(color = BitnagilTheme.colors.navy300),
- textAlign = TextAlign.Center,
- )
-
- Spacer(modifier = Modifier.height(64.dp))
-
- LazyVerticalGrid(
- columns = GridCells.Fixed(3),
- modifier = Modifier.padding(horizontal = 24.dp),
- horizontalArrangement = Arrangement.spacedBy(12.dp),
- verticalArrangement = Arrangement.spacedBy(28.dp),
- ) {
- items(state.emotionTypeUiModels) { emotion ->
- Column(
- modifier = Modifier
- .clickableWithoutRipple { onClickEmotion(emotion.emotionType) },
- horizontalAlignment = Alignment.CenterHorizontally,
- ) {
- 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.emotionMarbleName,
- style = BitnagilTheme.typography.body1Regular.copy(color = BitnagilTheme.colors.coolGray20),
- )
- }
- }
- }
- }
-}
-
-@Composable
-private fun EmotionRecommendRoutineScreen(
- state: EmotionState,
- onClickRoutine: (String) -> Unit,
- onClickRegisterRecommendRoutines: () -> Unit,
- onClickSkip: () -> Unit,
-) {
- Column(
- modifier = Modifier
- .fillMaxSize()
- .background(color = BitnagilTheme.colors.coolGray99)
- .statusBarsPadding()
- .padding(start = 16.dp, end = 16.dp, bottom = 20.dp, top = 32.dp),
- ) {
- 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)
- .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),
- )
- }
- }
-
- Spacer(modifier = Modifier.height(12.dp))
-
- BitnagilTextButton(
- text = "변경하기",
- onClick = onClickRegisterRecommendRoutines,
- enabled = state.registerRecommendRoutinesButtonEnabled,
- modifier = Modifier.fillMaxWidth(),
- )
-
- 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,
- modifier = Modifier.fillMaxWidth(),
- )
- }
-}
-
-@Preview
-@Composable
-private fun EmotionScreenPreview() {
- BitnagilTheme {
- EmotionScreen(
- state = EmotionState(
- 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(),
- ),
- onClickEmotion = { _ -> },
- onClickPreviousButton = {},
- )
- }
-}
-
-@Preview
-@Composable
-private fun EmotionRecommendRoutineScreenPreview() {
- BitnagilTheme {
- EmotionRecommendRoutineScreen(
- state = EmotionState(
- 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(
- EmotionRecommendRoutineUiModel(
- id = "1",
- name = "루틴 이름",
- description = "루틴 설명",
- selected = true,
- ),
- ),
- ),
- 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 5e95dd86..72cf4913 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
@@ -13,6 +13,7 @@ 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
import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.orbitmvi.orbit.syntax.simple.SimpleSyntax
import javax.inject.Inject
@@ -59,11 +60,13 @@ class EmotionViewModel @Inject constructor(
recommendRoutines = intent.recommendRoutines,
step = EmotionScreenStep.RecommendRoutines,
isLoading = false,
+ showLoadingView = false,
)
}
EmotionIntent.RegisterEmotionLoading -> {
return state.copy(
isLoading = true,
+ showLoadingView = true,
)
}
EmotionIntent.RegisterRecommendRoutinesLoading -> {
@@ -103,22 +106,35 @@ class EmotionViewModel @Inject constructor(
sendSideEffect(EmotionSideEffect.NavigateToBack)
return null
}
+
+ is EmotionIntent.RegisterEmotionFailure -> {
+ sendSideEffect(EmotionSideEffect.ShowToast(intent.message))
+ sendSideEffect(EmotionSideEffect.NavigateToBack)
+ return null
+ }
}
}
- fun selectEmotion(emotionType: String) {
+ fun selectEmotion(emotionType: String, minimumDelay: Long = 0) {
val isLoading = stateFlow.value.isLoading
if (isLoading) return
viewModelScope.launch {
sendIntent(EmotionIntent.RegisterEmotionLoading)
+
+ if (minimumDelay > 0) {
+ delay(minimumDelay)
+ }
+
registerEmotionUseCase(emotionType = emotionType).fold(
onSuccess = { emotionRecommendRoutines ->
val recommendRoutines = emotionRecommendRoutines.map { EmotionRecommendRoutineUiModel.fromEmotionRecommendRoutine(it) }
sendIntent(EmotionIntent.RegisterEmotionSuccess(recommendRoutines))
},
onFailure = {
- // todo 실패 케이스 정의되면 처리
+ sendIntent(
+ EmotionIntent.RegisterEmotionFailure(message = it.message ?: "에러가 발생했습니다. 잠시 후 시도해주세요."),
+ )
},
)
}
diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/atom/EmotionMarbleImage.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/atom/EmotionMarbleImage.kt
new file mode 100644
index 00000000..3bf89cd4
--- /dev/null
+++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/atom/EmotionMarbleImage.kt
@@ -0,0 +1,42 @@
+package com.threegap.bitnagil.presentation.emotion.component.atom
+
+import androidx.compose.foundation.Image
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import coil3.compose.AsyncImage
+import coil3.request.ImageRequest
+import coil3.request.crossfade
+import com.threegap.bitnagil.presentation.emotion.model.EmotionImageUiModel
+
+@Composable
+fun EmotionMarbleImage(
+ modifier: Modifier,
+ image: EmotionImageUiModel,
+ contentDescription: String? = null,
+ alpha: Float = 1f,
+) {
+ when (image) {
+ is EmotionImageUiModel.Url -> {
+ AsyncImage(
+ model = ImageRequest.Builder(LocalContext.current)
+ .data(image.url)
+ .crossfade(true)
+ .build(),
+ modifier = modifier,
+ contentDescription = contentDescription,
+ alpha = alpha,
+ error = image.offlineBackupImageResourceId?.let { painterResource(it) },
+ )
+ }
+ is EmotionImageUiModel.Resource -> {
+ Image(
+ painter = painterResource(id = image.resourceId),
+ contentDescription = contentDescription,
+ modifier = modifier,
+ alpha = alpha,
+ )
+ }
+ }
+}
diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/atom/SpeechBubbleText.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/atom/SpeechBubbleText.kt
new file mode 100644
index 00000000..1d90b7a7
--- /dev/null
+++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/atom/SpeechBubbleText.kt
@@ -0,0 +1,87 @@
+package com.threegap.bitnagil.presentation.emotion.component.atom
+
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.threegap.bitnagil.designsystem.BitnagilTheme
+
+@Composable
+fun SpeechBubbleText(
+ text: String,
+ backgroundColor: Long,
+ textColor: Long,
+ modifier: Modifier = Modifier,
+ maxLines: Int = 2,
+ minLines: Int = 2,
+) {
+ Column(
+ modifier = modifier,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Box(
+ modifier = Modifier
+ .background(Color(backgroundColor), shape = RoundedCornerShape(12.dp))
+ .padding(20.dp),
+ contentAlignment = Alignment.Center,
+ ) {
+ Text(
+ text = text,
+ style = BitnagilTheme.typography.cafe24SsurroundAir2.copy(color = Color(textColor)),
+ textAlign = TextAlign.Center,
+ maxLines = maxLines,
+ minLines = minLines,
+ )
+ }
+
+ Canvas(
+ modifier = Modifier
+ .height(10.dp)
+ .width(24.dp),
+ ) {
+ val path = Path().apply {
+ moveTo(0f, 0f)
+ lineTo(size.width, 0f)
+ lineTo(size.width / 2, size.height)
+ lineTo(0f, 0f)
+ close()
+ }
+ drawPath(path, color = Color(backgroundColor))
+ }
+ }
+}
+
+@Preview
+@Composable
+private fun SpeechBubbleTextPreview() {
+ BitnagilTheme {
+ Column {
+ SpeechBubbleText(
+ text = "hi",
+ backgroundColor = 0xFFFFFFFF,
+ textColor = 0xFF000000,
+ minLines = 1,
+ )
+
+ SpeechBubbleText(
+ text = "안녕하세요?",
+ backgroundColor = 0xFF000000,
+ textColor = 0xFFFFFFFF,
+ minLines = 1,
+ )
+ }
+ }
+}
diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/EmotionLoadingView.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/EmotionLoadingView.kt
new file mode 100644
index 00000000..fb58ed21
--- /dev/null
+++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/EmotionLoadingView.kt
@@ -0,0 +1,190 @@
+package com.threegap.bitnagil.presentation.emotion.component.template
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.zIndex
+import com.threegap.bitnagil.designsystem.BitnagilTheme
+import com.threegap.bitnagil.designsystem.R
+import com.threegap.bitnagil.presentation.common.dimension.pxToDp
+import com.threegap.bitnagil.presentation.emotion.component.atom.EmotionMarbleImage
+import com.threegap.bitnagil.presentation.emotion.component.atom.SpeechBubbleText
+import com.threegap.bitnagil.presentation.emotion.model.EmotionUiModel
+
+@Composable
+fun EmotionLoadingView(
+ emotion: EmotionUiModel,
+) {
+ Box(
+ modifier = Modifier.fillMaxSize().background(BitnagilTheme.colors.white),
+ ) {
+ BackgroundBox(modifier = Modifier.fillMaxSize())
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .align(Alignment.BottomCenter),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ SpeechBubbleText(
+ text = getRecommendRoutineText(emotion),
+ backgroundColor = 0xFF000000,
+ textColor = 0xFFFFFFFF,
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Box(
+ modifier = Modifier
+ .width(200.dp)
+ .height(280.dp)
+ .offset(y = 16.dp)
+ .zIndex(1f),
+ ) {
+ Image(
+ painter = painterResource(R.drawable.img_marble_pomo),
+ contentDescription = null,
+ )
+
+ Image(
+ modifier = Modifier
+ .offset(y = 117.dp, x = 18.dp)
+ .zIndex(2f),
+ painter = painterResource(R.drawable.img_marble_pomo_left_hand),
+ contentDescription = null,
+ )
+
+ Image(
+ modifier = Modifier
+ .align(Alignment.TopEnd)
+ .offset(y = 117.dp, x = (-17).dp)
+ .zIndex(2f),
+ painter = painterResource(R.drawable.img_marble_pomo_right_hand),
+ contentDescription = null,
+ )
+
+ EmotionMarbleImage(
+ modifier = Modifier
+ .size(120.dp)
+ .align(Alignment.TopCenter)
+ .offset(y = 97.dp)
+ .zIndex(1f),
+ image = emotion.image,
+ )
+ }
+
+ Image(
+ modifier = Modifier.height(150.dp).fillMaxWidth(),
+ painter = painterResource(R.drawable.img_ground),
+ contentDescription = null,
+ contentScale = ContentScale.FillBounds,
+ )
+ }
+ }
+}
+
+@Composable
+private fun BackgroundBox(modifier: Modifier = Modifier) {
+ BoxWithConstraints(modifier = modifier) {
+ val height = constraints.maxHeight
+ val width = constraints.maxWidth
+
+ BackgroundIcon(
+ iconResourceId = R.drawable.ic_wakeup,
+ offsetX = (width * 0.03f).pxToDp(),
+ offsetY = (height * 0.03f).pxToDp(),
+ )
+
+ BackgroundIcon(
+ iconResourceId = R.drawable.ic_rest,
+ offsetX = (width * 0.42f).pxToDp(),
+ offsetY = (height * 0.08f).pxToDp(),
+ )
+
+ BackgroundIcon(
+ iconResourceId = R.drawable.ic_shine,
+ offsetX = (width * 0.87f).pxToDp(),
+ offsetY = (height * 0.05f).pxToDp(),
+ )
+
+ BackgroundIcon(
+ iconResourceId = R.drawable.ic_grow,
+ offsetX = (width * -0.05f).pxToDp(),
+ offsetY = (height * 0.27f).pxToDp(),
+ )
+
+ BackgroundIcon(
+ iconResourceId = R.drawable.img_start_time,
+ offsetX = (width * 0.66f).pxToDp(),
+ offsetY = (height * 0.25f).pxToDp(),
+ )
+
+ BackgroundIcon(
+ iconResourceId = R.drawable.ic_outside,
+ offsetX = (width * -0.14f).pxToDp(),
+ offsetY = (height * 0.63f).pxToDp(),
+ )
+
+ BackgroundIcon(
+ iconResourceId = R.drawable.ic_connect,
+ offsetX = (width * 0.20f).pxToDp(),
+ offsetY = (height * 0.46f).pxToDp(),
+ )
+
+ BackgroundIcon(
+ iconResourceId = R.drawable.img_routine_period,
+ offsetX = (width * 0.93f).pxToDp(),
+ offsetY = (height * 0.54f).pxToDp(),
+ )
+ }
+}
+
+@Composable
+private fun BackgroundIcon(iconResourceId: Int, offsetX: Dp, offsetY: Dp) {
+ Image(
+ modifier = Modifier.size(72.dp).offset(x = offsetX, y = offsetY),
+ painter = painterResource(iconResourceId),
+ colorFilter = ColorFilter.tint(BitnagilTheme.colors.coolGray98),
+ contentDescription = null,
+ )
+}
+
+private fun getRecommendRoutineText(emotion: EmotionUiModel): String {
+ return when (emotion.emotionType) {
+ "CALM" -> "평온한 하루에 맞춰\n루틴을 추천해드릴게요!"
+ "VITALITY" -> "활기찬 하루에 맞춰\n루틴을 추천해드릴게요!"
+ "LETHARGY" -> "무기력한 하루에 맞춰\n루틴을 추천해드릴게요!"
+ "ANXIETY" -> "불안한 하루에 맞춰\n루틴을 추천해드릴게요!"
+ "SATISFACTION" -> "만족스러운 하루에 맞춰\n루틴을 추천해드릴게요!"
+ "FATIGUE" -> "피곤한 하루에 맞춰\n루틴을 추천해드릴게요!"
+ else -> "감정에 맞춰\n루틴을 추천해드릴게요!"
+ }
+}
+
+@Preview
+@Composable
+private fun EmotionLoadingViewPreview() {
+ BitnagilTheme {
+ EmotionLoadingView(
+ emotion = EmotionUiModel.Default,
+ )
+ }
+}
diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/EmotionRecommendRoutineScreen.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/EmotionRecommendRoutineScreen.kt
new file mode 100644
index 00000000..538383e3
--- /dev/null
+++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/EmotionRecommendRoutineScreen.kt
@@ -0,0 +1,138 @@
+package com.threegap.bitnagil.presentation.emotion.component.template
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.statusBarsPadding
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.style.TextDecoration
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+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.presentation.emotion.model.EmotionImageUiModel
+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.EmotionState
+
+@Composable
+fun EmotionRecommendRoutineScreen(
+ state: EmotionState,
+ onClickRoutine: (String) -> Unit,
+ onClickRegisterRecommendRoutines: () -> Unit,
+ onClickSkip: () -> Unit,
+) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(color = BitnagilTheme.colors.coolGray99)
+ .statusBarsPadding()
+ .padding(start = 16.dp, end = 16.dp, bottom = 20.dp, top = 32.dp),
+ ) {
+ Spacer(modifier = Modifier.height(54.dp))
+
+ Text(
+ text = "오늘의 감정에 맞는 루틴을 준비했어요!",
+ color = BitnagilTheme.colors.coolGray10,
+ style = BitnagilTheme.typography.title2Bold,
+ )
+
+ Spacer(modifier = Modifier.height(10.dp))
+
+ Text(
+ text = "오늘의 감정에 맞춰 준비된 맞춤 루틴이에요.\n원하는 루틴을 골라 가볍게 시작해 보세요.",
+ color = BitnagilTheme.colors.coolGray50,
+ style = BitnagilTheme.typography.body1Medium,
+ )
+
+ 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),
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.height(12.dp))
+
+ BitnagilTextButton(
+ text = "변경하기",
+ onClick = onClickRegisterRecommendRoutines,
+ enabled = state.registerRecommendRoutinesButtonEnabled,
+ modifier = Modifier.fillMaxWidth(),
+ )
+
+ 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,
+ modifier = Modifier.fillMaxWidth(),
+ )
+ }
+}
+
+@Preview
+@Composable
+private fun Preview() {
+ BitnagilTheme {
+ EmotionRecommendRoutineScreen(
+ state = EmotionState(
+ emotionTypeUiModels = listOf(
+ EmotionUiModel(
+ emotionType = "emotionType",
+ emotionMarbleName = "emotionMarbleName",
+ image = EmotionImageUiModel.Url(
+ url = "https://bitnagil-s3.s3.ap-northeast-2.amazonaws.com/home_satisfaction.png",
+ offlineBackupImageResourceId = null,
+ ),
+ ),
+ ),
+ isLoading = false,
+ step = EmotionScreenStep.RecommendRoutines,
+ recommendRoutines = listOf(
+ EmotionRecommendRoutineUiModel(
+ id = "1",
+ name = "루틴 이름",
+ description = "루틴 설명",
+ selected = true,
+ ),
+ ),
+ showLoadingView = false,
+ ),
+ onClickRoutine = {},
+ onClickRegisterRecommendRoutines = {},
+ onClickSkip = {},
+ )
+ }
+}
diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/SimpleEmotionSelectionScreen.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/SimpleEmotionSelectionScreen.kt
new file mode 100644
index 00000000..95e14dfd
--- /dev/null
+++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/SimpleEmotionSelectionScreen.kt
@@ -0,0 +1,130 @@
+package com.threegap.bitnagil.presentation.emotion.component.template
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.statusBarsPadding
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.lazy.grid.items
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.threegap.bitnagil.designsystem.BitnagilTheme
+import com.threegap.bitnagil.designsystem.component.block.BitnagilTopBar
+import com.threegap.bitnagil.designsystem.modifier.clickableWithoutRipple
+import com.threegap.bitnagil.presentation.emotion.component.atom.EmotionMarbleImage
+import com.threegap.bitnagil.presentation.emotion.model.EmotionImageUiModel
+import com.threegap.bitnagil.presentation.emotion.model.EmotionScreenStep
+import com.threegap.bitnagil.presentation.emotion.model.EmotionUiModel
+import com.threegap.bitnagil.presentation.emotion.model.mvi.EmotionState
+
+@Composable
+fun SimpleEmotionSelectionScreen(
+ state: EmotionState,
+ onClickPreviousButton: () -> Unit,
+ onClickEmotion: (String) -> Unit,
+) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(color = BitnagilTheme.colors.white)
+ .statusBarsPadding(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ BitnagilTopBar(
+ showBackButton = true,
+ title = "오늘 감정 등록하기",
+ onBackClick = onClickPreviousButton,
+ )
+
+ Box(
+ modifier = Modifier.fillMaxHeight(),
+ contentAlignment = Alignment.Center,
+ ) {
+ LazyVerticalGrid(
+ columns = GridCells.Fixed(3),
+ modifier = Modifier.padding(horizontal = 24.dp),
+ horizontalArrangement = Arrangement.spacedBy(12.dp),
+ verticalArrangement = Arrangement.spacedBy(28.dp),
+ ) {
+ items(state.emotionTypeUiModels) { emotion ->
+ Column(
+ modifier = Modifier
+ .clickableWithoutRipple { onClickEmotion(emotion.emotionType) },
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ EmotionMarbleImage(
+ modifier = Modifier.aspectRatio(1f),
+ image = emotion.image,
+ )
+
+ Text(
+ text = emotion.emotionMarbleName,
+ style = BitnagilTheme.typography.body1Regular.copy(color = BitnagilTheme.colors.coolGray20),
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
+@Preview(heightDp = 360, widthDp = 360)
+@Composable
+private fun Preview() {
+ BitnagilTheme {
+ SimpleEmotionSelectionScreen(
+ state = EmotionState(
+ emotionTypeUiModels = listOf(
+ EmotionUiModel(
+ emotionType = "emotionType",
+ emotionMarbleName = "name1",
+ image = EmotionImageUiModel.Url(
+ url = "https://bitnagil-s3.s3.ap-northeast-2.amazonaws.com/home_satisfaction.png",
+ offlineBackupImageResourceId = null,
+ ),
+ ),
+ EmotionUiModel(
+ emotionType = "emotionType",
+ emotionMarbleName = "name2",
+ image = EmotionImageUiModel.Url(
+ url = "https://bitnagil-s3.s3.ap-northeast-2.amazonaws.com/home_satisfaction.png",
+ offlineBackupImageResourceId = null,
+ ),
+ ),
+ EmotionUiModel(
+ emotionType = "emotionType",
+ emotionMarbleName = "name3",
+ image = EmotionImageUiModel.Url(
+ url = "https://bitnagil-s3.s3.ap-northeast-2.amazonaws.com/home_satisfaction.png",
+ offlineBackupImageResourceId = null,
+ ),
+ ),
+ EmotionUiModel(
+ emotionType = "emotionType",
+ emotionMarbleName = "name4",
+ image = EmotionImageUiModel.Url(
+ url = "https://bitnagil-s3.s3.ap-northeast-2.amazonaws.com/home_satisfaction.png",
+ offlineBackupImageResourceId = null,
+ ),
+ ),
+ ),
+ isLoading = false,
+ step = EmotionScreenStep.Emotion,
+ recommendRoutines = listOf(),
+ showLoadingView = false,
+ ),
+ onClickEmotion = { _ -> },
+ onClickPreviousButton = {},
+ )
+ }
+}
diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/SwipeEmotionSelectionScreen.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/SwipeEmotionSelectionScreen.kt
new file mode 100644
index 00000000..7398231b
--- /dev/null
+++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/component/template/SwipeEmotionSelectionScreen.kt
@@ -0,0 +1,462 @@
+package com.threegap.bitnagil.presentation.emotion.component.template
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.draggable
+import androidx.compose.foundation.gestures.rememberDraggableState
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.aspectRatio
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+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.foundation.layout.statusBarsPadding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.PagerState
+import androidx.compose.foundation.pager.rememberPagerState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Text
+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.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.lerp
+import androidx.compose.ui.zIndex
+import com.threegap.bitnagil.designsystem.BitnagilTheme
+import com.threegap.bitnagil.designsystem.R
+import com.threegap.bitnagil.designsystem.component.block.BitnagilTopBar
+import com.threegap.bitnagil.presentation.common.dimension.dpToPx
+import com.threegap.bitnagil.presentation.emotion.component.atom.EmotionMarbleImage
+import com.threegap.bitnagil.presentation.emotion.model.EmotionImageUiModel
+import com.threegap.bitnagil.presentation.emotion.model.EmotionScreenStep
+import com.threegap.bitnagil.presentation.emotion.model.EmotionUiModel
+import com.threegap.bitnagil.presentation.emotion.model.mvi.EmotionState
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+import kotlin.math.absoluteValue
+
+@Composable
+fun SwipeEmotionSelectionScreen(
+ state: EmotionState,
+ onClickPreviousButton: () -> Unit,
+ onSelectEmotion: (String) -> Unit,
+) {
+ val fadeInTransition = remember { fadeIn(animationSpec = tween(150)) }
+ val fadeOutTransition = remember { fadeOut(animationSpec = tween(50)) }
+
+ val emotions = remember(state.emotionTypeUiModels) { state.emotionTypeUiModels + EmotionUiModel.Default }
+ val actualItemCount = emotions.size
+
+ val pagerState = rememberPagerState(
+ initialPage = Int.MAX_VALUE / 2 - (Int.MAX_VALUE / 2 % actualItemCount),
+ pageCount = { Int.MAX_VALUE },
+ )
+ var showText by remember { mutableStateOf(true) }
+ val currentItem = emotions[pagerState.currentPage % actualItemCount]
+
+ LaunchedEffect(pagerState.isScrollInProgress) {
+ if (pagerState.isScrollInProgress) {
+ showText = false
+ } else {
+ delay(250)
+ showText = true
+ }
+ }
+
+ Box(
+ modifier = Modifier.fillMaxSize(),
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(color = BitnagilTheme.colors.white)
+ .statusBarsPadding(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ BitnagilTopBar(
+ showBackButton = true,
+ title = "오늘 감정 등록하기",
+ onBackClick = onClickPreviousButton,
+ )
+
+ Spacer(modifier = Modifier.height(20.dp))
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 20.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween,
+ ) {
+ state.emotionTypeUiModels.forEach { emotion ->
+ EmotionMarbleImage(
+ modifier = Modifier.size(40.dp),
+ image = emotion.image,
+ alpha = if (emotion.emotionType == currentItem.emotionType) 1f else 0.3f,
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.height(26.dp))
+
+ EmotionDescriptionText(
+ emotion = currentItem,
+ showText = showText,
+ enterTransition = fadeInTransition,
+ exitTransition = fadeOutTransition,
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Box(
+ modifier = Modifier.weight(1f),
+ ) {
+ EmotionPager(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(bottom = 90.dp)
+ .zIndex(1f),
+ emotions = emotions,
+ enabled = !state.isLoading,
+ onSelectEmotion = onSelectEmotion,
+ pagerState = pagerState,
+ showText = showText,
+ marbleNameTextEnterTransition = fadeInTransition,
+ marbleNameTextExitTransition = fadeOutTransition,
+ )
+
+ Column(
+ modifier = Modifier.align(Alignment.BottomCenter),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ GestureDescriptionText(
+ currentEmotionSelectable = currentItem.selectable,
+ )
+
+ Spacer(modifier = Modifier.height(12.dp))
+
+ Image(
+ painter = painterResource(R.drawable.img_pomo_hand),
+ contentScale = ContentScale.FillBounds,
+ contentDescription = null,
+ modifier = Modifier
+ .width(263.dp)
+ .height(192.dp),
+ )
+ }
+
+ Image(
+ painter = painterResource(R.drawable.img_pomo_thumb),
+ modifier = Modifier
+ .align(Alignment.BottomCenter)
+ .zIndex(3f)
+ .offset(x = (-20).dp, y = (-70).dp),
+ contentDescription = null,
+ )
+ }
+ }
+
+ AnimatedVisibility(
+ visible = state.showLoadingView,
+ enter = fadeIn(),
+ exit = ExitTransition.None,
+ ) {
+ EmotionLoadingView(emotion = currentItem)
+ }
+ }
+}
+
+@Composable
+private fun EmotionDescriptionText(
+ emotion: EmotionUiModel,
+ showText: Boolean,
+ enterTransition: EnterTransition,
+ exitTransition: ExitTransition,
+) {
+ Box(
+ modifier = Modifier.height(102.dp),
+ ) {
+ AnimatedVisibility(
+ visible = emotion.message != null && showText,
+ enter = enterTransition,
+ exit = exitTransition,
+ ) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Box(
+ modifier = Modifier
+ .background(Color(emotion.symbolBackgroundColor), shape = RoundedCornerShape(12.dp))
+ .padding(20.dp),
+ contentAlignment = Alignment.Center,
+ ) {
+ Text(
+ text = emotion.message ?: "",
+ style = BitnagilTheme.typography.cafe24SsurroundAir2.copy(color = Color(emotion.symbolColor)),
+ textAlign = TextAlign.Center,
+ maxLines = 2,
+ minLines = 2,
+ )
+ }
+
+ Canvas(
+ modifier = Modifier
+ .height(10.dp)
+ .width(24.dp),
+ ) {
+ val path = Path().apply {
+ moveTo(0f, 0f)
+ lineTo(size.width, 0f)
+ lineTo(size.width / 2, size.height)
+ lineTo(0f, 0f)
+ close()
+ }
+ drawPath(path, color = Color(emotion.symbolBackgroundColor))
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun GestureDescriptionText(
+ currentEmotionSelectable: Boolean,
+) {
+ if (currentEmotionSelectable) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Text("선택한 감정 구슬을 아래로 놓아주세요", style = BitnagilTheme.typography.body2Medium.copy(color = BitnagilTheme.colors.coolGray50))
+
+ Spacer(modifier = Modifier.height(12.dp))
+
+ Image(
+ painter = painterResource(R.drawable.ic_double_down_arrow_24),
+ modifier = Modifier.size(24.dp),
+ contentDescription = null,
+ )
+ }
+ } else {
+ Row(
+ horizontalArrangement = Arrangement.spacedBy(26.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Image(
+ painter = painterResource(R.drawable.ic_double_left_arrow_24),
+ modifier = Modifier.size(24.dp),
+ contentDescription = null,
+ )
+
+ Text(
+ "좌우로 스와이프해\n감정 구슬을 골라주세요",
+ style = BitnagilTheme.typography.body2Medium.copy(color = BitnagilTheme.colors.coolGray50),
+ textAlign = TextAlign.Center,
+ )
+
+ Image(
+ painter = painterResource(R.drawable.ic_double_right_arrow_24),
+ modifier = Modifier.size(24.dp),
+ contentDescription = null,
+ )
+ }
+ }
+}
+
+@Composable
+private fun EmotionPager(
+ modifier: Modifier = Modifier,
+ emotions: List,
+ enabled: Boolean,
+ onSelectEmotion: (String) -> Unit,
+ pagerState: PagerState,
+ showText: Boolean,
+ marbleNameTextEnterTransition: EnterTransition,
+ marbleNameTextExitTransition: ExitTransition,
+) {
+ val currentItem = emotions[pagerState.currentPage % emotions.size]
+
+ BoxWithConstraints(
+ modifier = modifier,
+ contentAlignment = Alignment.TopCenter,
+ ) {
+ val density = LocalDensity.current
+ val screenWidth = with(density) { constraints.maxWidth.toDp() }
+
+ val itemSize = 140.dp
+ val centerItemYOffset = 50.dp
+ val contentPadding = (screenWidth - itemSize) / 2
+ val pageSpacing = ((screenWidth - itemSize * 2) / 2)
+
+ HorizontalPager(
+ modifier = Modifier.fillMaxHeight(),
+ state = pagerState,
+ pageSpacing = pageSpacing,
+ contentPadding = PaddingValues(horizontal = contentPadding),
+ verticalAlignment = Alignment.Top,
+ userScrollEnabled = enabled,
+ ) { page ->
+ val itemIndex = page % emotions.size
+ EmotionPagerItem(
+ emotion = emotions[itemIndex],
+ pagerState = pagerState,
+ page = page,
+ size = itemSize,
+ centerItemYOffset = centerItemYOffset.dpToPx(),
+ maximumDraggableYOffset = constraints.maxHeight - (itemSize + centerItemYOffset).dpToPx(),
+ enabled = enabled && emotions[itemIndex].selectable,
+ onSelectEmotion = onSelectEmotion,
+ )
+ }
+
+ AnimatedVisibility(
+ visible = showText,
+ enter = marbleNameTextEnterTransition,
+ exit = marbleNameTextExitTransition,
+ ) {
+ Box(
+ modifier = Modifier
+ .background(Color(currentItem.symbolBackgroundColor), shape = RoundedCornerShape(10.dp))
+ .padding(horizontal = 16.dp, vertical = 8.dp),
+ ) {
+ Text(
+ text = currentItem.emotionMarbleName,
+ style = BitnagilTheme.typography.title3SemiBold,
+ color = Color(currentItem.symbolColor),
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun EmotionPagerItem(
+ emotion: EmotionUiModel,
+ pagerState: PagerState,
+ page: Int,
+ size: Dp,
+ centerItemYOffset: Float,
+ maximumDraggableYOffset: Float = Float.MAX_VALUE,
+ enabled: Boolean,
+ onSelectEmotion: (String) -> Unit,
+) {
+ val pageOffset = (
+ (pagerState.currentPage - page) + pagerState.currentPageOffsetFraction
+ ).absoluteValue
+
+ val offsetY = remember { Animatable(0f) }
+ val coroutineScope = rememberCoroutineScope()
+
+ EmotionMarbleImage(
+ image = emotion.image,
+ modifier = Modifier
+ .size(size)
+ .aspectRatio(1f)
+ .graphicsLayer {
+ translationY = lerp(start = centerItemYOffset * 1f, stop = 0f, pageOffset)
+ }
+ .offset {
+ IntOffset(0, offsetY.value.toInt())
+ }
+ .draggable(
+ orientation = Orientation.Vertical,
+ enabled = (pageOffset == 0f && enabled),
+ state = rememberDraggableState { deltaY ->
+ coroutineScope.launch {
+ val newOffsetY = offsetY.value + deltaY
+ if (newOffsetY in 0f..maximumDraggableYOffset) {
+ offsetY.snapTo(newOffsetY)
+ }
+ }
+ },
+ onDragStopped = {
+ coroutineScope.launch {
+ val targetOffsetY = if (offsetY.value >= maximumDraggableYOffset * 0.8) {
+ onSelectEmotion(emotion.emotionType)
+ maximumDraggableYOffset
+ } else {
+ 0f
+ }
+
+ offsetY.animateTo(
+ targetValue = targetOffsetY,
+ animationSpec = tween(durationMillis = 300),
+ )
+ }
+ },
+ ),
+ )
+}
+
+@Preview(widthDp = 360, heightDp = 800)
+@Composable
+private fun Preview() {
+ BitnagilTheme {
+ SwipeEmotionSelectionScreen(
+ state = EmotionState(
+ emotionTypeUiModels = listOf(
+ EmotionUiModel(
+ emotionType = "emotionType",
+ emotionMarbleName = "구슬 이름 1",
+ image = EmotionImageUiModel.Url(
+ url = "https://bitnagil-s3.s3.ap-northeast-2.amazonaws.com/home_satisfaction.png",
+ offlineBackupImageResourceId = null,
+ ),
+ symbolBackgroundColor = 0xFFEFECFF,
+ symbolColor = 0xFF692BD0,
+ ),
+ EmotionUiModel(
+ emotionType = "emotionType",
+ emotionMarbleName = "구슬 이름 2",
+ image = EmotionImageUiModel.Url(
+ url = "https://bitnagil-s3.s3.ap-northeast-2.amazonaws.com/home_satisfaction.png",
+ offlineBackupImageResourceId = null,
+ ),
+ symbolBackgroundColor = 0xFFE9FAD0,
+ symbolColor = 0xFF609F00,
+ ),
+ ),
+ isLoading = false,
+ step = EmotionScreenStep.Emotion,
+ recommendRoutines = listOf(),
+ showLoadingView = false,
+ ),
+ onClickPreviousButton = {},
+ onSelectEmotion = {},
+ )
+ }
+}
diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/EmotionImageUiModel.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/EmotionImageUiModel.kt
new file mode 100644
index 00000000..44d92fd9
--- /dev/null
+++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/EmotionImageUiModel.kt
@@ -0,0 +1,10 @@
+package com.threegap.bitnagil.presentation.emotion.model
+
+import android.os.Parcelable
+import kotlinx.parcelize.Parcelize
+
+@Parcelize
+sealed class EmotionImageUiModel : Parcelable {
+ data class Url(val url: String, val offlineBackupImageResourceId: Int?) : EmotionImageUiModel()
+ data class Resource(val resourceId: Int) : EmotionImageUiModel()
+}
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
index 4f575688..487e7275 100644
--- 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
@@ -9,15 +9,23 @@ import kotlinx.parcelize.Parcelize
data class EmotionUiModel(
val emotionType: String,
val emotionMarbleName: String,
- val imageUrl: String,
- val offlineBackupImageResourceId: Int?,
+ val image: EmotionImageUiModel,
+ val selectable: Boolean = true,
+ val message: String? = null,
+ val symbolBackgroundColor: Long = 0xFFEAEBEC,
+ val symbolColor: Long = 0xFF878A93,
) : Parcelable {
companion object {
fun fromDomain(emotion: Emotion) = EmotionUiModel(
emotionType = emotion.emotionType,
emotionMarbleName = emotion.emotionMarbleName,
- imageUrl = emotion.imageUrl,
- offlineBackupImageResourceId = getOfflineBackupImageResourceId(emotion.emotionType),
+ image = EmotionImageUiModel.Url(
+ url = emotion.imageUrl,
+ offlineBackupImageResourceId = getOfflineBackupImageResourceId(emotion.emotionType),
+ ),
+ message = getMessage(emotion.emotionType),
+ symbolBackgroundColor = getSymbolBackgroundColor(emotion.emotionType),
+ symbolColor = getSymbolColor(emotion.emotionType),
)
private fun getOfflineBackupImageResourceId(emotionType: String): Int? {
@@ -31,5 +39,51 @@ data class EmotionUiModel(
else -> null
}
}
+
+ private fun getMessage(emotionType: String): String? {
+ return when (emotionType) {
+ "CALM" -> "평온함은 마음이 고요하고 편안해\n균형을 이루는 상태에요."
+ "VITALITY" -> "활기참은 생기가 가득 차\n활발하고 적극적인 상태예요."
+ "LETHARGY" -> "무기력함은 의욕이 없어 아무것도\n하기 힘든 상태예요."
+ "ANXIETY" -> "불안함은 마음이 불안정하고 쉽게\n안심하기 어려운 상태예요."
+ "SATISFACTION" -> "만족함은 기대가 충족되어\n더 바랄 것이 없는 상태예요."
+ "FATIGUE" -> "피곤함은 몸과 마음이 지쳐\n휴식이 필요한 상태예요."
+ else -> null
+ }
+ }
+
+ private fun getSymbolBackgroundColor(emotionType: String): Long {
+ return when (emotionType) {
+ "CALM" -> 0xFFEFECFF
+ "VITALITY" -> 0xFFE9FAD0
+ "LETHARGY" -> 0xFFEAEBEC
+ "ANXIETY" -> 0xFFFFEEE4
+ "SATISFACTION" -> 0xFFE2F3F6
+ "FATIGUE" -> 0xFFFFE1E1
+ else -> 0xFFEAEBEC
+ }
+ }
+
+ private fun getSymbolColor(emotionType: String): Long {
+ return when (emotionType) {
+ "CALM" -> 0xFF692BD0
+ "VITALITY" -> 0xFF609F00
+ "LETHARGY" -> 0xFF5A5C63
+ "ANXIETY" -> 0xFFFE7120
+ "SATISFACTION" -> 0xFF26A792
+ "FATIGUE" -> 0xFFFF5151
+ else -> 0xFF878A93
+ }
+ }
+
+ val Default = EmotionUiModel(
+ emotionType = "NONE",
+ emotionMarbleName = "구슬 선택",
+ image = EmotionImageUiModel.Resource(R.drawable.default_marble),
+ selectable = false,
+ message = "오늘 기분 어때요?\n기록해 두면 내 루틴에 도움 돼요!",
+ symbolBackgroundColor = 0xFFEAEBEC,
+ symbolColor = 0xFF171719,
+ )
}
}
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 49b62216..9e155caa 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
@@ -8,6 +8,7 @@ sealed class EmotionIntent : MviIntent {
data class EmotionListLoadSuccess(val emotionTypeUiModels: List) : EmotionIntent()
data class RegisterEmotionSuccess(val recommendRoutines: List) : EmotionIntent()
data object RegisterEmotionLoading : EmotionIntent()
+ data class RegisterEmotionFailure(val message: String) : EmotionIntent()
data object RegisterRecommendRoutinesLoading : EmotionIntent()
data object RegisterRecommendRoutinesSuccess : EmotionIntent()
data object RegisterRecommendRoutinesFailure : EmotionIntent()
diff --git a/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/mvi/EmotionSideEffect.kt b/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/mvi/EmotionSideEffect.kt
index 819135c1..b039ec89 100644
--- a/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/mvi/EmotionSideEffect.kt
+++ b/presentation/src/main/java/com/threegap/bitnagil/presentation/emotion/model/mvi/EmotionSideEffect.kt
@@ -4,4 +4,5 @@ import com.threegap.bitnagil.presentation.common.mviviewmodel.MviSideEffect
sealed class EmotionSideEffect : MviSideEffect {
data object NavigateToBack : EmotionSideEffect()
+ data class ShowToast(val message: String) : EmotionSideEffect()
}
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 3e016ddb..b095935f 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
@@ -14,6 +14,7 @@ data class EmotionState(
val isLoading: Boolean,
val recommendRoutines: List,
val step: EmotionScreenStep,
+ val showLoadingView: Boolean,
) : MviState {
companion object {
val Init = EmotionState(
@@ -21,6 +22,7 @@ data class EmotionState(
isLoading = true,
recommendRoutines = emptyList(),
step = EmotionScreenStep.Emotion,
+ showLoadingView = false,
)
}