Skip to content

Commit

Permalink
[feat] #13 login screen 적용
Browse files Browse the repository at this point in the history
  • Loading branch information
beom84 committed Dec 6, 2024
1 parent e4fffc5 commit 0fc3fbb
Show file tree
Hide file tree
Showing 4 changed files with 121 additions and 83 deletions.
29 changes: 29 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,29 @@
package org.sopt.and.presentation.login

import androidx.annotation.StringRes
import org.sopt.and.presentation.util.base.UiEvent
import org.sopt.and.presentation.util.base.UiSideEffect
import org.sopt.and.presentation.util.base.UiState

class LogInContract {
data class LoginUiState(
val isPasswordVisible: Boolean = false,
val emailValue: String = "",
val passwordValue: String = ""
) : UiState

sealed interface LogInSideEffect : UiSideEffect {
data object NavigateToHome : LogInSideEffect
data object NavigateToSignUp : LogInSideEffect
data class ShowToast(@StringRes val message: Int) : LogInSideEffect
data class ShowSnackBar(@StringRes val message: Int) : LogInSideEffect
}

sealed class LogInEvent : UiEvent {
data class OnEmailValueChanged(val emailValue: String) : LogInEvent()
data class OnPasswordValueChanged(val passwordValue: String) : LogInEvent()
data class OnPasswordVisibleButtonClicked(val isPasswordVisible: Boolean) : LogInEvent()
data object OnLogInButtonClicked : LogInEvent()
data object OnSignUpButtonClicked : LogInEvent()
}
}
90 changes: 60 additions & 30 deletions app/src/main/java/org/sopt/and/presentation/login/LogInScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,7 @@ import androidx.compose.material3.TextButton
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.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
Expand All @@ -53,53 +51,85 @@ import org.sopt.and.core.extension.showsnackBar
import org.sopt.and.core.extension.toast
import org.sopt.and.presentation.signup.SignUpViewModel.Companion.EXTRA_SIGNUP_IMAGE_LIST


