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
13 changes: 13 additions & 0 deletions app/src/main/java/com/threegap/bitnagil/MainNavHost.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ import androidx.compose.ui.Modifier
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import com.threegap.bitnagil.presentation.home.HomeScreen
import com.threegap.bitnagil.presentation.intro.IntroScreenContainer
import com.threegap.bitnagil.presentation.login.LoginScreenContainer
import com.threegap.bitnagil.presentation.splash.SplashScreenContainer

@Composable
fun MainNavHost(
Expand All @@ -17,6 +19,17 @@ fun MainNavHost(
startDestination = navigator.startDestination,
modifier = modifier,
) {
composable<Route.Splash> {
SplashScreenContainer(
navigateToIntro = { navigator.navController.navigate(Route.Intro) },
navigateToHome = { navigator.navController.navigate(Route.Home) },
)
}

composable<Route.Intro> {
IntroScreenContainer()
}

composable<Route.Login> {
LoginScreenContainer()
}
Expand Down
2 changes: 1 addition & 1 deletion app/src/main/java/com/threegap/bitnagil/MainNavigator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import androidx.navigation.compose.rememberNavController
class MainNavigator(
val navController: NavHostController,
) {
val startDestination = Route.Login
val startDestination = Route.Splash
}

@Composable
Expand Down
6 changes: 6 additions & 0 deletions app/src/main/java/com/threegap/bitnagil/Route.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import kotlinx.serialization.Serializable

@Serializable
sealed interface Route {
@Serializable
data object Splash : Route

@Serializable
data object Intro : Route

@Serializable
data object Login : Route

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import kotlinx.coroutines.flow.Flow
interface AuthTokenDataStore {
val tokenFlow: Flow<AuthToken>

suspend fun hasToken(): Boolean

suspend fun updateAuthToken(accessToken: String, refreshToken: String)

suspend fun updateAccessToken(accessToken: String)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,24 @@ package com.threegap.bitnagil.datastore.auth.storage
import androidx.datastore.core.DataStore
import com.threegap.bitnagil.datastore.auth.model.AuthToken
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.firstOrNull

class AuthTokenDataStoreImpl(
private val dataStore: DataStore<AuthToken>,
) : AuthTokenDataStore {
override val tokenFlow: Flow<AuthToken> = dataStore.data

override suspend fun hasToken(): Boolean {
return try {
val currentToken = dataStore.data.firstOrNull()
currentToken?.let {
!it.accessToken.isNullOrEmpty() && !it.refreshToken.isNullOrEmpty()
} ?: false
} catch (e: Exception) {
false
}
}

override suspend fun updateAuthToken(accessToken: String, refreshToken: String) {
try {
dataStore.updateData {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.threegap.bitnagil.data.auth.datasource

interface AuthLocalDataSource {
suspend fun hasToken(): Boolean

suspend fun updateAuthToken(accessToken: String, refreshToken: String): Result<Unit>
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ class AuthLocalDataSourceImpl @Inject constructor(
private val authTokenDataStore: AuthTokenDataStore,
) : AuthLocalDataSource {

override suspend fun hasToken(): Boolean = authTokenDataStore.hasToken()

override suspend fun updateAuthToken(accessToken: String, refreshToken: String): Result<Unit> =
runCatching {
authTokenDataStore.updateAuthToken(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ class AuthRepositoryImpl @Inject constructor(
authRemoteDataSource.login(socialAccessToken, LoginRequestDto(socialType))
.map { it.toDomain() }

override suspend fun hasToken(): Boolean = authLocalDataSource.hasToken()

override suspend fun updateAuthToken(accessToken: String, refreshToken: String): Result<Unit> =
authLocalDataSource.updateAuthToken(accessToken, refreshToken)
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ import com.threegap.bitnagil.domain.auth.model.AuthSession
interface AuthRepository {
suspend fun login(socialAccessToken: String, socialType: String): Result<AuthSession>

suspend fun hasToken(): Boolean

suspend fun updateAuthToken(accessToken: String, refreshToken: String): Result<Unit>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.threegap.bitnagil.domain.auth.usecase

import com.threegap.bitnagil.domain.auth.repository.AuthRepository
import javax.inject.Inject

class HasTokenUseCase @Inject constructor(
private val authRepository: AuthRepository,
) {
suspend operator fun invoke(): Boolean = authRepository.hasToken()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.threegap.bitnagil.presentation.intro

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview

@Composable
fun IntroScreenContainer() {
IntroScreen()
}

@Composable
private fun IntroScreen(modifier: Modifier = Modifier) {
Column(
modifier = modifier
.fillMaxSize()
.background(Color.White),
) {
Text("인트로 화면")
}
}

@Preview
@Composable
private fun IntroScreenPreview() {
IntroScreen()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.threegap.bitnagil.presentation.splash

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.hilt.navigation.compose.hiltViewModel
import com.threegap.bitnagil.presentation.splash.model.SplashSideEffect
import org.orbitmvi.orbit.compose.collectSideEffect

@Composable
fun SplashScreenContainer(
navigateToIntro: () -> Unit,
navigateToHome: () -> Unit,
viewModel: SplashViewModel = hiltViewModel(),
) {
viewModel.collectSideEffect { sideEffect ->
when (sideEffect) {
is SplashSideEffect.NavigateToIntro -> navigateToIntro()
is SplashSideEffect.NavigateToHome -> navigateToHome()
}
}

SplashScreen()
}

@Composable
private fun SplashScreen(
modifier: Modifier = Modifier,
) {
Box(
modifier = modifier.fillMaxSize(),
) {
Text(
text = "야무진 로고 추가 예정",
modifier = Modifier
.align(Alignment.Center),
)
}
}

@Preview(showBackground = true)
@Composable
private fun SplashScreenPreview() {
SplashScreen()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package com.threegap.bitnagil.presentation.splash

import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewModelScope
import com.threegap.bitnagil.domain.auth.usecase.HasTokenUseCase
import com.threegap.bitnagil.presentation.common.mviviewmodel.MviViewModel
import com.threegap.bitnagil.presentation.splash.model.SplashIntent
import com.threegap.bitnagil.presentation.splash.model.SplashSideEffect
import com.threegap.bitnagil.presentation.splash.model.SplashState
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import org.orbitmvi.orbit.syntax.simple.SimpleSyntax
import javax.inject.Inject

@HiltViewModel
class SplashViewModel @Inject constructor(
private val savedStateHandle: SavedStateHandle,
private val hasTokenUseCase: HasTokenUseCase,
) : MviViewModel<SplashState, SplashSideEffect, SplashIntent>(
initState = SplashState(),
savedStateHandle = savedStateHandle,
) {

init {
checkAutoLogin()
}

override suspend fun SimpleSyntax<SplashState, SplashSideEffect>.reduceState(
intent: SplashIntent,
state: SplashState,
): SplashState? =
when (intent) {
is SplashIntent.SetLoading -> {
state.copy(isLoading = intent.isLoading)
}

is SplashIntent.NavigateToIntro -> {
sendSideEffect(SplashSideEffect.NavigateToIntro)
state.copy(isLoading = false)
}

is SplashIntent.NavigateToHome -> {
sendSideEffect(SplashSideEffect.NavigateToHome)
state.copy(isLoading = false)
}
}

private fun checkAutoLogin() {
viewModelScope.launch {
sendIntent(SplashIntent.SetLoading(true))
val delayDeferred = async { delay(2000L) }
val tokenDeferred = async { hasTokenUseCase() }

delayDeferred.await()
val hasToken = tokenDeferred.await()
Comment thread
l5x5l marked this conversation as resolved.

if (hasToken) {
sendIntent(SplashIntent.NavigateToHome)
} else {
sendIntent(SplashIntent.NavigateToIntro)
}
}
}
Comment thread
wjdrjs00 marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.threegap.bitnagil.presentation.splash.model

import com.threegap.bitnagil.presentation.common.mviviewmodel.MviIntent

sealed class SplashIntent : MviIntent {
data class SetLoading(val isLoading: Boolean) : SplashIntent()
data object NavigateToIntro : SplashIntent()
data object NavigateToHome : SplashIntent()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.threegap.bitnagil.presentation.splash.model

import com.threegap.bitnagil.presentation.common.mviviewmodel.MviSideEffect

sealed interface SplashSideEffect : MviSideEffect {
data object NavigateToIntro : SplashSideEffect
data object NavigateToHome : SplashSideEffect
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.threegap.bitnagil.presentation.splash.model

import com.threegap.bitnagil.presentation.common.mviviewmodel.MviState
import kotlinx.parcelize.Parcelize

@Parcelize
data class SplashState(
val isLoading: Boolean = false,
) : MviState