Skip to content

Commit

Permalink
#13 [Feat] : State, SideEffect, Event에 따른 LogIn 수정
Browse files Browse the repository at this point in the history
  • Loading branch information
imtaejugkim committed Dec 18, 2024
1 parent 637a829 commit 0f3f9bb
Show file tree
Hide file tree
Showing 10 changed files with 152 additions and 79 deletions.
26 changes: 26 additions & 0 deletions app/src/main/java/org/sopt/and/presentation/login/LogInContract.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package org.sopt.and.presentation.login

import org.sopt.and.presentation.core.UiEvent
import org.sopt.and.presentation.core.UiSideEffect
import org.sopt.and.presentation.core.UiState

// State 정의
data class LogInState(
val username: String = "",
val password: String = "",
val isLoading: Boolean = false,
val errorMessage: Int? = null
) : UiState

// Event 정의
sealed class LogInEvent : UiEvent {
data class UsernameChanged(val username: String) : LogInEvent()
data class PasswordChanged(val password: String) : LogInEvent()
object LogInClicked : LogInEvent()
}

// SideEffect 정의
sealed class LogInEffect : UiSideEffect {
data class ShowSnackBar(val message: Int) : LogInEffect()
object NavigateToMain : LogInEffect()
}
73 changes: 34 additions & 39 deletions app/src/main/java/org/sopt/and/presentation/login/LogInScreen.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.sopt.and.presentation.login

import android.app.Activity
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
Expand All @@ -20,66 +19,63 @@ import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.NavController
import org.sopt.and.R
import org.sopt.and.data.local.SharedPreferenceManager.saveToken
import org.sopt.and.presentation.login.components.AuthManagement
import org.sopt.and.presentation.signup.SignUpEvent
import org.sopt.and.presentation.signup.components.PasswordField
import org.sopt.and.presentation.signup.UserViewModel
import org.sopt.and.presentation.signup.components.IdHobbyTextField
import org.sopt.and.presentation.signup.components.SocialServiceLogIn
import org.sopt.and.util.showSnackBar