@ExperimentalPermissionsApi
@OptIn(ExperimentalPermissionsApi::class)
@Composable
fun LogInScreen(
fun LogInRoute(
navigateToSignUp: () -> Unit,
navigateToHome: () -> Unit
navigateToHome: () -> Unit,
viewModel: LogInViewModel = hiltViewModel()
) {
val viewModel: LogInViewModel = hiltViewModel()

val context = LocalContext.current

val lifecycleOwner = LocalLifecycleOwner.current

val loginState by viewModel.loginState.collectAsStateWithLifecycle()
val id = loginState.userName
val password = loginState.password

var isPasswordVisible by remember { mutableStateOf(false) }

val uiState by viewModel.uiState.collectAsStateWithLifecycle()
val snackBarHostState = remember { SnackbarHostState() }

LaunchedEffect(viewModel.signInSideEffect, lifecycleOwner) {
viewModel.signInSideEffect.flowWithLifecycle(lifecycle = lifecycleOwner.lifecycle)
LaunchedEffect(viewModel.sideEffect, lifecycleOwner) {
viewModel.sideEffect.flowWithLifecycle(lifecycle = lifecycleOwner.lifecycle)
.collect { sideEffect ->
when (sideEffect) {
is SignInSideEffect.ShowToast -> {
is LogInContract.LogInSideEffect.ShowToast -> {
context.toast(sideEffect.message)
}

is SignInSideEffect.ShowSnackBar -> {
is LogInContract.LogInSideEffect.ShowSnackBar -> {
snackBarHostState.showsnackBar(
sideEffect.message,
context
)
}

is SignInSideEffect.NavigateToSignUp -> {
is LogInContract.LogInSideEffect.NavigateToSignUp -> {
navigateToSignUp()
}

is SignInSideEffect.NavigateToHome -> {
is LogInContract.LogInSideEffect.NavigateToHome -> {
navigateToHome()
}
}
}
}
LogInScreen(
logInUiState = uiState,
snackBarHostState = snackBarHostState,
onEmailValueChanged = { emailValue ->
viewModel.setEvent(
LogInContract.LogInEvent.OnEmailValueChanged(
emailValue = emailValue
)
)
},
onPasswordValueChanged = { passwordValue ->
viewModel.setEvent(
LogInContract.LogInEvent.OnPasswordValueChanged(
passwordValue = passwordValue
)
)
},
onLogInButtonClicked = {
viewModel.setEvent(
LogInContract.LogInEvent.OnLogInButtonClicked
)
},
onSignUpButtonClicked = {
viewModel.setEvent(
LogInContract.LogInEvent.OnSignUpButtonClicked
)
}
)
}

@ExperimentalPermissionsApi
@Composable
fun LogInScreen(
logInUiState: LogInContract.LoginUiState,
snackBarHostState: SnackbarHostState,
onEmailValueChanged: (String) -> Unit = {},
onPasswordValueChanged: (String) -> Unit = {},
onLogInButtonClicked: () -> Unit = {},
onSignUpButtonClicked: () -> Unit = {},
onPasswordVisibleButtonClicked: (Boolean) -> Unit = {}
) {
Scaffold(
modifier = Modifier.fillMaxSize(),
snackbarHost = {
Expand Down Expand Up @@ -136,8 +166,8 @@ fun LogInScreen(
Spacer(Modifier.padding(40.dp))

UserInfoTextField(
textField = id,
onValueChange = viewModel::setUserName,
textField = logInUiState.emailValue,
onValueChange = onEmailValueChanged,
placeholder = stringResource(R.string.logintextfield_placeholder),
isShown = true,
keyboardOptions = KeyboardOptions(
Expand All @@ -148,19 +178,19 @@ fun LogInScreen(
Spacer(Modifier.padding(5.dp))

UserInfoTextField(
textField = password,
onValueChange = viewModel::setPassword,
textField = logInUiState.passwordValue,
onValueChange = onPasswordValueChanged,
placeholder = stringResource(R.string.password),
isShown = isPasswordVisible,
isShown = logInUiState.isPasswordVisible,
keyboardOptions = KeyboardOptions(
imeAction = ImeAction.Done,
),
trailingIcon = {
TextButton(
onClick = {
isPasswordVisible = !isPasswordVisible
onPasswordVisibleButtonClicked(logInUiState.isPasswordVisible)
}) {
if (isPasswordVisible) {
if (logInUiState.isPasswordVisible) {
Text(
text = stringResource(R.string.hide),
modifier = Modifier.padding(7.dp),
Expand All @@ -185,7 +215,7 @@ fun LogInScreen(
) {
Button(
onClick = {
viewModel.checkLoginData()
onLogInButtonClicked()
},
modifier = Modifier
.fillMaxWidth()
Expand All @@ -201,7 +231,7 @@ fun LogInScreen(
}
TextButton(
onClick = {
viewModel.navigateToSignUp()
onSignUpButtonClicked()
}
) {
Text(
Expand Down
82 changes: 30 additions & 52 deletions app/src/main/java/org/sopt/and/presentation/login/LogInViewModel.kt
Original file line number Diff line number Diff line change
@@ -1,81 +1,59 @@
package org.sopt.and.presentation.login

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
import org.sopt.and.R
import org.sopt.and.data.dataremote.model.request.RequestSignInDto
import org.sopt.and.domain.entity.UserLogInInfo
import org.sopt.and.domain.entity.UserToken
import org.sopt.and.domain.usecase.SetTokenUseCase
import org.sopt.and.domain.usecase.SignInUserUseCase
import org.sopt.and.presentation.util.base.BaseViewModel
import javax.inject.Inject

@HiltViewModel
class LogInViewModel @Inject constructor(
private val signInUserUseCase: SignInUserUseCase,
private val setTokenUseCase: SetTokenUseCase
) : ViewModel() {
private val _loginState = MutableStateFlow(UserLogInInfo())
val loginState = _loginState.asStateFlow()

private val _signInSideEffect = MutableSharedFlow<SignInSideEffect>()
val signInSideEffect get() = _signInSideEffect.asSharedFlow()

fun setUserName(userName: String) {
_loginState.update {
it.copy(
userName = userName
)
}
}

fun setPassword(password: String) {
_loginState.update {
it.copy(
password = password
)
}
}
private val loginValidation: SignInUserUseCase,
private val setToken: SetTokenUseCase
) : BaseViewModel<LogInContract.LoginUiState, LogInContract.LogInSideEffect, LogInContract.LogInEvent>() {
override fun createInitialState(): LogInContract.LoginUiState = LogInContract.LoginUiState()

override suspend fun handleEvent(event: LogInContract.LogInEvent) {
when (event) {
is LogInContract.LogInEvent.OnLogInButtonClicked -> {
loginWithValidation()
}

private suspend fun signInUser(request: RequestSignInDto): Result<UserToken> =
signInUserUseCase(request)
is LogInContract.LogInEvent.OnSignUpButtonClicked -> {
setSideEffect(LogInContract.LogInSideEffect.NavigateToSignUp)
}

private suspend fun setToken(token: String) {
setTokenUseCase(token)
}
is LogInContract.LogInEvent.OnEmailValueChanged -> {
setState { copy(emailValue = event.emailValue) }
}

private fun navigateToHome() {
viewModelScope.launch {
_signInSideEffect.emit(SignInSideEffect.NavigateToHome)
}
}
is LogInContract.LogInEvent.OnPasswordValueChanged -> {
setState { copy(passwordValue = event.passwordValue) }
}

fun navigateToSignUp() {
viewModelScope.launch {
_signInSideEffect.emit(SignInSideEffect.NavigateToSignUp)
is LogInContract.LogInEvent.OnPasswordVisibleButtonClicked -> {
setState { copy(isPasswordVisible = !isPasswordVisible) }
}
}
}

fun checkLoginData() {
private fun loginWithValidation() {
viewModelScope.launch {
signInUser(
loginValidation(
RequestSignInDto(
_loginState.value.userName,
_loginState.value.password
currentState.emailValue,
currentState.passwordValue
)
).onFailure {
_signInSideEffect.emit(SignInSideEffect.ShowSnackBar(R.string.check_id_password))
setSideEffect(LogInContract.LogInSideEffect.ShowSnackBar(R.string.check_id_password))
}.onSuccess { response ->
_signInSideEffect.emit(SignInSideEffect.ShowToast(R.string.login_success_toast))
setSideEffect(LogInContract.LogInSideEffect.ShowToast(R.string.login_success_toast))
setToken(response.token)
navigateToHome()
setSideEffect(LogInContract.LogInSideEffect.NavigateToHome)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.navigation.NavOptions
import androidx.navigation.compose.composable
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import org.sopt.and.core.navigation.Route
import org.sopt.and.presentation.login.LogInRoute
import org.sopt.and.presentation.login.LogInScreen

fun NavHostController.navigateToLogIn(navOptions: NavOptions) {
Expand All @@ -18,7 +19,7 @@ fun NavGraphBuilder.signInNavGraph(
navigationToHome: () -> Unit = {}
) {
composable<Route.LogIn> {
LogInScreen(
LogInRoute(
navigateToSignUp = navigationToSignUp,
navigateToHome = navigationToHome,
)
Expand Down

0 comments on commit 0fc3fbb

Please sign in to comment.