From f7703c6c0788d945999886b84730c3d949ed3a3d Mon Sep 17 00:00:00 2001 From: jusungwon Date: Fri, 27 Dec 2024 03:00:56 +0900 Subject: [PATCH 01/28] =?UTF-8?q?[Refactor]=20=EC=9D=B4=EB=A9=94=EC=9D=BC?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=BB=B4=ED=8F=AC=EC=A6=88=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20=EC=8B=9C=EC=9E=91=201.=20=EC=9D=B4?= =?UTF-8?q?=EB=A9=94=EC=9D=BC=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EC=9D=98=20'=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=B2=84?= =?UTF-8?q?=ED=8A=BC'=20=EA=B3=BC=20=EC=9D=B4=EB=A6=84=EC=9D=B4=20?= =?UTF-8?q?=EA=B2=B9=EC=B9=98=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EC=86=8C=EC=85=9C=EB=A1=9C=EA=B7=B8=EC=9D=B8=EC=9D=98=20?= =?UTF-8?q?=EC=9D=B4=EB=A9=94=EC=9D=BC=EB=A1=9C=20=EA=B0=80=EA=B8=B0=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=EB=AA=85=20=EB=B3=80=EA=B2=BD(EmailLoginButt?= =?UTF-8?q?on=20->=20NavigateToEmailLoginButton)=202.=20=EC=9D=B4=EB=A9=94?= =?UTF-8?q?=EC=9D=BC=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=8A=A4=ED=81=AC?= =?UTF-8?q?=EB=A6=B0=20=EC=83=9D=EC=84=B1=203.=20onCreateView=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=BB=B4=ED=8F=AC=EC=A6=88=EB=B7=B0=EB=A1=9C=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=ED=95=9C=20=EC=8A=A4=ED=81=AC=EB=A6=B0?= =?UTF-8?q?=EC=9D=84=20=EB=A6=AC=ED=84=B4=204.=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/login/email/EmailLoginFragment.kt | 25 +++++++++++++------ .../modigm/ui/login/email/EmailLoginScreen.kt | 16 ++++++++++++ .../{viewmodel => }/EmailLoginViewModel.kt | 2 +- .../ui/login/social/SocialLoginFragment.kt | 2 +- .../ui/login/social/SocialLoginScreen.kt | 6 ++--- ...utton.kt => NavigateToEmailLoginButton.kt} | 2 +- 6 files changed, 39 insertions(+), 14 deletions(-) create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt rename app/src/main/java/kr/co/lion/modigm/ui/login/email/{viewmodel => }/EmailLoginViewModel.kt (98%) rename app/src/main/java/kr/co/lion/modigm/ui/login/social/component/{EmailLoginButton.kt => NavigateToEmailLoginButton.kt} (98%) diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt index 4ea8fb9b..97109a7e 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt @@ -4,8 +4,11 @@ import android.os.Bundle import android.text.Editable import android.text.TextWatcher import android.util.Log +import android.view.LayoutInflater import android.view.View +import android.view.ViewGroup import android.view.inputmethod.EditorInfo +import androidx.compose.ui.platform.ComposeView import androidx.fragment.app.commit import androidx.fragment.app.replace import androidx.fragment.app.viewModels @@ -16,7 +19,6 @@ import kr.co.lion.modigm.ui.join.JoinFragment import kr.co.lion.modigm.ui.login.CustomLoginErrorDialog import kr.co.lion.modigm.ui.login.FindEmailFragment import kr.co.lion.modigm.ui.login.FindPasswordFragment -import kr.co.lion.modigm.ui.login.email.viewmodel.EmailLoginViewModel import kr.co.lion.modigm.ui.study.BottomNaviFragment import kr.co.lion.modigm.util.FragmentName import kr.co.lion.modigm.util.JoinType @@ -32,7 +34,20 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma // 태그 private val logTag by lazy { EmailLoginFragment::class.simpleName } - // --------------------------------- LC START --------------------------------- + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + return ComposeView(requireContext()).apply { + setContent { + EmailLoginScreen( + viewModel = viewModel, + + ) + } + } + } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -55,8 +70,6 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma viewModel.clearViewModelData() // ViewModel 데이터 초기화 } - // --------------------------------- LC END --------------------------------- - private fun initView() { with(binding){ @@ -170,10 +183,6 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma } } - /** - * 로그인 오류 처리 메서드 - * @param e 발생한 오류 - */ private fun showErrorDialog(e: Throwable) { val message = if (e.message != null) { e.message.toString() diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt new file mode 100644 index 00000000..07ca5ded --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt @@ -0,0 +1,16 @@ +package kr.co.lion.modigm.ui.login.email + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier + +@Composable +fun EmailLoginScreen( + viewModel: EmailLoginViewModel +) { + Box( + modifier = Modifier + .fillMaxSize(), + ) +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/viewmodel/EmailLoginViewModel.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginViewModel.kt similarity index 98% rename from app/src/main/java/kr/co/lion/modigm/ui/login/email/viewmodel/EmailLoginViewModel.kt rename to app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginViewModel.kt index f7880e3d..03b3eb08 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/viewmodel/EmailLoginViewModel.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginViewModel.kt @@ -1,4 +1,4 @@ -package kr.co.lion.modigm.ui.login.email.viewmodel +package kr.co.lion.modigm.ui.login.email import android.util.Log import androidx.lifecycle.LiveData diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginFragment.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginFragment.kt index 9fda18ec..adb05fdb 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginFragment.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginFragment.kt @@ -41,7 +41,7 @@ class SocialLoginFragment : Fragment() { viewModel = viewModel, onKakaoLoginClick = { viewModel.kakaoLogin(requireContext()) }, onGithubLoginClick = { viewModel.githubLogin(requireActivity()) }, - onEmailLoginClick = { navigateToEmailLoginFragment() }, + onNavigateEmailLoginClick = { navigateToEmailLoginFragment() }, navigateToJoinFragment = { joinType -> navigateToJoinFragment(joinType) }, navigateToBottomNaviFragment = { joinType -> navigateToBottomNaviFragment(joinType) }, showLoginErrorDialog = { message -> showLoginErrorDialog(message) }, diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginScreen.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginScreen.kt index b7acb5c9..28e952f1 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginScreen.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginScreen.kt @@ -45,9 +45,9 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import kotlinx.coroutines.launch import kr.co.lion.modigm.R -import kr.co.lion.modigm.ui.login.social.component.EmailLoginButton import kr.co.lion.modigm.ui.login.social.component.GithubLoginButton import kr.co.lion.modigm.ui.login.social.component.KakaoLoginButton +import kr.co.lion.modigm.ui.login.social.component.NavigateToEmailLoginButton import kr.co.lion.modigm.ui.login.social.component.SocialLoginLoading import kr.co.lion.modigm.util.JoinType @@ -56,7 +56,7 @@ fun SocialLoginScreen( viewModel: SocialLoginViewModel, onKakaoLoginClick: () -> Unit, onGithubLoginClick: () -> Unit, - onEmailLoginClick: () -> Unit, + onNavigateEmailLoginClick: () -> Unit, navigateToJoinFragment: (JoinType) -> Unit, navigateToBottomNaviFragment: (JoinType) -> Unit, showLoginErrorDialog: (Throwable) -> Unit, @@ -144,7 +144,7 @@ fun SocialLoginScreen( LoginSubTitleText() KakaoLoginButton(onClick = onKakaoLoginClick) GithubLoginButton(onClick = onGithubLoginClick) - EmailLoginButton(onClick = onEmailLoginClick) + NavigateToEmailLoginButton(onClick = onNavigateEmailLoginClick) } SocialLoginScrollArrow( scrollState = scrollState, diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/social/component/EmailLoginButton.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/social/component/NavigateToEmailLoginButton.kt similarity index 98% rename from app/src/main/java/kr/co/lion/modigm/ui/login/social/component/EmailLoginButton.kt rename to app/src/main/java/kr/co/lion/modigm/ui/login/social/component/NavigateToEmailLoginButton.kt index 0ff822ce..8e84df3a 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/social/component/EmailLoginButton.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/social/component/NavigateToEmailLoginButton.kt @@ -21,7 +21,7 @@ import androidx.compose.ui.unit.sp import kr.co.lion.modigm.R @Composable -fun EmailLoginButton( +fun NavigateToEmailLoginButton( modifier: Modifier = Modifier, onClick: () -> Unit) { Column( From cb1f06efd19318d175ed5d9e71c86b1fe841f3a2 Mon Sep 17 00:00:00 2001 From: jusungwon Date: Fri, 27 Dec 2024 17:02:12 +0900 Subject: [PATCH 02/28] =?UTF-8?q?[Refactor]=20=EC=9D=B4=EB=A9=94=EC=9D=BC?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EC=BB=B4=ED=8F=AC=EC=A6=88=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=20=EC=8B=9C=EC=9E=91=201.=20=EC=9D=B4?= =?UTF-8?q?=EB=A9=94=EC=9D=BC=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=ED=83=80=EC=9D=B4=ED=8B=80,=20=EC=84=9C=EB=B8=8C?= =?UTF-8?q?=20=ED=83=80=EC=9D=B4=ED=8B=80=20=EC=A0=81=EC=9A=A9=202.=20?= =?UTF-8?q?=EC=86=8C=EC=85=9C=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=EA=B3=BC=20=EA=B5=AC=EB=B6=84=EB=90=98=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=9D=B4=EB=A6=84=20=EB=B3=80=EA=B2=BD=20(LoginTit?= =?UTF-8?q?le=20->=20SocialLoginTitle,=20LoginSubTitle=20->=20SocialLoginS?= =?UTF-8?q?ubTitle)=203.=20=EC=8B=9C=EC=8A=A4=ED=85=9C=20=ED=8F=B0?= =?UTF-8?q?=ED=8A=B8=ED=81=AC=EA=B8=B0=20=EC=A0=81=EC=9A=A9=EC=9D=84=20?= =?UTF-8?q?=ED=94=BC=ED=95=A0=20=EC=88=98=20=EC=9E=88=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?dpToSp=20=ED=95=A8=EC=88=98=EB=A5=BC=20=EA=B0=81=20=EC=8A=A4?= =?UTF-8?q?=ED=81=AC=EB=A6=B0=EC=97=90=20=EC=A0=81=EC=9A=A9.=204.=20Modifi?= =?UTF-8?q?er=20=EB=A7=A4=EA=B0=9C=EB=B3=80=EC=88=98=20=EC=9C=84=EC=B9=98?= =?UTF-8?q?=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modigm/ui/login/email/EmailLoginScreen.kt | 71 ++++++++++++++++++- .../ui/login/social/SocialLoginScreen.kt | 20 +++--- .../social/component/GithubLoginButton.kt | 17 ++--- .../component/NavigateToEmailLoginButton.kt | 6 +- 4 files changed, 93 insertions(+), 21 deletions(-) diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt index 07ca5ded..78468f1f 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt @@ -1,16 +1,83 @@ package kr.co.lion.modigm.ui.login.email import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.wrapContentHeight +import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalDensity +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.dp +import kr.co.lion.modigm.R @Composable fun EmailLoginScreen( viewModel: EmailLoginViewModel ) { + val scrollState = rememberScrollState() + Box( + modifier = Modifier.fillMaxSize(), + ) { + Column( + modifier = Modifier + .verticalScroll(scrollState) + .fillMaxSize() + .padding(horizontal = 16.dp) + ) { + EmailLoginTitle() + EmailLoginSubTitle() + } + } +} + +@Composable +fun EmailLoginTitle() { + Text( + text = "모우다임", + modifier = Modifier + .wrapContentWidth() + .wrapContentHeight() + .padding(top = 100.dp), + fontFamily = FontFamily(Font(R.font.one_mobile_pop_otf)), + fontSize = dpToSp(70.dp), + color = Color.Black, + fontWeight = FontWeight.Normal + ) +} + +@Composable +fun EmailLoginSubTitle() { + Text( + text = "개발자 스터디의 새로운 패러다임", modifier = Modifier - .fillMaxSize(), + .wrapContentWidth() + .wrapContentHeight() + .padding(top = 10.dp), + fontFamily = FontFamily(Font(R.font.one_mobile_pop_otf)), + fontSize = dpToSp(22.dp), + color = Color.Black, + fontWeight = FontWeight.Normal ) -} \ No newline at end of file +} + +@Preview +@Composable +fun EmailLoginScreenPreview() { + EmailLoginScreen(viewModel = EmailLoginViewModel()) +} + +@Composable +fun dpToSp(dp: Dp) = with(LocalDensity.current) { dp.toSp() } + diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginScreen.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginScreen.kt index 28e952f1..cae477bb 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginScreen.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginScreen.kt @@ -37,12 +37,13 @@ import androidx.compose.ui.draw.blur import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource 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.unit.Dp import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import kotlinx.coroutines.launch import kr.co.lion.modigm.R import kr.co.lion.modigm.ui.login.social.component.GithubLoginButton @@ -140,8 +141,8 @@ fun SocialLoginScreen( .padding(30.dp) ) { LogoImage() - LoginTitle() - LoginSubTitleText() + SocialLoginTitle() + SocialLoginSubTitle() KakaoLoginButton(onClick = onKakaoLoginClick) GithubLoginButton(onClick = onGithubLoginClick) NavigateToEmailLoginButton(onClick = onNavigateEmailLoginClick) @@ -185,7 +186,7 @@ fun LogoImage() { } @Composable -fun LoginTitle() { +fun SocialLoginTitle() { Text( text = "모우다임", modifier = Modifier @@ -193,14 +194,14 @@ fun LoginTitle() { .wrapContentHeight() .padding(top = 60.dp), fontFamily = FontFamily(Font(R.font.one_mobile_pop_otf)), - fontSize = 70.sp, + fontSize = dpToSp(70.dp), color = Color.White, fontWeight = FontWeight.Normal ) } @Composable -fun LoginSubTitleText() { +fun SocialLoginSubTitle() { Text( text = "개발자 스터디의 새로운 패러다임", modifier = Modifier @@ -208,7 +209,7 @@ fun LoginSubTitleText() { .wrapContentHeight() .padding(top = 10.dp), fontFamily = FontFamily(Font(R.font.one_mobile_pop_otf)), - fontSize = 22.sp, + fontSize = dpToSp(22.dp), color = Color.White, fontWeight = FontWeight.Normal ) @@ -267,4 +268,7 @@ fun Modifier.animateEnterExit(): Modifier { label = "" ) return this.offset(y = offsetY.dp) -} \ No newline at end of file +} + +@Composable +fun dpToSp(dp: Dp) = with(LocalDensity.current) { dp.toSp() } \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/social/component/GithubLoginButton.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/social/component/GithubLoginButton.kt index a54d2b2e..7df37d3f 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/social/component/GithubLoginButton.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/social/component/GithubLoginButton.kt @@ -19,17 +19,17 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import kr.co.lion.modigm.R +import kr.co.lion.modigm.ui.login.social.dpToSp @Composable fun GithubLoginButton(onClick: () -> Unit) { Button( - onClick = onClick, modifier = Modifier .fillMaxWidth() .padding(top = 30.dp) .height(48.dp), + onClick = onClick, colors = ButtonDefaults.buttonColors( containerColor = Color.Black, contentColor = Color.White @@ -43,20 +43,21 @@ fun GithubLoginButton(onClick: () -> Unit) { .padding(horizontal = 16.dp) ) { Icon( - painter = painterResource(id = R.drawable.icon_github_logo), - contentDescription = "깃허브 로고", modifier = Modifier .size(24.dp) .align(Alignment.CenterStart), + painter = painterResource(id = R.drawable.icon_github_logo), + contentDescription = "깃허브 로고", tint = Color.White ) Text( - text = "깃허브 로그인", - fontSize = 20.sp, - color = Color.White, modifier = Modifier .align(Alignment.Center) - .offset(x = 12.dp) + .offset(x = 12.dp), + text = "깃허브 로그인", + fontSize = dpToSp(20.dp), + color = Color.White, + ) } } diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/social/component/NavigateToEmailLoginButton.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/social/component/NavigateToEmailLoginButton.kt index 8e84df3a..dc0a9a91 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/social/component/NavigateToEmailLoginButton.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/social/component/NavigateToEmailLoginButton.kt @@ -17,8 +17,8 @@ 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.unit.dp -import androidx.compose.ui.unit.sp import kr.co.lion.modigm.R +import kr.co.lion.modigm.ui.login.social.dpToSp @Composable fun NavigateToEmailLoginButton( @@ -42,11 +42,11 @@ fun NavigateToEmailLoginButton( ) { Text( text = "다른 방법으로 로그인", - fontSize = 16.sp, + fontSize = dpToSp(16.dp), fontFamily = FontFamily(Font(R.font.one_mobile_pop_otf)), fontWeight = FontWeight.Normal, color = Color.White ) } } -} \ No newline at end of file +} From ebfcf93becfdfdbeaf97e3229378507dadd4e6dd Mon Sep 17 00:00:00 2001 From: jusungwon Date: Fri, 27 Dec 2024 19:56:06 +0900 Subject: [PATCH 03/28] =?UTF-8?q?[Refactor]=20=EC=86=8C=EC=85=9C=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=201.?= =?UTF-8?q?=20=EC=86=8C=EC=85=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=20=EC=B0=B8=EC=A1=B0?= =?UTF-8?q?=EB=8A=94=20=ED=94=84=EB=9E=98=EA=B7=B8=EB=A8=BC=ED=8A=B8?= =?UTF-8?q?=EC=97=90=EC=84=9C=EB=A7=8C=20=ED=95=98=EB=8F=84=EB=A1=9D=20?= =?UTF-8?q?=EB=B7=B0=EB=AA=A8=EB=8D=B8=20=EA=B4=80=EC=B0=B0=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=9C=84=EC=B9=98=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F?= =?UTF-8?q?=20=EB=A7=A4=EA=B0=9C=EB=B3=80=EC=88=98=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(UI=EC=97=90=20=EC=A0=84=EC=A0=81=EC=9C=BC=EB=A1=9C=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=ED=95=98=EB=8A=94=20=EC=83=81=ED=83=9C=20=EC=A0=9C?= =?UTF-8?q?=EC=99=B8=20ex.=20scrollState=20=EB=93=B1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/login/social/SocialLoginFragment.kt | 31 +++++++++-- .../ui/login/social/SocialLoginScreen.kt | 55 ++++++++----------- 2 files changed, 49 insertions(+), 37 deletions(-) diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginFragment.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginFragment.kt index adb05fdb..a70f038e 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginFragment.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginFragment.kt @@ -5,6 +5,8 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.activity.OnBackPressedCallback +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.platform.ComposeView import androidx.fragment.app.Fragment import androidx.fragment.app.commit @@ -37,15 +39,33 @@ class SocialLoginFragment : Fragment() { ): View { return ComposeView(requireContext()).apply { setContent { + val isLoading by viewModel.isLoading.observeAsState(false) + val kakaoLoginResult by viewModel.kakaoLoginResult.observeAsState(false) + val githubLoginResult by viewModel.githubLoginResult.observeAsState(false) + val kakaoJoinResult by viewModel.kakaoJoinResult.observeAsState(false) + val githubJoinResult by viewModel.githubJoinResult.observeAsState(false) + val emailLoginResult by viewModel.emailAutoLoginResult.observeAsState(false) + val kakaoLoginError by viewModel.kakaoLoginError.observeAsState() + val githubLoginError by viewModel.githubLoginError.observeAsState() + val autoLoginError by viewModel.autoLoginError.observeAsState() + SocialLoginScreen( - viewModel = viewModel, + isLoading = isLoading, + kakaoLoginResult = kakaoLoginResult, + githubLoginResult = githubLoginResult, + kakaoJoinResult = kakaoJoinResult, + githubJoinResult = githubJoinResult, + emailLoginResult = emailLoginResult, + kakaoLoginError = kakaoLoginError, + githubLoginError = githubLoginError, + autoLoginError = autoLoginError, onKakaoLoginClick = { viewModel.kakaoLogin(requireContext()) }, onGithubLoginClick = { viewModel.githubLogin(requireActivity()) }, onNavigateEmailLoginClick = { navigateToEmailLoginFragment() }, - navigateToJoinFragment = { joinType -> navigateToJoinFragment(joinType) }, - navigateToBottomNaviFragment = { joinType -> navigateToBottomNaviFragment(joinType) }, - showLoginErrorDialog = { message -> showLoginErrorDialog(message) }, - showSnackBar = { message -> requireActivity().showLoginSnackBar(message,null) } + onNavigateToJoinFragment = { joinType -> navigateToJoinFragment(joinType) }, + onNavigateToBottomNaviFragment = { joinType -> navigateToBottomNaviFragment(joinType) }, + showLoginErrorDialog = { error -> showLoginErrorDialog(error) }, + showSnackBar = { message -> requireActivity().showLoginSnackBar(message, null) } ) } } @@ -125,6 +145,7 @@ class SocialLoginFragment : Fragment() { } private fun navigateToBottomNaviFragment(joinType: JoinType) { + viewModel.registerFcmTokenToServer() val bundle = Bundle().apply { putString("joinType", joinType.provider) } diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginScreen.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginScreen.kt index cae477bb..7f0d9000 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginScreen.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginScreen.kt @@ -26,7 +26,6 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -46,6 +45,7 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import kotlinx.coroutines.launch import kr.co.lion.modigm.R +import kr.co.lion.modigm.ui.login.email.dpToSp import kr.co.lion.modigm.ui.login.social.component.GithubLoginButton import kr.co.lion.modigm.ui.login.social.component.KakaoLoginButton import kr.co.lion.modigm.ui.login.social.component.NavigateToEmailLoginButton @@ -54,79 +54,70 @@ import kr.co.lion.modigm.util.JoinType @Composable fun SocialLoginScreen( - viewModel: SocialLoginViewModel, + isLoading: Boolean, + kakaoLoginResult: Boolean, + githubLoginResult: Boolean, + kakaoJoinResult: Boolean, + githubJoinResult: Boolean, + emailLoginResult: Boolean, + kakaoLoginError: Throwable?, + githubLoginError: Throwable?, + autoLoginError: Throwable?, onKakaoLoginClick: () -> Unit, onGithubLoginClick: () -> Unit, onNavigateEmailLoginClick: () -> Unit, - navigateToJoinFragment: (JoinType) -> Unit, - navigateToBottomNaviFragment: (JoinType) -> Unit, + onNavigateToJoinFragment: (JoinType) -> Unit, + onNavigateToBottomNaviFragment: (JoinType) -> Unit, showLoginErrorDialog: (Throwable) -> Unit, showSnackBar: (String) -> Unit ) { val scrollState = rememberScrollState() - val isLoading by viewModel.isLoading.observeAsState(false) - val kakaoLoginResult by viewModel.kakaoLoginResult.observeAsState(false) - val githubLoginResult by viewModel.githubLoginResult.observeAsState(false) - val kakaoJoinResult by viewModel.kakaoJoinResult.observeAsState(false) - val githubJoinResult by viewModel.githubJoinResult.observeAsState(false) - val emailLoginResult by viewModel.emailAutoLoginResult.observeAsState(false) - val kakaoLoginError by viewModel.kakaoLoginError.observeAsState() - val githubLoginError by viewModel.githubLoginError.observeAsState() - val autoLoginError by viewModel.autoLoginError.observeAsState() LaunchedEffect(kakaoLoginResult) { if (kakaoLoginResult) { - val joinType = JoinType.KAKAO - viewModel.registerFcmTokenToServer() - navigateToBottomNaviFragment(joinType) + onNavigateToBottomNaviFragment(JoinType.KAKAO) } } LaunchedEffect(githubLoginResult) { if (githubLoginResult) { - val joinType = JoinType.GITHUB - viewModel.registerFcmTokenToServer() - navigateToBottomNaviFragment(joinType) + onNavigateToBottomNaviFragment(JoinType.GITHUB) } } LaunchedEffect(kakaoJoinResult) { if (kakaoJoinResult) { - val joinType = JoinType.KAKAO - navigateToJoinFragment(joinType) + onNavigateToJoinFragment(JoinType.KAKAO) } } LaunchedEffect(githubJoinResult) { if (githubJoinResult) { - val joinType = JoinType.GITHUB - navigateToJoinFragment(joinType) + onNavigateToJoinFragment(JoinType.GITHUB) } } LaunchedEffect(emailLoginResult) { if (emailLoginResult) { - viewModel.registerFcmTokenToServer() - val joinType = JoinType.EMAIL - navigateToBottomNaviFragment(joinType) + onNavigateToBottomNaviFragment(JoinType.EMAIL) } } LaunchedEffect(kakaoLoginError) { - kakaoLoginError?.let { e -> - showLoginErrorDialog(e) + kakaoLoginError?.let { error -> + showLoginErrorDialog(error) } } LaunchedEffect(githubLoginError) { - githubLoginError?.let { e -> - showLoginErrorDialog(e) + githubLoginError?.let { error -> + showLoginErrorDialog(error) } } LaunchedEffect(autoLoginError) { - autoLoginError?.let { e -> - showSnackBar(e.message.toString()) + autoLoginError?.let { error -> + showSnackBar(error.message.toString()) } } From e968b8d45de335e77b210c173ec6a48c9b481b41 Mon Sep 17 00:00:00 2001 From: jusungwon Date: Sat, 28 Dec 2024 03:50:15 +0900 Subject: [PATCH 04/28] =?UTF-8?q?[Refactor]=20EmailLoginScreen=20=EC=9D=98?= =?UTF-8?q?=20=EB=A7=A4=EA=B0=9C=EB=B3=80=EC=88=98=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?1.=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=20=EC=B0=B8=EC=A1=B0=EB=A5=BC?= =?UTF-8?q?=20=ED=94=84=EB=9E=98=EA=B7=B8=EB=A8=BC=ED=8A=B8=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?2.=20=EB=9D=BC=EC=9D=B4=EB=B8=8C=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EA=B4=80=EC=B0=B0=EC=9D=84=20=EC=BB=B4=ED=8F=AC=EC=A6=88?= =?UTF-8?q?=EB=A1=9C=20=EB=B3=80=EA=B2=BD(LaunchedEffect,=20observeAsState?= =?UTF-8?q?)=203.=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EB=A5=BC=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20=ED=94=84=EB=A6=AC=EB=B7=B0=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EB=A7=A4=EA=B0=9C=EB=B3=80=EC=88=98=EB=8F=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/login/email/EmailLoginFragment.kt | 17 ++++++-- .../modigm/ui/login/email/EmailLoginScreen.kt | 40 ++++++++++++++++++- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt index 97109a7e..c69b2ef8 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt @@ -8,6 +8,8 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.inputmethod.EditorInfo +import androidx.compose.runtime.getValue +import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.platform.ComposeView import androidx.fragment.app.commit import androidx.fragment.app.replace @@ -41,9 +43,16 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma ): View? { return ComposeView(requireContext()).apply { setContent { - EmailLoginScreen( - viewModel = viewModel, + val isLoading by viewModel.isLoading.observeAsState(false) + val emailLoginResult by viewModel.emailLoginResult.observeAsState(false) + val emailLoginError by viewModel.emailLoginError.observeAsState() + EmailLoginScreen( + isLoading = isLoading, + emailLoginResult = emailLoginResult, + emailLoginError = emailLoginError, + onNavigateToBottomNaviFragment = { joinType -> navigateToBottomNaviFragment(joinType) }, + showLoginErrorDialog = { error -> showErrorDialog(error) }, ) } } @@ -171,7 +180,7 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma hideLoginLoading() Log.i(logTag, "이메일 로그인 성공") val joinType = JoinType.EMAIL - goToBottomNaviFragment(joinType) + navigateToBottomNaviFragment(joinType) } } // 이메일 로그인 실패 시 에러 처리 @@ -205,7 +214,7 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma } } - private fun goToBottomNaviFragment(joinType: JoinType) { + private fun navigateToBottomNaviFragment(joinType: JoinType) { val bundle = Bundle().apply { putString("joinType", joinType.provider) diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt index 78468f1f..d6e35d2a 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt @@ -10,6 +10,8 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalDensity @@ -20,13 +22,34 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import kr.co.lion.modigm.R +import kr.co.lion.modigm.ui.login.email.component.EmailLoginLoading +import kr.co.lion.modigm.ui.login.email.component.EmailLoginScrollArrow +import kr.co.lion.modigm.ui.login.email.component.EmailTextField +import kr.co.lion.modigm.ui.login.email.component.PasswordTextField +import kr.co.lion.modigm.util.JoinType @Composable fun EmailLoginScreen( - viewModel: EmailLoginViewModel + isLoading: Boolean, + emailLoginResult: Boolean, + emailLoginError: Throwable?, + onNavigateToBottomNaviFragment: (JoinType) -> Unit, + showLoginErrorDialog: (Throwable) -> Unit, ) { val scrollState = rememberScrollState() + LaunchedEffect(emailLoginResult) { + if (emailLoginResult) { + onNavigateToBottomNaviFragment(JoinType.EMAIL) + } + } + + LaunchedEffect(emailLoginError) { + emailLoginError?.let { error -> + showLoginErrorDialog(error) + } + } + Box( modifier = Modifier.fillMaxSize(), ) { @@ -38,7 +61,14 @@ fun EmailLoginScreen( ) { EmailLoginTitle() EmailLoginSubTitle() + EmailTextField() + PasswordTextField() } + EmailLoginScrollArrow( + scrollState = scrollState, + modifier = Modifier.align(Alignment.BottomCenter) + ) + EmailLoginLoading(isLoading = isLoading) } } @@ -75,7 +105,13 @@ fun EmailLoginSubTitle() { @Preview @Composable fun EmailLoginScreenPreview() { - EmailLoginScreen(viewModel = EmailLoginViewModel()) + EmailLoginScreen( + isLoading = false, + emailLoginResult = false, + emailLoginError = null, + onNavigateToBottomNaviFragment = {}, + showLoginErrorDialog = {} + ) } @Composable From 6746567a27f19902560cae50257b6dbd9773f683 Mon Sep 17 00:00:00 2001 From: jusungwon Date: Sat, 28 Dec 2024 03:51:26 +0900 Subject: [PATCH 05/28] =?UTF-8?q?[Refactor]=20EmailLoginLoading=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../email/component/EmailLoginLoading.kt | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginLoading.kt diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginLoading.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginLoading.kt new file mode 100644 index 00000000..d6a72652 --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginLoading.kt @@ -0,0 +1,29 @@ +package kr.co.lion.modigm.ui.login.email.component + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.input.pointer.pointerInput + +@Composable +fun EmailLoginLoading( + modifier: Modifier = Modifier, + isLoading: Boolean +) { + if (isLoading) { + Box( + modifier = modifier + .fillMaxSize() + .background(Color.Black.copy(alpha = 0.5f)) + .pointerInput(Unit) { }, + contentAlignment = Alignment.Center + ) { + CircularProgressIndicator(color = Color.White) + } + } +} \ No newline at end of file From 33896237debbc8a629aabd39f8897f60a5cea398 Mon Sep 17 00:00:00 2001 From: jusungwon Date: Sat, 28 Dec 2024 03:51:43 +0900 Subject: [PATCH 06/28] =?UTF-8?q?[Refactor]=20EmailLoginScrollArrow=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../email/component/EmailLoginScrollArrow.kt | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginScrollArrow.kt diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginScrollArrow.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginScrollArrow.kt new file mode 100644 index 00000000..3da1860a --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginScrollArrow.kt @@ -0,0 +1,87 @@ +package kr.co.lion.modigm.ui.login.email.component + +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.Image +import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.snapshotFlow +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.res.painterResource +import androidx.compose.ui.unit.dp +import kotlinx.coroutines.launch +import kr.co.lion.modigm.R + +@Composable +fun EmailLoginScrollArrow( + scrollState: ScrollState, + modifier: Modifier = Modifier +) { + val isVisible = remember { mutableStateOf(false) } + val coroutineScope = rememberCoroutineScope() + + LaunchedEffect(scrollState) { + snapshotFlow { scrollState.canScrollForward } + .collect { canScrollForward -> + isVisible.value = canScrollForward + } + } + + Box( + modifier = modifier + .fillMaxWidth() + .height(50.dp) + .background(Color.Transparent), + contentAlignment = Alignment.Center + ) { + if (isVisible.value) { + Image( + painter = painterResource(id = R.drawable.arrow_down_24px), + contentDescription = "Scroll Arrow", + modifier = Modifier + .size(24.dp) + .clickable { + coroutineScope.launch { + scrollState.animateScrollTo(scrollState.maxValue) + } + } + .animateEnterExit(), + colorFilter = ColorFilter.tint(Color.White) + ) + } + } +} + +@Composable +fun Modifier.animateEnterExit(): Modifier { + val infiniteTransition = rememberInfiniteTransition(label = "") + val offsetY by infiniteTransition.animateFloat( + initialValue = 0f, + targetValue = 10f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = 800, easing = LinearEasing), + repeatMode = RepeatMode.Reverse + ), + label = "" + ) + return this.offset(y = offsetY.dp) +} \ No newline at end of file From beb77682b9cbf00ba87d7258635d9ba0d71e759e Mon Sep 17 00:00:00 2001 From: jusungwon Date: Sat, 28 Dec 2024 03:51:58 +0900 Subject: [PATCH 07/28] =?UTF-8?q?[Refactor]=20EmailTextField=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../login/email/component/EmailTextField.kt | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailTextField.kt diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailTextField.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailTextField.kt new file mode 100644 index 00000000..5a4d0bef --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailTextField.kt @@ -0,0 +1,82 @@ +package kr.co.lion.modigm.ui.login.email.component + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Clear +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.dp +import androidx.core.content.ContextCompat +import kr.co.lion.modigm.R +import kr.co.lion.modigm.ui.login.email.dpToSp + +@Composable +fun EmailTextField() { + var email by remember { mutableStateOf("") } + val isError by remember { mutableStateOf(false) } + val pointColor = Color(ContextCompat.getColor(LocalContext.current, R.color.pointColor)) + Column( + modifier = Modifier + .fillMaxWidth() + .padding(top = 30.dp) + ) { + OutlinedTextField( + value = email, + onValueChange = { + email = it + }, + modifier = Modifier.fillMaxWidth(), + textStyle = LocalTextStyle.current.copy(fontSize = dpToSp(16.dp)), + colors = TextFieldDefaults.colors( + focusedTextColor = Color.Black, + unfocusedTextColor = Color.Black, + focusedContainerColor = Color.White, + unfocusedContainerColor = Color.White, + focusedIndicatorColor = pointColor, + unfocusedIndicatorColor = Color.Black, + errorContainerColor = Color.White, + ), + placeholder = { Text(text = "이메일") }, + leadingIcon = { + Icon( + painter = painterResource(id = R.drawable.icon_mail_24px), + contentDescription = "Mail Icon" + ) + }, + trailingIcon = { + if (email.isNotEmpty()) { + IconButton(onClick = { email = "" }) { + Icon( + imageVector = Icons.Default.Clear, + contentDescription = "Clear Text" + ) + } + } + }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Email, + imeAction = ImeAction.Next + ), + singleLine = true, + isError = isError, + ) + } +} \ No newline at end of file From 80dfa4b5fcfeafcdccbd70bbb34618ed55b594c7 Mon Sep 17 00:00:00 2001 From: jusungwon Date: Sat, 28 Dec 2024 03:52:09 +0900 Subject: [PATCH 08/28] =?UTF-8?q?[Refactor]=20PasswordTextField=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../email/component/PasswordTextFeild.kt | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/login/email/component/PasswordTextFeild.kt diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/PasswordTextFeild.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/PasswordTextFeild.kt new file mode 100644 index 00000000..32e7dcaf --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/PasswordTextFeild.kt @@ -0,0 +1,90 @@ +package kr.co.lion.modigm.ui.login.email.component + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Visibility +import androidx.compose.material.icons.filled.VisibilityOff +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.LocalTextStyle +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.unit.dp +import androidx.core.content.ContextCompat +import kr.co.lion.modigm.R +import kr.co.lion.modigm.ui.login.email.dpToSp + +@Composable +fun PasswordTextField() { + var password by remember { mutableStateOf("") } + var isPasswordVisible by remember { mutableStateOf(false) } + val pointColor = Color(ContextCompat.getColor(LocalContext.current, R.color.pointColor)) + Column( + modifier = Modifier + .fillMaxWidth() + .padding(top = 20.dp) + ) { + OutlinedTextField( + value = password, + onValueChange = { + password = it + isPasswordVisible = it.isEmpty() + }, + modifier = Modifier + .fillMaxWidth(), + textStyle = LocalTextStyle.current.copy(fontSize = dpToSp(16.dp)), + colors = TextFieldDefaults.colors( + focusedTextColor = Color.Black, + unfocusedTextColor = Color.Black, + focusedContainerColor = Color.White, + unfocusedContainerColor = Color.White, + focusedIndicatorColor = pointColor, + unfocusedIndicatorColor = Color.Black, + errorContainerColor = Color.White, + ), + placeholder = { Text(text = "비밀번호") }, + leadingIcon = { + Icon( + painter = painterResource(id = R.drawable.icon_key_24px), + contentDescription = "Key Icon" + ) + }, + trailingIcon = { + IconButton(onClick = { isPasswordVisible = !isPasswordVisible }) { + Icon( + imageVector = if (isPasswordVisible) Icons.Default.Visibility + else Icons.Default.VisibilityOff, + contentDescription = if (isPasswordVisible) "Hide Password" else "Show Password" + ) + } + }, + visualTransformation = if (isPasswordVisible) { + VisualTransformation.None + } else { + PasswordVisualTransformation() + }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Password, + imeAction = ImeAction.Done + ), + singleLine = true + ) + } +} \ No newline at end of file From 12ec8afc542b7e754ce9741550c7a1d4c500f822 Mon Sep 17 00:00:00 2001 From: jusungwon Date: Sat, 28 Dec 2024 23:08:18 +0900 Subject: [PATCH 09/28] =?UTF-8?q?[Refactor]=20EmailAutoLoginCheckBox=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=201.=20=EC=B2=B4=ED=81=AC=EB=B0=95=EC=8A=A4?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84=202.=20=EC=B2=B4=ED=81=AC=EB=B0=95?= =?UTF-8?q?=EC=8A=A4=20=EC=98=86=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=203.=20isChecked=20=EC=83=81=ED=83=9C=20=EC=A0=84?= =?UTF-8?q?=EB=8B=AC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../email/component/EmailAutoLoginCheckBox.kt | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailAutoLoginCheckBox.kt diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailAutoLoginCheckBox.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailAutoLoginCheckBox.kt new file mode 100644 index 00000000..8590375c --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailAutoLoginCheckBox.kt @@ -0,0 +1,35 @@ +package kr.co.lion.modigm.ui.login.email.component + +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Checkbox +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import kr.co.lion.modigm.ui.login.social.dpToSp + +@Composable +fun EmailAutoLoginCheckBox( + isChecked: MutableState +) { + Checkbox( + checked = isChecked.value, + onCheckedChange = { isChecked.value = it }, + modifier = Modifier + .size(20.dp) + .padding(top = 0.dp, bottom = 0.dp, start = 0.dp, end = 0.dp) + + ) + Spacer(modifier = Modifier + .size(8.dp) + .clickable { isChecked.value = !isChecked.value }) + Text( + text = "자동 로그인", + fontSize = dpToSp(16.dp), + modifier = Modifier.clickable { isChecked.value = !isChecked.value } + ) +} \ No newline at end of file From 6e4138d7ff000d6d708a23a8bb5f84762219719b Mon Sep 17 00:00:00 2001 From: jusungwon Date: Sat, 28 Dec 2024 23:09:59 +0900 Subject: [PATCH 10/28] =?UTF-8?q?[Refactor]=20=EC=9D=B4=EB=A9=94=EC=9D=BC?= =?UTF-8?q?=EC=B0=BE=EA=B8=B0=EC=99=80=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=B0=BE=EA=B8=B0=20=EB=B2=84=ED=8A=BC=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=201.=20FindEmailTextButton=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EA=B5=AC=ED=98=84=202.=20FindPasswordText?= =?UTF-8?q?Button=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=203.=20=ED=99=94=EB=A9=B4=EC=A0=84=ED=99=98=20?= =?UTF-8?q?=EC=83=81=ED=83=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../email/component/FindEmailTextButton.kt | 26 +++++++++++++++++++ .../email/component/FindPasswordTextButton.kt | 25 ++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindEmailTextButton.kt create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindPasswordTextButton.kt diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindEmailTextButton.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindEmailTextButton.kt new file mode 100644 index 00000000..6397998e --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindEmailTextButton.kt @@ -0,0 +1,26 @@ +package kr.co.lion.modigm.ui.login.email.component + +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@Composable +fun FindEmailTextButton( + onNavigateToFindEmailFragment: () -> Unit +) { + TextButton( + onClick = { onNavigateToFindEmailFragment() }, + modifier = Modifier.padding(end = 4.dp) + ) { + Text( + text = "이메일 찾기", + color = Color.Black, + fontSize = 16.sp + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindPasswordTextButton.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindPasswordTextButton.kt new file mode 100644 index 00000000..73178b18 --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindPasswordTextButton.kt @@ -0,0 +1,25 @@ +package kr.co.lion.modigm.ui.login.email.component + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@Composable +fun FindPasswordTextButton( + onNavigateToFindPasswordFragment: () -> Unit +) { + TextButton( + onClick = { onNavigateToFindPasswordFragment() }, + contentPadding = PaddingValues(start = 16.dp, end = 0.dp) + ) { + Text( + text = "비밀번호 찾기", + color = Color.Black, + fontSize = 16.sp + ) + } +} \ No newline at end of file From 6613309e301750c090c6f32f8dd083d6b2c78932 Mon Sep 17 00:00:00 2001 From: jusungwon Date: Sat, 28 Dec 2024 23:11:58 +0900 Subject: [PATCH 11/28] =?UTF-8?q?[Refactor]=20=EC=B6=95=EC=95=BD=ED=96=88?= =?UTF-8?q?=EB=8D=98=20=ED=94=84=EB=9E=98=EA=B7=B8=EB=A8=BC=ED=8A=B8=20?= =?UTF-8?q?=EC=9D=B4=EB=A6=84=20=ED=92=80=EC=96=B4=EC=84=9C=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/src/main/java/kr/co/lion/modigm/util/FragmentName.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/kr/co/lion/modigm/util/FragmentName.kt b/app/src/main/java/kr/co/lion/modigm/util/FragmentName.kt index 745c904c..16adb6b6 100644 --- a/app/src/main/java/kr/co/lion/modigm/util/FragmentName.kt +++ b/app/src/main/java/kr/co/lion/modigm/util/FragmentName.kt @@ -23,8 +23,8 @@ enum class FragmentName (var str: String){ EMAIL_LOGIN("EmailLoginFragment"), FIND_EMAIL("FindEmailFragment"), FIND_EMAIL_AUTH("FindEmailAuthFragment"), - FIND_PW("FindPwFragment"), - FIND_PW_AUTH("FindPwAuthFragment"), + FIND_PASSWORD("FindPasswordFragment"), + FIND_PW_AUTH("FindPasswordAuthFragment"), RESET_PW("ResetPwFragment"), // 프로필 From a05c9bc8d3a9ee1c31293dc9221e37d8cfe35b4b Mon Sep 17 00:00:00 2001 From: jusungwon Date: Sat, 28 Dec 2024 23:13:06 +0900 Subject: [PATCH 12/28] =?UTF-8?q?[Refactor]=20EmailLoginScreen=20=EC=97=90?= =?UTF-8?q?=20=EA=B0=81=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B0=98?= =?UTF-8?q?=EC=98=81=20=EB=B0=8F=20=ED=99=94=EB=A9=B4=20=EC=A0=84=ED=99=98?= =?UTF-8?q?=20=EC=83=81=ED=83=9C=20=EC=A0=84=EB=8B=AC=20=EA=B5=AC=ED=98=84?= =?UTF-8?q?.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/login/email/EmailLoginFragment.kt | 59 +++++++------------ .../modigm/ui/login/email/EmailLoginScreen.kt | 45 +++++++++++++- 2 files changed, 63 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt index c69b2ef8..5f80f8d2 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt @@ -12,7 +12,6 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.platform.ComposeView import androidx.fragment.app.commit -import androidx.fragment.app.replace import androidx.fragment.app.viewModels import kr.co.lion.modigm.R import kr.co.lion.modigm.databinding.FragmentEmailLoginBinding @@ -40,7 +39,7 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? - ): View? { + ): View { return ComposeView(requireContext()).apply { setContent { val isLoading by viewModel.isLoading.observeAsState(false) @@ -52,6 +51,9 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma emailLoginResult = emailLoginResult, emailLoginError = emailLoginError, onNavigateToBottomNaviFragment = { joinType -> navigateToBottomNaviFragment(joinType) }, + onNavigateToFindEmailFragment = { navigateToFindEmailFragment() }, + onNavigateToFindPasswordFragment = { navigateToFindPasswordFragment() }, + onNavigateToJoinFragment = { joinType -> navigateToJoinFragment(joinType) }, showLoginErrorDialog = { error -> showErrorDialog(error) }, ) } @@ -137,7 +139,6 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma } requireActivity().hideSoftInput() - showLoginLoading() val email = textInputEditOtherEmail.text.toString() val password = textInputEditOtherPassword.text.toString() val autoLogin = checkBoxOtherAutoLogin.isChecked @@ -147,23 +148,7 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma // 회원가입 버튼 클릭 시 회원가입 화면으로 이동 buttonOtherJoin.setOnClickListener { val joinType = JoinType.EMAIL - goToJoinFragment(joinType) - } - - // 이메일 찾기 버튼 클릭 시 - buttonOtherFindEmail.setOnClickListener { - parentFragmentManager.commit { - replace(R.id.containerMain) - addToBackStack(FragmentName.FIND_EMAIL.str) - } - } - - // 비밀번호 찾기 버튼 클릭 시 - buttonOtherFindPassword.setOnClickListener { - parentFragmentManager.commit { - replace(R.id.containerMain) - addToBackStack(FragmentName.FIND_PW.str) - } + navigateToJoinFragment(joinType) } // 돌아가기 버튼 클릭 시 buttonOtherBack.setOnClickListener { @@ -177,7 +162,6 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma // 이메일 로그인 데이터 관찰 viewModel.emailLoginResult.observe(viewLifecycleOwner) { result -> if (result) { - hideLoginLoading() Log.i(logTag, "이메일 로그인 성공") val joinType = JoinType.EMAIL navigateToBottomNaviFragment(joinType) @@ -186,7 +170,6 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma // 이메일 로그인 실패 시 에러 처리 viewModel.emailLoginError.observe(viewLifecycleOwner) { error -> if (error != null) { - hideLoginLoading() showErrorDialog(error) } } @@ -201,10 +184,23 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma showLoginErrorDialog(message) } + private fun navigateToFindEmailFragment() { + parentFragmentManager.commit { + replace(R.id.containerMain, FindEmailFragment()) + addToBackStack(FragmentName.FIND_EMAIL.str) + } + } + + private fun navigateToFindPasswordFragment() { + parentFragmentManager.commit { + replace(R.id.containerMain, FindPasswordFragment()) + addToBackStack(FragmentName.FIND_PASSWORD.str) + } + } + // 회원가입 화면으로 이동하는 메소드 - private fun goToJoinFragment(joinType: JoinType) { + private fun navigateToJoinFragment(joinType: JoinType) { Log.d(logTag, "navigateToJoinFragment - joinType: ${joinType.provider}") - val bundle = Bundle().apply { putString("joinType", joinType.provider) } @@ -215,7 +211,6 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma } private fun navigateToBottomNaviFragment(joinType: JoinType) { - val bundle = Bundle().apply { putString("joinType", joinType.provider) } @@ -325,18 +320,4 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma override fun afterTextChanged(p0: Editable?) { } } - - // 로딩 화면 표시 - private fun showLoginLoading() { - with(binding){ - layoutLoginLoadingBackground.visibility = View.VISIBLE - } - } - // 로딩 화면 숨김 - private fun hideLoginLoading() { - with(binding){ - layoutLoginLoadingBackground.visibility = View.GONE - } - } - } diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt index d6e35d2a..71f00930 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt @@ -1,8 +1,11 @@ package kr.co.lion.modigm.ui.login.email +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.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentWidth @@ -11,6 +14,8 @@ import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -21,11 +26,16 @@ 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.dp +import androidx.compose.ui.unit.sp import kr.co.lion.modigm.R +import kr.co.lion.modigm.ui.login.email.component.EmailAutoLoginCheckBox import kr.co.lion.modigm.ui.login.email.component.EmailLoginLoading import kr.co.lion.modigm.ui.login.email.component.EmailLoginScrollArrow import kr.co.lion.modigm.ui.login.email.component.EmailTextField +import kr.co.lion.modigm.ui.login.email.component.FindEmailTextButton +import kr.co.lion.modigm.ui.login.email.component.FindPasswordTextButton import kr.co.lion.modigm.ui.login.email.component.PasswordTextField +import kr.co.lion.modigm.ui.login.social.dpToSp import kr.co.lion.modigm.util.JoinType @Composable @@ -34,9 +44,13 @@ fun EmailLoginScreen( emailLoginResult: Boolean, emailLoginError: Throwable?, onNavigateToBottomNaviFragment: (JoinType) -> Unit, + onNavigateToFindEmailFragment: () -> Unit, + onNavigateToFindPasswordFragment: () -> Unit, + onNavigateToJoinFragment: (JoinType) -> Unit, showLoginErrorDialog: (Throwable) -> Unit, ) { val scrollState = rememberScrollState() + val isChecked = remember { mutableStateOf(false) } LaunchedEffect(emailLoginResult) { if (emailLoginResult) { @@ -63,6 +77,30 @@ fun EmailLoginScreen( EmailLoginSubTitle() EmailTextField() PasswordTextField() + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .padding(top = 10.dp, end = 0.dp), + horizontalArrangement = Arrangement.SpaceBetween + ){ + Row( + verticalAlignment = Alignment.CenterVertically + ) { + EmailAutoLoginCheckBox(isChecked = isChecked) + } + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + FindEmailTextButton(onNavigateToFindEmailFragment) + Text( + text = "|", + fontSize = 16.sp, + modifier = Modifier.padding(horizontal = 4.dp) + ) + FindPasswordTextButton(onNavigateToFindPasswordFragment) + } + } } EmailLoginScrollArrow( scrollState = scrollState, @@ -102,7 +140,7 @@ fun EmailLoginSubTitle() { ) } -@Preview +@Preview(showBackground = true) @Composable fun EmailLoginScreenPreview() { EmailLoginScreen( @@ -110,7 +148,10 @@ fun EmailLoginScreenPreview() { emailLoginResult = false, emailLoginError = null, onNavigateToBottomNaviFragment = {}, - showLoginErrorDialog = {} + showLoginErrorDialog = {}, + onNavigateToFindEmailFragment = {}, + onNavigateToFindPasswordFragment = {}, + onNavigateToJoinFragment = {}, ) } From 79a3637898318b85b62e61a7806c8083f4eb4e9d Mon Sep 17 00:00:00 2001 From: jusungwon Date: Sun, 29 Dec 2024 02:51:51 +0900 Subject: [PATCH 13/28] =?UTF-8?q?[Refactor]=20social,=20email=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=EB=A5=BC=20=EC=9E=98=EB=AA=BB=20=EC=B0=B8?= =?UTF-8?q?=EC=A1=B0=ED=95=98=EA=B3=A0=20=EC=9E=88=EB=8A=94=20dpToSp=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modigm/ui/login/email/component/EmailAutoLoginCheckBox.kt | 3 +-- .../kr/co/lion/modigm/ui/login/social/SocialLoginScreen.kt | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailAutoLoginCheckBox.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailAutoLoginCheckBox.kt index 8590375c..2a957022 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailAutoLoginCheckBox.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailAutoLoginCheckBox.kt @@ -10,7 +10,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp -import kr.co.lion.modigm.ui.login.social.dpToSp +import kr.co.lion.modigm.ui.login.email.dpToSp @Composable fun EmailAutoLoginCheckBox( @@ -22,7 +22,6 @@ fun EmailAutoLoginCheckBox( modifier = Modifier .size(20.dp) .padding(top = 0.dp, bottom = 0.dp, start = 0.dp, end = 0.dp) - ) Spacer(modifier = Modifier .size(8.dp) diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginScreen.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginScreen.kt index 7f0d9000..fab612c2 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginScreen.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginScreen.kt @@ -45,7 +45,6 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp import kotlinx.coroutines.launch import kr.co.lion.modigm.R -import kr.co.lion.modigm.ui.login.email.dpToSp import kr.co.lion.modigm.ui.login.social.component.GithubLoginButton import kr.co.lion.modigm.ui.login.social.component.KakaoLoginButton import kr.co.lion.modigm.ui.login.social.component.NavigateToEmailLoginButton From fc7e1dfefba223d2cad2e9652947023d48142d62 Mon Sep 17 00:00:00 2001 From: jusungwon Date: Sun, 29 Dec 2024 03:00:36 +0900 Subject: [PATCH 14/28] =?UTF-8?q?[Refactor]=20social,=20email=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=EB=A5=BC=20=EC=9E=98=EB=AA=BB=20=EC=B0=B8?= =?UTF-8?q?=EC=A1=B0=ED=95=98=EA=B3=A0=20=EC=9E=88=EB=8A=94=20dpToSp=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EC=A1=B0=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modigm/ui/login/email/component/FindEmailTextButton.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindEmailTextButton.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindEmailTextButton.kt index 6397998e..53287335 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindEmailTextButton.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindEmailTextButton.kt @@ -7,7 +7,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp +import kr.co.lion.modigm.ui.login.email.dpToSp @Composable fun FindEmailTextButton( @@ -20,7 +20,7 @@ fun FindEmailTextButton( Text( text = "이메일 찾기", color = Color.Black, - fontSize = 16.sp + fontSize = dpToSp(16.dp) ) } } \ No newline at end of file From b52291ee97d4d726d53375f9205a8552fb28f2a2 Mon Sep 17 00:00:00 2001 From: jusungwon Date: Sun, 29 Dec 2024 03:01:14 +0900 Subject: [PATCH 15/28] =?UTF-8?q?[Refactor]=20=EC=9D=B4=EB=A9=94=EC=9D=BC?= =?UTF-8?q?=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=B2=84=ED=8A=BC=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../login/email/component/EmailLoginButton.kt | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginButton.kt diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginButton.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginButton.kt new file mode 100644 index 00000000..a9c76175 --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginButton.kt @@ -0,0 +1,33 @@ +package kr.co.lion.modigm.ui.login.email.component + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults.buttonColors +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.unit.dp +import androidx.core.content.ContextCompat +import kr.co.lion.modigm.R +import kr.co.lion.modigm.ui.login.email.dpToSp + +@Composable +fun EmailLoginButton( + onEmailLoginButtonClick: () -> Unit +) { + Button( + onClick = { onEmailLoginButtonClick() }, + modifier = Modifier + .fillMaxWidth() + .padding(top = 10.dp), + colors = buttonColors(Color(ContextCompat.getColor(LocalContext.current, R.color.pointColor))) + ) { + Text( + text = "로그인", + fontSize = dpToSp(16.dp) + ) + } +} \ No newline at end of file From 54b0655c2bcd17566da4a16a0d00c88964e7d3fc Mon Sep 17 00:00:00 2001 From: jusungwon Date: Sun, 29 Dec 2024 03:01:54 +0900 Subject: [PATCH 16/28] =?UTF-8?q?[Refactor]=20=EB=8F=8C=EC=95=84=EA=B0=80?= =?UTF-8?q?=EA=B8=B0=20=EB=B2=84=ED=8A=BC=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../component/NavigateToSocialLoginButton.kt | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/login/email/component/NavigateToSocialLoginButton.kt diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/NavigateToSocialLoginButton.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/NavigateToSocialLoginButton.kt new file mode 100644 index 00000000..6fdf474b --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/NavigateToSocialLoginButton.kt @@ -0,0 +1,37 @@ +package kr.co.lion.modigm.ui.login.email.component + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import kr.co.lion.modigm.R +import kr.co.lion.modigm.ui.login.email.dpToSp + +@Composable +fun NavigateToSocialLoginButton( + onNavigateToSocialLoginFragment: () -> Unit +) { + TextButton( + onClick = { onNavigateToSocialLoginFragment() }, + contentPadding = PaddingValues(0.dp) + ) { + Icon( + painter = painterResource(R.drawable.icon_back_24px), + contentDescription = "돌아가기", + tint = Color.Black + ) + Spacer(modifier = Modifier.size(8.dp)) + Text( + text = "돌아가기", + fontSize = dpToSp(16.dp), + color = Color.Black + ) + } +} \ No newline at end of file From 0c054902d88cd913bd372552db7b186f1ffa8acd Mon Sep 17 00:00:00 2001 From: jusungwon Date: Sun, 29 Dec 2024 03:02:18 +0900 Subject: [PATCH 17/28] =?UTF-8?q?[Refactor]=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20=EB=B2=84=ED=8A=BC=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../email/component/EmailLoginJoinButton.kt | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginJoinButton.kt diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginJoinButton.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginJoinButton.kt new file mode 100644 index 00000000..ccb0686a --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginJoinButton.kt @@ -0,0 +1,41 @@ +package kr.co.lion.modigm.ui.login.email.component + +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.dp +import kr.co.lion.modigm.R +import kr.co.lion.modigm.ui.login.email.dpToSp +import kr.co.lion.modigm.util.JoinType + +@Composable +fun EmailLoginJoinButton( + onNavigateToJoinFragment: (JoinType) -> Unit +) { + Text( + text = "계정이 없으신가요?", + fontSize = dpToSp(16.dp), + color = Color.Black + ) + TextButton( + onClick = { onNavigateToJoinFragment(JoinType.EMAIL) } + ) { + Icon( + painter = painterResource(R.drawable.icon_person_add_24px), + contentDescription = "회원가입", + tint = Color.Black + ) + Spacer(Modifier.size(4.dp)) + Text( + text = "회원가입", + fontSize = dpToSp(16.dp), + color = Color.Black + ) + } +} \ No newline at end of file From 06f99980077706a886e3c6c0bc466074918f2baa Mon Sep 17 00:00:00 2001 From: jusungwon Date: Sun, 29 Dec 2024 03:04:10 +0900 Subject: [PATCH 18/28] =?UTF-8?q?[Refactor]=20EmailLoginScreen=EC=97=90=20?= =?UTF-8?q?=EA=B0=81=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=201.=20EmailLoginButton=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EC=A0=81=EC=9A=A9=202.=20=EB=8F=8C=EC=95=84?= =?UTF-8?q?=EA=B0=80=EA=B8=B0=20=EB=B2=84=ED=8A=BC,=20=ED=9A=8C=EC=9B=90?= =?UTF-8?q?=EA=B0=80=EC=9E=85=20=EB=B2=84=ED=8A=BC=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=20=EC=A0=81=EC=9A=A9=203.=20=ED=95=84?= =?UTF-8?q?=EC=9A=94=ED=95=9C=20=EC=83=81=ED=83=9C=20=EB=A7=A4=EA=B0=9C?= =?UTF-8?q?=EB=B3=80=EC=88=98=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/login/email/EmailLoginFragment.kt | 10 +++-- .../modigm/ui/login/email/EmailLoginScreen.kt | 38 +++++++++++++++---- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt index 5f80f8d2..0ac7335f 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt @@ -29,10 +29,8 @@ import kr.co.lion.modigm.util.showSoftInput class EmailLoginFragment : VBBaseFragment(FragmentEmailLoginBinding::inflate) { - // 뷰모델 - private val viewModel: EmailLoginViewModel by viewModels() // LoginViewModel 인스턴스 생성 + private val viewModel: EmailLoginViewModel by viewModels() - // 태그 private val logTag by lazy { EmailLoginFragment::class.simpleName } override fun onCreateView( @@ -54,6 +52,8 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma onNavigateToFindEmailFragment = { navigateToFindEmailFragment() }, onNavigateToFindPasswordFragment = { navigateToFindPasswordFragment() }, onNavigateToJoinFragment = { joinType -> navigateToJoinFragment(joinType) }, + onEmailLoginButtonClick = { }, + onNavigateToSocialLoginFragment = { navigateToSocialLoginFragment() }, showLoginErrorDialog = { error -> showErrorDialog(error) }, ) } @@ -198,6 +198,10 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma } } + private fun navigateToSocialLoginFragment() { + parentFragmentManager.popBackStack() + } + // 회원가입 화면으로 이동하는 메소드 private fun navigateToJoinFragment(joinType: JoinType) { Log.d(logTag, "navigateToJoinFragment - joinType: ${joinType.provider}") diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt index 71f00930..ffc4b85c 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt @@ -26,16 +26,17 @@ 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.dp -import androidx.compose.ui.unit.sp import kr.co.lion.modigm.R import kr.co.lion.modigm.ui.login.email.component.EmailAutoLoginCheckBox +import kr.co.lion.modigm.ui.login.email.component.EmailLoginButton +import kr.co.lion.modigm.ui.login.email.component.EmailLoginJoinButton import kr.co.lion.modigm.ui.login.email.component.EmailLoginLoading import kr.co.lion.modigm.ui.login.email.component.EmailLoginScrollArrow import kr.co.lion.modigm.ui.login.email.component.EmailTextField import kr.co.lion.modigm.ui.login.email.component.FindEmailTextButton import kr.co.lion.modigm.ui.login.email.component.FindPasswordTextButton +import kr.co.lion.modigm.ui.login.email.component.NavigateToSocialLoginButton import kr.co.lion.modigm.ui.login.email.component.PasswordTextField -import kr.co.lion.modigm.ui.login.social.dpToSp import kr.co.lion.modigm.util.JoinType @Composable @@ -46,6 +47,8 @@ fun EmailLoginScreen( onNavigateToBottomNaviFragment: (JoinType) -> Unit, onNavigateToFindEmailFragment: () -> Unit, onNavigateToFindPasswordFragment: () -> Unit, + onEmailLoginButtonClick: () -> Unit, + onNavigateToSocialLoginFragment: () -> Unit, onNavigateToJoinFragment: (JoinType) -> Unit, showLoginErrorDialog: (Throwable) -> Unit, ) { @@ -83,7 +86,7 @@ fun EmailLoginScreen( .fillMaxWidth() .padding(top = 10.dp, end = 0.dp), horizontalArrangement = Arrangement.SpaceBetween - ){ + ) { Row( verticalAlignment = Alignment.CenterVertically ) { @@ -92,13 +95,32 @@ fun EmailLoginScreen( Row( verticalAlignment = Alignment.CenterVertically, ) { - FindEmailTextButton(onNavigateToFindEmailFragment) + FindEmailTextButton(onNavigateToFindEmailFragment = onNavigateToFindEmailFragment) Text( text = "|", - fontSize = 16.sp, - modifier = Modifier.padding(horizontal = 4.dp) + fontSize = dpToSp(16.dp) ) - FindPasswordTextButton(onNavigateToFindPasswordFragment) + FindPasswordTextButton(onNavigateToFindPasswordFragment = onNavigateToFindPasswordFragment) + } + } + EmailLoginButton(onEmailLoginButtonClick = onEmailLoginButtonClick) + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .fillMaxWidth() + .padding(start = 0.dp, end = 0.dp), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Row( + verticalAlignment = Alignment.CenterVertically + ) { + NavigateToSocialLoginButton(onNavigateToSocialLoginFragment = onNavigateToSocialLoginFragment) + } + Row( + verticalAlignment = Alignment.CenterVertically, + ) { + EmailLoginJoinButton(onNavigateToJoinFragment = onNavigateToJoinFragment) } } } @@ -151,6 +173,8 @@ fun EmailLoginScreenPreview() { showLoginErrorDialog = {}, onNavigateToFindEmailFragment = {}, onNavigateToFindPasswordFragment = {}, + onEmailLoginButtonClick = {}, + onNavigateToSocialLoginFragment = {}, onNavigateToJoinFragment = {}, ) } From 937a4a620336da136d9c44551301e796178a2ef7 Mon Sep 17 00:00:00 2001 From: jusungwon Date: Sun, 29 Dec 2024 03:04:42 +0900 Subject: [PATCH 19/28] =?UTF-8?q?[Refactor]=20=EB=A1=9C=EB=94=A9=20?= =?UTF-8?q?=ED=99=94=EB=A9=B4=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=9A=A9=20?= =?UTF-8?q?=ED=94=84=EB=A6=AC=EB=B7=B0=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modigm/ui/login/email/component/EmailLoginLoading.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginLoading.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginLoading.kt index d6a72652..59022531 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginLoading.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginLoading.kt @@ -9,6 +9,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.pointerInput +import androidx.compose.ui.tooling.preview.Preview @Composable fun EmailLoginLoading( @@ -26,4 +27,10 @@ fun EmailLoginLoading( CircularProgressIndicator(color = Color.White) } } +} + +@Preview(showBackground = true) +@Composable +fun EmailLoginLoadingPreview() { + EmailLoginLoading(isLoading = true) } \ No newline at end of file From 95e7fd2734fafecf208b912dc1e2b9a0becc782e Mon Sep 17 00:00:00 2001 From: jusungwon Date: Thu, 2 Jan 2025 17:41:02 +0900 Subject: [PATCH 20/28] =?UTF-8?q?[Refactor]=20=EC=9D=B4=EB=A9=94=EC=9D=BC?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=20?= =?UTF-8?q?=EA=B4=80=EC=B0=B0=EC=BD=94=EB=93=9C=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EC=A6=88=EB=A1=9C=20=EB=B3=80=EA=B2=BD=201.=20=EA=B8=B0?= =?UTF-8?q?=EC=A1=B4=20observeVewmodel()=20=EC=BD=94=EB=93=9C=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C=202.=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=EB=B2=84=ED=8A=BC=20=ED=81=B4=EB=A6=AD=20?= =?UTF-8?q?=EC=8B=9C=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=A0=81=EC=9A=A9=20=20-=20email=20?= =?UTF-8?q?=EB=B0=8F=20password=20=ED=85=8D=EC=8A=A4=ED=8A=B8=ED=95=84?= =?UTF-8?q?=EB=93=9C=20=EA=B0=92=20=EC=A0=84=EB=8B=AC=20=20-=20autoLoginVa?= =?UTF-8?q?lue=20=EA=B0=92=20=EC=A0=84=EB=8B=AC=20=20-=20email=20=EA=B3=BC?= =?UTF-8?q?=20password=20=ED=85=8D=EC=8A=A4=ED=8A=B8=ED=95=84=EB=93=9C?= =?UTF-8?q?=EC=97=90=20=EA=B0=92=EC=9D=B4=20=EB=AA=A8=EB=91=90=20=EC=9E=88?= =?UTF-8?q?=EC=9D=84=20=EB=95=8C=20=EB=B2=84=ED=8A=BC=20=ED=99=9C=EC=84=B1?= =?UTF-8?q?=ED=99=94=20=EC=A0=81=EC=9A=A9=20(isEnabled)=203.=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=A0=95=EB=A6=AC=20(=EC=BB=A8=EB=B2=A4=EC=85=98)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/login/email/EmailLoginFragment.kt | 28 ++++--------------- .../modigm/ui/login/email/EmailLoginScreen.kt | 26 +++++++++++++---- .../email/component/EmailAutoLoginCheckBox.kt | 10 ++++--- .../login/email/component/EmailLoginButton.kt | 6 ++-- .../login/email/component/EmailTextField.kt | 17 ++++++----- .../email/component/PasswordTextFeild.kt | 21 ++++++-------- 6 files changed, 54 insertions(+), 54 deletions(-) diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt index 0ac7335f..6f4a6b46 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt @@ -4,6 +4,7 @@ import android.os.Bundle import android.text.Editable import android.text.TextWatcher import android.util.Log +import android.util.Patterns import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -52,7 +53,9 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma onNavigateToFindEmailFragment = { navigateToFindEmailFragment() }, onNavigateToFindPasswordFragment = { navigateToFindPasswordFragment() }, onNavigateToJoinFragment = { joinType -> navigateToJoinFragment(joinType) }, - onEmailLoginButtonClick = { }, + onEmailLoginButtonClick = { email, password, autoLoginValue -> + viewModel.emailLogin(email, password, autoLoginValue) + }, onNavigateToSocialLoginFragment = { navigateToSocialLoginFragment() }, showLoginErrorDialog = { error -> showErrorDialog(error) }, ) @@ -63,8 +66,7 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - initView() // 초기 UI 설정 - observeViewModel() // ViewModel의 데이터 변경 관찰 + initView() } override fun onResume() { @@ -157,24 +159,6 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma } } - // ViewModel의 데이터 변경을 관찰하여 UI 업데이트 - private fun observeViewModel() { - // 이메일 로그인 데이터 관찰 - viewModel.emailLoginResult.observe(viewLifecycleOwner) { result -> - if (result) { - Log.i(logTag, "이메일 로그인 성공") - val joinType = JoinType.EMAIL - navigateToBottomNaviFragment(joinType) - } - } - // 이메일 로그인 실패 시 에러 처리 - viewModel.emailLoginError.observe(viewLifecycleOwner) { error -> - if (error != null) { - showErrorDialog(error) - } - } - } - private fun showErrorDialog(e: Throwable) { val message = if (e.message != null) { e.message.toString() @@ -302,7 +286,7 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma // 이메일 유효성을 검사하는 함수 private fun isEmailValid(email: String): Boolean { - return android.util.Patterns.EMAIL_ADDRESS.matcher(email).matches() + return Patterns.EMAIL_ADDRESS.matcher(email).matches() } diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt index ffc4b85c..b90e0f0d 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt @@ -37,6 +37,7 @@ import kr.co.lion.modigm.ui.login.email.component.FindEmailTextButton import kr.co.lion.modigm.ui.login.email.component.FindPasswordTextButton import kr.co.lion.modigm.ui.login.email.component.NavigateToSocialLoginButton import kr.co.lion.modigm.ui.login.email.component.PasswordTextField +import kr.co.lion.modigm.ui.login.social.dpToSp import kr.co.lion.modigm.util.JoinType @Composable @@ -47,14 +48,20 @@ fun EmailLoginScreen( onNavigateToBottomNaviFragment: (JoinType) -> Unit, onNavigateToFindEmailFragment: () -> Unit, onNavigateToFindPasswordFragment: () -> Unit, - onEmailLoginButtonClick: () -> Unit, + onEmailLoginButtonClick: (String, String, Boolean) -> Unit, onNavigateToSocialLoginFragment: () -> Unit, onNavigateToJoinFragment: (JoinType) -> Unit, showLoginErrorDialog: (Throwable) -> Unit, ) { val scrollState = rememberScrollState() + val email = remember { mutableStateOf("") } + val password = remember { mutableStateOf("") } val isChecked = remember { mutableStateOf(false) } + val isLoginButtonEnabled = remember(email.value, password.value) { + email.value.isNotBlank() && password.value.isNotBlank() + } + LaunchedEffect(emailLoginResult) { if (emailLoginResult) { onNavigateToBottomNaviFragment(JoinType.EMAIL) @@ -78,8 +85,14 @@ fun EmailLoginScreen( ) { EmailLoginTitle() EmailLoginSubTitle() - EmailTextField() - PasswordTextField() + EmailTextField( + email = email.value, + onEmailChange = { email.value = it } + ) + PasswordTextField( + password = password.value, + onPasswordChange = { password.value = it } + ) Row( verticalAlignment = Alignment.CenterVertically, modifier = Modifier @@ -103,7 +116,10 @@ fun EmailLoginScreen( FindPasswordTextButton(onNavigateToFindPasswordFragment = onNavigateToFindPasswordFragment) } } - EmailLoginButton(onEmailLoginButtonClick = onEmailLoginButtonClick) + EmailLoginButton( + onEmailLoginButtonClick = { onEmailLoginButtonClick (email.value, password.value, isChecked.value) }, + isEnabled = isLoginButtonEnabled + ) Row( verticalAlignment = Alignment.CenterVertically, @@ -173,7 +189,7 @@ fun EmailLoginScreenPreview() { showLoginErrorDialog = {}, onNavigateToFindEmailFragment = {}, onNavigateToFindPasswordFragment = {}, - onEmailLoginButtonClick = {}, + onEmailLoginButtonClick = { email, password, autoLoginValue -> println("email: $email, password: $password, autoLoginValue: $autoLoginValue") }, onNavigateToSocialLoginFragment = {}, onNavigateToJoinFragment = {}, ) diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailAutoLoginCheckBox.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailAutoLoginCheckBox.kt index 2a957022..a2a025de 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailAutoLoginCheckBox.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailAutoLoginCheckBox.kt @@ -14,7 +14,7 @@ import kr.co.lion.modigm.ui.login.email.dpToSp @Composable fun EmailAutoLoginCheckBox( - isChecked: MutableState + isChecked: MutableState, ) { Checkbox( checked = isChecked.value, @@ -23,9 +23,11 @@ fun EmailAutoLoginCheckBox( .size(20.dp) .padding(top = 0.dp, bottom = 0.dp, start = 0.dp, end = 0.dp) ) - Spacer(modifier = Modifier - .size(8.dp) - .clickable { isChecked.value = !isChecked.value }) + Spacer( + modifier = Modifier + .size(8.dp) + .clickable { isChecked.value = !isChecked.value } + ) Text( text = "자동 로그인", fontSize = dpToSp(16.dp), diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginButton.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginButton.kt index a9c76175..5f88df7f 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginButton.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginButton.kt @@ -16,14 +16,16 @@ import kr.co.lion.modigm.ui.login.email.dpToSp @Composable fun EmailLoginButton( - onEmailLoginButtonClick: () -> Unit + onEmailLoginButtonClick: () -> Unit, + isEnabled: Boolean ) { Button( onClick = { onEmailLoginButtonClick() }, modifier = Modifier .fillMaxWidth() .padding(top = 10.dp), - colors = buttonColors(Color(ContextCompat.getColor(LocalContext.current, R.color.pointColor))) + colors = buttonColors(Color(ContextCompat.getColor(LocalContext.current, R.color.pointColor))), + enabled = isEnabled ) { Text( text = "로그인", diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailTextField.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailTextField.kt index 5a4d0bef..c17fdaef 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailTextField.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailTextField.kt @@ -16,7 +16,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @@ -29,8 +28,10 @@ import kr.co.lion.modigm.R import kr.co.lion.modigm.ui.login.email.dpToSp @Composable -fun EmailTextField() { - var email by remember { mutableStateOf("") } +fun EmailTextField( + email: String, + onEmailChange: (String) -> Unit +) { val isError by remember { mutableStateOf(false) } val pointColor = Color(ContextCompat.getColor(LocalContext.current, R.color.pointColor)) Column( @@ -40,9 +41,7 @@ fun EmailTextField() { ) { OutlinedTextField( value = email, - onValueChange = { - email = it - }, + onValueChange = { onEmailChange(it) }, modifier = Modifier.fillMaxWidth(), textStyle = LocalTextStyle.current.copy(fontSize = dpToSp(16.dp)), colors = TextFieldDefaults.colors( @@ -58,15 +57,15 @@ fun EmailTextField() { leadingIcon = { Icon( painter = painterResource(id = R.drawable.icon_mail_24px), - contentDescription = "Mail Icon" + contentDescription = "이메일" ) }, trailingIcon = { if (email.isNotEmpty()) { - IconButton(onClick = { email = "" }) { + IconButton(onClick = { onEmailChange("") }) { Icon( imageVector = Icons.Default.Clear, - contentDescription = "Clear Text" + contentDescription = "이메일 초기화" ) } } diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/PasswordTextFeild.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/PasswordTextFeild.kt index 32e7dcaf..b123eddf 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/PasswordTextFeild.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/PasswordTextFeild.kt @@ -32,8 +32,10 @@ import kr.co.lion.modigm.R import kr.co.lion.modigm.ui.login.email.dpToSp @Composable -fun PasswordTextField() { - var password by remember { mutableStateOf("") } +fun PasswordTextField( + password: String, + onPasswordChange: (String) -> Unit +) { var isPasswordVisible by remember { mutableStateOf(false) } val pointColor = Color(ContextCompat.getColor(LocalContext.current, R.color.pointColor)) Column( @@ -44,7 +46,7 @@ fun PasswordTextField() { OutlinedTextField( value = password, onValueChange = { - password = it + onPasswordChange(it) isPasswordVisible = it.isEmpty() }, modifier = Modifier @@ -63,23 +65,18 @@ fun PasswordTextField() { leadingIcon = { Icon( painter = painterResource(id = R.drawable.icon_key_24px), - contentDescription = "Key Icon" + contentDescription = "비밀번호 아이콘" ) }, trailingIcon = { IconButton(onClick = { isPasswordVisible = !isPasswordVisible }) { Icon( - imageVector = if (isPasswordVisible) Icons.Default.Visibility - else Icons.Default.VisibilityOff, - contentDescription = if (isPasswordVisible) "Hide Password" else "Show Password" + imageVector = if (isPasswordVisible) Icons.Default.Visibility else Icons.Default.VisibilityOff, + contentDescription = if (isPasswordVisible) "비밀번호 숨김" else "비밀번호 보임" ) } }, - visualTransformation = if (isPasswordVisible) { - VisualTransformation.None - } else { - PasswordVisualTransformation() - }, + visualTransformation = if (isPasswordVisible) VisualTransformation.None else PasswordVisualTransformation(), keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Password, imeAction = ImeAction.Done From f2e8f2c3ce0e0322d46f76bb0c63352554de42db Mon Sep 17 00:00:00 2001 From: jusungwon Date: Sun, 12 Jan 2025 01:36:49 +0900 Subject: [PATCH 21/28] =?UTF-8?q?[Refactor]=20=EC=9D=B4=EB=A9=94=EC=9D=BC?= =?UTF-8?q?=EB=A1=9C=EA=B7=B8=EC=9D=B8=20Modifier=20=EA=B3=84=EC=B8=B5=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9=201.=20=EC=83=81=EC=9C=84=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=EC=97=90=EC=84=9C=20=ED=95=98?= =?UTF-8?q?=EC=9C=84=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=EC=9D=98=20Mod?= =?UTF-8?q?ifier=EB=A5=BC=20=EC=A0=9C=EC=96=B4=ED=95=98=EB=8A=94=EA=B2=8C?= =?UTF-8?q?=20=EA=B0=80=EB=8A=A5=ED=95=98=EB=8F=84=EB=A1=9D=20=EA=B3=84?= =?UTF-8?q?=EC=B8=B5=20=EC=A0=81=EC=9A=A9=202.=20=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=9D=B4=EC=83=81=EC=9C=BC=EB=A1=9C=20=EB=B6=84=EB=A6=AC?= =?UTF-8?q?=ED=96=88=EB=8D=98=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20?= =?UTF-8?q?=EB=B3=91=ED=95=A9=20=EB=B0=8F=20=EC=9E=AC=EB=B0=B0=EC=B9=98=20?= =?UTF-8?q?3.=20=ED=85=8D=EC=8A=A4=ED=8A=B8=20'|'=20=EB=A1=9C=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=ED=96=88=EB=8D=98=20=EB=94=94=EB=B0=94=EC=9D=B4?= =?UTF-8?q?=EB=8D=94=20=EB=A8=B8=ED=84=B0=EB=A6=AC=EC=96=BC3=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EC=A0=9C=EA=B3=B5=ED=95=98=EB=8A=94=20VerticalDivi?= =?UTF-8?q?der=20API=20=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/login/email/EmailLoginFragment.kt | 37 +++-- .../modigm/ui/login/email/EmailLoginScreen.kt | 135 ++++++++++-------- .../email/component/EmailAutoLoginCheckBox.kt | 42 +++--- .../login/email/component/EmailLoginButton.kt | 3 +- .../email/component/EmailLoginJoinButton.kt | 37 +++-- .../email/component/EmailLoginScrollArrow.kt | 2 +- .../login/email/component/EmailTextField.kt | 5 +- .../email/component/FindEmailTextButton.kt | 5 +- .../email/component/FindPasswordTextButton.kt | 9 +- .../component/NavigateToSocialLoginButton.kt | 37 +++-- .../email/component/PasswordTextFeild.kt | 85 ++++++----- 11 files changed, 224 insertions(+), 173 deletions(-) diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt index 6f4a6b46..26a84d71 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt @@ -9,8 +9,12 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.inputmethod.EditorInfo +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Scaffold import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState +import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView import androidx.fragment.app.commit import androidx.fragment.app.viewModels @@ -45,20 +49,25 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma val emailLoginResult by viewModel.emailLoginResult.observeAsState(false) val emailLoginError by viewModel.emailLoginError.observeAsState() - EmailLoginScreen( - isLoading = isLoading, - emailLoginResult = emailLoginResult, - emailLoginError = emailLoginError, - onNavigateToBottomNaviFragment = { joinType -> navigateToBottomNaviFragment(joinType) }, - onNavigateToFindEmailFragment = { navigateToFindEmailFragment() }, - onNavigateToFindPasswordFragment = { navigateToFindPasswordFragment() }, - onNavigateToJoinFragment = { joinType -> navigateToJoinFragment(joinType) }, - onEmailLoginButtonClick = { email, password, autoLoginValue -> - viewModel.emailLogin(email, password, autoLoginValue) - }, - onNavigateToSocialLoginFragment = { navigateToSocialLoginFragment() }, - showLoginErrorDialog = { error -> showErrorDialog(error) }, - ) + Scaffold( + modifier = Modifier.fillMaxSize(), + ) { innerPadding -> + EmailLoginScreen( + modifier = Modifier.padding(innerPadding), + isLoading = isLoading, + emailLoginResult = emailLoginResult, + emailLoginError = emailLoginError, + onNavigateToBottomNaviFragment = { joinType -> navigateToBottomNaviFragment(joinType) }, + onNavigateToFindEmailFragment = { navigateToFindEmailFragment() }, + onNavigateToFindPasswordFragment = { navigateToFindPasswordFragment() }, + onNavigateToJoinFragment = { joinType -> navigateToJoinFragment(joinType) }, + onEmailLoginButtonClick = { email, password, autoLoginValue -> + viewModel.emailLogin(email, password, autoLoginValue) + }, + onNavigateToSocialLoginFragment = { navigateToSocialLoginFragment() }, + showLoginErrorDialog = { error -> showErrorDialog(error) }, + ) + } } } } diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt index b90e0f0d..31468bab 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt @@ -6,12 +6,14 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row 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.wrapContentHeight import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Text +import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf @@ -42,6 +44,7 @@ import kr.co.lion.modigm.util.JoinType @Composable fun EmailLoginScreen( + modifier: Modifier = Modifier, isLoading: Boolean, emailLoginResult: Boolean, emailLoginError: Throwable?, @@ -75,109 +78,119 @@ fun EmailLoginScreen( } Box( - modifier = Modifier.fillMaxSize(), + modifier = modifier.fillMaxSize(), ) { Column( - modifier = Modifier + modifier = modifier .verticalScroll(scrollState) .fillMaxSize() .padding(horizontal = 16.dp) ) { - EmailLoginTitle() - EmailLoginSubTitle() + Text( + text = "모우다임", + modifier = modifier + .wrapContentWidth() + .wrapContentHeight() + .padding(top = 100.dp), + fontFamily = FontFamily(Font(R.font.one_mobile_pop_otf)), + fontSize = dpToSp(70.dp), + color = Color.Black, + fontWeight = FontWeight.Normal + ) + Text( + text = "개발자 스터디의 새로운 패러다임", + modifier = modifier + .wrapContentWidth() + .wrapContentHeight() + .padding(top = 10.dp), + fontFamily = FontFamily(Font(R.font.one_mobile_pop_otf)), + fontSize = dpToSp(22.dp), + color = Color.Black, + fontWeight = FontWeight.Normal + ) + EmailTextField( + modifier = modifier, email = email.value, onEmailChange = { email.value = it } ) PasswordTextField( + modifier = modifier, password = password.value, onPasswordChange = { password.value = it } ) Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier + modifier = modifier .fillMaxWidth() .padding(top = 10.dp, end = 0.dp), + verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { + EmailAutoLoginCheckBox( + modifier = modifier, + isChecked = isChecked + ) + Row( - verticalAlignment = Alignment.CenterVertically - ) { - EmailAutoLoginCheckBox(isChecked = isChecked) - } - Row( + modifier = modifier, verticalAlignment = Alignment.CenterVertically, ) { - FindEmailTextButton(onNavigateToFindEmailFragment = onNavigateToFindEmailFragment) - Text( - text = "|", - fontSize = dpToSp(16.dp) + FindEmailTextButton( + modifier = modifier, + onNavigateToFindEmailFragment = onNavigateToFindEmailFragment + ) + VerticalDivider( + modifier = modifier + .height(15.dp), + thickness = 1.dp, + color = Color.Black, + ) + FindPasswordTextButton( + modifier = modifier, + onNavigateToFindPasswordFragment = onNavigateToFindPasswordFragment ) - FindPasswordTextButton(onNavigateToFindPasswordFragment = onNavigateToFindPasswordFragment) } } EmailLoginButton( - onEmailLoginButtonClick = { onEmailLoginButtonClick (email.value, password.value, isChecked.value) }, + modifier = modifier, + onEmailLoginButtonClick = { + onEmailLoginButtonClick( + email.value, + password.value, + isChecked.value + ) + }, isEnabled = isLoginButtonEnabled ) Row( - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier + modifier = modifier .fillMaxWidth() .padding(start = 0.dp, end = 0.dp), + verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { - Row( - verticalAlignment = Alignment.CenterVertically - ) { - NavigateToSocialLoginButton(onNavigateToSocialLoginFragment = onNavigateToSocialLoginFragment) - } - Row( - verticalAlignment = Alignment.CenterVertically, - ) { - EmailLoginJoinButton(onNavigateToJoinFragment = onNavigateToJoinFragment) - } + NavigateToSocialLoginButton( + modifier = modifier, + onNavigateToSocialLoginFragment = onNavigateToSocialLoginFragment + ) + EmailLoginJoinButton( + modifier = modifier, + onNavigateToJoinFragment = onNavigateToJoinFragment + ) } } EmailLoginScrollArrow( + modifier = modifier.align(Alignment.BottomCenter), scrollState = scrollState, - modifier = Modifier.align(Alignment.BottomCenter) ) - EmailLoginLoading(isLoading = isLoading) + EmailLoginLoading( + modifier = modifier, + isLoading = isLoading + ) } } -@Composable -fun EmailLoginTitle() { - Text( - text = "모우다임", - modifier = Modifier - .wrapContentWidth() - .wrapContentHeight() - .padding(top = 100.dp), - fontFamily = FontFamily(Font(R.font.one_mobile_pop_otf)), - fontSize = dpToSp(70.dp), - color = Color.Black, - fontWeight = FontWeight.Normal - ) -} - -@Composable -fun EmailLoginSubTitle() { - Text( - text = "개발자 스터디의 새로운 패러다임", - modifier = Modifier - .wrapContentWidth() - .wrapContentHeight() - .padding(top = 10.dp), - fontFamily = FontFamily(Font(R.font.one_mobile_pop_otf)), - fontSize = dpToSp(22.dp), - color = Color.Black, - fontWeight = FontWeight.Normal - ) -} - @Preview(showBackground = true) @Composable fun EmailLoginScreenPreview() { diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailAutoLoginCheckBox.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailAutoLoginCheckBox.kt index a2a025de..b85ecb89 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailAutoLoginCheckBox.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailAutoLoginCheckBox.kt @@ -1,6 +1,7 @@ package kr.co.lion.modigm.ui.login.email.component import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size @@ -8,29 +9,36 @@ import androidx.compose.material3.Checkbox import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.MutableState +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import kr.co.lion.modigm.ui.login.email.dpToSp @Composable fun EmailAutoLoginCheckBox( + modifier: Modifier = Modifier, isChecked: MutableState, ) { - Checkbox( - checked = isChecked.value, - onCheckedChange = { isChecked.value = it }, - modifier = Modifier - .size(20.dp) - .padding(top = 0.dp, bottom = 0.dp, start = 0.dp, end = 0.dp) - ) - Spacer( - modifier = Modifier - .size(8.dp) - .clickable { isChecked.value = !isChecked.value } - ) - Text( - text = "자동 로그인", - fontSize = dpToSp(16.dp), - modifier = Modifier.clickable { isChecked.value = !isChecked.value } - ) + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically + ) { + Checkbox( + checked = isChecked.value, + onCheckedChange = { isChecked.value = it }, + modifier = Modifier + .size(20.dp) + .padding(top = 0.dp, bottom = 0.dp, start = 0.dp, end = 0.dp) + ) + Spacer( + modifier = Modifier + .size(8.dp) + .clickable { isChecked.value = !isChecked.value } + ) + Text( + text = "자동 로그인", + fontSize = dpToSp(16.dp), + modifier = Modifier.clickable { isChecked.value = !isChecked.value } + ) + } } \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginButton.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginButton.kt index 5f88df7f..12be18ae 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginButton.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginButton.kt @@ -16,12 +16,13 @@ import kr.co.lion.modigm.ui.login.email.dpToSp @Composable fun EmailLoginButton( + modifier: Modifier = Modifier, onEmailLoginButtonClick: () -> Unit, isEnabled: Boolean ) { Button( onClick = { onEmailLoginButtonClick() }, - modifier = Modifier + modifier = modifier .fillMaxWidth() .padding(top = 10.dp), colors = buttonColors(Color(ContextCompat.getColor(LocalContext.current, R.color.pointColor))), diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginJoinButton.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginJoinButton.kt index ccb0686a..8766b0e9 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginJoinButton.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginJoinButton.kt @@ -1,11 +1,13 @@ package kr.co.lion.modigm.ui.login.email.component +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.size import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource @@ -16,26 +18,33 @@ import kr.co.lion.modigm.util.JoinType @Composable fun EmailLoginJoinButton( + modifier: Modifier = Modifier, onNavigateToJoinFragment: (JoinType) -> Unit ) { - Text( - text = "계정이 없으신가요?", - fontSize = dpToSp(16.dp), - color = Color.Black - ) - TextButton( - onClick = { onNavigateToJoinFragment(JoinType.EMAIL) } + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically ) { - Icon( - painter = painterResource(R.drawable.icon_person_add_24px), - contentDescription = "회원가입", - tint = Color.Black - ) - Spacer(Modifier.size(4.dp)) Text( - text = "회원가입", + text = "계정이 없으신가요?", fontSize = dpToSp(16.dp), color = Color.Black ) + TextButton( + onClick = { onNavigateToJoinFragment(JoinType.EMAIL) } + ) { + Icon( + painter = painterResource(R.drawable.icon_person_add_24px), + contentDescription = "회원가입", + tint = Color.Black + ) + Spacer(modifier.size(4.dp)) + Text( + text = "회원가입", + fontSize = dpToSp(16.dp), + color = Color.Black + ) + } } + } \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginScrollArrow.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginScrollArrow.kt index 3da1860a..b6ac7ab1 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginScrollArrow.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginScrollArrow.kt @@ -33,8 +33,8 @@ import kr.co.lion.modigm.R @Composable fun EmailLoginScrollArrow( + modifier: Modifier = Modifier, scrollState: ScrollState, - modifier: Modifier = Modifier ) { val isVisible = remember { mutableStateOf(false) } val coroutineScope = rememberCoroutineScope() diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailTextField.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailTextField.kt index c17fdaef..c07756d0 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailTextField.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailTextField.kt @@ -29,20 +29,21 @@ import kr.co.lion.modigm.ui.login.email.dpToSp @Composable fun EmailTextField( + modifier: Modifier = Modifier, email: String, onEmailChange: (String) -> Unit ) { val isError by remember { mutableStateOf(false) } val pointColor = Color(ContextCompat.getColor(LocalContext.current, R.color.pointColor)) Column( - modifier = Modifier + modifier = modifier .fillMaxWidth() .padding(top = 30.dp) ) { OutlinedTextField( value = email, onValueChange = { onEmailChange(it) }, - modifier = Modifier.fillMaxWidth(), + modifier = modifier.fillMaxWidth(), textStyle = LocalTextStyle.current.copy(fontSize = dpToSp(16.dp)), colors = TextFieldDefaults.colors( focusedTextColor = Color.Black, diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindEmailTextButton.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindEmailTextButton.kt index 53287335..cdafc4c1 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindEmailTextButton.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindEmailTextButton.kt @@ -11,11 +11,12 @@ import kr.co.lion.modigm.ui.login.email.dpToSp @Composable fun FindEmailTextButton( - onNavigateToFindEmailFragment: () -> Unit + modifier: Modifier = Modifier, + onNavigateToFindEmailFragment: () -> Unit, ) { TextButton( + modifier = modifier.padding(end = 4.dp), onClick = { onNavigateToFindEmailFragment() }, - modifier = Modifier.padding(end = 4.dp) ) { Text( text = "이메일 찾기", diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindPasswordTextButton.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindPasswordTextButton.kt index 73178b18..f474b488 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindPasswordTextButton.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindPasswordTextButton.kt @@ -4,22 +4,25 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @Composable fun FindPasswordTextButton( - onNavigateToFindPasswordFragment: () -> Unit + modifier: Modifier = Modifier, + onNavigateToFindPasswordFragment: () -> Unit, ) { TextButton( + modifier = modifier, onClick = { onNavigateToFindPasswordFragment() }, - contentPadding = PaddingValues(start = 16.dp, end = 0.dp) + contentPadding = PaddingValues(start = 16.dp, end = 0.dp), ) { Text( text = "비밀번호 찾기", color = Color.Black, - fontSize = 16.sp + fontSize = 16.sp, ) } } \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/NavigateToSocialLoginButton.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/NavigateToSocialLoginButton.kt index 6fdf474b..bd18dd11 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/NavigateToSocialLoginButton.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/NavigateToSocialLoginButton.kt @@ -1,12 +1,14 @@ package kr.co.lion.modigm.ui.login.email.component import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.size import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource @@ -16,22 +18,29 @@ import kr.co.lion.modigm.ui.login.email.dpToSp @Composable fun NavigateToSocialLoginButton( + modifier: Modifier = Modifier, onNavigateToSocialLoginFragment: () -> Unit ) { - TextButton( - onClick = { onNavigateToSocialLoginFragment() }, - contentPadding = PaddingValues(0.dp) + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically ) { - Icon( - painter = painterResource(R.drawable.icon_back_24px), - contentDescription = "돌아가기", - tint = Color.Black - ) - Spacer(modifier = Modifier.size(8.dp)) - Text( - text = "돌아가기", - fontSize = dpToSp(16.dp), - color = Color.Black - ) + TextButton( + onClick = { onNavigateToSocialLoginFragment() }, + contentPadding = PaddingValues(0.dp) + ) { + Icon( + painter = painterResource(R.drawable.icon_back_24px), + contentDescription = "돌아가기", + tint = Color.Black + ) + Spacer(modifier = modifier.size(8.dp)) + Text( + text = "돌아가기", + fontSize = dpToSp(16.dp), + color = Color.Black + ) + } } + } \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/PasswordTextFeild.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/PasswordTextFeild.kt index b123eddf..09a2344f 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/PasswordTextFeild.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/PasswordTextFeild.kt @@ -1,6 +1,5 @@ package kr.co.lion.modigm.ui.login.email.component -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardOptions @@ -33,55 +32,53 @@ import kr.co.lion.modigm.ui.login.email.dpToSp @Composable fun PasswordTextField( + modifier: Modifier = Modifier, password: String, onPasswordChange: (String) -> Unit ) { var isPasswordVisible by remember { mutableStateOf(false) } val pointColor = Color(ContextCompat.getColor(LocalContext.current, R.color.pointColor)) - Column( - modifier = Modifier + + OutlinedTextField( + value = password, + onValueChange = { + onPasswordChange(it) + isPasswordVisible = it.isEmpty() + }, + modifier = modifier .fillMaxWidth() - .padding(top = 20.dp) - ) { - OutlinedTextField( - value = password, - onValueChange = { - onPasswordChange(it) - isPasswordVisible = it.isEmpty() - }, - modifier = Modifier - .fillMaxWidth(), - textStyle = LocalTextStyle.current.copy(fontSize = dpToSp(16.dp)), - colors = TextFieldDefaults.colors( - focusedTextColor = Color.Black, - unfocusedTextColor = Color.Black, - focusedContainerColor = Color.White, - unfocusedContainerColor = Color.White, - focusedIndicatorColor = pointColor, - unfocusedIndicatorColor = Color.Black, - errorContainerColor = Color.White, - ), - placeholder = { Text(text = "비밀번호") }, - leadingIcon = { + .padding(top = 20.dp), + textStyle = LocalTextStyle.current.copy(fontSize = dpToSp(16.dp)), + colors = TextFieldDefaults.colors( + focusedTextColor = Color.Black, + unfocusedTextColor = Color.Black, + focusedContainerColor = Color.White, + unfocusedContainerColor = Color.White, + focusedIndicatorColor = pointColor, + unfocusedIndicatorColor = Color.Black, + errorContainerColor = Color.White, + ), + placeholder = { Text(text = "비밀번호") }, + leadingIcon = { + Icon( + painter = painterResource(id = R.drawable.icon_key_24px), + contentDescription = "비밀번호 아이콘" + ) + }, + trailingIcon = { + IconButton(onClick = { isPasswordVisible = !isPasswordVisible }) { Icon( - painter = painterResource(id = R.drawable.icon_key_24px), - contentDescription = "비밀번호 아이콘" + imageVector = if (isPasswordVisible) Icons.Default.Visibility else Icons.Default.VisibilityOff, + contentDescription = if (isPasswordVisible) "비밀번호 숨김" else "비밀번호 보임" ) - }, - trailingIcon = { - IconButton(onClick = { isPasswordVisible = !isPasswordVisible }) { - Icon( - imageVector = if (isPasswordVisible) Icons.Default.Visibility else Icons.Default.VisibilityOff, - contentDescription = if (isPasswordVisible) "비밀번호 숨김" else "비밀번호 보임" - ) - } - }, - visualTransformation = if (isPasswordVisible) VisualTransformation.None else PasswordVisualTransformation(), - keyboardOptions = KeyboardOptions( - keyboardType = KeyboardType.Password, - imeAction = ImeAction.Done - ), - singleLine = true - ) - } + } + }, + visualTransformation = if (isPasswordVisible) VisualTransformation.None else PasswordVisualTransformation(), + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Password, + imeAction = ImeAction.Done + ), + singleLine = true + ) + } \ No newline at end of file From 08c3ad859e239090e0ce46a5722185567d33e4a8 Mon Sep 17 00:00:00 2001 From: jusungwon Date: Fri, 24 Jan 2025 01:16:47 +0900 Subject: [PATCH 22/28] =?UTF-8?q?[Refactor]=20Modifier=20=EB=B0=8F=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EC=88=98=EC=A0=95=201.?= =?UTF-8?q?=20=EC=83=81=EC=9C=84=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=ED=95=98=EC=9C=84=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=EC=9D=98=20Modifier=EB=A5=BC=20=EC=A0=9C?= =?UTF-8?q?=EC=96=B4=ED=95=98=EB=8A=94=EA=B2=8C=20=EA=B0=80=EB=8A=A5?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EA=B3=84=EC=B8=B5=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9=202.=20=ED=95=84=EC=9A=94=20=EC=9D=B4=EC=83=81?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EB=B6=84=EB=A6=AC=ED=96=88=EB=8D=98=20?= =?UTF-8?q?=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=EB=B3=91=ED=95=A9=20?= =?UTF-8?q?=EB=B0=8F=20=EC=9E=AC=EB=B0=B0=EC=B9=98=203.=20=EC=BB=B4?= =?UTF-8?q?=ED=8F=AC=EB=84=8C=ED=8A=B8=20=ED=94=84=EB=A6=AC=EB=B7=B0=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/login/email/EmailLoginFragment.kt | 18 +-- .../modigm/ui/login/email/EmailLoginScreen.kt | 121 +++++++++++++----- .../login/email/component/EmailLoginButton.kt | 36 ------ .../email/component/EmailLoginJoinButton.kt | 21 ++- .../email/component/EmailLoginLoading.kt | 2 - .../email/component/EmailLoginScrollArrow.kt | 15 ++- .../login/email/component/EmailTextField.kt | 84 ++++++------ .../email/component/FindEmailTextButton.kt | 27 ---- .../email/component/FindPasswordTextButton.kt | 28 ---- .../component/NavigateToSocialLoginButton.kt | 46 ------- .../email/component/PasswordTextFeild.kt | 6 +- 11 files changed, 157 insertions(+), 247 deletions(-) delete mode 100644 app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginButton.kt delete mode 100644 app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindEmailTextButton.kt delete mode 100644 app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindPasswordTextButton.kt delete mode 100644 app/src/main/java/kr/co/lion/modigm/ui/login/email/component/NavigateToSocialLoginButton.kt diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt index 26a84d71..f95766bb 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt @@ -3,7 +3,6 @@ package kr.co.lion.modigm.ui.login.email import android.os.Bundle import android.text.Editable import android.text.TextWatcher -import android.util.Log import android.util.Patterns import android.view.LayoutInflater import android.view.View @@ -36,8 +35,6 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma private val viewModel: EmailLoginViewModel by viewModels() - private val logTag by lazy { EmailLoginFragment::class.simpleName } - override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, @@ -62,6 +59,7 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma onNavigateToFindPasswordFragment = { navigateToFindPasswordFragment() }, onNavigateToJoinFragment = { joinType -> navigateToJoinFragment(joinType) }, onEmailLoginButtonClick = { email, password, autoLoginValue -> + requireActivity().hideSoftInput() viewModel.emailLogin(email, password, autoLoginValue) }, onNavigateToSocialLoginFragment = { navigateToSocialLoginFragment() }, @@ -140,9 +138,6 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma } } - // 로그인 버튼 초기값을 비활성화 상태로 설정 - buttonOtherLogin.isEnabled = false - // 로그인 버튼 클릭 시 로그인 시도 buttonOtherLogin.setOnClickListener { if(!checkAllInput()) { @@ -155,16 +150,6 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma val autoLogin = checkBoxOtherAutoLogin.isChecked viewModel.emailLogin(email, password, autoLogin) } - - // 회원가입 버튼 클릭 시 회원가입 화면으로 이동 - buttonOtherJoin.setOnClickListener { - val joinType = JoinType.EMAIL - navigateToJoinFragment(joinType) - } - // 돌아가기 버튼 클릭 시 - buttonOtherBack.setOnClickListener { - parentFragmentManager.popBackStack() - } } } @@ -197,7 +182,6 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma // 회원가입 화면으로 이동하는 메소드 private fun navigateToJoinFragment(joinType: JoinType) { - Log.d(logTag, "navigateToJoinFragment - joinType: ${joinType.provider}") val bundle = Bundle().apply { putString("joinType", joinType.provider) } diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt index 31468bab..96cf0691 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt @@ -3,16 +3,23 @@ package kr.co.lion.modigm.ui.login.email import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row +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.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults.buttonColors +import androidx.compose.material3.Icon import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect @@ -21,23 +28,23 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.res.painterResource 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.dp +import androidx.compose.ui.unit.sp +import androidx.core.content.ContextCompat import kr.co.lion.modigm.R import kr.co.lion.modigm.ui.login.email.component.EmailAutoLoginCheckBox -import kr.co.lion.modigm.ui.login.email.component.EmailLoginButton import kr.co.lion.modigm.ui.login.email.component.EmailLoginJoinButton import kr.co.lion.modigm.ui.login.email.component.EmailLoginLoading import kr.co.lion.modigm.ui.login.email.component.EmailLoginScrollArrow import kr.co.lion.modigm.ui.login.email.component.EmailTextField -import kr.co.lion.modigm.ui.login.email.component.FindEmailTextButton -import kr.co.lion.modigm.ui.login.email.component.FindPasswordTextButton -import kr.co.lion.modigm.ui.login.email.component.NavigateToSocialLoginButton import kr.co.lion.modigm.ui.login.email.component.PasswordTextField import kr.co.lion.modigm.ui.login.social.dpToSp import kr.co.lion.modigm.util.JoinType @@ -88,7 +95,7 @@ fun EmailLoginScreen( ) { Text( text = "모우다임", - modifier = modifier + modifier = Modifier .wrapContentWidth() .wrapContentHeight() .padding(top = 100.dp), @@ -99,7 +106,7 @@ fun EmailLoginScreen( ) Text( text = "개발자 스터디의 새로운 패러다임", - modifier = modifier + modifier = Modifier .wrapContentWidth() .wrapContentHeight() .padding(top = 10.dp), @@ -110,82 +117,130 @@ fun EmailLoginScreen( ) EmailTextField( - modifier = modifier, + modifier = Modifier + .fillMaxWidth() + .padding(top = 30.dp), email = email.value, onEmailChange = { email.value = it } ) + PasswordTextField( - modifier = modifier, + modifier = Modifier + .fillMaxWidth() + .padding(top = 20.dp), password = password.value, onPasswordChange = { password.value = it } ) + Row( - modifier = modifier + modifier = Modifier .fillMaxWidth() .padding(top = 10.dp, end = 0.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { EmailAutoLoginCheckBox( - modifier = modifier, + modifier = Modifier, isChecked = isChecked ) Row( - modifier = modifier, + modifier = Modifier, verticalAlignment = Alignment.CenterVertically, ) { - FindEmailTextButton( - modifier = modifier, - onNavigateToFindEmailFragment = onNavigateToFindEmailFragment - ) + TextButton( + modifier = Modifier.padding(start = 0.dp, end = 15.dp), + onClick = { onNavigateToFindEmailFragment() }, + contentPadding = PaddingValues(0.dp), + ) { + Text( + text = "이메일 찾기", + color = Color.Black, + fontSize = dpToSp(16.dp) + ) + } VerticalDivider( - modifier = modifier + modifier = Modifier .height(15.dp), thickness = 1.dp, color = Color.Black, ) - FindPasswordTextButton( - modifier = modifier, - onNavigateToFindPasswordFragment = onNavigateToFindPasswordFragment - ) + TextButton( + modifier = Modifier + .padding(start = 15.dp, end = 0.dp), + onClick = { onNavigateToFindPasswordFragment() }, + contentPadding = PaddingValues(0.dp) + ) { + Text( + text = "비밀번호 찾기", + color = Color.Black, + fontSize = 16.sp, + ) + } } } - EmailLoginButton( - modifier = modifier, - onEmailLoginButtonClick = { + + Button( + modifier = Modifier + .fillMaxWidth() + .padding(top = 10.dp), + onClick = { onEmailLoginButtonClick( email.value, password.value, isChecked.value ) }, - isEnabled = isLoginButtonEnabled - ) + colors = buttonColors(Color(ContextCompat.getColor(LocalContext.current, R.color.pointColor))), + enabled = isLoginButtonEnabled + ) { + Text( + text = "로그인", + fontSize = dpToSp(16.dp) + ) + } Row( - modifier = modifier + modifier = Modifier .fillMaxWidth() .padding(start = 0.dp, end = 0.dp), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { - NavigateToSocialLoginButton( - modifier = modifier, - onNavigateToSocialLoginFragment = onNavigateToSocialLoginFragment - ) + TextButton( + modifier = Modifier, + onClick = { onNavigateToSocialLoginFragment() }, + contentPadding = PaddingValues(0.dp) + ) { + Icon( + painter = painterResource(R.drawable.icon_back_24px), + contentDescription = "돌아가기", + tint = Color.Black + ) + Spacer(modifier = Modifier.size(8.dp)) + Text( + text = "돌아가기", + fontSize = dpToSp(16.dp), + color = Color.Black + ) + } EmailLoginJoinButton( - modifier = modifier, + modifier = Modifier, onNavigateToJoinFragment = onNavigateToJoinFragment ) } } + EmailLoginScrollArrow( - modifier = modifier.align(Alignment.BottomCenter), + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomCenter), scrollState = scrollState, ) + EmailLoginLoading( - modifier = modifier, + modifier = Modifier + .fillMaxSize(), isLoading = isLoading ) } diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginButton.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginButton.kt deleted file mode 100644 index 12be18ae..00000000 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginButton.kt +++ /dev/null @@ -1,36 +0,0 @@ -package kr.co.lion.modigm.ui.login.email.component - -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults.buttonColors -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.unit.dp -import androidx.core.content.ContextCompat -import kr.co.lion.modigm.R -import kr.co.lion.modigm.ui.login.email.dpToSp - -@Composable -fun EmailLoginButton( - modifier: Modifier = Modifier, - onEmailLoginButtonClick: () -> Unit, - isEnabled: Boolean -) { - Button( - onClick = { onEmailLoginButtonClick() }, - modifier = modifier - .fillMaxWidth() - .padding(top = 10.dp), - colors = buttonColors(Color(ContextCompat.getColor(LocalContext.current, R.color.pointColor))), - enabled = isEnabled - ) { - Text( - text = "로그인", - fontSize = dpToSp(16.dp) - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginJoinButton.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginJoinButton.kt index 8766b0e9..0d1efd5c 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginJoinButton.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginJoinButton.kt @@ -1,8 +1,8 @@ package kr.co.lion.modigm.ui.login.email.component +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.padding import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.material3.TextButton @@ -11,6 +11,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import kr.co.lion.modigm.R import kr.co.lion.modigm.ui.login.email.dpToSp @@ -26,25 +27,37 @@ fun EmailLoginJoinButton( verticalAlignment = Alignment.CenterVertically ) { Text( + modifier = Modifier + .padding(end = 4.dp), text = "계정이 없으신가요?", fontSize = dpToSp(16.dp), color = Color.Black ) TextButton( - onClick = { onNavigateToJoinFragment(JoinType.EMAIL) } + modifier = Modifier, + onClick = { onNavigateToJoinFragment(JoinType.EMAIL) }, + contentPadding = PaddingValues(0.dp) ) { Icon( + modifier = Modifier.padding(end = 4.dp), painter = painterResource(R.drawable.icon_person_add_24px), contentDescription = "회원가입", tint = Color.Black ) - Spacer(modifier.size(4.dp)) Text( + modifier = Modifier, text = "회원가입", fontSize = dpToSp(16.dp), color = Color.Black ) } } +} +@Preview(showBackground = true) +@Composable +fun EmailLoginJoinButtonPreview() { + EmailLoginJoinButton( + onNavigateToJoinFragment = {} + ) } \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginLoading.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginLoading.kt index 59022531..a4dd5a25 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginLoading.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginLoading.kt @@ -2,7 +2,6 @@ package kr.co.lion.modigm.ui.login.email.component import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.material3.CircularProgressIndicator import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment @@ -19,7 +18,6 @@ fun EmailLoginLoading( if (isLoading) { Box( modifier = modifier - .fillMaxSize() .background(Color.Black.copy(alpha = 0.5f)) .pointerInput(Unit) { }, contentAlignment = Alignment.Center diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginScrollArrow.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginScrollArrow.kt index b6ac7ab1..be090dac 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginScrollArrow.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginScrollArrow.kt @@ -11,7 +11,6 @@ import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.size @@ -27,6 +26,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.res.painterResource +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import kotlinx.coroutines.launch import kr.co.lion.modigm.R @@ -48,10 +48,9 @@ fun EmailLoginScrollArrow( Box( modifier = modifier - .fillMaxWidth() .height(50.dp) .background(Color.Transparent), - contentAlignment = Alignment.Center + contentAlignment = Alignment.TopCenter ) { if (isVisible.value) { Image( @@ -65,7 +64,7 @@ fun EmailLoginScrollArrow( } } .animateEnterExit(), - colorFilter = ColorFilter.tint(Color.White) + colorFilter = ColorFilter.tint(Color.Black) ) } } @@ -84,4 +83,12 @@ fun Modifier.animateEnterExit(): Modifier { label = "" ) return this.offset(y = offsetY.dp) +} + +@Preview(showBackground = true) +@Composable +fun EmailLoginScrollArrowPreview() { + EmailLoginScrollArrow( + scrollState = ScrollState(0), + ) } \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailTextField.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailTextField.kt index c07756d0..231f5020 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailTextField.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailTextField.kt @@ -1,8 +1,6 @@ package kr.co.lion.modigm.ui.login.email.component -import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Clear @@ -35,48 +33,44 @@ fun EmailTextField( ) { val isError by remember { mutableStateOf(false) } val pointColor = Color(ContextCompat.getColor(LocalContext.current, R.color.pointColor)) - Column( - modifier = modifier - .fillMaxWidth() - .padding(top = 30.dp) - ) { - OutlinedTextField( - value = email, - onValueChange = { onEmailChange(it) }, - modifier = modifier.fillMaxWidth(), - textStyle = LocalTextStyle.current.copy(fontSize = dpToSp(16.dp)), - colors = TextFieldDefaults.colors( - focusedTextColor = Color.Black, - unfocusedTextColor = Color.Black, - focusedContainerColor = Color.White, - unfocusedContainerColor = Color.White, - focusedIndicatorColor = pointColor, - unfocusedIndicatorColor = Color.Black, - errorContainerColor = Color.White, - ), - placeholder = { Text(text = "이메일") }, - leadingIcon = { - Icon( - painter = painterResource(id = R.drawable.icon_mail_24px), - contentDescription = "이메일" - ) - }, - trailingIcon = { - if (email.isNotEmpty()) { - IconButton(onClick = { onEmailChange("") }) { - Icon( - imageVector = Icons.Default.Clear, - contentDescription = "이메일 초기화" - ) - } + + OutlinedTextField( + value = email, + onValueChange = { onEmailChange(it) }, + modifier = modifier.fillMaxWidth(), + textStyle = LocalTextStyle.current.copy(fontSize = dpToSp(16.dp)), + colors = TextFieldDefaults.colors( + focusedTextColor = Color.Black, + unfocusedTextColor = Color.Black, + focusedContainerColor = Color.White, + unfocusedContainerColor = Color.White, + focusedIndicatorColor = pointColor, + unfocusedIndicatorColor = Color.Black, + errorContainerColor = Color.White, + ), + placeholder = { Text(text = "이메일") }, + leadingIcon = { + Icon( + painter = painterResource(id = R.drawable.icon_mail_24px), + contentDescription = "이메일" + ) + }, + trailingIcon = { + if (email.isNotEmpty()) { + IconButton(onClick = { onEmailChange("") }) { + Icon( + imageVector = Icons.Default.Clear, + contentDescription = "이메일 초기화" + ) } - }, - keyboardOptions = KeyboardOptions( - keyboardType = KeyboardType.Email, - imeAction = ImeAction.Next - ), - singleLine = true, - isError = isError, - ) - } + } + }, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Email, + imeAction = ImeAction.Next + ), + singleLine = true, + isError = isError, + ) + } \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindEmailTextButton.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindEmailTextButton.kt deleted file mode 100644 index cdafc4c1..00000000 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindEmailTextButton.kt +++ /dev/null @@ -1,27 +0,0 @@ -package kr.co.lion.modigm.ui.login.email.component - -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.dp -import kr.co.lion.modigm.ui.login.email.dpToSp - -@Composable -fun FindEmailTextButton( - modifier: Modifier = Modifier, - onNavigateToFindEmailFragment: () -> Unit, -) { - TextButton( - modifier = modifier.padding(end = 4.dp), - onClick = { onNavigateToFindEmailFragment() }, - ) { - Text( - text = "이메일 찾기", - color = Color.Black, - fontSize = dpToSp(16.dp) - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindPasswordTextButton.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindPasswordTextButton.kt deleted file mode 100644 index f474b488..00000000 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/FindPasswordTextButton.kt +++ /dev/null @@ -1,28 +0,0 @@ -package kr.co.lion.modigm.ui.login.email.component - -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp - -@Composable -fun FindPasswordTextButton( - modifier: Modifier = Modifier, - onNavigateToFindPasswordFragment: () -> Unit, -) { - TextButton( - modifier = modifier, - onClick = { onNavigateToFindPasswordFragment() }, - contentPadding = PaddingValues(start = 16.dp, end = 0.dp), - ) { - Text( - text = "비밀번호 찾기", - color = Color.Black, - fontSize = 16.sp, - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/NavigateToSocialLoginButton.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/NavigateToSocialLoginButton.kt deleted file mode 100644 index bd18dd11..00000000 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/NavigateToSocialLoginButton.kt +++ /dev/null @@ -1,46 +0,0 @@ -package kr.co.lion.modigm.ui.login.email.component - -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.size -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.unit.dp -import kr.co.lion.modigm.R -import kr.co.lion.modigm.ui.login.email.dpToSp - -@Composable -fun NavigateToSocialLoginButton( - modifier: Modifier = Modifier, - onNavigateToSocialLoginFragment: () -> Unit -) { - Row( - modifier = modifier, - verticalAlignment = Alignment.CenterVertically - ) { - TextButton( - onClick = { onNavigateToSocialLoginFragment() }, - contentPadding = PaddingValues(0.dp) - ) { - Icon( - painter = painterResource(R.drawable.icon_back_24px), - contentDescription = "돌아가기", - tint = Color.Black - ) - Spacer(modifier = modifier.size(8.dp)) - Text( - text = "돌아가기", - fontSize = dpToSp(16.dp), - color = Color.Black - ) - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/PasswordTextFeild.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/PasswordTextFeild.kt index 09a2344f..5192760a 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/PasswordTextFeild.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/PasswordTextFeild.kt @@ -1,7 +1,5 @@ package kr.co.lion.modigm.ui.login.email.component -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Visibility @@ -40,14 +38,12 @@ fun PasswordTextField( val pointColor = Color(ContextCompat.getColor(LocalContext.current, R.color.pointColor)) OutlinedTextField( + modifier = modifier, value = password, onValueChange = { onPasswordChange(it) isPasswordVisible = it.isEmpty() }, - modifier = modifier - .fillMaxWidth() - .padding(top = 20.dp), textStyle = LocalTextStyle.current.copy(fontSize = dpToSp(16.dp)), colors = TextFieldDefaults.colors( focusedTextColor = Color.Black, From 8e5905d106cfb8e4b0e956f8e12863de02b3c5eb Mon Sep 17 00:00:00 2001 From: jusungwon Date: Fri, 24 Jan 2025 19:52:40 +0900 Subject: [PATCH 23/28] =?UTF-8?q?[Refactor]=20=EC=BB=B4=ED=8F=AC=EB=84=8C?= =?UTF-8?q?=ED=8A=B8=20=EA=B5=AC=EC=A1=B0=20=EC=88=98=EC=A0=95=201.=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=ED=95=B4=EC=84=9C=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=ED=95=98=EA=B3=A0=20=EC=9E=88=EB=8A=94=20=EC=BB=B4=ED=8F=AC?= =?UTF-8?q?=EB=84=8C=ED=8A=B8=EB=A7=8C=20=EC=B6=94=EC=B6=9C=202.=20?= =?UTF-8?q?=EC=9D=B4=EB=A9=94=EC=9D=BC,=20=EC=86=8C=EC=85=9C=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20=ED=99=94=EB=A9=B4=20=EA=B0=81=EA=B0=81?= =?UTF-8?q?=EC=9D=98=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=EB=A5=BC=20=EC=83=81=EC=9C=84(login)?= =?UTF-8?q?=EC=9D=98=20=EC=BB=B4=ED=8F=AC=EB=84=8C=ED=8A=B8=20=ED=8C=A8?= =?UTF-8?q?=ED=82=A4=EC=A7=80=EB=A1=9C=20=ED=86=B5=ED=95=A9=203.=20?= =?UTF-8?q?=EC=9C=A0=ED=8B=B8=20=ED=8C=A8=ED=82=A4=EC=A7=80=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=204.=20=ED=97=B7=EA=B0=88=EB=A6=B4=20=EC=88=98=20?= =?UTF-8?q?=EC=9E=88=EB=8A=94=20=EC=9D=B4=EB=A6=84=EB=93=A4=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{email => }/component/EmailTextField.kt | 41 +- .../LoginLoading.kt} | 8 +- .../component/PasswordTextFeild.kt | 18 +- .../ScrollArrow.kt} | 45 +-- .../ui/login/component/SocialLoginButton.kt | 38 ++ .../ui/login/email/EmailLoginFragment.kt | 26 +- .../modigm/ui/login/email/EmailLoginScreen.kt | 148 ++++---- .../email/component/EmailAutoLoginCheckBox.kt | 44 --- .../email/component/EmailLoginJoinButton.kt | 63 --- .../ui/login/social/SocialLoginFragment.kt | 61 ++- .../ui/login/social/SocialLoginScreen.kt | 358 ++++++++---------- .../social/component/GithubLoginButton.kt | 64 ---- .../social/component/KakaoLoginButton.kt | 42 -- .../component/NavigateToEmailLoginButton.kt | 52 --- .../social/component/SocialLoginLoading.kt | 29 -- .../kr/co/lion/modigm/ui/login/util/Util.kt | 34 ++ 16 files changed, 421 insertions(+), 650 deletions(-) rename app/src/main/java/kr/co/lion/modigm/ui/login/{email => }/component/EmailTextField.kt (72%) rename app/src/main/java/kr/co/lion/modigm/ui/login/{email/component/EmailLoginLoading.kt => component/LoginLoading.kt} (85%) rename app/src/main/java/kr/co/lion/modigm/ui/login/{email => }/component/PasswordTextFeild.kt (88%) rename app/src/main/java/kr/co/lion/modigm/ui/login/{email/component/EmailLoginScrollArrow.kt => component/ScrollArrow.kt} (59%) create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/login/component/SocialLoginButton.kt delete mode 100644 app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailAutoLoginCheckBox.kt delete mode 100644 app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginJoinButton.kt delete mode 100644 app/src/main/java/kr/co/lion/modigm/ui/login/social/component/GithubLoginButton.kt delete mode 100644 app/src/main/java/kr/co/lion/modigm/ui/login/social/component/KakaoLoginButton.kt delete mode 100644 app/src/main/java/kr/co/lion/modigm/ui/login/social/component/NavigateToEmailLoginButton.kt delete mode 100644 app/src/main/java/kr/co/lion/modigm/ui/login/social/component/SocialLoginLoading.kt create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/login/util/Util.kt diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailTextField.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/component/EmailTextField.kt similarity index 72% rename from app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailTextField.kt rename to app/src/main/java/kr/co/lion/modigm/ui/login/component/EmailTextField.kt index 231f5020..b50af032 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailTextField.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/component/EmailTextField.kt @@ -1,6 +1,5 @@ -package kr.co.lion.modigm.ui.login.email.component +package kr.co.lion.modigm.ui.login.component -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Clear @@ -8,7 +7,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Text import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -20,24 +18,26 @@ import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat import kr.co.lion.modigm.R -import kr.co.lion.modigm.ui.login.email.dpToSp +import kr.co.lion.modigm.ui.login.util.dpToSp @Composable fun EmailTextField( modifier: Modifier = Modifier, - email: String, - onEmailChange: (String) -> Unit + userEmail: String, + onValueChange: (String) -> Unit, + placeholder: @Composable () -> Unit, ) { val isError by remember { mutableStateOf(false) } val pointColor = Color(ContextCompat.getColor(LocalContext.current, R.color.pointColor)) OutlinedTextField( - value = email, - onValueChange = { onEmailChange(it) }, - modifier = modifier.fillMaxWidth(), + modifier = modifier, + value = userEmail, + onValueChange = { onValueChange(userEmail) }, textStyle = LocalTextStyle.current.copy(fontSize = dpToSp(16.dp)), colors = TextFieldDefaults.colors( focusedTextColor = Color.Black, @@ -48,29 +48,40 @@ fun EmailTextField( unfocusedIndicatorColor = Color.Black, errorContainerColor = Color.White, ), - placeholder = { Text(text = "이메일") }, + placeholder = placeholder, leadingIcon = { Icon( painter = painterResource(id = R.drawable.icon_mail_24px), - contentDescription = "이메일" + contentDescription = "이메일 리딩 아이콘" ) }, trailingIcon = { - if (email.isNotEmpty()) { - IconButton(onClick = { onEmailChange("") }) { + if (userEmail.isNotEmpty()) { + IconButton( + onClick = { onValueChange("") } + ) { Icon( imageVector = Icons.Default.Clear, - contentDescription = "이메일 초기화" + contentDescription = "이메일 초기화 아이콘" ) } } }, keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.Email, - imeAction = ImeAction.Next + imeAction = ImeAction.Next, ), singleLine = true, isError = isError, ) +} +@Preview(showBackground = true) +@Composable +fun EmailTextFieldPreview() { + EmailTextField( + userEmail = "", + onValueChange = {}, + placeholder = {} + ) } \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginLoading.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/component/LoginLoading.kt similarity index 85% rename from app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginLoading.kt rename to app/src/main/java/kr/co/lion/modigm/ui/login/component/LoginLoading.kt index a4dd5a25..81342f7d 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginLoading.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/component/LoginLoading.kt @@ -1,4 +1,4 @@ -package kr.co.lion.modigm.ui.login.email.component +package kr.co.lion.modigm.ui.login.component import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -11,7 +11,7 @@ import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.tooling.preview.Preview @Composable -fun EmailLoginLoading( +fun LoginLoading( modifier: Modifier = Modifier, isLoading: Boolean ) { @@ -29,6 +29,6 @@ fun EmailLoginLoading( @Preview(showBackground = true) @Composable -fun EmailLoginLoadingPreview() { - EmailLoginLoading(isLoading = true) +fun LoginLoadingPreview() { + LoginLoading(isLoading = true) } \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/PasswordTextFeild.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/component/PasswordTextFeild.kt similarity index 88% rename from app/src/main/java/kr/co/lion/modigm/ui/login/email/component/PasswordTextFeild.kt rename to app/src/main/java/kr/co/lion/modigm/ui/login/component/PasswordTextFeild.kt index 5192760a..cf4d91c0 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/PasswordTextFeild.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/component/PasswordTextFeild.kt @@ -1,4 +1,4 @@ -package kr.co.lion.modigm.ui.login.email.component +package kr.co.lion.modigm.ui.login.component import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons @@ -8,7 +8,6 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.OutlinedTextField -import androidx.compose.material3.Text import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -26,22 +25,23 @@ import androidx.compose.ui.text.input.VisualTransformation import androidx.compose.ui.unit.dp import androidx.core.content.ContextCompat import kr.co.lion.modigm.R -import kr.co.lion.modigm.ui.login.email.dpToSp +import kr.co.lion.modigm.ui.login.util.dpToSp @Composable fun PasswordTextField( modifier: Modifier = Modifier, - password: String, - onPasswordChange: (String) -> Unit + userPassword: String, + onValueChange: (String) -> Unit, + placeholder: @Composable () -> Unit, ) { var isPasswordVisible by remember { mutableStateOf(false) } val pointColor = Color(ContextCompat.getColor(LocalContext.current, R.color.pointColor)) OutlinedTextField( modifier = modifier, - value = password, + value = userPassword, onValueChange = { - onPasswordChange(it) + onValueChange(it) isPasswordVisible = it.isEmpty() }, textStyle = LocalTextStyle.current.copy(fontSize = dpToSp(16.dp)), @@ -54,11 +54,11 @@ fun PasswordTextField( unfocusedIndicatorColor = Color.Black, errorContainerColor = Color.White, ), - placeholder = { Text(text = "비밀번호") }, + placeholder = placeholder, leadingIcon = { Icon( painter = painterResource(id = R.drawable.icon_key_24px), - contentDescription = "비밀번호 아이콘" + contentDescription = "비밀번호" ) }, trailingIcon = { diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginScrollArrow.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/component/ScrollArrow.kt similarity index 59% rename from app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginScrollArrow.kt rename to app/src/main/java/kr/co/lion/modigm/ui/login/component/ScrollArrow.kt index be090dac..dbc0fd74 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginScrollArrow.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/component/ScrollArrow.kt @@ -1,22 +1,15 @@ -package kr.co.lion.modigm.ui.login.email.component +package kr.co.lion.modigm.ui.login.component -import androidx.compose.animation.core.LinearEasing -import androidx.compose.animation.core.RepeatMode -import androidx.compose.animation.core.animateFloat -import androidx.compose.animation.core.infiniteRepeatable -import androidx.compose.animation.core.rememberInfiniteTransition -import androidx.compose.animation.core.tween import androidx.compose.foundation.Image import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.size import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope @@ -26,15 +19,15 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import kotlinx.coroutines.launch import kr.co.lion.modigm.R +import kr.co.lion.modigm.ui.login.util.animateEnterExit @Composable -fun EmailLoginScrollArrow( - modifier: Modifier = Modifier, +fun ScrollArrow( scrollState: ScrollState, + modifier: Modifier = Modifier ) { val isVisible = remember { mutableStateOf(false) } val coroutineScope = rememberCoroutineScope() @@ -48,9 +41,10 @@ fun EmailLoginScrollArrow( Box( modifier = modifier + .fillMaxWidth() .height(50.dp) .background(Color.Transparent), - contentAlignment = Alignment.TopCenter + contentAlignment = Alignment.Center ) { if (isVisible.value) { Image( @@ -64,31 +58,8 @@ fun EmailLoginScrollArrow( } } .animateEnterExit(), - colorFilter = ColorFilter.tint(Color.Black) + colorFilter = ColorFilter.tint(Color.White) ) } } -} - -@Composable -fun Modifier.animateEnterExit(): Modifier { - val infiniteTransition = rememberInfiniteTransition(label = "") - val offsetY by infiniteTransition.animateFloat( - initialValue = 0f, - targetValue = 10f, - animationSpec = infiniteRepeatable( - animation = tween(durationMillis = 800, easing = LinearEasing), - repeatMode = RepeatMode.Reverse - ), - label = "" - ) - return this.offset(y = offsetY.dp) -} - -@Preview(showBackground = true) -@Composable -fun EmailLoginScrollArrowPreview() { - EmailLoginScrollArrow( - scrollState = ScrollState(0), - ) } \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/component/SocialLoginButton.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/component/SocialLoginButton.kt new file mode 100644 index 00000000..22557f51 --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/component/SocialLoginButton.kt @@ -0,0 +1,38 @@ +package kr.co.lion.modigm.ui.login.component + +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonColors +import androidx.compose.material3.ButtonDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp + +@Composable +fun SocialLoginButton( + modifier: Modifier = Modifier, + onClick: () -> Unit, + colors: ButtonColors, + content: @Composable () -> Unit +) { + Button( + onClick = onClick, + modifier = modifier, + colors = colors, + contentPadding = PaddingValues(0.dp), + shape = RoundedCornerShape(8.dp), + content = { content() } + ) +} + +@Preview(showBackground = true) +@Composable +fun SocialLoginButtonPreview() { + SocialLoginButton( + onClick = {}, + colors = ButtonDefaults.buttonColors(), + content = {} + ) +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt index f95766bb..a11c8c2a 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt @@ -11,6 +11,7 @@ import android.view.inputmethod.EditorInfo import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Modifier @@ -46,24 +47,33 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma val emailLoginResult by viewModel.emailLoginResult.observeAsState(false) val emailLoginError by viewModel.emailLoginError.observeAsState() + + LaunchedEffect(emailLoginResult) { + if (emailLoginResult) { + navigateToBottomNaviFragment(JoinType.EMAIL) + } + } + + LaunchedEffect(emailLoginError) { + emailLoginError?.let { error -> + showLoginErrorDialog(error.message.toString()) + } + } + Scaffold( modifier = Modifier.fillMaxSize(), ) { innerPadding -> EmailLoginScreen( modifier = Modifier.padding(innerPadding), isLoading = isLoading, - emailLoginResult = emailLoginResult, - emailLoginError = emailLoginError, - onNavigateToBottomNaviFragment = { joinType -> navigateToBottomNaviFragment(joinType) }, - onNavigateToFindEmailFragment = { navigateToFindEmailFragment() }, - onNavigateToFindPasswordFragment = { navigateToFindPasswordFragment() }, - onNavigateToJoinFragment = { joinType -> navigateToJoinFragment(joinType) }, + onFindEmailButtonClick = { navigateToFindEmailFragment() }, + onFindPasswordButtonClick = { navigateToFindPasswordFragment() }, onEmailLoginButtonClick = { email, password, autoLoginValue -> requireActivity().hideSoftInput() viewModel.emailLogin(email, password, autoLoginValue) }, - onNavigateToSocialLoginFragment = { navigateToSocialLoginFragment() }, - showLoginErrorDialog = { error -> showErrorDialog(error) }, + onBackButtonClick = { navigateToSocialLoginFragment() }, + onJoinButtonClick = { joinType -> navigateToJoinFragment(joinType) }, ) } } diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt index 96cf0691..b3799a6a 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt @@ -1,5 +1,6 @@ package kr.co.lion.modigm.ui.login.email +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -17,71 +18,51 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.verticalScroll import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults.buttonColors +import androidx.compose.material3.Checkbox import androidx.compose.material3.Icon import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource 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.dp import androidx.compose.ui.unit.sp import androidx.core.content.ContextCompat import kr.co.lion.modigm.R -import kr.co.lion.modigm.ui.login.email.component.EmailAutoLoginCheckBox -import kr.co.lion.modigm.ui.login.email.component.EmailLoginJoinButton -import kr.co.lion.modigm.ui.login.email.component.EmailLoginLoading -import kr.co.lion.modigm.ui.login.email.component.EmailLoginScrollArrow -import kr.co.lion.modigm.ui.login.email.component.EmailTextField -import kr.co.lion.modigm.ui.login.email.component.PasswordTextField -import kr.co.lion.modigm.ui.login.social.dpToSp +import kr.co.lion.modigm.ui.login.component.EmailTextField +import kr.co.lion.modigm.ui.login.component.LoginLoading +import kr.co.lion.modigm.ui.login.component.PasswordTextField +import kr.co.lion.modigm.ui.login.component.ScrollArrow +import kr.co.lion.modigm.ui.login.util.dpToSp import kr.co.lion.modigm.util.JoinType @Composable fun EmailLoginScreen( modifier: Modifier = Modifier, isLoading: Boolean, - emailLoginResult: Boolean, - emailLoginError: Throwable?, - onNavigateToBottomNaviFragment: (JoinType) -> Unit, - onNavigateToFindEmailFragment: () -> Unit, - onNavigateToFindPasswordFragment: () -> Unit, + onFindEmailButtonClick: () -> Unit, + onFindPasswordButtonClick: () -> Unit, onEmailLoginButtonClick: (String, String, Boolean) -> Unit, - onNavigateToSocialLoginFragment: () -> Unit, - onNavigateToJoinFragment: (JoinType) -> Unit, - showLoginErrorDialog: (Throwable) -> Unit, + onBackButtonClick: () -> Unit, + onJoinButtonClick: (JoinType) -> Unit, ) { val scrollState = rememberScrollState() - val email = remember { mutableStateOf("") } - val password = remember { mutableStateOf("") } + val userEmail = remember { mutableStateOf("") } + val userPassword = remember { mutableStateOf("") } val isChecked = remember { mutableStateOf(false) } - val isLoginButtonEnabled = remember(email.value, password.value) { - email.value.isNotBlank() && password.value.isNotBlank() - } - - LaunchedEffect(emailLoginResult) { - if (emailLoginResult) { - onNavigateToBottomNaviFragment(JoinType.EMAIL) - } - } - - LaunchedEffect(emailLoginError) { - emailLoginError?.let { error -> - showLoginErrorDialog(error) - } + val isLoginButtonEnabled = remember(userEmail.value, userPassword.value) { + userEmail.value.isNotBlank() && userPassword.value.isNotBlank() } Box( @@ -120,16 +101,18 @@ fun EmailLoginScreen( modifier = Modifier .fillMaxWidth() .padding(top = 30.dp), - email = email.value, - onEmailChange = { email.value = it } + userEmail = userEmail.value, + onValueChange = { userEmail.value = it }, + placeholder = { Text(text = "이메일") }, ) PasswordTextField( modifier = Modifier .fillMaxWidth() .padding(top = 20.dp), - password = password.value, - onPasswordChange = { password.value = it } + userPassword = userPassword.value, + onValueChange = { userPassword.value = it }, + placeholder = { Text(text = "비밀번호") }, ) Row( @@ -139,10 +122,28 @@ fun EmailLoginScreen( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween ) { - EmailAutoLoginCheckBox( - modifier = Modifier, - isChecked = isChecked - ) + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically + ) { + Checkbox( + checked = isChecked.value, + onCheckedChange = { isChecked.value = it }, + modifier = Modifier + .size(20.dp) + .padding(top = 0.dp, bottom = 0.dp, start = 0.dp, end = 0.dp) + ) + Spacer( + modifier = Modifier + .size(8.dp) + .clickable { isChecked.value = !isChecked.value } + ) + Text( + text = "자동 로그인", + fontSize = dpToSp(16.dp), + modifier = Modifier.clickable { isChecked.value = !isChecked.value } + ) + } Row( modifier = Modifier, @@ -150,7 +151,7 @@ fun EmailLoginScreen( ) { TextButton( modifier = Modifier.padding(start = 0.dp, end = 15.dp), - onClick = { onNavigateToFindEmailFragment() }, + onClick = { onFindEmailButtonClick() }, contentPadding = PaddingValues(0.dp), ) { Text( @@ -168,7 +169,7 @@ fun EmailLoginScreen( TextButton( modifier = Modifier .padding(start = 15.dp, end = 0.dp), - onClick = { onNavigateToFindPasswordFragment() }, + onClick = { onFindPasswordButtonClick() }, contentPadding = PaddingValues(0.dp) ) { Text( @@ -186,8 +187,8 @@ fun EmailLoginScreen( .padding(top = 10.dp), onClick = { onEmailLoginButtonClick( - email.value, - password.value, + userEmail.value, + userPassword.value, isChecked.value ) }, @@ -209,7 +210,7 @@ fun EmailLoginScreen( ) { TextButton( modifier = Modifier, - onClick = { onNavigateToSocialLoginFragment() }, + onClick = { onBackButtonClick() }, contentPadding = PaddingValues(0.dp) ) { Icon( @@ -224,21 +225,47 @@ fun EmailLoginScreen( color = Color.Black ) } - EmailLoginJoinButton( - modifier = Modifier, - onNavigateToJoinFragment = onNavigateToJoinFragment - ) + Row( + modifier = modifier, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + modifier = Modifier + .padding(end = 4.dp), + text = "계정이 없으신가요?", + fontSize = dpToSp(16.dp), + color = Color.Black + ) + TextButton( + modifier = Modifier, + onClick = { onJoinButtonClick(JoinType.EMAIL) }, + contentPadding = PaddingValues(0.dp) + ) { + Icon( + modifier = Modifier.padding(end = 4.dp), + painter = painterResource(R.drawable.icon_person_add_24px), + contentDescription = "회원가입", + tint = Color.Black + ) + Text( + modifier = Modifier, + text = "회원가입", + fontSize = dpToSp(16.dp), + color = Color.Black + ) + } + } } } - EmailLoginScrollArrow( + ScrollArrow( modifier = Modifier .fillMaxWidth() .align(Alignment.BottomCenter), scrollState = scrollState, ) - EmailLoginLoading( + LoginLoading( modifier = Modifier .fillMaxSize(), isLoading = isLoading @@ -251,18 +278,11 @@ fun EmailLoginScreen( fun EmailLoginScreenPreview() { EmailLoginScreen( isLoading = false, - emailLoginResult = false, - emailLoginError = null, - onNavigateToBottomNaviFragment = {}, - showLoginErrorDialog = {}, - onNavigateToFindEmailFragment = {}, - onNavigateToFindPasswordFragment = {}, + onFindEmailButtonClick = {}, + onFindPasswordButtonClick = {}, onEmailLoginButtonClick = { email, password, autoLoginValue -> println("email: $email, password: $password, autoLoginValue: $autoLoginValue") }, - onNavigateToSocialLoginFragment = {}, - onNavigateToJoinFragment = {}, + onBackButtonClick = {}, + onJoinButtonClick = {}, ) } -@Composable -fun dpToSp(dp: Dp) = with(LocalDensity.current) { dp.toSp() } - diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailAutoLoginCheckBox.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailAutoLoginCheckBox.kt deleted file mode 100644 index b85ecb89..00000000 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailAutoLoginCheckBox.kt +++ /dev/null @@ -1,44 +0,0 @@ -package kr.co.lion.modigm.ui.login.email.component - -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.size -import androidx.compose.material3.Checkbox -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import kr.co.lion.modigm.ui.login.email.dpToSp - -@Composable -fun EmailAutoLoginCheckBox( - modifier: Modifier = Modifier, - isChecked: MutableState, -) { - Row( - modifier = modifier, - verticalAlignment = Alignment.CenterVertically - ) { - Checkbox( - checked = isChecked.value, - onCheckedChange = { isChecked.value = it }, - modifier = Modifier - .size(20.dp) - .padding(top = 0.dp, bottom = 0.dp, start = 0.dp, end = 0.dp) - ) - Spacer( - modifier = Modifier - .size(8.dp) - .clickable { isChecked.value = !isChecked.value } - ) - Text( - text = "자동 로그인", - fontSize = dpToSp(16.dp), - modifier = Modifier.clickable { isChecked.value = !isChecked.value } - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginJoinButton.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginJoinButton.kt deleted file mode 100644 index 0d1efd5c..00000000 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/component/EmailLoginJoinButton.kt +++ /dev/null @@ -1,63 +0,0 @@ -package kr.co.lion.modigm.ui.login.email.component - -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import kr.co.lion.modigm.R -import kr.co.lion.modigm.ui.login.email.dpToSp -import kr.co.lion.modigm.util.JoinType - -@Composable -fun EmailLoginJoinButton( - modifier: Modifier = Modifier, - onNavigateToJoinFragment: (JoinType) -> Unit -) { - Row( - modifier = modifier, - verticalAlignment = Alignment.CenterVertically - ) { - Text( - modifier = Modifier - .padding(end = 4.dp), - text = "계정이 없으신가요?", - fontSize = dpToSp(16.dp), - color = Color.Black - ) - TextButton( - modifier = Modifier, - onClick = { onNavigateToJoinFragment(JoinType.EMAIL) }, - contentPadding = PaddingValues(0.dp) - ) { - Icon( - modifier = Modifier.padding(end = 4.dp), - painter = painterResource(R.drawable.icon_person_add_24px), - contentDescription = "회원가입", - tint = Color.Black - ) - Text( - modifier = Modifier, - text = "회원가입", - fontSize = dpToSp(16.dp), - color = Color.Black - ) - } - } -} - -@Preview(showBackground = true) -@Composable -fun EmailLoginJoinButtonPreview() { - EmailLoginJoinButton( - onNavigateToJoinFragment = {} - ) -} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginFragment.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginFragment.kt index a70f038e..a26608f2 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginFragment.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginFragment.kt @@ -5,6 +5,7 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.activity.OnBackPressedCallback +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.platform.ComposeView @@ -49,23 +50,59 @@ class SocialLoginFragment : Fragment() { val githubLoginError by viewModel.githubLoginError.observeAsState() val autoLoginError by viewModel.autoLoginError.observeAsState() + LaunchedEffect(kakaoLoginResult) { + if (kakaoLoginResult) { + navigateToBottomNaviFragment(JoinType.KAKAO) + } + } + + LaunchedEffect(githubLoginResult) { + if (githubLoginResult) { + navigateToBottomNaviFragment(JoinType.GITHUB) + } + } + + LaunchedEffect(kakaoJoinResult) { + if (kakaoJoinResult) { + navigateToJoinFragment(JoinType.KAKAO) + } + } + + LaunchedEffect(githubJoinResult) { + if (githubJoinResult) { + navigateToJoinFragment(JoinType.GITHUB) + } + } + + LaunchedEffect(emailLoginResult) { + if (emailLoginResult) { + navigateToBottomNaviFragment(JoinType.EMAIL) + } + } + + LaunchedEffect(kakaoLoginError) { + kakaoLoginError?.let { error -> + showLoginErrorDialog(error) + } + } + + LaunchedEffect(githubLoginError) { + githubLoginError?.let { error -> + showLoginErrorDialog(error) + } + } + + LaunchedEffect(autoLoginError) { + autoLoginError?.let { error -> + requireActivity().showLoginSnackBar(error.message.toString(), null) + } + } + SocialLoginScreen( isLoading = isLoading, - kakaoLoginResult = kakaoLoginResult, - githubLoginResult = githubLoginResult, - kakaoJoinResult = kakaoJoinResult, - githubJoinResult = githubJoinResult, - emailLoginResult = emailLoginResult, - kakaoLoginError = kakaoLoginError, - githubLoginError = githubLoginError, - autoLoginError = autoLoginError, onKakaoLoginClick = { viewModel.kakaoLogin(requireContext()) }, onGithubLoginClick = { viewModel.githubLogin(requireActivity()) }, onNavigateEmailLoginClick = { navigateToEmailLoginFragment() }, - onNavigateToJoinFragment = { joinType -> navigateToJoinFragment(joinType) }, - onNavigateToBottomNaviFragment = { joinType -> navigateToBottomNaviFragment(joinType) }, - showLoginErrorDialog = { error -> showLoginErrorDialog(error) }, - showSnackBar = { message -> requireActivity().showLoginSnackBar(message, null) } ) } } diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginScreen.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginScreen.kt index fab612c2..597feb97 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginScreen.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/social/SocialLoginScreen.kt @@ -1,15 +1,7 @@ package kr.co.lion.modigm.ui.login.social -import androidx.compose.animation.core.LinearEasing -import androidx.compose.animation.core.RepeatMode -import androidx.compose.animation.core.animateFloat -import androidx.compose.animation.core.infiniteRepeatable -import androidx.compose.animation.core.rememberInfiniteTransition -import androidx.compose.animation.core.tween import androidx.compose.foundation.Image -import androidx.compose.foundation.ScrollState import androidx.compose.foundation.background -import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxSize @@ -21,244 +13,196 @@ import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.Icon import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.runtime.Composable -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.rememberCoroutineScope -import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.blur import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.ColorFilter import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource 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.unit.Dp +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import kotlinx.coroutines.launch import kr.co.lion.modigm.R -import kr.co.lion.modigm.ui.login.social.component.GithubLoginButton -import kr.co.lion.modigm.ui.login.social.component.KakaoLoginButton -import kr.co.lion.modigm.ui.login.social.component.NavigateToEmailLoginButton -import kr.co.lion.modigm.ui.login.social.component.SocialLoginLoading -import kr.co.lion.modigm.util.JoinType +import kr.co.lion.modigm.ui.login.component.LoginLoading +import kr.co.lion.modigm.ui.login.component.ScrollArrow +import kr.co.lion.modigm.ui.login.component.SocialLoginButton +import kr.co.lion.modigm.ui.login.util.dpToSp @Composable fun SocialLoginScreen( isLoading: Boolean, - kakaoLoginResult: Boolean, - githubLoginResult: Boolean, - kakaoJoinResult: Boolean, - githubJoinResult: Boolean, - emailLoginResult: Boolean, - kakaoLoginError: Throwable?, - githubLoginError: Throwable?, - autoLoginError: Throwable?, onKakaoLoginClick: () -> Unit, onGithubLoginClick: () -> Unit, onNavigateEmailLoginClick: () -> Unit, - onNavigateToJoinFragment: (JoinType) -> Unit, - onNavigateToBottomNaviFragment: (JoinType) -> Unit, - showLoginErrorDialog: (Throwable) -> Unit, - showSnackBar: (String) -> Unit -) { - val scrollState = rememberScrollState() - - LaunchedEffect(kakaoLoginResult) { - if (kakaoLoginResult) { - onNavigateToBottomNaviFragment(JoinType.KAKAO) - } - } - - LaunchedEffect(githubLoginResult) { - if (githubLoginResult) { - onNavigateToBottomNaviFragment(JoinType.GITHUB) - } - } - - LaunchedEffect(kakaoJoinResult) { - if (kakaoJoinResult) { - onNavigateToJoinFragment(JoinType.KAKAO) - } - } - - LaunchedEffect(githubJoinResult) { - if (githubJoinResult) { - onNavigateToJoinFragment(JoinType.GITHUB) - } - } - - LaunchedEffect(emailLoginResult) { - if (emailLoginResult) { - onNavigateToBottomNaviFragment(JoinType.EMAIL) - } - } - - LaunchedEffect(kakaoLoginError) { - kakaoLoginError?.let { error -> - showLoginErrorDialog(error) - } - } - - LaunchedEffect(githubLoginError) { - githubLoginError?.let { error -> - showLoginErrorDialog(error) - } - } - LaunchedEffect(autoLoginError) { - autoLoginError?.let { error -> - showSnackBar(error.message.toString()) - } - } + ) { + val scrollState = rememberScrollState() Box( modifier = Modifier.fillMaxSize() ) { - BackgroundImage() + Image( + painter = painterResource(id = R.drawable.background_login2), + contentDescription = "Social Login Background", + modifier = Modifier + .fillMaxSize() + .blur(5.dp), + contentScale = ContentScale.Crop + ) + Box( + modifier = Modifier + .fillMaxSize() + .background(Color.Black.copy(alpha = 0.4f)) + ) Column( modifier = Modifier .verticalScroll(scrollState) .fillMaxSize() .padding(30.dp) ) { - LogoImage() - SocialLoginTitle() - SocialLoginSubTitle() - KakaoLoginButton(onClick = onKakaoLoginClick) - GithubLoginButton(onClick = onGithubLoginClick) - NavigateToEmailLoginButton(onClick = onNavigateEmailLoginClick) - } - SocialLoginScrollArrow( - scrollState = scrollState, - modifier = Modifier.align(Alignment.BottomCenter) - ) - SocialLoginLoading(isLoading = isLoading) - } -} - -@Composable -fun BackgroundImage() { - Image( - painter = painterResource(id = R.drawable.background_login2), - contentDescription = "Social Login Background", - modifier = Modifier - .fillMaxSize() - .blur(5.dp), - contentScale = ContentScale.Crop - ) - Box( - modifier = Modifier - .fillMaxSize() - .background(Color.Black.copy(alpha = 0.4f)) - ) -} - -@Composable -fun LogoImage() { - Image( - painter = painterResource(id = R.drawable.logo_modigm), - contentDescription = "Login Logo", - modifier = Modifier - .fillMaxSize() - .padding(top = 100.dp) - .size(150.dp, 150.dp), - alignment = Alignment.Center - ) -} - -@Composable -fun SocialLoginTitle() { - Text( - text = "모우다임", - modifier = Modifier - .wrapContentWidth() - .wrapContentHeight() - .padding(top = 60.dp), - fontFamily = FontFamily(Font(R.font.one_mobile_pop_otf)), - fontSize = dpToSp(70.dp), - color = Color.White, - fontWeight = FontWeight.Normal - ) -} - -@Composable -fun SocialLoginSubTitle() { - Text( - text = "개발자 스터디의 새로운 패러다임", - modifier = Modifier - .wrapContentWidth() - .wrapContentHeight() - .padding(top = 10.dp), - fontFamily = FontFamily(Font(R.font.one_mobile_pop_otf)), - fontSize = dpToSp(22.dp), - color = Color.White, - fontWeight = FontWeight.Normal - ) -} - -@Composable -fun SocialLoginScrollArrow( - scrollState: ScrollState, - modifier: Modifier = Modifier -) { - val isVisible = remember { mutableStateOf(false) } - val coroutineScope = rememberCoroutineScope() - - LaunchedEffect(scrollState) { - snapshotFlow { scrollState.canScrollForward } - .collect { canScrollForward -> - isVisible.value = canScrollForward - } - } - - Box( - modifier = modifier - .fillMaxWidth() - .height(50.dp) - .background(Color.Transparent), - contentAlignment = Alignment.Center - ) { - if (isVisible.value) { Image( - painter = painterResource(id = R.drawable.arrow_down_24px), - contentDescription = "Scroll Arrow", + painter = painterResource(id = R.drawable.logo_modigm), + contentDescription = "로그인 로고 이미지", + modifier = Modifier + .fillMaxSize() + .padding(top = 100.dp) + .size(150.dp, 150.dp), + alignment = Alignment.Center + ) + Text( + text = "모우다임", + modifier = Modifier + .wrapContentWidth() + .wrapContentHeight() + .padding(top = 60.dp), + fontFamily = FontFamily(Font(R.font.one_mobile_pop_otf)), + fontSize = dpToSp(70.dp), + color = Color.White, + fontWeight = FontWeight.Normal + ) + Text( + text = "개발자 스터디의 새로운 패러다임", + modifier = Modifier + .wrapContentWidth() + .wrapContentHeight() + .padding(top = 10.dp), + fontFamily = FontFamily(Font(R.font.one_mobile_pop_otf)), + fontSize = dpToSp(22.dp), + color = Color.White, + fontWeight = FontWeight.Normal + ) + + SocialLoginButton( modifier = Modifier - .size(24.dp) - .clickable { - coroutineScope.launch { - scrollState.animateScrollTo(scrollState.maxValue) - } + .fillMaxWidth() + .padding(top = 40.dp), + onClick = onKakaoLoginClick, + content = { + Image( + painter = painterResource(id = R.drawable.kakao_login_large_wide), + contentDescription = "카카오 로그인", + modifier = Modifier + .fillMaxWidth() + .height(48.dp), + contentScale = ContentScale.FillBounds + ) + }, + colors = ButtonDefaults.buttonColors(containerColor = Color.Transparent) + ) + SocialLoginButton( + modifier = Modifier + .fillMaxWidth() + .padding(top = 30.dp) + .height(48.dp), + onClick = onGithubLoginClick, + colors = ButtonDefaults.buttonColors( + containerColor = Color.Black, + contentColor = Color.White + ), + content = { + Box( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp) + ) { + Icon( + modifier = Modifier + .size(24.dp) + .align(Alignment.CenterStart), + painter = painterResource(id = R.drawable.icon_github_logo), + contentDescription = "깃허브 로고 아이콘", + tint = Color.White + ) + Text( + modifier = Modifier + .align(Alignment.Center) + .offset(x = 12.dp), + text = "깃허브 로그인", + fontSize = dpToSp(16.dp), + color = Color.White + ) } - .animateEnterExit(), - colorFilter = ColorFilter.tint(Color.White) + } ) + + Column( + modifier = Modifier + .fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally, + ) { + TextButton( + onClick = onNavigateEmailLoginClick, + modifier = Modifier + .wrapContentWidth() + .wrapContentHeight() + .padding(top = 20.dp), + colors = ButtonDefaults.textButtonColors( + contentColor = Color.White + ), + shape = RoundedCornerShape(0.dp), + ) { + Text( + text = "다른 방법으로 로그인", + fontSize = dpToSp(16.dp), + fontFamily = FontFamily(Font(R.font.one_mobile_pop_otf)), + fontWeight = FontWeight.Normal, + color = Color.White + ) + } + } } + + ScrollArrow( + scrollState = scrollState, + modifier = Modifier + .fillMaxWidth() + .align(Alignment.BottomCenter) + ) + + LoginLoading( + modifier = Modifier + .fillMaxSize(), + isLoading = isLoading + ) } } +@Preview(showBackground = true) @Composable -fun Modifier.animateEnterExit(): Modifier { - val infiniteTransition = rememberInfiniteTransition(label = "") - val offsetY by infiniteTransition.animateFloat( - initialValue = 0f, - targetValue = 10f, - animationSpec = infiniteRepeatable( - animation = tween(durationMillis = 800, easing = LinearEasing), - repeatMode = RepeatMode.Reverse - ), - label = "" +fun SocialLoginScreenPreview() { + SocialLoginScreen( + isLoading = false, + onKakaoLoginClick = {}, + onGithubLoginClick = {}, + onNavigateEmailLoginClick = {}, ) - return this.offset(y = offsetY.dp) -} - -@Composable -fun dpToSp(dp: Dp) = with(LocalDensity.current) { dp.toSp() } \ No newline at end of file +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/social/component/GithubLoginButton.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/social/component/GithubLoginButton.kt deleted file mode 100644 index 7df37d3f..00000000 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/social/component/GithubLoginButton.kt +++ /dev/null @@ -1,64 +0,0 @@ -package kr.co.lion.modigm.ui.login.social.component - -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.PaddingValues -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.size -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Icon -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.unit.dp -import kr.co.lion.modigm.R -import kr.co.lion.modigm.ui.login.social.dpToSp - -@Composable -fun GithubLoginButton(onClick: () -> Unit) { - Button( - modifier = Modifier - .fillMaxWidth() - .padding(top = 30.dp) - .height(48.dp), - onClick = onClick, - colors = ButtonDefaults.buttonColors( - containerColor = Color.Black, - contentColor = Color.White - ), - shape = RoundedCornerShape(8.dp), - contentPadding = PaddingValues(0.dp), - ) { - Box( - modifier = Modifier - .fillMaxSize() - .padding(horizontal = 16.dp) - ) { - Icon( - modifier = Modifier - .size(24.dp) - .align(Alignment.CenterStart), - painter = painterResource(id = R.drawable.icon_github_logo), - contentDescription = "깃허브 로고", - tint = Color.White - ) - Text( - modifier = Modifier - .align(Alignment.Center) - .offset(x = 12.dp), - text = "깃허브 로그인", - fontSize = dpToSp(20.dp), - color = Color.White, - - ) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/social/component/KakaoLoginButton.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/social/component/KakaoLoginButton.kt deleted file mode 100644 index 688bc0ac..00000000 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/social/component/KakaoLoginButton.kt +++ /dev/null @@ -1,42 +0,0 @@ -package kr.co.lion.modigm.ui.login.social.component - -import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Button -import androidx.compose.material3.ButtonDefaults -import androidx.compose.runtime.Composable -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.ContentScale -import androidx.compose.ui.res.painterResource -import androidx.compose.ui.unit.dp -import kr.co.lion.modigm.R - -@Composable -fun KakaoLoginButton( - modifier: Modifier = Modifier, - onClick: () -> Unit -) { - Button( - onClick = onClick, - modifier = modifier - .fillMaxWidth() - .padding(top = 40.dp), - colors = ButtonDefaults.buttonColors(containerColor = Color.Transparent), - contentPadding = PaddingValues(0.dp), - shape = RoundedCornerShape(0.dp), - ) { - Image( - painter = painterResource(id = R.drawable.kakao_login_large_wide), - contentDescription = "카카오 로그인", - modifier = modifier - .fillMaxWidth() - .height(48.dp), - contentScale = ContentScale.FillBounds - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/social/component/NavigateToEmailLoginButton.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/social/component/NavigateToEmailLoginButton.kt deleted file mode 100644 index dc0a9a91..00000000 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/social/component/NavigateToEmailLoginButton.kt +++ /dev/null @@ -1,52 +0,0 @@ -package kr.co.lion.modigm.ui.login.social.component - -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.foundation.layout.wrapContentHeight -import androidx.compose.foundation.layout.wrapContentWidth -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.ButtonDefaults -import androidx.compose.material3.Text -import androidx.compose.material3.TextButton -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -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.unit.dp -import kr.co.lion.modigm.R -import kr.co.lion.modigm.ui.login.social.dpToSp - -@Composable -fun NavigateToEmailLoginButton( - modifier: Modifier = Modifier, - onClick: () -> Unit) { - Column( - modifier = modifier - .fillMaxSize(), - horizontalAlignment = Alignment.CenterHorizontally, - ) { - TextButton( - onClick = onClick, - modifier = modifier - .wrapContentWidth() - .wrapContentHeight() - .padding(top = 20.dp), - colors = ButtonDefaults.textButtonColors( - contentColor = Color.White - ), - shape = RoundedCornerShape(0.dp), - ) { - Text( - text = "다른 방법으로 로그인", - fontSize = dpToSp(16.dp), - fontFamily = FontFamily(Font(R.font.one_mobile_pop_otf)), - fontWeight = FontWeight.Normal, - color = Color.White - ) - } - } -} diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/social/component/SocialLoginLoading.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/social/component/SocialLoginLoading.kt deleted file mode 100644 index dc3862b5..00000000 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/social/component/SocialLoginLoading.kt +++ /dev/null @@ -1,29 +0,0 @@ -package kr.co.lion.modigm.ui.login.social.component - -import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.material3.CircularProgressIndicator -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.input.pointer.pointerInput - -@Composable -fun SocialLoginLoading( - modifier: Modifier = Modifier, - isLoading: Boolean -) { - if (isLoading) { - Box( - modifier = modifier - .fillMaxSize() - .background(Color.Black.copy(alpha = 0.5f)) - .pointerInput(Unit) { }, - contentAlignment = Alignment.Center - ) { - CircularProgressIndicator(color = Color.White) - } - } -} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/util/Util.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/util/Util.kt new file mode 100644 index 00000000..eee13aef --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/util/Util.kt @@ -0,0 +1,34 @@ +package kr.co.lion.modigm.ui.login.util + +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.RepeatMode +import androidx.compose.animation.core.animateFloat +import androidx.compose.animation.core.infiniteRepeatable +import androidx.compose.animation.core.rememberInfiniteTransition +import androidx.compose.animation.core.tween +import androidx.compose.foundation.layout.offset +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +@Composable +fun dpToSp(dp: Dp) = with(LocalDensity.current) { dp.toSp() } + +@Composable +fun Modifier.animateEnterExit(): Modifier { + val infiniteTransition = rememberInfiniteTransition(label = "") + val offsetY by infiniteTransition.animateFloat( + initialValue = 0f, + targetValue = 10f, + animationSpec = infiniteRepeatable( + animation = tween(durationMillis = 800, easing = LinearEasing), + repeatMode = RepeatMode.Reverse + ), + label = "" + ) + return this.offset(y = offsetY.dp) +} + From da626a806b96f8477092d709921973674d30d649 Mon Sep 17 00:00:00 2001 From: language7606 Date: Sun, 23 Mar 2025 00:19:01 +0900 Subject: [PATCH 24/28] =?UTF-8?q?[Refactor]=20=EB=B7=B0=EB=B0=94=EC=9D=B8?= =?UTF-8?q?=EB=94=A9=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F?= =?UTF-8?q?=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=20=EA=B4=80=EB=A0=A8=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=BB=B4=ED=8F=AC=EC=A6=88=EB=A1=9C=20=EC=A0=84?= =?UTF-8?q?=ED=99=98=201.=20=EB=B7=B0=EB=B0=94=EC=9D=B8=EB=94=A9=20?= =?UTF-8?q?=EC=82=AD=EC=A0=9C=202.=20=EB=B7=B0=EB=AA=A8=EB=8D=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20EmailLoginScreen=20=EC=9C=BC=EB=A1=9C=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/login/email/EmailLoginFragment.kt | 251 +----------------- 1 file changed, 11 insertions(+), 240 deletions(-) diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt index a11c8c2a..909a2a21 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginFragment.kt @@ -1,40 +1,25 @@ package kr.co.lion.modigm.ui.login.email import android.os.Bundle -import android.text.Editable -import android.text.TextWatcher -import android.util.Patterns import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.view.inputmethod.EditorInfo import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold -import androidx.compose.runtime.LaunchedEffect -import androidx.compose.runtime.getValue -import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Modifier import androidx.compose.ui.platform.ComposeView +import androidx.fragment.app.Fragment import androidx.fragment.app.commit -import androidx.fragment.app.viewModels import kr.co.lion.modigm.R -import kr.co.lion.modigm.databinding.FragmentEmailLoginBinding -import kr.co.lion.modigm.ui.VBBaseFragment import kr.co.lion.modigm.ui.join.JoinFragment -import kr.co.lion.modigm.ui.login.CustomLoginErrorDialog import kr.co.lion.modigm.ui.login.FindEmailFragment import kr.co.lion.modigm.ui.login.FindPasswordFragment import kr.co.lion.modigm.ui.study.BottomNaviFragment import kr.co.lion.modigm.util.FragmentName import kr.co.lion.modigm.util.JoinType -import kr.co.lion.modigm.util.hideSoftInput -import kr.co.lion.modigm.util.shake -import kr.co.lion.modigm.util.showSoftInput -class EmailLoginFragment : VBBaseFragment(FragmentEmailLoginBinding::inflate) { - - private val viewModel: EmailLoginViewModel by viewModels() +class EmailLoginFragment : Fragment() { override fun onCreateView( inflater: LayoutInflater, @@ -43,135 +28,23 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma ): View { return ComposeView(requireContext()).apply { setContent { - val isLoading by viewModel.isLoading.observeAsState(false) - val emailLoginResult by viewModel.emailLoginResult.observeAsState(false) - val emailLoginError by viewModel.emailLoginError.observeAsState() - - - LaunchedEffect(emailLoginResult) { - if (emailLoginResult) { - navigateToBottomNaviFragment(JoinType.EMAIL) - } - } - - LaunchedEffect(emailLoginError) { - emailLoginError?.let { error -> - showLoginErrorDialog(error.message.toString()) - } - } Scaffold( modifier = Modifier.fillMaxSize(), ) { innerPadding -> EmailLoginScreen( + navigateToBottomNavi = { navigateToBottomNaviFragment() }, + navigateToFindEmail = { navigateToFindEmailFragment() }, + navigateToFindPassword = { navigateToFindPasswordFragment() }, + navigateToSocialLogin = { navigateToSocialLoginFragment() }, + navigateToJoin = {navigateToJoinFragment() }, modifier = Modifier.padding(innerPadding), - isLoading = isLoading, - onFindEmailButtonClick = { navigateToFindEmailFragment() }, - onFindPasswordButtonClick = { navigateToFindPasswordFragment() }, - onEmailLoginButtonClick = { email, password, autoLoginValue -> - requireActivity().hideSoftInput() - viewModel.emailLogin(email, password, autoLoginValue) - }, - onBackButtonClick = { navigateToSocialLoginFragment() }, - onJoinButtonClick = { joinType -> navigateToJoinFragment(joinType) }, ) } } } } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - - initView() - } - - override fun onResume() { - super.onResume() - // 이메일 텍스트 필드 포커싱 및 소프트키보드 보여주기 - with(binding) { - requireActivity().showSoftInput(textInputEditOtherEmail) - } - } - - override fun onDestroyView() { - super.onDestroyView() - - viewModel.clearViewModelData() // ViewModel 데이터 초기화 - } - - private fun initView() { - with(binding){ - - // 실시간 텍스트 변경 감지 설정 - textInputEditOtherEmail.apply{ - // 텍스트 변경 시 - addTextChangedListener(inputWatcher) - - // 포커스 변경 시 - setOnFocusChangeListener { _, hasFocus -> - if (hasFocus) { - // 이메일 필드가 포커스를 받을 때 해당 필드로 스크롤 - otherLoginScrollView.smoothScrollTo(0, textViewOtherTitle.top) - requireActivity().showSoftInput(this) - } else { - // 이메일 필드가 포커스를 잃으면 키보드 숨기기 - requireActivity().hideSoftInput() - } - } - } - // 비밀번호 입력 - textInputEditOtherPassword.apply { - // 텍스트 변경 시 - addTextChangedListener(inputWatcher) - // 에디터 액션 설정 - setOnEditorActionListener { _, actionId, _ -> - // 엔터키 입력 시 로그인 시도 - if (actionId == EditorInfo.IME_ACTION_DONE) { - buttonOtherLogin.performClick() // 로그인 버튼 클릭 - true - } else { - false - } - } - // 포커스 변경 시 - setOnFocusChangeListener { _, hasFocus -> - if (hasFocus) { - // 비밀번호 필드가 포커스를 받을 때 해당 필드로 스크롤 - otherLoginScrollView.smoothScrollTo(0, textViewOtherSecondTitle.top) - requireActivity().showSoftInput(this) - } - if(!hasFocus){ - // 비밀번호 필드가 포커스를 잃으면 키보드 숨기기 - requireActivity().hideSoftInput() - } - } - } - - // 로그인 버튼 클릭 시 로그인 시도 - buttonOtherLogin.setOnClickListener { - if(!checkAllInput()) { - return@setOnClickListener - } - requireActivity().hideSoftInput() - - val email = textInputEditOtherEmail.text.toString() - val password = textInputEditOtherPassword.text.toString() - val autoLogin = checkBoxOtherAutoLogin.isChecked - viewModel.emailLogin(email, password, autoLogin) - } - } - } - - private fun showErrorDialog(e: Throwable) { - val message = if (e.message != null) { - e.message.toString() - } else { - "알 수 없는 오류!\n코드번호: 9999" - } - showLoginErrorDialog(message) - } - private fun navigateToFindEmailFragment() { parentFragmentManager.commit { replace(R.id.containerMain, FindEmailFragment()) @@ -190,10 +63,9 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma parentFragmentManager.popBackStack() } - // 회원가입 화면으로 이동하는 메소드 - private fun navigateToJoinFragment(joinType: JoinType) { + private fun navigateToJoinFragment() { val bundle = Bundle().apply { - putString("joinType", joinType.provider) + putString("joinType", JoinType.EMAIL.provider) } parentFragmentManager.commit { replace(R.id.containerMain, JoinFragment().apply { arguments = bundle }) @@ -201,114 +73,13 @@ class EmailLoginFragment : VBBaseFragment(FragmentEma } } - private fun navigateToBottomNaviFragment(joinType: JoinType) { + private fun navigateToBottomNaviFragment() { val bundle = Bundle().apply { - putString("joinType", joinType.provider) + putString("joinType", JoinType.EMAIL.provider) } parentFragmentManager.commit { replace(R.id.containerMain, BottomNaviFragment().apply { arguments = bundle }) addToBackStack(FragmentName.BOTTOM_NAVI.str) } } - - // 오류 다이얼로그 표시 - private fun showLoginErrorDialog(message: String) { - // 다이얼로그 생성 - val dialog = CustomLoginErrorDialog(requireContext()) - with(dialog){ - // 다이얼로그 제목 - setTitle("오류") - // 다이얼로그 메시지 - setMessage(message) - // 확인 버튼 - setPositiveButton("확인") { - // 확인 버튼 클릭 시 다이얼로그 닫기 - dismiss() - } - // 다이얼로그 표시 - show() - } - - } - - // 유효성 검사 - private fun checkAllInput(): Boolean { - return checkEmail() && checkPassword() - } - - private fun checkEmail(): Boolean { - with(binding){ - // 에러 메시지를 설정하고 포커스와 흔들기 동작을 수행하는 함수 - fun showError(message: String) { - textInputLayoutOtherEmail.error = message - textInputEditOtherEmail.requestFocus() - textInputEditOtherEmail.shake() - } - return when { - textInputEditOtherEmail.text.toString().isEmpty() -> { - showError("이메일을 입력해주세요.") - false - } - !isEmailValid(textInputEditOtherEmail.text.toString()) -> { - showError("올바른 이메일을 입력해주세요.") - false - } - else -> { - textInputLayoutOtherEmail.error = null - true - } - } - } - } - - private fun checkPassword(): Boolean { - with(binding) { - - // 에러 메시지를 설정하고 포커스와 흔들기 동작을 수행하는 함수 - fun showError(message: String) { - textInputLayoutOtherPassword.error = message - textInputEditOtherPassword.requestFocus() - textInputEditOtherPassword.shake() - } - return when { - textInputEditOtherPassword.text.toString().isEmpty() -> { - showError("비밀번호를 입력해주세요.") - false - } - !isPasswordValid(textInputEditOtherPassword.text.toString()) -> { - showError("올바른 비밀번호를 입력해주세요.") - false - } - else -> { - textInputLayoutOtherPassword.error = null - true - } - } - } - } - - // 이메일 유효성을 검사하는 함수 - private fun isEmailValid(email: String): Boolean { - return Patterns.EMAIL_ADDRESS.matcher(email).matches() - } - - - // 비밀번호 유효성을 검사하는 함수 - private fun isPasswordValid(password: String): Boolean { - return password.length >= 6 - } - - // 유효성 검사 및 버튼 활성화/비활성화 업데이트 - private val inputWatcher = object : TextWatcher { - override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} - - override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { - with(binding){ - buttonOtherLogin.isEnabled = - !textInputEditOtherEmail.text.isNullOrEmpty() && !textInputEditOtherPassword.text.isNullOrEmpty() - } - } - override fun afterTextChanged(p0: Editable?) { - } - } } From 35c1981bddb4a339aca5cdc128c8f8a95349820d Mon Sep 17 00:00:00 2001 From: language7606 Date: Sun, 23 Mar 2025 00:23:05 +0900 Subject: [PATCH 25/28] =?UTF-8?q?[Refactor]=20EamilLoginScreen=EC=97=90=20?= =?UTF-8?q?ViewModel=20=EC=A0=81=EC=9A=A9=201.=20LiveData=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20Flow=EB=A1=9C=20=EC=A0=84=ED=99=98=202.=20=EC=9C=A0?= =?UTF-8?q?=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=20=EB=B0=8F=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=EC=BD=94=EB=93=9C=EB=93=A4=20ViewModel=EC=97=90?= =?UTF-8?q?=EC=84=9C=20=EA=B4=80=EB=A6=AC=203.=20=EC=97=90=EB=9F=AC?= =?UTF-8?q?=EB=A9=94=EC=84=B8=EC=A7=80=20=EB=B0=8F=20=EB=8B=A4=EC=9D=B4?= =?UTF-8?q?=EC=96=BC=EB=A1=9C=EA=B7=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/login/component/EmailTextField.kt | 20 +- .../ui/login/component/ErrorAlertDialog.kt | 35 ++++ .../ui/login/component/PasswordTextFeild.kt | 15 +- .../modigm/ui/login/email/EmailLoginScreen.kt | 176 ++++++++++++++---- .../ui/login/email/EmailLoginViewModel.kt | 114 +++++++++--- 5 files changed, 293 insertions(+), 67 deletions(-) create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/login/component/ErrorAlertDialog.kt diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/component/EmailTextField.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/component/EmailTextField.kt index b50af032..4b834f7d 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/component/EmailTextField.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/component/EmailTextField.kt @@ -1,5 +1,6 @@ package kr.co.lion.modigm.ui.login.component +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Clear @@ -7,11 +8,9 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext @@ -30,14 +29,15 @@ fun EmailTextField( userEmail: String, onValueChange: (String) -> Unit, placeholder: @Composable () -> Unit, + errorMessage: String = "" ) { - val isError by remember { mutableStateOf(false) } + val isEmailInputError = errorMessage != "" val pointColor = Color(ContextCompat.getColor(LocalContext.current, R.color.pointColor)) OutlinedTextField( modifier = modifier, value = userEmail, - onValueChange = { onValueChange(userEmail) }, + onValueChange = { onValueChange(it) }, textStyle = LocalTextStyle.current.copy(fontSize = dpToSp(16.dp)), colors = TextFieldDefaults.colors( focusedTextColor = Color.Black, @@ -72,8 +72,16 @@ fun EmailTextField( imeAction = ImeAction.Next, ), singleLine = true, - isError = isError, + isError = isEmailInputError, ) + if (isEmailInputError) { + Text( + text = errorMessage, + color = Color.Red, + fontSize = dpToSp(12.dp), + modifier = Modifier.padding(start = 16.dp, top = 4.dp) + ) + } } @Preview(showBackground = true) diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/component/ErrorAlertDialog.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/component/ErrorAlertDialog.kt new file mode 100644 index 00000000..a3c66f52 --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/component/ErrorAlertDialog.kt @@ -0,0 +1,35 @@ +package kr.co.lion.modigm.ui.login.component + +import androidx.compose.material3.AlertDialog +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable + +@Composable +fun ErrorAlertDialog( + title: String, + message: String, + confirmButtonText: String = "확인", + onConfirmClick: () -> Unit, + onDismissRequest: () -> Unit = {}, + dismissOnBackPress: Boolean = false, + dismissOnClickOutside: Boolean = false, +) { + AlertDialog( + onDismissRequest = { + if (dismissOnBackPress || dismissOnClickOutside) { + onDismissRequest() + } + }, + title = { Text(text = title) }, + text = { Text(text = message) }, + confirmButton = { + TextButton(onClick = { + onConfirmClick() + onDismissRequest() + }) { + Text(text = confirmButtonText) + } + } + ) +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/component/PasswordTextFeild.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/component/PasswordTextFeild.kt index cf4d91c0..e7ffd043 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/component/PasswordTextFeild.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/component/PasswordTextFeild.kt @@ -1,5 +1,6 @@ package kr.co.lion.modigm.ui.login.component +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Visibility @@ -8,6 +9,7 @@ import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.LocalTextStyle import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -33,9 +35,11 @@ fun PasswordTextField( userPassword: String, onValueChange: (String) -> Unit, placeholder: @Composable () -> Unit, + errorMessage: String = "" ) { var isPasswordVisible by remember { mutableStateOf(false) } val pointColor = Color(ContextCompat.getColor(LocalContext.current, R.color.pointColor)) + val isPasswordInputError = errorMessage != "" OutlinedTextField( modifier = modifier, @@ -74,7 +78,16 @@ fun PasswordTextField( keyboardType = KeyboardType.Password, imeAction = ImeAction.Done ), - singleLine = true + singleLine = true, + isError = isPasswordInputError, ) + if (isPasswordInputError) { + Text( + text = errorMessage, + color = Color.Red, + fontSize = dpToSp(12.dp), + modifier = Modifier.padding(start = 16.dp, top = 4.dp) + ) + } } \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt index b3799a6a..c76cac66 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt @@ -1,6 +1,7 @@ package kr.co.lion.modigm.ui.login.email import androidx.compose.foundation.clickable +import androidx.compose.foundation.focusable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -24,12 +25,18 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.VerticalDivider import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.collectAsState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalSoftwareKeyboardController import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontFamily @@ -38,33 +45,113 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.core.content.ContextCompat +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.LifecycleEventObserver +import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.lifecycle.viewmodel.compose.viewModel import kr.co.lion.modigm.R import kr.co.lion.modigm.ui.login.component.EmailTextField +import kr.co.lion.modigm.ui.login.component.ErrorAlertDialog import kr.co.lion.modigm.ui.login.component.LoginLoading import kr.co.lion.modigm.ui.login.component.PasswordTextField import kr.co.lion.modigm.ui.login.component.ScrollArrow import kr.co.lion.modigm.ui.login.util.dpToSp -import kr.co.lion.modigm.util.JoinType @Composable fun EmailLoginScreen( + navigateToBottomNavi: () -> Unit, + navigateToFindEmail: () -> Unit, + navigateToFindPassword: () -> Unit, + navigateToSocialLogin: () -> Unit, + navigateToJoin: () -> Unit, modifier: Modifier = Modifier, - isLoading: Boolean, - onFindEmailButtonClick: () -> Unit, - onFindPasswordButtonClick: () -> Unit, - onEmailLoginButtonClick: (String, String, Boolean) -> Unit, - onBackButtonClick: () -> Unit, - onJoinButtonClick: (JoinType) -> Unit, + viewModel: EmailLoginViewModel = viewModel() ) { - val scrollState = rememberScrollState() - val userEmail = remember { mutableStateOf("") } - val userPassword = remember { mutableStateOf("") } - val isChecked = remember { mutableStateOf(false) } + val userEmail = viewModel.userEmail.collectAsState() + val userPassword = viewModel.userPassword.collectAsState() + val isChecked = viewModel.isChecked.collectAsState() + val isLoginButtonEnabled = viewModel.isLoginEnabled.collectAsState() + val isLoading = viewModel.isLoading.collectAsState() + + val emailLoginResult = viewModel.emailLoginResult.collectAsState() + + LaunchedEffect(emailLoginResult) { + if (emailLoginResult.value) { + navigateToBottomNavi() + } + } + + val emailLoginError = viewModel.emailLoginError.collectAsState() + val showDialog = remember { mutableStateOf(false) } + val dialogMessage = remember { mutableStateOf("") } + + LaunchedEffect(emailLoginError.value) { + emailLoginError.value?.let { error -> + dialogMessage.value = error.message ?: "알 수 없는 오류입니다." + showDialog.value = true + } + } + if (showDialog.value) { + ErrorAlertDialog( + title = "오류", + message = dialogMessage.value, + confirmButtonText = "확인", + onConfirmClick = { + showDialog.value = false + viewModel.clearLoginError() + }, + onDismissRequest = { + showDialog.value = false + viewModel.clearLoginError() + } + ) + } + + val lifecycleOwner = LocalLifecycleOwner.current + val keyboardController = LocalSoftwareKeyboardController.current + val emailFocusRequester = remember { FocusRequester() } + val passwordFocusRequester = remember { FocusRequester() } + + DisposableEffect(lifecycleOwner) { + val observer = LifecycleEventObserver { _, event -> + if (event == Lifecycle.Event.ON_RESUME) { + emailFocusRequester.requestFocus() + keyboardController?.show() + } + } + lifecycleOwner.lifecycle.addObserver(observer) + onDispose { + lifecycleOwner.lifecycle.removeObserver(observer) + viewModel.clearViewModelData() + } + } + val emailInputError = viewModel.emailInputError.collectAsState() + val passwordInputError = viewModel.passwordInputError.collectAsState() + + LaunchedEffect(emailInputError.value, passwordInputError.value) { + when { + emailInputError.value != "" -> { + emailFocusRequester.requestFocus() + keyboardController?.show() + } + passwordInputError.value != "" -> { + passwordFocusRequester.requestFocus() + keyboardController?.show() + } + } + } + val focusField = viewModel.focusField.collectAsState() - val isLoginButtonEnabled = remember(userEmail.value, userPassword.value) { - userEmail.value.isNotBlank() && userPassword.value.isNotBlank() + LaunchedEffect(focusField.value) { + when (focusField.value) { + FocusTarget.EMAIL_INPUT -> emailFocusRequester.requestFocus() + FocusTarget.PASSWORD_INPUT -> passwordFocusRequester.requestFocus() + null -> Unit + } } + val scrollState = rememberScrollState() + Box( modifier = modifier.fillMaxSize(), ) { @@ -100,19 +187,31 @@ fun EmailLoginScreen( EmailTextField( modifier = Modifier .fillMaxWidth() - .padding(top = 30.dp), + .padding(top = 30.dp) + .focusRequester(emailFocusRequester) + .focusable(), userEmail = userEmail.value, - onValueChange = { userEmail.value = it }, + onValueChange = { + viewModel.onUserEmailChange(it) + viewModel.clearAllInputError() + }, placeholder = { Text(text = "이메일") }, + errorMessage = emailInputError.value, ) PasswordTextField( modifier = Modifier .fillMaxWidth() - .padding(top = 20.dp), + .padding(top = 20.dp) + .focusRequester(passwordFocusRequester) + .focusable(), userPassword = userPassword.value, - onValueChange = { userPassword.value = it }, + onValueChange = { + viewModel.onUserPasswordChange(it) + viewModel.clearAllInputError() + }, placeholder = { Text(text = "비밀번호") }, + errorMessage = passwordInputError.value, ) Row( @@ -128,7 +227,7 @@ fun EmailLoginScreen( ) { Checkbox( checked = isChecked.value, - onCheckedChange = { isChecked.value = it }, + onCheckedChange = { viewModel.onCheckedChange(it) }, modifier = Modifier .size(20.dp) .padding(top = 0.dp, bottom = 0.dp, start = 0.dp, end = 0.dp) @@ -136,12 +235,12 @@ fun EmailLoginScreen( Spacer( modifier = Modifier .size(8.dp) - .clickable { isChecked.value = !isChecked.value } + .clickable { viewModel.onCheckedChange(!isChecked.value) } ) Text( text = "자동 로그인", fontSize = dpToSp(16.dp), - modifier = Modifier.clickable { isChecked.value = !isChecked.value } + modifier = Modifier.clickable { viewModel.onCheckedChange(!isChecked.value) } ) } @@ -151,7 +250,7 @@ fun EmailLoginScreen( ) { TextButton( modifier = Modifier.padding(start = 0.dp, end = 15.dp), - onClick = { onFindEmailButtonClick() }, + onClick = { navigateToFindEmail() }, contentPadding = PaddingValues(0.dp), ) { Text( @@ -169,7 +268,7 @@ fun EmailLoginScreen( TextButton( modifier = Modifier .padding(start = 15.dp, end = 0.dp), - onClick = { onFindPasswordButtonClick() }, + onClick = { navigateToFindPassword() }, contentPadding = PaddingValues(0.dp) ) { Text( @@ -186,14 +285,18 @@ fun EmailLoginScreen( .fillMaxWidth() .padding(top = 10.dp), onClick = { - onEmailLoginButtonClick( - userEmail.value, - userPassword.value, - isChecked.value - ) + keyboardController?.hide() + + if(viewModel.checkAllInputAndSetError()) { + viewModel.emailLogin( + userEmail = userEmail.value, + userPassword = userPassword.value, + autoLoginValue = isChecked.value + ) + } }, colors = buttonColors(Color(ContextCompat.getColor(LocalContext.current, R.color.pointColor))), - enabled = isLoginButtonEnabled + enabled = isLoginButtonEnabled.value ) { Text( text = "로그인", @@ -210,7 +313,7 @@ fun EmailLoginScreen( ) { TextButton( modifier = Modifier, - onClick = { onBackButtonClick() }, + onClick = { navigateToSocialLogin() }, contentPadding = PaddingValues(0.dp) ) { Icon( @@ -238,7 +341,7 @@ fun EmailLoginScreen( ) TextButton( modifier = Modifier, - onClick = { onJoinButtonClick(JoinType.EMAIL) }, + onClick = { navigateToJoin() }, contentPadding = PaddingValues(0.dp) ) { Icon( @@ -268,7 +371,7 @@ fun EmailLoginScreen( LoginLoading( modifier = Modifier .fillMaxSize(), - isLoading = isLoading + isLoading = isLoading.value ) } } @@ -277,12 +380,11 @@ fun EmailLoginScreen( @Composable fun EmailLoginScreenPreview() { EmailLoginScreen( - isLoading = false, - onFindEmailButtonClick = {}, - onFindPasswordButtonClick = {}, - onEmailLoginButtonClick = { email, password, autoLoginValue -> println("email: $email, password: $password, autoLoginValue: $autoLoginValue") }, - onBackButtonClick = {}, - onJoinButtonClick = {}, + navigateToBottomNavi = {}, + navigateToFindEmail = {}, + navigateToFindPassword = {}, + navigateToSocialLogin = {}, + navigateToJoin = {}, ) } diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginViewModel.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginViewModel.kt index 03b3eb08..31b0d489 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginViewModel.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginViewModel.kt @@ -1,8 +1,7 @@ package kr.co.lion.modigm.ui.login.email import android.util.Log -import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData +import android.util.Patterns import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.google.firebase.FirebaseNetworkException @@ -10,6 +9,11 @@ import com.google.firebase.FirebaseTooManyRequestsException import com.google.firebase.auth.FirebaseAuthInvalidCredentialsException import com.google.firebase.auth.FirebaseAuthInvalidUserException import com.google.firebase.messaging.FirebaseMessaging +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.launch import kr.co.lion.modigm.repository.LoginRepository import kr.co.lion.modigm.util.JoinType @@ -19,23 +23,83 @@ class EmailLoginViewModel: ViewModel() { private val loginRepository by lazy { LoginRepository() } - private val _isLoading = MutableLiveData(false) - val isLoading : LiveData = _isLoading + private val _userEmail = MutableStateFlow("") + val userEmail: StateFlow = _userEmail - // 이메일 로그인 - private val _emailLoginResult = MutableLiveData() - val emailLoginResult: LiveData = _emailLoginResult + fun onUserEmailChange(newEmail: String) { + _userEmail.value = newEmail + } + + private val _userPassword = MutableStateFlow("") + val userPassword: StateFlow = _userPassword + + fun onUserPasswordChange(newPassword: String) { + _userPassword.value = newPassword + } + + private val _isChecked = MutableStateFlow(false) + val isChecked: StateFlow = _isChecked + + fun onCheckedChange(isChecked: Boolean) { + _isChecked.value = isChecked + } + + val isLoginEnabled: StateFlow = combine(_userEmail, _userPassword) { email, password -> + email.isNotBlank() && password.isNotBlank() + }.stateIn(viewModelScope, SharingStarted.Eagerly, false) + + private val _isLoading = MutableStateFlow(false) + val isLoading: StateFlow = _isLoading + + private val _emailLoginResult = MutableStateFlow(false) + val emailLoginResult: StateFlow = _emailLoginResult + + private val _emailLoginError = MutableStateFlow(null) + val emailLoginError: StateFlow = _emailLoginError + + fun clearLoginError() { + _emailLoginError.value = null + } + + private val _emailInputError = MutableStateFlow("") + val emailInputError: StateFlow = _emailInputError - private val _emailLoginError = MutableLiveData() - val emailLoginError: LiveData = _emailLoginError + private val _passwordInputError = MutableStateFlow("") + val passwordInputError: StateFlow = _passwordInputError - fun emailLogin(email: String, password: String, autoLoginValue: Boolean) { + fun clearAllInputError() { + _emailInputError.value = "" + _passwordInputError.value = "" + } + fun checkAllInputAndSetError(): Boolean { + _emailInputError.value = if (!isEmailValid()) "올바른 이메일 형식이 아닙니다." else "" + _passwordInputError.value = if (!isPasswordValid()) "비밀번호는 6자 이상이어야 합니다." else "" + + _focusField.value = when { + !isEmailValid() -> FocusTarget.EMAIL_INPUT + !isPasswordValid() -> FocusTarget.PASSWORD_INPUT + else -> null + } + + return isEmailValid() && isPasswordValid() + } + private fun isEmailValid(): Boolean { + return Patterns.EMAIL_ADDRESS.matcher(_userEmail.value).matches() + } + private fun isPasswordValid(): Boolean { + return _userPassword.value.length >= 6 + } + + private val _focusField = MutableStateFlow(null) + val focusField: StateFlow = _focusField + + fun emailLogin(userEmail: String, userPassword: String, autoLoginValue: Boolean) { _isLoading.value = true - _emailLoginResult.postValue(false) + _emailLoginResult.value = false viewModelScope.launch { - val result = loginRepository.emailLogin(email, password) + val result = loginRepository.emailLogin(userEmail, userPassword) result.onSuccess { userIdx -> - _isLoading.postValue(false) + _isLoading.value = false prefs.setBoolean( key = "autoLogin", value = autoLoginValue @@ -48,14 +112,13 @@ class EmailLoginViewModel: ViewModel() { key = "currentUserIdx", value = userIdx ) - _emailLoginResult.postValue(true) - - // 로그인 성공 후 즉시 FCM 토큰 등록 registerFcmTokenToServer(userIdx) + _emailLoginResult.value = true + }.onFailure { e -> - _isLoading.postValue(false) + _isLoading.value = false prefs.clearAllPrefs() - _emailLoginResult.postValue(false) + _emailLoginResult.value = false // 예외 처리 및 사용자에게 전달할 메시지 설정 val errorMessage = when (e) { is FirebaseAuthInvalidCredentialsException -> { @@ -78,16 +141,18 @@ class EmailLoginViewModel: ViewModel() { "로그인 중 오류가 발생했습니다. 잠시 후 다시 시도해 주세요." } } - _emailLoginError.postValue(Exception(errorMessage, e)) + _emailLoginError.value = Throwable(errorMessage, e) } } } // 뷰모델 데이터 초기화 fun clearViewModelData() { - _isLoading.postValue(false) - _emailLoginResult.postValue(false) - _emailLoginError.postValue(null) + _isLoading.value = false + _emailLoginResult.value = false + _emailLoginError.value = null + _emailInputError.value = "" + _passwordInputError.value = "" } private fun registerFcmTokenToServer(userIdx: Int) { @@ -114,4 +179,7 @@ class EmailLoginViewModel: ViewModel() { } } } -} \ No newline at end of file +} + + +enum class FocusTarget { EMAIL_INPUT, PASSWORD_INPUT } \ No newline at end of file From 91800153da1e81204b962818c8584e797bf71c2f Mon Sep 17 00:00:00 2001 From: jusungwon Date: Mon, 24 Mar 2025 19:48:59 +0900 Subject: [PATCH 26/28] =?UTF-8?q?[Refactor]=20AnimateEnterExit=20=EC=99=80?= =?UTF-8?q?=20DpToSp=20=ED=8C=8C=EC=9D=BC=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../modigm/ui/login/util/{Util.kt => AnimateEnterExit.kt} | 5 ----- .../main/java/kr/co/lion/modigm/ui/login/util/DpToSp.kt | 8 ++++++++ 2 files changed, 8 insertions(+), 5 deletions(-) rename app/src/main/java/kr/co/lion/modigm/ui/login/util/{Util.kt => AnimateEnterExit.kt} (86%) create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/login/util/DpToSp.kt diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/util/Util.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/util/AnimateEnterExit.kt similarity index 86% rename from app/src/main/java/kr/co/lion/modigm/ui/login/util/Util.kt rename to app/src/main/java/kr/co/lion/modigm/ui/login/util/AnimateEnterExit.kt index eee13aef..b71c06af 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/util/Util.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/util/AnimateEnterExit.kt @@ -10,13 +10,8 @@ import androidx.compose.foundation.layout.offset import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier -import androidx.compose.ui.platform.LocalDensity -import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp -@Composable -fun dpToSp(dp: Dp) = with(LocalDensity.current) { dp.toSp() } - @Composable fun Modifier.animateEnterExit(): Modifier { val infiniteTransition = rememberInfiniteTransition(label = "") diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/util/DpToSp.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/util/DpToSp.kt new file mode 100644 index 00000000..95eddbbf --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/util/DpToSp.kt @@ -0,0 +1,8 @@ +package kr.co.lion.modigm.ui.login.util + +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Dp + +@Composable +fun dpToSp(dp: Dp) = with(LocalDensity.current) { dp.toSp() } \ No newline at end of file From a37a02f28539191752d78adb98c2597d4d7be216 Mon Sep 17 00:00:00 2001 From: jusungwon Date: Mon, 24 Mar 2025 19:57:13 +0900 Subject: [PATCH 27/28] =?UTF-8?q?[Refactor]=20=EB=B0=B1=EA=B7=B8=EB=9D=BC?= =?UTF-8?q?=EC=9A=B4=EB=93=9C=20=EC=BB=AC=EB=9F=AC=20=ED=99=94=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20=EC=A0=81=EC=9A=A9=201.=20=EB=B3=B4=EB=9D=BC?= =?UTF-8?q?=EB=B9=9B=EC=9D=B4=20=EB=8F=84=EB=8A=94=20=EA=B8=B0=EB=B3=B8=20?= =?UTF-8?q?=EB=B0=B0=EA=B2=BD=EC=83=89=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt index c76cac66..8f1670b9 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/login/email/EmailLoginScreen.kt @@ -1,5 +1,6 @@ package kr.co.lion.modigm.ui.login.email +import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.focusable import androidx.compose.foundation.layout.Arrangement @@ -153,7 +154,9 @@ fun EmailLoginScreen( val scrollState = rememberScrollState() Box( - modifier = modifier.fillMaxSize(), + modifier = modifier + .fillMaxSize() + .background(Color.White), ) { Column( modifier = modifier From 8e908b222149bcc417ec48bc4835c8994ee75b54 Mon Sep 17 00:00:00 2001 From: jusungwon Date: Mon, 24 Mar 2025 21:41:56 +0900 Subject: [PATCH 28/28] =?UTF-8?q?[chore]=20=EC=9D=B4=EC=A0=84=20=ED=8C=80?= =?UTF-8?q?=EC=9B=90=EB=93=A4=20PR=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...0\353\212\245_\354\235\264\354\212\210.md" | 6 + ...4\352\267\270_\354\235\264\354\212\210.md" | 6 + app/build.gradle.kts | 5 + ...oinUserRepository.kt => JoinRepository.kt} | 2 +- .../java/kr/co/lion/modigm/ui/common/Theme.kt | 39 ++ .../modigm/ui/join/JoinDuplicateFragment.kt | 18 +- .../ui/join/JoinEmailVerificationFragment.kt | 26 +- .../ui/join/JoinEmailVerificationScreen.kt | 59 +++ .../kr/co/lion/modigm/ui/join/JoinFragment.kt | 424 ++++++++------- .../modigm/ui/join/JoinFragmentConstants.kt | 8 + .../ui/join/JoinStep1EmailAndPwFragment.kt | 32 ++ .../ui/join/JoinStep1EmailAndPwScreen.kt | 85 +++ .../lion/modigm/ui/join/JoinStep1Fragment.kt | 52 -- .../lion/modigm/ui/join/JoinStep2Fragment.kt | 145 ------ .../ui/join/JoinStep2NameAndPhoneFragment.kt | 61 +++ .../ui/join/JoinStep2NameAndPhoneScreen.kt | 131 +++++ .../lion/modigm/ui/join/JoinStep3Fragment.kt | 81 --- .../ui/join/JoinStep3InterestFragment.kt | 37 ++ .../modigm/ui/join/JoinStep3InterestScreen.kt | 92 ++++ .../ui/join/adapter/JoinViewPagerAdapter.kt | 4 +- .../ui/join/component/EmailTextField.kt | 90 ++++ .../modigm/ui/join/component/NameTextField.kt | 90 ++++ .../ui/join/component/PasswordTextField.kt | 103 ++++ .../ui/join/component/PhoneAuthTextField.kt | 90 ++++ .../ui/join/component/PhoneTextField.kt | 92 ++++ .../join/vm/JoinStep1EmailAndPwViewModel.kt | 132 +++++ .../modigm/ui/join/vm/JoinStep1ViewModel.kt | 118 ----- .../join/vm/JoinStep2NameAndPhoneViewModel.kt | 356 +++++++++++++ .../modigm/ui/join/vm/JoinStep2ViewModel.kt | 277 ---------- .../ui/join/vm/JoinStep3InterestViewModel.kt | 35 ++ .../modigm/ui/join/vm/JoinStep3ViewModel.kt | 35 -- .../lion/modigm/ui/join/vm/JoinViewModel.kt | 120 +++-- .../notification/NotificationErrorHandler.kt | 43 ++ .../ui/notification/NotificationFragment.kt | 205 +++++--- .../notification/vm/NotificationViewModel.kt | 6 +- .../lion/modigm/ui/profile/ProfileFragment.kt | 485 +++--------------- .../lion/modigm/ui/profile/ProfileScreen.kt | 323 ++++++++++++ .../lion/modigm/ui/profile/SettingsScreen.kt | 4 + .../popup/InterestBottomSheetFragment.kt | 2 +- .../fragment_join_email_verification.xml | 42 -- .../main/res/layout/fragment_join_step1.xml | 126 ----- .../main/res/layout/fragment_join_step2.xml | 155 ------ .../main/res/layout/fragment_join_step3.xml | 61 --- app/src/main/res/values/join_strings.xml | 33 ++ 44 files changed, 2474 insertions(+), 1862 deletions(-) rename app/src/main/java/kr/co/lion/modigm/repository/{JoinUserRepository.kt => JoinRepository.kt} (92%) create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/common/Theme.kt create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/join/JoinEmailVerificationScreen.kt create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/join/JoinFragmentConstants.kt create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep1EmailAndPwFragment.kt create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep1EmailAndPwScreen.kt delete mode 100644 app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep1Fragment.kt delete mode 100644 app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep2Fragment.kt create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep2NameAndPhoneFragment.kt create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep2NameAndPhoneScreen.kt delete mode 100644 app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep3Fragment.kt create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep3InterestFragment.kt create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep3InterestScreen.kt create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/join/component/EmailTextField.kt create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/join/component/NameTextField.kt create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/join/component/PasswordTextField.kt create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/join/component/PhoneAuthTextField.kt create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/join/component/PhoneTextField.kt create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinStep1EmailAndPwViewModel.kt delete mode 100644 app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinStep1ViewModel.kt create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinStep2NameAndPhoneViewModel.kt delete mode 100644 app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinStep2ViewModel.kt create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinStep3InterestViewModel.kt delete mode 100644 app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinStep3ViewModel.kt create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/notification/NotificationErrorHandler.kt create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/profile/ProfileScreen.kt create mode 100644 app/src/main/java/kr/co/lion/modigm/ui/profile/SettingsScreen.kt delete mode 100644 app/src/main/res/layout/fragment_join_email_verification.xml delete mode 100644 app/src/main/res/layout/fragment_join_step1.xml delete mode 100644 app/src/main/res/layout/fragment_join_step2.xml delete mode 100644 app/src/main/res/layout/fragment_join_step3.xml create mode 100644 app/src/main/res/values/join_strings.xml diff --git "a/.github/ISSUE_TEMPLATE/\352\270\260\353\212\245_\354\235\264\354\212\210.md" "b/.github/ISSUE_TEMPLATE/\352\270\260\353\212\245_\354\235\264\354\212\210.md" index d596185a..744ed41a 100644 --- "a/.github/ISSUE_TEMPLATE/\352\270\260\353\212\245_\354\235\264\354\212\210.md" +++ "b/.github/ISSUE_TEMPLATE/\352\270\260\353\212\245_\354\235\264\354\212\210.md" @@ -17,4 +17,10 @@ assignees: dmeiei, language7606, ums1212, zunione - [ ] TODO - [ ] TODO +## 이슈 발행 전 체크 리스트 + +- [ ] 발행자 확인 +- [ ] 프로젝트 설정 확인 +- [ ] 라벨 확인 + ## 참고할만한 자료(선택) diff --git "a/.github/ISSUE_TEMPLATE/\353\262\204\352\267\270_\354\235\264\354\212\210.md" "b/.github/ISSUE_TEMPLATE/\353\262\204\352\267\270_\354\235\264\354\212\210.md" index 86676daa..7617e594 100644 --- "a/.github/ISSUE_TEMPLATE/\353\262\204\352\267\270_\354\235\264\354\212\210.md" +++ "b/.github/ISSUE_TEMPLATE/\353\262\204\352\267\270_\354\235\264\354\212\210.md" @@ -19,4 +19,10 @@ assignees: dmeiei, language7606, ums1212, zunione > 예상했던 정상적인 결과가 어떤 것이었는지 설명해주세요 +## 이슈 발행 전 체크 리스트 + +- [ ] 발행자 확인 +- [ ] 프로젝트 설정 확인 +- [ ] 라벨 확인 + ## 참고할만한 자료(선택) diff --git a/app/build.gradle.kts b/app/build.gradle.kts index fe1dfc81..7d332e8c 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -152,6 +152,10 @@ dependencies { // Glide implementation("com.github.bumptech.glide:glide:4.16.0") implementation("jp.wasabeef:glide-transformations:4.3.0") + implementation("com.github.bumptech.glide:compose:1.0.0-alpha.1") + + // Coil: 버전 3은 androidx.core:core-ktx:1.15.0를 요구해 버전 2 사용 + implementation("io.coil-kt:coil-compose:2.7.0") // 원형 이미지 라이브러리 implementation("de.hdodenhof:circleimageview:3.1.0") @@ -192,6 +196,7 @@ dependencies { // lottie 애니메이션 implementation("com.airbnb.android:lottie:6.5.2") + implementation("com.airbnb.android:lottie-compose:6.5.2") // java.time 패키지를 사용하기 위한 ThreeTen 백포트 사용 implementation("com.jakewharton.threetenabp:threetenabp:1.3.0") diff --git a/app/src/main/java/kr/co/lion/modigm/repository/JoinUserRepository.kt b/app/src/main/java/kr/co/lion/modigm/repository/JoinRepository.kt similarity index 92% rename from app/src/main/java/kr/co/lion/modigm/repository/JoinUserRepository.kt rename to app/src/main/java/kr/co/lion/modigm/repository/JoinRepository.kt index 4833283a..c686c30d 100644 --- a/app/src/main/java/kr/co/lion/modigm/repository/JoinUserRepository.kt +++ b/app/src/main/java/kr/co/lion/modigm/repository/JoinRepository.kt @@ -5,7 +5,7 @@ import kr.co.lion.modigm.model.UserData import javax.inject.Inject -class JoinUserRepository @Inject constructor( +class JoinRepository @Inject constructor( private val _joinUserDataSource: RemoteJoinUserDataSource ) { diff --git a/app/src/main/java/kr/co/lion/modigm/ui/common/Theme.kt b/app/src/main/java/kr/co/lion/modigm/ui/common/Theme.kt new file mode 100644 index 00000000..a22fa3ec --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/common/Theme.kt @@ -0,0 +1,39 @@ +package kr.co.lion.modigm.ui.common + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.Color + +private val LightColorScheme = lightColorScheme( + primary = Color(0xFF6200EE), + onPrimary = Color.White, + secondary = Color(0xFF03DAC6), + onSecondary = Color.Black, + background = Color.White, + onBackground = Color.Black +) + +private val DarkColorScheme = darkColorScheme( + primary = Color(0xFFBB86FC), + onPrimary = Color.Black, + secondary = Color(0xFF03DAC6), + onSecondary = Color.Black, + background = Color.Black, + onBackground = Color.White +) + +@Composable +fun ModigmTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit +) { + val colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme + + MaterialTheme( + colorScheme = colorScheme, +// typography = Typography, // typography 설정 추가 가능 +// shapes = Shapes, // shapes 설정 추가 가능 + content = content + ) +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/join/JoinDuplicateFragment.kt b/app/src/main/java/kr/co/lion/modigm/ui/join/JoinDuplicateFragment.kt index 7a2a0bab..3d3e5047 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/join/JoinDuplicateFragment.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/join/JoinDuplicateFragment.kt @@ -15,9 +15,9 @@ import kotlinx.coroutines.launch import kr.co.lion.modigm.R import kr.co.lion.modigm.databinding.FragmentJoinDuplicateBinding import kr.co.lion.modigm.ui.VBBaseFragment -import kr.co.lion.modigm.ui.join.vm.JoinStep1ViewModel -import kr.co.lion.modigm.ui.join.vm.JoinStep2ViewModel -import kr.co.lion.modigm.ui.join.vm.JoinStep3ViewModel +import kr.co.lion.modigm.ui.join.vm.JoinStep1EmailAndPwViewModel +import kr.co.lion.modigm.ui.join.vm.JoinStep2NameAndPhoneViewModel +import kr.co.lion.modigm.ui.join.vm.JoinStep3InterestViewModel import kr.co.lion.modigm.ui.login.social.SocialLoginFragment import kr.co.lion.modigm.util.JoinType @@ -39,9 +39,9 @@ class JoinDuplicateFragment : VBBaseFragment(Fragm arguments?.getString("email") } - private val viewModelStep1: JoinStep1ViewModel by activityViewModels() - private val viewModelStep2: JoinStep2ViewModel by activityViewModels() - private val viewModelStep3: JoinStep3ViewModel by activityViewModels() + private val viewModelStep1: JoinStep1EmailAndPwViewModel by activityViewModels() + private val viewModelStep2: JoinStep2NameAndPhoneViewModel by activityViewModels() + private val viewModelStep3: JoinStep3InterestViewModel by activityViewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) @@ -81,9 +81,9 @@ class JoinDuplicateFragment : VBBaseFragment(Fragm // 로그인 화면으로 돌아가거나 추후 가능할 경우 sns로그인 api 연동 예정 binding.buttonJoinDupLogin.setOnClickListener { // 로그인 화면으로 돌아가면 입력값 초기화 및 계정(이메일,SNS) 삭제 - viewModelStep1.reset() - viewModelStep2.reset() - viewModelStep3.reset() + viewModelStep1.resetStep1States() + viewModelStep2.resetStep2States() + viewModelStep3.resetStep3States() CoroutineScope(Dispatchers.IO).launch { currentUser?.delete()?.addOnSuccessListener { diff --git a/app/src/main/java/kr/co/lion/modigm/ui/join/JoinEmailVerificationFragment.kt b/app/src/main/java/kr/co/lion/modigm/ui/join/JoinEmailVerificationFragment.kt index 3ab2c21c..fe26c330 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/join/JoinEmailVerificationFragment.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/join/JoinEmailVerificationFragment.kt @@ -1,6 +1,26 @@ package kr.co.lion.modigm.ui.join -import kr.co.lion.modigm.databinding.FragmentJoinEmailVerificationBinding -import kr.co.lion.modigm.ui.VBBaseFragment +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.fragment.app.Fragment -class JoinEmailVerificationFragment : VBBaseFragment(FragmentJoinEmailVerificationBinding::inflate) \ No newline at end of file +class JoinEmailVerificationFragment : Fragment(){ + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + + return ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + JoinEmailVerificationScreen() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/join/JoinEmailVerificationScreen.kt b/app/src/main/java/kr/co/lion/modigm/ui/join/JoinEmailVerificationScreen.kt new file mode 100644 index 00000000..d637787b --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/join/JoinEmailVerificationScreen.kt @@ -0,0 +1,59 @@ +package kr.co.lion.modigm.ui.join + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +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 com.airbnb.lottie.compose.LottieAnimation +import com.airbnb.lottie.compose.LottieCompositionSpec +import com.airbnb.lottie.compose.LottieConstants +import com.airbnb.lottie.compose.rememberLottieComposition +import kr.co.lion.modigm.R + +@Composable +fun JoinEmailVerificationScreen(){ + + val lottieComposition by rememberLottieComposition(LottieCompositionSpec.RawRes(R.raw.auth_email_send_animation)) + + Column( + modifier = Modifier + .fillMaxSize() + .padding(start = 16.dp, end = 16.dp, top = 40.dp) + ) { + + Text( + text = stringResource(R.string.JOIN_EMAIL_VERIFICATION_TITLE), + fontSize = 26.sp + ) + + Text( + text = stringResource(R.string.JOIN_EMAIL_VERIFICATION_DESCRIPTION), + modifier = Modifier.padding(top = 40.dp), + fontSize = 20.sp + ) + + LottieAnimation( + composition = lottieComposition, + modifier = Modifier + .size(200.dp) + .align(Alignment.CenterHorizontally), + isPlaying = true, + iterations = LottieConstants.IterateForever + ) + } +} + +@Preview(showBackground = true) +@Composable +fun PreviewJoinEmailVerificationScreen(){ + JoinEmailVerificationScreen() +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/join/JoinFragment.kt b/app/src/main/java/kr/co/lion/modigm/ui/join/JoinFragment.kt index e30e05d2..98b97955 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/join/JoinFragment.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/join/JoinFragment.kt @@ -1,6 +1,5 @@ package kr.co.lion.modigm.ui.join -import android.content.Context import android.os.Bundle import android.util.Log import android.util.TypedValue @@ -25,9 +24,9 @@ import kr.co.lion.modigm.R import kr.co.lion.modigm.databinding.FragmentJoinBinding import kr.co.lion.modigm.ui.DBBaseFragment import kr.co.lion.modigm.ui.join.adapter.JoinViewPagerAdapter -import kr.co.lion.modigm.ui.join.vm.JoinStep1ViewModel -import kr.co.lion.modigm.ui.join.vm.JoinStep2ViewModel -import kr.co.lion.modigm.ui.join.vm.JoinStep3ViewModel +import kr.co.lion.modigm.ui.join.vm.JoinStep1EmailAndPwViewModel +import kr.co.lion.modigm.ui.join.vm.JoinStep2NameAndPhoneViewModel +import kr.co.lion.modigm.ui.join.vm.JoinStep3InterestViewModel import kr.co.lion.modigm.ui.join.vm.JoinViewModel import kr.co.lion.modigm.ui.login.social.SocialLoginFragment import kr.co.lion.modigm.util.JoinType @@ -39,13 +38,65 @@ import kr.co.lion.modigm.util.setCurrentItemWithDuration @AndroidEntryPoint class JoinFragment : DBBaseFragment(R.layout.fragment_join) { - private val viewModel: JoinViewModel by viewModels() - private val viewModelStep1: JoinStep1ViewModel by activityViewModels() - private val viewModelStep2: JoinStep2ViewModel by activityViewModels() - private val viewModelStep3: JoinStep3ViewModel by activityViewModels() + private val joinViewModel: JoinViewModel by viewModels() + private val step1EmailAndPwViewModel: JoinStep1EmailAndPwViewModel by activityViewModels() + private val step2NameAndPhoneViewModel: JoinStep2NameAndPhoneViewModel by activityViewModels() + private val step3InterestViewModel: JoinStep3InterestViewModel by activityViewModels() + + companion object { + private const val VIEW_PAGER_INITIAL_PAGE = 0 + private const val VIEW_PAGER_MOVE_PAGE_COUNTER = 1 + private const val VIEW_PAGER_ANIMATE_DURATION = 300L + private const val VIEW_PAGER_MAX_PROGRESS_VALUE = 100 + private const val VIEW_PAGER_START_POSITION = 1 + private const val SNACK_BAR_TEXT_SIZE = 16f + } private val joinType: JoinType by lazy { - JoinType.getType(arguments?.getString("joinType")?:"") + JoinType.getType(arguments?.getString(BUNDLE_KEY_JOIN_TYPE)?:"") + } + + private val viewPagerAdapter by lazy { + JoinViewPagerAdapter(this).also { + // 뷰페이저에 보여줄 프래그먼트를 회원가입 유형에 따라 다르게 셋팅해준다. + when(joinType){ + // 이메일로 회원가입할 때 + JoinType.EMAIL -> { + it.setFragments( + listOf( + JoinStep1EmailAndPwFragment(), + JoinEmailVerificationFragment(), + JoinStep2NameAndPhoneFragment(), + JoinStep3InterestFragment() + ) + ) + } + // SNS계정으로 회원가입할 때 + else -> { + it.setFragments( + listOf( + JoinStep2NameAndPhoneFragment(), + JoinStep3InterestFragment() + ) + ) + } + } + } + } + + + private val showLoading = fun(){ + requireActivity().hideSoftInput() + binding.layoutLoadingJoin.visibility = View.VISIBLE + requireActivity().window?.setFlags( + WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, + WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE + ) + } + + private val hideLoading = fun(){ + binding.layoutLoadingJoin.visibility = View.GONE + requireActivity().window?.clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) } override fun onCreateView( @@ -54,11 +105,9 @@ class JoinFragment : DBBaseFragment(R.layout.fragment_join) ): View? { // Inflate the layout for this fragment super.onCreateView(inflater, container, savedInstanceState) - binding.viewModel = viewModel + binding.viewModel = joinViewModel settingValuesFromBundle() - settingToolBar() - settingCollector() /** * SNS계정은 자동로그인이 강제되기때문에 앱을 껏다 켜도 로그아웃을 직접 하지 않는 이상 회원가입 화면에 진입할 수 없음 @@ -68,45 +117,43 @@ class JoinFragment : DBBaseFragment(R.layout.fragment_join) * 안그러면 이미 로그인된 계정이 있을 때 회원가입 화면에서 나갈 때 계정이 파이어베이스 인증 등록에서 삭제될 수 있음 */ if(joinType==JoinType.EMAIL){ - viewModel.signOut() + joinViewModel.signOutCurrentFirebaseUser() } // sms 인증 코드 발송 시 보여줄 프로그래스바 익명함수를 viewModelStep2에 전달 - viewModelStep2.hideCallback.value = hideLoading - viewModelStep2.showCallback.value = showLoading + step2NameAndPhoneViewModel.setHideLoading(hideLoading) + step2NameAndPhoneViewModel.setShowLoading(showLoading) return binding.root } // 번들로 전달받은 값들을 뷰모델 라이브 데이터에 셋팅 private fun settingValuesFromBundle(){ - if(joinType != null){ - // 프로바이더 셋팅 - viewModel.setUserProvider(joinType?.provider?:"") - // SNS계정인경우 uid, email 셋팅 - if(joinType != JoinType.EMAIL){ - viewModel.setUserUid() - viewModel.setUserEmail() - } + // 프로바이더 셋팅 + joinViewModel.setUserProvider(joinType.provider) + // SNS계정인경우 uid, email 셋팅 + if(joinType != JoinType.EMAIL){ + joinViewModel.setUserUid() + joinViewModel.setSnsUserEmail() } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - settingViewPagerAdapter() + settingToolBar() + settingBackPressedEvent() - // 안드로이드 뒤로가기 기능에 뒤로가기 버튼 기능 추가 - lifecycleScope.launch { - requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { - if(binding.viewPagerJoin.currentItem!=0){ - binding.viewPagerJoin.currentItem -= 1 - }else{ - // JoinFragment 회원가입 종료, 확인 알림창 띄우기? - showCancelJoinDialog() - } - } - } + // 프로그래스바 최대치 설정 + binding.progressBarJoin.max = VIEW_PAGER_MAX_PROGRESS_VALUE + + settingViewPagerAttribute() + settingViewPagerNextButton() + + settingPhoneVerificationStateCollector() + settingPhoneVerificationCancelStateCollector() + settingPhoneAlreadyRegisteredStateCollector() + settingJoinCompleteStateCollector() } override fun onDestroyView() { @@ -114,30 +161,30 @@ class JoinFragment : DBBaseFragment(R.layout.fragment_join) // 전화번호 중복 계정 화면으로 넘어가는 경우는 // 전부 리셋하지 않고 isPhoneAlreadyRegistered값만 false로 변경 - if(viewModel.isPhoneAlreadyRegistered.value == true){ - viewModel.setIsPhoneAlreadyRegistered(false) + if(joinViewModel.isPhoneAlreadyRegistered.value){ + joinViewModel.setIsPhoneAlreadyRegistered(false) return } // 회원가입을 완료하지 않고 화면을 이탈한 경우 이미 등록되어있던 Auth 정보를 삭제한다. - if(viewModel.joinCompleted.value == false){ - viewModel.deleteCurrentUser() + if(!joinViewModel.isJoinCompleted.value){ + joinViewModel.deleteCurrentRegisteredFirebaseUser() } // 뷰모델 값 리셋 - viewModelStep1.reset() - viewModelStep2.reset() - viewModelStep3.reset() - viewModel.reset() + step1EmailAndPwViewModel.resetStep1States() + step2NameAndPhoneViewModel.resetStep2States() + step3InterestViewModel.resetStep3States() + joinViewModel.resetViewModelStates() } private fun settingToolBar(){ with(binding.toolbarJoin){ - title = "회원가입" + title = resources.getString(R.string.JOIN_TOOLBAR_TITLE) setNavigationIcon(R.drawable.arrow_back_24px) setNavigationOnClickListener { - if(binding.viewPagerJoin.currentItem!=0){ - binding.viewPagerJoin.currentItem -= 1 + if(binding.viewPagerJoin.currentItem != VIEW_PAGER_INITIAL_PAGE){ + binding.viewPagerJoin.currentItem -= VIEW_PAGER_MOVE_PAGE_COUNTER }else{ // JoinFragment 회원가입 종료, 확인 알림창 띄우기? showCancelJoinDialog() @@ -150,12 +197,12 @@ class JoinFragment : DBBaseFragment(R.layout.fragment_join) private fun showCancelJoinDialog() { val dialogView = LayoutInflater.from(requireContext()).inflate(R.layout.custom_dialog, null) val dialog = MaterialAlertDialogBuilder(requireContext(), R.style.dialogColor) - .setTitle("회원가입 취소") - .setMessage("정말로 회원가입을 취소하시겠습니까?") + .setTitle(resources.getString(R.string.CANCEL_JOIN_TITLE)) + .setMessage(resources.getString(R.string.CANCEL_JOIN_MESSAGE)) .setView(dialogView) .create() - dialogView.findViewById(R.id.btnYes).text = "네" + dialogView.findViewById(R.id.btnYes).text = resources.getString(R.string.DIALOG_BUTTON_YES) dialogView.findViewById(R.id.btnYes).setOnClickListener { parentFragmentManager.beginTransaction() .replace(R.id.containerMain, SocialLoginFragment()) @@ -163,63 +210,41 @@ class JoinFragment : DBBaseFragment(R.layout.fragment_join) dialog.dismiss() } - dialogView.findViewById(R.id.btnNo).text = "아니오" + dialogView.findViewById(R.id.btnNo).text = resources.getString(R.string.DIALOG_BUTTON_NO) dialogView.findViewById(R.id.btnNo).setOnClickListener { - // 아니요 버튼 로직 dialog.dismiss() } dialog.show() } - private fun settingViewPagerAdapter(){ - val viewPagerAdapter = JoinViewPagerAdapter(this) - - // 뷰페이저에 보여줄 프래그먼트를 회원가입 유형에 따라 다르게 셋팅해준다. - when(joinType){ - // 이메일로 회원가입할 때 - JoinType.EMAIL -> { - viewPagerAdapter.addFragments( - arrayListOf( - JoinStep1Fragment(), - JoinEmailVerificationFragment(), - JoinStep2Fragment(), - JoinStep3Fragment() - ) - ) - } - // SNS계정으로 회원가입할 때 - else -> { - viewPagerAdapter.addFragments( - arrayListOf( - JoinStep2Fragment(), - JoinStep3Fragment() - ) - ) - } - } - - with(binding){ + /** 뷰페이저 속성 셋팅 */ + private fun settingViewPagerAttribute(){ + with(binding.viewPagerJoin){ // 어댑터 설정 - viewPagerJoin.adapter = viewPagerAdapter + adapter = viewPagerAdapter // 전환 방향 - viewPagerJoin.orientation = ViewPager2.ORIENTATION_HORIZONTAL + orientation = ViewPager2.ORIENTATION_HORIZONTAL // 터치로 스크롤 막기 - viewPagerJoin.isUserInputEnabled = false + isUserInputEnabled = false // 현재 인덱스 기준으로 프래그먼트 생성,소멸 기준수 - viewPagerJoin.offscreenPageLimit = 3 + offscreenPageLimit = 3 - // 프로그래스바 설정 - progressBarJoin.max = 100 - - viewPagerJoin.registerOnPageChangeCallback( + registerOnPageChangeCallback( object: ViewPager2.OnPageChangeCallback(){ override fun onPageSelected(position: Int) { - val progress = (position + 1) * 100 / viewPagerAdapter.itemCount - binding.progressBarJoin.setProgress(progress, true) + binding.progressBarJoin.setProgress( + (position + VIEW_PAGER_START_POSITION) * VIEW_PAGER_MAX_PROGRESS_VALUE / viewPagerAdapter.itemCount, + true + ) } } ) + } + } + /** 뷰페이저 다음 버튼 클릭 리스너 셋팅 */ + private fun settingViewPagerNextButton(){ + with(binding){ // 다음 버튼 클릭 시 다음 화면으로 넘어가기 buttonJoinNext.setOnClickListener { @@ -228,22 +253,22 @@ class JoinFragment : DBBaseFragment(R.layout.fragment_join) JoinType.EMAIL -> { when(viewPagerJoin.currentItem){ // 이메일, 비밀번호 화면 - 0 -> step1Process() + JoinFragmentPage.EMAIL_TYPE_EMAIL_AND_PW_PAGE.page -> step1EmailAndPwProcess() // 이메일 인증 화면 - 1 -> checkEmailVerified() + JoinFragmentPage.EMAIL_TYPE_EMAIL_VERIFICATION_PAGE.page -> checkEmailVerified() // 이름, 전화번호 인증 화면 - 2 -> step2Process() + JoinFragmentPage.EMAIL_TYPE_NAME_AND_PHONE_PAGE.page -> step2NameAndPhoneProcess() // 관심 분야 선택 화면 - 3 -> step3Process() + JoinFragmentPage.EMAIL_TYPE_INTEREST_PAGE.page -> step3InterestProcess() } } // SNS계정으로 회원가입할 때 else -> { when(viewPagerJoin.currentItem){ // 이름, 전화번호 인증 화면 - 0 -> step2Process() + JoinFragmentPage.SNS_TYPE_NAME_AND_PHONE_PAGE.page -> step2NameAndPhoneProcess() // 관심 분야 선택 화면 - 1 -> step3Process() + JoinFragmentPage.SNS_TYPE_INTEREST_PAGE.page -> step3InterestProcess() } } } @@ -251,122 +276,126 @@ class JoinFragment : DBBaseFragment(R.layout.fragment_join) } } - private fun step1Process(){ + private fun moveViewPager(item: Int){ + binding.viewPagerJoin.setCurrentItemWithDuration(item, VIEW_PAGER_ANIMATE_DURATION) + } + + private fun step1EmailAndPwProcess(){ // 유효성 검사 - val validation = viewModelStep1.validate() - if(!validation) return + if(!step1EmailAndPwViewModel.validateStep1UserInput()) return // 응답 받은 이메일, 비밀번호 - viewModel.setEmailAndPw( - viewModelStep1.userEmail.value, - viewModelStep1.userPassword.value + joinViewModel.setEmailAndPw( + step1EmailAndPwViewModel.userInputEmail.value, + step1EmailAndPwViewModel.userInputPassword.value ) lifecycleScope.launch { showLoading() // 처음 화면인 경우 - if(viewModel.verifiedEmail.value.isEmpty() + if(joinViewModel.verifiedEmail.value.isEmpty() // 다음 화면으로 넘어갔다가 다시 돌아와서 이메일을 변경한 경우 - || (viewModelStep1.userEmail.value != viewModel.verifiedEmail.value && viewModel.verifiedEmail.value.isNotEmpty()) + || (step1EmailAndPwViewModel.userInputEmail.value != joinViewModel.verifiedEmail.value && joinViewModel.verifiedEmail.value.isNotEmpty()) ){ - if(viewModelStep1.userEmail.value != viewModel.verifiedEmail.value && viewModel.verifiedEmail.value.isNotEmpty()){ + if(step1EmailAndPwViewModel.userInputEmail.value != joinViewModel.verifiedEmail.value && joinViewModel.verifiedEmail.value.isNotEmpty()){ // 다음 화면으로 넘어갔다가 다시 돌아와서 이메일을 변경한 경우에는 기존에 등록한 이메일 계정을 삭제 - viewModel.deleteCurrentUser() + joinViewModel.deleteCurrentRegisteredFirebaseUser() // 이메일 인증 여부 초기화 - viewModelStep1.resetEmailVerified() + step1EmailAndPwViewModel.resetEmailVerified() } // 계정 중복 확인 - val isDup = viewModel.createEmailUser() - if(isDup.isNotEmpty()){ - viewModelStep1.emailValidation.value = isDup + val isDuplicated = joinViewModel.registerEmailUserToFirebaseAuth() + if(isDuplicated.isNotEmpty()){ + step1EmailAndPwViewModel.setUserInputEmailValidationMessage(isDuplicated) hideLoading() return@launch } } hideLoading() // 다음 화면으로 이동 - if(viewModelStep1.isEmailVerified.value){ - binding.viewPagerJoin.setCurrentItemWithDuration(2, 300) + if(step1EmailAndPwViewModel.isEmailVerified.value){ + moveViewPager(JoinFragmentPage.EMAIL_TYPE_NAME_AND_PHONE_PAGE.page) }else{ - binding.viewPagerJoin.setCurrentItemWithDuration(1, 300) + moveViewPager(JoinFragmentPage.EMAIL_TYPE_EMAIL_VERIFICATION_PAGE.page) // 인증 이메일 발송 - viewModelStep1.sendEmailVerification() + step1EmailAndPwViewModel.sendEmailVerification() } } } private fun checkEmailVerified(){ showLoading() - viewModelStep1.checkEmailValidation{ isVerified -> + step1EmailAndPwViewModel.checkFirebaseEmailValidation{ isVerified -> if (isVerified) { // 인증이 되었으면 다음으로 이동 - binding.viewPagerJoin.setCurrentItemWithDuration(2, 300) + moveViewPager(JoinFragmentPage.EMAIL_TYPE_NAME_AND_PHONE_PAGE.page) }else{ // 인증이 안되었으면 스낵바 표시 - showSnackBar(emailNotVerifiedMessage) + showSnackBar(resources.getString(R.string.EMAIL_NOT_VERIFIED)) } hideLoading() } } - private fun step2Process(){ + private fun step2NameAndPhoneProcess(){ // 유효성 검사 - val validation = viewModelStep2.validate() + val validation = step2NameAndPhoneViewModel.validateStep2UserInput() if(!validation) return // 뒤로가기로 돌아왔을 때 이미 인증된 상태인 경우에는 바로 다음페이지로 넘어갈 수 있음 // 전화번호를 변경하지 않은 경우에만 넘어갈 수 있음 - if(viewModel.verifiedPhoneNumber.value.isNotEmpty() && viewModel.verifiedPhoneNumber.value == viewModelStep2.userPhone.value){ + if(joinViewModel.verifiedPhoneNumber.value.isNotEmpty() + && joinViewModel.verifiedPhoneNumber.value == step2NameAndPhoneViewModel.userInputPhone.value) + { if(joinType==JoinType.EMAIL){ - binding.viewPagerJoin.setCurrentItemWithDuration(3, 300) + moveViewPager(JoinFragmentPage.EMAIL_TYPE_INTEREST_PAGE.page) }else{ - binding.viewPagerJoin.setCurrentItemWithDuration(2, 300) + moveViewPager(JoinFragmentPage.SNS_TYPE_INTEREST_PAGE.page) } return } // 응답 받은 이름, 전화번호 - viewModel.setUserNameAndPhoneNumber( - viewModelStep2.userName.value, - viewModelStep2.userPhone.value + joinViewModel.setUserNameAndUserPhone( + step2NameAndPhoneViewModel.userInputName.value, + step2NameAndPhoneViewModel.userInputPhone.value ) lifecycleScope.launch { showLoading() - val result = viewModelStep2.createPhoneUser() + val result = step2NameAndPhoneViewModel.linkWithPhoneAuthCredential() if(result.isEmpty()){ // 인증 번호 확인 성공 - viewModel.setPhoneVerified(true) - viewModelStep2.userPhone.value.let { viewModel.setVerifiedPhoneNumber(it) } - viewModelStep2.cancelTimer() + joinViewModel.setPhoneNumberVerificationState(true) + step2NameAndPhoneViewModel.userInputPhone.value.let { joinViewModel.setVerifiedPhoneNumber(it) } + step2NameAndPhoneViewModel.cancelPhoneAuthTimer() }else{ // 인증 번호 확인 실패 - viewModel.setPhoneVerified(false) + joinViewModel.setPhoneNumberVerificationState(false) } - if(!viewModel.phoneVerification.value && result=="이미 해당 번호로 가입한 계정이 있습니다."){ - viewModel.setAlreadyRegisteredUser( - viewModelStep2.alreadyRegisteredUserEmail.value, - viewModelStep2.alreadyRegisteredUserProvider.value + if(!joinViewModel.isPhoneNumberVerified.value && result==resources.getString(R.string.ALREADY_REGISTERED_PHONE_USER)){ + joinViewModel.setAlreadyRegisteredUser( + step2NameAndPhoneViewModel.alreadyRegisteredUserEmail.value, + step2NameAndPhoneViewModel.alreadyRegisteredUserProvider.value ) - viewModel.setIsPhoneAlreadyRegistered(true) - viewModelStep2.cancelTimer() + joinViewModel.setIsPhoneAlreadyRegistered(true) + step2NameAndPhoneViewModel.cancelPhoneAuthTimer() } hideLoading() } } - private fun step3Process(){ + private fun step3InterestProcess(){ // 유효성 검사 - val validation = viewModelStep3.validate() - if(!validation) return + if(!step3InterestViewModel.validateStep3UserInput()) return // 응답값 - viewModelStep3.selectedInterestList.value.let { it1 -> - viewModel.setInterests(it1) + step3InterestViewModel.selectedInterestList.value.let { + joinViewModel.setUserInterests(it.toMutableList()) } val handler = CoroutineExceptionHandler { context, throwable -> - Log.e("JoinError", "$context ${throwable.message}") + Log.e(LOG_TAG_JOIN_ERROR, "$context ${throwable.message}") hideLoading() showSnackBar(throwable.message.toString()) } @@ -374,75 +403,83 @@ class JoinFragment : DBBaseFragment(R.layout.fragment_join) // 회원가입 완료 처리 lifecycleScope.launch { showLoading() - viewModel.completeJoinUser(handler) + joinViewModel.completeJoinProcess(handler) } } - private val emailNotVerifiedMessage = "이메일 인증이 완료되지 않았습니다." - private fun showSnackBar(message: String) { - val snackbar = + val snackBar = Snackbar.make(binding.root, message, Snackbar.LENGTH_SHORT) // 스낵바의 뷰를 가져옵니다. - val snackbarView = snackbar.view + val snackBarView = snackBar.view // 스낵바 텍스트 뷰 찾기 val textView = - snackbarView.findViewById(com.google.android.material.R.id.snackbar_text) + snackBarView.findViewById(com.google.android.material.R.id.snackbar_text) // 텍스트 크기를 dp 단위로 설정 - val textSizeInPx = dpToPx(requireContext(), 16f) + val textSizeInPx = SNACK_BAR_TEXT_SIZE * requireContext().resources.displayMetrics.density textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSizeInPx) - snackbar.show() + snackBar.show() } - // 스낵바 글시 크기 설정을 위해 dp를 px로 변환 - fun dpToPx(context: Context, dp: Float): Float { - return dp * context.resources.displayMetrics.density + private fun settingBackPressedEvent(){ + requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner) { + if(binding.viewPagerJoin.currentItem!=0){ + binding.viewPagerJoin.currentItem -= 1 + }else{ + // JoinFragment 회원가입 종료, 확인 알림창 띄우기? + showCancelJoinDialog() + } + } } - // 회원가입 절차 StateFlow값 collect 세팅 - private fun settingCollector() { - // 전화번호 인증이 확인 되었을 때 - collectWhenStarted(viewModel.phoneVerification) { isVerified -> + /** 전화번호 인증 여부 확인 */ + private fun settingPhoneVerificationStateCollector() { + collectWhenStarted(joinViewModel.isPhoneNumberVerified) { isVerified -> hideLoading() if(isVerified){ // 인증이 되었으면 다음으로 이동 if(joinType==JoinType.EMAIL){ - binding.viewPagerJoin.setCurrentItemWithDuration(3, 300) + moveViewPager(JoinFragmentPage.EMAIL_TYPE_INTEREST_PAGE.page) }else{ - binding.viewPagerJoin.setCurrentItemWithDuration(2, 300) + moveViewPager(JoinFragmentPage.SNS_TYPE_INTEREST_PAGE.page) } // 인증 관련 초기화 - viewModelStep2.apply { - resetIsCodeSent() - resetValidationText() - cancelTimer() + step2NameAndPhoneViewModel.apply { + resetPhoneAuthCodeSentState() + cancelPhoneAuthTimer() } - viewModel.setPhoneVerified(false) + joinViewModel.setPhoneNumberVerificationState(false) } } + } - // 인증하기를 다시 했을 때 기존의 인증 완료 취소 - collectWhenStarted(viewModelStep2.isVerifiedPhone) { - if(!it){ - viewModel.setPhoneVerified(false) + /** 인증하기를 다시 했을 때 기존의 인증 완료 취소 */ + private fun settingPhoneVerificationCancelStateCollector() { + collectWhenStarted(step2NameAndPhoneViewModel.isVerifiedPhone) { isVerified -> + if(!isVerified){ + joinViewModel.setPhoneNumberVerificationState(false) } } + } - // 전화번호가 기존에 등록된 번호인 것이 확인되었을 때 - collectWhenStarted(viewModel.isPhoneAlreadyRegistered) { isRegistered -> + /** 전화번호가 기존에 등록된 번호인 것이 확인되었는지 여부 */ + private fun settingPhoneAlreadyRegisteredStateCollector() { + collectWhenStarted(joinViewModel.isPhoneAlreadyRegistered) { isRegistered -> if(isRegistered){ // 중복인 경우 중복 알림 프래그먼트로 이동 - val bundle = Bundle() - bundle.putString("email", viewModel.alreadyRegisteredUserEmail.value) - bundle.putString("provider", viewModel.alreadyRegisteredUserProvider.value) - bundle.putParcelable("user", viewModel.user.value) - val joinDupFragment = JoinDuplicateFragment() - joinDupFragment.arguments = bundle + val joinDupFragment = JoinDuplicateFragment().apply { + arguments = Bundle().apply { + putString(BUNDLE_KEY_EMAIL, joinViewModel.alreadyRegisteredUserEmail.value) + putString(BUNDLE_KEY_PROVIDER, joinViewModel.alreadyRegisteredUserProvider.value) + putParcelable(BUNDLE_KEY_USER, joinViewModel.firebaseUser.value) + } + } + parentFragmentManager.commit { hide(this@JoinFragment) if(joinDupFragment.isAdded){ @@ -451,25 +488,29 @@ class JoinFragment : DBBaseFragment(R.layout.fragment_join) add(R.id.containerMain, joinDupFragment) } } - viewModel.setIsPhoneAlreadyRegistered(false) + + joinViewModel.setIsPhoneAlreadyRegistered(false) } } + } - // 회원가입 완료 시 완료 화면으로 이동 - collectWhenStarted(viewModel.joinCompleted) { isCompleted -> + /** 회원가입 절차 완료 여부 */ + private fun settingJoinCompleteStateCollector() { + + collectWhenStarted(joinViewModel.isJoinCompleted) { isCompleted -> hideLoading() if(isCompleted){ if(joinType==JoinType.EMAIL){ // 이메일 계정 회원가입인 경우에는 로그아웃 처리 - viewModel.signOut() + joinViewModel.signOutCurrentFirebaseUser() }else{ // SNS 계정 회원가입인 경우에는 자동로그인값 preferences에 저장 - prefs.setBoolean("autoLogin", true) + prefs.setBoolean(PREFS_KEY_AUTO_LOGIN, true) } val joinCompleteFragment = JoinCompleteFragment().apply { arguments = Bundle().apply { - putString("joinType", joinType?.provider) + putString(BUNDLE_KEY_JOIN_TYPE, joinType.provider) } } // popBackStack에서 name값을 null로 넣어주면 기존의 backstack을 모두 없애준다. @@ -482,18 +523,13 @@ class JoinFragment : DBBaseFragment(R.layout.fragment_join) } } - private val showLoading = fun(){ - requireActivity().hideSoftInput() - binding.layoutLoadingJoin.visibility = View.VISIBLE - requireActivity().window?.setFlags( - WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, - WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE - ) - } - - private val hideLoading = fun(){ - binding.layoutLoadingJoin.visibility = View.GONE - requireActivity().window?.clearFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE) - } +} +enum class JoinFragmentPage(val page:Int){ + EMAIL_TYPE_EMAIL_AND_PW_PAGE(0), + EMAIL_TYPE_EMAIL_VERIFICATION_PAGE(1), + EMAIL_TYPE_NAME_AND_PHONE_PAGE(2), + EMAIL_TYPE_INTEREST_PAGE(3), + SNS_TYPE_NAME_AND_PHONE_PAGE(0), + SNS_TYPE_INTEREST_PAGE(1), } \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/join/JoinFragmentConstants.kt b/app/src/main/java/kr/co/lion/modigm/ui/join/JoinFragmentConstants.kt new file mode 100644 index 00000000..b0fca4b3 --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/join/JoinFragmentConstants.kt @@ -0,0 +1,8 @@ +package kr.co.lion.modigm.ui.join + +const val BUNDLE_KEY_JOIN_TYPE = "joinType" +const val BUNDLE_KEY_EMAIL = "email" +const val BUNDLE_KEY_PROVIDER = "provider" +const val BUNDLE_KEY_USER = "user" +const val PREFS_KEY_AUTO_LOGIN = "autoLogin" +const val LOG_TAG_JOIN_ERROR = "JoinError" \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep1EmailAndPwFragment.kt b/app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep1EmailAndPwFragment.kt new file mode 100644 index 00000000..c3551548 --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep1EmailAndPwFragment.kt @@ -0,0 +1,32 @@ +package kr.co.lion.modigm.ui.join + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import kr.co.lion.modigm.ui.join.vm.JoinStep1EmailAndPwViewModel + +class JoinStep1EmailAndPwFragment : Fragment() { + + private val joinStep1EmailAndPwViewModel: JoinStep1EmailAndPwViewModel by activityViewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + // Inflate the layout for this fragment + super.onCreateView(inflater, container, savedInstanceState) + + return ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + JoinStep1EmailAndPwScreen(joinStep1EmailAndPwViewModel) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep1EmailAndPwScreen.kt b/app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep1EmailAndPwScreen.kt new file mode 100644 index 00000000..6e966e2b --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep1EmailAndPwScreen.kt @@ -0,0 +1,85 @@ +package kr.co.lion.modigm.ui.join + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import kr.co.lion.modigm.R +import kr.co.lion.modigm.ui.join.component.EmailTextField +import kr.co.lion.modigm.ui.join.component.PasswordTextField +import kr.co.lion.modigm.ui.join.vm.JoinStep1EmailAndPwViewModel + +@Composable +fun JoinStep1EmailAndPwScreen( + joinStep1EmailAndPwViewModel: JoinStep1EmailAndPwViewModel +){ + + val scrollState = rememberScrollState() + val emailTextValueState = joinStep1EmailAndPwViewModel.userInputEmail.collectAsStateWithLifecycle() + val emailValidationMessageState = joinStep1EmailAndPwViewModel.userInputEmailValidationMessage.collectAsStateWithLifecycle() + val passwordTextValueState = joinStep1EmailAndPwViewModel.userInputPassword.collectAsStateWithLifecycle() + val passwordValidationMessageState = joinStep1EmailAndPwViewModel.userInputPwValidationMessage.collectAsStateWithLifecycle() + val passwordCheckTextValueState = joinStep1EmailAndPwViewModel.userInputPasswordCheck.collectAsStateWithLifecycle() + val passwordCheckValidationMessageState = joinStep1EmailAndPwViewModel.userInputPwCheckValidationMessage.collectAsStateWithLifecycle() + + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp) + .padding(top = 40.dp) + .verticalScroll(scrollState) + ) { + Text( + text = stringResource(R.string.JOIN_STEP1_TITLE), + style = TextStyle(fontSize = 26.sp) + ) + EmailTextField( + inputTitle = stringResource(R.string.JOIN_STEP1_EMAIL_LABEL), + textValue = emailTextValueState.value, + onValueChange = { joinStep1EmailAndPwViewModel.setUserInputEmail(it) }, + isError = emailValidationMessageState.value.isNotEmpty(), + errorMessage = emailValidationMessageState.value, + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Email, + imeAction = ImeAction.Next + ), + modifier = Modifier.padding(top = 40.dp) + ) + PasswordTextField( + inputTitle = stringResource(R.string.JOIN_STEP1_PASSWORD_LABEL), + textValue = passwordTextValueState.value, + onValueChange = { joinStep1EmailAndPwViewModel.setUserInputPassword(it) }, + isError = passwordValidationMessageState.value.isNotEmpty(), + errorMessage = passwordValidationMessageState.value, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Next), + modifier = Modifier.padding(top = 16.dp) + ) + Text( + text = stringResource(R.string.JOIN_STEP1_PASSWORD_GUIDE), + color = colorResource(R.color.redColor), + modifier = Modifier.padding(start = 15.dp), + ) + PasswordTextField( + inputTitle = stringResource(R.string.JOIN_STEP1_PASSWORD_CHECK_LABEL), + textValue = passwordCheckTextValueState.value, + onValueChange = { joinStep1EmailAndPwViewModel.setUserInputPasswordCheck(it) }, + isError = passwordCheckValidationMessageState.value.isNotEmpty(), + errorMessage = passwordCheckValidationMessageState.value, + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done), + modifier = Modifier.padding(top = 16.dp) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep1Fragment.kt b/app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep1Fragment.kt deleted file mode 100644 index fe23364b..00000000 --- a/app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep1Fragment.kt +++ /dev/null @@ -1,52 +0,0 @@ -package kr.co.lion.modigm.ui.join - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.activityViewModels -import kr.co.lion.modigm.R -import kr.co.lion.modigm.databinding.FragmentJoinStep1Binding -import kr.co.lion.modigm.ui.DBBaseFragment -import kr.co.lion.modigm.ui.join.vm.JoinStep1ViewModel -import kr.co.lion.modigm.util.collectWhenStarted - -class JoinStep1Fragment : DBBaseFragment(R.layout.fragment_join_step1) { - - private val joinStep1ViewModel: JoinStep1ViewModel by activityViewModels() - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - // Inflate the layout for this fragment - super.onCreateView(inflater, container, savedInstanceState) - binding.viewModel = joinStep1ViewModel - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - settingTextInputLayoutError() - } - - // 에러 메시지 설정 - private fun settingTextInputLayoutError(){ - - // 이메일 에러 - collectWhenStarted(joinStep1ViewModel.emailValidation) { - binding.textInputLayoutJoinUserEmail.error = it - } - - // 비밀번호 에러 - collectWhenStarted(joinStep1ViewModel.pwValidation) { - binding.textInputLayoutJoinUserPassword.error = it - } - - // 비밀번호 확인 에러 - collectWhenStarted(joinStep1ViewModel.pwCheckValidation) { - binding.textInputLayoutJoinUserPasswordCheck.error = it - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep2Fragment.kt b/app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep2Fragment.kt deleted file mode 100644 index 876f9610..00000000 --- a/app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep2Fragment.kt +++ /dev/null @@ -1,145 +0,0 @@ -package kr.co.lion.modigm.ui.join - -import android.content.Context -import android.os.Build -import android.os.Bundle -import android.telephony.PhoneNumberFormattingTextWatcher -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.activityViewModels -import com.google.android.gms.auth.api.phone.SmsRetriever -import dagger.hilt.android.AndroidEntryPoint -import kr.co.lion.modigm.R -import kr.co.lion.modigm.databinding.FragmentJoinStep2Binding -import kr.co.lion.modigm.ui.DBBaseFragment -import kr.co.lion.modigm.ui.join.vm.JoinStep2ViewModel -import kr.co.lion.modigm.util.SmsReceiver -import kr.co.lion.modigm.util.collectWhenStarted - -@AndroidEntryPoint -class JoinStep2Fragment : DBBaseFragment(R.layout.fragment_join_step2) { - - private val joinStep2ViewModel: JoinStep2ViewModel by activityViewModels() - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - // Inflate the layout for this fragment - super.onCreateView(inflater, container, savedInstanceState) - binding.viewModel = joinStep2ViewModel - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - settingTextInputLayoutError() - settingTextInputUserPhone() - settingButtonPhoneAuth() - settingCollector() - joinStep2ViewModel.inputSmsCode.value = SmsReceiver.smsCode.value - } - - // 에러 메시지 설정 - private fun settingTextInputLayoutError(){ - - // 이름 에러 - collectWhenStarted(joinStep2ViewModel.nameValidation){ - binding.textInputLayoutJoinUserName.error = it - } - - collectWhenStarted(joinStep2ViewModel.phoneValidation){ - binding.textInputLayoutJoinUserPhone.error = it - } - - collectWhenStarted(joinStep2ViewModel.inputSmsCodeValidation){ - binding.textInputLayoutJoinPhoneAuth.error = it - } - - } - - private fun settingTextInputUserPhone(){ - // 번호 입력 시 자동으로 하이픈을 넣어줌 - binding.textinputJoinUserPhone.addTextChangedListener(PhoneNumberFormattingTextWatcher()) - } - - private fun settingButtonPhoneAuth(){ - binding.textInputLayoutJoinUserPhone.error = "" - - binding.buttonJoinPhoneAuth.setOnClickListener { - joinStep2ViewModel.showLoading() - // 전화번호 유효성 검사 먼저 한 후 - if(!joinStep2ViewModel.checkPhoneValidation()){ - joinStep2ViewModel.hideLoading() - return@setOnClickListener - } - - // 응답한 전화번호로 인증번호 SMS 보내기 - joinStep2ViewModel.sendCode(requireActivity()){ - startSmsReceiver() - } - } - } - - private fun settingCollector(){ - // 인증 코드 발송이 성공하면 인증번호 입력 창 보여주기 - collectWhenStarted(joinStep2ViewModel.isCodeSent) { - joinStep2ViewModel.hideLoading() - if(it){ - binding.linearLayoutJoinPhoneAuth.visibility = View.VISIBLE - binding.textinputJoinPhoneAuth.requestFocus() - }else{ - binding.linearLayoutJoinPhoneAuth.visibility = View.GONE - } - } - - collectWhenStarted(joinStep2ViewModel.authExpired) { - if(it){ - binding.buttonJoinPhoneAuth.setBackgroundColor(requireContext().getColor(R.color.pointColor)) - binding.buttonJoinPhoneAuth.isClickable = true - }else{ - binding.buttonJoinPhoneAuth.setBackgroundColor(requireContext().getColor(R.color.textGray)) - binding.buttonJoinPhoneAuth.isClickable = false - } - } - - // SmsReceiver에서 받은 인증 코드를 입력창에 넣어줌 - collectWhenStarted(SmsReceiver.smsCode){ - joinStep2ViewModel.inputSmsCode.value = it - } - } - - private var smsReceiver: SmsReceiver? = null - - private fun startSmsReceiver(){ - SmsRetriever. getClient(requireContext()).startSmsRetriever().also { task -> - task.addOnSuccessListener { - smsReceiver = SmsReceiver() - - if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU){ - requireContext().registerReceiver(smsReceiver, smsReceiver!!.doFilter(), - Context.RECEIVER_NOT_EXPORTED) - }else{ - requireContext().registerReceiver(smsReceiver, smsReceiver!!.doFilter()) - } - } - task.addOnFailureListener { - stopSmsReceiver() - } - } - } - - private fun stopSmsReceiver(){ - if(smsReceiver != null) { - requireContext().unregisterReceiver(smsReceiver) - smsReceiver = null - } - } - - override fun onStop() { - super.onStop() - stopSmsReceiver() - } - -} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep2NameAndPhoneFragment.kt b/app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep2NameAndPhoneFragment.kt new file mode 100644 index 00000000..a2eb5702 --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep2NameAndPhoneFragment.kt @@ -0,0 +1,61 @@ +package kr.co.lion.modigm.ui.join + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import dagger.hilt.android.AndroidEntryPoint +import kr.co.lion.modigm.ui.join.vm.JoinStep2NameAndPhoneViewModel + +@AndroidEntryPoint +class JoinStep2NameAndPhoneFragment : Fragment() { + + private val joinStep2NameAndPhoneViewModel: JoinStep2NameAndPhoneViewModel by activityViewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + // Inflate the layout for this fragment + super.onCreateView(inflater, container, savedInstanceState) + + return ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + JoinStep2NameAndPhoneScreen( + inputName = joinStep2NameAndPhoneViewModel.userInputName.collectAsStateWithLifecycle().value, + nameValidationMessage = joinStep2NameAndPhoneViewModel.userInputNameValidation.collectAsStateWithLifecycle().value, + inputPhoneNumber = joinStep2NameAndPhoneViewModel.userInputPhone.collectAsStateWithLifecycle().value, + phoneValidationMessage = joinStep2NameAndPhoneViewModel.userInputPhoneValidation.collectAsStateWithLifecycle().value, + inputPhoneAuthCode = joinStep2NameAndPhoneViewModel.userInputSmsCode.collectAsStateWithLifecycle().value, + phoneAuthCodeValidationMessage = joinStep2NameAndPhoneViewModel.userInputSmsCodeValidation.collectAsStateWithLifecycle().value, + phoneAuthButtonText = joinStep2NameAndPhoneViewModel.phoneAuthButtonText.collectAsStateWithLifecycle().value, + isPhoneAuthCodeSent = joinStep2NameAndPhoneViewModel.isPhoneAuthCodeSent.collectAsStateWithLifecycle().value, + isPhoneAuthExpired = joinStep2NameAndPhoneViewModel.isPhoneAuthExpired.collectAsStateWithLifecycle().value, + setUserInputName = { + joinStep2NameAndPhoneViewModel.setUserInputName(it) + }, + setUserInputPhone = { + joinStep2NameAndPhoneViewModel.setUserInputPhone(it) + }, + phoneAuthButtonClickEvent = { + joinStep2NameAndPhoneViewModel.phoneAuthButtonClickEvent(requireActivity()) + }, + setUserInputSmsCode = { + joinStep2NameAndPhoneViewModel.setUserInputSmsCode(it) + }, + cancelPhoneAuth = { + joinStep2NameAndPhoneViewModel.cancelPhoneAuthTimer() + joinStep2NameAndPhoneViewModel.stopSmsReceiver(requireActivity()) + }, + ) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep2NameAndPhoneScreen.kt b/app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep2NameAndPhoneScreen.kt new file mode 100644 index 00000000..24bb6d87 --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep2NameAndPhoneScreen.kt @@ -0,0 +1,131 @@ +package kr.co.lion.modigm.ui.join + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonColors +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import kr.co.lion.modigm.R +import kr.co.lion.modigm.ui.join.component.NameTextField +import kr.co.lion.modigm.ui.join.component.PhoneAuthTextField +import kr.co.lion.modigm.ui.join.component.PhoneTextField + +@Composable +fun JoinStep2NameAndPhoneScreen( + inputName: String, + nameValidationMessage: String, + inputPhoneNumber: String, + phoneValidationMessage: String, + inputPhoneAuthCode: String, + phoneAuthCodeValidationMessage: String, + phoneAuthButtonText: String, + isPhoneAuthCodeSent: Boolean, + isPhoneAuthExpired: Boolean, + setUserInputName: (String) -> Unit, + setUserInputPhone: (String) -> Unit, + phoneAuthButtonClickEvent: () -> Unit, + setUserInputSmsCode: (String) -> Unit, + cancelPhoneAuth: () -> Unit, +){ + + DisposableEffect(Unit) { + onDispose { + cancelPhoneAuth() + } + } + + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp) + .padding(top = 40.dp) + .verticalScroll(rememberScrollState()) + ) { + Text( + text = stringResource(R.string.JOIN_STEP2_TITLE), + style = TextStyle(fontSize = 26.sp) + ) + + NameTextField( + inputTitle = stringResource(R.string.JOIN_STEP2_NAME_LABEL), + textValue = inputName, + onValueChange = setUserInputName, + isError = nameValidationMessage.isNotEmpty(), + errorMessage = nameValidationMessage, + modifier = Modifier.padding(top = 16.dp), + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Next, + keyboardType = KeyboardType.Text + ) + ) + + Text( + text = stringResource(R.string.JOIN_STEP2_NAME_GUIDE), + modifier = Modifier.padding(start = 15.dp), + color = colorResource(R.color.redColor), + ) + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier.padding(top = 16.dp) + ) { + PhoneTextField( + inputTitle = stringResource(R.string.JOIN_STEP2_PHONE_LABEL), + textValue = inputPhoneNumber, + onValueChange = setUserInputPhone, + isError = phoneValidationMessage.isNotEmpty(), + errorMessage = phoneValidationMessage, + modifier = Modifier.weight(2f), + keyboardOptions = KeyboardOptions( + imeAction = ImeAction.Done, + keyboardType = KeyboardType.Phone + ) + ) + + Button( + onClick = phoneAuthButtonClickEvent, + modifier = Modifier + .align(Alignment.CenterVertically) + .padding(start = 10.dp) + .weight(1f), + enabled = isPhoneAuthExpired, + colors = ButtonColors( + containerColor = colorResource(R.color.pointColor), + contentColor = colorResource(R.color.white), + disabledContainerColor = colorResource(R.color.textGray), + disabledContentColor = colorResource(R.color.black) + ) + ) { + Text(phoneAuthButtonText) + } + } + + if(isPhoneAuthCodeSent){ + PhoneAuthTextField( + inputTitle = stringResource(R.string.JOIN_STEP2_PHONE_AUTH_LABEL), + textValue = inputPhoneAuthCode, + onValueChange = setUserInputSmsCode, + isError = phoneAuthCodeValidationMessage.isNotEmpty(), + errorMessage = phoneAuthCodeValidationMessage, + modifier = Modifier.padding(top = 16.dp), + keyboardOptions = KeyboardOptions(imeAction = ImeAction.Done) + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep3Fragment.kt b/app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep3Fragment.kt deleted file mode 100644 index 62a413c1..00000000 --- a/app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep3Fragment.kt +++ /dev/null @@ -1,81 +0,0 @@ -package kr.co.lion.modigm.ui.join - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.activityViewModels -import com.google.android.material.chip.Chip -import kr.co.lion.modigm.R -import kr.co.lion.modigm.databinding.FragmentJoinStep3Binding -import kr.co.lion.modigm.ui.DBBaseFragment -import kr.co.lion.modigm.ui.join.vm.JoinStep3ViewModel -import kr.co.lion.modigm.util.Interest -import kr.co.lion.modigm.util.collectWhenStarted - -class JoinStep3Fragment : DBBaseFragment(R.layout.fragment_join_step3) { - - private val joinStep3ViewModel: JoinStep3ViewModel by activityViewModels() - - override fun onCreateView( - inflater: LayoutInflater, container: ViewGroup?, - savedInstanceState: Bundle? - ): View? { - // Inflate the layout for this fragment - super.onCreateView(inflater, container, savedInstanceState) - binding.viewModel = joinStep3ViewModel - - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - settingChips() - settingCollector() - } - - private fun settingChips(){ - //Chip 셋팅 - Interest.entries.toTypedArray().also { - if(it.isNotEmpty()){ - binding.textViewJoinAlert.visibility = View.GONE - } - for(chipName in it){ - binding.chipGroupJoinInterest.addView( - Chip(requireContext()).apply { - text = chipName.str - isCheckable = true - setTextAppearance(R.style.ChipTextStyle) - - setTextColor(resources.getColor(R.color.textGray, null)) - setChipBackgroundColorResource(R.color.white) - setChipStrokeColorResource(R.color.buttonGray) - setOnCheckedChangeListener { _, isChecked -> - if(isChecked){ - setTextColor(resources.getColor(R.color.white, null)) - setChipBackgroundColorResource(R.color.pointColor) - joinStep3ViewModel.addInterest(chipName.str) - - }else{ - setTextColor(resources.getColor(R.color.textGray, null)) - setChipBackgroundColorResource(R.color.white) - joinStep3ViewModel.removeInterest(chipName.str) - } - } - } - ) - } - } - } - private fun settingCollector(){ - // 에러메시지 셋팅 - collectWhenStarted(joinStep3ViewModel.isValidate) { - if(it == true){ - binding.textViewJoinAlert.visibility = View.GONE - }else if(it == false){ - binding.textViewJoinAlert.visibility = View.VISIBLE - } - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep3InterestFragment.kt b/app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep3InterestFragment.kt new file mode 100644 index 00000000..b98bc869 --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep3InterestFragment.kt @@ -0,0 +1,37 @@ +package kr.co.lion.modigm.ui.join + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.platform.ViewCompositionStrategy +import androidx.fragment.app.Fragment +import androidx.fragment.app.activityViewModels +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import kr.co.lion.modigm.ui.join.vm.JoinStep3InterestViewModel + +class JoinStep3InterestFragment : Fragment() { + + private val joinStep3InterestViewModel: JoinStep3InterestViewModel by activityViewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + // Inflate the layout for this fragment + super.onCreateView(inflater, container, savedInstanceState) + + return ComposeView(requireContext()).apply { + setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed) + setContent { + JoinStep3InterestScreen( + isInterestSelected = joinStep3InterestViewModel.isInterestListValidated.collectAsStateWithLifecycle().value, + removeFromInterestList = joinStep3InterestViewModel::removeFromInterestList, + addToInterestList = joinStep3InterestViewModel::addToInterestList + ) + } + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep3InterestScreen.kt b/app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep3InterestScreen.kt new file mode 100644 index 00000000..547a3ca6 --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/join/JoinStep3InterestScreen.kt @@ -0,0 +1,92 @@ +package kr.co.lion.modigm.ui.join + +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.AssistChip +import androidx.compose.material3.AssistChipDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import kr.co.lion.modigm.R +import kr.co.lion.modigm.util.Interest + +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun JoinStep3InterestScreen( + isInterestSelected: Boolean, + removeFromInterestList: (String) -> Unit, + addToInterestList: (String) -> Unit +){ + + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 16.dp) + .padding(top = 40.dp) + .verticalScroll(rememberScrollState()) + ) { + Text( + text = stringResource(R.string.JOIN_STEP3_TITLE), + style = TextStyle(fontSize = 26.sp), + modifier = Modifier.padding(bottom = 40.dp) + ) + if(!isInterestSelected){ + Text( + text = stringResource(R.string.JOIN_STEP3_ALERT_EMPTY_INTEREST_LIST), + style = TextStyle(fontSize = 16.sp, color = colorResource(R.color.redColor)), + ) + } + FlowRow( + horizontalArrangement = Arrangement.spacedBy(12.dp), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + Interest.entries.forEach { + var isSelected by remember { mutableStateOf(false) } + AssistChip( + onClick = { + if(isSelected){ + removeFromInterestList(it.str) + }else{ + addToInterestList(it.str) + } + isSelected = !isSelected + }, + label = { + Text( + text = it.str, + fontSize = 18.sp, + color = if(isSelected){ + colorResource(R.color.white) + }else{ + colorResource(R.color.textGray) + } + ) + }, + colors = AssistChipDefaults.assistChipColors( + containerColor = if(isSelected){ + colorResource(R.color.pointColor) + }else{ + Color.Unspecified + } + ) + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/join/adapter/JoinViewPagerAdapter.kt b/app/src/main/java/kr/co/lion/modigm/ui/join/adapter/JoinViewPagerAdapter.kt index 89278088..8da84537 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/join/adapter/JoinViewPagerAdapter.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/join/adapter/JoinViewPagerAdapter.kt @@ -5,13 +5,13 @@ import androidx.viewpager2.adapter.FragmentStateAdapter class JoinViewPagerAdapter(fragment: Fragment): FragmentStateAdapter(fragment) { - private lateinit var fragmentList : ArrayList + private lateinit var fragmentList : List override fun getItemCount(): Int = fragmentList.size override fun createFragment(position: Int): Fragment = fragmentList[position] - fun addFragments(fragments: ArrayList) { + fun setFragments(fragments: List) { fragmentList = fragments } diff --git a/app/src/main/java/kr/co/lion/modigm/ui/join/component/EmailTextField.kt b/app/src/main/java/kr/co/lion/modigm/ui/join/component/EmailTextField.kt new file mode 100644 index 00000000..99478754 --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/join/component/EmailTextField.kt @@ -0,0 +1,90 @@ +package kr.co.lion.modigm.ui.join.component + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Clear +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.sp +import kr.co.lion.modigm.R + +@Composable +fun EmailTextField( + inputTitle: String, + textValue: String, + onValueChange: (String) -> Unit, + isError: Boolean, + errorMessage: String, + modifier: Modifier = Modifier, + keyboardOptions: KeyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Email, + imeAction = ImeAction.Default + ), +){ + Column( + modifier = modifier + ) { + Text( + text = inputTitle, + style = TextStyle( + fontSize = 16.sp, + ) + ) + OutlinedTextField( + value = textValue, + onValueChange = onValueChange, + placeholder = { + Text( + stringResource(R.string.JOIN_STEP1_INPUT_PLACEHOLDER_TEXT, inputTitle), + color = colorResource(R.color.textGray) + ) + }, + trailingIcon = { + if (textValue.isEmpty()) return@OutlinedTextField + val emptyValue = stringResource(R.string.JOIN_TEXT_RESET_VALUE) + IconButton( + onClick = { + onValueChange(emptyValue) + } + ) { + Icon( + imageVector = Icons.Filled.Clear, + contentDescription = stringResource(R.string.CLEAR_ICON_DESCRIPTION) + ) + } + }, + singleLine = true, + isError = isError, + supportingText = { + if (isError) { + Text( + text = errorMessage, + color = colorResource(R.color.redColor) + ) + } + }, + colors = TextFieldDefaults.colors( + unfocusedContainerColor = Color.Transparent, + focusedContainerColor = Color.Transparent, + errorContainerColor = Color.Transparent, + focusedIndicatorColor = colorResource(R.color.pointColor), + ), + keyboardOptions = keyboardOptions, + modifier = Modifier + .fillMaxWidth() + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/join/component/NameTextField.kt b/app/src/main/java/kr/co/lion/modigm/ui/join/component/NameTextField.kt new file mode 100644 index 00000000..2e812725 --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/join/component/NameTextField.kt @@ -0,0 +1,90 @@ +package kr.co.lion.modigm.ui.join.component + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Clear +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.sp +import kr.co.lion.modigm.R + +@Composable +fun NameTextField( + inputTitle: String, + textValue: String, + onValueChange: (String) -> Unit, + isError: Boolean, + errorMessage: String, + modifier: Modifier = Modifier, + keyboardOptions: KeyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Text, + imeAction = ImeAction.Default + ), +){ + Column( + modifier = modifier + ) { + Text( + text = inputTitle, + style = TextStyle( + fontSize = 16.sp, + ) + ) + OutlinedTextField( + value = textValue, + onValueChange = onValueChange, + placeholder = { + Text( + stringResource(R.string.JOIN_STEP1_INPUT_PLACEHOLDER_TEXT, inputTitle), + color = colorResource(R.color.textGray) + ) + }, + trailingIcon = { + if (textValue.isEmpty()) return@OutlinedTextField + val emptyValue = stringResource(R.string.JOIN_TEXT_RESET_VALUE) + IconButton( + onClick = { + onValueChange(emptyValue) + } + ) { + Icon( + imageVector = Icons.Filled.Clear, + contentDescription = stringResource(R.string.CLEAR_ICON_DESCRIPTION) + ) + } + }, + singleLine = true, + isError = isError, + supportingText = { + if (isError) { + Text( + text = errorMessage, + color = colorResource(R.color.redColor) + ) + } + }, + colors = TextFieldDefaults.colors( + unfocusedContainerColor = Color.Transparent, + focusedContainerColor = Color.Transparent, + errorContainerColor = Color.Transparent, + focusedIndicatorColor = colorResource(R.color.pointColor), + ), + keyboardOptions = keyboardOptions, + modifier = Modifier + .fillMaxWidth() + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/join/component/PasswordTextField.kt b/app/src/main/java/kr/co/lion/modigm/ui/join/component/PasswordTextField.kt new file mode 100644 index 00000000..8cf636f6 --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/join/component/PasswordTextField.kt @@ -0,0 +1,103 @@ +package kr.co.lion.modigm.ui.join.component + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Visibility +import androidx.compose.material.icons.filled.VisibilityOff +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.text.input.VisualTransformation +import androidx.compose.ui.unit.sp +import kr.co.lion.modigm.R + +@Composable +fun PasswordTextField( + inputTitle: String, + textValue: String, + onValueChange: (String) -> Unit, + isError: Boolean, + errorMessage: String, + modifier: Modifier = Modifier, + keyboardOptions: KeyboardOptions = KeyboardOptions(imeAction = ImeAction.Default) +){ + var passwordVisible by rememberSaveable { mutableStateOf(false) } + val passwordVisibilityIcon = if (passwordVisible){ + Icons.Filled.Visibility + } else { + Icons.Filled.VisibilityOff + } + + Column( + modifier = modifier + ) { + Text( + text = inputTitle, + style = TextStyle( + fontSize = 16.sp, + ) + ) + OutlinedTextField( + value = textValue, + onValueChange = onValueChange, + placeholder = { + Text( + stringResource(R.string.JOIN_STEP1_INPUT_PLACEHOLDER_TEXT, inputTitle), + color = colorResource(R.color.textGray) + ) + }, + visualTransformation = if (passwordVisible) { + VisualTransformation.None + } else { + PasswordVisualTransformation() + }, + trailingIcon = { + IconButton( + onClick = { + passwordVisible = !passwordVisible + } + ) { + Icon( + imageVector = passwordVisibilityIcon, + contentDescription = stringResource(R.string.PASSWORD_TOGGLE_ICON_DESCRIPTION) + ) + } + }, + singleLine = true, + isError = isError, + supportingText = { + if (isError) { + Text( + text = errorMessage, + color = colorResource(R.color.redColor) + ) + } + }, + colors = TextFieldDefaults.colors( + unfocusedContainerColor = Color.Transparent, + focusedContainerColor = Color.Transparent, + errorContainerColor = Color.Transparent, + focusedIndicatorColor = colorResource(R.color.pointColor), + ), + keyboardOptions = keyboardOptions, + modifier = Modifier + .fillMaxWidth() + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/join/component/PhoneAuthTextField.kt b/app/src/main/java/kr/co/lion/modigm/ui/join/component/PhoneAuthTextField.kt new file mode 100644 index 00000000..dbf1121e --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/join/component/PhoneAuthTextField.kt @@ -0,0 +1,90 @@ +package kr.co.lion.modigm.ui.join.component + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Clear +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.sp +import kr.co.lion.modigm.R + +@Composable +fun PhoneAuthTextField( + inputTitle: String, + textValue: String, + onValueChange: (String) -> Unit, + isError: Boolean, + errorMessage: String, + modifier: Modifier = Modifier, + keyboardOptions: KeyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Text, + imeAction = ImeAction.Default + ), +){ + Column( + modifier = modifier + ) { + Text( + text = inputTitle, + style = TextStyle( + fontSize = 16.sp, + ) + ) + OutlinedTextField( + value = textValue, + onValueChange = onValueChange, + placeholder = { + Text( + stringResource(R.string.JOIN_STEP1_INPUT_PLACEHOLDER_TEXT, inputTitle), + color = colorResource(R.color.textGray) + ) + }, + trailingIcon = { + if(textValue.isEmpty()) return@OutlinedTextField + val emptyValue = stringResource(R.string.JOIN_TEXT_RESET_VALUE) + IconButton( + onClick = { + onValueChange(emptyValue) + } + ){ + Icon( + imageVector = Icons.Filled.Clear, + contentDescription = stringResource(R.string.CLEAR_ICON_DESCRIPTION) + ) + } + }, + singleLine = true, + isError = isError, + supportingText = { + if(isError){ + Text( + text = errorMessage, + color = colorResource(R.color.redColor) + ) + } + }, + colors = TextFieldDefaults.colors( + unfocusedContainerColor = Color.Transparent, + focusedContainerColor = Color.Transparent, + errorContainerColor = Color.Transparent, + focusedIndicatorColor = colorResource(R.color.pointColor), + ), + keyboardOptions = keyboardOptions, + modifier = Modifier + .fillMaxWidth() + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/join/component/PhoneTextField.kt b/app/src/main/java/kr/co/lion/modigm/ui/join/component/PhoneTextField.kt new file mode 100644 index 00000000..df90d9cc --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/join/component/PhoneTextField.kt @@ -0,0 +1,92 @@ +package kr.co.lion.modigm.ui.join.component + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Clear +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.colorResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.unit.sp +import kr.co.lion.modigm.R + +@Composable +fun PhoneTextField( + inputTitle: String, + textValue: String, + onValueChange: (String) -> Unit, + isError: Boolean, + errorMessage: String, + modifier: Modifier = Modifier, + keyboardOptions: KeyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Phone, + imeAction = ImeAction.Default + ), +){ + Column( + modifier = modifier + ) { + Text( + text = inputTitle, + style = TextStyle( + fontSize = 16.sp, + ) + ) + OutlinedTextField( + value = textValue, + onValueChange = { + onValueChange(it) + }, + placeholder = { + Text( + stringResource(R.string.JOIN_STEP1_INPUT_PLACEHOLDER_TEXT, inputTitle), + color = colorResource(R.color.textGray) + ) + }, + trailingIcon = { + if (textValue.isEmpty()) return@OutlinedTextField + val emptyValue = stringResource(R.string.JOIN_TEXT_RESET_VALUE) + IconButton( + onClick = { + onValueChange(emptyValue) + } + ) { + Icon( + imageVector = Icons.Filled.Clear, + contentDescription = stringResource(R.string.CLEAR_ICON_DESCRIPTION) + ) + } + }, + singleLine = true, + isError = isError, + supportingText = { + if (isError) { + Text( + text = errorMessage, + color = colorResource(R.color.redColor) + ) + } + }, + colors = TextFieldDefaults.colors( + unfocusedContainerColor = Color.Transparent, + focusedContainerColor = Color.Transparent, + errorContainerColor = Color.Transparent, + focusedIndicatorColor = colorResource(R.color.pointColor), + ), + keyboardOptions = keyboardOptions, + modifier = Modifier + .fillMaxWidth() + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinStep1EmailAndPwViewModel.kt b/app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinStep1EmailAndPwViewModel.kt new file mode 100644 index 00000000..a796dbbc --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinStep1EmailAndPwViewModel.kt @@ -0,0 +1,132 @@ +package kr.co.lion.modigm.ui.join.vm + +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.google.firebase.auth.FirebaseAuth +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.tasks.await +import java.util.regex.Pattern +import javax.inject.Inject + +@HiltViewModel +class JoinStep1EmailAndPwViewModel @Inject constructor( + private val _auth: FirebaseAuth +): ViewModel() { + + // 이메일 + private val _userInputEmail = MutableStateFlow("") + val userInputEmail = _userInputEmail.asStateFlow() + fun setUserInputEmail(email: String){ + _userInputEmail.value = email + } + private val _userInputEmailValidationMessage = MutableStateFlow("") + val userInputEmailValidationMessage = _userInputEmailValidationMessage.asStateFlow() + fun setUserInputEmailValidationMessage(message: String){ + _userInputEmailValidationMessage.value = message + } + + // 비밀번호 + private val _userInputPassword = MutableStateFlow("") + val userInputPassword = _userInputPassword.asStateFlow() + fun setUserInputPassword(password: String){ + _userInputPassword.value = password + } + private val _userInputPwValidationMessage = MutableStateFlow("") + val userInputPwValidationMessage = _userInputPwValidationMessage.asStateFlow() + + // 비밀번호 확인 + private val _userInputPasswordCheck = MutableStateFlow("") + val userInputPasswordCheck = _userInputPasswordCheck.asStateFlow() + fun setUserInputPasswordCheck(passwordCheck: String){ + _userInputPasswordCheck.value = passwordCheck + } + private val _userInputPwCheckValidationMessage = MutableStateFlow("") + val userInputPwCheckValidationMessage = _userInputPwCheckValidationMessage.asStateFlow() + + // 입력한 내용 유효성 검사 + fun validateStep1UserInput(): Boolean { + // 에러 초기화 + _userInputEmailValidationMessage.value = "" + _userInputPwValidationMessage.value = "" + _userInputPwCheckValidationMessage.value = "" + + var result = true + + if(_userInputEmail.value.isEmpty()){ + _userInputEmailValidationMessage.value = "이메일을 입력해주세요." + result = false + } + if(_userInputEmail.value.isNotEmpty() && !android.util.Patterns.EMAIL_ADDRESS.matcher(_userInputEmail.value).matches()){ + _userInputEmailValidationMessage.value = "올바른 이메일 형식이 아닙니다." + result = false + } + if(_userInputPassword.value.isEmpty()){ + _userInputPwValidationMessage.value = "비밀번호를 입력해주세요." + result = false + } + if(_userInputPassword.value.isNotEmpty() && !Pattern.matches("^(?=.*[A-Za-z])(?=.*[0-9])(?=.*[$@$!%*#?&.])[A-Za-z[0-9]$@$!%*#?&.]{8,20}$", userInputPassword.value)){ + _userInputPwValidationMessage.value = "영문, 숫자, 특수문자가 포함된 비밀번호를 8~20자로 입력해주세요." + result = false + } + if(_userInputPasswordCheck.value.isEmpty()){ + _userInputPwCheckValidationMessage.value = "비밀번호 확인을 입력해주세요." + result = false + } + if(_userInputPassword.value.isNotEmpty() && _userInputPasswordCheck.value.isNotEmpty() && _userInputPassword.value != _userInputPasswordCheck.value){ + _userInputPwCheckValidationMessage.value = "비밀번호가 일치하지 않습니다." + result = false + } + + return result + } + + // 입력값 초기화 + fun resetStep1States(){ + _userInputEmail.value = "" + _userInputEmailValidationMessage.value = "" + _userInputPassword.value = "" + _userInputPwValidationMessage.value = "" + _userInputPasswordCheck.value = "" + _userInputPwCheckValidationMessage.value = "" + _isEmailVerified.value = false + } + + + + // ================2. 이메일 인증 관련============================================================== + + private val _isEmailVerified = MutableStateFlow(false) + val isEmailVerified: StateFlow = _isEmailVerified + fun resetEmailVerified(){ + _isEmailVerified.value = false + } + + // 메일 인증 여부 체크 + fun checkFirebaseEmailValidation(moveNext: (boolean: Boolean) -> Unit) { + viewModelScope.launch { + _auth.currentUser?.let { user -> + user.reload().await() + (user.isEmailVerified).let { result -> + _isEmailVerified.value = result + moveNext(result) + } + } + } + } + + // 인증 메일 발송 + fun sendEmailVerification(){ + try { + _auth.currentUser?.sendEmailVerification() + }catch (e: Exception){ + Log.e("sendEmailAuth", "${e.message}") + _userInputEmailValidationMessage.value = "인증 메일 발송 실패" + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinStep1ViewModel.kt b/app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinStep1ViewModel.kt deleted file mode 100644 index 6a9217f1..00000000 --- a/app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinStep1ViewModel.kt +++ /dev/null @@ -1,118 +0,0 @@ -package kr.co.lion.modigm.ui.join.vm - -import android.util.Log -import androidx.lifecycle.ViewModel -import androidx.lifecycle.viewModelScope -import com.google.firebase.auth.FirebaseAuth -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.launch -import kotlinx.coroutines.tasks.await -import java.util.regex.Pattern -import javax.inject.Inject - -@HiltViewModel -class JoinStep1ViewModel @Inject constructor( - private val _auth: FirebaseAuth -): ViewModel() { - - // ================1. 유효성 검사 관련============================================================== - - // 이메일 - val userEmail = MutableStateFlow("") - // 이메일 유효성 검사 - val emailValidation = MutableStateFlow("") - - // 비밀번호 - val userPassword = MutableStateFlow("") - // 비밀번호 유효성 검사 - val pwValidation = MutableStateFlow("") - - // 비밀번호 확인 - val userPasswordCheck = MutableStateFlow("") - // 비밀번호 확인 유효성 검사 - val pwCheckValidation = MutableStateFlow("") - - // 입력한 내용 유효성 검사 - fun validate(): Boolean { - // 에러 초기화 - emailValidation.value = "" - pwValidation.value = "" - pwCheckValidation.value = "" - - var result = true - - if(userEmail.value.isEmpty()){ - emailValidation.value = "이메일을 입력해주세요." - result = false - } - if(userEmail.value.isNotEmpty() && !android.util.Patterns.EMAIL_ADDRESS.matcher(userEmail.value).matches()){ - emailValidation.value = "올바른 이메일 형식이 아닙니다." - result = false - } - if(userPassword.value.isEmpty()){ - pwValidation.value = "비밀번호를 입력해주세요." - result = false - } - if(userPassword.value.isNotEmpty() && !Pattern.matches("^(?=.*[A-Za-z])(?=.*[0-9])(?=.*[$@$!%*#?&.])[A-Za-z[0-9]$@$!%*#?&.]{8,20}$", userPassword.value)){ - pwValidation.value = "영문, 숫자, 특수문자가 포함된 비밀번호를 8~20자로 입력해주세요." - result = false - } - if(userPasswordCheck.value.isEmpty()){ - pwCheckValidation.value = "비밀번호 확인을 입력해주세요." - result = false - } - if(userPassword.value.isNotEmpty() && userPasswordCheck.value.isNotEmpty() && userPassword.value != userPasswordCheck.value){ - pwCheckValidation.value = "비밀번호가 일치하지 않습니다." - result = false - } - - return result - } - - // 입력값 초기화 - fun reset(){ - userEmail.value = "" - emailValidation.value = "" - userPassword.value = "" - pwValidation.value = "" - userPasswordCheck.value = "" - pwCheckValidation.value = "" - _isEmailVerified.value = false - } - - - - // ================2. 이메일 인증 관련============================================================== - - private val _isEmailVerified = MutableStateFlow(false) - val isEmailVerified: StateFlow = _isEmailVerified - fun resetEmailVerified(){ - _isEmailVerified.value = false - } - - // 메일 인증 여부 체크 - fun checkEmailValidation(moveNext: (boolean: Boolean) -> Unit) { - viewModelScope.launch { - _auth.currentUser?.let { user -> - user.reload().await() - (user.isEmailVerified).let { result -> - _isEmailVerified.value = result - moveNext(result) - } - } - } - } - - // 인증 메일 발송 - fun sendEmailVerification(){ - try { - _auth.currentUser?.sendEmailVerification() - }catch (e: Exception){ - Log.e("sendEmailAuth", "${e.message}") - emailValidation.value = "인증 메일 발송 실패" - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinStep2NameAndPhoneViewModel.kt b/app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinStep2NameAndPhoneViewModel.kt new file mode 100644 index 00000000..80771d9d --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinStep2NameAndPhoneViewModel.kt @@ -0,0 +1,356 @@ +package kr.co.lion.modigm.ui.join.vm + +import android.app.Activity +import android.content.Context +import android.os.Build +import android.os.CountDownTimer +import android.util.Log +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.google.android.gms.auth.api.phone.SmsRetriever +import com.google.firebase.FirebaseException +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.auth.FirebaseAuthException +import com.google.firebase.auth.PhoneAuthCredential +import com.google.firebase.auth.PhoneAuthOptions +import com.google.firebase.auth.PhoneAuthProvider +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch +import kotlinx.coroutines.tasks.await +import kr.co.lion.modigm.repository.JoinRepository +import kr.co.lion.modigm.util.SmsReceiver +import java.util.concurrent.TimeUnit +import java.util.regex.Pattern +import javax.inject.Inject + +@HiltViewModel +class JoinStep2NameAndPhoneViewModel @Inject constructor( + private val _joinRepository: JoinRepository, + private val _firebaseAuth: FirebaseAuth +): ViewModel() { + // ================0. SMS 인증 코드 관련 프로그래스 바 이벤트====================================================== + private val _showLoadingCallback = MutableStateFlow {} + + fun setShowLoading(showLoading: () -> Unit){ + _showLoadingCallback.value = showLoading + } + + private val _hideLoadingCallback = MutableStateFlow {} + + fun setHideLoading(hideLoading: () -> Unit){ + _hideLoadingCallback.value = hideLoading + } + + // ================1. 유효성 검사 관련============================================================== + + // 이름 + private val _userInputName = MutableStateFlow("") + val userInputName = _userInputName.asStateFlow() + + fun setUserInputName(name: String){ + _userInputName.value = name + } + + // 전화번호 + private val _userInputPhone = MutableStateFlow("") + val userInputPhone = _userInputPhone.asStateFlow() + + fun setUserInputPhone(phone: String){ + val phoneNumberWithOutHyphens = phone.replace("-","") + + if(phoneNumberWithOutHyphens.length > 11) return + + val formattedText = when { + phoneNumberWithOutHyphens.length >= 11 -> "${phoneNumberWithOutHyphens.substring(0, 3)}-${phoneNumberWithOutHyphens.substring(3, 7)}-${phoneNumberWithOutHyphens.substring(7)}" + else -> phoneNumberWithOutHyphens + } + + _userInputPhone.value = formattedText + } + + // 인증번호입력 + private val _userInputSmsCode = MutableStateFlow(SmsReceiver.smsCode.value) + val userInputSmsCode = _userInputSmsCode.asStateFlow() + + fun setUserInputSmsCode(code: String){ + _userInputSmsCode.value = code + } + + private val _userInputNameValidation = MutableStateFlow("") + val userInputNameValidation = _userInputNameValidation.asStateFlow() + + private val _userInputPhoneValidation = MutableStateFlow("") + val userInputPhoneValidation = _userInputPhoneValidation.asStateFlow() + + private val _userInputSmsCodeValidation = MutableStateFlow("") + val userInputSmsCodeValidation = _userInputSmsCodeValidation.asStateFlow() + + // 유효성 검사 + fun validateStep2UserInput(): Boolean { + // 에러 표시 초기화 + _userInputNameValidation.value ="" + _userInputPhoneValidation.value ="" + _userInputSmsCodeValidation.value ="" + + var result = true + + if(_userInputName.value.isEmpty()){ + _userInputNameValidation.value = "이름을 입력해주세요." + result = false + } + if(_userInputPhone.value.isEmpty()){ + _userInputPhoneValidation.value = "전화번호를 입력해주세요." + result = false + }else{ + if(!Pattern.matches("^01(?:0|1|[6-9])-(?:\\d{3}|\\d{4})-\\d{4}$", _userInputPhone.value)){ + _userInputPhoneValidation.value = "올바른 전화번호가 아닙니다." + result = false + }else if(!_isPhoneAuthCodeSent.value){ + _userInputPhoneValidation.value = "인증하기 버튼을 눌러서 인증을 진행해주세요." + result = false + } + } + if(_userInputSmsCode.value.isEmpty()){ + _userInputSmsCodeValidation.value = "인증번호를 입력해주세요." + result = false + } + return result + } + + // 인증하기 버튼 눌렀을 때 유효성 검사 + private fun checkUserInputPhoneValidation(): Boolean { + // 인증번호 입력칸 초기화 + _userInputSmsCode.value = "" + // 에러 표시 초기화 + _userInputPhoneValidation.value ="" + + var result = true + + if(_userInputPhone.value.isEmpty()){ + _userInputPhoneValidation.value = "전화번호를 입력해주세요." + result = false + }else{ + if(!Pattern.matches("^01(?:0|1|[6-9])-(?:\\d{3}|\\d{4})-\\d{4}$", _userInputPhone.value)){ + _userInputPhoneValidation.value = "올바른 전화번호가 아닙니다." + result = false + } + } + return result + } + + // ================2. 전화번호 인증 관련============================================================== + + private val _phoneAuthButtonText = MutableStateFlow("인증하기") + val phoneAuthButtonText: StateFlow = _phoneAuthButtonText + + private val _isPhoneAuthExpired = MutableStateFlow(true) + val isPhoneAuthExpired: StateFlow = _isPhoneAuthExpired + + // 인증문자 발송 여부 + private val _isPhoneAuthCodeSent = MutableStateFlow(false) + val isPhoneAuthCodeSent: StateFlow = _isPhoneAuthCodeSent + + fun resetPhoneAuthCodeSentState(){ + _isPhoneAuthCodeSent.value = false + } + + // 인증 ID(인증 코드 내용 아님) + private val _phoneAuthVerificationId = MutableStateFlow("") + + // 올바른 전화번호 확인 여부 + // onVerificationCompleted, onVerificationFailed에서 확인 + private val _isVerifiedPhone = MutableStateFlow(false) + val isVerifiedPhone: StateFlow = _isVerifiedPhone + + // 인증 에러 메시지 + private val _phoneAuthErrorMessage = MutableStateFlow("") + + // 이미 등록된 전화번호 계정이 있는지 여부 + private val _isAlreadyRegisteredPhoneUser = MutableStateFlow(false) + + // 이미 등록된 전화번호 계정의 이메일 + private val _alreadyRegisteredUserEmail = MutableStateFlow("") + val alreadyRegisteredUserEmail: StateFlow = _alreadyRegisteredUserEmail + + // 이미 등록된 전화번호 계정의 프로바이더 + private val _alreadyRegisteredUserProvider = MutableStateFlow("") + val alreadyRegisteredUserProvider: StateFlow = _alreadyRegisteredUserProvider + + // 문자 수신 60초 타이머 + private val _phoneAuthTimer: CountDownTimer by lazy { + _isPhoneAuthExpired.value = false + object : CountDownTimer(60000, 1000){ + override fun onTick(millisUntilFinished: Long) { + _phoneAuthButtonText.value = "${millisUntilFinished/1000}" + } + + override fun onFinish() { + _isPhoneAuthExpired.value = true + _phoneAuthButtonText.value = "인증하기" + } + } + } + + // 전화번호 인증계정을 앞선 이메일(SNS)계정과 연결 + suspend fun linkWithPhoneAuthCredential(): String { + // DB에 해당 번호로 등록된 계정이 있는지 확인한다. + _joinRepository.checkUserByPhone(_userInputPhone.value).onSuccess { resultMap -> + if(resultMap != null){ + _phoneAuthErrorMessage.value = "이미 해당 번호로 가입한 계정이 있습니다." + _alreadyRegisteredUserProvider.value = resultMap["userProvider"] ?: "" + _alreadyRegisteredUserEmail.value = resultMap["userEmail"] ?: "" + return _phoneAuthErrorMessage.value + } + } + // 오류 메시지 + if(_isPhoneAuthCodeSent.value){ + try{ + _phoneAuthErrorMessage.value = "" + val phoneCredential = PhoneAuthProvider.getCredential(_phoneAuthVerificationId.value, userInputSmsCode.value) + val linkedNumber = _firebaseAuth.currentUser?.providerData?.find { it.providerId == PhoneAuthProvider.PROVIDER_ID }?.phoneNumber + if (!linkedNumber.isNullOrEmpty()) { + _firebaseAuth.currentUser?.reload()?.await() + _firebaseAuth.currentUser?.unlink(PhoneAuthProvider.PROVIDER_ID)?.await() + } + _firebaseAuth.currentUser?.linkWithCredential(phoneCredential)?.await() + }catch (e: FirebaseAuthException){ + _phoneAuthErrorMessage.value = e.message.toString() + _userInputSmsCodeValidation.value = _phoneAuthErrorMessage.value + } + } + return _phoneAuthErrorMessage.value + } + + // 전화 인증 발송 + private fun sendPhoneAuthCode(activity: Activity, startSmsReceiver: ()->Unit){ + // 전화 인증 여부를 초기화 + _isVerifiedPhone.value = false + _isPhoneAuthExpired.value = false + + // 전화번호 앞에 "+82 " 국가코드 붙여주기 + val setNumber = _userInputPhone.value.replaceRange(0,1,"+82 ") + + _firebaseAuth.setLanguageCode("kr") + + val options = PhoneAuthOptions.newBuilder(_firebaseAuth) + .setPhoneNumber(setNumber) // Phone number to verify + .setTimeout(60L, TimeUnit.SECONDS) // Timeout and unit + .setActivity(activity) // Activity (for callback binding) + .setCallbacks(phoneAuthCallbacks) // OnVerificationStateChangedCallbacks + .build() + PhoneAuthProvider.verifyPhoneNumber(options) + startSmsReceiver() + } + + // 전화 인증코드 발송 콜백 + private val phoneAuthCallbacks = object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() { + + override fun onCodeAutoRetrievalTimeOut(p0: String) { + // 제한 시간이 경과된 경우 + super.onCodeAutoRetrievalTimeOut(p0) + _isVerifiedPhone.value = false + _isPhoneAuthExpired.value = true + } + + override fun onVerificationCompleted(credential: PhoneAuthCredential) { + // 입력한 전화번호가 정상적으로 확인될 경우(인증이 완료된게 아님, 실제 번호일때만 호출됨) + _isVerifiedPhone.value = true + } + + override fun onVerificationFailed(e: FirebaseException) { + // 입력한 전화번호 또는 인증번호가 잘못되었을 경우 + _isVerifiedPhone.value = false + _userInputPhoneValidation.value = e.message ?: "인증에 실패했습니다." + _isPhoneAuthExpired.value = true + _hideLoadingCallback.value.invoke() + } + + override fun onCodeSent( + verificationId: String, + token: PhoneAuthProvider.ForceResendingToken + ) { + // verificationId는 문자로 받는 코드가 아니었다 + _phoneAuthVerificationId.value = verificationId + _isPhoneAuthCodeSent.value = true + _userInputSmsCode.value = "" + _phoneAuthTimer.start() + } + } + + fun phoneAuthButtonClickEvent(activity: Activity){ + _showLoadingCallback.value.invoke() + // 전화번호 유효성 검사 먼저 한 후 + if(!checkUserInputPhoneValidation()){ + _hideLoadingCallback.value.invoke() + return + } + + // 응답한 전화번호로 인증번호 SMS 보내기 + sendPhoneAuthCode(activity){ + startSmsReceiver(activity) + } + } + + private val smsReceiver by lazy { SmsReceiver() } + + private fun startSmsReceiver(context: Context){ + SmsRetriever.getClient(context).startSmsRetriever().also { task -> + task.addOnSuccessListener { + + if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU){ + context.registerReceiver(smsReceiver, smsReceiver.doFilter(), + Context.RECEIVER_NOT_EXPORTED) + }else{ + context.registerReceiver(smsReceiver, smsReceiver.doFilter()) + } + + viewModelScope.launch { + SmsReceiver.smsCode.collectLatest { + setUserInputSmsCode(it) + } + } + } + task.addOnFailureListener { + stopSmsReceiver(context) + } + } + } + + fun stopSmsReceiver(context: Context){ + try { + context.unregisterReceiver(smsReceiver) + } catch (e: IllegalArgumentException){ + Log.e("stopSmsReceiver", "${e.message}") + } + } + + // ================3. 초기화 ============================================================== + fun resetStep2States(){ + _userInputName.value = "" + _userInputPhone.value = "" + _userInputSmsCode.value = "" + _userInputNameValidation.value = "" + _userInputPhoneValidation.value = "" + _userInputSmsCodeValidation.value = "" + + _isPhoneAuthCodeSent.value = false + _phoneAuthVerificationId.value = "" + _isVerifiedPhone.value = false + _phoneAuthErrorMessage.value = "" + _isAlreadyRegisteredPhoneUser.value = false + _alreadyRegisteredUserEmail.value = "" + _alreadyRegisteredUserProvider.value = "" + _phoneAuthButtonText.value = "인증하기" + _isPhoneAuthExpired.value = true + } + + fun cancelPhoneAuthTimer(){ + _phoneAuthTimer.cancel() + _phoneAuthButtonText.value = "인증하기" + _isPhoneAuthExpired.value = true + } +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinStep2ViewModel.kt b/app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinStep2ViewModel.kt deleted file mode 100644 index 74fd741e..00000000 --- a/app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinStep2ViewModel.kt +++ /dev/null @@ -1,277 +0,0 @@ -package kr.co.lion.modigm.ui.join.vm - -import android.app.Activity -import android.os.CountDownTimer -import androidx.lifecycle.ViewModel -import com.google.firebase.FirebaseException -import com.google.firebase.auth.AuthCredential -import com.google.firebase.auth.FirebaseAuth -import com.google.firebase.auth.FirebaseAuthException -import com.google.firebase.auth.PhoneAuthCredential -import com.google.firebase.auth.PhoneAuthOptions -import com.google.firebase.auth.PhoneAuthProvider -import dagger.hilt.android.lifecycle.HiltViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow -import kotlinx.coroutines.tasks.await -import kr.co.lion.modigm.repository.JoinUserRepository -import java.util.concurrent.TimeUnit -import java.util.regex.Pattern -import javax.inject.Inject - -@HiltViewModel -class JoinStep2ViewModel @Inject constructor( - private val _db: JoinUserRepository, - private val _auth: FirebaseAuth -): ViewModel() { - // ================0. SMS 인증 코드 관련 프로그래스 바 이벤트====================================================== - val showCallback = MutableStateFlow<(() -> Unit)?>(null) - fun showLoading(){ - showCallback.value?.invoke() - } - - val hideCallback = MutableStateFlow<(() -> Unit)?>(null) - fun hideLoading(){ - hideCallback.value?.invoke() - } - - // ================1. 유효성 검사 관련============================================================== - - // 이름 - val userName = MutableStateFlow("") - // 이름 유효성 검사 - val nameValidation = MutableStateFlow("") - - // 전화번호 - val userPhone = MutableStateFlow("") - // 전화번호 유효성 검사 - val phoneValidation = MutableStateFlow("") - - // 인증번호입력 - val inputSmsCode = MutableStateFlow("") - // 인증번호입력 유효성 검사 - val inputSmsCodeValidation = MutableStateFlow("") - - // 유효성 검사 - fun validate(): Boolean { - // 에러 표시 초기화 - nameValidation.value ="" - phoneValidation.value ="" - inputSmsCodeValidation.value ="" - var result = true - - if(userName.value.isEmpty()){ - nameValidation.value = "이름을 입력해주세요." - result = false - } - if(userPhone.value.isEmpty()){ - phoneValidation.value = "전화번호를 입력해주세요." - result = false - }else{ - if(!Pattern.matches("^01(?:0|1|[6-9])-(?:\\d{3}|\\d{4})-\\d{4}$", userPhone.value)){ - phoneValidation.value = "올바른 전화번호가 아닙니다." - result = false - }else if(!_isCodeSent.value){ - phoneValidation.value = "인증하기 버튼을 눌러서 인증을 진행해주세요." - result = false - } - } - if(inputSmsCode.value.isEmpty()){ - inputSmsCodeValidation.value = "인증번호를 입력해주세요." - result = false - } - return result - } - - // 인증하기 버튼 눌렀을 때 유효성 검사 - fun checkPhoneValidation(): Boolean { - // 인증번호 입력칸 초기화 - inputSmsCode.value = "" - // 에러 표시 초기화 - phoneValidation.value ="" - var result = true - - if(userPhone.value.isEmpty()){ - phoneValidation.value = "전화번호를 입력해주세요." - result = false - }else{ - if(!Pattern.matches("^01(?:0|1|[6-9])-(?:\\d{3}|\\d{4})-\\d{4}$", userPhone.value)){ - phoneValidation.value = "올바른 전화번호가 아닙니다." - result = false - } - } - return result - } - - // ================2. 전화번호 인증 관련============================================================== - - private val _authButtonText = MutableStateFlow("인증하기") - val authButtonText: StateFlow = _authButtonText - - private val _authExpired = MutableStateFlow(true) - val authExpired: StateFlow = _authExpired - - // 인증문자 발송 여부 - private val _isCodeSent = MutableStateFlow(false) - val isCodeSent: StateFlow = _isCodeSent - fun resetIsCodeSent(){ - _isCodeSent.value = false - } - - // 인증 ID(인증 코드 내용 아님) - private val _verificationId = MutableStateFlow("") - val verificationId: StateFlow = _verificationId - - // 올바른 전화번호 확인 여부 - // onVerificationCompleted, onVerificationFailed에서 확인 - private val _isVerifiedPhone = MutableStateFlow(false) - val isVerifiedPhone: StateFlow = _isVerifiedPhone - - // 인증 에러 메시지 - private val _errorMessage = MutableStateFlow("") - - // 나중에 이메일 계정과 합칠 때 필요한 전화번호 인증 credential - private var _credential = MutableStateFlow(null) - - // 이미 등록된 전화번호 계정이 있는지 여부 - private val _alreadyRegisteredUser = MutableStateFlow(false) - - // 이미 등록된 전화번호 계정의 이메일 - private val _alreadyRegisteredUserEmail = MutableStateFlow("") - val alreadyRegisteredUserEmail: StateFlow = _alreadyRegisteredUserEmail - - // 이미 등록된 전화번호 계정의 프로바이더 - private val _alreadyRegisteredUserProvider = MutableStateFlow("") - val alreadyRegisteredUserProvider: StateFlow = _alreadyRegisteredUserProvider - - // 문자 수신 60초 타이머 - val setTimer: CountDownTimer by lazy { - _authExpired.value = false - object : CountDownTimer(60000, 1000){ - override fun onTick(millisUntilFinished: Long) { - _authButtonText.value = "${millisUntilFinished/1000}" - } - - override fun onFinish() { - _authExpired.value = true - _authButtonText.value = "인증하기" - } - } - } - - // 전화번호 인증 - suspend fun createPhoneUser(): String { - // DB에 해당 번호로 등록된 계정이 있는지 확인한다. - val checkResult = _db.checkUserByPhone(userPhone.value) - - checkResult.onSuccess { resultMap -> - if(resultMap != null){ - _errorMessage.value = "이미 해당 번호로 가입한 계정이 있습니다." - _alreadyRegisteredUserProvider.value = resultMap["userProvider"] ?: "" - _alreadyRegisteredUserEmail.value = resultMap["userEmail"] ?: "" - return _errorMessage.value - } - } - // 오류 메시지 - if(_isCodeSent.value){ - try{ - _errorMessage.value = "" - val phoneCredential = PhoneAuthProvider.getCredential(_verificationId.value, inputSmsCode.value) - val linkedNumber = _auth.currentUser?.providerData?.find { it.providerId == PhoneAuthProvider.PROVIDER_ID }?.phoneNumber - if (!linkedNumber.isNullOrEmpty()) { - _auth.currentUser?.reload()?.await() - _auth.currentUser?.unlink(PhoneAuthProvider.PROVIDER_ID)?.await() - } - _auth.currentUser?.linkWithCredential(phoneCredential)?.await() - }catch (e: FirebaseAuthException){ - _errorMessage.value = e.message.toString() - inputSmsCodeValidation.value = _errorMessage.value - } - } - return _errorMessage.value - } - - // 전화 인증 발송 - fun sendCode(activity: Activity, startSmsReceiver: ()->Unit){ - // 전화 인증 여부를 초기화 - _isVerifiedPhone.value = false - _authExpired.value = false - - // 전화번호 앞에 "+82 " 국가코드 붙여주기 - val setNumber = userPhone.value.replaceRange(0,1,"+82 ") - - _auth.setLanguageCode("kr") - - val options = PhoneAuthOptions.newBuilder(_auth) - .setPhoneNumber(setNumber) // Phone number to verify - .setTimeout(60L, TimeUnit.SECONDS) // Timeout and unit - .setActivity(activity) // Activity (for callback binding) - .setCallbacks(callbacks) // OnVerificationStateChangedCallbacks - .build() - PhoneAuthProvider.verifyPhoneNumber(options) - startSmsReceiver() - } - - // 전화 인증코드 발송 콜백 - private val callbacks = object : PhoneAuthProvider.OnVerificationStateChangedCallbacks() { - - override fun onCodeAutoRetrievalTimeOut(p0: String) { - // 제한 시간이 경과된 경우 - super.onCodeAutoRetrievalTimeOut(p0) - _isVerifiedPhone.value = false - } - - override fun onVerificationCompleted(credential: PhoneAuthCredential) { - // 입력한 전화번호가 정상적으로 확인될 경우(인증이 완료된게 아님, 실제 번호일때만 호출됨) - _isVerifiedPhone.value = true - } - - override fun onVerificationFailed(e: FirebaseException) { - // 입력한 전화번호 또는 인증번호가 잘못되었을 경우 - _isVerifiedPhone.value = false - phoneValidation.value = e.message ?: "인증에 실패했습니다." - } - - override fun onCodeSent( - verificationId: String, - token: PhoneAuthProvider.ForceResendingToken - ) { - // verificationId는 문자로 받는 코드가 아니었다 - _verificationId.value = verificationId - _isCodeSent.value = true - inputSmsCode.value = "" - setTimer.start() - } - } - - // ================3. 초기화 ============================================================== - fun reset(){ - userName.value = "" - userPhone.value = "" - inputSmsCode.value = "" - resetValidationText() - - _isCodeSent.value = false - _verificationId.value = "" - _isVerifiedPhone.value = false - _errorMessage.value = "" - _credential.value = null - _alreadyRegisteredUser.value = false - _alreadyRegisteredUserEmail.value = "" - _alreadyRegisteredUserProvider.value = "" - _authButtonText.value = "인증하기" - _authExpired.value = true - } - - fun resetValidationText(){ - nameValidation.value = "" - phoneValidation.value = "" - inputSmsCodeValidation.value = "" - } - - fun cancelTimer(){ - setTimer.cancel() - _authButtonText.value = "인증하기" - _authExpired.value = true - } -} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinStep3InterestViewModel.kt b/app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinStep3InterestViewModel.kt new file mode 100644 index 00000000..2c1b0007 --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinStep3InterestViewModel.kt @@ -0,0 +1,35 @@ +package kr.co.lion.modigm.ui.join.vm + +import androidx.lifecycle.ViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class JoinStep3InterestViewModel: ViewModel() { + + // 유효성 검사 여부 + private val _isInterestListValidated = MutableStateFlow(true) + val isInterestListValidated: StateFlow = _isInterestListValidated + + // 선택한 관심분야 리스트 + private val _selectedInterestList = MutableStateFlow>(mutableListOf()) + val selectedInterestList: StateFlow> = _selectedInterestList + + fun addToInterestList(interest: String) { + _selectedInterestList.value.add(interest) + } + + fun removeFromInterestList(interest: String) { + _selectedInterestList.value.remove(interest) + } + + // 유효성 검사 + fun validateStep3UserInput(): Boolean { + _isInterestListValidated.value = selectedInterestList.value.isNotEmpty() + return isInterestListValidated.value + } + + fun resetStep3States(){ + _isInterestListValidated.value = true + _selectedInterestList.value = mutableListOf() + } +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinStep3ViewModel.kt b/app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinStep3ViewModel.kt deleted file mode 100644 index d354886d..00000000 --- a/app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinStep3ViewModel.kt +++ /dev/null @@ -1,35 +0,0 @@ -package kr.co.lion.modigm.ui.join.vm - -import androidx.lifecycle.ViewModel -import kotlinx.coroutines.flow.MutableStateFlow -import kotlinx.coroutines.flow.StateFlow - -class JoinStep3ViewModel: ViewModel() { - - // 유효성 검사 여부 - private var _isValidate = MutableStateFlow(null) - val isValidate: StateFlow = _isValidate - - // 선택한 관심분야 리스트 - private val _selectedInterestList = MutableStateFlow>(mutableListOf()) - val selectedInterestList: StateFlow> = _selectedInterestList - - fun addInterest(interest: String) { - _selectedInterestList.value.add(interest) - } - - fun removeInterest(interest: String) { - _selectedInterestList.value.remove(interest) - } - - // 유효성 검사 - fun validate(): Boolean { - _isValidate.value = selectedInterestList.value.isNotEmpty() - return isValidate.value ?: false - } - - fun reset(){ - _isValidate.value = null - _selectedInterestList.value = mutableListOf() - } -} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinViewModel.kt b/app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinViewModel.kt index 9c262cc0..1dcaaa6e 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinViewModel.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/join/vm/JoinViewModel.kt @@ -16,7 +16,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import kotlinx.coroutines.tasks.await import kr.co.lion.modigm.model.UserData -import kr.co.lion.modigm.repository.JoinUserRepository +import kr.co.lion.modigm.repository.JoinRepository import kr.co.lion.modigm.repository.LoginRepository import kr.co.lion.modigm.util.JoinType import kr.co.lion.modigm.util.ModigmApplication.Companion.prefs @@ -25,28 +25,26 @@ import javax.inject.Inject @HiltViewModel class JoinViewModel @Inject constructor( - private val _joinUserRepository: JoinUserRepository, + private val _joinRepository: JoinRepository, private val _loginRepository: LoginRepository, - private val _auth: FirebaseAuth + private val _firebaseAuth: FirebaseAuth ) : ViewModel() { // 파이어베이스에 연동된 유저 - private var _user: MutableStateFlow = MutableStateFlow(null) - val user: StateFlow = _user + private var _firebaseUser: MutableStateFlow = MutableStateFlow(null) + val firebaseUser: StateFlow = _firebaseUser // provider(제공자 이름, email, kakao, github) private var _userProvider: MutableStateFlow = MutableStateFlow("") - val userProvider: StateFlow = _userProvider fun setUserProvider(provider:String){ _userProvider.value = provider } - // email - private var _userEmail: MutableStateFlow = MutableStateFlow(null) - val userEmail: StateFlow = _userEmail - fun setUserEmail(){ - if(_auth.currentUser != null){ - _userEmail.value = _auth.currentUser?.email ?: "" + // sns계정의 email + private var _snsUserEmail: MutableStateFlow = MutableStateFlow(null) + fun setSnsUserEmail(){ + if(_firebaseAuth.currentUser != null){ + _snsUserEmail.value = _firebaseAuth.currentUser?.email ?: "" } } @@ -54,11 +52,11 @@ class JoinViewModel @Inject constructor( private var _verifiedEmail: MutableStateFlow = MutableStateFlow("") val verifiedEmail: StateFlow = _verifiedEmail - // 전화번호 인증 - private var _phoneVerification: MutableStateFlow = MutableStateFlow(false) - val phoneVerification: StateFlow = _phoneVerification - fun setPhoneVerified(verified:Boolean){ - _phoneVerification.value = verified + // 전화번호 인증 여부 + private var _isPhoneNumberVerified: MutableStateFlow = MutableStateFlow(false) + val isPhoneNumberVerified: StateFlow = _isPhoneNumberVerified + fun setPhoneNumberVerificationState(verified:Boolean){ + _isPhoneNumberVerified.value = verified } // 인증된 전화번호 @@ -88,45 +86,45 @@ class JoinViewModel @Inject constructor( } // 회원가입 완료 여부 - private var _joinCompleted: MutableStateFlow = MutableStateFlow(false) - val joinCompleted: StateFlow = _joinCompleted + private var _isJoinCompleted: MutableStateFlow = MutableStateFlow(false) + val isJoinCompleted: StateFlow = _isJoinCompleted // 회원 객체 생성을 위한 정보 - private val _uid: MutableStateFlow = MutableStateFlow(null) + private val _userUid: MutableStateFlow = MutableStateFlow(null) fun setUserUid() { - if (_auth.currentUser != null) { - _uid.value = _auth.currentUser?.uid + if (_firebaseAuth.currentUser != null) { + _userUid.value = _firebaseAuth.currentUser?.uid } } - private val _email: MutableStateFlow = MutableStateFlow("") - private val _password: MutableStateFlow = MutableStateFlow("") + private val _userEmail: MutableStateFlow = MutableStateFlow("") + private val _userPassword: MutableStateFlow = MutableStateFlow("") fun setEmailAndPw(email:String, pw:String){ - _email.value = email - _password.value = pw + _userEmail.value = email + _userPassword.value = pw } private val _userName: MutableStateFlow = MutableStateFlow("") - private val _phoneNumber: MutableStateFlow = MutableStateFlow("") - fun setUserNameAndPhoneNumber(name:String, phone:String){ + private val _userPhone: MutableStateFlow = MutableStateFlow("") + fun setUserNameAndUserPhone(name:String, phone:String){ _userName.value = name - _phoneNumber.value = phone + _userPhone.value = phone } - private val _interests: MutableStateFlow?> = MutableStateFlow(null) - fun setInterests(interests:MutableList){ - _interests.value = interests + private val _userInterests: MutableStateFlow?> = MutableStateFlow(null) + fun setUserInterests(selectedInterests:MutableList){ + _userInterests.value = selectedInterests } // FirebaseAuth에 이메일 계정 등록 - suspend fun createEmailUser(): String { + suspend fun registerEmailUserToFirebaseAuth(): String { // 오류 메시지 var error = "" try { - val authResult = _auth.createUserWithEmailAndPassword(_email.value, _password.value).await() - _user.value = authResult.user - _uid.value = authResult.user?.uid?:"" - _verifiedEmail.value = _email.value + val authResult = _firebaseAuth.createUserWithEmailAndPassword(_userEmail.value, _userPassword.value).await() + _firebaseUser.value = authResult.user + _userUid.value = authResult.user?.uid?:"" + _verifiedEmail.value = _userEmail.value }catch (e:FirebaseAuthException){ if(e.errorCode=="ERROR_EMAIL_ALREADY_IN_USE"){ error = "이미 등록되어 있는 이메일 계정입니다." @@ -136,10 +134,10 @@ class JoinViewModel @Inject constructor( } // 회원가입 이탈 시 이미 Auth에 등록되어있는 인증 정보 삭제 - fun deleteCurrentUser(){ + fun deleteCurrentRegisteredFirebaseUser(){ // 인증 정보 삭제 CoroutineScope(Dispatchers.IO).launch { - _auth.currentUser?.delete()?.addOnSuccessListener { + _firebaseAuth.currentUser?.delete()?.addOnSuccessListener { Log.d("JoinViewModel", "deleteCurrentUser: 인증 정보 삭제 성공") } } @@ -150,25 +148,25 @@ class JoinViewModel @Inject constructor( // 각 화면에서 응답받은 정보 가져와서 객체 생성 후 return return UserData( -1, - _uid.value?:"", + _userUid.value?:"", _userName.value, - _phoneNumber.value, + _userPhone.value, "", "", - _userEmail.value?:_email.value, + _snsUserEmail.value?:_userEmail.value, _userProvider.value, - _interests.value?.joinToString(",")?:"", + _userInterests.value?.joinToString(",")?:"", LocalDateTime.now() ) } // 회원 가입 완료 - fun completeJoinUser(handler: CoroutineExceptionHandler){ + fun completeJoinProcess(handler: CoroutineExceptionHandler){ viewModelScope.launch(handler) { - _user.value = _auth.currentUser + _firebaseUser.value = _firebaseAuth.currentUser val user = createUserInfoData() - val result = _joinUserRepository.insetUserData(user) + val result = _joinRepository.insetUserData(user) result.onSuccess { userIdx -> // SharedPreferences에 유저 idx 저장 prefs.setInt("currentUserIdx", userIdx) @@ -177,25 +175,25 @@ class JoinViewModel @Inject constructor( registerFcmTokenToServer(userIdx) } // 회원가입 완료 처리 - _joinCompleted.value = true + _isJoinCompleted.value = true }.onFailure { - _joinCompleted.value = false + _isJoinCompleted.value = false throw Exception("회원가입에 실패했습니다. 잠시 후 다시 시도해주세요.") } } } // 상태값 초기화 - fun reset(){ - _user.value = null + fun resetViewModelStates(){ + _firebaseUser.value = null _userProvider.value = "" - _userEmail.value = null + _snsUserEmail.value = null _verifiedEmail.value = "" - _phoneVerification.value = false + _isPhoneNumberVerified.value = false _verifiedPhoneNumber.value = "" @@ -205,22 +203,22 @@ class JoinViewModel @Inject constructor( _alreadyRegisteredUserProvider.value = "" - _joinCompleted.value = false + _isJoinCompleted.value = false - _uid.value = null + _userUid.value = null - _email.value = "" - _password.value = "" + _userEmail.value = "" + _userPassword.value = "" _userName.value = "" - _phoneNumber.value = "" + _userPhone.value = "" - _interests.value = null + _userInterests.value = null } - fun signOut(){ - if(_auth.currentUser != null){ - _auth.signOut() + fun signOutCurrentFirebaseUser(){ + if(_firebaseAuth.currentUser != null){ + _firebaseAuth.signOut() } } diff --git a/app/src/main/java/kr/co/lion/modigm/ui/notification/NotificationErrorHandler.kt b/app/src/main/java/kr/co/lion/modigm/ui/notification/NotificationErrorHandler.kt new file mode 100644 index 00000000..00946db4 --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/notification/NotificationErrorHandler.kt @@ -0,0 +1,43 @@ +package kr.co.lion.modigm.ui.notification +import android.content.Context +import android.util.Log +import android.widget.Toast +import org.apache.http.HttpException +import java.io.IOException +import java.net.SocketTimeoutException +import java.net.UnknownHostException + +object NotificationErrorHandler { + fun handleException(context: Context, e: Exception, logTag: String) { + when (e) { + is IOException -> { // 네트워크 오류 + Log.e(logTag, "Network error: ${e.message}", e) + showErrorMessage(context, "인터넷 연결을 확인해주세요.") + } + + is HttpException -> { // 서버 오류 + Log.e(logTag, "Server error: ${e.message}", e) + showErrorMessage(context, "서버 응답이 없습니다. 잠시 후 다시 시도해주세요.") + } + + is SocketTimeoutException -> { // 서버 응답 지연 + Log.e(logTag, "SocketTimeoutException: ${e.message}", e) + showErrorMessage(context, "서버 응답 시간이 초과되었습니다. 잠시 후 다시 시도해주세요.") + } + + is UnknownHostException -> { // 서버 찾을 수 없음 + Log.e(logTag, "UnknownHostException: ${e.message}", e) + showErrorMessage(context, "네트워크 연결이 원활하지 않습니다.") + } + + else -> { // 기타 오류 + Log.e(logTag, "Unexpected error: ${e.message}", e) + showErrorMessage(context, "알 수 없는 오류가 발생했습니다.") + } + } + } + + private fun showErrorMessage(context: Context, message: String) { + Toast.makeText(context, message, Toast.LENGTH_SHORT).show() + } +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/notification/NotificationFragment.kt b/app/src/main/java/kr/co/lion/modigm/ui/notification/NotificationFragment.kt index 8a902aa8..3b0f547e 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/notification/NotificationFragment.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/notification/NotificationFragment.kt @@ -8,6 +8,7 @@ import android.content.IntentFilter import android.os.Bundle import android.util.Log import android.view.View +import android.widget.Toast import androidx.fragment.app.viewModels import androidx.lifecycle.lifecycleScope import androidx.localbroadcastmanager.content.LocalBroadcastManager @@ -22,6 +23,10 @@ import kr.co.lion.modigm.ui.VBBaseFragment import kr.co.lion.modigm.ui.notification.adapter.NotificationAdapter import kr.co.lion.modigm.util.ModigmApplication import kr.co.lion.modigm.util.ModigmApplication.Companion.prefs +import org.apache.http.HttpException +import java.io.IOException +import java.net.SocketTimeoutException +import java.net.UnknownHostException class NotificationFragment : VBBaseFragment(FragmentNotificationBinding::inflate) { @@ -32,25 +37,47 @@ class NotificationFragment : VBBaseFragment(Fragmen private val dataRefreshReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { // 데이터 갱신 요청 수신 시 호출되는 메소드 - refreshData() + fetchAndDisplayNotifications() } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - initView() - observeViewModel() + setupUI() + registerReceiver() + fetchAndDisplayNotifications() + } + + private fun setupUI() { + setupToolbar() + setupRecyclerView() - val userIdx = ModigmApplication.prefs.getInt("currentUserIdx", 0) - viewModel.fetchNotifications(userIdx) // 알림 데이터 가져오기 + observeNotifications() + observeLoadingState() + } + private fun registerReceiver() { // 데이터 새로고침 브로드캐스트 리시버 등록 LocalBroadcastManager.getInstance(requireContext()) .registerReceiver(dataRefreshReceiver, IntentFilter("ACTION_REFRESH_DATA")) } + private fun setupToolbar() { + with(binding.toolBarNotification) { + title = "알림" + inflateMenu(R.menu.menu_notification_toolbar) // 메뉴 설정 + setOnMenuItemClickListener { item -> + when (item.itemId) { + R.id.notification_toolbar_refresh -> { + fetchAndDisplayNotifications() // 새로고침 실행 + true + } + else -> false + } + } + } + } - private fun initView() { - settingToolbar() + private fun setupRecyclerView() { binding.recyclerviewNotification.layoutManager = LinearLayoutManager(requireContext()) // NotificationAdapter 생성 시 onDeleteClick 람다 전달 adapter = NotificationAdapter( @@ -61,132 +88,152 @@ class NotificationFragment : VBBaseFragment(Fragmen binding.recyclerviewNotification.adapter = adapter } - private fun observeViewModel() { + private fun observeNotifications() { // Fragment에서 lifecycleScope을 사용하여 StateFlow 구독 lifecycleScope.launchWhenStarted { viewModel.notifications.collect { notifications -> Log.d("NotificationFragment", "Notifications: $notifications") - if (notifications.isNotEmpty()) { - binding.blankLayoutNotification.visibility = View.GONE - binding.recyclerviewNotification.visibility = View.VISIBLE - adapter.updateData(notifications) // RecyclerView 어댑터 데이터 업데이트 - } else { - binding.blankLayoutNotification.visibility = View.VISIBLE - binding.recyclerviewNotification.visibility = View.GONE - clearBadgeOnBottomNavigation() // 알림이 없을 때 배지를 지움 - } + updateNotificationUI(notifications) // 알림 UI 업데이트 } } + } + private fun observeLoadingState() { // 로딩 상태 관찰 lifecycleScope.launchWhenStarted { viewModel.isLoading.collect { isLoading -> - showLoading(isLoading) + if (isLoading) showLoading() else hideLoading() } } } - private fun showLoading(isLoading: Boolean) { - binding.progressBar.visibility = if (isLoading) View.VISIBLE else View.GONE - if (isLoading) { - // 로딩 중에는 알림 목록 및 빈 레이아웃 모두 숨김 - binding.recyclerviewNotification.visibility = View.GONE - binding.blankLayoutNotification.visibility = View.GONE + private fun showLoading() { + binding.progressBar.visibility = View.VISIBLE + hideNotificationUI() + } + + private fun hideLoading() { + binding.progressBar.visibility = View.GONE + updateNotificationUI(viewModel.notifications.value) + } + + private fun updateNotificationUI(notifications: List) { + if (notifications.isNotEmpty()) { + displayNotifications(notifications) } else { - // 로딩이 끝나면 알림 상태에 따라 레이아웃 가시성 조정 (isLoading이 끝난 후 조정) - if (viewModel.notifications.value.isEmpty()) { - binding.blankLayoutNotification.visibility = View.VISIBLE - } else { - binding.recyclerviewNotification.visibility = View.VISIBLE - } + displayEmptyState() } } + private fun displayNotifications(notifications: List) { + binding.blankLayoutNotification.visibility = View.GONE + binding.recyclerviewNotification.visibility = View.VISIBLE + adapter.updateData(notifications) + } + + private fun displayEmptyState() { + binding.blankLayoutNotification.visibility = View.VISIBLE + binding.recyclerviewNotification.visibility = View.GONE + clearBadgeOnBottomNavigation() + } + + private fun hideNotificationUI() { + binding.recyclerviewNotification.visibility = View.GONE + binding.blankLayoutNotification.visibility = View.GONE + } + private fun clearBadgeOnBottomNavigation() { // 모든 알림이 삭제된 경우, BottomNaviFragment에 배지를 숨기라는 브로드캐스트를 전송합니다. - val intent = Intent("ACTION_HIDE_NOTIFICATION_BADGE") - LocalBroadcastManager.getInstance(requireContext()).sendBroadcast(intent) + LocalBroadcastManager.getInstance(requireContext()).sendBroadcast(Intent("ACTION_HIDE_NOTIFICATION_BADGE")) } private fun deleteNotification(notification: NotificationData) { - val userIdx = prefs.getInt("currentUserIdx", 0) - lifecycleScope.launch { - val result = viewModel.deleteNotification(notification) // MySQL에서 삭제 - if (result) { - viewModel.refreshNotifications(userIdx) // RecyclerView 갱신 - checkAndUpdateBadge() // 모든 알림 삭제 후 상태 업데이트 + try { + val userIdx = ModigmApplication.prefs.getInt("currentUserIdx", 0) + val isDeleted = viewModel.deleteNotification(notification) + + if (isDeleted) { + viewModel.refreshNotifications(userIdx)// RecyclerView 갱신 + updateBadgeState()// 모든 알림 상태 업데이트 + } else { + Log.e("NotificationFragment", "Failed to delete notification: ${notification.notificationIdx}") + showErrorMessage("알림을 삭제하는 데 실패했습니다.") + } + } catch (e: Exception) { + NotificationErrorHandler.handleException(requireContext(), e, "NotificationFragment") } } } - // **배지 상태를 확인하는 메서드** - private fun checkAndUpdateBadge() { - // 남은 읽지 않은 알림이 있는지 확인 - val hasUnreadNotifications = viewModel.notifications.value.any { !it.isRead } - - // **읽지 않은 알림이 없으면 바텀 네비 배지를 숨김** - if (!hasUnreadNotifications) { + private fun updateBadgeState() { + if (viewModel.notifications.value.none { !it.isRead }) { clearBadgeOnBottomNavigation() } } - private fun settingToolbar() { - with(binding) { - toolBarNotification.title = "알림" - toolBarNotification.inflateMenu(R.menu.menu_notification_toolbar) // 메뉴 설정 - toolBarNotification.setOnMenuItemClickListener { item -> - when (item.itemId) { - R.id.notification_toolbar_refresh -> { - refreshData() // 새로고침 실행 - true - } - else -> false - } - } + private fun fetchAndDisplayNotifications(){ + try { + // 현재 사용자 ID 가져오기 + val userIdx = ModigmApplication.prefs.getInt("currentUserIdx", 0) + // ViewModel을 통해 알림 데이터를 다시 가져옵니다. + viewModel.fetchNotifications(userIdx) + } catch (e: Exception) { + NotificationErrorHandler.handleException(requireContext(), e, "NotificationFragment") } } - private fun refreshData() { - // 현재 사용자 ID 가져오기 - val userIdx = ModigmApplication.prefs.getInt("currentUserIdx", 0) - Log.d("NotificationFragment", "Refreshing notifications for userIdx: $userIdx") - // ViewModel을 통해 알림 데이터를 다시 가져옵니다. - viewModel.fetchNotifications(userIdx) + // 사용자에게 에러 메시지를 보여주는 함수 + private fun showErrorMessage(message: String) { + Toast.makeText(requireContext(), message, Toast.LENGTH_SHORT).show() } override fun onResume() { super.onResume() - // 화면으로 돌아올 때 모든 알림을 읽음으로 표시 - LocalBroadcastManager.getInstance(requireContext()).sendBroadcast(Intent("ACTION_MARK_ALL_READ")) + handleScreenVisibilityChange() } override fun onPause() { super.onPause() // 알림 화면을 벗어날 때 모든 알림을 읽음으로 표시 - LocalBroadcastManager.getInstance(requireContext()).sendBroadcast(Intent("ACTION_MARK_ALL_READ")) + handleScreenVisibilityChange() + } + + private fun handleScreenVisibilityChange() { + try { + viewModel.markAllNotificationsAsRead() + + //UI 관련 이벤트는 Fragment에서 처리 + sendMarkAllReadBroadcast() + + } catch (e: Exception) { + NotificationErrorHandler.handleException(requireContext(), e, "NotificationFragment") + } + } + + private fun sendMarkAllReadBroadcast() { + LocalBroadcastManager.getInstance(requireContext()) + .sendBroadcast(Intent("ACTION_MARK_ALL_READ")) } override fun onDestroyView() { super.onDestroyView() // 리시버 해제 LocalBroadcastManager.getInstance(requireContext()).unregisterReceiver(dataRefreshReceiver) - adapter.onDestroy() // Adapter의 리시버 해제 } private fun markNotificationAsRead(notification: NotificationData) { lifecycleScope.launch { - viewModel.markNotificationAsRead(notification.notificationIdx) // 서버에 읽음 상태 업데이트 - - // **알림 읽음 처리 후 isRead 값을 true로 업데이트** - notification.isRead = true - - // **읽음 처리한 알림의 배지만 사라지게 함** - adapter.updateData(viewModel.notifications.value) - - // **남은 읽지 않은 알림이 없다면 바텀 네비 배지 숨기기** - checkAndUpdateBadge() + try { + viewModel.markNotificationAsRead(notification.notificationIdx) // 서버에 읽음 상태 업데이트 + + // **알림 읽음 처리 후 isRead 값을 true로 업데이트** + notification.isRead = true + adapter.updateData(viewModel.notifications.value) + updateBadgeState() + } catch (e: Exception) { + NotificationErrorHandler.handleException(requireContext(), e, "NotificationFragment") + } } } - } diff --git a/app/src/main/java/kr/co/lion/modigm/ui/notification/vm/NotificationViewModel.kt b/app/src/main/java/kr/co/lion/modigm/ui/notification/vm/NotificationViewModel.kt index 4698436b..91fe9abf 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/notification/vm/NotificationViewModel.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/notification/vm/NotificationViewModel.kt @@ -1,6 +1,8 @@ +import android.content.Intent import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import androidx.localbroadcastmanager.content.LocalBroadcastManager import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow @@ -8,6 +10,7 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import kr.co.lion.modigm.model.NotificationData import kr.co.lion.modigm.repository.NotificationRepository +import kr.co.lion.modigm.util.ModigmApplication class NotificationViewModel : ViewModel() { private val repository = NotificationRepository() @@ -79,8 +82,9 @@ class NotificationViewModel : ViewModel() { // 모든 알림을 읽음으로 표시하는 메서드 - fun markAllNotificationsAsRead(userIdx: Int) { + fun markAllNotificationsAsRead() { viewModelScope.launch { + val userIdx = ModigmApplication.prefs.getInt("currentUserIdx", 0) repository.markAllNotificationsAsRead(userIdx) refreshNotifications(userIdx) // 모든 알림 상태를 읽음으로 변경 후 데이터 갱신 } diff --git a/app/src/main/java/kr/co/lion/modigm/ui/profile/ProfileFragment.kt b/app/src/main/java/kr/co/lion/modigm/ui/profile/ProfileFragment.kt index a766a45e..b220f4a2 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/profile/ProfileFragment.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/profile/ProfileFragment.kt @@ -1,142 +1,46 @@ package kr.co.lion.modigm.ui.profile -import android.graphics.drawable.Drawable import android.os.Bundle -import android.util.Log import android.view.LayoutInflater import android.view.View import android.view.ViewGroup -import android.widget.ImageView -import android.widget.LinearLayout -import android.widget.PopupWindow -import android.widget.TextView -import androidx.appcompat.content.res.AppCompatResources +import androidx.compose.ui.platform.ComposeView +import androidx.fragment.app.Fragment import androidx.fragment.app.commit import androidx.fragment.app.viewModels -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope -import androidx.recyclerview.widget.LinearLayoutManager -import androidx.recyclerview.widget.RecyclerView -import com.bumptech.glide.Glide -import com.bumptech.glide.request.RequestOptions -import com.bumptech.glide.request.target.CustomViewTarget -import com.google.android.material.chip.Chip -import com.google.android.play.integrity.internal.f import kotlinx.coroutines.launch import kr.co.lion.modigm.R -import kr.co.lion.modigm.databinding.FragmentProfileBinding -import kr.co.lion.modigm.ui.DBBaseFragment import kr.co.lion.modigm.ui.detail.DetailFragment -import kr.co.lion.modigm.ui.profile.adapter.LinkAdapter -import kr.co.lion.modigm.ui.profile.adapter.ProfileStudyAdapter import kr.co.lion.modigm.ui.profile.vm.ProfileViewModel +import kr.co.lion.modigm.ui.common.ModigmTheme import kr.co.lion.modigm.util.FragmentName -import kr.co.lion.modigm.util.Links -import kr.co.lion.modigm.util.ModigmApplication.Companion.prefs -import java.util.UUID -class ProfileFragment: DBBaseFragment(R.layout.fragment_profile) { - private val profileViewModel: ProfileViewModel by viewModels() +class ProfileFragment : Fragment() { + + private val viewModel: ProfileViewModel by viewModels() // onCreateView에서 초기화 var userIdx: Int? = null var isBottomNavi: Boolean? = null - // 어댑터 선언 - val linkAdapter: LinkAdapter = LinkAdapter( - // 빈 리스트를 넣어 초기화 - emptyList(), - - // 항목을 클릭: Url을 받아온다 - rowClickListener = { linkUrl -> - Log.d("테스트 rowClickListener deliveryIdx", linkUrl) - viewLifecycleOwner.lifecycleScope.launch { - // bundle 에 필요한 정보를 담는다 - val bundle = Bundle() - bundle.putString("link", linkUrl) - - // 이동할 프래그먼트로 bundle을 넘긴다 - val profileWebFragment = ProfileWebFragment() - profileWebFragment.arguments = bundle - - // Fragment 교체 - requireActivity().supportFragmentManager.commit { - setCustomAnimations(R.anim.slide_in, R.anim.fade_out, R.anim.fade_in, R.anim.slide_out) - replace(R.id.containerMain, profileWebFragment) - addToBackStack(FragmentName.PROFILE_WEB.str) - } - } - } - ) - - val partStudyAdapter: ProfileStudyAdapter = ProfileStudyAdapter( - // 빈 리스트를 넣어 초기화 - emptyList(), - - // 항목을 클릭: 스터디 고유번호를 이용하여 해당 스터디 화면으로 이동한다 - rowClickListener = { studyIdx -> - viewLifecycleOwner.lifecycleScope.launch { - val detailFragment = DetailFragment() - - // Bundle 생성 및 현재 사용자 uid 담기 - val bundle = Bundle() - Log.d("zunione", "$studyIdx") - bundle.putInt("studyIdx", studyIdx) - - // Bundle을 ProfileFragment에 설정 - detailFragment.arguments = bundle - - requireActivity().supportFragmentManager.commit { - setCustomAnimations(R.anim.slide_in, R.anim.fade_out, R.anim.fade_in, R.anim.slide_out) - replace(R.id.containerMain, detailFragment) - addToBackStack(FragmentName.DETAIL.str) - } - } - } - ) - - val hostStudyAdapter: ProfileStudyAdapter = ProfileStudyAdapter( - // 빈 리스트를 넣어 초기화 - emptyList(), - - // 항목을 클릭: 스터디 고유번호를 이용하여 해당 스터디 화면으로 이동한다 - rowClickListener = { studyIdx -> - viewLifecycleOwner.lifecycleScope.launch { - val detailFragment = DetailFragment() - - // Bundle 생성 및 현재 사용자 uid 담기 - val bundle = Bundle() - Log.d("zunione", "$studyIdx") - bundle.putInt("studyIdx", studyIdx) - - // Bundle을 ProfileFragment에 설정 - detailFragment.arguments = bundle - - requireActivity().supportFragmentManager.commit { - setCustomAnimations(R.anim.slide_in, R.anim.fade_out, R.anim.fade_in, R.anim.slide_out) - replace(R.id.containerMain, detailFragment) - addToBackStack(FragmentName.DETAIL.str) - } - } - } - ) - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - super.onCreateView(inflater, container, savedInstanceState) - // Bind ViewModel and lifecycle owner - binding.profileViewModel = profileViewModel - binding.lifecycleOwner = this.viewLifecycleOwner - userIdx = arguments?.getInt("userIdx") isBottomNavi = arguments?.getBoolean("isBottomNavi") - return binding.root - } + return ComposeView(requireContext()).apply { + setContent { + ModigmTheme { + ProfileScreen( + viewModel = viewModel, + changeToSettingsFragment = { changeToSettingsFragment() }, + changeToLinkWebView = { link -> changeToLinkWebView(link) }, + changeToDetailFragment = { studyIdx -> changeToDetailFragment(studyIdx) } - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - initView() + ) + } + } + } } override fun onResume() { @@ -144,335 +48,78 @@ class ProfileFragment: DBBaseFragment(R.layout.fragment_ setupUserInfo() } - private fun initView() { - setupToolbar() - setupRecyclerViewLink() - setupRecyclerViewPartStudy() - setupRecyclerViewHostStudy() - setupIconMoreStudy() - - observeData() + private fun setupUserInfo() { + viewModel.profileUserIdx.value = userIdx + viewModel.loadUserData() + viewModel.loadUserLinkListData() + viewModel.loadHostStudyList(userIdx!!) + viewModel.loadPartStudyList(userIdx!!) } - private fun setupToolbar() { - binding.toolbarProfile.apply { - // 툴바 메뉴 - inflateMenu(R.menu.menu_profile) - setOnMenuItemClickListener { - when (it.itemId) { - R.id.menu_item_profile_setting -> { - requireActivity().supportFragmentManager.commit { - setCustomAnimations(R.anim.slide_in, R.anim.fade_out, R.anim.fade_in, R.anim.slide_out) - replace(R.id.containerMain, SettingsFragment()) - addToBackStack(FragmentName.SETTINGS.str) - } - } - - R.id.menu_item_profile_more -> { - showDropdownReport(this.findViewById(R.id.menu_item_profile_more)) - } - } - true - } - - // 모든 메뉴를 보이지 않는 상태로 둔다. - // 사용자 정보를 가져온 다음 메뉴를 노출 시킨다. - menu.findItem(R.id.menu_item_profile_setting).isVisible = false - menu.findItem(R.id.menu_item_profile_more).isVisible = false - - // BottomNavigation 으로 접근했을 때: 설정 아이콘 - if (isBottomNavi == true) { - // 설정 아이콘 표시 - menu.findItem(R.id.menu_item_profile_setting).isVisible = true - } else { - // 스터디 상세 화면의 프로필로 접근했을 때: 뒤로 가기, 더보기 아이콘 - // 뒤로 가기 - setNavigationIcon(R.drawable.icon_arrow_back_24px) - setNavigationOnClickListener { - parentFragmentManager.popBackStack() - } + private fun changeToSettingsFragment() { + viewLifecycleOwner.lifecycleScope.launch { + val settingsFragment = SettingsFragment() - // 더보기 아이콘: 신고 기능이므로 타인의 프로필일 때만 표시 - if (userIdx != prefs.getInt("currentUserIdx")) { - menu.findItem(R.id.menu_item_profile_more).isVisible = true - } + // Fragment 교체 + requireActivity().supportFragmentManager.commit { + setCustomAnimations( + R.anim.slide_in, + R.anim.fade_out, + R.anim.fade_in, + R.anim.slide_out + ) + replace(R.id.containerMain, settingsFragment) + addToBackStack(FragmentName.SETTINGS.str) } } } - private fun showDropdownReport(anchorView: View) { - // 팝업 윈도우의 레이아웃을 설정 - val popupView = LayoutInflater.from(requireContext()).inflate(R.layout.custom_profile_report_dropdown, null) - - // 팝업 윈도우 객체 생성 - val popupWindow = PopupWindow(popupView, ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) - - // 배경을 설정해야 그림자가 적용됨 (반드시 배경이 있어야 함) - popupWindow.setBackgroundDrawable(AppCompatResources.getDrawable(requireContext(), android.R.drawable.dialog_holo_light_frame)) - - // 팝업 윈도우 외부를 터치하면 닫히도록 설정 - popupWindow.isOutsideTouchable = true - popupWindow.isFocusable = true - - // 팝업 윈도우를 anchorView 아래에 표시 - popupWindow.showAsDropDown(anchorView, -200, -30) - popupWindow.elevation = 10f - - // 팝업 안의 아이템 클릭 리스너 설정 - val item1: LinearLayout = popupView.findViewById(R.id.layoutProfileReport) - item1.isClickable = true - - popupView.findViewById(R.id.menuItem3).setOnClickListener { - Log.d("zunione", "touched") - // 아이템 1이 클릭되었을 때의 처리 - openWebView() - popupWindow.dismiss() - } - } - - private fun openWebView(){ + private fun changeToLinkWebView(link: String) { viewLifecycleOwner.lifecycleScope.launch { - // 회원 신고하기 구글폼 띄우고 필요한 값 전달 by ms. - val link = StringBuilder(Links.USER_SERVICE.url) - // 신고 대상 idx - link.append("&entry.1881471803=${UUID.randomUUID().toString().split("-")[4]}#${userIdx}") - // 신고자 idx - link.append("&entry.759987217=${UUID.randomUUID().toString().split("-")[4]}#${prefs.getInt("currentUserIdx")}") - // bundle 에 필요한 정보를 담는다 val bundle = Bundle() - bundle.putString("link", link.toString()) + bundle.putString("link", link) // 이동할 프래그먼트로 bundle을 넘긴다 val profileWebFragment = ProfileWebFragment() profileWebFragment.arguments = bundle // Fragment 교체 - parentFragmentManager.commit { - //setCustomAnimations(R.anim.slide_in, R.anim.fade_out, R.anim.fade_in, R.anim.slide_out) + requireActivity().supportFragmentManager.commit { + setCustomAnimations( + R.anim.slide_in, + R.anim.fade_out, + R.anim.fade_in, + R.anim.slide_out + ) replace(R.id.containerMain, profileWebFragment) addToBackStack(FragmentName.PROFILE_WEB.str) } } } - private fun setupUserInfo() { - profileViewModel.profileUserIdx.value = userIdx - profileViewModel.loadUserData() - profileViewModel.loadUserLinkListData() - profileViewModel.loadHostStudyList(userIdx!!) - profileViewModel.loadPartStudyList(userIdx!!) - } - - private fun setupRecyclerViewLink() { - // 리사이클러뷰 구성 - binding.recyclerVIewProfileLink.apply { - // 리사이클러뷰 어댑터 - adapter = linkAdapter - - // 리사이클러뷰 레이아웃 - layoutManager = LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL, false) - } - } - - private fun setupRecyclerViewPartStudy() { - // 리사이클러뷰 구성 - binding.recyclerViewProfilePartStudy.apply { - // 리사이클러뷰 어댑터 - adapter = partStudyAdapter - - // 리사이클러뷰 레이아웃 - layoutManager = LinearLayoutManager(requireContext()) - } - } - - private fun setupRecyclerViewHostStudy() { - // 리사이클러뷰 구성 - binding.recyclerViewProfileHostStudy.apply { - // 리사이클러뷰 어댑터 - adapter = hostStudyAdapter - - // 리사이클러뷰 레이아웃 - layoutManager = LinearLayoutManager(requireContext()) - } - } - - private fun setupIconMoreStudy() { - binding.apply { - layoutMoreProfileHostStudy.setOnClickListener { - // bundle 에 필요한 정보를 담는다 - val bundle = Bundle() - bundle.putInt("type", 1) - bundle.putInt("userIdx", userIdx!!) - - // 이동할 프래그먼트로 bundle을 넘긴다 - val profileStudyFragment = ProfileStudyFragment() - profileStudyFragment.arguments = bundle - - requireActivity().supportFragmentManager.commit { - setCustomAnimations(R.anim.slide_in, R.anim.fade_out, R.anim.fade_in, R.anim.slide_out) - replace(R.id.containerMain, profileStudyFragment) - addToBackStack(FragmentName.PROFILE_STUDY.str) - } - } - - layoutMoreProfilePartStudy.setOnClickListener { - // bundle 에 필요한 정보를 담는다 - val bundle = Bundle() - bundle.putInt("type", 2) - bundle.putInt("userIdx", userIdx!!) - - // 이동할 프래그먼트로 bundle을 넘긴다 - val profileStudyFragment = ProfileStudyFragment() - profileStudyFragment.arguments = bundle + private fun changeToDetailFragment(studyIdx: Int) { + viewLifecycleOwner.lifecycleScope.launch { + val detailFragment = DetailFragment() - requireActivity().supportFragmentManager.commit { - setCustomAnimations(R.anim.slide_in, R.anim.fade_out, R.anim.fade_in, R.anim.slide_out) - replace(R.id.containerMain, profileStudyFragment) - addToBackStack(FragmentName.PROFILE_STUDY.str) - } + // Bundle 생성 및 현재 사용자 uid 담기 + val bundle = Bundle() + bundle.putInt("studyIdx", studyIdx) + + // Bundle을 ProfileFragment에 설정 + detailFragment.arguments = bundle + + requireActivity().supportFragmentManager.commit { + setCustomAnimations( + R.anim.slide_in, + R.anim.fade_out, + R.anim.fade_in, + R.anim.slide_out + ) + replace(R.id.containerMain, detailFragment) + addToBackStack(FragmentName.DETAIL.str) } } } +} - // 데이터 변경 관찰 - fun observeData() { - // 자기소개 - lifecycleScope.launch { - profileViewModel.profileIntro.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).collect { intro -> - if (intro.isNullOrEmpty()) { - binding.textViewProfileIntro.visibility = View.GONE - } else { - binding.textViewProfileIntro.visibility = View.VISIBLE - } - - } - } - - // 프로필 사진 - lifecycleScope.launch { - profileViewModel.profileUserImage.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).collect { image -> - if (!image.isNullOrEmpty()) { - val requestOptions = RequestOptions() - .placeholder(R.drawable.image_loading_gray) // 필요 시 기본 플레이스홀더 설정 - .error(R.drawable.image_detail_1) // 이미지 로딩 실패 시 표시할 이미지 - - Glide.with(requireContext()) - .load(image) - .apply(requestOptions) - .into(object : CustomViewTarget(binding.imageProfilePic) { - override fun onLoadFailed(errorDrawable: Drawable?) { - // 로딩 실패 시 기본 이미지를 보여줌 - binding.imageProfilePic.setImageResource(R.drawable.image_default_profile) - } - - override fun onResourceCleared(placeholder: Drawable?) { - // 리소스가 클리어 될 때 - } - - override fun onResourceReady(resource: Drawable, transition: com.bumptech.glide.request.transition.Transition?) { - // 로딩 성공 시 - binding.imageProfilePic.setImageDrawable(resource) - } - }) - } else { - // 등록되어 있는 이미지가 없을 때 - binding.imageProfilePic.setImageResource(R.drawable.image_default_profile) - } - } - } - - // 관심 분야 chipGroup - lifecycleScope.launch { - profileViewModel.profileInterests.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).collect { interests -> - // 기존 칩들 제거 - binding.chipGroupProfile.removeAllViews() - - // 리스트가 변경될 때마다 for 문을 사용하여 아이템을 처리 - if (interests.isNullOrEmpty()) { - binding.layoutInterestChipGroup.visibility = View.GONE - } else { - binding.layoutInterestChipGroup.visibility = View.VISIBLE - val interestList = interests.split(",").map { it.trim() } - - for (interest in interestList) { - // 아이템 처리 코드 - binding.chipGroupProfile.addView(Chip(context).apply { - text = interest - setTextAppearance(R.style.ChipTextStyle) - // 자동 padding 없애기 - setEnsureMinTouchTargetSize(false) - // 배경 흰색으로 지정 - setChipBackgroundColorResource(android.R.color.white) - // 클릭 불가 - isClickable = false - }) - } - } - } - } - - // 링크 리스트 - lifecycleScope.launch { - profileViewModel.profileLinkList.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).collect { profileLinkList -> - linkAdapter.updateData(profileLinkList) - - if (profileLinkList.isEmpty()) { - binding.layoutProfileLink.visibility = View.GONE - } else { - binding.layoutProfileLink.visibility = View.VISIBLE - } - } - } - - // 진행한 스터디 리스트 - lifecycleScope.launch { - profileViewModel.profileHostStudyList.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).collect { profileHostStudyList -> - if (profileHostStudyList != null) { - hostStudyAdapter.updateData(profileHostStudyList) - } - - // 데이터 유무에 따른 뷰 가시성 설정 - if (profileHostStudyList.isNullOrEmpty()) { - binding.layoutListProfileHostStudy.visibility = View.GONE - binding.layoutBlankProfileHostStudy.visibility = View.VISIBLE - } else { - binding.layoutListProfileHostStudy.visibility = View.VISIBLE - binding.layoutBlankProfileHostStudy.visibility = View.GONE - } - - // 2개 이하이면 더보기 아이콘 표시 안함 - if (profileHostStudyList != null) { - if (profileHostStudyList.size < 3) { - binding.layoutMoreProfileHostStudy.visibility = View.GONE - } - } - } - } - - // 참여한 스터디 리스트 - lifecycleScope.launch { - profileViewModel.profilePartStudyList.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).collect { profilePartStudyList -> - if (profilePartStudyList != null) { - partStudyAdapter.updateData(profilePartStudyList) - } - - // 데이터 유무에 따른 뷰 가시성 설정 - if (profilePartStudyList.isNullOrEmpty()) { - binding.layoutListProfilePartStudy.visibility = View.GONE - binding.layoutBlankProfilePartStudy.visibility = View.VISIBLE - } else { - binding.layoutListProfilePartStudy.visibility = View.VISIBLE - binding.layoutBlankProfilePartStudy.visibility = View.GONE - } - - // 2개 이하이면 더보기 아이콘 표시 안함 - if (profilePartStudyList != null) { - if (profilePartStudyList.size < 3) { - binding.layoutMoreProfilePartStudy.visibility = View.GONE - } - } - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/profile/ProfileScreen.kt b/app/src/main/java/kr/co/lion/modigm/ui/profile/ProfileScreen.kt new file mode 100644 index 00000000..1afa9223 --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/profile/ProfileScreen.kt @@ -0,0 +1,323 @@ +package kr.co.lion.modigm.ui.profile + +import androidx.compose.foundation.BorderStroke +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.Column +import androidx.compose.foundation.layout.ExperimentalLayoutApi +import androidx.compose.foundation.layout.FlowRow +import androidx.compose.foundation.layout.Row +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.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.verticalScroll +import androidx.compose.material3.Card +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.HorizontalDivider +import androidx.compose.material3.Icon +import androidx.compose.material3.IconButton +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SuggestionChip +import androidx.compose.material3.SuggestionChipDefaults +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.material3.TopAppBarDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +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.graphics.vector.ImageVector +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import coil.compose.rememberAsyncImagePainter +import coil.request.ImageRequest +import com.bumptech.glide.integration.compose.ExperimentalGlideComposeApi +import com.bumptech.glide.integration.compose.GlideImage +import kr.co.lion.modigm.R +import kr.co.lion.modigm.model.StudyData +import kr.co.lion.modigm.ui.profile.vm.ProfileViewModel +import java.net.URL + +private val domainIcons = mapOf( + "youtube.com" to R.drawable.icon_youtube_logo, + "github.com" to R.drawable.icon_github_logo, + "linkedin.com" to R.drawable.icon_linkedin_logo, + "velog.io" to R.drawable.icon_velog_logo, + "instagram.com" to R.drawable.icon_instagram_logo, + "notion.com" to R.drawable.icon_notion_logo, + "facebook.com" to R.drawable.icon_facebook_logo, + "twitter.com" to R.drawable.icon_twitter_logo, + "open.kakao.com" to R.drawable.kakaotalk_sharing_btn_small, + "default" to R.drawable.icon_link +) + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun ProfileScreen( + viewModel: ProfileViewModel, + changeToSettingsFragment: () -> Unit, + changeToLinkWebView: (String) -> Unit, + changeToDetailFragment: (Int) -> Unit +) { + val profileName by viewModel.profileName.collectAsState(initial = "") + val profileIntro by viewModel.profileIntro.collectAsState(initial = "") + val profilePicUrl by viewModel.profileUserImage.collectAsState(initial = "") + val profileInterests by viewModel.profileInterests.collectAsState(initial = "") + val profileLinks by viewModel.profileLinkList.collectAsState(initial = emptyList()) + val profileHostStudies by viewModel.profileHostStudyList.collectAsState(initial = emptyList()) + val profilePartStudies by viewModel.profilePartStudyList.collectAsState(initial = emptyList()) + + Scaffold( + topBar = { + TopAppBar( + modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 10.dp), + title = { Text(text = "프로필") }, + colors = TopAppBarDefaults.topAppBarColors(containerColor = Color.White), + actions = { + IconButton(onClick = changeToSettingsFragment) { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.icon_settings_24px), + contentDescription = "Settings" + ) + } + } + ) + } + ) { paddingValues -> + Column( + modifier = Modifier + .padding(paddingValues) + .fillMaxSize() + .background(Color.White) + .verticalScroll(rememberScrollState()) + .padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + ProfileHeader(name = profileName ?: "", profilePicUrl = profilePicUrl ?: "") + IntroSection(intro = profileIntro ?: "", links = profileLinks, changeToLinkWebView = changeToLinkWebView) + Spacer(modifier = Modifier.height(16.dp)) + + InterestsSection(profileInterests ?: "") + Spacer(modifier = Modifier.height(16.dp)) + + StudiesSection(title = "진행한 스터디", studies = profileHostStudies, changeToDetailFragment = changeToDetailFragment) + Spacer(modifier = Modifier.height(16.dp)) + + StudiesSection(title = "참여한 스터디", studies = profilePartStudies, changeToDetailFragment = changeToDetailFragment) + } + } +} + + + +@Composable +fun ProfileHeader(name: String, profilePicUrl: String?) { + val painter = rememberAsyncImagePainter( + model = ImageRequest.Builder(LocalContext.current) + .data(profilePicUrl) + .crossfade(true) + .build(), + contentScale = ContentScale.Crop, + error = painterResource(id = R.drawable.image_default_profile) + ) + + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Image( + painter = painter, + contentDescription = "Profile Picture", + contentScale = ContentScale.Crop, + modifier = Modifier + .size(100.dp) + .clip(CircleShape) + .background(Color.Gray) + ) + Text(text = name, fontSize = 18.sp, fontWeight = FontWeight.Bold, modifier = Modifier.padding(top = 8.dp, bottom = 4.dp)) + } +} + +@Composable +fun IntroSection(intro: String, links: List, changeToLinkWebView: (String) -> Unit) { + Column(horizontalAlignment = Alignment.CenterHorizontally,) { + Row(modifier = Modifier.padding(start = 8.dp)) { + links.forEach { link -> + val domain = extractDomain(link) + val iconRes = domainIcons[domain] ?: R.drawable.icon_link + + Image( + painter = painterResource(id = iconRes), + contentDescription = "$domain icon", + modifier = Modifier + .size(32.dp) + .padding(end = 8.dp) + .clickable { changeToLinkWebView(link) } + ) + } + + } + + if (intro.isNotBlank()) { + Text( + text = intro, + fontSize = 14.sp, + lineHeight = 18.sp, + color = Color(0xFF777777), + modifier = Modifier.fillMaxWidth().padding(start = 4.dp, end = 4.dp, top = 4.dp) + ) + } + } + +} + +private fun extractDomain(link: String): String { + return try { + val uri = URL(link) + val domain = uri.host + if (domain.startsWith("www.")) domain.substring(4) else domain + } catch (e: Exception) { + "invalid" + } +} + +@OptIn(ExperimentalLayoutApi::class) +@Composable +fun InterestsSection(interests: String) { + Column( + modifier = Modifier + .fillMaxWidth() + .clip(RoundedCornerShape(32.dp)) // 모서리 둥글게 + .background(Color(0x66e3eeff)) // 배경색 변경 + .padding(16.dp) // 내부 패딩 추가 + ) { + Text(text = "관심분야", fontSize = 16.sp, fontWeight = FontWeight.Bold, modifier = Modifier.padding(start = 8.dp)) + HorizontalDivider(modifier = Modifier.padding(top = 8.dp, bottom = 4.dp), color = Color(0x55777777)) + FlowRow( + verticalArrangement = Arrangement.spacedBy((-8).dp), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + interests.split(",").forEach { interest -> + SuggestionChip( + onClick = {}, + label = { Text(interest, color = Color(0xff666666)) }, + colors = SuggestionChipDefaults.suggestionChipColors(containerColor = Color(0x10888888)), + border = BorderStroke(1.dp, Color(0xff888888)) + ) + } + } + } +} + +@Composable +fun StudiesSection( + title: String, + studies: List?, + changeToDetailFragment: (Int) -> Unit +) { + Column(modifier = Modifier.fillMaxWidth()) { + Text(text = title, fontSize = 18.sp, fontWeight = FontWeight.Bold) + Spacer(modifier = Modifier.height(8.dp)) + if (studies.isNullOrEmpty()) { + Text( + text = "데이터가 없습니다", + fontSize = 16.sp, + modifier = Modifier.padding(16.dp) + ) + } else { + Column { + studies.forEach { study -> + StudyItem(study = study, changeToDetailFragment = changeToDetailFragment) + HorizontalDivider(thickness = 0.5.dp, color = Color.LightGray) + } + } + } + } +} + +@OptIn(ExperimentalGlideComposeApi::class) +@Composable +fun StudyItem(study: StudyData, changeToDetailFragment: (Int) -> Unit) { + Row(modifier = Modifier + .fillMaxWidth() + .padding(8.dp) + .clickable { changeToDetailFragment(study.studyIdx) }) { + Card( + shape = RoundedCornerShape(8.dp), + modifier = Modifier.size(70.dp) + ) { + GlideImage( + model = study.studyPic, + contentDescription = "Study Image", + contentScale = ContentScale.Crop, + modifier = Modifier.fillMaxSize() + ) + } + Spacer(modifier = Modifier.width(8.dp)) + Column(modifier = Modifier.weight(1f)) { + Text( + text = study.studyTitle, + fontSize = 16.sp, + maxLines = 1, + overflow = TextOverflow.Ellipsis + ) + Spacer(modifier = Modifier.height(4.dp)) + Row(verticalAlignment = Alignment.CenterVertically) { + Icon( + painter = painterResource(id = R.drawable.icon_category_24px), + contentDescription = "Category Icon", + modifier = Modifier.size(16.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) + Text(text = study.studyType, fontSize = 14.sp) + + Spacer(modifier = Modifier.width(16.dp)) + + Icon( + painter = painterResource(id = R.drawable.icon_location_on_24px), + contentDescription = "Location Icon", + modifier = Modifier.size(16.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) + Text(text = study.studyOnOffline, fontSize = 14.sp) + + Spacer(modifier = Modifier.width(16.dp)) + + Icon( + painter = painterResource(id = R.drawable.icon_person_24px), + contentDescription = "Member Icon", + modifier = Modifier.size(16.dp) + ) + Spacer(modifier = Modifier.width(4.dp)) + Text(text = study.studyCanApply, fontSize = 14.sp) + } + } + } +} + +@Preview +@Composable +fun ProfileScreenPreview() { + ProfileScreen( + viewModel = ProfileViewModel(), + changeToSettingsFragment = {}, + changeToLinkWebView = {}, + changeToDetailFragment = {} + ) +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/profile/SettingsScreen.kt b/app/src/main/java/kr/co/lion/modigm/ui/profile/SettingsScreen.kt new file mode 100644 index 00000000..05632d89 --- /dev/null +++ b/app/src/main/java/kr/co/lion/modigm/ui/profile/SettingsScreen.kt @@ -0,0 +1,4 @@ +package kr.co.lion.modigm.ui.profile + +class SettingsScreen { +} \ No newline at end of file diff --git a/app/src/main/java/kr/co/lion/modigm/ui/profile/popup/InterestBottomSheetFragment.kt b/app/src/main/java/kr/co/lion/modigm/ui/profile/popup/InterestBottomSheetFragment.kt index 199c5e0d..4f06f153 100644 --- a/app/src/main/java/kr/co/lion/modigm/ui/profile/popup/InterestBottomSheetFragment.kt +++ b/app/src/main/java/kr/co/lion/modigm/ui/profile/popup/InterestBottomSheetFragment.kt @@ -95,7 +95,7 @@ class InterestBottomSheetFragment: VBBaseBottomSheetFragment - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_join_step1.xml b/app/src/main/res/layout/fragment_join_step1.xml deleted file mode 100644 index 8aaaea2e..00000000 --- a/app/src/main/res/layout/fragment_join_step1.xml +++ /dev/null @@ -1,126 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_join_step2.xml b/app/src/main/res/layout/fragment_join_step2.xml deleted file mode 100644 index a179940f..00000000 --- a/app/src/main/res/layout/fragment_join_step2.xml +++ /dev/null @@ -1,155 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -