diff --git a/core/data/api/src/main/kotlin/com/ninecraft/booket/core/data/api/repository/AuthRepository.kt b/core/data/api/src/main/kotlin/com/ninecraft/booket/core/data/api/repository/AuthRepository.kt index c8943668..5dde9334 100644 --- a/core/data/api/src/main/kotlin/com/ninecraft/booket/core/data/api/repository/AuthRepository.kt +++ b/core/data/api/src/main/kotlin/com/ninecraft/booket/core/data/api/repository/AuthRepository.kt @@ -1,6 +1,7 @@ package com.ninecraft.booket.core.data.api.repository import com.ninecraft.booket.core.model.AutoLoginState +import com.ninecraft.booket.core.model.LoginMethod import com.ninecraft.booket.core.model.UserState import kotlinx.coroutines.flow.Flow @@ -16,4 +17,10 @@ interface AuthRepository { val userState: Flow suspend fun getCurrentUserState(): UserState + + val recentLoginMethod: Flow + + suspend fun setRecentLoginMethod(loginMethod: LoginMethod) + + suspend fun clearRecentLoginMethod() } diff --git a/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultAuthRepository.kt b/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultAuthRepository.kt index 4172d9e0..3163b68f 100644 --- a/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultAuthRepository.kt +++ b/core/data/impl/src/main/kotlin/com/ninecraft/booket/core/data/impl/repository/DefaultAuthRepository.kt @@ -2,8 +2,10 @@ package com.ninecraft.booket.core.data.impl.repository import com.ninecraft.booket.core.common.utils.runSuspendCatching import com.ninecraft.booket.core.data.api.repository.AuthRepository +import com.ninecraft.booket.core.datastore.api.datasource.LoginMethodDataSource import com.ninecraft.booket.core.datastore.api.datasource.TokenDataSource import com.ninecraft.booket.core.model.AutoLoginState +import com.ninecraft.booket.core.model.LoginMethod import com.ninecraft.booket.core.model.UserState import com.ninecraft.booket.core.network.request.LoginRequest import com.ninecraft.booket.core.network.service.ReedService @@ -19,6 +21,7 @@ private const val KAKAO_PROVIDER_TYPE = "KAKAO" class DefaultAuthRepository( private val service: ReedService, private val tokenDataSource: TokenDataSource, + private val loginMethodDataSource: LoginMethodDataSource, ) : AuthRepository { override suspend fun login(accessToken: String) = runSuspendCatching { val response = service.login( @@ -38,6 +41,7 @@ class DefaultAuthRepository( override suspend fun withdraw() = runSuspendCatching { service.withdraw() clearTokens() + clearRecentLoginMethod() } private suspend fun saveTokens(accessToken: String, refreshToken: String) { @@ -51,6 +55,10 @@ class DefaultAuthRepository( tokenDataSource.clearTokens() } + override suspend fun clearRecentLoginMethod() { + loginMethodDataSource.clearRecentLoginMethod() + } + override val autoLoginState = tokenDataSource.accessToken .map { accessToken -> if (accessToken.isBlank()) AutoLoginState.NOT_LOGGED_IN else AutoLoginState.LOGGED_IN @@ -65,4 +73,10 @@ class DefaultAuthRepository( val accessToken = tokenDataSource.getAccessToken() return if (accessToken.isBlank()) UserState.Guest else UserState.LoggedIn } + + override val recentLoginMethod = loginMethodDataSource.recentLoginMethod + + override suspend fun setRecentLoginMethod(loginMethod: LoginMethod) { + loginMethodDataSource.setRecentLoginMethod(loginMethod) + } } diff --git a/core/datastore/api/src/main/kotlin/com/ninecraft/booket/core/datastore/api/datasource/LoginMethodDataSource.kt b/core/datastore/api/src/main/kotlin/com/ninecraft/booket/core/datastore/api/datasource/LoginMethodDataSource.kt new file mode 100644 index 00000000..5a3bcf79 --- /dev/null +++ b/core/datastore/api/src/main/kotlin/com/ninecraft/booket/core/datastore/api/datasource/LoginMethodDataSource.kt @@ -0,0 +1,10 @@ +package com.ninecraft.booket.core.datastore.api.datasource + +import com.ninecraft.booket.core.model.LoginMethod +import kotlinx.coroutines.flow.Flow + +interface LoginMethodDataSource { + val recentLoginMethod: Flow + suspend fun setRecentLoginMethod(loginMethod: LoginMethod) + suspend fun clearRecentLoginMethod() +} \ No newline at end of file diff --git a/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultLoginMethodDataSource.kt b/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultLoginMethodDataSource.kt new file mode 100644 index 00000000..0cef2c5e --- /dev/null +++ b/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/datasource/DefaultLoginMethodDataSource.kt @@ -0,0 +1,48 @@ +package com.ninecraft.booket.core.datastore.impl.datasource + +import androidx.datastore.core.DataStore +import androidx.datastore.preferences.core.Preferences +import androidx.datastore.preferences.core.edit +import androidx.datastore.preferences.core.stringPreferencesKey +import com.ninecraft.booket.core.datastore.api.datasource.LoginMethodDataSource +import com.ninecraft.booket.core.datastore.impl.di.LoginMethodDataStore +import com.ninecraft.booket.core.datastore.impl.util.handleIOException +import com.ninecraft.booket.core.di.DataScope +import com.ninecraft.booket.core.model.LoginMethod +import dev.zacsweers.metro.Inject +import dev.zacsweers.metro.SingleIn +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +@SingleIn(DataScope::class) +@Inject +class DefaultLoginMethodDataSource( + @LoginMethodDataStore private val dataStore: DataStore, +) : LoginMethodDataSource { + override val recentLoginMethod: Flow = dataStore.data + .handleIOException() + .map { prefs -> + val method = prefs[RECENT_LOGIN_METHOD] + when (method) { + "KAKAO" -> LoginMethod.KAKAO + "GOOGLE" -> LoginMethod.GOOGLE + else -> LoginMethod.NONE + } + } + + override suspend fun setRecentLoginMethod(loginMethod: LoginMethod) { + dataStore.edit { prefs -> + prefs[RECENT_LOGIN_METHOD] = loginMethod.name + } + } + + override suspend fun clearRecentLoginMethod() { + dataStore.edit { prefs -> + prefs.remove(RECENT_LOGIN_METHOD) + } + } + + companion object { + private val RECENT_LOGIN_METHOD = stringPreferencesKey("RECENT_LOGIN_METHOD") + } +} \ No newline at end of file diff --git a/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/di/DataStoreGraph.kt b/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/di/DataStoreGraph.kt index a052bb43..b390b5ab 100644 --- a/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/di/DataStoreGraph.kt +++ b/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/di/DataStoreGraph.kt @@ -6,11 +6,13 @@ import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.preferencesDataStore import com.ninecraft.booket.core.datastore.api.datasource.BookRecentSearchDataSource import com.ninecraft.booket.core.datastore.api.datasource.LibraryRecentSearchDataSource +import com.ninecraft.booket.core.datastore.api.datasource.LoginMethodDataSource import com.ninecraft.booket.core.datastore.api.datasource.NotificationDataSource import com.ninecraft.booket.core.datastore.api.datasource.OnboardingDataSource import com.ninecraft.booket.core.datastore.api.datasource.TokenDataSource import com.ninecraft.booket.core.datastore.impl.datasource.DefaultBookRecentSearchDataSource import com.ninecraft.booket.core.datastore.impl.datasource.DefaultLibraryRecentSearchDataSource +import com.ninecraft.booket.core.datastore.impl.datasource.DefaultLoginMethodDataSource import com.ninecraft.booket.core.datastore.impl.datasource.DefaultNotificationDataSource import com.ninecraft.booket.core.datastore.impl.datasource.DefaultOnboardingDataSource 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 private const val LIBRARY_RECENT_SEARCH_DATASTORE_NAME = "LIBRARY_RECENT_SEARCH_DATASTORE" private const val ONBOARDING_DATASTORE_NAME = "ONBOARDING_DATASTORE" private const val NOTIFICATION_DATASTORE_NAME = "NOTIFICATION_DATASTORE" +private const val LOGIN_METHOD_DATASTORE_NAME = "LOGIN_METHOD_DATASTORE" private val Context.tokenDataStore by preferencesDataStore(name = TOKEN_DATASTORE_NAME) private val Context.bookRecentSearchDataStore by preferencesDataStore(name = BOOK_RECENT_SEARCH_DATASTORE_NAME) private val Context.libraryRecentSearchDataStore by preferencesDataStore(name = LIBRARY_RECENT_SEARCH_DATASTORE_NAME) private val Context.onboardingDataStore by preferencesDataStore(name = ONBOARDING_DATASTORE_NAME) private val Context.notificationDataStore by preferencesDataStore(name = NOTIFICATION_DATASTORE_NAME) +private val Context.loginMethodDataStore by preferencesDataStore(name = LOGIN_METHOD_DATASTORE_NAME) @ContributesTo(DataScope::class) interface DataStoreGraph { @@ -65,6 +69,12 @@ interface DataStoreGraph { @ApplicationContext context: Context, ): DataStore = context.notificationDataStore + @LoginMethodDataStore + @Provides + fun provideLoginMethodDataStore( + @ApplicationContext context: Context, + ): DataStore = context.loginMethodDataStore + @Binds val DefaultTokenDataSource.bind: TokenDataSource @@ -79,4 +89,7 @@ interface DataStoreGraph { @Binds val DefaultNotificationDataSource.bind: NotificationDataSource + + @Binds + val DefaultLoginMethodDataSource.bind: LoginMethodDataSource } diff --git a/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/di/DataStoreQualifier.kt b/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/di/DataStoreQualifier.kt index 9dd43f0c..4f404203 100644 --- a/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/di/DataStoreQualifier.kt +++ b/core/datastore/impl/src/main/kotlin/com/ninecraft/booket/core/datastore/impl/di/DataStoreQualifier.kt @@ -21,3 +21,7 @@ annotation class OnboardingDataStore @Qualifier @Retention(AnnotationRetention.BINARY) annotation class NotificationDataStore + +@Qualifier +@Retention(AnnotationRetention.BINARY) +annotation class LoginMethodDataStore diff --git a/core/model/src/main/kotlin/com/ninecraft/booket/core/model/LoginMethod.kt b/core/model/src/main/kotlin/com/ninecraft/booket/core/model/LoginMethod.kt new file mode 100644 index 00000000..dd7f9445 --- /dev/null +++ b/core/model/src/main/kotlin/com/ninecraft/booket/core/model/LoginMethod.kt @@ -0,0 +1,7 @@ +package com.ninecraft.booket.core.model + +enum class LoginMethod { + NONE, + KAKAO, + GOOGLE, +} diff --git a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt index 6a717c49..abda69fb 100644 --- a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt +++ b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginPresenter.kt @@ -1,6 +1,7 @@ package com.ninecraft.booket.feature.login import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.rememberCoroutineScope @@ -10,6 +11,7 @@ import com.ninecraft.booket.core.common.constants.ErrorScope import com.ninecraft.booket.core.common.event.postErrorDialog import com.ninecraft.booket.core.data.api.repository.AuthRepository import com.ninecraft.booket.core.data.api.repository.UserRepository +import com.ninecraft.booket.core.model.LoginMethod import com.ninecraft.booket.feature.screens.HomeScreen import com.ninecraft.booket.feature.screens.LoginScreen import com.ninecraft.booket.feature.screens.TermsAgreementScreen @@ -50,6 +52,15 @@ class LoginPresenter( val scope = rememberCoroutineScope() var isLoading by rememberRetained { mutableStateOf(false) } var sideEffect by rememberRetained { mutableStateOf(null) } + var showLoginTooltip by rememberRetained { mutableStateOf(false) } + var recentLoginMethod by rememberRetained { mutableStateOf(LoginMethod.NONE) } + + LaunchedEffect(Unit) { + authRepository.recentLoginMethod.collect { method -> + recentLoginMethod = method + showLoginTooltip = method != LoginMethod.NONE + } + } fun navigateAfterLogin() { scope.launch { @@ -97,6 +108,7 @@ class LoginPresenter( isLoading = true authRepository.login(event.accessToken) .onSuccess { + authRepository.setRecentLoginMethod(LoginMethod.KAKAO) userRepository.syncFcmToken() navigateAfterLogin() }.onFailure { exception -> @@ -120,6 +132,10 @@ class LoginPresenter( is LoginUiEvent.OnCloseButtonClick -> { navigator.pop() } + + is LoginUiEvent.OnDismissLoginTooltip -> { + showLoginTooltip = false + } } } @@ -131,6 +147,8 @@ class LoginPresenter( isLoading = isLoading, returnToScreen = screen.returnToScreen, sideEffect = sideEffect, + showLoginTooltip = showLoginTooltip, + recentLoginMethod = recentLoginMethod, eventSink = ::handleEvent, ) } diff --git a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginUi.kt b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginUi.kt index 2b0d8f83..cbc7080f 100644 --- a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginUi.kt +++ b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginUi.kt @@ -2,14 +2,16 @@ package com.ninecraft.booket.feature.login import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding +import androidx.compose.ui.zIndex +import com.ninecraft.booket.core.common.extensions.noRippleClickable import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.runtime.Composable @@ -20,6 +22,7 @@ import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.dp import com.ninecraft.booket.core.designsystem.DevicePreview import com.ninecraft.booket.core.designsystem.component.button.ReedButton @@ -32,6 +35,7 @@ import com.ninecraft.booket.core.designsystem.theme.White import com.ninecraft.booket.core.ui.ReedScaffold import com.ninecraft.booket.core.ui.component.ReedCloseTopAppBar import com.ninecraft.booket.core.ui.component.ReedLoadingIndicator +import com.ninecraft.booket.feature.login.component.LoginTooltipBox import com.ninecraft.booket.feature.screens.LoginScreen import com.skydoves.compose.stability.runtime.TraceRecomposition import com.slack.circuit.codegen.annotations.CircuitInject @@ -52,87 +56,103 @@ internal fun LoginUi( ReedScaffold( modifier = modifier.fillMaxSize(), ) { innerPadding -> - Column( - modifier = modifier + Box( + modifier = Modifier .fillMaxSize() .background(White) - .padding(innerPadding), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center, + .padding(innerPadding) + .then( + if (state.showLoginTooltip) { + Modifier.noRippleClickable { + state.eventSink(LoginUiEvent.OnDismissLoginTooltip) + } + } else { + Modifier + }, + ), ) { - Box(modifier = Modifier.fillMaxSize()) { - Column { - if (state.returnToScreen != null) { - ReedCloseTopAppBar( - onClose = { - state.eventSink(LoginUiEvent.OnCloseButtonClick) - }, - ) - } - Column( + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + if (state.returnToScreen != null) { + ReedCloseTopAppBar( + onClose = { + state.eventSink(LoginUiEvent.OnCloseButtonClick) + }, + ) + } + Spacer(modifier = Modifier.weight(1f)) + Image( + painter = painterResource(R.drawable.img_reed_logo_big), + contentDescription = "Reed Logo", + modifier = Modifier.height(67.14.dp), + ) + Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing5)) + Text( + text = stringResource(R.string.login_reed_slogan), + color = ReedTheme.colors.contentBrand, + style = ReedTheme.typography.headline2SemiBold, + ) + Spacer(modifier = Modifier.weight(1f)) + Box( + modifier = Modifier + .fillMaxWidth(), + ) { + ReedButton( + onClick = { + state.eventSink(LoginUiEvent.OnKakaoLoginButtonClick) + }, + sizeStyle = largeButtonStyle, + colorStyle = ReedButtonColorStyle.KAKAO, modifier = Modifier .fillMaxWidth() - .weight(1f), - verticalArrangement = Arrangement.Center, - horizontalAlignment = Alignment.CenterHorizontally, - ) { - Image( - painter = painterResource(R.drawable.img_reed_logo_big), - contentDescription = "Reed Logo", - modifier = Modifier.height(67.14.dp), - ) - Spacer(modifier = Modifier.height(ReedTheme.spacing.spacing5)) - Text( - text = stringResource(R.string.login_reed_slogan), - color = ReedTheme.colors.contentBrand, - style = ReedTheme.typography.headline2SemiBold, - ) - } - Column( - modifier = Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - ReedButton( - onClick = { - state.eventSink(LoginUiEvent.OnKakaoLoginButtonClick) - }, - sizeStyle = largeButtonStyle, - colorStyle = ReedButtonColorStyle.KAKAO, + .padding( + start = ReedTheme.spacing.spacing5, + end = ReedTheme.spacing.spacing5, + ), + text = stringResource(id = R.string.kakao_login), + leadingIcon = { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_kakao), + contentDescription = "Kakao Icon", + tint = Color.Unspecified, + ) + }, + ) + + if (state.showLoginTooltip) { + LoginTooltipBox( + messageResId = R.string.recent_login, modifier = Modifier - .fillMaxWidth() - .padding( - start = ReedTheme.spacing.spacing5, - end = ReedTheme.spacing.spacing5, - ), - text = stringResource(id = R.string.kakao_login), - leadingIcon = { - Icon( - imageVector = ImageVector.vectorResource(id = R.drawable.ic_kakao), - contentDescription = "Kakao Icon", - tint = Color.Unspecified, - ) - }, - ) - Spacer( - modifier = Modifier.height(if (state.returnToScreen == null) ReedTheme.spacing.spacing2 else ReedTheme.spacing.spacing8), + .align(Alignment.BottomEnd) + .offset { + IntOffset( + x = (-28).dp.roundToPx(), + y = (-32).dp.roundToPx(), + ) + } ) - if (state.returnToScreen == null) { - ReedTextButton( - onClick = { - state.eventSink(LoginUiEvent.OnGuestLoginButtonClick) - }, - text = stringResource(R.string.guest_login), - sizeStyle = smallButtonStyle, - colorStyle = ReedButtonColorStyle.TEXT, - ) - } } } - - if (state.isLoading) { - ReedLoadingIndicator() + Spacer( + modifier = Modifier.height(if (state.returnToScreen == null) ReedTheme.spacing.spacing2 else ReedTheme.spacing.spacing8), + ) + if (state.returnToScreen == null) { + ReedTextButton( + onClick = { + state.eventSink(LoginUiEvent.OnGuestLoginButtonClick) + }, + text = stringResource(R.string.guest_login), + sizeStyle = smallButtonStyle, + colorStyle = ReedButtonColorStyle.TEXT, + ) } } + + if (state.isLoading) { + ReedLoadingIndicator() + } } } } diff --git a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginUiState.kt b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginUiState.kt index 1f58e248..6af2f2ad 100644 --- a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginUiState.kt +++ b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/LoginUiState.kt @@ -1,6 +1,7 @@ package com.ninecraft.booket.feature.login import androidx.compose.runtime.Immutable +import com.ninecraft.booket.core.model.LoginMethod import com.slack.circuit.runtime.CircuitUiEvent import com.slack.circuit.runtime.CircuitUiState import com.slack.circuit.runtime.screen.Screen @@ -10,6 +11,8 @@ data class LoginUiState( val isLoading: Boolean = false, val returnToScreen: Screen? = null, val sideEffect: LoginSideEffect? = null, + val showLoginTooltip: Boolean = false, + val recentLoginMethod: LoginMethod = LoginMethod.NONE, val eventSink: (LoginUiEvent) -> Unit, ) : CircuitUiState @@ -28,4 +31,5 @@ sealed interface LoginUiEvent : CircuitUiEvent { data class LoginFailure(val message: String) : LoginUiEvent data object OnGuestLoginButtonClick : LoginUiEvent data object OnCloseButtonClick : LoginUiEvent + data object OnDismissLoginTooltip : LoginUiEvent } diff --git a/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/component/LoginTooltipBox.kt b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/component/LoginTooltipBox.kt new file mode 100644 index 00000000..48f9222d --- /dev/null +++ b/feature/login/src/main/kotlin/com/ninecraft/booket/feature/login/component/LoginTooltipBox.kt @@ -0,0 +1,99 @@ +package com.ninecraft.booket.feature.login.component + +import androidx.annotation.StringRes +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.draw.shadow +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.Outline +import androidx.compose.ui.graphics.Path +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.Density +import androidx.compose.ui.unit.IntOffset +import androidx.compose.ui.unit.LayoutDirection +import androidx.compose.ui.unit.dp +import com.ninecraft.booket.core.designsystem.ComponentPreview +import com.ninecraft.booket.core.designsystem.theme.ReedTheme +import com.ninecraft.booket.feature.login.R + +private val TriangleShape = object : Shape { + override fun createOutline( + size: Size, + layoutDirection: LayoutDirection, + density: Density, + ): Outline { + val path = Path().apply { + // 왼쪽 위 + moveTo(0f, 0f) + // 오른쪽 위 + lineTo(size.width, 0f) + // 중앙 아래 (뾰족한 부분) + lineTo(size.width / 2, size.height) + // 닫기 + close() + } + return Outline.Generic(path) + } +} + +@Composable +internal fun LoginTooltipBox( + @StringRes messageResId: Int, + modifier: Modifier = Modifier, +) { + Column(modifier = modifier) { + Box( + modifier = Modifier + .shadow(ReedTheme.radius.xs, RoundedCornerShape(ReedTheme.radius.xs), clip = false) + .clip(RoundedCornerShape(ReedTheme.radius.xs)) + .background(ReedTheme.colors.contentBrand) + .padding( + horizontal = ReedTheme.spacing.spacing3, + vertical = ReedTheme.spacing.spacing2, + ), + ) { + Text( + text = stringResource(messageResId), + color = ReedTheme.colors.contentInverse, + style = ReedTheme.typography.label2Regular, + ) + } + Box( + Modifier + .width(ReedTheme.spacing.spacing3) + .height(ReedTheme.spacing.spacing3 / 2) + .offset { + IntOffset( + x = 14.dp.roundToPx(), + y = 0, + ) + } + .graphicsLayer { + shadowElevation = 8.dp.toPx() + shape = TriangleShape + clip = true + } + .background(ReedTheme.colors.contentBrand), + ) + } +} + +@ComponentPreview +@Composable +private fun RecordTooltipBoxPreview() { + ReedTheme { + LoginTooltipBox(messageResId = R.string.recent_login) + } +} diff --git a/feature/login/src/main/res/values/strings.xml b/feature/login/src/main/res/values/strings.xml index 51ac38df..4250a70b 100644 --- a/feature/login/src/main/res/values/strings.xml +++ b/feature/login/src/main/res/values/strings.xml @@ -6,6 +6,7 @@ 약관 전체 동의 시작하기 회원가입 없이 둘러보기 + 최근 로그인 (필수)서비스 이용약관 (필수)개인정보처리방침 diff --git a/feature/onboarding/stability/onboarding.stability b/feature/onboarding/stability/onboarding.stability index 4a020e64..50af8bfe 100644 --- a/feature/onboarding/stability/onboarding.stability +++ b/feature/onboarding/stability/onboarding.stability @@ -4,12 +4,6 @@ // Do not edit this file directly. To update it, run: // ./gradlew :onboarding:stabilityDump -@Composable -public fun com.ninecraft.booket.feature.onboarding.OnboardingPresenter.present(): com.ninecraft.booket.feature.onboarding.OnboardingUiState - skippable: true - restartable: true - params: - @Composable private fun com.ninecraft.booket.feature.onboarding.OnboardingScreenPreview(): kotlin.Unit skippable: true @@ -41,18 +35,3 @@ private fun com.ninecraft.booket.feature.onboarding.component.OnboardingPagePrev restartable: true params: -@Composable -internal fun com.ninecraft.booket.feature.onboarding.component.PagerIndicator(pageCount: kotlin.Int, pagerState: androidx.compose.foundation.pager.PagerState, modifier: androidx.compose.ui.Modifier): kotlin.Unit - skippable: true - restartable: true - params: - - pageCount: STABLE (primitive type) - - pagerState: STABLE (marked @Stable or @Immutable) - - modifier: STABLE (marked @Stable or @Immutable) - -@Composable -private fun com.ninecraft.booket.feature.onboarding.component.PagerIndicatorPreview(): kotlin.Unit - skippable: true - restartable: true - params: - diff --git a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/component/CustomTooltipBox.kt b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/component/RecordTooltipBox.kt similarity index 94% rename from feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/component/CustomTooltipBox.kt rename to feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/component/RecordTooltipBox.kt index 5fd7d65c..fb70bdff 100644 --- a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/component/CustomTooltipBox.kt +++ b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/component/RecordTooltipBox.kt @@ -23,7 +23,7 @@ import com.ninecraft.booket.core.designsystem.theme.ReedTheme import com.ninecraft.booket.feature.record.R @Composable -internal fun CustomTooltipBox( +internal fun RecordTooltipBox( @StringRes messageResId: Int, ) { Row(verticalAlignment = Alignment.CenterVertically) { @@ -65,8 +65,8 @@ internal fun CustomTooltipBox( @ComponentPreview @Composable -private fun CustomTooltipBoxPreview() { +private fun RecordTooltipBoxPreview() { ReedTheme { - CustomTooltipBox(messageResId = R.string.scan_tooltip_message) + RecordTooltipBox(messageResId = R.string.scan_tooltip_message) } } diff --git a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step/ImpressionStep.kt b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step/ImpressionStep.kt index 016546ce..677e05df 100644 --- a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step/ImpressionStep.kt +++ b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step/ImpressionStep.kt @@ -51,7 +51,7 @@ import com.ninecraft.booket.core.designsystem.component.textfield.ReedRecordText import com.ninecraft.booket.core.designsystem.theme.ReedTheme import com.ninecraft.booket.core.designsystem.theme.White import com.ninecraft.booket.feature.record.R -import com.ninecraft.booket.feature.record.component.CustomTooltipBox +import com.ninecraft.booket.feature.record.component.RecordTooltipBox import com.ninecraft.booket.feature.record.component.ImpressionGuideBottomSheet import com.ninecraft.booket.feature.record.register.RecordRegisterUiEvent import com.ninecraft.booket.feature.record.register.RecordRegisterUiState @@ -156,7 +156,7 @@ fun ImpressionStep( verticalAlignment = Alignment.CenterVertically, ) { if (state.isImpressionGuideTooltipVisible) { - CustomTooltipBox( + RecordTooltipBox( messageResId = R.string.impression_guide_tooltip_message, ) } diff --git a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step/QuoteStep.kt b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step/QuoteStep.kt index 602ed8f7..80e0835a 100644 --- a/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step/QuoteStep.kt +++ b/feature/record/src/main/kotlin/com/ninecraft/booket/feature/record/step/QuoteStep.kt @@ -45,7 +45,7 @@ import com.ninecraft.booket.core.designsystem.component.textfield.digitOnlyInput import com.ninecraft.booket.core.designsystem.theme.ReedTheme import com.ninecraft.booket.core.designsystem.theme.White import com.ninecraft.booket.feature.record.R -import com.ninecraft.booket.feature.record.component.CustomTooltipBox +import com.ninecraft.booket.feature.record.component.RecordTooltipBox import com.ninecraft.booket.feature.record.register.RecordRegisterUiEvent import com.ninecraft.booket.feature.record.register.RecordRegisterUiState import com.skydoves.compose.stability.runtime.TraceRecomposition @@ -145,7 +145,7 @@ internal fun QuoteStep( verticalAlignment = Alignment.CenterVertically, ) { if (state.isScanTooltipVisible) { - CustomTooltipBox(messageResId = R.string.scan_tooltip_message) + RecordTooltipBox(messageResId = R.string.scan_tooltip_message) } ReedButton(