Skip to content

Commit 7d1ceb7

Browse files
authored
Merge pull request #25 from Nexters/feature/implement-home-navigation
[FEAT] 로또 번호 추천 로딩 추가
2 parents 298e344 + c215eb5 commit 7d1ceb7

File tree

19 files changed

+433
-93
lines changed

19 files changed

+433
-93
lines changed

build-logic/src/main/kotlin/convention/ComposeAndroid.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ internal fun Project.configureComposeAndroid() {
3636
implementation(libs.findLibrary("orbit-viewmodel").get())
3737
implementation(libs.findLibrary("orbit-compose").get())
3838
implementation(libs.findLibrary("coil-compose").get())
39+
implementation(libs.findLibrary("coil-network").get())
3940

4041
debugImplementation(libs.findLibrary("androidx-ui-tooling").get())
4142
debugImplementation(libs.findLibrary("androidx-ui-test-manifest").get())

core/navigation/src/main/java/com/hanbang/navigation/feature/editprofile/RouteEditProfile.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,4 @@ import kotlinx.serialization.Serializable
99
* @created 2025/08/06
1010
*/
1111
@Serializable
12-
data class RouteEditProfile(val editProfileJson: String) : Route
12+
data class RouteEditProfile(val editProfileJson: String) : Route
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.hanbang.navigation.feature.lottorecommend
2+
3+
import com.hanbang.navigation.model.Route
4+
import kotlinx.serialization.Serializable
5+
6+
@Serializable
7+
data object RouteLottoRecommend: Route
8+
9+
@Serializable
10+
data object RouteLottoRecommendAd: Route

data/src/main/java/com/hanbang/data/repository/DefaultSattoRepository.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,4 +107,16 @@ class DefaultSattoRepository @Inject constructor(
107107
val lottoRecommendationDto = userRemoteDataSource.getLottoRecommendation(userId)
108108
emit(lottoRecommendationDto.toDomain())
109109
}.flowOn(Dispatchers.IO)
110+
111+
override fun createLottoRecommendation(): Flow<LottoRecommendation> = flow {
112+
val userId = userLocalDataSource.getUserId()
113+
val lottoRecommendationDto = userRemoteDataSource.createLottoRecommendation(userId)
114+
emit(lottoRecommendationDto.toDomain())
115+
}.flowOn(Dispatchers.IO)
116+
117+
private fun formatBirthDateTime(year: Int, month: Int, day: Int, hour: Int, minute: Int): String {
118+
val dateTime = LocalDateTime.of(year, month, day, hour, minute)
119+
val formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
120+
return dateTime.format(formatter)
121+
}
110122
}

domain/src/main/java/com/hanbang/domain/repository/SattoRepository.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,4 +44,6 @@ interface SattoRepository {
4444
): Flow<DailyFortune>
4545

