Skip to content

Commit 70e0774

Browse files
authored
Merge pull request #243 from YAPP-Github/BOOK-475-feature/#233
feat: 최근 로그인 계정 정보 안내 기능 구현
2 parents 7248274 + b9823b0 commit 70e0774

17 files changed

Lines changed: 606 additions & 147 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,5 +1,6 @@
11
package com.ninecraft.booket.core.data.api.repository
22

3+
import com.ninecraft.booket.core.model.LoginMethod
34
import com.ninecraft.booket.core.model.state.AutoLoginState
45
import com.ninecraft.booket.core.model.state.UserState
56
import kotlinx.coroutines.flow.Flow
@@ -19,4 +20,10 @@ interface AuthRepository {
1920
val userState: Flow<UserState>
2021

2122
suspend fun getCurrentUserState(): UserState
23+
24+
val recentLoginMethod: Flow<LoginMethod>
25+
26+
suspend fun setRecentLoginMethod(loginMethod: LoginMethod)
27+
28+
suspend fun clearRecentLoginMethod()
2229
}

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.di.DataScope
8+
import com.ninecraft.booket.core.model.LoginMethod
79
import com.ninecraft.booket.core.model.state.AutoLoginState
810
import com.ninecraft.booket.core.model.state.UserState
911
import com.ninecraft.booket.core.network.request.LoginRequest
@@ -17,6 +19,7 @@ import kotlinx.coroutines.flow.map
1719
class DefaultAuthRepository(
1820
private val service: ReedService,
1921
private val tokenDataSource: TokenDataSource,
22+
private val loginMethodDataSource: LoginMethodDataSource,
2023
) : AuthRepository {
2124
override suspend fun login(
2225
providerType: String,
@@ -39,6 +42,7 @@ class DefaultAuthRepository(
3942
override suspend fun withdraw() = runSuspendCatching {
4043
service.withdraw()
4144
clearTokens()
45+
clearRecentLoginMethod()
4246
}
4347

4448
private suspend fun saveTokens(accessToken: String, refreshToken: String) {
@@ -66,4 +70,14 @@ class DefaultAuthRepository(
6670
val accessToken = tokenDataSource.getAccessToken()
6771
return if (accessToken.isBlank()) UserState.Guest else UserState.LoggedIn
6872
}
73+
74+
override val recentLoginMethod = loginMethodDataSource.recentLoginMethod
75+
76+
override suspend fun setRecentLoginMethod(loginMethod: LoginMethod) {
77+
loginMethodDataSource.setRecentLoginMethod(loginMethod)
78+
}
79+
80+
override suspend fun clearRecentLoginMethod() {
81+
loginMethodDataSource.clearRecentLoginMethod()
82+
}
6983
}
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

core/designsystem/stability/designsystem.stability

Lines changed: 37 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -163,39 +163,6 @@ public fun com.ninecraft.booket.core.designsystem.component.checkbox.TickOnlyChe
163163
- onCheckedChange: STABLE (function type)
164164
- modifier: STABLE (marked @Stable or @Immutable)
165165

166-
@Composable
167-
public fun com.ninecraft.booket.core.designsystem.component.chip.ReedRemovableChip(label: kotlin.String, chipSizeStyle: com.ninecraft.booket.core.designsystem.component.chip.ChipSizeStyle, onRemove: kotlin.Function0<kotlin.Unit>, modifier: androidx.compose.ui.Modifier): kotlin.Unit
168-
skippable: true
169-
restartable: true
170-
params:
171-
- label: STABLE (String is immutable)
172-
- chipSizeStyle: STABLE (class with no mutable properties)
173-
- onRemove: STABLE (function type)
174-
- modifier: STABLE (marked @Stable or @Immutable)
175-
176-
@Composable
177-
public fun com.ninecraft.booket.core.designsystem.component.chip.ReedSelectableChip(label: kotlin.String, chipSizeStyle: com.ninecraft.booket.core.designsystem.component.chip.ChipSizeStyle, selected: kotlin.Boolean, onClick: kotlin.Function0<kotlin.Unit>, modifier: androidx.compose.ui.Modifier): kotlin.Unit
178-
skippable: true
179-
restartable: true
180-
params:
181-
- label: STABLE (String is immutable)
182-
- chipSizeStyle: STABLE (class with no mutable properties)
183-
- selected: STABLE (primitive type)
184-
- onClick: STABLE (function type)
185-
- modifier: STABLE (marked @Stable or @Immutable)
186-
187-
@Composable
188-
public fun com.ninecraft.booket.core.designsystem.component.chip.mediumChipStyle(): com.ninecraft.booket.core.designsystem.component.chip.ChipSizeStyle
189-
skippable: true
190-
restartable: true
191-
params:
192-
193-
@Composable
194-
public fun com.ninecraft.booket.core.designsystem.component.chip.smallChipStyle(): com.ninecraft.booket.core.designsystem.component.chip.ChipSizeStyle
195-
skippable: true
196-
restartable: true
197-
params:
198-
199166
@Composable
200167
public fun com.ninecraft.booket.core.designsystem.component.textfield.ReedRecordTextField(recordState: androidx.compose.foundation.text.input.TextFieldState, recordHintRes: kotlin.Int, modifier: androidx.compose.ui.Modifier, inputTransformation: androidx.compose.foundation.text.input.InputTransformation?, keyboardOptions: androidx.compose.foundation.text.KeyboardOptions, lineLimits: androidx.compose.foundation.text.input.TextFieldLineLimits, isError: kotlin.Boolean, errorMessage: kotlin.String, onClear: kotlin.Function0<kotlin.Unit>?, onNext: kotlin.Function0<kotlin.Unit>, backgroundColor: androidx.compose.ui.graphics.Color, textColor: androidx.compose.ui.graphics.Color, cornerShape: androidx.compose.foundation.shape.RoundedCornerShape, borderStroke: androidx.compose.foundation.BorderStroke): kotlin.Unit
201168
skippable: true
@@ -232,3 +199,40 @@ public fun com.ninecraft.booket.core.designsystem.component.textfield.ReedTextFi
232199
- borderStroke: STABLE (marked @Stable or @Immutable)
233200
- searchIconTint: STABLE (marked @Stable or @Immutable)
234201

202+
@Composable
203+
public fun com.ninecraft.booket.core.designsystem.theme.ReedTheme(content: @[Composable] androidx.compose.runtime.internal.ComposableFunction0<kotlin.Unit>): kotlin.Unit
204+
skippable: true
205+
restartable: true
206+
params:
207+
- content: STABLE (composable function type)
208+
209+
@Composable
210+
public fun com.ninecraft.booket.core.designsystem.theme.ReedTheme.border(): com.ninecraft.booket.core.designsystem.theme.ReedBorder
211+
skippable: true
212+
restartable: true
213+
params:
214+
215+
@Composable
216+
public fun com.ninecraft.booket.core.designsystem.theme.ReedTheme.colors(): com.ninecraft.booket.core.designsystem.theme.ReedColorScheme
217+
skippable: true
218+
restartable: true
219+
params:
220+
221+
@Composable
222+
public fun com.ninecraft.booket.core.designsystem.theme.ReedTheme.radius(): com.ninecraft.booket.core.designsystem.theme.ReedRadius
223+
skippable: true
224+
restartable: true
225+
params:
226+
227+
@Composable
228+
public fun com.ninecraft.booket.core.designsystem.theme.ReedTheme.spacing(): com.ninecraft.booket.core.designsystem.theme.ReedSpacing
229+
skippable: true
230+
restartable: true
231+
params:
232+
233+
@Composable
234+
public fun com.ninecraft.booket.core.designsystem.theme.ReedTheme.typography(): com.ninecraft.booket.core.designsystem.theme.ReedTypography
235+
skippable: true
236+
restartable: true
237+
params:
238+
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: 24 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 {
@@ -102,6 +113,13 @@ class LoginPresenter(
102113
isLoading = true
103114
authRepository.login(event.providerType, event.token)
104115
.onSuccess {
116+
authRepository.setRecentLoginMethod(
117+
if (event.providerType == LoginUiEvent.PROVIDER_TYPE_KAKAO) {
118+
LoginMethod.KAKAO
119+
} else {
120+
LoginMethod.GOOGLE
121+
},
122+
)
105123
userRepository.syncFcmToken()
106124
navigateAfterLogin()
107125
}.onFailure { exception ->
@@ -125,6 +143,10 @@ class LoginPresenter(
125143
is LoginUiEvent.OnCloseButtonClick -> {
126144
navigator.pop()
127145
}
146+
147+
is LoginUiEvent.OnDismissLoginTooltip -> {
148+
showLoginTooltip = false
149+
}
128150
}
129151
}
130152

@@ -136,6 +158,8 @@ class LoginPresenter(
136158
isLoading = isLoading,
137159
returnToScreen = screen.returnToScreen,
138160
sideEffect = sideEffect,
161+
showLoginTooltip = showLoginTooltip,
162+
recentLoginMethod = recentLoginMethod,
139163
eventSink = ::handleEvent,
140164
)
141165
}

0 commit comments

Comments
 (0)