Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 0 additions & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,6 @@ android {
}

dependencies {
implementation(project(":domain"))
implementation(project(":presentation"))
implementation(project(":data"))

Expand Down
31 changes: 3 additions & 28 deletions app/src/main/java/com/smtm/pickle/MainActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,18 +5,15 @@ import androidx.activity.ComponentActivity
import androidx.activity.SystemBarStyle
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.runtime.remember
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.navigation.compose.rememberNavController
import com.smtm.pickle.presentation.PickleApp
import com.smtm.pickle.presentation.designsystem.theme.PickleTheme
import com.smtm.pickle.presentation.navigation.GlobalNavEvent
import com.smtm.pickle.presentation.navigation.PickleNavHost
import com.smtm.pickle.presentation.navigation.route.LoginRoute
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint
class MainActivity : ComponentActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

Expand All @@ -33,29 +30,7 @@ class MainActivity : ComponentActivity() {

setContent {
PickleTheme {
val navController = rememberNavController()
val handleGlobalNavEvent: (GlobalNavEvent) -> Unit = remember(navController) {
{ event ->
when (event) {
is GlobalNavEvent.Logout -> {
navController.navigate(LoginRoute) {
popUpTo(navController.graph.id) { inclusive = true }
}
}

is GlobalNavEvent.SessionExpired -> {
navController.navigate(LoginRoute) {
popUpTo(navController.graph.id) { inclusive = true }
}
}
}
}
}

PickleNavHost(
navController = navController,
onGlobalNavEvent = handleGlobalNavEvent,
)
PickleApp()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
package com.smtm.pickle.data.source.local.provider

import com.smtm.pickle.data.source.local.datastore.TokenDataStore
import com.smtm.pickle.domain.model.auth.AuthState
import com.smtm.pickle.domain.model.auth.AuthToken
import com.smtm.pickle.domain.provider.TokenProvider
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import javax.inject.Inject
import javax.inject.Singleton

Expand All @@ -15,6 +18,8 @@ class TokenProviderImpl @Inject constructor(
@Volatile
private var cachedToken: AuthToken? = null

private val _authState = MutableStateFlow<AuthState>(AuthState.Authenticated)

override suspend fun init() {
cachedToken = tokenDataStore.getToken()
}
Expand All @@ -34,6 +39,13 @@ class TokenProviderImpl @Inject constructor(
override suspend fun clearToken() {
cachedToken = null
tokenDataStore.clearToken()
_authState.value = AuthState.Unauthenticated
}

override suspend fun expireSession() {
cachedToken = null
tokenDataStore.clearToken()
_authState.value = AuthState.SessionExpired
}
Comment thread
Jooman-Lee marked this conversation as resolved.

override fun getCachedToken(): AuthToken? = cachedToken
Expand All @@ -44,4 +56,7 @@ class TokenProviderImpl @Inject constructor(
override fun getAccessTokenFlow(): Flow<String?> =
tokenDataStore.getAccessTokenFlow()

override fun getAuthStateFlow(): Flow<AuthState> =
_authState.asStateFlow()

}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ class TokenRefresher @Inject constructor(
if (e is CancellationException) throw e
// 리프레시 토큰 만료 또는 무효일 때만 로그아웃 처리
if (e is HttpException && (e.code() == 401 || e.code() == 400)) {
tokenProvider.clearToken()
tokenProvider.expireSession()
}
return@withLock null
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.smtm.pickle.domain.model.auth

sealed interface AuthState {
data object Authenticated : AuthState
data object Unauthenticated : AuthState
data object SessionExpired : AuthState
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.smtm.pickle.domain.provider

import com.smtm.pickle.domain.model.auth.AuthState
import com.smtm.pickle.domain.model.auth.AuthToken
import kotlinx.coroutines.flow.Flow

Expand All @@ -19,9 +20,13 @@ interface TokenProvider {

suspend fun clearToken()

suspend fun expireSession()

fun getCachedToken(): AuthToken?

fun getTokenFlow(): Flow<AuthToken?>

fun getAccessTokenFlow(): Flow<String?>

fun getAuthStateFlow(): Flow<AuthState>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.smtm.pickle.presentation

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.smtm.pickle.domain.model.auth.AuthState
import com.smtm.pickle.domain.provider.TokenProvider
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch
import javax.inject.Inject

@HiltViewModel
class AppViewModel @Inject constructor(
private val tokenProvider: TokenProvider,
) : ViewModel() {

private val _authNavEvent = Channel<AuthNavEvent>(Channel.CONFLATED)
val authNavEvent = _authNavEvent.receiveAsFlow()

init {
viewModelScope.launch {
tokenProvider.getAuthStateFlow().collect { state ->
when (state) {
AuthState.SessionExpired -> _authNavEvent.trySend(AuthNavEvent.ToLoginWithMessage)
AuthState.Unauthenticated -> _authNavEvent.trySend(AuthNavEvent.ToLogin)
else -> {}
}
}
}
}
}

sealed interface AuthNavEvent {
data object ToLogin : AuthNavEvent
data object ToLoginWithMessage : AuthNavEvent
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.smtm.pickle.presentation

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.remember
import androidx.compose.ui.res.stringResource
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.navigation.compose.rememberNavController
import com.smtm.pickle.presentation.designsystem.components.snackbar.PickleSnackbar
import com.smtm.pickle.presentation.designsystem.components.snackbar.SnackbarHost
import com.smtm.pickle.presentation.designsystem.components.snackbar.model.SnackbarState
import com.smtm.pickle.presentation.navigation.PickleNavHost
import com.smtm.pickle.presentation.navigation.route.LoginRoute

@Composable
fun PickleApp(
appViewModel: AppViewModel = hiltViewModel(),
) {
val navController = rememberNavController()
val snackbarState = remember { SnackbarState() }
val sessionExpiredMessage = stringResource(R.string.global_session_expired)

LaunchedEffect(Unit) {
appViewModel.authNavEvent.collect { event ->
navController.navigate(LoginRoute) {
popUpTo(navController.graph.id) { inclusive = true }
}
if (event is AuthNavEvent.ToLoginWithMessage) {
snackbarState.show(
PickleSnackbar.snackbarShort(message = sessionExpiredMessage)
)
}
}
}

PickleNavHost(navController = navController)
SnackbarHost(snackbarState)
Comment on lines +36 to +37
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

PickleNavHostSnackbarHostBox와 같은 레이아웃 컨테이너 없이 함께 배치되었습니다. SnackbarHost가 화면 콘텐츠 위에 오버레이되어야 한다면, 두 컴포저블을 Box로 감싸야 합니다. 그렇지 않으면 SnackbarHostPickleNavHost 아래에 표시되는 등 의도치 않은 UI가 발생할 수 있습니다.

참고: import androidx.compose.foundation.layout.Box 추가가 필요할 수 있습니다.

    Box {
        PickleNavHost(navController = navController)
        SnackbarHost(snackbarState)
    }

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ import com.smtm.pickle.presentation.home.HomeScreen
import com.smtm.pickle.presentation.main.component.DimOverlay
import com.smtm.pickle.presentation.main.component.HomeExpandableFab
import com.smtm.pickle.presentation.mypage.MyPageScreen
import com.smtm.pickle.presentation.navigation.GlobalNavEvent
import com.smtm.pickle.presentation.navigation.PickleBottomNavigationBar
import com.smtm.pickle.presentation.navigation.route.AlarmSettingRoute
import com.smtm.pickle.presentation.navigation.route.HomeTabRoute
Expand All @@ -51,7 +50,6 @@ import java.time.LocalDate
@Composable
fun MainScreen(
rootNavController: NavHostController,
onGlobalNavEvent: (GlobalNavEvent) -> Unit,
) {
val tabNavController = rememberNavController()
val navBackStackEntry by tabNavController.currentBackStackEntryAsState()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import com.smtm.pickle.presentation.mypage.profile.nicknamesetting.NicknameSetti
import com.smtm.pickle.presentation.navigation.route.AlarmSettingRoute
import com.smtm.pickle.presentation.navigation.route.LedgerCreateRoute
import com.smtm.pickle.presentation.navigation.route.LedgerDetailRoute
import com.smtm.pickle.presentation.navigation.route.LoginRoute
import com.smtm.pickle.presentation.navigation.route.MyLedgerRoute
import com.smtm.pickle.presentation.navigation.route.MyProfileRoute
import com.smtm.pickle.presentation.navigation.route.NicknameSettingRoute
Expand Down Expand Up @@ -45,14 +44,6 @@ fun NavGraphBuilder.myPageDestinations(navController: NavController) {
context.startActivity(intent)
}
},
onNavigateToLogin = {
navController.navigate(LoginRoute) {
popUpTo(navController.graph.id) {
inclusive = true
}
launchSingleTop = true
}
},
onNavigateBack = {
navController.popBackStack()
},
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ import com.smtm.pickle.presentation.verdict.navigation.verdictDestinations
@Composable
fun PickleNavHost(
navController: NavHostController = rememberNavController(),
onGlobalNavEvent: (GlobalNavEvent) -> Unit,
) {
NavHost(
navController = navController,
Expand All @@ -36,7 +35,6 @@ fun PickleNavHost(
composable<MainRoute> {
MainScreen(
rootNavController = navController,
onGlobalNavEvent = onGlobalNavEvent
)
}

Expand All @@ -48,4 +46,4 @@ fun PickleNavHost(
myPageDestinations(navController)
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ import com.smtm.pickle.presentation.setting.model.SettingTrailingType
fun SettingScreen(
viewModel: SettingViewModel = hiltViewModel(),
onNavigateToPrivacyPolicy: () -> Unit,
onNavigateToLogin: () -> Unit,
onNavigateBack: () -> Unit,
) {
val context = LocalContext.current
Expand All @@ -56,10 +55,6 @@ fun SettingScreen(
onNavigateToPrivacyPolicy()
}

SettingEffect.NavigateToLogin -> {
onNavigateToLogin()
}

SettingEffect.NavigateBack -> {
onNavigateBack()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,6 @@ class SettingViewModel @Inject constructor(
logoutUseCase()
.onSuccess {
_uiState.update { it.copy(isLoading = false) }
_effect.emit(SettingEffect.NavigateToLogin)
}
.onFailure {
_uiState.update { it.copy(isLoading = false) }
Expand All @@ -79,7 +78,6 @@ class SettingViewModel @Inject constructor(
withdrawAccountUseCase()
.onSuccess {
_uiState.update { it.copy(isLoading = false) }
_effect.emit(SettingEffect.NavigateToLogin)
}
.onFailure {
_uiState.update { it.copy(isLoading = false) }
Expand Down Expand Up @@ -115,7 +113,6 @@ data class SettingUiState(

sealed interface SettingEffect {
data object NavigateToPrivacyPolicy : SettingEffect
data object NavigateToLogin : SettingEffect
data object NavigateBack : SettingEffect
data object OpenGooglePlay : SettingEffect
data class ShowSnackBar(val msg: String) : SettingEffect
Expand Down
3 changes: 3 additions & 0 deletions presentation/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -142,4 +142,7 @@
<string name="setting_dialog_withdraw_description">탈퇴하면 이전 기록이 모두 삭제돼요.\n그래도 괜찮으신가요?</string>
<string name="setting_dialog_withdraw_confirm">탈퇴하기</string>

<!-- Global -->
<string name="global_session_expired">세션이 만료되었습니다. 다시 로그인해주세요.</string>

</resources>
Loading