State
package org.sopt.and.presentation.sign.state
data class SignInState (
val username: String = "",
val password: String = ""
)
ViewModel
@HiltViewModel
class SignInViewModel @Inject constructor(
private val loginUseCase: LoginUseCase
) : ViewModel() {
val _signInState = MutableStateFlow(SignInState())
val signInState = _signInState.asStateFlow()
private val _loginUserResultState = MutableStateFlow<UserLoginResult?>(null)
val loginUserResultState: StateFlow<UserLoginResult?> = _loginUserResultState
private val _errorMessageState = MutableStateFlow<String?>(null)
val errorMessageState: StateFlow<String?> = _errorMessageState
private val _signInSuccess = MutableSharedFlow<Boolean>()
val signInSuccess: SharedFlow<Boolean> = _signInSuccess
fun updateUserName(newUserName: String) {
_signInState.update { currentState ->
currentState.copy(
username = newUserName
)
}
}
fun updatePassword(newPassword: String) {
_signInState.update { currentState ->
currentState.copy(
password = newPassword
)
}
}
private suspend fun setSignInSuccess(value: Boolean) {
_signInSuccess.emit(value)
}
fun signIn() {
viewModelScope.launch {
when (val result = loginUseCase(
UserData(
_signInState.value.username,
_signInState.value.password,
""
)
)
) {
is BaseResult.Success -> {
_loginUserResultState.value = result.data
_errorMessageState.value = null
setSignInSuccess(true)
}
is BaseResult.Error -> {
_loginUserResultState.value = null
_errorMessageState.value = result.message
setSignInSuccess(false)
}
}
}
}
suspend fun resetSignInSuccess() {
setSignInSuccess(false)
}
}
Screen
@Composable
fun SignInScreen(
navigateToMy: () -> Unit,
navigateToSignUp: () -> Unit,
modifier: Modifier = Modifier,
viewModel: SignInViewModel = hiltViewModel(),
) {
val signInState by viewModel.signInState.collectAsState()
val loginState by viewModel.loginUserResultState.collectAsState()
val context = LocalContext.current
LaunchedEffect(Unit) {
viewModel.signInSuccess.collectLatest { success ->
if (success) {
loginState?.let { loginState ->
PreferenceUtils.saveUserToken(context, loginState.token)
}
CoroutineScope(Dispatchers.Main).launch {
SnackBarUtils.showSnackBar(
message = context.getString(R.string.sign_in_snackbar_login_success),
actionLabel = context.getString(R.string.sign_in_snackbar_action_close)
)
}
navigateToMy()
viewModel.resetSignInSuccess()
} else {
viewModel.errorMessageState.value?.let { message ->
CoroutineScope(Dispatchers.Main).launch {
SnackBarUtils.showSnackBar(
message = message,
actionLabel = context.getString(R.string.sign_in_snackbar_action_close)
)
}
}
}
}
}
Column(
modifier = modifier
.fillMaxSize()
.background(WavveBg)
) {
BackButtonTopBar({ /*TODO : 뒤로가기처리*/ })
Column(
modifier = modifier
.fillMaxSize()
.background(WavveBg)
.padding(16.dp)
) {
WavveCommonTextField(
value = signInState.username,
onValueChange = **viewModel::updateUserName**,
hint = stringResource(R.string.sign_in_text_field_id_hint)
)
Spacer(modifier = Modifier.height(4.dp))
WavveCommonPasswordField(
value = signInState.password,
onValueChange = viewModel::updatePassword,
hint = stringResource(R.string.sign_in_text_field_password_hint)
)
Spacer(modifier = Modifier.height(24.dp))
WavveBasicButton(
text = stringResource(R.string.sign_in_text_login),
onClick = { viewModel.signIn() },
modifier = Modifier
)
Spacer(modifier = Modifier.height(16.dp))
Row(
modifier = Modifier
.align(Alignment.CenterHorizontally),
horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.CenterHorizontally)
) {
Text(
text = stringResource(R.string.sign_in_text_find_id),
fontSize = 12.sp,
color = Gray3,
)
Text(
text = stringResource(R.string.sign_in_text_divider),
fontSize = 12.sp,
color = Gray3,
)
Text(
text = stringResource(R.string.sign_in_text_password_reset),
fontSize = 12.sp,
color = Gray3,
)
Text(
text = stringResource(R.string.sign_in_text_divider),
fontSize = 12.sp,
color = Gray3,
)
Text(
text = stringResource(R.string.sign_in_text_sign_up),
fontSize = 12.sp,
color = Gray3,
modifier = Modifier
.noRippleClickable(navigateToSignUp)
)
}
Spacer(modifier = Modifier.height(12.dp))
Row(
modifier = modifier
.fillMaxWidth()
.wrapContentHeight(),
verticalAlignment = Alignment.CenterVertically
) {
HorizontalDivider(
thickness = 0.5.dp,
color = Gray4,
modifier = Modifier
.weight(1f)
)
Text(
text = stringResource(R.string.sign_in_text_other_service),
color = Gray3,
fontSize = 12.sp,
modifier = Modifier.padding(horizontal = 8.dp)
)
HorizontalDivider(
thickness = 0.5.dp,
color = Gray4,
modifier = Modifier
.weight(1f)
)
}
ServiceAccountItemRow()
Spacer(Modifier.height(16.dp))
Text(
text = stringResource(R.string.sign_in_text_information),
color = Gray3,
fontSize = 12.sp,
modifier = Modifier.padding(horizontal = 8.dp)
)
}
}
}
// TODO - 얘네를 우선 완성해야함.
// {
// "phoneNumber" : "01064446919",
// "nickname" : "지혀닝",
// "birthYear" : 2000,
// "gender" : false,
// "profileImage" : "image2",
// "region" : "성북/강북/노원/도봉/중랑"
// }