Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,29 +1,34 @@
package com.cherrish.android.presentation.challenge.loading

import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
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.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.animateLottieCompositionAsState
import com.airbnb.lottie.compose.rememberLottieComposition
import com.cherrish.android.R
import com.cherrish.android.core.common.extension.collectLatestSideEffect
import com.cherrish.android.core.common.extension.noRippleClickable
Expand Down Expand Up @@ -61,6 +66,14 @@ private fun ChallengeLoadingScreen(
onClick: () -> Unit,
modifier: Modifier = Modifier
) {
val composition by rememberLottieComposition(
LottieCompositionSpec.RawRes(R.raw.lt_challenge_loading)
)
val progress by animateLottieCompositionAsState(
composition,
iterations = LottieConstants.IterateForever,
isPlaying = true
)
Column(
modifier = modifier
.fillMaxSize()
Expand Down Expand Up @@ -106,12 +119,17 @@ private fun ChallengeLoadingScreen(
style = CherrishTheme.typography.title1SB18
)

Spacer(modifier = Modifier.height(60.dp))
Spacer(modifier = Modifier.height(80.dp))

Image(
painter = painterResource(id = R.drawable.img_challenge_loading),
contentDescription = null,
modifier = Modifier.size(150.dp)
LottieAnimation(
composition = composition,
progress = { progress },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 105.dp)
.aspectRatio(130f / 154f)
.align(Alignment.CenterHorizontally)
.noRippleClickable(onClick = onClick)
)

Spacer(modifier = Modifier.height(80.dp))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,15 +98,19 @@ class ChallengeMissionProgressViewModel @Inject constructor(
}

private fun ChallengeMissionProgressResponseModel.toUiState(): ChallengeMissionProgressUiState {
val cherryType = CherryType.entries.first { it.step == cherryLevel }
val cherryType = CherryType.entries.firstOrNull {
it.step == cherryLevel
} ?: CherryType.MONGRONG

val isMaxLevel = cherryType == CherryType.KKUKKU

val remainingText = if (isMaxLevel) {
"챌린지 완료까지 ${remainingRoutinesToNextLevel}개의 미션을 수행해야 해요!"
} else {
"체리가 크려면 ${remainingRoutinesToNextLevel}개의 미션을 수행해야 해요!"
}
val completeButtonText = if (isMaxLevel) {
"챌린지 완료하기"
"챌린지 종료하기"
} else {
"오늘 미션 종료하기"
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,36 @@
package com.cherrish.android.presentation.splash

import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
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.padding
import androidx.compose.foundation.layout.size
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LifecycleEventEffect
import com.airbnb.lottie.compose.LottieAnimation
import com.airbnb.lottie.compose.LottieCompositionSpec
import com.airbnb.lottie.compose.LottieConstants
import com.airbnb.lottie.compose.animateLottieCompositionAsState
import com.airbnb.lottie.compose.rememberLottieComposition
import com.cherrish.android.R
import com.cherrish.android.core.common.extension.collectLatestSideEffect
import com.cherrish.android.core.designsystem.theme.CherrishTheme
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

@Composable
fun SplashRoute(
Expand All @@ -33,20 +39,19 @@ fun SplashRoute(
paddingValues: PaddingValues,
viewModel: SplashViewModel = hiltViewModel()
) {
LaunchedEffect(Unit) {
viewModel.isAutoLoginCheck()
val scope = rememberCoroutineScope()

LifecycleEventEffect(Lifecycle.Event.ON_START) {
scope.launch {
delay(3000)
viewModel.isAutoLoginCheck()
}
}
Comment on lines +44 to 49
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

ON_START 이벤트 사용 시 중복 실행 가능성이 있습니다.

Lifecycle.Event.ON_START는 앱이 백그라운드에서 포그라운드로 돌아올 때마다 다시 트리거됩니다. 사용자가 스플래시 화면에서 다른 앱으로 전환했다가 돌아오면 3초 딜레이 후 네비게이션이 다시 시도될 수 있습니다.

또한 rememberCoroutineScope로 시작된 코루틴은 Composable이 dispose되어도 자동으로 취소되지 않을 수 있어, 화면 전환 후에도 네비게이션 로직이 실행될 위험이 있습니다.

🔧 LaunchedEffect 사용 권장
-    val scope = rememberCoroutineScope()
-
-    LifecycleEventEffect(Lifecycle.Event.ON_START) {
-        scope.launch {
-            delay(3000)
-            viewModel.isAutoLoginCheck()
-        }
-    }
+    LaunchedEffect(Unit) {
+        delay(3000)
+        viewModel.isAutoLoginCheck()
+    }

LaunchedEffect(Unit)은 최초 composition 시 한 번만 실행되며, recomposition이나 lifecycle 변경에 영향받지 않습니다. 또한 Composable이 dispose될 때 자동으로 취소됩니다.

🤖 Prompt for AI Agents
In `@app/src/main/java/com/cherrish/android/presentation/splash/SplashScreen.kt`
around lines 44 - 49, The code uses
LifecycleEventEffect(Lifecycle.Event.ON_START) with rememberCoroutineScope and
scope.launch+delay(3000) which can re-run when the app returns to foreground and
may continue after the Composable is disposed; replace this with
LaunchedEffect(Unit) and move the delay(3000) and viewModel.isAutoLoginCheck()
into the LaunchedEffect so it runs only once on initial composition and is
automatically cancelled when the Composable is disposed; specifically replace
the block containing LifecycleEventEffect, Lifecycle.Event.ON_START,
rememberCoroutineScope/ scope.launch and delay with a LaunchedEffect(Unit) {
delay(3000); viewModel.isAutoLoginCheck() } to fix duplicate navigation and
cancellation issues.


viewModel.sideEffect.collectLatestSideEffect { sideEffect ->
delay(3000)

when (sideEffect) {
SplashSideEffect.NavigateToOnboarding -> {
navigateToOnboarding()
}
SplashSideEffect.NavigateToHome -> {
navigateToHome()
}
SplashSideEffect.NavigateToOnboarding -> navigateToOnboarding()
SplashSideEffect.NavigateToHome -> navigateToHome()
}
}

Expand All @@ -62,6 +67,15 @@ private fun SplashScreen(
) {
val gradationColors = listOf(CherrishTheme.colors.gradation, CherrishTheme.colors.gradation2)

val composition by rememberLottieComposition(
LottieCompositionSpec.RawRes(R.raw.lt_challenge_loading)
)
val progress by animateLottieCompositionAsState(
composition,
iterations = LottieConstants.IterateForever,
isPlaying = true
)

Column(
modifier = modifier
.fillMaxSize()
Expand All @@ -77,19 +91,18 @@ private fun SplashScreen(
) {
Spacer(modifier = Modifier.weight(283f))

Image(
painter = painterResource(id = R.drawable.ic_app_logo),
contentDescription = null,
modifier = Modifier.size(width = 114.dp, height = 100.dp)
LottieAnimation(
composition = composition,
progress = { progress },
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 105.dp)
.aspectRatio(130f / 154f)
.align(Alignment.CenterHorizontally)
)

Spacer(modifier = Modifier.height(14.dp))

Image(
imageVector = ImageVector.vectorResource(id = R.drawable.ic_app_logo_title),
contentDescription = null
)

Spacer(modifier = Modifier.weight(298f))
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,7 @@ class SplashViewModel @Inject constructor(
fun isAutoLoginCheck() {
viewModelScope.launch {
val id = tokenManager.getId()
_sideEffect.emit(
if (id != null) {
SplashSideEffect.NavigateToHome
} else {
SplashSideEffect.NavigateToOnboarding
}
)
_sideEffect.emit(SplashSideEffect.NavigateToOnboarding)
}
}
}
Loading