Skip to content
Merged
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
2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ android {
}

dependencies {
implementation(project(":feature:onboarding"))
implementation(projects.domain)
implementation(projects.data)
implementation(projects.core.common)
Expand All @@ -91,6 +92,7 @@ dependencies {
implementation(projects.feature.chat)
implementation(projects.feature.calendar)
implementation(projects.feature.login)
implementation(projects.feature.onboarding)
implementation(projects.feature.setting)
implementation(projects.feature.webview)

Expand Down
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">

<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE"
tools:node="remove"/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package com.gamss.android.app.navigation

import android.Manifest
import android.os.Build
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
Expand All @@ -14,6 +18,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation3.runtime.NavKey
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberNavBackStack
import androidx.navigation3.ui.NavDisplay
Expand All @@ -23,6 +28,8 @@ import com.gamss.android.app.main.MainViewModel
import com.gamss.android.domain.auth.SessionState
import com.gamss.android.feature.login.LoginScreen
import com.gamss.android.feature.login.navigation.LoginKey
import com.gamss.android.feature.onboarding.OnboardingScreen
import com.gamss.android.feature.onboarding.navigation.OnboardingKey
import org.orbitmvi.orbit.compose.collectAsState

@Composable
Expand All @@ -40,6 +47,7 @@ fun GamssRootNavHost(
CircularProgressIndicator()
}
}

else -> RootNavDisplay(
sessionState = state.sessionState,
useCardFeature = state.useCardFeature,
Expand All @@ -54,25 +62,45 @@ private fun RootNavDisplay(
) {
val initialKey = if (sessionState == SessionState.Authenticated) MainKey else LoginKey
val backStack = rememberNavBackStack(initialKey)
val notificationPermissionLauncher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission(),
onResult = {},
)

LaunchedEffect(sessionState) {
val destination = when (sessionState) {
SessionState.Authenticated -> MainKey
SessionState.Unauthenticated -> LoginKey
SessionState.Loading -> return@LaunchedEffect
}
fun replaceRoot(destination: NavKey) {
if (backStack.lastOrNull() != destination) {
backStack.clear()
backStack.add(destination)
}
}

LaunchedEffect(sessionState) {
Comment thread
soyeonLee126 marked this conversation as resolved.
// 로그인 성공 시에는 LoginResult.isFirstLogin 분기를 기다린다. 세션 상태만 보고 Main으로
// 보내면 온보딩 콜백과 경쟁하므로, 이 effect는 세션 만료/로그아웃만 처리한다.
if (sessionState == SessionState.Unauthenticated) {
replaceRoot(LoginKey)
}
}

NavDisplay(
entries = backStack.map(
entryProvider {
entry<LoginKey> {
LoginScreen(
googleWebClientId = stringResource(R.string.default_web_client_id),
onLoginSuccess = { isFirstLogin ->
replaceRoot(if (isFirstLogin) OnboardingKey else MainKey)
},
)
}
entry<OnboardingKey> {
OnboardingScreen(
onComplete = { replaceRoot(MainKey) },
onNotificationPermissionRequest = {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
notificationPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
},
)
}
entry<MainKey> { MainScreen(useCardFeature = useCardFeature) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ private val ButtonBottomSpacing = 54.dp
@Composable
fun LoginScreen(
googleWebClientId: String,
onLoginSuccess: (isFirstLogin: Boolean) -> Unit,
viewModel: LoginViewModel = hiltViewModel(),
) {
val state by viewModel.collectAsState()
Expand All @@ -76,6 +77,9 @@ fun LoginScreen(

viewModel.collectSideEffect { sideEffect ->
when (sideEffect) {
is LoginSideEffect.LoginSucceeded ->
onLoginSuccess(sideEffect.isFirstLogin)

is LoginSideEffect.ShowToast ->
Toast.makeText(context, sideEffect.message, Toast.LENGTH_SHORT).show()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ package com.gamss.android.feature.login

sealed interface LoginSideEffect {
data class ShowToast(val message: String) : LoginSideEffect

data class LoginSucceeded(val isFirstLogin: Boolean) : LoginSideEffect
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,7 @@ class LoginViewModel @Inject constructor(
when (val result = loginUseCase(googleIdToken)) {
is AppResult.Success -> {
reduce { state.copy(isLoading = false) }
if (result.data.isFirstLogin) {
// 온보딩 화면이 정의되면 첫 로그인 사용자를 온보딩 플로우로 이동시킨다.
}
postSideEffect(LoginSideEffect.LoginSucceeded(result.data.isFirstLogin))
}

is AppResult.Failure -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,11 +60,25 @@ class LoginViewModelTest {
containerHost.onGoogleCredentialResolved("google-id-token")
expectState { copy(googleSignInRequestId = null) }
expectState { copy(isLoading = false) }
expectSideEffect(LoginSideEffect.LoginSucceeded(isFirstLogin = false))
}

coVerify(exactly = 1) { loginUseCase("google-id-token") }
}

@Test
fun `첫 로그인에 성공하면 온보딩 분기에 사용할 결과를 전달한다`() = runTest {
coEvery { loginUseCase(any()) } returns AppResult.Success(LoginResult(isFirstLogin = true))

viewModel().test(this) {
containerHost.onGoogleCredentialResolved("google-id-token")

expectState { copy(isLoading = true) }
expectState { copy(isLoading = false) }
expectSideEffect(LoginSideEffect.LoginSucceeded(isFirstLogin = true))
}
}

@Test
fun `Credential 요청이 실패하면 요청 id와 로딩 상태를 초기화하고 토스트를 노출한다`() = runTest {
viewModel().test(this) {
Expand Down
7 changes: 7 additions & 0 deletions feature/onboarding/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
plugins {
alias(libs.plugins.gamss.android.feature)
}

android {
namespace = "com.gamss.android.feature.onboarding"
}
Loading
Loading