diff --git a/app/src/main/java/com/dhc/dhcandroid/navigation/DhcNavHost.kt b/app/src/main/java/com/dhc/dhcandroid/navigation/DhcNavHost.kt
index 8ce82abf..7285ccd0 100644
--- a/app/src/main/java/com/dhc/dhcandroid/navigation/DhcNavHost.kt
+++ b/app/src/main/java/com/dhc/dhcandroid/navigation/DhcNavHost.kt
@@ -26,6 +26,7 @@ import com.dhc.intro.start.IntroRoute
import com.dhc.missionstatus.MissionStatusRoute
import com.dhc.mypage.MyPageRoute
import com.dhc.reward.RewardRoute
+import com.dhc.reward.yearfortune.YearFortuneRoute
import com.example.survey.SurveyRoute
@Composable
@@ -128,7 +129,9 @@ fun DhcNavHost(
}
composable(DhcRoute.MAIN_REWARD.route) {
- RewardRoute()
+ RewardRoute(
+ navigateToYearFortune = { navController.navigateTo(DhcRoute.YEAR_FORTUNE) },
+ )
}
composable(DhcRoute.MAIN_MY.route) {
@@ -155,5 +158,8 @@ fun DhcNavHost(
navigateToPrevScreen = { navController.navigateUp() },
)
}
+ composable(DhcRoute.YEAR_FORTUNE.route) {
+ YearFortuneRoute()
+ }
}
}
diff --git a/app/src/main/java/com/dhc/dhcandroid/navigation/DhcRoute.kt b/app/src/main/java/com/dhc/dhcandroid/navigation/DhcRoute.kt
index 330169a6..999b9634 100644
--- a/app/src/main/java/com/dhc/dhcandroid/navigation/DhcRoute.kt
+++ b/app/src/main/java/com/dhc/dhcandroid/navigation/DhcRoute.kt
@@ -161,6 +161,16 @@ enum class DhcRoute(
route = DhcRouteConst.FORTUNE_SURVEY,
screenConfig = ScreenConfig(),
),
+ YEAR_FORTUNE(
+ route = DhcRouteConst.YEAR_FORTUNE,
+ screenConfig = ScreenConfig(
+ topBarState = DhcTopBarState.Basic(
+ title = "1년 운세",
+ isShowBackButton = true,
+ ),
+ bottomBarState = DhcBottomBarState.None,
+ ),
+ ),
NONE(
route = DhcRouteConst.NONE,
screenConfig = ScreenConfig(),
diff --git a/app/src/main/java/com/dhc/dhcandroid/navigation/DhcRouteConst.kt b/app/src/main/java/com/dhc/dhcandroid/navigation/DhcRouteConst.kt
index 070c979b..8dc22235 100644
--- a/app/src/main/java/com/dhc/dhcandroid/navigation/DhcRouteConst.kt
+++ b/app/src/main/java/com/dhc/dhcandroid/navigation/DhcRouteConst.kt
@@ -18,5 +18,6 @@ object DhcRouteConst {
const val MAIN_MY = "main/my"
const val HOME_MONETARY_DETAIL = "home/monetaryDetail"
const val FORTUNE_SURVEY = "fortuneSurvey"
+ const val YEAR_FORTUNE = "reward/yearFortune"
const val NONE = "none"
}
diff --git a/core/designsystem/src/main/kotlin/com/dhc/designsystem/tipcard/DhcTipCard.kt b/core/designsystem/src/main/kotlin/com/dhc/designsystem/tipcard/DhcTipCard.kt
index 5ad3d1ae..0e568bdc 100644
--- a/core/designsystem/src/main/kotlin/com/dhc/designsystem/tipcard/DhcTipCard.kt
+++ b/core/designsystem/src/main/kotlin/com/dhc/designsystem/tipcard/DhcTipCard.kt
@@ -19,6 +19,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
@@ -38,7 +39,9 @@ import com.dhc.designsystem.getSvgImageLoader
@Composable
fun DhcTipCard(
tipCardItem: TipCardModel,
- modifier: Modifier = Modifier
+ modifier: Modifier = Modifier,
+ contentTextStyle: TextStyle = DhcTypoTokens.TitleH3,
+ contentTextAlign: TextAlign = TextAlign.Center,
) {
Column(
modifier = modifier
@@ -53,7 +56,9 @@ fun DhcTipCard(
Spacer(modifier = Modifier.height(8.dp))
TipCardContent(
color = tipCardItem.color,
- content = tipCardItem.cont
+ content = tipCardItem.cont,
+ textAlign = contentTextAlign,
+ textStyle = contentTextStyle
)
}
}
@@ -140,6 +145,8 @@ fun TipCardTitle(
fun TipCardContent(
color: String?,
content: String,
+ textStyle: TextStyle = DhcTypoTokens.TitleH3,
+ textAlign: TextAlign = TextAlign.Center,
modifier: Modifier = Modifier,
) {
val dhcColor = LocalDhcColors.current
@@ -159,9 +166,9 @@ fun TipCardContent(
Text(
text = content,
- style = DhcTypoTokens.TitleH3,
+ style = textStyle,
color = if (color.isNotNullOrEmpty()) hexToColor(color) else dhcColor.text.textBodyPrimary,
- textAlign = TextAlign.Center,
+ textAlign = textAlign,
)
}
}
diff --git a/core/designsystem/src/main/res/drawable/ico_lock.xml b/core/designsystem/src/main/res/drawable/ico_lock.xml
new file mode 100644
index 00000000..20adc281
--- /dev/null
+++ b/core/designsystem/src/main/res/drawable/ico_lock.xml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
diff --git a/presentation/reward/src/main/java/com/dhc/reward/RewardContract.kt b/presentation/reward/src/main/java/com/dhc/reward/RewardContract.kt
new file mode 100644
index 00000000..e5c41b0a
--- /dev/null
+++ b/presentation/reward/src/main/java/com/dhc/reward/RewardContract.kt
@@ -0,0 +1,4 @@
+package com.dhc.reward
+
+class RewardContract {
+}
\ No newline at end of file
diff --git a/presentation/reward/src/main/java/com/dhc/reward/RewardRoute.kt b/presentation/reward/src/main/java/com/dhc/reward/RewardRoute.kt
index 7b84acfc..8993716f 100644
--- a/presentation/reward/src/main/java/com/dhc/reward/RewardRoute.kt
+++ b/presentation/reward/src/main/java/com/dhc/reward/RewardRoute.kt
@@ -6,7 +6,11 @@ import androidx.compose.ui.Modifier
@Composable
fun RewardRoute(
modifier: Modifier = Modifier,
+ navigateToYearFortune: () -> Unit = {},
) {
- RewardScreen(modifier = modifier)
+ RewardScreen(
+ modifier = modifier,
+ navigateToYearFortune = navigateToYearFortune
+ )
}
diff --git a/presentation/reward/src/main/java/com/dhc/reward/RewardScreen.kt b/presentation/reward/src/main/java/com/dhc/reward/RewardScreen.kt
index 11350c29..d34620be 100644
--- a/presentation/reward/src/main/java/com/dhc/reward/RewardScreen.kt
+++ b/presentation/reward/src/main/java/com/dhc/reward/RewardScreen.kt
@@ -8,19 +8,17 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.aspectRatio
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.widthIn
import androidx.compose.foundation.rememberScrollState
-import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.ExperimentalMaterial3Api
-import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.material3.rememberTooltipState
import androidx.compose.runtime.Composable
@@ -29,18 +27,15 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.drawBehind
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Brush
-import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.Path
-import androidx.compose.ui.graphics.drawscope.rotate
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.buildAnnotatedString
import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@@ -61,6 +56,7 @@ import kotlinx.coroutines.launch
@Composable
fun RewardScreen(
modifier: Modifier = Modifier,
+ navigateToYearFortune: () -> Unit = {},
) {
val colors = LocalDhcColors.current
val scrollState = rememberScrollState()
@@ -159,18 +155,36 @@ fun RewardScreen(
Spacer(modifier = Modifier.height(40.dp))
- // 리워드 카드들
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp),
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
- // 리워드 카드
RewardCard()
+ }
+
+ Spacer(modifier = Modifier.height(40.dp))
+
+ // 받은 리워드들 섹션
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 20.dp),
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ // 섹션 타이틀
+ Text(
+ text = stringResource(R.string.reward_received_rewards_title),
+ style = DhcTypoTokens.TitleH6,
+ color = colors.text.textMain,
+ modifier = Modifier.padding(horizontal = 4.dp)
+ )
- // 프리미엄 운세 카드
- PremiumFortuneCard()
+ // 리워드 리스트
+ ReceivedRewardsList(
+ onClickItem = { navigateToYearFortune() }
+ )
}
Spacer(modifier = Modifier.height(40.dp))
@@ -183,12 +197,12 @@ private fun RewardCard(
currentStep: Int = 1,
) {
val colors = LocalDhcColors.current
-
+
// 레벨별 필요 포인트 (예시)
val levelThresholds = listOf(0, 100, 500, 900, 1600)
val nextLevelPoints = levelThresholds.getOrNull(currentStep + 1) ?: levelThresholds.last()
val remainingPoints = nextLevelPoints - currentPoints
-
+
// 툴팁 위치 계산 (0.0 ~ 1.0)
val progressWidthByStep = listOf(0.094f, 0.37f, 0.66f, 1.0f)
val currentProgress = progressWidthByStep.getOrElse(currentStep) { 0.094f }
@@ -201,7 +215,6 @@ private fun RewardCard(
.padding(20.dp),
verticalArrangement = Arrangement.spacedBy(16.dp)
) {
- // 지금까지 얻은 리워드
Column(
modifier = Modifier.fillMaxWidth(),
) {
@@ -227,8 +240,6 @@ private fun RewardCard(
)
}
}
-
- // 구분선
Box(
modifier = Modifier
.fillMaxWidth()
@@ -236,7 +247,6 @@ private fun RewardCard(
.background(TransparentColor.glassEffect)
)
- // 툴팁 (동적 위치) - 모든 레벨에서 표시
Box(
modifier = Modifier
.fillMaxWidth()
@@ -248,7 +258,7 @@ private fun RewardCard(
val targetX = currentProgress * progressBarWidth
val tooltipX = (targetX - (tooltipWidth / 2))
.coerceIn(0.dp, progressBarWidth - tooltipWidth)
-
+
Column(
modifier = Modifier
.size(width = tooltipWidth, height = 48.dp)
@@ -286,7 +296,7 @@ private fun RewardCard(
maxLines = 1
)
}
-
+
// 삼각형 화살표 (툴팁 중앙에서 목표 위치로 offset)
val triangleOffset = targetX - tooltipX - (tooltipWidth / 2)
Box(
@@ -300,7 +310,7 @@ private fun RewardCard(
lineTo(size.width, 0f)
close()
}
-
+
drawPath(
path = trianglePath,
brush = Brush.verticalGradient(
@@ -323,55 +333,111 @@ private fun RewardCard(
modifier = Modifier.fillMaxWidth(),
totalStepList = listOf("lv.1", "lv.4", "lv.8", "Goal")
)
+ DhcButton(
+ text = stringResource(R.string.reward_open_reward_button),
+ buttonSize = DhcButtonSize.LARGE,
+ buttonStyle = DhcButtonStyle.Secondary(false),
+ onClick = { },
+ modifier = Modifier.fillMaxWidth()
+ )
+ Text(
+ modifier = Modifier.fillMaxWidth(),
+ text = stringResource(R.string.reward_explain_text_button),
+ style = DhcTypoTokens.Body5,
+ textDecoration = TextDecoration.Underline,
+ color = colors.text.textHighLightsSecondary,
+ textAlign = TextAlign.Center,
+ )
+
+ }
+}
+
+// 받은 리워드 데이터 모델
+private data class ReceivedReward(
+ val id: String,
+ val name: String,
+ val isLocked: Boolean = true
+)
+
+@Composable
+private fun ReceivedRewardsList(
+ rewards: List = listOf(
+ ReceivedReward("1", "1년 운세"),
+ ReceivedReward("2", "전반적 사주"),
+ ReceivedReward("3", "복합 사주"),
+ ReceivedReward("4", "복합 사주"),
+ ReceivedReward("5", "1년 운세"),
+ ReceivedReward("6", "전반적 사주"),
+ ReceivedReward("7", "복합 사주"),
+ ReceivedReward("8", "복합 사주"),
+ ReceivedReward("9", "1년 운세"),
+ ReceivedReward("10", "전반적 사주"),
+ ReceivedReward("11", "복합 사주"),
+ ),
+ onClickItem: (ReceivedReward) -> Unit,
+) {
+ val itemsPerRow = 4
+ Column(
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ rewards.chunked(itemsPerRow).forEach { rowItems ->
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ rowItems.forEach { reward ->
+ ReceivedRewardItem(
+ reward = reward,
+ modifier = Modifier.weight(1f),
+ onClickItem = {onClickItem(reward)}
+ )
+ }
+ // 마지막 행에서 빈 공간 채우기
+ repeat(itemsPerRow - rowItems.size) {
+ Spacer(modifier = Modifier.weight(1f))
+ }
+ }
+ }
}
}
@Composable
-private fun PremiumFortuneCard() {
+private fun ReceivedRewardItem(
+ reward: ReceivedReward,
+ onClickItem: () -> Unit,
+ modifier: Modifier = Modifier
+) {
val colors = LocalDhcColors.current
Column(
- modifier = Modifier
- .fillMaxWidth()
- .clip(RoundedCornerShape(12.dp))
- .background(SurfaceColor.neutral700)
- .padding(20.dp),
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.spacedBy(16.dp)
+ modifier = modifier.fillMaxWidth().clickable { onClickItem() },
+ horizontalAlignment = Alignment.CenterHorizontally
) {
- // 선물 아이콘
- Icon(
- painter = painterResource(com.dhc.designsystem.R.drawable.ico_present),
- contentDescription = null,
- modifier = Modifier.size(40.dp),
- tint = Color.Unspecified,
- )
-
- // Goal에 도달하면 프리미엄 운세를 볼 수 있어요
- Row(
- modifier = Modifier.fillMaxWidth(),
- horizontalArrangement = Arrangement.Center,
- verticalAlignment = Alignment.CenterVertically
+ // 카드 (아이콘만 포함)
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .aspectRatio(1f)
+ .clip(RoundedCornerShape(8.dp))
+ .background(SurfaceColor.neutral700)
+ .padding(10.dp),
+ contentAlignment = Alignment.Center
) {
- Text(
- text = "Goal",
- style = DhcTypoTokens.TitleH7,
- color = colors.text.textHighLightsPrimary
- )
- Text(
- text = "에 도달하면 프리미엄 운세를 볼 수 있어요",
- style = DhcTypoTokens.Body5,
- color = SurfaceColor.neutral300
+ Image(
+ painter = painterResource(com.dhc.designsystem.R.drawable.ico_lock),
+ contentDescription = "lock icon",
+ colorFilter = ColorFilter.tint(SurfaceColor.neutral400),
+ modifier = Modifier.size(28.dp)
)
}
- // 프리미엄 운세 열기 버튼
- DhcButton(
- text = stringResource(R.string.reward_premium_fortune_button),
- buttonSize = DhcButtonSize.LARGE,
- buttonStyle = DhcButtonStyle.Secondary(false),
- onClick = { },
- modifier = Modifier.fillMaxWidth()
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ text = reward.name,
+ style = DhcTypoTokens.Body6,
+ color = colors.text.textBodyPrimary,
+ textAlign = TextAlign.Center
)
}
}
@@ -427,3 +493,26 @@ private fun RewardCardLevel4Preview() {
)
}
}
+
+@Preview(name = "받은 리워드들", showBackground = true, backgroundColor = 0xFF0F1114)
+@Composable
+private fun ReceivedRewardsListPreview() {
+ DhcTheme {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(20.dp),
+ verticalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ Text(
+ text = stringResource(R.string.reward_received_rewards_title),
+ style = DhcTypoTokens.TitleH6,
+ color = LocalDhcColors.current.text.textMain,
+ modifier = Modifier.padding(horizontal = 4.dp)
+ )
+ ReceivedRewardsList(
+ onClickItem = {}
+ )
+ }
+ }
+}
diff --git a/presentation/reward/src/main/java/com/dhc/reward/yearfortune/FiveElementGaugeContent.kt b/presentation/reward/src/main/java/com/dhc/reward/yearfortune/FiveElementGaugeContent.kt
new file mode 100644
index 00000000..e0d3ad45
--- /dev/null
+++ b/presentation/reward/src/main/java/com/dhc/reward/yearfortune/FiveElementGaugeContent.kt
@@ -0,0 +1,225 @@
+package com.dhc.reward.yearfortune
+
+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.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+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.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.tooling.preview.Preview
+import com.dhc.designsystem.DhcTheme
+import com.dhc.designsystem.DhcTypoTokens
+import com.dhc.designsystem.LocalDhcColors
+import com.dhc.designsystem.SurfaceColor
+import com.dhc.reward.R
+
+// 오행 요소 타입
+enum class FiveElementType(
+ val koreanName: String,
+ val character: String,
+ val color: Color
+) {
+ WOOD("목", "木", Color(0xFF4ECDC4)), // 목기운 - 청록색
+ FIRE("화", "火", Color(0xFFFF6B6B)), // 화기운 - 빨간색
+ EARTH("토", "土", Color(0xFFFFD93D)), // 토기운 - 노란색
+ METAL("금", "金", Color(0xFFC0C0C0)), // 금기운 - 은색
+ WATER("수", "水", Color(0xFFB19CD9)) // 수기운 - 보라색
+}
+
+// 오행 상태
+enum class FiveElementStatus(
+ val koreanName: String,
+ val color: Color
+) {
+ EXCESSIVE("과다", Color(0xFFFF6F6F)), // 40% 이상 - 빨간색
+ APPROPRIATE("적정", Color(0xFF02B579)), // 20~30% - 초록색
+ INSUFFICIENT("부족", Color(0xFF70A2FF)) // 10% 이하 - 연한 파란색
+}
+
+// 오행 데이터 모델
+data class FiveElementData(
+ val type: FiveElementType,
+ val percentage: Int,
+ val status: FiveElementStatus
+) {
+ companion object {
+ fun fromPercentage(type: FiveElementType, percentage: Int): FiveElementData {
+ val status = when {
+ percentage >= 40 -> FiveElementStatus.EXCESSIVE
+ percentage in 20..30 -> FiveElementStatus.APPROPRIATE
+ percentage <= 10 -> FiveElementStatus.INSUFFICIENT
+ else -> FiveElementStatus.APPROPRIATE
+ }
+ return FiveElementData(type, percentage, status)
+ }
+ }
+}
+
+@Composable
+fun FiveElementGaugeContent(
+ modifier: Modifier = Modifier,
+ fiveElementData: List = defaultFiveElementData()
+) {
+ val colors = LocalDhcColors.current
+
+ Column(
+ modifier = modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp, vertical = 24.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ text = stringResource(R.string.next_year_five_gauge),
+ style = DhcTypoTokens.TitleH4_1,
+ color = colors.text.textMain,
+ textAlign = TextAlign.Center
+ )
+ Spacer(modifier = Modifier.height(8.dp))
+ Text(
+ text = "내년엔 화기운이 너무 강해져요!\n" +
+ "화기운을 조심해야 해요~",
+ style = DhcTypoTokens.TitleH4_1,
+ color = colors.text.textMain,
+ textAlign = TextAlign.Center
+ )
+ Spacer(modifier = Modifier.height(16.dp))
+ FiveElementGaugeChart(
+ data = fiveElementData,
+ modifier = Modifier.fillMaxWidth()
+ )
+ }
+}
+
+@Composable
+private fun FiveElementGaugeChart(
+ data: List,
+ modifier: Modifier = Modifier
+) {
+ Row(
+ modifier = modifier,
+ horizontalArrangement = Arrangement.SpaceEvenly,
+ verticalAlignment = Alignment.Bottom
+ ) {
+ data.forEach { elementData ->
+ FiveElementGaugeItem(
+ data = elementData,
+ modifier = Modifier.weight(1f)
+ )
+ }
+ }
+}
+
+@Composable
+private fun FiveElementGaugeItem(
+ data: FiveElementData,
+ modifier: Modifier = Modifier
+) {
+ val colors = LocalDhcColors.current
+ val gaugeHeight = 166.dp
+ val gaugeWidth = 30.dp
+ val fillHeight = (gaugeHeight * (data.percentage / 100f))
+
+ Column(
+ modifier = modifier,
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Bottom
+ ) {
+ // 퍼센트 표시
+ Text(
+ text = "${data.percentage}%",
+ style = DhcTypoTokens.Body7,
+ color = colors.text.textBodyPrimary,
+ textAlign = TextAlign.Center
+ )
+
+ Spacer(modifier = Modifier.height(12.dp))
+
+ Box(
+ modifier = Modifier
+ .width(gaugeWidth)
+ .height(gaugeHeight),
+ contentAlignment = Alignment.BottomCenter
+ ) {
+ // 백그라운드 바
+ Box(
+ modifier = Modifier
+ .width(10.dp)
+ .height(gaugeHeight - 4.dp)
+ .clip(RoundedCornerShape(5.dp))
+ .background(SurfaceColor.neutral500)
+ )
+
+ // 포그라운드 바 (채워진 부분) - 상태에 따른 색상
+ if (data.percentage > 0) {
+ Box(
+ modifier = Modifier
+ .width(10.dp)
+ .height(fillHeight - 4.dp)
+ .clip(RoundedCornerShape(5.dp))
+ .background(data.status.color)
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ // 한자 아이콘 - 상태에 따른 색상
+ Box(
+ modifier = Modifier
+ .size(24.dp)
+ .clip(RoundedCornerShape(4.dp))
+ .background(data.status.color),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = data.type.character,
+ style = DhcTypoTokens.Body5,
+ color = Color.White,
+ textAlign = TextAlign.Center
+ )
+ }
+
+ Spacer(modifier = Modifier.height(4.dp))
+
+ // 상태 텍스트 - 상태에 따른 색상
+ Text(
+ text = data.status.koreanName,
+ style = DhcTypoTokens.Body7,
+ color = data.status.color,
+ textAlign = TextAlign.Center
+ )
+ }
+}
+
+private fun defaultFiveElementData(): List {
+ return listOf(
+ FiveElementData.fromPercentage(FiveElementType.WOOD, 20),
+ FiveElementData.fromPercentage(FiveElementType.FIRE, 40),
+ FiveElementData.fromPercentage(FiveElementType.EARTH, 30),
+ FiveElementData.fromPercentage(FiveElementType.METAL, 20),
+ FiveElementData.fromPercentage(FiveElementType.WATER, 10)
+ )
+}
+
+@Preview(name = "오행 게이지 그래프", showBackground = true, backgroundColor = 0xFF0F1114)
+@Composable
+private fun YearFortuneDetailScreenPreview() {
+ DhcTheme {
+ FiveElementGaugeContent()
+ }
+}
\ No newline at end of file
diff --git a/presentation/reward/src/main/java/com/dhc/reward/yearfortune/YearFortuneInfo.kt b/presentation/reward/src/main/java/com/dhc/reward/yearfortune/YearFortuneInfo.kt
new file mode 100644
index 00000000..d7bc88f7
--- /dev/null
+++ b/presentation/reward/src/main/java/com/dhc/reward/yearfortune/YearFortuneInfo.kt
@@ -0,0 +1,22 @@
+package com.dhc.reward.yearfortune
+
+import com.dhc.common.ImageResource
+import com.dhc.designsystem.tipcard.TipCardModel
+import com.dhc.presentation.ui.monetaryDetail.FortuneCard
+import com.dhc.presentation.ui.monetaryDetail.ScoreInfo
+
+data class YearFortuneInfo(
+ val scoreInfo: ScoreInfo = ScoreInfo(),
+ val fortuneCard: FortuneCard = FortuneCard(),
+ val overallFortune: String = "",
+ val quickViewFortune: List = listOf(),
+ val fiveElementData: List = listOf(),
+ val energyChange: String = "",
+ val yearTips: List = listOf(),
+)
+
+data class QuickViewFortuneItem(
+ val title: String,
+ val content: String,
+ val icon: ImageResource? = null,
+)
diff --git a/presentation/reward/src/main/java/com/dhc/reward/yearfortune/YearFortuneRoute.kt b/presentation/reward/src/main/java/com/dhc/reward/yearfortune/YearFortuneRoute.kt
new file mode 100644
index 00000000..1b421e03
--- /dev/null
+++ b/presentation/reward/src/main/java/com/dhc/reward/yearfortune/YearFortuneRoute.kt
@@ -0,0 +1,16 @@
+package com.dhc.reward.yearfortune
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+
+@Composable
+fun YearFortuneRoute(
+ modifier: Modifier = Modifier,
+) {
+ // TODO: 실제 데이터를 가져오는 로직이 필요하면 ViewModel을 통해 가져와야 함
+ // 현재는 Preview에서 사용하는 샘플 데이터를 사용
+ YearFortuneScreen(
+ yearFortuneInfo = YearFortuneInfo(),
+ modifier = modifier
+ )
+}
diff --git a/presentation/reward/src/main/java/com/dhc/reward/yearfortune/YearFortuneScreen.kt b/presentation/reward/src/main/java/com/dhc/reward/yearfortune/YearFortuneScreen.kt
new file mode 100644
index 00000000..3c599a46
--- /dev/null
+++ b/presentation/reward/src/main/java/com/dhc/reward/yearfortune/YearFortuneScreen.kt
@@ -0,0 +1,281 @@
+package com.dhc.reward.yearfortune
+
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+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.size
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.dhc.designsystem.DhcTheme
+import com.dhc.designsystem.DhcTypoTokens
+import com.dhc.designsystem.GradientColor
+import com.dhc.designsystem.fortunecard.DhcFortuneCard
+import com.dhc.designsystem.messagecard.DhcMessageCard
+import com.dhc.designsystem.score.DhcScoreText
+import com.dhc.designsystem.score.toGradientScoreColor
+import com.dhc.designsystem.tipcard.DhcTipCard
+import com.dhc.designsystem.tipcard.DhcTipCardGrid
+import com.dhc.designsystem.title.DhcTitle
+import com.dhc.designsystem.title.DhcTitleState
+import com.dhc.presentation.ui.monetaryDetail.FortuneCard
+import com.dhc.presentation.ui.monetaryDetail.ScoreInfo
+
+@Composable
+fun YearFortuneScreen(
+ yearFortuneInfo: YearFortuneInfo,
+ modifier: Modifier = Modifier,
+ scrollState: ScrollState = rememberScrollState()
+) {
+ Box(modifier = modifier) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .verticalScroll(scrollState)
+ .padding(horizontal = 20.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Spacer(modifier = Modifier.height(24.dp))
+ DhcScoreText(
+ badgeText = yearFortuneInfo.scoreInfo.date,
+ score = yearFortuneInfo.scoreInfo.score,
+ description = yearFortuneInfo.scoreInfo.description,
+ scoreTextColor = yearFortuneInfo.scoreInfo.score.toGradientScoreColor()
+ )
+ Spacer(modifier = Modifier.height(64.dp))
+ DhcFortuneCard(
+ title = yearFortuneInfo.fortuneCard.title,
+ description = yearFortuneInfo.fortuneCard.message,
+ imageResource = yearFortuneInfo.fortuneCard.image,
+ modifier = Modifier.size(width = 144.dp, height = 200.dp),
+ )
+ Spacer(modifier = Modifier.height(12.dp))
+ Canvas(
+ modifier = Modifier
+ .size(width = 32.dp, height = 32.dp)
+ .graphicsLayer { scaleX = 4f },
+ ) {
+ drawOval(
+ brush = GradientColor.cardBottomGradient01,
+ size = size,
+ alpha = 0.4f,
+ )
+ }
+ Spacer(modifier = Modifier.height(24.dp))
+
+ // 전반적인 운세
+ OverallFortuneSection(message = yearFortuneInfo.overallFortune)
+ Spacer(modifier = Modifier.height(24.dp))
+
+ // 한 눈에 보는 운세
+ QuickViewFortuneSection(items = yearFortuneInfo.quickViewFortune)
+ Spacer(modifier = Modifier.height(24.dp))
+
+ // 내년 내 사주 오행의 균형
+ FiveElementGaugeContent(
+ modifier = Modifier.fillMaxWidth(),
+ fiveElementData = yearFortuneInfo.fiveElementData
+ )
+ Spacer(modifier = Modifier.height(24.dp))
+
+ // 올해의 기운 변화
+ EnergyChangeSection(message = yearFortuneInfo.energyChange)
+ Spacer(modifier = Modifier.height(24.dp))
+
+ // 이번년도 꿀팁
+ YearTipsSection(tips = yearFortuneInfo.yearTips)
+ Spacer(modifier = Modifier.height(97.dp))
+ }
+ }
+}
+
+@Composable
+private fun OverallFortuneSection(
+ message: String,
+) {
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ DhcTitle(
+ titleState = DhcTitleState(
+ title = "전반적인 운세",
+ titleStyle = DhcTypoTokens.TitleH4_1,
+ ),
+ textAlign = TextAlign.Start,
+ modifier = Modifier.fillMaxWidth()
+ )
+ Spacer(modifier = Modifier.height(12.dp))
+ DhcMessageCard(
+ modifier = Modifier.fillMaxWidth(),
+ title = "타이틀",
+ content = message
+ )
+ }
+}
+
+@Composable
+private fun QuickViewFortuneSection(
+ items: List,
+) {
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ DhcTitle(
+ titleState = DhcTitleState(
+ title = "한 눈에 보는 운세",
+ titleStyle = DhcTypoTokens.TitleH5_1,
+ ),
+ textAlign = TextAlign.Start,
+ modifier = Modifier.fillMaxWidth()
+ )
+ Spacer(modifier = Modifier.height(12.dp))
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ verticalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ items.forEach { item ->
+ DhcTipCard(
+ tipCardItem = com.dhc.designsystem.tipcard.TipCardModel(
+ title = item.title,
+ cont = item.content,
+ icon = item.icon,
+ color = null
+ ),
+ modifier = Modifier.fillMaxWidth(),
+ contentTextStyle = DhcTypoTokens.Body3,
+ contentTextAlign = TextAlign.Start
+ )
+ }
+ }
+ }
+}
+
+@Composable
+private fun EnergyChangeSection(
+ message: String,
+) {
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ DhcTitle(
+ titleState = DhcTitleState(
+ title = "올해의 기운 변화",
+ titleStyle = DhcTypoTokens.TitleH5_1,
+ ),
+ textAlign = TextAlign.Start,
+ modifier = Modifier.fillMaxWidth()
+ )
+ Spacer(modifier = Modifier.height(12.dp))
+ DhcMessageCard(
+ modifier = Modifier.fillMaxWidth(),
+ title = "타이틀",
+ content = message
+ )
+ }
+}
+
+@Composable
+private fun YearTipsSection(
+ tips: List,
+) {
+ Column(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ DhcTitle(
+ titleState = DhcTitleState(
+ title = "이번년도 꿀팁",
+ titleStyle = DhcTypoTokens.TitleH5_1,
+ ),
+ textAlign = TextAlign.Start,
+ modifier = Modifier.fillMaxWidth()
+ )
+ Spacer(modifier = Modifier.height(12.dp))
+ DhcTipCardGrid(tipCards = tips)
+ }
+}
+
+@Preview
+@Composable
+private fun PreviewYearFortuneScreen() {
+ DhcTheme {
+ YearFortuneScreen(
+ yearFortuneInfo = YearFortuneInfo(
+ scoreInfo = ScoreInfo(
+ date = "2026년 운세 총평",
+ score = 99,
+ description = "올 한해는 전반적으로 마음이 들뜨는 날이에요,\n한템포 쉬어가요."
+ ),
+ fortuneCard = FortuneCard(
+ title = "최고의 날",
+ message = "네잎클로버",
+ ),
+ overallFortune = "이번 달은 마음이 한층 단단해지는 시기예요.\n불필요한 걱정에 에너지를 쓰기보단, 지금 눈앞의 상황에 집중하면 일이 자연스럽게 풀려갑니다.\n충동적인 선택이 아니라 조금 더 생각하고 결정하는 것만으로도 내일의 나에게 분명 고마운 한 달이 될 거예요.",
+ quickViewFortune = listOf(
+ QuickViewFortuneItem(
+ title = "금전운",
+ content = "현재의 상황을 최우선적으로 고려해보는 것이 좋아요. 과거의 상황까지 고려할 필요는 없어요."
+ ),
+ QuickViewFortuneItem(
+ title = "연애운",
+ content = "상대방의 말보다 분위기를 읽는 게 관계에 좋은 영향이 있어요."
+ ),
+ QuickViewFortuneItem(
+ title = "학업운",
+ content = "현재의 상황을 최우선적으로 고려해보는 것이 좋아요. 과거의 상황까지 고려할 필요는 없어요."
+ )
+ ),
+ fiveElementData = listOf(
+ FiveElementData.fromPercentage(FiveElementType.WOOD, 20),
+ FiveElementData.fromPercentage(FiveElementType.FIRE, 40),
+ FiveElementData.fromPercentage(FiveElementType.EARTH, 30),
+ FiveElementData.fromPercentage(FiveElementType.METAL, 20),
+ FiveElementData.fromPercentage(FiveElementType.WATER, 10)
+ ),
+ energyChange = "화의 기운은\n'결단력・집중・주체성'을 밝히는 에너지예요.\n불안이나 충동으로 흐르면 지치기 쉽지만,\n올바르게 쓰이면 원하는 방향으로 크게 나아가는 달이 됩니다.\n\n그래서 이번 달엔…\n• 지금의 상황을 기준으로 결정해보세요.\n• 감정보다는 리듬을 안정시키면 잘 흘러가요.\n• 내일의 나에게 분명 고마운 선택을 하게 될 거예요.",
+ yearTips = listOf(
+ com.dhc.designsystem.tipcard.TipCardModel(
+ title = "추천메뉴",
+ icon = null,
+ color = null,
+ cont = "카레"
+ ),
+ com.dhc.designsystem.tipcard.TipCardModel(
+ title = "행운의 색상",
+ icon = null,
+ color = "#23B169",
+ cont = "연두색"
+ ),
+ com.dhc.designsystem.tipcard.TipCardModel(
+ title = "피해야 할 음식",
+ icon = null,
+ color = null,
+ cont = "치킨, 닭"
+ ),
+ com.dhc.designsystem.tipcard.TipCardModel(
+ title = "피해야 할 색상",
+ icon = null,
+ color = "#F4F4F5",
+ cont = "흰색"
+ )
+ )
+ )
+ )
+ }
+}
\ No newline at end of file
diff --git a/presentation/reward/src/main/res/values/strings.xml b/presentation/reward/src/main/res/values/strings.xml
index 5c41139d..d774e20b 100644
--- a/presentation/reward/src/main/res/values/strings.xml
+++ b/presentation/reward/src/main/res/values/strings.xml
@@ -2,13 +2,17 @@
리워드
Lv.%d
동 복주머니
- 지금까지 얻은 리워드
+ 지금까지 얻은 경험치
%dpt
다음 레벨까지 %dpt 남았어요
좋은 금융 습관 형성을 위해\n2주간 매일 진행하는 미션이에요!
프리미엄 운세
Goal에 도달하면 프리미엄 운세를 볼 수 있어요
프리미엄 운세 열기
+ 리워드 열기
그래픽 (변경예정)
+ 리워드는 뭔가요?>
+ 받은 리워드들
+ 내년 내 사주 오행의 균형