From a9f9ea66125cd7e84fffaa12080c6195e0ed0709 Mon Sep 17 00:00:00 2001 From: KwakEuiJin Date: Sun, 22 Oct 2023 15:02:35 +0900 Subject: [PATCH 1/6] [feature/school_auth]: add SchoolAuthScreen --- .../components/EveryMealTextFiled.kt | 6 +- .../ui/signup/school/SchoolAuthScreen.kt | 117 ++++++++++++++++++ .../ui/signup/school/SchoolAuthViewModel.kt | 29 +++++ .../ui/signup/school/SchoolContract.kt | 21 ++++ presentation/src/main/res/values/strings.xml | 8 +- 5 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthScreen.kt create mode 100644 presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthViewModel.kt create mode 100644 presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolContract.kt diff --git a/presentation/src/main/java/com/everymeal/presentation/components/EveryMealTextFiled.kt b/presentation/src/main/java/com/everymeal/presentation/components/EveryMealTextFiled.kt index a7ee6f9d..20ffe883 100644 --- a/presentation/src/main/java/com/everymeal/presentation/components/EveryMealTextFiled.kt +++ b/presentation/src/main/java/com/everymeal/presentation/components/EveryMealTextFiled.kt @@ -19,7 +19,9 @@ fun EveryMealTextField( modifier: Modifier = Modifier, value: String, onValueChange: (String) -> Unit, - placeholderText: String, + placeholderText: String = "", + isError: Boolean = false, + supportingText: (@Composable () -> Unit)? = null, leadingIcon: (@Composable () -> Unit)? = null, otherCustomization: (@Composable () -> Unit)? = null ) { @@ -28,6 +30,8 @@ fun EveryMealTextField( value = value, onValueChange = onValueChange, leadingIcon = leadingIcon, + isError = isError, + supportingText = supportingText, placeholder = { Text( text = placeholderText, diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthScreen.kt b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthScreen.kt new file mode 100644 index 00000000..0a051e31 --- /dev/null +++ b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthScreen.kt @@ -0,0 +1,117 @@ +package com.everymeal.presentation.ui.signup.school + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.material3.CenterAlignedTopAppBar +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel +import com.everymeal.presentation.R +import com.everymeal.presentation.components.EveryMealMainButton +import com.everymeal.presentation.components.EveryMealTextField +import com.everymeal.presentation.ui.theme.EveryMealTypography +import com.everymeal.presentation.ui.theme.Main100 + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun SchoolAuthScreen( + viewModel: SchoolAuthViewModel = hiltViewModel() +) { + val viewState by viewModel.viewState.collectAsState() + Scaffold( + topBar = { + CenterAlignedTopAppBar( + title = { + Text( + text = stringResource(id = R.string.school_auth_title), + style = EveryMealTypography.Subtitle2, + ) + }, + navigationIcon = { + Image( + modifier = Modifier + .size(48.dp) + .padding(12.dp), + painter = painterResource(id = R.drawable.icon_x_mono), + contentDescription = "close" + ) + }) + } + ) + { innerPadding -> + SchoolAuthContent( + modifier = Modifier + .padding(innerPadding) + .padding(horizontal = 20.dp) + .padding(top = 28.dp), + viewModel = viewModel, + state = viewState + ) + } +} + +@Composable +fun SchoolAuthContent( + modifier: Modifier = Modifier, + viewModel: SchoolAuthViewModel, + state: SchoolContract.State +) { + Column( + modifier = modifier + ) { + Text( + text = stringResource(id = R.string.school_auth_content), + style = EveryMealTypography.Heading1 + ) + Spacer(modifier = Modifier.size(40.dp)) + Text( + text = stringResource(id = R.string.email), + style = EveryMealTypography.Body5 + ) + Spacer(modifier = Modifier.size(6.dp)) + EveryMealTextField( + modifier = Modifier.fillMaxWidth(), + value = state.emailLink, + onValueChange = { + viewModel.setEvent(SchoolContract.Event.OnEmailTextChanged(it)) + }, + supportingText = { + if (state.isEmailError) { + Text( + text = stringResource(id = R.string.email_error), + style = EveryMealTypography.Body5, + color = Main100 + ) + } + } + ) + Spacer(modifier = Modifier.weight(1f)) + EveryMealMainButton( + text = stringResource(id = R.string.next), + onClick = { + + } + ) + + + } +} + +@Preview +@Composable +fun SchoolAuthScreenPreview() { + SchoolAuthScreen() +} diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthViewModel.kt b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthViewModel.kt new file mode 100644 index 00000000..2cadbae8 --- /dev/null +++ b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthViewModel.kt @@ -0,0 +1,29 @@ +package com.everymeal.presentation.ui.signup.school + +import com.everymeal.presentation.base.BaseViewModel + +class SchoolAuthViewModel : + BaseViewModel(SchoolContract.State()) { + + companion object { + private val EMAIL_REGEX = "[a-zA-Z0-9._-]+@[a-z]+\\.+[a-z]+".toRegex() + } + + override fun handleEvents(event: SchoolContract.Event) { + when (event) { + is SchoolContract.Event.OnEmailTextChanged -> { + updateState { + copy( + isEmailError = isValidEmail(event.emailLink), + emailLink = event.emailLink + ) + } + } + } + } + + + private fun isValidEmail(email: String): Boolean { + return EMAIL_REGEX.matches(email) + } +} diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolContract.kt b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolContract.kt new file mode 100644 index 00000000..61329a7d --- /dev/null +++ b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolContract.kt @@ -0,0 +1,21 @@ +package com.everymeal.presentation.ui.signup.school + +import com.everymeal.presentation.base.ViewEvent +import com.everymeal.presentation.base.ViewSideEffect +import com.everymeal.presentation.base.ViewState + +class SchoolContract { + + data class State( + val isEmailError: Boolean = false, + val emailLink: String = "" + ) : ViewState + + sealed class Event : ViewEvent { + data class OnEmailTextChanged(val emailLink: String) : Event() + + } + + sealed class Effect : ViewSideEffect { + } +} diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index dc3c771a..00da014f 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -7,6 +7,9 @@ 확인 닫기 나가기 + 다음 + 이메일 + 잘못된 이메일 형식이에요 반가워요!\n대학을 선택해주세요 @@ -98,7 +101,6 @@ 빠른 시일 내에 이용하실 수 있도록\n에브리밀이 준비하고 있어요. - 다음 우리 오늘 뭐먹지? 학식에서\n학교 주변 맛집까지 에브리밀에서\n간편하게 @@ -109,4 +111,8 @@ 리뷰 작성하기 리뷰가 등록되었어요 + + 학교 인증 + 학교 인증을 위해\n대학 메일을 입력해주세요 + From b20c2035bd3977fbcdc506f890800eb2adbf9784 Mon Sep 17 00:00:00 2001 From: KwakEuiJin Date: Mon, 23 Oct 2023 22:15:25 +0900 Subject: [PATCH 2/6] [feature/school_auth]: add BottomSheet --- .../components/EveryMealBottomSheetDialog.kt | 76 ++++++++++++++++++- .../presentation/ui/main/MainScreen.kt | 2 +- .../ui/signup/school/SchoolAuthScreen.kt | 38 +++++++++- .../ui/signup/school/SchoolAuthViewModel.kt | 7 ++ .../ui/signup/school/SchoolContract.kt | 4 +- presentation/src/main/res/values/strings.xml | 2 + 6 files changed, 120 insertions(+), 9 deletions(-) diff --git a/presentation/src/main/java/com/everymeal/presentation/components/EveryMealBottomSheetDialog.kt b/presentation/src/main/java/com/everymeal/presentation/components/EveryMealBottomSheetDialog.kt index c092969c..fc23a3ab 100644 --- a/presentation/src/main/java/com/everymeal/presentation/components/EveryMealBottomSheetDialog.kt +++ b/presentation/src/main/java/com/everymeal/presentation/components/EveryMealBottomSheetDialog.kt @@ -10,7 +10,9 @@ import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size +import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.lazy.items import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Divider import androidx.compose.material3.ExperimentalMaterial3Api @@ -34,9 +36,11 @@ import com.everymeal.presentation.R import com.everymeal.presentation.ui.detail.ReportCategoryType import com.everymeal.presentation.ui.home.HomeCategoryList import com.everymeal.presentation.ui.theme.EveryMealTypo +import com.everymeal.presentation.ui.theme.EveryMealTypography import com.everymeal.presentation.ui.theme.Gray200 import com.everymeal.presentation.ui.theme.Gray400 import com.everymeal.presentation.ui.theme.Gray600 +import com.everymeal.presentation.ui.theme.Gray700 import com.everymeal.presentation.ui.theme.Gray800 import com.everymeal.presentation.ui.theme.Gray900 import com.everymeal.presentation.ui.theme.Grey2 @@ -49,7 +53,7 @@ import com.everymeal.presentation.ui.theme.Typography fun EveryMealMainBottomSheetDialog( title: String, content: String, - onClick : () -> Unit, + onClick: () -> Unit, onDismiss: () -> Unit ) { ModalBottomSheet( @@ -148,7 +152,7 @@ fun SortCategoryItem( color = Gray900, style = EveryMealTypo.displayMedium, ) - if(title == category) { + if (title == category) { Image( modifier = Modifier.size(24.dp), imageVector = ImageVector.vectorResource(R.drawable.icon_check_mono), @@ -266,6 +270,7 @@ fun RatingItem( } } } + @OptIn(ExperimentalMaterial3Api::class) @Composable fun EveryMealReportBottomSheetDialog( @@ -305,6 +310,71 @@ fun EveryMealReportBottomSheetDialog( } } +data class EveryMealConditionAgreeDialogItem( + val title: String, + val isAgreed: Boolean, + val onClick: () -> Unit +) + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun EveryMealConditionAgreeDialog( + onClick: () -> Unit, + onDismiss: () -> Unit, + conditionItems: List +) { + ModalBottomSheet( + onDismissRequest = { onDismiss() }, + containerColor = Color.White, + ) { + Column( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp) + ) { + Text( + text = stringResource(R.string.condition_agree_title), + style = EveryMealTypography.Heading2, + color = Gray900, + ) + Spacer(modifier = Modifier.size(13.dp)) + LazyColumn(content = { + items(conditionItems) { + Row( + modifier = Modifier + .clickable(onClick = onClick) + .padding(vertical = 7.dp), + ) { + Image( + painter = if (it.isAgreed) painterResource( + id = R.drawable.icon_check_mono + ) else painterResource(id = R.drawable.icon_check_gray_mono), + contentDescription = "check" + ) + Text( + modifier = Modifier.padding(start = 6.dp), + text = it.title, + style = EveryMealTypography.Subtitle3, + color = Gray700, + ) + Spacer(modifier = Modifier.weight(1f)) + Image( + painter = painterResource(id = R.drawable.icon_arrow_right), + contentDescription = null + ) + } + } + }) + EveryMealMainButton( + text = stringResource(R.string.ok), + enabled = true, + onClick = onClick, + ) + Spacer(modifier = Modifier.size(24.dp)) + } + } +} + @OptIn(ExperimentalMaterial3Api::class) @Composable fun EveryMealDetailReportBottomSheetDialog( @@ -385,7 +455,7 @@ fun ReportCategoryItem( modifier = Modifier.size(24.dp), imageVector = ImageVector.vectorResource(R.drawable.icon_check_gray_mono), contentDescription = null, - colorFilter = if(title == category) { + colorFilter = if (title == category) { ColorFilter.tint(Main100) } else { ColorFilter.tint(Gray400) diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/main/MainScreen.kt b/presentation/src/main/java/com/everymeal/presentation/ui/main/MainScreen.kt index d187555c..5b311531 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/main/MainScreen.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/main/MainScreen.kt @@ -101,4 +101,4 @@ fun MainScreen( @Composable fun MainScreenPreview() { MainScreen() -} \ No newline at end of file +} diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthScreen.kt b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthScreen.kt index 0a051e31..f0e4eb68 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthScreen.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthScreen.kt @@ -20,6 +20,8 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import com.everymeal.presentation.R +import com.everymeal.presentation.components.EveryMealConditionAgreeDialog +import com.everymeal.presentation.components.EveryMealConditionAgreeDialogItem import com.everymeal.presentation.components.EveryMealMainButton import com.everymeal.presentation.components.EveryMealTextField import com.everymeal.presentation.ui.theme.EveryMealTypography @@ -60,6 +62,35 @@ fun SchoolAuthScreen( viewModel = viewModel, state = viewState ) + if (viewState.isShowConditionBottomSheet) { + EveryMealConditionAgreeDialog( + onClick = { }, + onDismiss = { }, + conditionItems = listOf( + EveryMealConditionAgreeDialogItem( + title = "[필수] 이용 약관 동의", + isAgreed = true, + onClick = { + + } + ), + EveryMealConditionAgreeDialogItem( + title = "[필수] 개인정보 수집 및 이용 동의", + isAgreed = true, + onClick = { + + } + ), + EveryMealConditionAgreeDialogItem( + title = "[선택] 마케팅 정보 수집 동의", + isAgreed = true, + onClick = { + + } + ) + ) + ) + } } } @@ -102,14 +133,13 @@ fun SchoolAuthContent( EveryMealMainButton( text = stringResource(id = R.string.next), onClick = { - - } + viewModel.setEvent(SchoolContract.Event.OnNextButtonClicked) + }, ) - - } } + @Preview @Composable fun SchoolAuthScreenPreview() { diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthViewModel.kt b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthViewModel.kt index 2cadbae8..8369f445 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthViewModel.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthViewModel.kt @@ -19,6 +19,13 @@ class SchoolAuthViewModel : ) } } + is SchoolContract.Event.OnNextButtonClicked -> { + updateState { + copy( + isShowConditionBottomSheet = true + ) + } + } } } diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolContract.kt b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolContract.kt index 61329a7d..d89fd115 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolContract.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolContract.kt @@ -7,15 +7,17 @@ import com.everymeal.presentation.base.ViewState class SchoolContract { data class State( + val isShowConditionBottomSheet : Boolean = false, val isEmailError: Boolean = false, val emailLink: String = "" ) : ViewState sealed class Event : ViewEvent { data class OnEmailTextChanged(val emailLink: String) : Event() - + object OnNextButtonClicked : Event() } sealed class Effect : ViewSideEffect { + object ShowConditionAgreeBottomSheet : Effect() } } diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 00da014f..e12bc171 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -114,5 +114,7 @@ 학교 인증 학교 인증을 위해\n대학 메일을 입력해주세요 + 이용을 위한 동의가 필요해요 + [필수] 이용 약관 동의 From 04e8cad1bdde7ab561515589ac3788fac5a13cc3 Mon Sep 17 00:00:00 2001 From: KwakEuiJin Date: Wed, 1 Nov 2023 22:11:46 +0900 Subject: [PATCH 3/6] [feature/school_auth]: add WebScreenContent --- gradle/libs.versions.toml | 8 +-- presentation/build.gradle.kts | 1 + .../presentation/ui/web/WebScreenContent.kt | 66 +++++++++++++++++++ 3 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 presentation/src/main/java/com/everymeal/presentation/ui/web/WebScreenContent.kt diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e4cc630b..1b96bf8c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -9,18 +9,15 @@ espresso-core = "3.5.1" lifecycle-runtime-ktx = "2.6.2" activity = "1.7.2" compose-bom = "2023.10.01" - dagger-hilt = "2.46.1" hilt-compose = "1.0.0" - okhttp = "4.11.0" retrofit = "2.9.0" serialization = "1.6.0" kotlin-serilization = "1.0.0" - lottie = "6.1.0" - compose-navigation = "2.7.4" +accompanist = "0.33.0-alpha" [libraries] agp = { module = "com.android.tools.build:gradle", version.ref = "agp" } @@ -41,6 +38,7 @@ ui-tooling-preview = { group = "androidx.compose.ui", name = "ui-tooling-preview ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } material3 = { group = "androidx.compose.material3", name = "material3" } +accompanist-webview = { group = "com.google.accompanist", name = "accompanist-webview", version.ref = "accompanist" } # Kotlin kotlin = { group = "org.jetbrains.kotlin", name = "kotlin-stdlib", version.ref = "kotlin" } @@ -57,7 +55,7 @@ hilt-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = # Retrofit okhttp-bom = { module = "com.squareup.okhttp3:okhttp-bom", version.ref = "okhttp" } -okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp"} +okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } okhttp-logging-interceptor = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" } retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" } diff --git a/presentation/build.gradle.kts b/presentation/build.gradle.kts index 97b81035..4a5158ff 100644 --- a/presentation/build.gradle.kts +++ b/presentation/build.gradle.kts @@ -62,6 +62,7 @@ dependencies { implementation(libs.ui.graphics) implementation(libs.ui.tooling.preview) implementation(libs.material3) + implementation(libs.accompanist.webview) testImplementation(libs.junit) androidTestImplementation(libs.androidx.test.ext.junit) androidTestImplementation(libs.espresso.core) diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/web/WebScreenContent.kt b/presentation/src/main/java/com/everymeal/presentation/ui/web/WebScreenContent.kt new file mode 100644 index 00000000..0e8ead41 --- /dev/null +++ b/presentation/src/main/java/com/everymeal/presentation/ui/web/WebScreenContent.kt @@ -0,0 +1,66 @@ +package com.everymeal.presentation.ui.web + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.material3.TopAppBar +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.painterResource +import com.everymeal.presentation.R +import com.google.accompanist.web.AccompanistWebChromeClient +import com.google.accompanist.web.AccompanistWebViewClient +import com.google.accompanist.web.WebView +import com.google.accompanist.web.WebViewNavigator +import com.google.accompanist.web.rememberWebViewState + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun WebScreenContent( + url: String, + staticTitle: String? = null, + webViewNavigator: WebViewNavigator, + onBackPressed: (() -> Unit)? = null, +) { + + val webViewState = rememberWebViewState(url = url) + + Scaffold( + topBar = { + TopAppBar( + title = { Text(text = staticTitle ?: (webViewState.pageTitle ?: "")) }, + navigationIcon = { + onBackPressed?.let { + Image( + modifier = Modifier.clickable { + if (webViewNavigator.canGoBack) webViewNavigator.navigateBack() + else onBackPressed() + }, + painter = painterResource(id = R.drawable.icon_arrow_back_mono), + contentDescription = "back" + ) + } + } + ) + } + ) { innerPadding -> + WebView( + state = webViewState, + modifier = Modifier + .fillMaxSize() + .padding(innerPadding), + navigator = webViewNavigator, + onCreated = { webView -> + webView.settings.javaScriptEnabled = true + }, + client = remember { AccompanistWebViewClient() }, + chromeClient = remember { AccompanistWebChromeClient() } + ) + } + +} From 1664ae35e45ba4c1b12e3881e8573abd15febee9 Mon Sep 17 00:00:00 2001 From: KwakEuiJin Date: Thu, 9 Nov 2023 14:59:34 +0900 Subject: [PATCH 4/6] [feat/school_auth]:add school auth api --- .../everymeal_android/di/NetworkModule.kt | 11 ++++++++-- .../everymeal_android/di/RepositoryModule.kt | 18 ++++++++++++++++- .../datasource/auth/AuthRemoteDataSource.kt | 7 +++++++ .../auth/AuthRemoteRemoteDataSourceImpl.kt | 16 +++++++++++++++ .../everymeal/data/model/auth/EmailRequest.kt | 20 +++++++++++++++++++ .../data/repository/DefaultAuthRepository.kt | 14 +++++++++++++ .../everymeal/data/service/auth/AuthApi.kt | 10 ++++++++++ .../com/everymeal/domain/model/auth/Email.kt | 5 +++++ .../domain/repository/auth/AuthRepository.kt | 8 ++++++++ .../domain/usecase/auth/PostEmailUseCase.kt | 13 ++++++++++++ .../ui/signup/school/SchoolAuthViewModel.kt | 20 ++++++++++++++++++- .../ui/signup/school/SchoolContract.kt | 1 + 12 files changed, 139 insertions(+), 4 deletions(-) create mode 100644 data/src/main/java/com/everymeal/data/datasource/auth/AuthRemoteDataSource.kt create mode 100644 data/src/main/java/com/everymeal/data/datasource/auth/AuthRemoteRemoteDataSourceImpl.kt create mode 100644 data/src/main/java/com/everymeal/data/model/auth/EmailRequest.kt create mode 100644 data/src/main/java/com/everymeal/data/repository/DefaultAuthRepository.kt create mode 100644 data/src/main/java/com/everymeal/data/service/auth/AuthApi.kt create mode 100644 domain/src/main/java/com/everymeal/domain/model/auth/Email.kt create mode 100644 domain/src/main/java/com/everymeal/domain/repository/auth/AuthRepository.kt create mode 100644 domain/src/main/java/com/everymeal/domain/usecase/auth/PostEmailUseCase.kt diff --git a/app/src/main/java/com/everymeal/everymeal_android/di/NetworkModule.kt b/app/src/main/java/com/everymeal/everymeal_android/di/NetworkModule.kt index 207c5046..20b421f3 100644 --- a/app/src/main/java/com/everymeal/everymeal_android/di/NetworkModule.kt +++ b/app/src/main/java/com/everymeal/everymeal_android/di/NetworkModule.kt @@ -1,5 +1,6 @@ package com.everymeal.everymeal_android.di +import com.everymeal.data.service.auth.AuthApi import com.everymeal.data.service.onboarding.OnboardingApi import com.everymeal.everymeal_android.BuildConfig import com.jakewharton.retrofit2.converter.kotlinx.serialization.asConverterFactory @@ -24,7 +25,7 @@ object NetworkModule { @Provides @Singleton - fun provideClient(): OkHttpClient { + fun provideClient(): OkHttpClient { return OkHttpClient.Builder() .connectTimeout(100, TimeUnit.SECONDS) .readTimeout(100, TimeUnit.SECONDS) @@ -46,4 +47,10 @@ object NetworkModule { fun provideOnboardingApi(retrofit: Retrofit): OnboardingApi { return retrofit.create(OnboardingApi::class.java) } -} \ No newline at end of file + + @Provides + @Singleton + fun provideAuthApi(retrofit: Retrofit): AuthApi { + return retrofit.create(AuthApi::class.java) + } +} diff --git a/app/src/main/java/com/everymeal/everymeal_android/di/RepositoryModule.kt b/app/src/main/java/com/everymeal/everymeal_android/di/RepositoryModule.kt index da5bacce..6c98f071 100644 --- a/app/src/main/java/com/everymeal/everymeal_android/di/RepositoryModule.kt +++ b/app/src/main/java/com/everymeal/everymeal_android/di/RepositoryModule.kt @@ -1,8 +1,12 @@ package com.everymeal.everymeal_android.di +import com.everymeal.data.datasource.auth.AuthRemoteDataSource +import com.everymeal.data.datasource.auth.AuthRemoteRemoteDataSourceImpl import com.everymeal.data.datasource.onboarding.OnboardingDataSource import com.everymeal.data.datasource.onboarding.OnboardingDataSourceImpl +import com.everymeal.data.repository.DefaultAuthRepository import com.everymeal.data.repository.onboarding.OnboardingRepositoryImpl +import com.everymeal.domain.repository.auth.AuthRepository import com.everymeal.domain.repository.onboarding.OnboardingRepository import dagger.Binds import dagger.Module @@ -25,4 +29,16 @@ abstract class RepositoryModule { abstract fun bindOnboardingDataSource( onboardingDataSourceImpl: OnboardingDataSourceImpl ): OnboardingDataSource -} \ No newline at end of file + + @Singleton + @Binds + abstract fun bindAuthRemoteDataSource( + authRemoteDataSourceImpl: AuthRemoteRemoteDataSourceImpl + ): AuthRemoteDataSource + + @Singleton + @Binds + abstract fun bindAuthRepository( + defaultAuthRepository: DefaultAuthRepository + ): AuthRepository +} diff --git a/data/src/main/java/com/everymeal/data/datasource/auth/AuthRemoteDataSource.kt b/data/src/main/java/com/everymeal/data/datasource/auth/AuthRemoteDataSource.kt new file mode 100644 index 00000000..d7614ba3 --- /dev/null +++ b/data/src/main/java/com/everymeal/data/datasource/auth/AuthRemoteDataSource.kt @@ -0,0 +1,7 @@ +package com.everymeal.data.datasource.auth + +import com.everymeal.domain.model.auth.Email + +interface AuthRemoteDataSource { + suspend fun postEmail(email: Email): Result +} diff --git a/data/src/main/java/com/everymeal/data/datasource/auth/AuthRemoteRemoteDataSourceImpl.kt b/data/src/main/java/com/everymeal/data/datasource/auth/AuthRemoteRemoteDataSourceImpl.kt new file mode 100644 index 00000000..385992a9 --- /dev/null +++ b/data/src/main/java/com/everymeal/data/datasource/auth/AuthRemoteRemoteDataSourceImpl.kt @@ -0,0 +1,16 @@ +package com.everymeal.data.datasource.auth + +import com.everymeal.data.model.auth.toEmailRequest +import com.everymeal.data.model.unwrapData +import com.everymeal.data.service.auth.AuthApi +import com.everymeal.domain.model.auth.Email +import javax.inject.Inject + +class AuthRemoteRemoteDataSourceImpl @Inject constructor( + private val authApi: AuthApi +) : AuthRemoteDataSource { + + override suspend fun postEmail(email: Email): Result = runCatching { + authApi.postEmail(email.toEmailRequest()) + }.unwrapData() +} diff --git a/data/src/main/java/com/everymeal/data/model/auth/EmailRequest.kt b/data/src/main/java/com/everymeal/data/model/auth/EmailRequest.kt new file mode 100644 index 00000000..02b758cb --- /dev/null +++ b/data/src/main/java/com/everymeal/data/model/auth/EmailRequest.kt @@ -0,0 +1,20 @@ +package com.everymeal.data.model.auth + + +import com.everymeal.domain.model.auth.Email +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class EmailRequest( + @SerialName("email") + val email: String? = null +) + +fun EmailRequest.toEmail() = Email( + email = email ?: "" +) + +fun Email.toEmailRequest() = EmailRequest( + email = email +) diff --git a/data/src/main/java/com/everymeal/data/repository/DefaultAuthRepository.kt b/data/src/main/java/com/everymeal/data/repository/DefaultAuthRepository.kt new file mode 100644 index 00000000..5fdbf858 --- /dev/null +++ b/data/src/main/java/com/everymeal/data/repository/DefaultAuthRepository.kt @@ -0,0 +1,14 @@ +package com.everymeal.data.repository + +import com.everymeal.data.datasource.auth.AuthRemoteDataSource +import com.everymeal.domain.model.auth.Email +import com.everymeal.domain.repository.auth.AuthRepository +import javax.inject.Inject + +class DefaultAuthRepository @Inject constructor( + private val authRemoteDataSource: AuthRemoteDataSource +) : AuthRepository { + override suspend fun postEmail(email: Email): Result { + return authRemoteDataSource.postEmail(email) + } +} diff --git a/data/src/main/java/com/everymeal/data/service/auth/AuthApi.kt b/data/src/main/java/com/everymeal/data/service/auth/AuthApi.kt new file mode 100644 index 00000000..e0079350 --- /dev/null +++ b/data/src/main/java/com/everymeal/data/service/auth/AuthApi.kt @@ -0,0 +1,10 @@ +package com.everymeal.data.service.auth + +import com.everymeal.data.model.BaseResponse +import com.everymeal.data.model.auth.EmailRequest +import retrofit2.http.POST + +interface AuthApi { + @POST("/api/v1/users/email") + suspend fun postEmail(emailRequest: EmailRequest): BaseResponse +} diff --git a/domain/src/main/java/com/everymeal/domain/model/auth/Email.kt b/domain/src/main/java/com/everymeal/domain/model/auth/Email.kt new file mode 100644 index 00000000..7d8e58fb --- /dev/null +++ b/domain/src/main/java/com/everymeal/domain/model/auth/Email.kt @@ -0,0 +1,5 @@ +package com.everymeal.domain.model.auth + +data class Email( + val email: String +) diff --git a/domain/src/main/java/com/everymeal/domain/repository/auth/AuthRepository.kt b/domain/src/main/java/com/everymeal/domain/repository/auth/AuthRepository.kt new file mode 100644 index 00000000..8d4c53e6 --- /dev/null +++ b/domain/src/main/java/com/everymeal/domain/repository/auth/AuthRepository.kt @@ -0,0 +1,8 @@ +package com.everymeal.domain.repository.auth + +import com.everymeal.domain.model.auth.Email + +interface AuthRepository { + suspend fun postEmail(email: Email): Result + +} diff --git a/domain/src/main/java/com/everymeal/domain/usecase/auth/PostEmailUseCase.kt b/domain/src/main/java/com/everymeal/domain/usecase/auth/PostEmailUseCase.kt new file mode 100644 index 00000000..155af81c --- /dev/null +++ b/domain/src/main/java/com/everymeal/domain/usecase/auth/PostEmailUseCase.kt @@ -0,0 +1,13 @@ +package com.everymeal.domain.usecase.auth + +import com.everymeal.domain.model.auth.Email +import com.everymeal.domain.repository.auth.AuthRepository +import javax.inject.Inject + +class PostEmailUseCase @Inject constructor( + private val authRepository: AuthRepository +) { + suspend operator fun invoke( + email: Email + ) = authRepository.postEmail(email) +} diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthViewModel.kt b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthViewModel.kt index 8369f445..2a88a9e9 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthViewModel.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthViewModel.kt @@ -1,8 +1,15 @@ package com.everymeal.presentation.ui.signup.school +import androidx.lifecycle.viewModelScope +import com.everymeal.domain.model.auth.Email +import com.everymeal.domain.usecase.auth.PostEmailUseCase import com.everymeal.presentation.base.BaseViewModel +import kotlinx.coroutines.launch +import javax.inject.Inject -class SchoolAuthViewModel : +class SchoolAuthViewModel @Inject constructor( + private val postEmailUseCase: PostEmailUseCase +) : BaseViewModel(SchoolContract.State()) { companion object { @@ -19,6 +26,7 @@ class SchoolAuthViewModel : ) } } + is SchoolContract.Event.OnNextButtonClicked -> { updateState { copy( @@ -26,6 +34,10 @@ class SchoolAuthViewModel : ) } } + + is SchoolContract.Event.OnPostEmail -> { + postEmail() + } } } @@ -33,4 +45,10 @@ class SchoolAuthViewModel : private fun isValidEmail(email: String): Boolean { return EMAIL_REGEX.matches(email) } + + private fun postEmail() { + viewModelScope.launch { + postEmailUseCase(Email(viewState.value.emailLink)) + } + } } diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolContract.kt b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolContract.kt index d89fd115..ef1d1c93 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolContract.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolContract.kt @@ -15,6 +15,7 @@ class SchoolContract { sealed class Event : ViewEvent { data class OnEmailTextChanged(val emailLink: String) : Event() object OnNextButtonClicked : Event() + object OnPostEmail : Event() } sealed class Effect : ViewSideEffect { From 91e51d2ef2f760b1792ddc2114ae1b82ac0f77d6 Mon Sep 17 00:00:00 2001 From: KwakEuiJin Date: Sat, 11 Nov 2023 00:27:37 +0900 Subject: [PATCH 5/6] [feat/school_auth]: add school Email BottomSheet --- .../domain/usecase/auth/PostEmailUseCase.kt | 4 +- presentation/build.gradle.kts | 3 + .../components/EveryMealBottomSheetDialog.kt | 19 +++--- .../ui/signup/school/SchoolAuthScreen.kt | 64 +++++++++++-------- .../ui/signup/school/SchoolAuthViewModel.kt | 19 ++++-- .../ui/signup/school/SchoolContract.kt | 8 ++- 6 files changed, 75 insertions(+), 42 deletions(-) diff --git a/domain/src/main/java/com/everymeal/domain/usecase/auth/PostEmailUseCase.kt b/domain/src/main/java/com/everymeal/domain/usecase/auth/PostEmailUseCase.kt index 155af81c..e3a44222 100644 --- a/domain/src/main/java/com/everymeal/domain/usecase/auth/PostEmailUseCase.kt +++ b/domain/src/main/java/com/everymeal/domain/usecase/auth/PostEmailUseCase.kt @@ -9,5 +9,7 @@ class PostEmailUseCase @Inject constructor( ) { suspend operator fun invoke( email: Email - ) = authRepository.postEmail(email) + ): Result { + return authRepository.postEmail(email) + } } diff --git a/presentation/build.gradle.kts b/presentation/build.gradle.kts index 4a5158ff..b1d59c11 100644 --- a/presentation/build.gradle.kts +++ b/presentation/build.gradle.kts @@ -76,6 +76,9 @@ dependencies { implementation(libs.hilt.compose) kapt(libs.hilt.testing.compiler) + // Retrofit + implementation(libs.retrofit) + // Lottie implementation(libs.compose.lottie) diff --git a/presentation/src/main/java/com/everymeal/presentation/components/EveryMealBottomSheetDialog.kt b/presentation/src/main/java/com/everymeal/presentation/components/EveryMealBottomSheetDialog.kt index fc23a3ab..732ac1db 100644 --- a/presentation/src/main/java/com/everymeal/presentation/components/EveryMealBottomSheetDialog.kt +++ b/presentation/src/main/java/com/everymeal/presentation/components/EveryMealBottomSheetDialog.kt @@ -12,7 +12,7 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyRow -import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Divider import androidx.compose.material3.ExperimentalMaterial3Api @@ -313,13 +313,14 @@ fun EveryMealReportBottomSheetDialog( data class EveryMealConditionAgreeDialogItem( val title: String, val isAgreed: Boolean, - val onClick: () -> Unit + val isEssential: Boolean = false, ) @OptIn(ExperimentalMaterial3Api::class) @Composable fun EveryMealConditionAgreeDialog( - onClick: () -> Unit, + onItemClicked: (Int) -> Unit, + onNextButtonClicked: () -> Unit, onDismiss: () -> Unit, conditionItems: List ) { @@ -339,21 +340,23 @@ fun EveryMealConditionAgreeDialog( ) Spacer(modifier = Modifier.size(13.dp)) LazyColumn(content = { - items(conditionItems) { + itemsIndexed(conditionItems) {index, item -> Row( modifier = Modifier - .clickable(onClick = onClick) + .clickable(onClick = { + onItemClicked(index) + }) .padding(vertical = 7.dp), ) { Image( - painter = if (it.isAgreed) painterResource( + painter = if (item.isAgreed) painterResource( id = R.drawable.icon_check_mono ) else painterResource(id = R.drawable.icon_check_gray_mono), contentDescription = "check" ) Text( modifier = Modifier.padding(start = 6.dp), - text = it.title, + text = item.title, style = EveryMealTypography.Subtitle3, color = Gray700, ) @@ -368,7 +371,7 @@ fun EveryMealConditionAgreeDialog( EveryMealMainButton( text = stringResource(R.string.ok), enabled = true, - onClick = onClick, + onClick = onNextButtonClicked, ) Spacer(modifier = Modifier.size(24.dp)) } diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthScreen.kt b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthScreen.kt index f0e4eb68..5c5c39b2 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthScreen.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthScreen.kt @@ -13,6 +13,8 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateListOf +import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource @@ -63,35 +65,45 @@ fun SchoolAuthScreen( state = viewState ) if (viewState.isShowConditionBottomSheet) { - EveryMealConditionAgreeDialog( - onClick = { }, - onDismiss = { }, - conditionItems = listOf( - EveryMealConditionAgreeDialogItem( - title = "[필수] 이용 약관 동의", - isAgreed = true, - onClick = { - - } - ), - EveryMealConditionAgreeDialogItem( - title = "[필수] 개인정보 수집 및 이용 동의", - isAgreed = true, - onClick = { - - } - ), - EveryMealConditionAgreeDialogItem( - title = "[선택] 마케팅 정보 수집 동의", - isAgreed = true, - onClick = { + EmailAuthBottomSheet(viewModel) + } + } +} - } - ) - ) +@Composable +private fun EmailAuthBottomSheet(viewModel: SchoolAuthViewModel) { + val conditionItems = remember { + mutableStateListOf( + EveryMealConditionAgreeDialogItem( + title = "[필수] 이용 약관 동의", + isAgreed = true, + isEssential = true, + ), + EveryMealConditionAgreeDialogItem( + title = "[필수] 개인정보 수집 및 이용 동의", + isAgreed = true, + isEssential = true, + ), + EveryMealConditionAgreeDialogItem( + title = "[선택] 마케팅 정보 수집 동의", + isAgreed = true, ) - } + ) } + + EveryMealConditionAgreeDialog( + onItemClicked = { + conditionItems[it] = + conditionItems[it].copy(isAgreed = !conditionItems[it].isAgreed) + }, + onNextButtonClicked = { + if (conditionItems.filter { it.isEssential }.any { it.isAgreed }) { + viewModel.setEvent(SchoolContract.Event.OnPostEmail) + } + }, + onDismiss = {}, + conditionItems = conditionItems + ) } @Composable diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthViewModel.kt b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthViewModel.kt index 2a88a9e9..fff37636 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthViewModel.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthViewModel.kt @@ -5,6 +5,7 @@ import com.everymeal.domain.model.auth.Email import com.everymeal.domain.usecase.auth.PostEmailUseCase import com.everymeal.presentation.base.BaseViewModel import kotlinx.coroutines.launch +import retrofit2.HttpException import javax.inject.Inject class SchoolAuthViewModel @Inject constructor( @@ -29,15 +30,17 @@ class SchoolAuthViewModel @Inject constructor( is SchoolContract.Event.OnNextButtonClicked -> { updateState { - copy( - isShowConditionBottomSheet = true - ) + copy(isShowConditionBottomSheet = true) } } is SchoolContract.Event.OnPostEmail -> { postEmail() } + + SchoolContract.Event.FailEmailVerification -> { + sendEffect() + } } } @@ -48,7 +51,15 @@ class SchoolAuthViewModel @Inject constructor( private fun postEmail() { viewModelScope.launch { - postEmailUseCase(Email(viewState.value.emailLink)) + postEmailUseCase(Email(viewState.value.emailLink)).onSuccess { + updateState { + copy(emailAuthToken = it) + } + }.onFailure { + if (it is HttpException) { + sendEffect({ SchoolContract.Effect.Error(it.code()) }) + } + } } } } diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolContract.kt b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolContract.kt index ef1d1c93..cfc4eec4 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolContract.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolContract.kt @@ -7,18 +7,20 @@ import com.everymeal.presentation.base.ViewState class SchoolContract { data class State( - val isShowConditionBottomSheet : Boolean = false, + val isShowConditionBottomSheet: Boolean = false, val isEmailError: Boolean = false, - val emailLink: String = "" + val emailLink: String = "", + val emailAuthToken: String = "" ) : ViewState sealed class Event : ViewEvent { data class OnEmailTextChanged(val emailLink: String) : Event() object OnNextButtonClicked : Event() object OnPostEmail : Event() + object FailEmailVerification : Event() } sealed class Effect : ViewSideEffect { - object ShowConditionAgreeBottomSheet : Effect() + data class Error(val code: Int) : Effect() } } From e72050bff05a5d0691a7b2d97717df9d766fdd57 Mon Sep 17 00:00:00 2001 From: KwakEuiJin Date: Sat, 11 Nov 2023 00:58:00 +0900 Subject: [PATCH 6/6] [feat/school_auth]: Add Token Verify Screen --- .../ui/signup/school/SchoolAuthScreen.kt | 57 +++----------- .../ui/signup/school/SchoolContract.kt | 1 + .../school/email/SchoolAuthPostEmailScreen.kt | 75 +++++++++++++++++++ presentation/src/main/res/values/strings.xml | 2 + 4 files changed, 90 insertions(+), 45 deletions(-) create mode 100644 presentation/src/main/java/com/everymeal/presentation/ui/signup/school/email/SchoolAuthPostEmailScreen.kt diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthScreen.kt b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthScreen.kt index 5c5c39b2..4fd1535b 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthScreen.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolAuthScreen.kt @@ -1,9 +1,6 @@ package com.everymeal.presentation.ui.signup.school import androidx.compose.foundation.Image -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.material3.CenterAlignedTopAppBar @@ -24,10 +21,13 @@ import androidx.hilt.navigation.compose.hiltViewModel import com.everymeal.presentation.R import com.everymeal.presentation.components.EveryMealConditionAgreeDialog import com.everymeal.presentation.components.EveryMealConditionAgreeDialogItem -import com.everymeal.presentation.components.EveryMealMainButton -import com.everymeal.presentation.components.EveryMealTextField +import com.everymeal.presentation.ui.signup.school.email.SchoolAuthPostEmailScreen import com.everymeal.presentation.ui.theme.EveryMealTypography -import com.everymeal.presentation.ui.theme.Main100 + +enum class SchoolAuthScreenType { + POST_EMAIL, + VERIFY_TOKEN, +} @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -59,8 +59,7 @@ fun SchoolAuthScreen( SchoolAuthContent( modifier = Modifier .padding(innerPadding) - .padding(horizontal = 20.dp) - .padding(top = 28.dp), + .padding(horizontal = 20.dp), viewModel = viewModel, state = viewState ) @@ -112,43 +111,11 @@ fun SchoolAuthContent( viewModel: SchoolAuthViewModel, state: SchoolContract.State ) { - Column( - modifier = modifier - ) { - Text( - text = stringResource(id = R.string.school_auth_content), - style = EveryMealTypography.Heading1 - ) - Spacer(modifier = Modifier.size(40.dp)) - Text( - text = stringResource(id = R.string.email), - style = EveryMealTypography.Body5 - ) - Spacer(modifier = Modifier.size(6.dp)) - EveryMealTextField( - modifier = Modifier.fillMaxWidth(), - value = state.emailLink, - onValueChange = { - viewModel.setEvent(SchoolContract.Event.OnEmailTextChanged(it)) - }, - supportingText = { - if (state.isEmailError) { - Text( - text = stringResource(id = R.string.email_error), - style = EveryMealTypography.Body5, - color = Main100 - ) - } - } - ) - Spacer(modifier = Modifier.weight(1f)) - EveryMealMainButton( - text = stringResource(id = R.string.next), - onClick = { - viewModel.setEvent(SchoolContract.Event.OnNextButtonClicked) - }, - ) - } + SchoolAuthPostEmailScreen( + modifier = modifier, + viewModel = viewModel, + state = state, + ) } diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolContract.kt b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolContract.kt index cfc4eec4..9edecde4 100644 --- a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolContract.kt +++ b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/SchoolContract.kt @@ -7,6 +7,7 @@ import com.everymeal.presentation.base.ViewState class SchoolContract { data class State( + val schoolAuthScreenType: SchoolAuthScreenType = SchoolAuthScreenType.POST_EMAIL, val isShowConditionBottomSheet: Boolean = false, val isEmailError: Boolean = false, val emailLink: String = "", diff --git a/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/email/SchoolAuthPostEmailScreen.kt b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/email/SchoolAuthPostEmailScreen.kt new file mode 100644 index 00000000..6a43dc3e --- /dev/null +++ b/presentation/src/main/java/com/everymeal/presentation/ui/signup/school/email/SchoolAuthPostEmailScreen.kt @@ -0,0 +1,75 @@ +package com.everymeal.presentation.ui.signup.school.email + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +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.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.unit.dp +import com.everymeal.presentation.R +import com.everymeal.presentation.components.EveryMealMainButton +import com.everymeal.presentation.components.EveryMealTextField +import com.everymeal.presentation.ui.signup.school.SchoolAuthScreenType +import com.everymeal.presentation.ui.signup.school.SchoolAuthViewModel +import com.everymeal.presentation.ui.signup.school.SchoolContract +import com.everymeal.presentation.ui.theme.EveryMealTypography +import com.everymeal.presentation.ui.theme.Gray100 +import com.everymeal.presentation.ui.theme.Gray900 +import com.everymeal.presentation.ui.theme.Main100 + +@Composable +fun SchoolAuthPostEmailScreen( + modifier: Modifier, + state: SchoolContract.State, + viewModel: SchoolAuthViewModel +) { + Column( + modifier = modifier.padding(top = 48.dp) + ) { + Text( + text = when (state.schoolAuthScreenType) { + SchoolAuthScreenType.POST_EMAIL -> stringResource(id = R.string.school_auth_content) + SchoolAuthScreenType.VERIFY_TOKEN -> stringResource(id = R.string.email_token_verify_title) + }, + style = EveryMealTypography.Heading1, + color = Gray900 + ) + Spacer(modifier = Modifier.size(40.dp)) + Text( + text = when(state.schoolAuthScreenType) { + SchoolAuthScreenType.POST_EMAIL -> stringResource(id = R.string.email) + SchoolAuthScreenType.VERIFY_TOKEN -> stringResource(id = R.string.verify_token) + }, + style = EveryMealTypography.Body5, + color = Gray100 + ) + Spacer(modifier = Modifier.size(6.dp)) + EveryMealTextField( + modifier = Modifier.fillMaxWidth(), + value = state.emailLink, + onValueChange = { + viewModel.setEvent(SchoolContract.Event.OnEmailTextChanged(it)) + }, + supportingText = { + if (state.isEmailError) { + Text( + text = stringResource(id = R.string.email_error), + style = EveryMealTypography.Body5, + color = Main100 + ) + } + } + ) + Spacer(modifier = Modifier.weight(1f)) + EveryMealMainButton( + text = stringResource(id = R.string.next), + onClick = { + viewModel.setEvent(SchoolContract.Event.OnNextButtonClicked) + }, + ) + } +} diff --git a/presentation/src/main/res/values/strings.xml b/presentation/src/main/res/values/strings.xml index 9756e7fc..cb764820 100644 --- a/presentation/src/main/res/values/strings.xml +++ b/presentation/src/main/res/values/strings.xml @@ -9,6 +9,7 @@ 나가기 다음 이메일 + 인증번호 잘못된 이메일 형식이에요 @@ -123,6 +124,7 @@ 학교 인증을 위해\n대학 메일을 입력해주세요 이용을 위한 동의가 필요해요 [필수] 이용 약관 동의 + 메일로 전달받은\n인증번호를 입력해주세요