diff --git a/app/src/main/java/com/sopt/geonppang/data/datasource/remote/AuthDataSource.kt b/app/src/main/java/com/sopt/geonppang/data/datasource/remote/AuthDataSource.kt index 2a5d73f4..c627b181 100644 --- a/app/src/main/java/com/sopt/geonppang/data/datasource/remote/AuthDataSource.kt +++ b/app/src/main/java/com/sopt/geonppang/data/datasource/remote/AuthDataSource.kt @@ -1,7 +1,9 @@ package com.sopt.geonppang.data.datasource.remote +import com.sopt.geonppang.data.model.request.RequestLogin import com.sopt.geonppang.data.model.request.RequestNicknameSetting import com.sopt.geonppang.data.model.request.RequestSignup +import com.sopt.geonppang.data.model.response.ResponseLogin import com.sopt.geonppang.data.model.response.ResponseLogout import com.sopt.geonppang.data.model.response.ResponseNickNameSetting import com.sopt.geonppang.data.model.response.ResponseSignup @@ -23,4 +25,5 @@ class AuthDataSource @Inject constructor( suspend fun logout(): ResponseLogout = authService.logout() suspend fun settingNickname(nickname: RequestNicknameSetting): ResponseNickNameSetting = authService.settingNickName(nickname) + suspend fun login(requestLogin: RequestLogin): Response = authService.login(requestLogin) } diff --git a/app/src/main/java/com/sopt/geonppang/data/model/request/RequestLogin.kt b/app/src/main/java/com/sopt/geonppang/data/model/request/RequestLogin.kt new file mode 100644 index 00000000..5c9ca57d --- /dev/null +++ b/app/src/main/java/com/sopt/geonppang/data/model/request/RequestLogin.kt @@ -0,0 +1,12 @@ +package com.sopt.geonppang.data.model.request + +import com.google.gson.annotations.SerializedName +import kotlinx.serialization.Serializable + +@Serializable +data class RequestLogin( + @SerializedName("email") + val email: String?, + @SerializedName("password") + val password: String?, +) diff --git a/app/src/main/java/com/sopt/geonppang/data/model/response/ResponseLogin.kt b/app/src/main/java/com/sopt/geonppang/data/model/response/ResponseLogin.kt new file mode 100644 index 00000000..a35894f4 --- /dev/null +++ b/app/src/main/java/com/sopt/geonppang/data/model/response/ResponseLogin.kt @@ -0,0 +1,14 @@ +package com.sopt.geonppang.data.model.response + +import com.google.gson.annotations.SerializedName +import kotlinx.serialization.Serializable + +@Serializable +data class ResponseLogin( + @SerializedName("code") + val code: String, + @SerializedName("message") + val message: String, + @SerializedName("data") + val data: String?, +) diff --git a/app/src/main/java/com/sopt/geonppang/data/repository/AuthRepositoryImpl.kt b/app/src/main/java/com/sopt/geonppang/data/repository/AuthRepositoryImpl.kt index 4a9aa601..76ccbb69 100644 --- a/app/src/main/java/com/sopt/geonppang/data/repository/AuthRepositoryImpl.kt +++ b/app/src/main/java/com/sopt/geonppang/data/repository/AuthRepositoryImpl.kt @@ -1,8 +1,10 @@ package com.sopt.geonppang.data.repository import com.sopt.geonppang.data.datasource.remote.AuthDataSource +import com.sopt.geonppang.data.model.request.RequestLogin import com.sopt.geonppang.data.model.request.RequestNicknameSetting import com.sopt.geonppang.data.model.request.RequestSignup +import com.sopt.geonppang.data.model.response.ResponseLogin import com.sopt.geonppang.data.model.response.ResponseLogout import com.sopt.geonppang.data.model.response.ResponseNickNameSetting import com.sopt.geonppang.data.model.response.ResponseSignup @@ -28,4 +30,7 @@ class AuthRepositoryImpl @Inject constructor( override suspend fun settingNickname(nickName: RequestNicknameSetting): Result = runCatching { authDataSource.settingNickname(nickName) } + + override suspend fun login(responseLogin: RequestLogin): Result> = + runCatching { authDataSource.login(responseLogin) } } diff --git a/app/src/main/java/com/sopt/geonppang/data/service/AuthService.kt b/app/src/main/java/com/sopt/geonppang/data/service/AuthService.kt index f446a6de..9cfdd68a 100644 --- a/app/src/main/java/com/sopt/geonppang/data/service/AuthService.kt +++ b/app/src/main/java/com/sopt/geonppang/data/service/AuthService.kt @@ -1,7 +1,9 @@ package com.sopt.geonppang.data.service +import com.sopt.geonppang.data.model.request.RequestLogin import com.sopt.geonppang.data.model.request.RequestNicknameSetting import com.sopt.geonppang.data.model.request.RequestSignup +import com.sopt.geonppang.data.model.response.ResponseLogin import com.sopt.geonppang.data.model.response.ResponseLogout import com.sopt.geonppang.data.model.response.ResponseNickNameSetting import com.sopt.geonppang.data.model.response.ResponseSignup @@ -25,6 +27,11 @@ interface AuthService { @POST("auth/logout") suspend fun logout(): ResponseLogout + @POST("auth/login") + suspend fun login( + @Body requestLogin: RequestLogin + ): Response + @POST("member/nickname") suspend fun settingNickName( @Body requestNicknameSetting: RequestNicknameSetting diff --git a/app/src/main/java/com/sopt/geonppang/domain/repository/AuthRepository.kt b/app/src/main/java/com/sopt/geonppang/domain/repository/AuthRepository.kt index a545fc81..d1054e4b 100644 --- a/app/src/main/java/com/sopt/geonppang/domain/repository/AuthRepository.kt +++ b/app/src/main/java/com/sopt/geonppang/domain/repository/AuthRepository.kt @@ -1,7 +1,9 @@ package com.sopt.geonppang.domain.repository +import com.sopt.geonppang.data.model.request.RequestLogin import com.sopt.geonppang.data.model.request.RequestNicknameSetting import com.sopt.geonppang.data.model.request.RequestSignup +import com.sopt.geonppang.data.model.response.ResponseLogin import com.sopt.geonppang.data.model.response.ResponseLogout import com.sopt.geonppang.data.model.response.ResponseNickNameSetting import com.sopt.geonppang.data.model.response.ResponseSignup @@ -17,4 +19,5 @@ interface AuthRepository { suspend fun withdraw(): Result suspend fun logout(): Result suspend fun settingNickname(nickName: RequestNicknameSetting): Result + suspend fun login(requestLogin: RequestLogin): Result> } diff --git a/app/src/main/java/com/sopt/geonppang/presentation/login/LogInViewModel.kt b/app/src/main/java/com/sopt/geonppang/presentation/login/LogInViewModel.kt index 36b78e28..c1084f13 100644 --- a/app/src/main/java/com/sopt/geonppang/presentation/login/LogInViewModel.kt +++ b/app/src/main/java/com/sopt/geonppang/presentation/login/LogInViewModel.kt @@ -1,12 +1,56 @@ package com.sopt.geonppang.presentation.login -import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.sopt.geonppang.data.datasource.local.GPDataSource +import com.sopt.geonppang.data.model.request.RequestLogin +import com.sopt.geonppang.domain.repository.AuthRepository import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.launch +import timber.log.Timber import javax.inject.Inject @HiltViewModel -class LogInViewModel @Inject constructor() : ViewModel() { - val loginEmail = MutableLiveData("") - val loginPassword = MutableLiveData("") +class LogInViewModel @Inject constructor( + private val authRepository: AuthRepository, + private val gpDataSource: GPDataSource +) : ViewModel() { + val loginEmail = MutableStateFlow("") + val loginPassword = MutableStateFlow("") + private val _loginState = MutableStateFlow(null) + val loginState get() = _loginState.asStateFlow() + + fun initLogin() { + _loginState.value = null + } + + fun login() { + viewModelScope.launch { + authRepository.login(RequestLogin(loginEmail.value, loginPassword.value)) + .onSuccess { loginResponse -> + val responseHeader = loginResponse.headers() + val accessToken = responseHeader[AUTHORIZATION].toString() + if (loginResponse.code() == 200) { + gpDataSource.accessToken = BEARER_PREFIX + accessToken + gpDataSource.isLogin = true + _loginState.value = true + } + if (loginResponse.code() == 400) { + _loginState.value = false + } + if (loginResponse.code() == 500) { + Timber.tag("서버 오류") + } + }.onFailure { throwable -> + Timber.tag("로그인 실패 on Failure").e(throwable.message) + } + } + } + + companion object { + const val AUTHORIZATION = "Authorization" + const val BEARER_PREFIX = "Bearer " + } } diff --git a/app/src/main/java/com/sopt/geonppang/presentation/login/LoginActivity.kt b/app/src/main/java/com/sopt/geonppang/presentation/login/LoginActivity.kt index 0b2dc583..a6a026e7 100644 --- a/app/src/main/java/com/sopt/geonppang/presentation/login/LoginActivity.kt +++ b/app/src/main/java/com/sopt/geonppang/presentation/login/LoginActivity.kt @@ -3,12 +3,18 @@ package com.sopt.geonppang.presentation.login import android.content.Intent import android.os.Bundle import androidx.activity.viewModels +import androidx.lifecycle.flowWithLifecycle +import androidx.lifecycle.lifecycleScope import com.sopt.geonppang.R +import com.sopt.geonppang.data.datasource.local.GPDataSource import com.sopt.geonppang.databinding.ActivityLoginBinding import com.sopt.geonppang.presentation.auth.SignUpActivity +import com.sopt.geonppang.presentation.home.HomeFragment import com.sopt.geonppang.util.binding.BindingActivity import com.sopt.geonppang.util.extension.hideKeyboard import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.flow.launchIn +import kotlinx.coroutines.flow.onEach @AndroidEntryPoint class LoginActivity : @@ -19,7 +25,9 @@ class LoginActivity : binding.viewModel = viewModel binding.lifecycleOwner = this + autoLogin() addListener() + collectData() } private fun addListener() { @@ -34,7 +42,43 @@ class LoginActivity : } } + private fun collectData() { + viewModel.loginState.flowWithLifecycle(lifecycle).onEach { loginState -> + when (loginState) { + true -> { + moveToHome() + } + + false -> { + showLoginFailDialog() + viewModel.initLogin() + } + + else -> {} + } + }.launchIn(lifecycleScope) + } + + private fun showLoginFailDialog() { + LoginFailBottomDialogFragment().show(supportFragmentManager, LOGIN_FAIL) + } + + private fun moveToHome() { + startActivity(Intent(this, HomeFragment::class.java)) + finish() + } + private fun moveToSignup() { startActivity(Intent(this, SignUpActivity::class.java)) } + + private fun autoLogin() { + val gpDataSource = GPDataSource(this) + if (gpDataSource.isLogin) + moveToHome() + } + + companion object { + const val LOGIN_FAIL = "loginFail" + } } diff --git a/app/src/main/java/com/sopt/geonppang/presentation/login/LoginFailBottomDialogFragment.kt b/app/src/main/java/com/sopt/geonppang/presentation/login/LoginFailBottomDialogFragment.kt new file mode 100644 index 00000000..9f9fe4bd --- /dev/null +++ b/app/src/main/java/com/sopt/geonppang/presentation/login/LoginFailBottomDialogFragment.kt @@ -0,0 +1,23 @@ +package com.sopt.geonppang.presentation.login + +import android.os.Bundle +import android.view.View +import com.sopt.geonppang.R +import com.sopt.geonppang.databinding.DialogBottomLoginFailBinding +import com.sopt.geonppang.util.binding.BindingBottomSheetDialogFragment + +class LoginFailBottomDialogFragment : + BindingBottomSheetDialogFragment(R.layout.dialog_bottom_login_fail) { + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.lifecycleOwner = this.viewLifecycleOwner + + addListeners() + } + + private fun addListeners() { + binding.btnLoginFailConfirm.setOnClickListener { + dismiss() + } + } +} diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml index abea69f2..8aa2a864 100644 --- a/app/src/main/res/layout/activity_login.xml +++ b/app/src/main/res/layout/activity_login.xml @@ -160,12 +160,13 @@ android:layout_marginHorizontal="24dp" android:layout_marginTop="@dimen/spacing20" android:layout_marginBottom="24dp" - android:backgroundTint="@{viewModel.loginPassword.length() > 0 ? @color/main_2 : @color/gray_200}" - android:enabled="@{viewModel.loginPassword.length() > 0}" + android:backgroundTint="@{viewModel.loginPassword.length > 0 ? @color/main_2 : @color/gray_200}" + android:enabled="@{viewModel.loginPassword.length > 0}" + android:onClick="@{() -> viewModel.login()}" android:paddingVertical="18dp" android:text="@string/login_text_phrase" android:textAppearance="@style/TextAppearance.BodyM1" - android:textColor="@{viewModel.loginPassword.length() > 0 ? @color/white : @color/gray_400}" + android:textColor="@{viewModel.loginPassword.length > 0 ? @color/white : @color/gray_400}" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" diff --git a/app/src/main/res/layout/activity_signup_nickname.xml b/app/src/main/res/layout/activity_signup_nickname.xml index e5d1302c..cb90f2e5 100644 --- a/app/src/main/res/layout/activity_signup_nickname.xml +++ b/app/src/main/res/layout/activity_signup_nickname.xml @@ -81,7 +81,7 @@ android:layout_marginTop="16dp" android:text="@string/tv_nickname" android:textAppearance="@style/TextAppearance.Subhead" - android:textColor="@{!viewModel.email.empty & !viewModel.isValidEmail() ? @color/error : @color/gray_400}" /> + android:textColor="@{!viewModel.nickname.empty & !viewModel.isValidNickname() ? @color/error : @color/gray_400}" />