Skip to content

Commit 55d9c59

Browse files
committed
[BOOK-475] feat: 최근 로그인 방식 관리를 위한 DataStore 환경 구축
1 parent d4e3170 commit 55d9c59

9 files changed

Lines changed: 125 additions & 0 deletions

File tree

core/data/api/src/main/kotlin/com/ninecraft/booket/core/data/api/repository/AuthRepository.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.ninecraft.booket.core.data.api.repository
22

33
import com.ninecraft.booket.core.model.AutoLoginState
4+
import com.ninecraft.booket.core.model.LoginMethod
45
import com.ninecraft.booket.core.model.UserState
56
import kotlinx.coroutines.flow.Flow
67

@@ -16,4 +17,10 @@ interface AuthRepository {
1617
val userState: Flow<UserState>
1718

1819
suspend fun getCurrentUserState(): UserState
20+
21+
val recentLoginMethod: Flow<LoginMethod>
22+
23+
suspend fun setRecentLoginMethod(loginMethod: LoginMethod)
24+
25+
suspend fun clearRecentLoginMethod()
1926
}

core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultAuthRepository.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package com.ninecraft.booket.core.data.impl.repository
22

33
import com.ninecraft.booket.core.common.utils.runSuspendCatching
44
import com.ninecraft.booket.core.data.api.repository.AuthRepository
5+
import com.ninecraft.booket.core.datastore.api.datasource.LoginMethodDataSource
56
import com.ninecraft.booket.core.datastore.api.datasource.TokenDataSource
67
import com.ninecraft.booket.core.model.AutoLoginState
8+
import com.ninecraft.booket.core.model.LoginMethod
79
import com.ninecraft.booket.core.model.UserState
810
import com.ninecraft.booket.core.network.request.LoginRequest
911
import com.ninecraft.booket.core.network.service.ReedService
@@ -19,6 +21,7 @@ private const val KAKAO_PROVIDER_TYPE = "KAKAO"
1921
class DefaultAuthRepository(
2022
private val service: ReedService,
2123
private val tokenDataSource: TokenDataSource,
24+
private val loginMethodDataSource: LoginMethodDataSource,
2225
) : AuthRepository {
2326
override suspend fun login(accessToken: String) = runSuspendCatching {
2427
val response = service.login(
@@ -38,6 +41,7 @@ class DefaultAuthRepository(
3841
override suspend fun withdraw() = runSuspendCatching {
3942
service.withdraw()
4043
clearTokens()
44+
clearRecentLoginMethod()
4145
}
4246

4347
private suspend fun saveTokens(accessToken: String, refreshToken: String) {
@@ -51,6 +55,10 @@ class DefaultAuthRepository(
5155
tokenDataSource.clearTokens()
5256
}
5357

58+
override suspend fun clearRecentLoginMethod() {
59+
loginMethodDataSource.clearRecentLoginMethod()
60+
}
61+
5462
override val autoLoginState = tokenDataSource.accessToken
5563
.map { accessToken ->
5664
if (accessToken.isBlank()) AutoLoginState.NOT_LOGGED_IN else AutoLoginState.LOGGED_IN
@@ -65,4 +73,10 @@ class DefaultAuthRepository(
6573
val accessToken = tokenDataSource.getAccessToken()
6674
return if (accessToken.isBlank()) UserState.Guest else UserState.LoggedIn
6775
}
76+
77+
override val recentLoginMethod = loginMethodDataSource.recentLoginMethod
78+
79+
override suspend fun setRecentLoginMethod(loginMethod: LoginMethod) {
80+
loginMethodDataSource.setRecentLoginMethod(loginMethod)
81+
}
6882
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package com.ninecraft.booket.core.datastore.api.datasource
2+
3+
import com.ninecraft.booket.core.model.LoginMethod
4+
import kotlinx.coroutines.flow.Flow
5+
6+
interface LoginMethodDataSource {
7+
val recentLoginMethod: Flow<LoginMethod>
8+
suspend fun setRecentLoginMethod(loginMethod: LoginMethod)
9+
suspend fun clearRecentLoginMethod()
10+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.ninecraft.booket.core.datastore.impl.datasource
2+
3+
import androidx.datastore.core.DataStore
4+
import androidx.datastore.preferences.core.Preferences
5+
import androidx.datastore.preferences.core.edit
6+
import androidx.datastore.preferences.core.stringPreferencesKey
7+
import com.ninecraft.booket.core.datastore.api.datasource.LoginMethodDataSource
8+
import com.ninecraft.booket.core.datastore.impl.di.LoginMethodDataStore
9+
import com.ninecraft.booket.core.datastore.impl.util.handleIOException
10+
import com.ninecraft.booket.core.di.DataScope
11+
import com.ninecraft.booket.core.model.LoginMethod
12+
import dev.zacsweers.metro.Inject
13+
import dev.zacsweers.metro.SingleIn
14+
import kotlinx.coroutines.flow.Flow
15+
import kotlinx.coroutines.flow.map
16+
17+
@SingleIn(DataScope::class)
18+
@Inject
19+
class DefaultLoginMethodDataSource(
20+
@LoginMethodDataStore private val dataStore: DataStore<Preferences>,
21+
) : LoginMethodDataSource {
22+
override val recentLoginMethod: Flow<LoginMethod> = dataStore.data
23+
.handleIOException()
24+
.map { prefs ->
25+
val method = prefs[RECENT_LOGIN_METHOD]
26+
when (method) {
27+
"KAKAO" -> LoginMethod.KAKAO
28+
"GOOGLE" -> LoginMethod.GOOGLE
29+
else -> LoginMethod.NONE
30+
}
31+
}
32+
33+
override suspend fun setRecentLoginMethod(loginMethod: LoginMethod) {
34+
dataStore.edit { prefs ->
35+
prefs[RECENT_LOGIN_METHOD] = loginMethod.name
36+
}
37+
}
38+
39+
override suspend fun clearRecentLoginMethod() {
40+
dataStore.edit { prefs ->
41+
prefs.remove(RECENT_LOGIN_METHOD)
42+
}
43+
}
44+
45+
companion object {
46+
private val RECENT_LOGIN_METHOD = stringPreferencesKey("RECENT_LOGIN_METHOD")
47+
}
48+
}

core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/di/DataStoreGraph.kt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ import androidx.datastore.preferences.core.Preferences
66
import androidx.datastore.preferences.preferencesDataStore
77
import com.ninecraft.booket.core.datastore.api.datasource.BookRecentSearchDataSource
88
import com.ninecraft.booket.core.datastore.api.datasource.LibraryRecentSearchDataSource
9+
import com.ninecraft.booket.core.datastore.api.datasource.LoginMethodDataSource
910
import com.ninecraft.booket.core.datastore.api.datasource.NotificationDataSource
1011
import com.ninecraft.booket.core.datastore.api.datasource.OnboardingDataSource
1112
import com.ninecraft.booket.core.datastore.api.datasource.TokenDataSource
1213
import com.ninecraft.booket.core.datastore.impl.datasource.DefaultBookRecentSearchDataSource
1314
import com.ninecraft.booket.core.datastore.impl.datasource.DefaultLibraryRecentSearchDataSource
15+
import com.ninecraft.booket.core.datastore.impl.datasource.DefaultLoginMethodDataSource
1416
import com.ninecraft.booket.core.datastore.impl.datasource.DefaultNotificationDataSource
1517
import com.ninecraft.booket.core.datastore.impl.datasource.DefaultOnboardingDataSource
1618
import com.ninecraft.booket.core.datastore.impl.datasource.DefaultTokenDataSource
@@ -25,12 +27,14 @@ private const val BOOK_RECENT_SEARCH_DATASTORE_NAME = "BOOK_RECENT_SEARCH_DATAST
2527
private const val LIBRARY_RECENT_SEARCH_DATASTORE_NAME = "LIBRARY_RECENT_SEARCH_DATASTORE"
2628
private const val ONBOARDING_DATASTORE_NAME = "ONBOARDING_DATASTORE"
2729
private const val NOTIFICATION_DATASTORE_NAME = "NOTIFICATION_DATASTORE"
30+
private const val LOGIN_METHOD_DATASTORE_NAME = "LOGIN_METHOD_DATASTORE"
2831

2932
private val Context.tokenDataStore by preferencesDataStore(name = TOKEN_DATASTORE_NAME)
3033
private val Context.bookRecentSearchDataStore by preferencesDataStore(name = BOOK_RECENT_SEARCH_DATASTORE_NAME)
3134
private val Context.libraryRecentSearchDataStore by preferencesDataStore(name = LIBRARY_RECENT_SEARCH_DATASTORE_NAME)
3235
private val Context.onboardingDataStore by preferencesDataStore(name = ONBOARDING_DATASTORE_NAME)
3336
private val Context.notificationDataStore by preferencesDataStore(name = NOTIFICATION_DATASTORE_NAME)
37+
private val Context.loginMethodDataStore by preferencesDataStore(name = LOGIN_METHOD_DATASTORE_NAME)
3438

3539
@ContributesTo(DataScope::class)
3640
interface DataStoreGraph {
@@ -65,6 +69,12 @@ interface DataStoreGraph {
6569
@ApplicationContext context: Context,
6670
): DataStore<Preferences> = context.notificationDataStore
6771

72+
@LoginMethodDataStore
73+
@Provides
74+
fun provideLoginMethodDataStore(
75+
@ApplicationContext context: Context,
76+
): DataStore<Preferences> = context.loginMethodDataStore
77+
6878
@Binds
6979
val DefaultTokenDataSource.bind: TokenDataSource
7080

@@ -79,4 +89,7 @@ interface DataStoreGraph {
7989

8090
@Binds
8191
val DefaultNotificationDataSource.bind: NotificationDataSource
92+
93+
@Binds
94+
val DefaultLoginMethodDataSource.bind: LoginMethodDataSource
8295
}

core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/di/DataStoreQualifier.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,3 +21,7 @@ annotation class OnboardingDataStore
2121
@Qualifier
2222
@Retention(AnnotationRetention.BINARY)
2323
annotation class NotificationDataStore
24+
25+
@Qualifier
26+
@Retention(AnnotationRetention.BINARY)
27+
annotation class LoginMethodDataStore
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.ninecraft.booket.core.model
2+
3+
enum class LoginMethod {
4+
NONE,
5+
KAKAO,
6+
GOOGLE,
7+
}

feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.ninecraft.booket.feature.login
22

33
import androidx.compose.runtime.Composable
4+
import androidx.compose.runtime.LaunchedEffect
45
import androidx.compose.runtime.getValue
56
import androidx.compose.runtime.mutableStateOf
67
import androidx.compose.runtime.rememberCoroutineScope
@@ -10,6 +11,7 @@ import com.ninecraft.booket.core.common.constants.ErrorScope
1011
import com.ninecraft.booket.core.common.event.postErrorDialog
1112
import com.ninecraft.booket.core.data.api.repository.AuthRepository
1213
import com.ninecraft.booket.core.data.api.repository.UserRepository
14+
import com.ninecraft.booket.core.model.LoginMethod
1315
import com.ninecraft.booket.feature.screens.HomeScreen
1416
import com.ninecraft.booket.feature.screens.LoginScreen
1517
import com.ninecraft.booket.feature.screens.TermsAgreementScreen
@@ -50,6 +52,15 @@ class LoginPresenter(
5052
val scope = rememberCoroutineScope()
5153
var isLoading by rememberRetained { mutableStateOf(false) }
5254
var sideEffect by rememberRetained { mutableStateOf<LoginSideEffect?>(null) }
55+
var showLoginTooltip by rememberRetained { mutableStateOf(false) }
56+
var recentLoginMethod by rememberRetained { mutableStateOf(LoginMethod.NONE) }
57+
58+
LaunchedEffect(Unit) {
59+
authRepository.recentLoginMethod.collect { method ->
60+
recentLoginMethod = method
61+
showLoginTooltip = method != LoginMethod.NONE
62+
}
63+
}
5364

5465
fun navigateAfterLogin() {
5566
scope.launch {
@@ -97,6 +108,7 @@ class LoginPresenter(
97108
isLoading = true
98109
authRepository.login(event.accessToken)
99110
.onSuccess {
111+
authRepository.setRecentLoginMethod(LoginMethod.KAKAO)
100112
userRepository.syncFcmToken()
101113
navigateAfterLogin()
102114
}.onFailure { exception ->
@@ -120,6 +132,10 @@ class LoginPresenter(
120132
is LoginUiEvent.OnCloseButtonClick -> {
121133
navigator.pop()
122134
}
135+
136+
is LoginUiEvent.OnDismissLoginTooltip -> {
137+
showLoginTooltip = false
138+
}
123139
}
124140
}
125141

@@ -131,6 +147,8 @@ class LoginPresenter(
131147
isLoading = isLoading,
132148
returnToScreen = screen.returnToScreen,
133149
sideEffect = sideEffect,
150+
showLoginTooltip = showLoginTooltip,
151+
recentLoginMethod = recentLoginMethod,
134152
eventSink = ::handleEvent,
135153
)
136154
}

feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginUiState.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.ninecraft.booket.feature.login
22

33
import androidx.compose.runtime.Immutable
4+
import com.ninecraft.booket.core.model.LoginMethod
45
import com.slack.circuit.runtime.CircuitUiEvent
56
import com.slack.circuit.runtime.CircuitUiState
67
import com.slack.circuit.runtime.screen.Screen
@@ -10,6 +11,8 @@ data class LoginUiState(
1011
val isLoading: Boolean = false,
1112
val returnToScreen: Screen? = null,
1213
val sideEffect: LoginSideEffect? = null,
14+
val showLoginTooltip: Boolean = false,
15+
val recentLoginMethod: LoginMethod = LoginMethod.NONE,
1316
val eventSink: (LoginUiEvent) -> Unit,
1417
) : CircuitUiState
1518

@@ -28,4 +31,5 @@ sealed interface LoginUiEvent : CircuitUiEvent {
2831
data class LoginFailure(val message: String) : LoginUiEvent
2932
data object OnGuestLoginButtonClick : LoginUiEvent
3033
data object OnCloseButtonClick : LoginUiEvent
34+
data object OnDismissLoginTooltip : LoginUiEvent
3135
}

0 commit comments

Comments
 (0)