Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SplashScreen 구현, 로그인 & 회원가입 시 토큰 저장 #93

Merged
merged 10 commits into from
Nov 21, 2023
13 changes: 8 additions & 5 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -14,17 +14,17 @@
android:theme="@style/Theme.PriceGuard"
tools:targetApi="34">
<activity
android:name=".ui.main.additem.AddItemActivity"
android:exported="false" />
<activity
android:name=".ui.intro.IntroActivity"
android:exported="true" >
android:name=".ui.splash.SplashScreenActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".ui.intro.IntroActivity"
android:exported="false" />
<activity
android:name=".ui.login.LoginActivity"
android:exported="false" />
Expand All @@ -34,6 +34,9 @@
<activity
android:name=".ui.main.MainActivity"
android:exported="false" />
<activity
android:name=".ui.main.additem.AddItemActivity"
android:exported="false" />
</application>

</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ data class LoginResponse(

data class LoginResult(
val loginState: LoginState,
val loginResponse: LoginResponse? = null
val accessToken: String?,
val refreshToken: String?
)

enum class LoginState {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ package app.priceguard.data.dto
import kotlinx.serialization.Serializable

@Serializable
data class SignUpRequest(
data class SignupRequest(
val email: String,
val userName: String,
val password: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,20 @@ package app.priceguard.data.dto
import kotlinx.serialization.Serializable

@Serializable
data class SignUpResponse(
data class SignupResponse(
val statusCode: Int,
val message: String,
val accessToken: String,
val refreshToken: String
)

data class SignUpResult(
val signUpState: SignUpState,
val signUpResponse: SignUpResponse? = null
data class SignupResult(
val signUpState: SignupState,
val accessToken: String?,
val refreshToken: String?
)

enum class SignUpState {
enum class SignupState {
SUCCESS,
INVALID_PARAMETER,
DUPLICATE_EMAIL,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ package app.priceguard.data.network

import app.priceguard.data.dto.LoginRequest
import app.priceguard.data.dto.LoginResponse
import app.priceguard.data.dto.SignUpRequest
import app.priceguard.data.dto.SignUpResponse
import app.priceguard.data.dto.SignupRequest
import app.priceguard.data.dto.SignupResponse
import retrofit2.Response
import retrofit2.http.Body
import retrofit2.http.POST
Expand All @@ -17,6 +17,6 @@ interface UserAPI {

@POST("register")
suspend fun register(
@Body request: SignUpRequest
): Response<SignUpResponse>
@Body request: SignupRequest
): Response<SignupResponse>
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package app.priceguard.data.repository

import app.priceguard.data.dto.LoginResult
import app.priceguard.data.dto.SignUpResult
import app.priceguard.data.dto.SignupResult

interface UserRepository {

suspend fun signUp(email: String, userName: String, password: String): SignUpResult
suspend fun signUp(email: String, userName: String, password: String): SignupResult

suspend fun login(email: String, password: String): LoginResult
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,37 +3,37 @@ package app.priceguard.data.repository
import app.priceguard.data.dto.LoginRequest
import app.priceguard.data.dto.LoginResult
import app.priceguard.data.dto.LoginState
import app.priceguard.data.dto.SignUpRequest
import app.priceguard.data.dto.SignUpResult
import app.priceguard.data.dto.SignUpState
import app.priceguard.data.dto.SignupRequest
import app.priceguard.data.dto.SignupResult
import app.priceguard.data.dto.SignupState
import app.priceguard.data.network.APIResult
import app.priceguard.data.network.UserAPI
import app.priceguard.data.network.getApiResult
import javax.inject.Inject

class UserRepositoryImpl @Inject constructor(private val userAPI: UserAPI) : UserRepository {

override suspend fun signUp(email: String, userName: String, password: String): SignUpResult {
override suspend fun signUp(email: String, userName: String, password: String): SignupResult {
val response = getApiResult {
userAPI.register(SignUpRequest(email, userName, password))
userAPI.register(SignupRequest(email, userName, password))
}
when (response) {
is APIResult.Success -> {
return SignUpResult(SignUpState.SUCCESS, response.data)
return SignupResult(SignupState.SUCCESS, response.data.accessToken, response.data.refreshToken)
}

is APIResult.Error -> {
return when (response.code) {
400 -> {
SignUpResult(SignUpState.INVALID_PARAMETER)
SignupResult(SignupState.INVALID_PARAMETER, null, null)
}

409 -> {
SignUpResult(SignUpState.DUPLICATE_EMAIL)
SignupResult(SignupState.DUPLICATE_EMAIL, null, null)
}

else -> {
SignUpResult(SignUpState.UNDEFINED_ERROR)
SignupResult(SignupState.UNDEFINED_ERROR, null, null)
}
}
}
Expand All @@ -46,17 +46,17 @@ class UserRepositoryImpl @Inject constructor(private val userAPI: UserAPI) : Use
}
when (response) {
is APIResult.Success -> {
return LoginResult(LoginState.SUCCESS, response.data)
return LoginResult(LoginState.SUCCESS, response.data.accessToken, response.data.refreshToken)
}

is APIResult.Error -> {
return when (response.code) {
400 -> {
LoginResult(LoginState.INVALID_PARAMETER)
LoginResult(LoginState.INVALID_PARAMETER, null, null)
}

else -> {
LoginResult(LoginState.UNDEFINED_ERROR)
LoginResult(LoginState.UNDEFINED_ERROR, null, null)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ class LoginActivity : AppCompatActivity() {
private fun initListener() {
with(binding) {
btnLoginLogin.setOnClickListener {
(binding.btnLoginLogin as MaterialButton).icon = getCircularProgressIndicatorDrawable(this@LoginActivity)
(binding.btnLoginLogin as MaterialButton).icon =
getCircularProgressIndicatorDrawable(this@LoginActivity)
}
btnLoginSignup.setOnClickListener {
gotoSignUp()
Expand All @@ -47,7 +48,8 @@ class LoginActivity : AppCompatActivity() {
loginViewModel.event.collect { eventType ->
when (eventType) {
LoginEvent.LoginStart -> {
(binding.btnLoginLogin as MaterialButton).icon = getCircularProgressIndicatorDrawable(this@LoginActivity)
(binding.btnLoginLogin as MaterialButton).icon =
getCircularProgressIndicatorDrawable(this@LoginActivity)
}

else -> {
Expand All @@ -72,8 +74,8 @@ class LoginActivity : AppCompatActivity() {
showDialog(getString(R.string.login_fail), getString(R.string.login_fail_message))
}

is LoginEvent.LoginSuccess -> {
gotoHome()
is LoginEvent.LoginInfoSaved -> {
gotoHomeActivity()
}

else -> {}
Expand All @@ -93,7 +95,7 @@ class LoginActivity : AppCompatActivity() {
startActivity(Intent(this, SignupActivity::class.java))
}

private fun gotoHome() {
private fun gotoHomeActivity() {
val intent = Intent(this, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package app.priceguard.ui.login
import android.util.Log
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import app.priceguard.data.dto.LoginResponse
import app.priceguard.data.dto.LoginState
import app.priceguard.data.repository.TokenRepository
import app.priceguard.data.repository.UserRepository
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
Expand All @@ -18,7 +18,8 @@ import kotlinx.coroutines.launch

@HiltViewModel
class LoginViewModel @Inject constructor(
private val userRepository: UserRepository
private val userRepository: UserRepository,
private val tokenRepository: TokenRepository
) : ViewModel() {

data class State(
Expand All @@ -31,8 +32,9 @@ class LoginViewModel @Inject constructor(
sealed class LoginEvent {
data object LoginStart : LoginEvent()
data object Invalid : LoginEvent()
data class LoginSuccess(val response: LoginResponse?) : LoginEvent()
data class LoginSuccess(val accessToken: String, val refreshToken: String) : LoginEvent()
data class LoginFailure(val status: LoginState) : LoginEvent()
data object LoginInfoSaved : LoginEvent()
}

private val emailPattern =
Expand Down Expand Up @@ -62,35 +64,55 @@ class LoginViewModel @Inject constructor(
}

viewModelScope.launch {
_state.value = _state.value.copy(isLoading = true)
setLoading(true)
sendLoginEvent(LoginEvent.LoginStart)

if (checkEmailAndPassword()) {
val result = userRepository.login(_state.value.email, _state.value.password)
if (!checkEmailAndPassword()) {
sendLoginEvent(LoginEvent.Invalid)
setLoading(false)
return@launch
}

when (result.loginState) {
LoginState.SUCCESS -> {
_state.value = _state.value.copy(isLoginFinished = true)
sendLoginEvent(
LoginEvent.LoginSuccess(result.loginResponse)
)
}
val result = userRepository.login(_state.value.email, _state.value.password)

if (result.accessToken == null || result.refreshToken == null) {
sendLoginEvent(LoginEvent.LoginFailure(result.loginState))
setLoading(false)
return@launch
}

else -> {
sendLoginEvent(LoginEvent.LoginFailure(result.loginState))
}
when (result.loginState) {
LoginState.SUCCESS -> {
setLoginFinished(true)
sendLoginEvent(LoginEvent.LoginSuccess(result.accessToken, result.refreshToken))
saveTokens(result.accessToken, result.refreshToken)
sendLoginEvent(LoginEvent.LoginInfoSaved)
}

else -> {
sendLoginEvent(LoginEvent.LoginFailure(result.loginState))
}
} else {
sendLoginEvent(LoginEvent.Invalid)
}
_state.value = _state.value.copy(isLoading = false)
setLoading(false)
}
}

private suspend fun saveTokens(accessToken: String, refreshToken: String) {
tokenRepository.storeTokens(accessToken, refreshToken)
}

private suspend fun sendLoginEvent(event: LoginEvent) {
_event.emit(event)
}

private fun setLoading(isLoading: Boolean) {
_state.value = _state.value.copy(isLoading = isLoading)
}

private fun setLoginFinished(finished: Boolean) {
_state.value = _state.value.copy(isLoginFinished = finished)
}

private fun checkEmailAndPassword(): Boolean {
return emailPattern.matchEntire(_state.value.email) != null && passwordPattern.matchEntire(
_state.value.password
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.widget.NestedScrollView
import androidx.databinding.DataBindingUtil
import app.priceguard.R
import app.priceguard.data.dto.SignUpState
import app.priceguard.data.dto.SignupState
import app.priceguard.databinding.ActivitySignupBinding
import app.priceguard.ui.main.MainActivity
import app.priceguard.ui.signup.SignupViewModel.SignupEvent
Expand Down Expand Up @@ -73,42 +73,40 @@ class SignupActivity : AppCompatActivity() {

is SignupEvent.SignupSuccess -> {
(binding.btnSignupSignup as MaterialButton).icon = null
val response = event.response

if (response == null) {
showDialog(getString(R.string.error), getString(R.string.undefined_error))
} else {
// TODO: DataStore에 저장하기
val intent = Intent(this, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)

// TODO: 뒤에 액티비티 스택 다 날리기
finish()
}
}

is SignupEvent.SignupFailure -> {
(binding.btnSignupSignup as MaterialButton).icon = null
when (event.errorState) {
SignUpState.INVALID_PARAMETER -> {
SignupState.INVALID_PARAMETER -> {
showDialog(getString(R.string.error), getString(R.string.invalid_parameter))
}

SignUpState.DUPLICATE_EMAIL -> {
SignupState.DUPLICATE_EMAIL -> {
showDialog(getString(R.string.error), getString(R.string.duplicate_email))
}

SignUpState.UNDEFINED_ERROR -> {
SignupState.UNDEFINED_ERROR -> {
showDialog(getString(R.string.error), getString(R.string.undefined_error))
}

else -> {}
}
}

SignupEvent.SignupInfoSaved -> {
gotoHomeActivity()
}
}
}

private fun gotoHomeActivity() {
val intent = Intent(this, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
startActivity(intent)
finish()
}

private fun setNavigationButton() {
binding.mtSignupTopbar.setNavigationOnClickListener {
finish()
Expand Down
Loading