@Composable
fun LogInScreen(
navController: NavController,
viewModel: UserViewModel = hiltViewModel()
viewModel: LogInViewModel = hiltViewModel()
) {
// textStyle 변경을 위한 textFieldValue 추적
val idState = remember { mutableStateOf(TextFieldValue()) }
val passwordState = remember { mutableStateOf(TextFieldValue()) }
val loginState = viewModel.userLoginState.collectAsState().value
val context = LocalContext.current as Activity
val snackbarHostState = remember { SnackbarHostState() }
val state by viewModel.uiState.collectAsStateWithLifecycle()
val context = LocalContext.current
val snackBarHostState = remember { SnackbarHostState() }
val coroutineScope = rememberCoroutineScope()

LaunchedEffect(loginState) {
when (loginState) {
is LoginState.Loading -> {}
is LoginState.Success -> {
saveToken(loginState.data)
navController.popBackStack("login", inclusive = true)
navController.navigate("mainScreen")
LaunchedEffect(Unit) {
viewModel.sideEffect.collect { effect ->
when (effect) {
is LogInEffect.ShowSnackBar -> {
snackBarHostState.showSnackBar(
context = context,
scope = coroutineScope,
message = effect.message
)
}
is LogInEffect.NavigateToMain -> {
navController.popBackStack("login", inclusive = true)
navController.navigate("mainScreen")
}
}
is LoginState.Failure -> {
snackbarHostState.showSnackbar(
message = context.getString(R.string.log_in_method),
actionLabel = context.getString(R.string.log_in_ok)
)
}
else -> {}
}

}

// SnackBar 구현을 위해 Scaffold 안에 정의
Scaffold(
// 스낵바의 표시 상태 관리
snackbarHost = { SnackbarHost(hostState = snackbarHostState) }) { padding ->
snackbarHost = { SnackbarHost(hostState = snackBarHostState) }) { padding ->

Column(
modifier = Modifier
Expand All @@ -103,32 +99,31 @@ fun LogInScreen(
modifier = Modifier.size(28.dp)
)
}

// id text remeber를 통한 변수 변경
var textId by remember { mutableStateOf("") }

Spacer(modifier = Modifier.weight(2f))
// 윤곽선의 색상 및 두께를 커스텀 가능한 OutLinedTextField
IdHobbyTextField(
valueState = idState,
valueState = state.username,
onValueChange = {
viewModel.setEvent(LogInEvent.UsernameChanged(it))
},
holderText = R.string.log_in_id,
modifier = Modifier.padding(bottom = 8.dp)
)


Spacer(modifier = Modifier.height(10.dp))
// password text remeber를 통한 변수 변경
var textPasswd by remember { mutableStateOf("") }
PasswordField(
passwordState = passwordState, modifier = Modifier.padding(top = 8.dp)
passwordState = state.password,
onValueChange = {
viewModel.setEvent(LogInEvent.PasswordChanged(it))
},
modifier = Modifier.padding(top = 8.dp)
)

Spacer(modifier = Modifier.weight(1f))
Button(
onClick = {
textId = idState.value.text
textPasswd = passwordState.value.text
viewModel.logIn(textId, textPasswd)
viewModel.setEvent(LogInEvent.LogInClicked)
},
modifier = Modifier.fillMaxWidth(), contentPadding = PaddingValues(16.dp),
// ButtonDefaults 의 4가지 형태
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package org.sopt.and.presentation.login

import android.util.Log
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import org.sopt.and.domain.model.UserLoginRequest
import org.sopt.and.domain.repository.UserRepository
import org.sopt.and.presentation.core.BaseViewModel
import javax.inject.Inject
import org.sopt.and.R
import retrofit2.HttpException

@HiltViewModel
class LogInViewModel @Inject constructor(
private val userRepository: UserRepository
) : BaseViewModel<LogInState, LogInEffect, LogInEvent>() {

override fun createInitialState(): LogInState = LogInState()

override suspend fun handleEvent(event: LogInEvent) {
when (event) {
is LogInEvent.UsernameChanged -> {
setState { copy(username = event.username) }
}

is LogInEvent.PasswordChanged -> {
setState { copy(password = event.password) }
}

is LogInEvent.LogInClicked -> {
logIn()
}
}
}


// 로그인 로직
fun logIn() {
setState { copy(isLoading = true) }
viewModelScope.launch {
val state = uiState.value
val result = userRepository.postUserLogin(
UserLoginRequest(
username = state.username,
password = state.password
)
)
result.fold(
onSuccess = {
setState { copy(isLoading = false) }
setSideEffect { LogInEffect.NavigateToMain }
},
onFailure = { error ->
val message = if (error is HttpException) {
Log.d("error", error.code().toString())
when (error.code()) {
400 -> R.string.log_in_method
403 -> R.string.sign_up_paswd
else -> R.string.unknown_error
}
} else {
R.string.unknown_error
}
setState { copy(isLoading = false) }
setSideEffect { LogInEffect.ShowSnackBar(message) }
}
)
}
}

}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.navigation.NavController
import org.sopt.and.R
import org.sopt.and.presentation.signup.UserViewModel

@Composable
fun AuthManagement(
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import org.sopt.and.util.showToast
@Composable
fun SignUpScreen(
navController: NavController,
viewModel: UserViewModel = hiltViewModel()
viewModel: SignUpViewModel = hiltViewModel()
) {
val state by viewModel.uiState.collectAsStateWithLifecycle()
val context = LocalContext.current
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package org.sopt.and.presentation.signup

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
Expand All @@ -10,18 +9,12 @@ import org.sopt.and.domain.model.UserLoginRequest
import org.sopt.and.domain.model.UserRegisterRequest
import org.sopt.and.domain.repository.UserRepository
import org.sopt.and.presentation.core.BaseViewModel
import org.sopt.and.presentation.login.LoginState
import javax.inject.Inject

@HiltViewModel
class UserViewModel @Inject constructor(
class SignUpViewModel @Inject constructor(
private val userRepository: UserRepository
) : BaseViewModel<SignUpState, SignUpSideEffect, SignUpEvent>() {
private val _userRegisterState = MutableStateFlow<RegisterState>(RegisterState.Idle)
val userRegisterState: StateFlow<RegisterState> = _userRegisterState

private val _userLoginState = MutableStateFlow<LoginState>(LoginState.Idle)
val userLoginState: StateFlow<LoginState> = _userLoginState

override fun createInitialState(): SignUpState = SignUpState()

Expand Down Expand Up @@ -69,18 +62,4 @@ class UserViewModel @Inject constructor(
)
}
}

// 로그인 로직
fun logIn(username: String, password: String) {
_userLoginState.value = LoginState.Loading
viewModelScope.launch {
val result = userRepository.postUserLogin(
UserLoginRequest(
username = username, password = password
)
)
_userLoginState.value = result.fold(onSuccess = { LoginState.Success(it.token) },
onFailure = { LoginState.Failure(it.message ?: "") })
}
}
}
17 changes: 17 additions & 0 deletions app/src/main/java/org/sopt/and/util/ShowSnackBar.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.sopt.and.util

import android.content.Context
import androidx.compose.material3.SnackbarHostState
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch

fun SnackbarHostState.showSnackBar(
context: Context,
scope: CoroutineScope,
message: Int,
actionLabel: String? = "확인"
) {
scope.launch {
this@showSnackBar.showSnackbar(context.getString(message), actionLabel)
}
}
1 change: 1 addition & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
<string name="sign_up_title_top_end">만으로</string>
<string name="sign_up_title_bottom_start">Wavve를 즐길 수 </string>
<string name="sign_up_title_bottom_end">있어요!</string>
<string name="unknown_error">알 수 없는 오류가 발생하였습니다.</string>


<!-- MyScreen -->
Expand Down

0 comments on commit 0f3f9bb

Please sign in to comment.