diff --git a/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/MyPageIntent.kt b/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/MyPageIntent.kt index 0a6f3128..d488c40e 100644 --- a/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/MyPageIntent.kt +++ b/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/MyPageIntent.kt @@ -2,4 +2,5 @@ package ac.dnd.mour.android.presentation.ui.main.home.mypage sealed interface MyPageIntent { data object OnLogout : MyPageIntent + data object OnLoad : MyPageIntent } diff --git a/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/MyPageScreen.kt b/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/MyPageScreen.kt index 3800a2f0..465cc079 100644 --- a/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/MyPageScreen.kt +++ b/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/MyPageScreen.kt @@ -104,6 +104,10 @@ private fun MyPageScreen( handler: CoroutineExceptionHandler ) { + LaunchedEffectWithLifecycle(context = handler) { + intent(MyPageIntent.OnLoad) + } + val scope = rememberCoroutineScope() val context = LocalContext.current var isShowingLogoutDialog by remember { mutableStateOf(false) } @@ -118,7 +122,7 @@ private fun MyPageScreen( ContextCompat.startActivity(context, browserIntent, null) } - fun navigateToTermsOfUse(){ + fun navigateToTermsOfUse() { val browserIntent = Intent( Intent.ACTION_VIEW, diff --git a/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/MyPageViewModel.kt b/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/MyPageViewModel.kt index 87108f86..6a326545 100644 --- a/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/MyPageViewModel.kt +++ b/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/MyPageViewModel.kt @@ -50,6 +50,7 @@ class MyPageViewModel @Inject constructor( fun onIntent(intent: MyPageIntent) { when (intent) { MyPageIntent.OnLogout -> logout() + MyPageIntent.OnLoad -> getProfile() } } diff --git a/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/profile/MyPageProfileEvent.kt b/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/profile/MyPageProfileEvent.kt index 406d9c8c..6e398f8c 100644 --- a/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/profile/MyPageProfileEvent.kt +++ b/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/profile/MyPageProfileEvent.kt @@ -1,7 +1,18 @@ package ac.dnd.mour.android.presentation.ui.main.home.mypage.profile +import ac.dnd.mour.android.domain.model.member.Profile + sealed interface MyPageProfileEvent { + sealed interface LoadProfile : MyPageProfileEvent { + data class Success(val profile: Profile) : LoadProfile + } + sealed interface Edit : MyPageProfileEvent { data object Success : Edit } + + sealed interface CheckName : MyPageProfileEvent { + data object Success : CheckName + data object Fail : CheckName + } } diff --git a/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/profile/MyPageProfileIntent.kt b/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/profile/MyPageProfileIntent.kt index 22e2408d..15d588f7 100644 --- a/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/profile/MyPageProfileIntent.kt +++ b/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/profile/MyPageProfileIntent.kt @@ -7,4 +7,8 @@ sealed interface MyPageProfileIntent { val profile: Profile, val imageName: String ) : MyPageProfileIntent + + data class CheckName( + val name: String + ) : MyPageProfileIntent } diff --git a/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/profile/MyPageProfileScreen.kt b/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/profile/MyPageProfileScreen.kt index 485fc322..bdcaab91 100644 --- a/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/profile/MyPageProfileScreen.kt +++ b/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/profile/MyPageProfileScreen.kt @@ -103,6 +103,88 @@ fun MyPageProfileScreen( var currentImageUrl by remember { mutableStateOf(model.profile.profileImageUrl) } var currentImageName by remember { mutableStateOf("") } var isShowingEditSnackBar by remember { mutableStateOf(false) } + var isShowingInvalidNameSnackBar by remember { mutableStateOf(false) } + var isShowingInvalidDateSnackBar by remember { mutableStateOf(false) } + + fun loadProfile(event: MyPageProfileEvent.LoadProfile) { + when (event) { + is MyPageProfileEvent.LoadProfile.Success -> { + event.profile.apply { + userNameText = nickname + userYearText = birth.year.toString() + userMonthText = birth.monthNumber.toString() + userDayText = birth.dayOfMonth.toString() + userGender = if (gender == "male") "남자" else "여자" + currentImageUrl = profileImageUrl + } + } + } + } + + fun checkDate(birth: LocalDate): Boolean { + return if (birth.year != 1) { + true + } else { + scope.launch { + isShowingInvalidDateSnackBar = true + delay(1000L) + isShowingInvalidDateSnackBar = false + } + false + } + } + + fun saveProfile() { + val birth = try { + LocalDate( + userYearText.toInt(), + userMonthText.toInt(), + userDayText.toInt() + ) + } catch (e: Exception) { + LocalDate(1, 1, 1) + } + if (!isUserNameInValid && userNameText.isNotEmpty() && checkDate(birth)) { + intent( + MyPageProfileIntent.OnEdit( + Profile( + id = model.profile.id, + email = model.profile.email, + profileImageUrl = currentImageUrl, + name = model.profile.name, + nickname = userNameText, + gender = if (userGender == "남자") "male" else "female", + birth = birth + ), + imageName = currentImageName + ) + ) + } + } + + fun checkNicknameChange() { + if (model.profile.nickname != userNameText) { + intent(MyPageProfileIntent.CheckName(userNameText)) + } else { + saveProfile() + } + } + + fun checkNickName(event: MyPageProfileEvent.CheckName) { + when (event) { + is MyPageProfileEvent.CheckName.Success -> { + saveProfile() + } + + is MyPageProfileEvent.CheckName.Fail -> { + scope.launch { + isShowingInvalidNameSnackBar = true + delay(1000L) + isShowingInvalidNameSnackBar = false + } + } + } + } Box( modifier = Modifier @@ -183,7 +265,7 @@ fun MyPageProfileScreen( } Spacer(modifier = Modifier.height(60.dp)) Text( - text = "이름", + text = "닉네임", style = Body1.merge( color = Gray700, fontWeight = FontWeight.SemiBold @@ -335,31 +417,7 @@ fun MyPageProfileScreen( type = ConfirmButtonType.Primary ), onClick = { - val birth = try { - LocalDate( - userYearText.toInt(), - userMonthText.toInt(), - userDayText.toInt() - ) - } catch (e: Exception) { - LocalDate(1, 1, 1) - } - if (!isUserNameInValid && userNameText.isNotEmpty() && birth.year != 1) { - intent( - MyPageProfileIntent.OnEdit( - Profile( - id = model.profile.id, - email = model.profile.email, - profileImageUrl = currentImageUrl, - name = model.profile.name, - nickname = userNameText, - gender = if (userGender == "남자") "male" else "female", - birth = birth - ), - imageName = currentImageName - ) - ) - } + checkNicknameChange() } ) { Text( @@ -381,6 +439,26 @@ fun MyPageProfileScreen( SnackBarScreen("저장이 완료되었습니다.") } } + + if (isShowingInvalidNameSnackBar) { + Box( + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(bottom = 71.dp) + ) { + SnackBarScreen("이미 존재하는 닉네임입니다.") + } + } + + if (isShowingInvalidDateSnackBar) { + Box( + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(bottom = 71.dp) + ) { + SnackBarScreen("생년월일이 유효하지 않습니다.") + } + } } if (isShowingGalleryView) { @@ -397,9 +475,12 @@ fun MyPageProfileScreen( } + LaunchedEffectWithLifecycle(event, handler) { event.eventObserve { event -> when (event) { + is MyPageProfileEvent.LoadProfile -> loadProfile(event) + is MyPageProfileEvent.Edit -> { scope.launch { isShowingEditSnackBar = true @@ -407,6 +488,8 @@ fun MyPageProfileScreen( isShowingEditSnackBar = false } } + + is MyPageProfileEvent.CheckName -> checkNickName(event) } } } diff --git a/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/profile/MyPageProfileViewModel.kt b/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/profile/MyPageProfileViewModel.kt index 3bf14c7b..704f741f 100644 --- a/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/profile/MyPageProfileViewModel.kt +++ b/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/profile/MyPageProfileViewModel.kt @@ -2,6 +2,7 @@ package ac.dnd.mour.android.presentation.ui.main.home.mypage.profile import ac.dnd.mour.android.domain.model.error.ServerException import ac.dnd.mour.android.domain.model.member.Profile +import ac.dnd.mour.android.domain.usecase.member.CheckNicknameUseCase import ac.dnd.mour.android.domain.usecase.member.EditProfileUseCase import ac.dnd.mour.android.domain.usecase.member.EditProfileWithUploadUseCase import ac.dnd.mour.android.domain.usecase.member.GetProfileUseCase @@ -24,6 +25,7 @@ class MyPageProfileViewModel @Inject constructor( private val editProfileUseCase: EditProfileUseCase, private val editProfileWithUploadUseCase: EditProfileWithUploadUseCase, private val getProfileUseCase: GetProfileUseCase, + private val checkNicknameUseCase: CheckNicknameUseCase ) : BaseViewModel() { private val _state: MutableStateFlow = @@ -62,6 +64,10 @@ class MyPageProfileViewModel @Inject constructor( ) } } + + is MyPageProfileIntent.CheckName -> { + checkUserNameValid(intent.name) + } } } @@ -72,6 +78,7 @@ class MyPageProfileViewModel @Inject constructor( .onSuccess { _state.value = MyPageProfileState.Init _profile.value = it + _event.emit(MyPageProfileEvent.LoadProfile.Success(it)) } .onFailure { exception -> _state.value = MyPageProfileState.Init @@ -99,6 +106,7 @@ class MyPageProfileViewModel @Inject constructor( ) .onSuccess { _state.value = MyPageProfileState.Init + _profile.value = profile _event.emit(MyPageProfileEvent.Edit.Success) } .onFailure { exception -> @@ -147,4 +155,20 @@ class MyPageProfileViewModel @Inject constructor( } } } + + private fun checkUserNameValid(name: String) { + launch { + checkNicknameUseCase(name) + .onSuccess { + if (it) { + _event.emit(MyPageProfileEvent.CheckName.Success) + } else { + _event.emit(MyPageProfileEvent.CheckName.Fail) + } + } + .onFailure { + _event.emit(MyPageProfileEvent.CheckName.Fail) + } + } + } } diff --git a/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/setting/withdraw/MyPageSettingWithdrawScreen.kt b/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/setting/withdraw/MyPageSettingWithdrawScreen.kt index 4ecf8990..e1f1278b 100644 --- a/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/setting/withdraw/MyPageSettingWithdrawScreen.kt +++ b/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/home/mypage/setting/withdraw/MyPageSettingWithdrawScreen.kt @@ -161,9 +161,9 @@ fun MyPageSettingWithdrawScreen( ) { ConfirmButton( modifier = Modifier.fillMaxWidth(), - properties = ConfirmButtonProperties( + properties = ConfirmButtonProperties( size = ConfirmButtonSize.Xlarge, - type = ConfirmButtonType.Primary + type = if (isCheckAgree) ConfirmButtonType.Primary else ConfirmButtonType.Secondary ), isEnabled = isCheckAgree, onClick = { diff --git a/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/login/main/LoginMainScreen.kt b/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/login/main/LoginMainScreen.kt index 5fd3da66..90c84e3a 100644 --- a/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/login/main/LoginMainScreen.kt +++ b/presentation/src/main/kotlin/ac/dnd/mour/android/presentation/ui/main/login/main/LoginMainScreen.kt @@ -1,12 +1,11 @@ package ac.dnd.mour.android.presentation.ui.main.login.main import ac.dnd.mour.android.presentation.R -import ac.dnd.mour.android.presentation.common.theme.Body1 +import ac.dnd.mour.android.presentation.common.theme.Body0 import ac.dnd.mour.android.presentation.common.theme.Body2 import ac.dnd.mour.android.presentation.common.theme.Gray000 import ac.dnd.mour.android.presentation.common.theme.Gray700 import ac.dnd.mour.android.presentation.common.theme.Gray800 -import ac.dnd.mour.android.presentation.common.theme.Primary2 import ac.dnd.mour.android.presentation.common.theme.Shapes import ac.dnd.mour.android.presentation.common.util.LaunchedEffectWithLifecycle import ac.dnd.mour.android.presentation.common.util.coroutine.event.EventFlow @@ -48,9 +47,12 @@ 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.Font +import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontWeight 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 @@ -121,10 +123,8 @@ fun LoginMainScreen( ) { Text( text = "감사한 마음을 잊지 않도록", - style = Body1.merge( - color = Gray800, - fontWeight = FontWeight.Normal - ) + fontWeight = FontWeight.Normal, + style = Body0.merge(color = Gray800) ) Spacer(modifier = Modifier.height(12.64.dp)) Image( @@ -161,10 +161,8 @@ fun LoginMainScreen( ) { Text( text = "3초만에 시작하기", - style = Body2.merge( - color = Gray700, - fontWeight = FontWeight.SemiBold - ) + fontWeight = FontWeight.SemiBold, + style = Body2.merge(color = Gray800) ) } } @@ -187,18 +185,32 @@ fun LoginMainScreen( start = 22.dp, end = 14.dp ) - .fillMaxWidth() .clip(Shapes.medium) + .fillMaxWidth() + .height(47.dp) + .background( + color = Color(0xFFFEE500), + shape = Shapes.medium + ) + .clickable { + if (model.state == LoginMainState.Init) intent(LoginMainIntent.Click) + } + .padding(horizontal = 17.dp), ) { Image( - painter = painterResource(R.drawable.ic_kakao_login_button), - contentDescription = null, - modifier = Modifier - .fillMaxWidth() - .clickable { - if (model.state == LoginMainState.Init) intent(LoginMainIntent.Click) - }, - contentScale = ContentScale.Crop + modifier = Modifier.align(Alignment.CenterStart), + painter = painterResource(R.drawable.ic_kakao_logo), + contentDescription = null + ) + + Text( + text = "카카오로 로그인", + modifier = Modifier.align(Alignment.Center), + fontWeight = FontWeight.SemiBold, + fontFamily = FontFamily(Font(R.font.pretendard)), + color = Gray700, + letterSpacing = (-0.25).sp, + fontSize = 16.sp ) } } diff --git a/presentation/src/main/res/drawable/ic_kakao_logo.xml b/presentation/src/main/res/drawable/ic_kakao_logo.xml new file mode 100644 index 00000000..4747e239 --- /dev/null +++ b/presentation/src/main/res/drawable/ic_kakao_logo.xml @@ -0,0 +1,14 @@ + + + + + +