From cce3c2980dc7ec45b57780140d241033e4261c83 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=A7=84=EC=9A=B0?= <85734140+jinuemong@users.noreply.github.com> Date: Mon, 19 Feb 2024 01:14:37 +0900 Subject: [PATCH] =?UTF-8?q?[Feat]:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=EA=B5=AC=EC=B2=B4=ED=99=94=20&=20=EB=94=94=EC=9E=90=EC=9D=B8?= =?UTF-8?q?=20=EA=B2=80=EC=88=98=20(#91)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../network/model/authentication/LoginRes.kt | 10 +- .../MockAuthenticationRepository.kt | 5 +- .../RealAuthenticationRepository.kt | 2 +- .../android/domain/model/legacy/Login.kt | 2 +- .../home/history/detail/HistoryDetailItem.kt | 3 +- .../main/login/main/LoginMainDestination.kt | 3 - .../ui/main/login/main/LoginMainEvent.kt | 4 +- .../ui/main/login/main/LoginMainScreen.kt | 155 +-- .../ui/main/login/main/LoginMainViewModel.kt | 71 +- .../login/onboarding/LoginOnBoardingScreen.kt | 107 +- .../onboarding/LoginOnBoardingViewType.kt | 7 + .../main/RegistrationMainScreen.kt | 146 ++- .../ui/main/splash/SplashScreen.kt | 40 +- .../src/main/res/drawable/bc_onboarding_1.xml | 967 ++++++++++++++++++ .../src/main/res/drawable/bc_onboarding_2.xml | 548 ++++++++++ .../src/main/res/drawable/bc_onboarding_3.xml | 280 +++++ .../res/drawable/{board.xml => ic_board.xml} | 0 .../res/drawable/ic_kakao_login_button.xml | 12 +- .../src/main/res/drawable/ic_login_logo.xml | 19 + 19 files changed, 2185 insertions(+), 196 deletions(-) create mode 100644 presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/login/onboarding/LoginOnBoardingViewType.kt create mode 100644 presentation/src/main/res/drawable/bc_onboarding_1.xml create mode 100644 presentation/src/main/res/drawable/bc_onboarding_2.xml create mode 100644 presentation/src/main/res/drawable/bc_onboarding_3.xml rename presentation/src/main/res/drawable/{board.xml => ic_board.xml} (100%) create mode 100644 presentation/src/main/res/drawable/ic_login_logo.xml diff --git a/data/src/main/kotlin/ac/dnd/bookkeeping/android/data/remote/network/model/authentication/LoginRes.kt b/data/src/main/kotlin/ac/dnd/bookkeeping/android/data/remote/network/model/authentication/LoginRes.kt index 647ce416..ae22a517 100644 --- a/data/src/main/kotlin/ac/dnd/bookkeeping/android/data/remote/network/model/authentication/LoginRes.kt +++ b/data/src/main/kotlin/ac/dnd/bookkeeping/android/data/remote/network/model/authentication/LoginRes.kt @@ -7,14 +7,14 @@ import kotlinx.serialization.Serializable @Serializable data class LoginRes( // TODO : isNew: Boolean -> id: Long 으로 변경되었음. - @SerialName("isNew") - val isNew: Boolean, + @SerialName("id") + val id: Long, @SerialName("accessToken") val accessToken: String, @SerialName("refreshToken") val refreshToken: String -) : DataMapper { - override fun toDomain(): Boolean { - return isNew +) : DataMapper { + override fun toDomain(): Long { + return id } } diff --git a/data/src/main/kotlin/ac/dnd/bookkeeping/android/data/repository/authentication/MockAuthenticationRepository.kt b/data/src/main/kotlin/ac/dnd/bookkeeping/android/data/repository/authentication/MockAuthenticationRepository.kt index 868b7cb6..a04475ed 100644 --- a/data/src/main/kotlin/ac/dnd/bookkeeping/android/data/repository/authentication/MockAuthenticationRepository.kt +++ b/data/src/main/kotlin/ac/dnd/bookkeeping/android/data/repository/authentication/MockAuthenticationRepository.kt @@ -2,6 +2,7 @@ package ac.dnd.bookkeeping.android.data.repository.authentication import ac.dnd.bookkeeping.android.data.remote.local.SharedPreferencesManager import ac.dnd.bookkeeping.android.domain.model.authentication.JwtToken +import ac.dnd.bookkeeping.android.domain.model.error.ServerException import ac.dnd.bookkeeping.android.domain.model.legacy.Login import ac.dnd.bookkeeping.android.domain.model.legacy.Register import ac.dnd.bookkeeping.android.domain.repository.AuthenticationRepository @@ -37,9 +38,7 @@ class MockAuthenticationRepository @Inject constructor( email: String ): Result { randomShortDelay() - return Result.success( - Login(isNew = true) - ) + return Result.failure(ServerException("MEMBER_001", "사용자 정보가 존재하지 않습니다.")) } override suspend fun logout(): Result { diff --git a/data/src/main/kotlin/ac/dnd/bookkeeping/android/data/repository/authentication/RealAuthenticationRepository.kt b/data/src/main/kotlin/ac/dnd/bookkeeping/android/data/repository/authentication/RealAuthenticationRepository.kt index 4ad08a2d..6b2f4644 100644 --- a/data/src/main/kotlin/ac/dnd/bookkeeping/android/data/repository/authentication/RealAuthenticationRepository.kt +++ b/data/src/main/kotlin/ac/dnd/bookkeeping/android/data/repository/authentication/RealAuthenticationRepository.kt @@ -48,7 +48,7 @@ class RealAuthenticationRepository @Inject constructor( this.refreshToken = token.refreshToken this.accessToken = token.accessToken }.map { login -> - Login(isNew = login.isNew) + Login(id = login.id) } } diff --git a/domain/src/main/kotlin/ac/dnd/bookkeeping/android/domain/model/legacy/Login.kt b/domain/src/main/kotlin/ac/dnd/bookkeeping/android/domain/model/legacy/Login.kt index 4fcac3ec..b527f26f 100644 --- a/domain/src/main/kotlin/ac/dnd/bookkeeping/android/domain/model/legacy/Login.kt +++ b/domain/src/main/kotlin/ac/dnd/bookkeeping/android/domain/model/legacy/Login.kt @@ -1,5 +1,5 @@ package ac.dnd.bookkeeping.android.domain.model.legacy data class Login( - val isNew: Boolean + val id: Long ) diff --git a/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/home/history/detail/HistoryDetailItem.kt b/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/home/history/detail/HistoryDetailItem.kt index 4f839f4a..ba1c480b 100644 --- a/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/home/history/detail/HistoryDetailItem.kt +++ b/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/home/history/detail/HistoryDetailItem.kt @@ -27,6 +27,7 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.material.Card import androidx.compose.material.Text @@ -111,7 +112,7 @@ fun HistoryDetailItem( if (measureTextWidth(relatedHeart.memo, memeStyle) <= currentViewWidth - 80.dp) { Row(verticalAlignment = Alignment.CenterVertically) { Image( - painter = painterResource(R.drawable.board), + painter = painterResource(R.drawable.ic_board), contentDescription = null ) Spacer(modifier = Modifier.width(6.dp)) diff --git a/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/login/main/LoginMainDestination.kt b/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/login/main/LoginMainDestination.kt index 9a1cd758..3f7030e2 100644 --- a/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/login/main/LoginMainDestination.kt +++ b/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/login/main/LoginMainDestination.kt @@ -1,6 +1,5 @@ package ac.dnd.bookkeeping.android.presentation.ui.main.login.main -import ac.dnd.bookkeeping.android.presentation.common.util.ErrorObserver import ac.dnd.bookkeeping.android.presentation.ui.main.ApplicationState import ac.dnd.bookkeeping.android.presentation.ui.main.login.LoginConstant import ac.dnd.bookkeeping.android.presentation.ui.main.login.LoginViewModel @@ -31,8 +30,6 @@ fun NavGraphBuilder.loginMainDestination( ) } - ErrorObserver(viewModel) - LoginMainScreen( appState = appState, model = model, diff --git a/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/login/main/LoginMainEvent.kt b/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/login/main/LoginMainEvent.kt index 6f0fd12e..0f1a8a87 100644 --- a/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/login/main/LoginMainEvent.kt +++ b/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/login/main/LoginMainEvent.kt @@ -5,8 +5,8 @@ import ac.dnd.bookkeeping.android.presentation.model.login.KakaoUserInformationM sealed interface LoginMainEvent { sealed interface Login : LoginMainEvent { - data class Success( - val isNew: Boolean, + data object Success : Login + data class RequireRegister( val kakaoUserModel: KakaoUserInformationModel ) : Login diff --git a/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/login/main/LoginMainScreen.kt b/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/login/main/LoginMainScreen.kt index 7000abe5..2e79d307 100644 --- a/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/login/main/LoginMainScreen.kt +++ b/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/login/main/LoginMainScreen.kt @@ -1,6 +1,13 @@ package ac.dnd.bookkeeping.android.presentation.ui.main.login.main import ac.dnd.bookkeeping.android.presentation.R +import ac.dnd.bookkeeping.android.presentation.common.theme.Body1 +import ac.dnd.bookkeeping.android.presentation.common.theme.Body2 +import ac.dnd.bookkeeping.android.presentation.common.theme.Gray000 +import ac.dnd.bookkeeping.android.presentation.common.theme.Gray100 +import ac.dnd.bookkeeping.android.presentation.common.theme.Gray700 +import ac.dnd.bookkeeping.android.presentation.common.theme.Gray800 +import ac.dnd.bookkeeping.android.presentation.common.theme.Shapes import ac.dnd.bookkeeping.android.presentation.common.util.LaunchedEffectWithLifecycle import ac.dnd.bookkeeping.android.presentation.common.util.coroutine.event.EventFlow import ac.dnd.bookkeeping.android.presentation.common.util.coroutine.event.MutableEventFlow @@ -21,9 +28,11 @@ 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.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Card import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -33,15 +42,14 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip -import androidx.compose.ui.draw.shadow import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.navigation.NavHostController import kotlinx.coroutines.CoroutineExceptionHandler @@ -70,65 +78,119 @@ fun LoginMainScreen( fun login(event: LoginMainEvent.Login) { when (event) { is LoginMainEvent.Login.Success -> { - when (event.isNew) { - true -> navigateToOnBoarding(event.kakaoUserModel) - false -> navigateToHome() - } + navigateToHome() } - is LoginMainEvent.Login.Error -> { - isDialogShowing = true + is LoginMainEvent.Login.RequireRegister -> { + navigateToOnBoarding(event.kakaoUserModel) } - is LoginMainEvent.Login.Failure -> { - + is LoginMainEvent.Login.Error -> { isDialogShowing = true } + + is LoginMainEvent.Login.Failure -> {} } } Box( modifier = Modifier .fillMaxSize() - .background(color = Color.White) + .background(Gray100) ) { - Text( - text = "서비스 이름", - fontWeight = FontWeight.Bold, - fontSize = 30.sp, - color = Color(0xFF474747), + Column( modifier = Modifier - .align(Alignment.TopCenter) - .padding(top = 157.16.dp) - ) + .fillMaxWidth() + .padding(top = 99.36.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "감사한 마음을 잊지 않도록", + style = Body1.merge( + color = Gray800, + fontWeight = FontWeight.Normal + ) + ) + Spacer(modifier = Modifier.height(12.64.dp)) + Image( + painter = painterResource(R.drawable.ic_login_logo), + contentDescription = null, + ) + } - Column( + Box( + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(bottom = 100.87.dp) + ) { + Box( + modifier = Modifier + .background(Color.Transparent) + .wrapContentSize() + .height(39.dp) + ) { + Card( + backgroundColor = Gray000, + shape = RoundedCornerShape(100.dp), + elevation = 0.dp, + contentColor = Color.Transparent, + modifier = Modifier + .height(34.dp) + .align(Alignment.TopCenter) + ) { + Box( + modifier = Modifier + .height(34.dp) + .padding(horizontal = 18.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = "3초만에 시작하기", + style = Body2.merge( + color = Gray700, + fontWeight = FontWeight.SemiBold + ) + ) + } + } + Image( + painter = painterResource(R.drawable.ic_polygon), + modifier = Modifier + .offset(y = (-1).dp) + .align(Alignment.BottomCenter), + colorFilter = ColorFilter.tint(Gray000), + contentDescription = null, + ) + } + } + + Box( modifier = Modifier .align(Alignment.BottomCenter) .padding( - bottom = 77.dp, - start = 18.dp, - end = 18.dp + bottom = 34.87.dp, + start = 22.dp, + end = 14.dp ) .fillMaxWidth() - .clip(RoundedCornerShape(10.dp)), - horizontalAlignment = Alignment.CenterHorizontally + .clip(Shapes.medium) ) { - SampleComponent() - Spacer(Modifier.height(20.23.dp)) Image( painter = painterResource(R.drawable.ic_kakao_login_button), contentDescription = null, - modifier = Modifier.clickable { - if (model.state == LoginMainState.Init) intent(LoginMainIntent.Click) - } + modifier = Modifier + .fillMaxWidth() + .clickable { + if (model.state == LoginMainState.Init) intent(LoginMainIntent.Click) + }, + contentScale = ContentScale.Crop ) } } if (isDialogShowing) { DialogScreen( - title = stringResource(R.string.login_main_dialog_message), + message = stringResource(R.string.login_main_dialog_message), onDismissRequest = { isDialogShowing = false } @@ -154,33 +216,6 @@ private fun NavHostController.sendKakaoUserModel(kakaoUserModel: KakaoUserInform navigate(LoginOnBoardingConstant.CONTAIN_USER_MODEL) } -@Composable -private fun SampleComponent() { - Box( - modifier = Modifier - .width(115.96.dp) - .height(32.47.dp) - .shadow( - elevation = 3.dp, - shape = RoundedCornerShape(16.23.dp) - ) - .background( - shape = RoundedCornerShape(16.23.dp), - color = Color.White - ), - contentAlignment = Alignment.Center - ) { - Text( - text = "3초만에 시작하기", - color = Color.Black, - fontSize = 12.sp, - fontWeight = FontWeight.Bold, - letterSpacing = 0.2.sp, - textAlign = TextAlign.Center - ) - } -} - @Preview @Composable fun LoginMainScreenPreview() { diff --git a/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/login/main/LoginMainViewModel.kt b/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/login/main/LoginMainViewModel.kt index 2a8bbc72..a56c8db8 100644 --- a/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/login/main/LoginMainViewModel.kt +++ b/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/login/main/LoginMainViewModel.kt @@ -54,7 +54,16 @@ class LoginMainViewModel @Inject constructor( getUserInfo() } .onFailure { error -> - submitError(error) + when (error) { + is ServerException -> { + _event.emit(LoginMainEvent.Login.Failure(error)) + } + + else -> { + _event.emit(LoginMainEvent.Login.Error(error)) + } + } + _state.emit(LoginMainState.Init) } } @@ -68,41 +77,45 @@ class LoginMainViewModel @Inject constructor( ) } .onFailure { error -> - submitError(error) + when (error) { + is ServerException -> { + _event.emit(LoginMainEvent.Login.Failure(error)) + } + + else -> { + _event.emit(LoginMainEvent.Login.Error(error)) + } + } + _state.emit(LoginMainState.Init) } } private fun login( userSocialId: Long, userEmail: String, - ) = launch { - loginUseCase.invoke( - socialId = userSocialId, - email = userEmail - ) - .onSuccess { - _event.emit( - LoginMainEvent.Login.Success( - isNew = it.isNew, - kakaoUserModel = kakaoUserInfo.value - ) - ) - } - .onFailure { error -> - submitError(error) - } - } - - private fun submitError(exception: Throwable) = launch { - when (exception) { - is ServerException -> { - _event.emit(LoginMainEvent.Login.Failure(exception)) - } + ) { + launch { + _state.value = LoginMainState.Loading + loginUseCase.invoke( + socialId = userSocialId, + email = userEmail + ) + .onSuccess { + _state.value = LoginMainState.Init + _event.emit(LoginMainEvent.Login.Success) + } + .onFailure { error -> + _state.value = LoginMainState.Init + when (error) { + is ServerException -> { + _event.emit(LoginMainEvent.Login.RequireRegister(kakaoUserModel = kakaoUserInfo.value)) + } - else -> { - _event.emit(LoginMainEvent.Login.Error(exception)) - } + else -> { + _event.emit(LoginMainEvent.Login.Error(error)) + } + } + } } - _state.emit(LoginMainState.Init) } } diff --git a/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/login/onboarding/LoginOnBoardingScreen.kt b/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/login/onboarding/LoginOnBoardingScreen.kt index 2d49358a..57792d29 100644 --- a/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/login/onboarding/LoginOnBoardingScreen.kt +++ b/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/login/onboarding/LoginOnBoardingScreen.kt @@ -1,6 +1,9 @@ package ac.dnd.bookkeeping.android.presentation.ui.main.login.onboarding import ac.dnd.bookkeeping.android.presentation.R +import ac.dnd.bookkeeping.android.presentation.common.theme.Gray000 +import ac.dnd.bookkeeping.android.presentation.common.theme.Gray100 +import ac.dnd.bookkeeping.android.presentation.common.theme.Primary4 import ac.dnd.bookkeeping.android.presentation.common.util.LaunchedEffectWithLifecycle import ac.dnd.bookkeeping.android.presentation.common.util.coroutine.event.EventFlow import ac.dnd.bookkeeping.android.presentation.common.util.coroutine.event.MutableEventFlow @@ -15,16 +18,15 @@ import ac.dnd.bookkeeping.android.presentation.ui.main.registration.main.Registr import ac.dnd.bookkeeping.android.presentation.ui.main.rememberApplicationState import androidx.compose.animation.animateColorAsState import androidx.compose.foundation.ExperimentalFoundationApi +import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentSize @@ -39,10 +41,11 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import androidx.navigation.NavHostController import kotlinx.coroutines.CoroutineExceptionHandler import kotlinx.coroutines.launch @@ -69,47 +72,45 @@ fun LoginOnBoardingScreen( Box( modifier = Modifier .fillMaxSize() - .background(Color.White) + .background(Gray100) ) { - Column( - modifier = Modifier - .padding(bottom = 16.82.dp) - .align(Alignment.Center), - horizontalAlignment = Alignment.CenterHorizontally - ) { - HorizontalPager( - state = pagerState, - ) { page -> - LoginOnBoardingPage(page.toString()) + HorizontalPager( + state = pagerState, + ) { page -> + when (page) { + 0 -> LoginOnBoardingPage(LoginOnBoardingViewType.FIRST) + 1 -> LoginOnBoardingPage(LoginOnBoardingViewType.SECOND) + 2 -> LoginOnBoardingPage(LoginOnBoardingViewType.THIRD) + else -> LoginOnBoardingPage(LoginOnBoardingViewType.FIRST) } + } - Spacer(Modifier.height(29.18.dp)) - - Row( - Modifier.wrapContentSize(), - horizontalArrangement = Arrangement.spacedBy(14.dp) - ) { - repeat(pagerState.pageCount) { index -> - val isSelected = pagerState.currentPage == index + Row( + Modifier + .wrapContentSize() + .align(Alignment.BottomCenter) + .padding(bottom = 99.55.dp), + horizontalArrangement = Arrangement.spacedBy(14.dp) + ) { + repeat(pagerState.pageCount) { index -> + val isSelected = pagerState.currentPage == index - val color by animateColorAsState( - targetValue = if (isSelected) Color.DarkGray else Color.LightGray, - label = "iteration color" - ) + val color by animateColorAsState( + targetValue = if (isSelected) Primary4 else Color(0xFFD9D9D9), + label = "iteration color" + ) - Box( - modifier = Modifier - .padding(2.dp) - .clip(CircleShape) - .clickable { - scope.launch { - pagerState.animateScrollToPage(index) - } + Box( + modifier = Modifier + .clip(CircleShape) + .clickable { + scope.launch { + pagerState.animateScrollToPage(index) } - .background(color) - .size(8.dp) - ) - } + } + .background(color) + .size(8.dp) + ) } } @@ -120,8 +121,12 @@ fun LoginOnBoardingScreen( ), modifier = Modifier .fillMaxWidth() + .background(Gray000) .align(Alignment.BottomCenter) - .padding(25.dp), + .padding( + horizontal = 20.dp, + vertical = 12.dp + ), onClick = { intent(LoginOnBoardingIntent.Click) } @@ -146,21 +151,23 @@ fun LoginOnBoardingScreen( @Composable private fun LoginOnBoardingPage( - text: String + loginOnBoardingViewType: LoginOnBoardingViewType ) { Box( - modifier = Modifier - .fillMaxWidth() - .height(442.65.dp) - .padding(horizontal = 31.25.dp) - .background(color = Color(0xFFEFEFEF)), + modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { - Text( - text = text, - fontSize = 16.sp, - color = Color.Black, + Image( + painter = when (loginOnBoardingViewType) { + LoginOnBoardingViewType.FIRST -> painterResource(R.drawable.bc_onboarding_1) + LoginOnBoardingViewType.SECOND -> painterResource(R.drawable.bc_onboarding_2) + LoginOnBoardingViewType.THIRD -> painterResource(R.drawable.bc_onboarding_3) + }, + modifier = Modifier.fillMaxHeight(), + contentScale = ContentScale.Crop, + contentDescription = null ) + } } diff --git a/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/login/onboarding/LoginOnBoardingViewType.kt b/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/login/onboarding/LoginOnBoardingViewType.kt new file mode 100644 index 00000000..1c1d3383 --- /dev/null +++ b/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/login/onboarding/LoginOnBoardingViewType.kt @@ -0,0 +1,7 @@ +package ac.dnd.bookkeeping.android.presentation.ui.main.login.onboarding + +enum class LoginOnBoardingViewType { + FIRST, + SECOND, + THIRD +} diff --git a/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/registration/main/RegistrationMainScreen.kt b/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/registration/main/RegistrationMainScreen.kt index 6ea21861..e209b508 100644 --- a/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/registration/main/RegistrationMainScreen.kt +++ b/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/registration/main/RegistrationMainScreen.kt @@ -7,6 +7,7 @@ import ac.dnd.bookkeeping.android.presentation.common.theme.Gray200 import ac.dnd.bookkeeping.android.presentation.common.theme.Gray400 import ac.dnd.bookkeeping.android.presentation.common.theme.Gray500 import ac.dnd.bookkeeping.android.presentation.common.theme.Gray600 +import ac.dnd.bookkeeping.android.presentation.common.theme.Gray800 import ac.dnd.bookkeeping.android.presentation.common.theme.Headline3 import ac.dnd.bookkeeping.android.presentation.common.theme.Primary1 import ac.dnd.bookkeeping.android.presentation.common.theme.Primary4 @@ -51,6 +52,8 @@ import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.material.Button import androidx.compose.material.ButtonDefaults import androidx.compose.material.IconButton +import androidx.compose.material.RadioButton +import androidx.compose.material.RadioButtonDefaults import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -61,9 +64,13 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.SpanStyle +import androidx.compose.ui.text.buildAnnotatedString import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.withStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import kotlinx.coroutines.CoroutineExceptionHandler @@ -90,6 +97,9 @@ fun RegistrationNamingScreen( var userGender: UserGender? by remember { mutableStateOf(null) } var buttonClickState by remember { mutableStateOf(false) } var checkNonDuplicationState by remember { mutableStateOf(false) } + var isSelectedFirstAgreeButton by remember { mutableStateOf(false) } + var isSelectedSecondAgreeButton by remember { mutableStateOf(false) } + var isSelectedThirdAgreeButton by remember { mutableStateOf(false) } val registrationButtonColorState = animateColorAsState( targetValue = checkColorState( buttonClickState = buttonClickState, @@ -97,7 +107,8 @@ fun RegistrationNamingScreen( gender = userGender, year = userYearText, month = userMonthText, - day = userDayText + day = userDayText, + selectedAllAgree = isSelectedFirstAgreeButton && isSelectedSecondAgreeButton && isSelectedThirdAgreeButton ), label = "registration button state" ) @@ -169,11 +180,7 @@ fun RegistrationNamingScreen( modifier = Modifier .fillMaxWidth() .align(Alignment.Center) - .padding( - start = Space20, - end = Space20, - bottom = 220.dp - ) + .padding(horizontal = 20.dp) ) { Text( text = "닉네임", @@ -335,6 +342,124 @@ fun RegistrationNamingScreen( ) } } + Spacer(modifier = Modifier.height(40.dp)) + + + Row( + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton( + selected = isSelectedFirstAgreeButton, + onClick = { + isSelectedFirstAgreeButton = !isSelectedFirstAgreeButton + }, + modifier = Modifier.size(20.dp), + colors = RadioButtonDefaults.colors( + selectedColor = Primary4, + unselectedColor = Gray500 + ) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = buildAnnotatedString { + withStyle( + SpanStyle(color = Primary4) + ) { + append("[필수]") + } + append(" 이용약관 동의") + }, + style = Body1.merge( + color = Gray800, + fontWeight = FontWeight.Medium + ) + ) + Spacer(modifier = Modifier.weight(1f)) + Image( + painter = painterResource(R.drawable.ic_chevron_right), + contentDescription = null, + modifier = Modifier.size(20.dp), + colorFilter = ColorFilter.tint(Gray600) + ) + } + Spacer(modifier = Modifier.height(16.dp)) + Row( + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton( + selected = isSelectedSecondAgreeButton, + onClick = { + isSelectedSecondAgreeButton = !isSelectedSecondAgreeButton + }, + modifier = Modifier.size(20.dp), + colors = RadioButtonDefaults.colors( + selectedColor = Primary4, + unselectedColor = Gray500 + ) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = buildAnnotatedString { + + withStyle( + SpanStyle(color = Primary4) + ) { + append("[필수]") + } + append(" 개인정보 수집 및 이용동의") + }, + style = Body1.merge( + color = Gray800, + fontWeight = FontWeight.Medium + ) + ) + Spacer(modifier = Modifier.weight(1f)) + Image( + painter = painterResource(R.drawable.ic_chevron_right), + contentDescription = null, + modifier = Modifier.size(20.dp), + colorFilter = ColorFilter.tint(Gray600) + ) + } + Spacer(modifier = Modifier.height(16.dp)) + Row( + verticalAlignment = Alignment.CenterVertically + ) { + RadioButton( + selected = isSelectedThirdAgreeButton, + onClick = { + isSelectedThirdAgreeButton = !isSelectedThirdAgreeButton + }, + modifier = Modifier.size(20.dp), + colors = RadioButtonDefaults.colors( + selectedColor = Primary4, + unselectedColor = Gray500 + ) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = buildAnnotatedString { + + withStyle( + SpanStyle(color = Primary4) + ) { + append("[필수]") + } + append(" 만 14세 이상입니다.") + }, + style = Body1.merge( + color = Gray800, + fontWeight = FontWeight.Medium + ) + ) + Spacer(modifier = Modifier.weight(1f)) + Image( + painter = painterResource(R.drawable.ic_chevron_right), + contentDescription = null, + modifier = Modifier.size(20.dp), + colorFilter = ColorFilter.tint(Gray600) + ) + } } Box( @@ -353,10 +478,6 @@ fun RegistrationNamingScreen( colors = ButtonDefaults.textButtonColors( backgroundColor = registrationButtonColorState.value ), - border = BorderStroke( - color = if (userGender == UserGender.Male) Primary4 else Gray400, - width = 1.dp, - ), enabled = registrationButtonColorState.value == Primary4, onClick = { scope.launch { @@ -414,10 +535,11 @@ private fun checkColorState( gender: UserGender?, year: String, month: String, - day: String + day: String, + selectedAllAgree: Boolean ): Color = if (buttonClickState) Primary5 - else if (nameValid && gender != null && year.length == 4 && month.length == 2 && day.length == 2) Primary4 + else if (nameValid && gender != null && year.length == 4 && month.length == 2 && day.length == 2 && selectedAllAgree) Primary4 else Gray400 @Preview diff --git a/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/splash/SplashScreen.kt b/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/splash/SplashScreen.kt index ca8d20f1..7be6a792 100644 --- a/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/splash/SplashScreen.kt +++ b/presentation/src/main/kotlin/ac/dnd/bookkeeping/android/presentation/ui/main/splash/SplashScreen.kt @@ -1,7 +1,5 @@ package ac.dnd.bookkeeping.android.presentation.ui.main.splash -import ac.dnd.bookkeeping.android.presentation.R -import ac.dnd.bookkeeping.android.presentation.common.theme.Headline1 import ac.dnd.bookkeeping.android.presentation.common.util.LaunchedEffectWithLifecycle import ac.dnd.bookkeeping.android.presentation.common.util.alarm.registerAlarm import ac.dnd.bookkeeping.android.presentation.common.util.alarm.unregisterAlarm @@ -12,21 +10,17 @@ import ac.dnd.bookkeeping.android.presentation.ui.main.ApplicationState import ac.dnd.bookkeeping.android.presentation.ui.main.home.HomeConstant import ac.dnd.bookkeeping.android.presentation.ui.main.login.LoginConstant import ac.dnd.bookkeeping.android.presentation.ui.main.rememberApplicationState -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.size -import androidx.compose.material.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Brush +import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp import kotlinx.coroutines.CoroutineExceptionHandler @Composable @@ -70,20 +64,20 @@ fun SplashScreen( } } - Column( - modifier = Modifier.fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center + Box( + modifier = Modifier + .fillMaxSize() + .background( + Brush.verticalGradient( + listOf( + Color(0xFFE579FF), + Color(0xFFDE56FF), + ) + ) + ), + contentAlignment = Alignment.Center ) { - Image( - modifier = Modifier.size(100.dp), - painter = painterResource(id = R.drawable.ic_launcher), - contentDescription = "" - ) - Text( - text = stringResource(id = R.string.app_name), - style = Headline1 - ) + } LaunchedEffectWithLifecycle(event, handler) { diff --git a/presentation/src/main/res/drawable/bc_onboarding_1.xml b/presentation/src/main/res/drawable/bc_onboarding_1.xml new file mode 100644 index 00000000..6c108f47 --- /dev/null +++ b/presentation/src/main/res/drawable/bc_onboarding_1.xml @@ -0,0 +1,967 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/presentation/src/main/res/drawable/bc_onboarding_2.xml b/presentation/src/main/res/drawable/bc_onboarding_2.xml new file mode 100644 index 00000000..bf0b6e08 --- /dev/null +++ b/presentation/src/main/res/drawable/bc_onboarding_2.xml @@ -0,0 +1,548 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/presentation/src/main/res/drawable/bc_onboarding_3.xml b/presentation/src/main/res/drawable/bc_onboarding_3.xml new file mode 100644 index 00000000..8bf6272f --- /dev/null +++ b/presentation/src/main/res/drawable/bc_onboarding_3.xml @@ -0,0 +1,280 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/presentation/src/main/res/drawable/board.xml b/presentation/src/main/res/drawable/ic_board.xml similarity index 100% rename from presentation/src/main/res/drawable/board.xml rename to presentation/src/main/res/drawable/ic_board.xml diff --git a/presentation/src/main/res/drawable/ic_kakao_login_button.xml b/presentation/src/main/res/drawable/ic_kakao_login_button.xml index 9ed14df2..d73de919 100644 --- a/presentation/src/main/res/drawable/ic_kakao_login_button.xml +++ b/presentation/src/main/res/drawable/ic_kakao_login_button.xml @@ -1,20 +1,20 @@ + android:viewportHeight="48"> + android:pathData="M17,15.13h18v18h-18z"/> diff --git a/presentation/src/main/res/drawable/ic_login_logo.xml b/presentation/src/main/res/drawable/ic_login_logo.xml new file mode 100644 index 00000000..c5ccee85 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_login_logo.xml @@ -0,0 +1,19 @@ + + + + + +