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에 도달하면 프리미엄 운세를 볼 수 있어요 프리미엄 운세 열기 + 리워드 열기 그래픽 (변경예정) + 리워드는 뭔가요?> + 받은 리워드들 + 내년 내 사주 오행의 균형