4646
fun getLottoRecommendation(): Flow<LottoRecommendation>
47+
48+
fun createLottoRecommendation(): Flow<LottoRecommendation>
4749
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.hanbang.domain.usecase
2+
3+
import com.hanbang.domain.repository.SattoRepository
4+
import javax.inject.Inject
5+
6+
class CreateLottoRecommendationUseCase @Inject constructor(
7+
private val sattoRepository: SattoRepository
8+
) {
9+
operator fun invoke() = sattoRepository.createLottoRecommendation()
10+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package com.hanbang.home.lottoad
2+
3+
import androidx.compose.animation.core.Animatable
4+
import androidx.compose.animation.core.LinearEasing
5+
import androidx.compose.animation.core.tween
6+
import androidx.compose.foundation.background
7+
import androidx.compose.foundation.layout.Column
8+
import androidx.compose.foundation.layout.Spacer
9+
import androidx.compose.foundation.layout.fillMaxSize
10+
import androidx.compose.foundation.layout.fillMaxWidth
11+
import androidx.compose.foundation.layout.height
12+
import androidx.compose.foundation.layout.systemBarsPadding
13+
import androidx.compose.material3.Text
14+
import androidx.compose.runtime.Composable
15+
import androidx.compose.runtime.LaunchedEffect
16+
import androidx.compose.runtime.remember
17+
import androidx.compose.ui.Modifier
18+
import androidx.compose.ui.draw.alpha
19+
import androidx.compose.ui.graphics.Brush
20+
import androidx.compose.ui.graphics.Color
21+
import androidx.compose.ui.text.style.TextAlign
22+
import androidx.compose.ui.tooling.preview.Preview
23+
import androidx.compose.ui.unit.dp
24+
import androidx.hilt.navigation.compose.hiltViewModel
25+
import androidx.lifecycle.compose.LifecycleResumeEffect
26+
import coil3.compose.AsyncImage
27+
import com.hanbang.designsystem.navigation.TopAppBar
28+
import com.hanbang.designsystem.theme.Primary3
29+
import com.hanbang.designsystem.theme.Primary5
30+
import com.hanbang.designsystem.theme.SattoTheme
31+
import com.hanbang.designsystem.theme.White
32+
import com.hanbang.home.R
33+
import org.orbitmvi.orbit.compose.collectAsState
34+
import org.orbitmvi.orbit.compose.collectSideEffect
35+
36+
@Composable
37+
internal fun LottoAdScreen(
38+
viewModel: LottoAdViewModel = hiltViewModel(),
39+
navigateToLottoRecommend: () -> Unit
40+
) {
41+
viewModel.collectSideEffect {
42+
when (it) {
43+
LottoAdEvent.NavigateToLottoRecommend -> {
44+
navigateToLottoRecommend()
45+
}
46+
}
47+
}
48+
49+
LifecycleResumeEffect(Unit) {
50+
viewModel.onResume()
51+
onPauseOrDispose {
52+
viewModel.onPause()
53+
}
54+
}
55+
56+
val state = viewModel.collectAsState().value
57+
LottoAdContent(state = state)
58+
}
59+
60+
@Composable
61+
private fun LottoAdContent(
62+
state: LottoAdUiState
63+
) {
64+
val alphaAnim = remember { Animatable(0f) }
65+
LaunchedEffect(Unit) {
66+
while (true) {
67+
alphaAnim.animateTo(
68+
targetValue = 1f,
69+
animationSpec = tween(durationMillis = 500, easing = LinearEasing)
70+
)
71+
alphaAnim.animateTo(
72+
targetValue = 0f,
73+
animationSpec = tween(durationMillis = 500, easing = LinearEasing)
74+
)
75+
}
76+
}
77+
78+
Column(
79+
modifier = Modifier
80+
.fillMaxSize()
81+
.background(
82+
brush = Brush.verticalGradient(
83+
listOf(
84+
Color(0xFF312354),
85+
Primary3
86+
)
87+
)
88+
)
89+
.systemBarsPadding()
90+
) {
91+
TopAppBar(
92+
containerColor = Color.Transparent,
93+
contentColor = Color.White,
94+
)
95+
Text(
96+
text = "${state.userName}의 사주 분석 완료",
97+
style = SattoTheme.typography.body14Medium,
98+
color = Primary5,
99+
modifier = Modifier.fillMaxWidth(),
100+
textAlign = TextAlign.Center
101+
)
102+
Spacer(modifier = Modifier.height(8.dp))
103+
Text(
104+
text = "그대에게 딱 맞는\n번호를 추천 중이라네...",
105+
style = SattoTheme.typography.headline24Bold,
106+
color = White,
107+
modifier = Modifier.fillMaxWidth(),
108+
textAlign = TextAlign.Center
109+
)
110+
AsyncImage(
111+
model = R.drawable.img_lotto_ad,
112+
contentDescription = null,
113+
modifier = Modifier
114+
.weight(1f)
115+
.alpha(alphaAnim.value)
116+
)
117+
}
118+
}
119+
120+
@Preview
121+
@Composable
122+
private fun LottoAdScreenPreview() {
123+
SattoTheme {
124+
LottoAdContent(
125+
state = LottoAdUiState(
126+
userName = "user name"
127+
)
128+
)
129+
}
130+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.hanbang.home.lottoad
2+
3+
data class LottoAdUiState(
4+
val isPlaying: Boolean = false,
5+
val elapsedMillis: Long = 0L,
6+
val userName: String = ""
7+
) {
8+
val TOTAL_MILLIS = 2000L
9+
val isComplete = elapsedMillis >= TOTAL_MILLIS
10+
}
11+
12+
sealed interface LottoAdEvent {
13+
data object NavigateToLottoRecommend: LottoAdEvent
14+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package com.hanbang.home.lottoad
2+
3+
import androidx.lifecycle.ViewModel
4+
import androidx.lifecycle.viewModelScope
5+
import com.hanbang.domain.usecase.CreateLottoRecommendationUseCase
6+
import com.hanbang.domain.usecase.GetUserUseCase
7+
import dagger.hilt.android.lifecycle.HiltViewModel
8+
import kotlinx.coroutines.Job
9+
import kotlinx.coroutines.async
10+
import kotlinx.coroutines.delay
11+
import kotlinx.coroutines.flow.first
12+
import kotlinx.coroutines.launch
13+
import org.orbitmvi.orbit.Container
14+
import org.orbitmvi.orbit.ContainerHost
15+
import org.orbitmvi.orbit.viewmodel.container
16+
import javax.inject.Inject
17+
18+
@HiltViewModel
19+
class LottoAdViewModel @Inject constructor(
20+
private val getUserUseCase: GetUserUseCase,
21+
private val createLottoRecommendationUseCase: CreateLottoRecommendationUseCase
22+
) : ContainerHost<LottoAdUiState, LottoAdEvent>, ViewModel() {
23+
override val container: Container<LottoAdUiState, LottoAdEvent> = container(
24+
initialState = LottoAdUiState(),
25+
onCreate = {
26+
val userName = getUserUseCase().first().name
27+
reduce {
28+
state.copy(userName = userName)
29+
}
30+
}
31+
)
32+
33+
private var animationJob: Job? = null
34+
private var currentMillis: Long = 0
35+
36+
fun onResume() {
37+
startAnimation()
38+
}
39+
40+
fun onPause() {
41+
pauseAnimation()
42+
}
43+
44+
private fun startAnimation() = intent {
45+
if (state.isComplete) return@intent
46+
if (animationJob?.isActive == true) return@intent
47+
48+
animationJob?.cancel()
49+
animationJob = viewModelScope.launch {
50+
val lottoDeferred = async { createLottoRecommendationUseCase().first() }
51+
currentMillis = System.currentTimeMillis()
52+
reduce { state.copy(isPlaying = true) }
53+
val delayDeferred = async { delay(state.TOTAL_MILLIS - state.elapsedMillis) }
54+
55+
lottoDeferred.await()
56+
delayDeferred.await()
57+
58+
reduce { state.copy(isPlaying = false) }
59+
postSideEffect(LottoAdEvent.NavigateToLottoRecommend)
60+
}
61+
}
62+
63+
private fun pauseAnimation() = intent {
64+
val remainMillis = System.currentTimeMillis() - currentMillis
65+
reduce { state.copy(isPlaying = false, elapsedMillis = remainMillis) }
66+
}
67+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.hanbang.home.lottoad.navigation
2+
3+
import androidx.navigation.NavController
4+
import androidx.navigation.NavGraphBuilder
5+
import androidx.navigation.compose.composable
6+
import com.hanbang.home.lottoad.LottoAdScreen
7+
import com.hanbang.navigation.feature.lottorecommend.RouteLottoRecommendAd
8+
9+
fun NavController.navigateToLottoAd() {
10+
this.navigate(route = RouteLottoRecommendAd)
11+
}
12+
13+
fun NavGraphBuilder.lottoAdNavGraph(
14+
navigateToLottoRecommend: () -> Unit
15+
) {
16+
composable<RouteLottoRecommendAd> {
17+
LottoAdScreen(
18+
navigateToLottoRecommend = navigateToLottoRecommend
19+
)
20+
}
21+
}

0 commit comments

Comments
 (0)