From eb1524086d3427f0e69e38d5be96e44dfdf70e80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=ED=98=84=EC=84=9C?= Date: Tue, 23 Jan 2024 17:51:16 +0900 Subject: [PATCH 01/19] :sparkles: :: Add sign up model --- .../goms/model/request/auth/SendNumberRequest.kt | 9 +++++++++ .../com/goms/model/request/auth/SignUpRequest.kt | 15 +++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 core/model/src/main/java/com/goms/model/request/auth/SendNumberRequest.kt create mode 100644 core/model/src/main/java/com/goms/model/request/auth/SignUpRequest.kt diff --git a/core/model/src/main/java/com/goms/model/request/auth/SendNumberRequest.kt b/core/model/src/main/java/com/goms/model/request/auth/SendNumberRequest.kt new file mode 100644 index 00000000..956ba39b --- /dev/null +++ b/core/model/src/main/java/com/goms/model/request/auth/SendNumberRequest.kt @@ -0,0 +1,9 @@ +package com.goms.model.request.auth + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class SendNumberRequest( + @Json(name = "email") val email: String +) \ No newline at end of file diff --git a/core/model/src/main/java/com/goms/model/request/auth/SignUpRequest.kt b/core/model/src/main/java/com/goms/model/request/auth/SignUpRequest.kt new file mode 100644 index 00000000..e18b08b8 --- /dev/null +++ b/core/model/src/main/java/com/goms/model/request/auth/SignUpRequest.kt @@ -0,0 +1,15 @@ +package com.goms.model.request.auth + +import com.goms.model.enum.Gender +import com.goms.model.enum.Major +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class SignUpRequest( + @Json(name = "email") val email: String, + @Json(name = "password") val password: String, + @Json(name = "name") val name: String, + @Json(name = "gender") val gender: String, + @Json(name = "major") val major: String +) From d119f18570d09441c2388e45fd24733ddcb19fae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=ED=98=84=EC=84=9C?= Date: Tue, 23 Jan 2024 17:51:40 +0900 Subject: [PATCH 02/19] :sparkles: :: Add sign up to AuthAPI --- .../main/java/com/goms/network/api/AuthAPI.kt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/core/network/src/main/java/com/goms/network/api/AuthAPI.kt b/core/network/src/main/java/com/goms/network/api/AuthAPI.kt index e61ed829..24224ad4 100644 --- a/core/network/src/main/java/com/goms/network/api/AuthAPI.kt +++ b/core/network/src/main/java/com/goms/network/api/AuthAPI.kt @@ -1,13 +1,33 @@ package com.goms.network.api import com.goms.model.request.auth.LoginRequest +import com.goms.model.request.auth.SendNumberRequest +import com.goms.model.request.auth.SignUpRequest import com.goms.model.response.auth.LoginResponse import retrofit2.http.Body +import retrofit2.http.GET import retrofit2.http.POST +import retrofit2.http.Query interface AuthAPI { + @POST("/api/v2/auth/signup") + suspend fun signUp( + @Body body: SignUpRequest + ) + @POST("/api/v2/auth/signin") suspend fun login( @Body body: LoginRequest ): LoginResponse + + @POST("/api/v2/auth/email/send") + suspend fun sendNumber( + @Body body: SendNumberRequest + ) + + @GET("/api/v2/auth/email/verify") + suspend fun verifyNumber( + @Query("email") email: String, + @Query("authCode") authCode: String + ) } \ No newline at end of file From ccbcb58302e4975ac219a805a6726a0e88ca7fe4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=ED=98=84=EC=84=9C?= Date: Tue, 23 Jan 2024 17:51:59 +0900 Subject: [PATCH 03/19] :sparkles: :: Add sign up to AuthDataSource --- .../network/datasource/auth/AuthDataSource.kt | 8 ++++++ .../datasource/auth/AuthDataSourceImpl.kt | 26 +++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/core/network/src/main/java/com/goms/network/datasource/auth/AuthDataSource.kt b/core/network/src/main/java/com/goms/network/datasource/auth/AuthDataSource.kt index 452ed2d2..372ca11e 100644 --- a/core/network/src/main/java/com/goms/network/datasource/auth/AuthDataSource.kt +++ b/core/network/src/main/java/com/goms/network/datasource/auth/AuthDataSource.kt @@ -1,9 +1,17 @@ package com.goms.network.datasource.auth import com.goms.model.request.auth.LoginRequest +import com.goms.model.request.auth.SendNumberRequest +import com.goms.model.request.auth.SignUpRequest import com.goms.model.response.auth.LoginResponse import kotlinx.coroutines.flow.Flow interface AuthDataSource { + suspend fun signUp(body: SignUpRequest): Flow + suspend fun login(body: LoginRequest): Flow + + suspend fun sendNumber(body: SendNumberRequest): Flow + + suspend fun verifyNumber(email: String, authCode: String): Flow } \ No newline at end of file diff --git a/core/network/src/main/java/com/goms/network/datasource/auth/AuthDataSourceImpl.kt b/core/network/src/main/java/com/goms/network/datasource/auth/AuthDataSourceImpl.kt index b2a1af98..fb19bf54 100644 --- a/core/network/src/main/java/com/goms/network/datasource/auth/AuthDataSourceImpl.kt +++ b/core/network/src/main/java/com/goms/network/datasource/auth/AuthDataSourceImpl.kt @@ -1,6 +1,8 @@ package com.goms.network.datasource.auth import com.goms.model.request.auth.LoginRequest +import com.goms.model.request.auth.SendNumberRequest +import com.goms.model.request.auth.SignUpRequest import com.goms.model.response.auth.LoginResponse import com.goms.network.api.AuthAPI import com.goms.network.util.GomsApiHandler @@ -13,6 +15,14 @@ import javax.inject.Inject class AuthDataSourceImpl @Inject constructor( private val authAPI: AuthAPI ) : AuthDataSource { + override suspend fun signUp(body: SignUpRequest): Flow = flow { + emit( + GomsApiHandler() + .httpRequest { authAPI.signUp(body = body) } + .sendRequest() + ) + }.flowOn(Dispatchers.IO) + override suspend fun login(body: LoginRequest): Flow = flow { emit( GomsApiHandler() @@ -20,4 +30,20 @@ class AuthDataSourceImpl @Inject constructor( .sendRequest() ) }.flowOn(Dispatchers.IO) + + override suspend fun sendNumber(body: SendNumberRequest): Flow = flow { + emit( + GomsApiHandler() + .httpRequest { authAPI.sendNumber(body = body) } + .sendRequest() + ) + }.flowOn(Dispatchers.IO) + + override suspend fun verifyNumber(email: String, authCode: String): Flow = flow { + emit( + GomsApiHandler() + .httpRequest { authAPI.verifyNumber(email = email, authCode = authCode) } + .sendRequest() + ) + }.flowOn(Dispatchers.IO) } \ No newline at end of file From 207952100667d13b1084f29f6e7c9f7ad5a20a46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=ED=98=84=EC=84=9C?= Date: Tue, 23 Jan 2024 17:52:12 +0900 Subject: [PATCH 04/19] :sparkles: :: Add sign up to AuthRepository --- .../goms/data/repository/auth/AuthRepository.kt | 8 ++++++++ .../data/repository/auth/AuthRepositoryImpl.kt | 14 ++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/core/data/src/main/java/com/goms/data/repository/auth/AuthRepository.kt b/core/data/src/main/java/com/goms/data/repository/auth/AuthRepository.kt index 78dc3012..0a0094c3 100644 --- a/core/data/src/main/java/com/goms/data/repository/auth/AuthRepository.kt +++ b/core/data/src/main/java/com/goms/data/repository/auth/AuthRepository.kt @@ -1,11 +1,19 @@ package com.goms.data.repository.auth import com.goms.model.request.auth.LoginRequest +import com.goms.model.request.auth.SendNumberRequest +import com.goms.model.request.auth.SignUpRequest import com.goms.model.response.auth.LoginResponse import kotlinx.coroutines.flow.Flow interface AuthRepository { + suspend fun signUp(body: SignUpRequest): Flow + suspend fun login(body: LoginRequest): Flow suspend fun saveToken(token: LoginResponse) + + suspend fun sendNumber(body: SendNumberRequest): Flow + + suspend fun verifyNumber(email: String, authCode: String): Flow } \ No newline at end of file diff --git a/core/data/src/main/java/com/goms/data/repository/auth/AuthRepositoryImpl.kt b/core/data/src/main/java/com/goms/data/repository/auth/AuthRepositoryImpl.kt index b2dbb499..b50e23df 100644 --- a/core/data/src/main/java/com/goms/data/repository/auth/AuthRepositoryImpl.kt +++ b/core/data/src/main/java/com/goms/data/repository/auth/AuthRepositoryImpl.kt @@ -2,6 +2,8 @@ package com.goms.data.repository.auth import com.goms.datastore.AuthTokenDataSource import com.goms.model.request.auth.LoginRequest +import com.goms.model.request.auth.SendNumberRequest +import com.goms.model.request.auth.SignUpRequest import com.goms.model.response.auth.LoginResponse import com.goms.network.datasource.auth.AuthDataSource import kotlinx.coroutines.flow.Flow @@ -11,6 +13,10 @@ class AuthRepositoryImpl @Inject constructor( private val remoteAuthDataSource: AuthDataSource, private val localAuthDataSource: AuthTokenDataSource ) : AuthRepository { + override suspend fun signUp(body: SignUpRequest): Flow { + return remoteAuthDataSource.signUp(body = body) + } + override suspend fun login(body: LoginRequest): Flow { return remoteAuthDataSource.login(body = body) } @@ -24,4 +30,12 @@ class AuthRepositoryImpl @Inject constructor( localAuthDataSource.setAuthority(it.authority.name) } } + + override suspend fun sendNumber(body: SendNumberRequest): Flow { + return remoteAuthDataSource.sendNumber(body = body) + } + + override suspend fun verifyNumber(email: String, authCode: String): Flow { + return remoteAuthDataSource.verifyNumber(email = email, authCode = authCode) + } } \ No newline at end of file From 84c383ebc470dc46e88b0c65681999e9e8f63f60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=ED=98=84=EC=84=9C?= Date: Tue, 23 Jan 2024 17:52:26 +0900 Subject: [PATCH 05/19] :sparkles: :: Add SendNumberUseCase --- .../java/com/goms/domain/auth/SendNumberUseCase.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 core/domain/src/main/java/com/goms/domain/auth/SendNumberUseCase.kt diff --git a/core/domain/src/main/java/com/goms/domain/auth/SendNumberUseCase.kt b/core/domain/src/main/java/com/goms/domain/auth/SendNumberUseCase.kt new file mode 100644 index 00000000..d971baff --- /dev/null +++ b/core/domain/src/main/java/com/goms/domain/auth/SendNumberUseCase.kt @@ -0,0 +1,13 @@ +package com.goms.domain.auth + +import com.goms.data.repository.auth.AuthRepository +import com.goms.model.request.auth.SendNumberRequest +import javax.inject.Inject + +class SendNumberUseCase @Inject constructor( + private val authRepository: AuthRepository +) { + suspend operator fun invoke(body: SendNumberRequest) = kotlin.runCatching { + authRepository.sendNumber(body = body) + } +} \ No newline at end of file From acd032a4a6dd0e7f9664180e8a6ffda1ab91ed21 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=ED=98=84=EC=84=9C?= Date: Tue, 23 Jan 2024 17:52:33 +0900 Subject: [PATCH 06/19] :sparkles: :: Add VerifyNumberUseCase --- .../java/com/goms/domain/auth/VerifyNumberUseCase.kt | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 core/domain/src/main/java/com/goms/domain/auth/VerifyNumberUseCase.kt diff --git a/core/domain/src/main/java/com/goms/domain/auth/VerifyNumberUseCase.kt b/core/domain/src/main/java/com/goms/domain/auth/VerifyNumberUseCase.kt new file mode 100644 index 00000000..d2f03aa6 --- /dev/null +++ b/core/domain/src/main/java/com/goms/domain/auth/VerifyNumberUseCase.kt @@ -0,0 +1,12 @@ +package com.goms.domain.auth + +import com.goms.data.repository.auth.AuthRepository +import javax.inject.Inject + +class VerifyNumberUseCase @Inject constructor( + private val authRepository: AuthRepository +) { + suspend operator fun invoke(email: String, authCode: String) = kotlin.runCatching { + authRepository.verifyNumber(email = email, authCode = authCode) + } +} \ No newline at end of file From 993e6eac55f3e83789f63e04f3444a4a2d9da5b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=ED=98=84=EC=84=9C?= Date: Tue, 23 Jan 2024 17:52:41 +0900 Subject: [PATCH 07/19] :sparkles: :: Add SighUpUseCase --- .../main/java/com/goms/domain/auth/SighUpUseCase.kt | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 core/domain/src/main/java/com/goms/domain/auth/SighUpUseCase.kt diff --git a/core/domain/src/main/java/com/goms/domain/auth/SighUpUseCase.kt b/core/domain/src/main/java/com/goms/domain/auth/SighUpUseCase.kt new file mode 100644 index 00000000..8f6aa9e5 --- /dev/null +++ b/core/domain/src/main/java/com/goms/domain/auth/SighUpUseCase.kt @@ -0,0 +1,13 @@ +package com.goms.domain.auth + +import com.goms.data.repository.auth.AuthRepository +import com.goms.model.request.auth.SignUpRequest +import javax.inject.Inject + +class SighUpUseCase @Inject constructor( + private val authRepository: AuthRepository +) { + suspend operator fun invoke(body: SignUpRequest) = kotlin.runCatching { + authRepository.signUp(body = body) + } +} \ No newline at end of file From 6ff74249d75769114bb7819a1faef33164391af7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=ED=98=84=EC=84=9C?= Date: Tue, 23 Jan 2024 17:53:01 +0900 Subject: [PATCH 08/19] :sparkles: :: Add sign up to SignUpViewModel --- .../goms/sign_up/viewmodel/SignUpViewModel.kt | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 feature/sign-up/src/main/java/com/goms/sign_up/viewmodel/SignUpViewModel.kt diff --git a/feature/sign-up/src/main/java/com/goms/sign_up/viewmodel/SignUpViewModel.kt b/feature/sign-up/src/main/java/com/goms/sign_up/viewmodel/SignUpViewModel.kt new file mode 100644 index 00000000..17998021 --- /dev/null +++ b/feature/sign-up/src/main/java/com/goms/sign_up/viewmodel/SignUpViewModel.kt @@ -0,0 +1,120 @@ +package com.goms.sign_up.viewmodel + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.goms.common.result.Result +import com.goms.domain.auth.SendNumberUseCase +import com.goms.domain.auth.SighUpUseCase +import com.goms.domain.auth.VerifyNumberUseCase +import com.goms.model.request.auth.SendNumberRequest +import com.goms.model.request.auth.SignUpRequest +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.catch +import kotlinx.coroutines.launch +import javax.inject.Inject + +@HiltViewModel +class SignUpViewModel @Inject constructor( + private val savedStateHandle: SavedStateHandle, + private val signUpUseCase: SighUpUseCase, + private val sendNumberUseCase: SendNumberUseCase, + private val verifyNumberUseCase: VerifyNumberUseCase +) : ViewModel() { + private val _signUpResponse = MutableStateFlow>(Result.Loading) + val signUpResponse = _signUpResponse.asStateFlow() + + private val _sendNumberResponse = MutableStateFlow>(Result.Loading) + val sendNumberResponse = _sendNumberResponse.asStateFlow() + + private val _verifyNumberResponse = MutableStateFlow>(Result.Loading) + val verifyNumberResponse = _verifyNumberResponse.asStateFlow() + + var name = savedStateHandle.getStateFlow(key = NAME, initialValue = "") + var email = savedStateHandle.getStateFlow(key = EMAIL, initialValue = "") + var gender = savedStateHandle.getStateFlow(key = GENDER, initialValue = "") + var major = savedStateHandle.getStateFlow(key = MAJOR, initialValue = "") + var number = savedStateHandle.getStateFlow(key = NUMBER, initialValue = "") + var password = savedStateHandle.getStateFlow(key = PASSWORD, initialValue = "") + + fun signUp(body: SignUpRequest) = viewModelScope.launch { + signUpUseCase(body = body) + .onSuccess { + it.catch { remoteError -> + _signUpResponse.value = Result.Error(remoteError) + }.collect { result -> + _signUpResponse.value = Result.Success(result) + } + }.onFailure { + _signUpResponse.value = Result.Error(it) + } + } + + fun sendNumber(body: SendNumberRequest) = viewModelScope.launch { + sendNumberUseCase(body = body) + .onSuccess { + it.catch { remoteError -> + _sendNumberResponse.value = Result.Error(remoteError) + }.collect { result -> + _sendNumberResponse.value = Result.Success(result) + } + }.onFailure { + _sendNumberResponse.value = Result.Error(it) + } + } + + fun initSendNumber() { + _sendNumberResponse.value = Result.Loading + } + + + fun verifyNumber(email: String, authCode: String) = viewModelScope.launch { + verifyNumberUseCase(email = email, authCode = authCode) + .onSuccess { + it.catch { remoteError -> + _verifyNumberResponse.value = Result.Error(remoteError) + }.collect { result -> + _verifyNumberResponse.value = Result.Success(result) + } + }.onFailure { + _verifyNumberResponse.value = Result.Error(it) + } + } + + fun initVerifyNumber() { + _verifyNumberResponse.value = Result.Loading + } + + fun onNameChange(value: String) { + savedStateHandle[NAME] = value + } + + fun onEmailChange(value: String) { + savedStateHandle[EMAIL] = value + } + + fun onGenderChange(value: String) { + savedStateHandle[GENDER] = value + } + + fun onMajorChange(value: String) { + savedStateHandle[MAJOR] = value + } + + fun onNumberChange(value: String) { + savedStateHandle[NUMBER] = value + } + + fun onPasswordChange(value: String) { + savedStateHandle[PASSWORD] = value + } +} + +private const val NAME = "name" +private const val EMAIL = "email" +private const val GENDER = "gender" +private const val MAJOR = "major" +private const val NUMBER = "number" +private const val PASSWORD = "password" From 7b139246c950a284d37254b79f7fe7049537b02c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=ED=98=84=EC=84=9C?= Date: Tue, 23 Jan 2024 17:53:40 +0900 Subject: [PATCH 09/19] :sparkles: :: Add savedStateHandle --- .../{AuthViewModel.kt => LoginViewModel.kt} | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) rename feature/login/src/main/java/com/goms/login/viewmodel/{AuthViewModel.kt => LoginViewModel.kt} (75%) diff --git a/feature/login/src/main/java/com/goms/login/viewmodel/AuthViewModel.kt b/feature/login/src/main/java/com/goms/login/viewmodel/LoginViewModel.kt similarity index 75% rename from feature/login/src/main/java/com/goms/login/viewmodel/AuthViewModel.kt rename to feature/login/src/main/java/com/goms/login/viewmodel/LoginViewModel.kt index a23f00ea..c3d9345f 100644 --- a/feature/login/src/main/java/com/goms/login/viewmodel/AuthViewModel.kt +++ b/feature/login/src/main/java/com/goms/login/viewmodel/LoginViewModel.kt @@ -1,5 +1,6 @@ package com.goms.login.viewmodel +import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.goms.common.result.Result @@ -16,7 +17,8 @@ import kotlinx.coroutines.launch import javax.inject.Inject @HiltViewModel -class AuthViewModel @Inject constructor( +class LoginViewModel @Inject constructor( + private val savedStateHandle: SavedStateHandle, private val loginUseCase: LoginUseCase, private val saveTokenUseCase: SaveTokenUseCase ) : ViewModel() { @@ -26,13 +28,16 @@ class AuthViewModel @Inject constructor( private val _saveTokenResponse = MutableStateFlow>(Result.Loading) val saveTokenResponse = _saveTokenResponse.asStateFlow() + var email = savedStateHandle.getStateFlow(key = EMAIL, initialValue = "") + var password = savedStateHandle.getStateFlow(key = PASSWORD, initialValue = "") + fun login(body: LoginRequest) = viewModelScope.launch { loginUseCase(body = body) .asResult() .collectLatest{ result -> when (result) { is Result.Loading -> _loginResponse.value = LoginUiState.Loading - is Result.Success -> _loginResponse.value = LoginUiState.Success(result.data) + is Result.Success -> _loginResponse.value = LoginUiState.Success(result.data!!) is Result.Error -> _loginResponse.value = LoginUiState.Error(result.exception) } } @@ -46,4 +51,15 @@ class AuthViewModel @Inject constructor( _saveTokenResponse.value = Result.Error(it) } } -} \ No newline at end of file + + fun onEmailChange(value: String) { + savedStateHandle[EMAIL] = value + } + + fun onPasswordChange(value: String) { + savedStateHandle[PASSWORD] = value + } +} + +private const val EMAIL = "email" +private const val PASSWORD = "password" \ No newline at end of file From 3aa5ee9a02cd93435f589752f70658fde8f1df51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=ED=98=84=EC=84=9C?= Date: Tue, 23 Jan 2024 17:54:02 +0900 Subject: [PATCH 10/19] :sparkles: :: Add isStrongPassword function --- .../java/com/goms/design_system/util/isStrongPassword.kt | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 core/design-system/src/main/java/com/goms/design_system/util/isStrongPassword.kt diff --git a/core/design-system/src/main/java/com/goms/design_system/util/isStrongPassword.kt b/core/design-system/src/main/java/com/goms/design_system/util/isStrongPassword.kt new file mode 100644 index 00000000..7102e7a2 --- /dev/null +++ b/core/design-system/src/main/java/com/goms/design_system/util/isStrongPassword.kt @@ -0,0 +1,6 @@ +package com.goms.design_system.util + +fun isStrongPassword(password: String): Boolean { + val regex = Regex("^(?=.*[A-Z])(?=.*[a-z])(?=.*[!@#\$%^&*(),.?\":{}|<>])(.{12,})\$") + return regex.matches(password) +} \ No newline at end of file From 310bda02d269d44885fdc2f2ef48aa8aa26d9257 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=ED=98=84=EC=84=9C?= Date: Tue, 23 Jan 2024 17:54:33 +0900 Subject: [PATCH 11/19] :sparkles: :: Modify GomsPasswordTextField --- .../component/textfield/GomsTextField.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/core/design-system/src/main/java/com/goms/design_system/component/textfield/GomsTextField.kt b/core/design-system/src/main/java/com/goms/design_system/component/textfield/GomsTextField.kt index 31899fc0..aa83ef3e 100644 --- a/core/design-system/src/main/java/com/goms/design_system/component/textfield/GomsTextField.kt +++ b/core/design-system/src/main/java/com/goms/design_system/component/textfield/GomsTextField.kt @@ -411,20 +411,21 @@ fun GomsPasswordTextField( ) Column( modifier = Modifier - .height(32.dp) + .height(50.dp) .padding(start = 8.dp), verticalArrangement = Arrangement.Center ) { - if (isError) { + if (!isFocused.value) { Text( - text = errorText, - color = colors.N5, + text = "대/소문자, 특수문자 12자 이상", + color = colors.G4, style = typography.buttonLarge ) - } else if (!isFocused.value) { + } + if (isError) { Text( - text = "대/소문자, 특수문자 12자 이상", - color = colors.G4, + text = errorText, + color = colors.N5, style = typography.buttonLarge ) } From 78e22f3702ce3dc84896bc0ae5890dc478282d28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=ED=98=84=EC=84=9C?= Date: Tue, 23 Jan 2024 17:55:02 +0900 Subject: [PATCH 12/19] :sparkles: :: Add 204 exception --- .../src/main/java/com/goms/network/util/AuthInterceptor.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/network/src/main/java/com/goms/network/util/AuthInterceptor.kt b/core/network/src/main/java/com/goms/network/util/AuthInterceptor.kt index f64684d3..c45ce754 100644 --- a/core/network/src/main/java/com/goms/network/util/AuthInterceptor.kt +++ b/core/network/src/main/java/com/goms/network/util/AuthInterceptor.kt @@ -21,6 +21,7 @@ class AuthInterceptor @Inject constructor( ): Interceptor { override fun intercept(chain: Interceptor.Chain): Response { val request = chain.request() + val response = chain.proceed(request) val builder = request.newBuilder() val currentTime = System.currentTimeMillis().toGomsTimeDate() val ignorePath = listOf("/auth") @@ -34,6 +35,10 @@ class AuthInterceptor @Inject constructor( } } + if (response.code == 204) { + return response.newBuilder().code(200).build() + } + runBlocking { val refreshTime = dataSource.getRefreshTokenExp().first().replace("\"", "") val accessTime = dataSource.getAccessTokenExp().first().replace("\"", "") From 3312846753fd13c1e672e72732cec5dbf32fb90e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=ED=98=84=EC=84=9C?= Date: Tue, 23 Jan 2024 17:55:26 +0900 Subject: [PATCH 13/19] :sparkles: :: Add navigateToTopLevelDestination --- .../goms/goms_android_v2/ui/GomsAppState.kt | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/app/src/main/java/com/goms/goms_android_v2/ui/GomsAppState.kt b/app/src/main/java/com/goms/goms_android_v2/ui/GomsAppState.kt index 38e86bb7..3d721810 100644 --- a/app/src/main/java/com/goms/goms_android_v2/ui/GomsAppState.kt +++ b/app/src/main/java/com/goms/goms_android_v2/ui/GomsAppState.kt @@ -6,12 +6,16 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.Stable import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.util.trace import androidx.navigation.NavDestination +import androidx.navigation.NavGraph.Companion.findStartDestination import androidx.navigation.NavHostController import androidx.navigation.compose.currentBackStackEntryAsState import androidx.navigation.compose.rememberNavController +import androidx.navigation.navOptions import com.goms.goms_android_v2.navigation.TopLevelDestination import com.goms.login.navigation.loginRoute +import com.goms.login.navigation.navigateToLogin import kotlinx.coroutines.CoroutineScope @Composable @@ -53,4 +57,20 @@ class GomsAppState( get() = windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact val topLevelDestinations: List = TopLevelDestination.values().asList() + + fun navigateToTopLevelDestination(topLevelDestination: TopLevelDestination) { + trace("Navigation: ${topLevelDestination.name}") { + val topLevelNavOptions = navOptions { + popUpTo(navController.graph.findStartDestination().id) { + saveState = true + } + launchSingleTop = true + restoreState = true + } + + when (topLevelDestination) { + TopLevelDestination.LOGIN -> navController.navigateToLogin(topLevelNavOptions) + } + } + } } \ No newline at end of file From f969ba8c92f6bb428f25b644fd7c94c0c250aef6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=ED=98=84=EC=84=9C?= Date: Tue, 23 Jan 2024 18:47:15 +0900 Subject: [PATCH 14/19] :sparkles: :: Modify response --- .../java/com/goms/network/util/AuthInterceptor.kt | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/core/network/src/main/java/com/goms/network/util/AuthInterceptor.kt b/core/network/src/main/java/com/goms/network/util/AuthInterceptor.kt index c45ce754..05daaac5 100644 --- a/core/network/src/main/java/com/goms/network/util/AuthInterceptor.kt +++ b/core/network/src/main/java/com/goms/network/util/AuthInterceptor.kt @@ -31,14 +31,10 @@ class AuthInterceptor @Inject constructor( ignorePath.forEachIndexed { index, s -> if (s == path && ignoreMethod[index] == method) { - return chain.proceed(request) + return response } } - if (response.code == 204) { - return response.newBuilder().code(200).build() - } - runBlocking { val refreshTime = dataSource.getRefreshTokenExp().first().replace("\"", "") val accessTime = dataSource.getAccessTokenExp().first().replace("\"", "") @@ -82,6 +78,10 @@ class AuthInterceptor @Inject constructor( val accessToken = dataSource.getAccessToken().first().replace("\"", "") builder.addHeader("Authorization", "Bearer $accessToken") } - return chain.proceed(builder.build()) + return if (response.code == 204) { + response.newBuilder().code(200).build() + } else { + response + } } } \ No newline at end of file From 8e8a7ccec36471df13cef3edd5a4bb6428171408 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=ED=98=84=EC=84=9C?= Date: Tue, 23 Jan 2024 18:47:27 +0900 Subject: [PATCH 15/19] :sparkles: :: Modify navigateToTopLevelDestination --- app/src/main/java/com/goms/goms_android_v2/ui/GomsAppState.kt | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/com/goms/goms_android_v2/ui/GomsAppState.kt b/app/src/main/java/com/goms/goms_android_v2/ui/GomsAppState.kt index 3d721810..37eb846f 100644 --- a/app/src/main/java/com/goms/goms_android_v2/ui/GomsAppState.kt +++ b/app/src/main/java/com/goms/goms_android_v2/ui/GomsAppState.kt @@ -62,10 +62,8 @@ class GomsAppState( trace("Navigation: ${topLevelDestination.name}") { val topLevelNavOptions = navOptions { popUpTo(navController.graph.findStartDestination().id) { - saveState = true + inclusive = true } - launchSingleTop = true - restoreState = true } when (topLevelDestination) { From d8fb8e1be07a6067f78e0d705f77b413d4aaad06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=ED=98=84=EC=84=9C?= Date: Tue, 23 Jan 2024 18:47:44 +0900 Subject: [PATCH 16/19] :sparkles: :: Add SignUpViewModelProvider --- .../sign_up/viewmodel/SharedViewModelProvider.kt | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 feature/sign-up/src/main/java/com/goms/sign_up/viewmodel/SharedViewModelProvider.kt diff --git a/feature/sign-up/src/main/java/com/goms/sign_up/viewmodel/SharedViewModelProvider.kt b/feature/sign-up/src/main/java/com/goms/sign_up/viewmodel/SharedViewModelProvider.kt new file mode 100644 index 00000000..b4fc0c8d --- /dev/null +++ b/feature/sign-up/src/main/java/com/goms/sign_up/viewmodel/SharedViewModelProvider.kt @@ -0,0 +1,14 @@ +package com.goms.sign_up.viewmodel + +import androidx.compose.runtime.Composable +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.lifecycle.ViewModelStoreOwner + +@Composable +fun SignUpViewModelProvider( + viewModelStoreOwner: ViewModelStoreOwner, + content: @Composable (viewModel: SignUpViewModel) -> Unit +) { + val viewModel: SignUpViewModel = hiltViewModel(viewModelStoreOwner) + content(viewModel) +} \ No newline at end of file From 1e5db26c9fb5f0abee62bdd090b99ea69851e65a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=ED=98=84=EC=84=9C?= Date: Tue, 23 Jan 2024 18:48:25 +0900 Subject: [PATCH 17/19] :sparkles: :: Add viewModelStoreOwner --- .../com/goms/goms_android_v2/navigation/GomsNavHost.kt | 7 ++++++- .../java/com/goms/sign_up/navigation/SignUpNavigation.kt | 7 +++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/goms/goms_android_v2/navigation/GomsNavHost.kt b/app/src/main/java/com/goms/goms_android_v2/navigation/GomsNavHost.kt index e963e59f..098a828b 100644 --- a/app/src/main/java/com/goms/goms_android_v2/navigation/GomsNavHost.kt +++ b/app/src/main/java/com/goms/goms_android_v2/navigation/GomsNavHost.kt @@ -2,6 +2,7 @@ package com.goms.goms_android_v2.navigation import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner import androidx.navigation.compose.NavHost import com.goms.goms_android_v2.ui.GomsAppState import com.goms.login.navigation.inputLoginScreen @@ -23,6 +24,7 @@ fun GomsNavHost( startDestination: String = loginRoute ) { val navController = appState.navController + val viewModelStoreOwner = checkNotNull(LocalViewModelStoreOwner.current) NavHost( navController = navController, startDestination = startDestination, @@ -36,16 +38,19 @@ fun GomsNavHost( onBackClick = navController::popBackStack ) signUpScreen( + viewModelStoreOwner = viewModelStoreOwner, onBackClick = navController::popBackStack, onNumberClick = navController::navigateToNumber ) numberScreen( + viewModelStoreOwner = viewModelStoreOwner, onBackClick = navController::popBackStack, onPasswordClick = navController::navigateToPassword ) passwordScreen( + viewModelStoreOwner = viewModelStoreOwner, onBackClick = navController::popBackStack, - onLoginClick = navController::navigateToLogin + onLoginClick = { appState.navigateToTopLevelDestination(TopLevelDestination.LOGIN) } ) } } \ No newline at end of file diff --git a/feature/sign-up/src/main/java/com/goms/sign_up/navigation/SignUpNavigation.kt b/feature/sign-up/src/main/java/com/goms/sign_up/navigation/SignUpNavigation.kt index 91fc743e..eb57daac 100644 --- a/feature/sign-up/src/main/java/com/goms/sign_up/navigation/SignUpNavigation.kt +++ b/feature/sign-up/src/main/java/com/goms/sign_up/navigation/SignUpNavigation.kt @@ -1,5 +1,6 @@ package com.goms.sign_up.navigation +import androidx.lifecycle.ViewModelStoreOwner import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavOptions @@ -17,11 +18,13 @@ fun NavController.navigateToSignUp(navOptions: NavOptions? = null) { } fun NavGraphBuilder.signUpScreen( + viewModelStoreOwner: ViewModelStoreOwner, onBackClick: () -> Unit, onNumberClick: () -> Unit ) { composable(route = signUpRoute) { SignUpRoute( + viewModelStoreOwner = viewModelStoreOwner, onBackClick = onBackClick, onNumberClick = onNumberClick ) @@ -33,11 +36,13 @@ fun NavController.navigateToNumber(navOptions: NavOptions? = null) { } fun NavGraphBuilder.numberScreen( + viewModelStoreOwner: ViewModelStoreOwner, onBackClick: () -> Unit, onPasswordClick: () -> Unit ) { composable(route = numberRoute) { NumberRoute( + viewModelStoreOwner = viewModelStoreOwner, onBackClick = onBackClick, onPasswordClick = onPasswordClick ) @@ -49,11 +54,13 @@ fun NavController.navigateToPassword(navOptions: NavOptions? = null) { } fun NavGraphBuilder.passwordScreen( + viewModelStoreOwner: ViewModelStoreOwner, onBackClick: () -> Unit, onLoginClick: () -> Unit ) { composable(route = passwordRoute) { PasswordRoute( + viewModelStoreOwner = viewModelStoreOwner, onBackClick = onBackClick, onLoginClick = onLoginClick ) From 26a60bef7aec6dad20e29cc09014cbc241e1c31c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=ED=98=84=EC=84=9C?= Date: Tue, 23 Jan 2024 18:48:51 +0900 Subject: [PATCH 18/19] :sparkles: :: Add savedStateHandle --- .../java/com/goms/login/InputLoginScreen.kt | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/feature/login/src/main/java/com/goms/login/InputLoginScreen.kt b/feature/login/src/main/java/com/goms/login/InputLoginScreen.kt index 29bb766b..0b9c685e 100644 --- a/feature/login/src/main/java/com/goms/login/InputLoginScreen.kt +++ b/feature/login/src/main/java/com/goms/login/InputLoginScreen.kt @@ -37,16 +37,18 @@ import com.goms.design_system.theme.GomsTheme import com.goms.design_system.util.keyboardAsState import com.goms.design_system.util.lockScreenOrientation import com.goms.login.component.InputLoginText -import com.goms.login.viewmodel.AuthViewModel +import com.goms.login.viewmodel.LoginViewModel import com.goms.login.viewmodel.LoginUiState import com.goms.model.request.auth.LoginRequest @Composable fun InputLoginRoute( onBackClick: () -> Unit, - viewModel: AuthViewModel = hiltViewModel(), + viewModel: LoginViewModel = hiltViewModel(), ) { val loginResponse = viewModel.loginResponse.collectAsStateWithLifecycle() + val email by viewModel.email.collectAsStateWithLifecycle() + val password by viewModel.password.collectAsStateWithLifecycle() var isError by remember { mutableStateOf(false) } var errorText by remember { mutableStateOf("") } @@ -57,36 +59,39 @@ fun InputLoginRoute( } is LoginUiState.Error -> { isError = true - errorText = "로그인에 실패하였습니다." + errorText = "로그인에 실패하였습니다" } } InputLoginScreen( - onBackClick = onBackClick, + email = email, + password = password, isError = isError, errorText = errorText, - loginCallBack = { email, password -> - viewModel.login(body = LoginRequest(email, password)) - } + onEmailChange = viewModel::onEmailChange, + onPasswordChange = viewModel::onPasswordChange, + onBackClick = onBackClick, + loginCallBack = { viewModel.login(body = LoginRequest("${viewModel.email.value}@gsm.hs.kr", viewModel.password.value)) } ) } @Composable fun InputLoginScreen( - onBackClick: () -> Unit, + email: String, + password: String, isError: Boolean, errorText: String, - loginCallBack: (email: String, password: String) -> Unit + onEmailChange: (String) -> Unit, + onPasswordChange: (String) -> Unit, + onBackClick: () -> Unit, + loginCallBack: () -> Unit ) { val focusManager = LocalFocusManager.current val isKeyboardOpen by keyboardAsState() var isHidden by remember { mutableStateOf(false) } val animatedSpacerHeight by animateDpAsState(targetValue = if (isHidden) 100.dp else 16.dp) - var email by remember { mutableStateOf("") } - var password by remember { mutableStateOf("") } - LaunchedEffect(isKeyboardOpen) { if (isKeyboardOpen) { isHidden = false @@ -125,9 +130,7 @@ fun InputLoginScreen( keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email), placeHolder = "이메일", setText = email, - onValueChange = { emailChange -> - email = emailChange - }, + onValueChange = onEmailChange, isError = isError, singleLine = true ) @@ -137,9 +140,7 @@ fun InputLoginScreen( keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), placeHolder = "비밀번호", setText = password, - onValueChange = { passwordChange -> - password = passwordChange - }, + onValueChange = onPasswordChange, isError = isError, errorText = errorText, singleLine = true, @@ -152,7 +153,7 @@ fun InputLoginScreen( state = if (email.isNotBlank() && password.isNotBlank()) ButtonState.Normal else ButtonState.Enable ) { - loginCallBack("$email@gsm.hs.kr", password) + loginCallBack() } Spacer(modifier = Modifier.height(animatedSpacerHeight)) } From 76a1973c5cb60febc13ddb4033cb8f980b54d36d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=86=A1=ED=98=84=EC=84=9C?= Date: Tue, 23 Jan 2024 18:49:22 +0900 Subject: [PATCH 19/19] :sparkles: :: Write the logic sign up --- .../java/com/goms/sign_up/NumberScreen.kt | 61 +++++++++---- .../java/com/goms/sign_up/PasswordScreen.kt | 78 ++++++++++++++--- .../java/com/goms/sign_up/SignUpScreen.kt | 85 ++++++++++++------- 3 files changed, 163 insertions(+), 61 deletions(-) diff --git a/feature/sign-up/src/main/java/com/goms/sign_up/NumberScreen.kt b/feature/sign-up/src/main/java/com/goms/sign_up/NumberScreen.kt index b6149398..e481de6a 100644 --- a/feature/sign-up/src/main/java/com/goms/sign_up/NumberScreen.kt +++ b/feature/sign-up/src/main/java/com/goms/sign_up/NumberScreen.kt @@ -24,6 +24,9 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.unit.dp +import androidx.lifecycle.ViewModelStoreOwner +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.goms.common.result.Result import com.goms.design_system.component.button.ButtonState import com.goms.design_system.component.button.GomsBackButton import com.goms.design_system.component.button.GomsButton @@ -32,32 +35,62 @@ import com.goms.design_system.theme.GomsTheme import com.goms.design_system.util.keyboardAsState import com.goms.design_system.util.lockScreenOrientation import com.goms.sign_up.component.NumberText +import com.goms.sign_up.viewmodel.SignUpViewModelProvider @Composable fun NumberRoute( + viewModelStoreOwner: ViewModelStoreOwner, onBackClick: () -> Unit, - onPasswordClick: () -> Unit + onPasswordClick: () -> Unit, ) { - NumberScreen( - onBackClick = onBackClick, - onPasswordClick = onPasswordClick - ) + SignUpViewModelProvider(viewModelStoreOwner = viewModelStoreOwner) { viewModel -> + val verifyNumberResponse = viewModel.verifyNumberResponse.collectAsStateWithLifecycle() + val number by viewModel.number.collectAsStateWithLifecycle() + var isError by remember { mutableStateOf(false) } + var errorText by remember { mutableStateOf("") } + + when (verifyNumberResponse.value) { + is Result.Loading -> Unit + is Result.Success -> { + onPasswordClick() + viewModel.initVerifyNumber() + } + is Result.Error -> { + isError = true + errorText = "잘못된 인증번호입니다" + } + } + + NumberScreen( + number = number, + isError = isError, + errorText = errorText, + onNumberChange = viewModel::onNumberChange, + onBackClick = onBackClick, + numberCallback = { + viewModel.verifyNumber( + email = "${viewModel.email.value}@gsm.hs.kr", + authCode = viewModel.number.value + ) + } + ) + } } @Composable fun NumberScreen( + number: String, + isError: Boolean, + errorText: String, + onNumberChange: (String) -> Unit, onBackClick: () -> Unit, - onPasswordClick: () -> Unit + numberCallback: () -> Unit ) { val focusManager = LocalFocusManager.current val isKeyboardOpen by keyboardAsState() var isHidden by remember { mutableStateOf(false) } val animatedSpacerHeight by animateDpAsState(targetValue = if (isHidden) 100.dp else 16.dp) - var number by remember { mutableStateOf("") } - var isNumberError by remember { mutableStateOf(false) } - var errorText by remember { mutableStateOf("") } - LaunchedEffect(isKeyboardOpen) { if (isKeyboardOpen) { isHidden = false @@ -93,11 +126,9 @@ fun NumberScreen( Spacer(modifier = Modifier.weight(2.1f)) NumberTextField( text = number, - isError = isNumberError, + isError = isError, errorText = errorText, - onValueChange = { - number = it - }, + onValueChange = onNumberChange, onResendClick = {} ) Spacer(modifier = Modifier.weight(1f)) @@ -106,7 +137,7 @@ fun NumberScreen( text = "인증", state = if (number.isNotBlank()) ButtonState.Normal else ButtonState.Enable ) { - onPasswordClick() + numberCallback() } Spacer(modifier = Modifier.height(animatedSpacerHeight)) } diff --git a/feature/sign-up/src/main/java/com/goms/sign_up/PasswordScreen.kt b/feature/sign-up/src/main/java/com/goms/sign_up/PasswordScreen.kt index 16fe4457..d0c70a3c 100644 --- a/feature/sign-up/src/main/java/com/goms/sign_up/PasswordScreen.kt +++ b/feature/sign-up/src/main/java/com/goms/sign_up/PasswordScreen.kt @@ -26,39 +26,91 @@ import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp +import androidx.lifecycle.ViewModelStoreOwner +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.goms.common.result.Result import com.goms.design_system.component.button.ButtonState import com.goms.design_system.component.button.GomsBackButton import com.goms.design_system.component.button.GomsButton import com.goms.design_system.component.textfield.GomsPasswordTextField -import com.goms.design_system.component.textfield.GomsTextField import com.goms.design_system.theme.GomsTheme +import com.goms.design_system.util.isStrongPassword import com.goms.design_system.util.keyboardAsState import com.goms.design_system.util.lockScreenOrientation +import com.goms.model.enum.Gender +import com.goms.model.enum.Major +import com.goms.model.request.auth.SignUpRequest import com.goms.sign_up.component.PasswordText +import com.goms.sign_up.viewmodel.SignUpViewModelProvider @Composable fun PasswordRoute( + viewModelStoreOwner: ViewModelStoreOwner, onBackClick: () -> Unit, - onLoginClick: () -> Unit + onLoginClick: () -> Unit, ) { - PasswordScreen( - onBackClick = onBackClick, - onLoginClick = onLoginClick - ) + SignUpViewModelProvider(viewModelStoreOwner = viewModelStoreOwner) { viewModel -> + val signUpResponse = viewModel.signUpResponse.collectAsStateWithLifecycle() + val password by viewModel.password.collectAsStateWithLifecycle() + var isError by remember { mutableStateOf(false) } + var errorText by remember { mutableStateOf("") } + + when (signUpResponse.value) { + is Result.Loading -> Unit + is Result.Success -> { + onLoginClick() + } + is Result.Error -> { + isError = true + errorText = "오류가 발생하였습니다" + } + } + + PasswordScreen( + password = password, + isError = isError, + errorText = errorText, + onPasswordChange = viewModel::onPasswordChange, + onBackClick = onBackClick, + passwordCallback = { + if (isStrongPassword(viewModel.password.value)) { + viewModel.signUp( + body = SignUpRequest( + email = "${viewModel.email.value}@gsm.hs.kr", + password = viewModel.password.value, + name = viewModel.name.value, + gender = if (viewModel.gender.value == Gender.MAN.value) Gender.MAN.name else Gender.WOMAN.name, + major = when (viewModel.major.value) { + Major.SW_DEVELOP.value -> Major.SW_DEVELOP.name + Major.SMART_IOT.value -> Major.SMART_IOT.name + Major.AI.value -> Major.AI.name + else -> "" + }, + ) + ) + } else { + isError = true + errorText = "비밀번호 요구사항을 충족하지 않습니다" + } + } + ) + } } @Composable fun PasswordScreen( + password: String, + isError: Boolean, + errorText: String, + onPasswordChange: (String) -> Unit, onBackClick: () -> Unit, - onLoginClick: () -> Unit + passwordCallback: () -> Unit ) { val focusManager = LocalFocusManager.current val isKeyboardOpen by keyboardAsState() var isHidden by remember { mutableStateOf(false) } val animatedSpacerHeight by animateDpAsState(targetValue = if (isHidden) 100.dp else 16.dp) - var password by remember { mutableStateOf("") } - LaunchedEffect(isKeyboardOpen) { if (isKeyboardOpen) { isHidden = false @@ -94,12 +146,12 @@ fun PasswordScreen( Spacer(modifier = Modifier.weight(1.1f)) GomsPasswordTextField( modifier = Modifier.fillMaxWidth(), + isError = isError, + errorText = errorText, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), placeHolder = "비밀번호", setText = password, - onValueChange = { passwordChange -> - password = passwordChange - }, + onValueChange = onPasswordChange, singleLine = true ) Spacer(modifier = Modifier.weight(1f)) @@ -108,7 +160,7 @@ fun PasswordScreen( text = "회원가입", state = if (password.isNotBlank()) ButtonState.Normal else ButtonState.Enable ) { - onLoginClick() + passwordCallback() } Spacer(modifier = Modifier.height(animatedSpacerHeight)) } diff --git a/feature/sign-up/src/main/java/com/goms/sign_up/SignUpScreen.kt b/feature/sign-up/src/main/java/com/goms/sign_up/SignUpScreen.kt index d37b4a11..71ca8680 100644 --- a/feature/sign-up/src/main/java/com/goms/sign_up/SignUpScreen.kt +++ b/feature/sign-up/src/main/java/com/goms/sign_up/SignUpScreen.kt @@ -1,7 +1,6 @@ package com.goms.sign_up import android.content.pm.ActivityInfo -import android.util.Log import androidx.compose.foundation.background import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.Column @@ -17,7 +16,6 @@ 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.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment @@ -26,6 +24,9 @@ import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.unit.dp +import androidx.lifecycle.ViewModelStoreOwner +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.goms.common.result.Result import com.goms.design_system.component.bottomsheet.SelectorBottomSheet import com.goms.design_system.component.button.ButtonState import com.goms.design_system.component.button.GomsBackButton @@ -37,33 +38,64 @@ import com.goms.design_system.util.keyboardAsState import com.goms.design_system.util.lockScreenOrientation import com.goms.model.enum.Gender import com.goms.model.enum.Major +import com.goms.model.request.auth.SendNumberRequest import com.goms.sign_up.component.SignUpText +import com.goms.sign_up.viewmodel.SignUpViewModelProvider @Composable fun SignUpRoute( + viewModelStoreOwner: ViewModelStoreOwner, onBackClick: () -> Unit, - onNumberClick: () -> Unit + onNumberClick: () -> Unit, ) { - SignUpScreen( - onBackClick = onBackClick, - onNumberClick = onNumberClick - ) + SignUpViewModelProvider(viewModelStoreOwner = viewModelStoreOwner) { viewModel -> + val sendNumberResponse = viewModel.sendNumberResponse.collectAsStateWithLifecycle() + val name by viewModel.name.collectAsStateWithLifecycle() + val email by viewModel.email.collectAsStateWithLifecycle() + val gender by viewModel.gender.collectAsStateWithLifecycle() + val major by viewModel.major.collectAsStateWithLifecycle() + + when (sendNumberResponse.value) { + is Result.Loading -> Unit + is Result.Success -> { + onNumberClick() + viewModel.initSendNumber() + } + is Result.Error -> {} + } + + SignUpScreen( + name = name, + email = email, + gender = gender, + major = major, + onNameChange = viewModel::onNameChange, + onEmailChange = viewModel::onEmailChange, + onGenderChange = viewModel::onGenderChange, + onMajorChange = viewModel::onMajorChange, + onBackClick = onBackClick, + signUpCallBack = { viewModel.sendNumber(body = SendNumberRequest("${viewModel.email.value}@gsm.hs.kr")) } + ) + } } @Composable fun SignUpScreen( + name: String, + email: String, + gender: String, + major: String, + onNameChange: (String) -> Unit, + onEmailChange: (String) -> Unit, + onGenderChange: (String) -> Unit, + onMajorChange: (String) -> Unit, onBackClick: () -> Unit, - onNumberClick: () -> Unit + signUpCallBack: () -> Unit ) { val focusManager = LocalFocusManager.current val isKeyboardOpen by keyboardAsState() - var name by remember { mutableStateOf("") } - var email by remember { mutableStateOf("") } - var gender by remember { mutableStateOf("") } - var major by remember { mutableStateOf("") } - var onGenderBottomSheetOpenClick by rememberSaveable { mutableStateOf(false) } var onMajorBottomSheetOpenClick by rememberSaveable { mutableStateOf(false) } @@ -101,9 +133,7 @@ fun SignUpScreen( keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), placeHolder = "이름", setText = name, - onValueChange = { nameChange -> - name = nameChange - }, + onValueChange = onNameChange, isEmail = false, singleLine = true ) @@ -112,9 +142,7 @@ fun SignUpScreen( keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Email), placeHolder = "이메일", setText = email, - onValueChange = { emailChange -> - email = emailChange - }, + onValueChange = onEmailChange, singleLine = true ) GomsBottomSheetTextField( @@ -122,9 +150,7 @@ fun SignUpScreen( keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), placeHolder = "성별", setText = gender, - onValueChange = { genderChange -> - gender = genderChange - }, + onValueChange = onGenderChange, readOnly = true, singleLine = true ) { @@ -136,9 +162,7 @@ fun SignUpScreen( keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Text), placeHolder = "과", setText = major, - onValueChange = { majorChange -> - major = majorChange - }, + onValueChange = onMajorChange, readOnly = true, singleLine = true ) { @@ -152,7 +176,7 @@ fun SignUpScreen( state = if (name.isNotBlank() && email.isNotBlank() && gender.isNotBlank() && major.isNotBlank()) ButtonState.Normal else ButtonState.Enable ) { - onNumberClick() + signUpCallBack() } Spacer(modifier = Modifier.height(100.dp)) } @@ -163,10 +187,7 @@ fun SignUpScreen( title = "성별", list = listOf(Gender.MAN.value, Gender.WOMAN.value), selected = gender, - itemChange = { - gender = it - Log.d("testt", gender) - }, + itemChange = onGenderChange, closeSheet = { onGenderBottomSheetOpenClick = false } @@ -178,9 +199,7 @@ fun SignUpScreen( title = "과", list = listOf(Major.SW_DEVELOP.value, Major.SMART_IOT.value, Major.AI.value), selected = major, - itemChange = { - major = it - }, + itemChange = onMajorChange, closeSheet = { onMajorBottomSheetOpenClick = false }