Rework inputs to registration screen component
This commit is contained in:
parent
2dbf6d61d0
commit
878ff48d95
10 changed files with 367 additions and 201 deletions
|
@ -13,6 +13,8 @@ import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import androidx.lifecycle.compose.LocalLifecycleOwner
|
import androidx.lifecycle.compose.LocalLifecycleOwner
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
@ -26,7 +28,9 @@ import dagger.hilt.android.AndroidEntryPoint
|
||||||
import ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.confirm.ConfirmationData
|
import ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.confirm.ConfirmationData
|
||||||
import ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.confirm.ConfirmationScreen
|
import ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.confirm.ConfirmationScreen
|
||||||
import ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.login.LoginScreen
|
import ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.login.LoginScreen
|
||||||
|
import ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.login.LoginScreenViewModel
|
||||||
import ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.register.RegistrationScreen
|
import ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.register.RegistrationScreen
|
||||||
|
import ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.register.RegistrationScreenViewModel
|
||||||
import ing.bikeshedengineer.debtpirate.app.screen.home.presentation.overview.OverviewScreen
|
import ing.bikeshedengineer.debtpirate.app.screen.home.presentation.overview.OverviewScreen
|
||||||
import ing.bikeshedengineer.debtpirate.domain.usecase.GetStoredTokensUseCase
|
import ing.bikeshedengineer.debtpirate.domain.usecase.GetStoredTokensUseCase
|
||||||
import ing.bikeshedengineer.debtpirate.navigation.Destination
|
import ing.bikeshedengineer.debtpirate.navigation.Destination
|
||||||
|
@ -96,8 +100,42 @@ class MainActivity : ComponentActivity() {
|
||||||
navigation<Destination.AuthGraph>(
|
navigation<Destination.AuthGraph>(
|
||||||
startDestination = Destination.AuthLogin
|
startDestination = Destination.AuthLogin
|
||||||
) {
|
) {
|
||||||
composable<Destination.AuthLogin> { LoginScreen() }
|
composable<Destination.AuthLogin> {
|
||||||
composable<Destination.AuthRegistration> { RegistrationScreen() }
|
val viewModel = hiltViewModel<LoginScreenViewModel>()
|
||||||
|
|
||||||
|
LoginScreen(
|
||||||
|
emailAddress = viewModel.emailAddress.collectAsState(""),
|
||||||
|
isEmailAddressValid = viewModel.isEmailAddressValid.collectAsState(true),
|
||||||
|
password = viewModel.password.collectAsState(""),
|
||||||
|
isPasswordValid = viewModel.isPasswordValid.collectAsState(true),
|
||||||
|
toastMessages = viewModel.toastMessages.collectAsState(""),
|
||||||
|
onAction = viewModel::onAction,
|
||||||
|
handleCredentialManagerSignIn = viewModel::handleCredentialManagerSignIn,
|
||||||
|
onRegisterButtonClick = viewModel::onRegisterButtonClick
|
||||||
|
)
|
||||||
|
}
|
||||||
|
composable<Destination.AuthRegistration> {
|
||||||
|
val viewModel = hiltViewModel<RegistrationScreenViewModel>()
|
||||||
|
|
||||||
|
RegistrationScreen(
|
||||||
|
emailAddress = viewModel.emailAddress.collectAsState(""),
|
||||||
|
emailAddressError = viewModel.emailAddressError.collectAsState(""),
|
||||||
|
isEmailAddressValid = viewModel.isEmailAddressValid.collectAsState(true),
|
||||||
|
name = viewModel.name.collectAsState(""),
|
||||||
|
nameError = viewModel.nameError.collectAsState(""),
|
||||||
|
isNameValid = viewModel.isNameValid.collectAsState(true),
|
||||||
|
password = viewModel.password.collectAsState(""),
|
||||||
|
passwordError = viewModel.passwordError.collectAsState(""),
|
||||||
|
isPasswordValid = viewModel.isPasswordValid.collectAsState(true),
|
||||||
|
confirmPassword = viewModel.confirmPassword.collectAsState(""),
|
||||||
|
confirmPasswordError = viewModel.confirmPasswordError.collectAsState(""),
|
||||||
|
isConfirmPasswordValid = viewModel.isConfirmPasswordValid.collectAsState(true),
|
||||||
|
onAction = viewModel::onAction,
|
||||||
|
onRegistrationMessage = viewModel.onRegistrationComplete.collectAsState(null),
|
||||||
|
navigateToConfirmationScreen = viewModel::navigateToConfirmationScreen,
|
||||||
|
onNavigateUp = viewModel::navigateUp
|
||||||
|
)
|
||||||
|
}
|
||||||
composable<Destination.AuthRegistrationConfirmation> {
|
composable<Destination.AuthRegistrationConfirmation> {
|
||||||
val (emailAddress) = it.toRoute<Destination.AuthRegistrationConfirmation>()
|
val (emailAddress) = it.toRoute<Destination.AuthRegistrationConfirmation>()
|
||||||
ConfirmationScreen(
|
ConfirmationScreen(
|
||||||
|
|
|
@ -3,7 +3,7 @@ package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.login
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.compose.LocalActivity
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
@ -31,10 +31,8 @@ import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.compose.ui.text.TextStyle
|
import androidx.compose.ui.text.TextStyle
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.input.KeyboardCapitalization
|
import androidx.compose.ui.text.input.KeyboardCapitalization
|
||||||
|
@ -44,6 +42,7 @@ import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.credentials.CredentialManager
|
import androidx.credentials.CredentialManager
|
||||||
import androidx.credentials.GetCredentialRequest
|
import androidx.credentials.GetCredentialRequest
|
||||||
|
import androidx.credentials.GetCredentialResponse
|
||||||
import androidx.credentials.GetPasswordOption
|
import androidx.credentials.GetPasswordOption
|
||||||
import androidx.credentials.exceptions.GetCredentialException
|
import androidx.credentials.exceptions.GetCredentialException
|
||||||
import androidx.credentials.exceptions.NoCredentialException
|
import androidx.credentials.exceptions.NoCredentialException
|
||||||
|
@ -52,9 +51,16 @@ import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
|
||||||
@Composable
|
@Composable
|
||||||
fun LoginScreen(
|
fun LoginScreen(
|
||||||
viewModel: LoginScreenViewModel = hiltViewModel<LoginScreenViewModel>()
|
emailAddress: State<String>,
|
||||||
|
isEmailAddressValid: State<Boolean>,
|
||||||
|
password: State<String>,
|
||||||
|
isPasswordValid: State<Boolean>,
|
||||||
|
toastMessages: State<String>,
|
||||||
|
onAction: (LoginScreenStateAction) -> Unit,
|
||||||
|
handleCredentialManagerSignIn: (GetCredentialResponse) -> Unit,
|
||||||
|
onRegisterButtonClick: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current as ComponentActivity
|
val context = LocalActivity.current!!
|
||||||
val credentialManager = CredentialManager.create(context)
|
val credentialManager = CredentialManager.create(context)
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
try {
|
try {
|
||||||
|
@ -64,7 +70,7 @@ fun LoginScreen(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
viewModel.handleCredentialManagerSignIn(result)
|
handleCredentialManagerSignIn(result)
|
||||||
} catch (err: GetCredentialException) {
|
} catch (err: GetCredentialException) {
|
||||||
when (err) {
|
when (err) {
|
||||||
is NoCredentialException -> {
|
is NoCredentialException -> {
|
||||||
|
@ -79,7 +85,6 @@ fun LoginScreen(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val toastMessages = viewModel.toastMessages.collectAsState("")
|
|
||||||
LaunchedEffect(toastMessages.value) {
|
LaunchedEffect(toastMessages.value) {
|
||||||
val message = toastMessages.value
|
val message = toastMessages.value
|
||||||
|
|
||||||
|
@ -103,37 +108,28 @@ fun LoginScreen(
|
||||||
.weight(3f)
|
.weight(3f)
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
val emailAddress = viewModel.emailAddress.collectAsState()
|
|
||||||
val isValidEmailAddress = viewModel.isValidEmailAddress.collectAsState(true)
|
|
||||||
|
|
||||||
val password = viewModel.password.collectAsState()
|
|
||||||
val isValidPassword = viewModel.isValidPassword.collectAsState(true)
|
|
||||||
|
|
||||||
LoginComponent(
|
LoginComponent(
|
||||||
emailAddress,
|
emailAddress,
|
||||||
onUpdateEmailAddress = {
|
onUpdateEmailAddress = {
|
||||||
viewModel.onAction(
|
onAction(
|
||||||
LoginScreenStateAction.UpdateEmailAddress(
|
LoginScreenStateAction.UpdateEmailAddress(
|
||||||
it
|
it
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
isValidEmailAddress,
|
isEmailAddressValid,
|
||||||
password,
|
password,
|
||||||
onUpdatePassword = {
|
onUpdatePassword = {
|
||||||
viewModel.onAction(
|
onAction(
|
||||||
LoginScreenStateAction.UpdatePassword(
|
LoginScreenStateAction.UpdatePassword(
|
||||||
it
|
it
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
isValidPassword,
|
isPasswordValid,
|
||||||
submitLoginRequest = {
|
submitLoginRequest = {
|
||||||
viewModel.onAction(
|
onAction(
|
||||||
LoginScreenStateAction.ValidateCredentials(
|
LoginScreenStateAction.ValidateCredentials
|
||||||
emailAddress.value,
|
|
||||||
password.value
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -147,7 +143,7 @@ fun LoginScreen(
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
RegisterButton(viewModel = viewModel)
|
RegisterButton(onRegisterButtonClick)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -258,9 +254,9 @@ private fun Separator(modifier: Modifier = Modifier) {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RegisterButton(viewModel: LoginScreenViewModel) {
|
private fun RegisterButton(onRegisterButtonClick: () -> Unit) {
|
||||||
OutlinedButton(
|
OutlinedButton(
|
||||||
onClick = { viewModel.onRegisterButtonClick() },
|
onClick = { onRegisterButtonClick() },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.login
|
||||||
|
|
||||||
|
data class LoginScreenState(
|
||||||
|
val emailAddress: String,
|
||||||
|
val password: String,
|
||||||
|
val isEmailAddressPristine: Boolean,
|
||||||
|
val isPasswordPristine: Boolean,
|
||||||
|
val isEmailAddressValid: Boolean,
|
||||||
|
val isPasswordValid: Boolean,
|
||||||
|
)
|
|
@ -3,6 +3,6 @@ package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.login
|
||||||
sealed interface LoginScreenStateAction {
|
sealed interface LoginScreenStateAction {
|
||||||
data class UpdateEmailAddress(val emailAddress: String) : LoginScreenStateAction
|
data class UpdateEmailAddress(val emailAddress: String) : LoginScreenStateAction
|
||||||
data class UpdatePassword(val password: String) : LoginScreenStateAction
|
data class UpdatePassword(val password: String) : LoginScreenStateAction
|
||||||
data class ValidateCredentials(val emailAddress: String, val password: String) : LoginScreenStateAction
|
data object ValidateCredentials : LoginScreenStateAction
|
||||||
data class SubmitLoginRequest(val emailAddress: String, val password: String) : LoginScreenStateAction
|
data object SubmitLoginRequest : LoginScreenStateAction
|
||||||
}
|
}
|
|
@ -1,6 +1,7 @@
|
||||||
package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.login
|
package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.login
|
||||||
|
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
|
import android.util.Patterns
|
||||||
import androidx.credentials.GetCredentialResponse
|
import androidx.credentials.GetCredentialResponse
|
||||||
import androidx.credentials.PasswordCredential
|
import androidx.credentials.PasswordCredential
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
|
@ -17,9 +18,11 @@ import ing.bikeshedengineer.debtpirate.navigation.Navigator
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -30,19 +33,48 @@ class LoginScreenViewModel @Inject constructor(
|
||||||
private val validateLoginCredentials: ValidateLoginCredentialsUseCase,
|
private val validateLoginCredentials: ValidateLoginCredentialsUseCase,
|
||||||
private val updateStoreData: UpdateStoreDataUseCase
|
private val updateStoreData: UpdateStoreDataUseCase
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private val _isEmailAddressPristine = MutableStateFlow(true)
|
private val _state = MutableStateFlow(
|
||||||
private val _emailAddress = MutableStateFlow("")
|
LoginScreenState(
|
||||||
val emailAddress = _emailAddress.asStateFlow()
|
emailAddress = "",
|
||||||
val isValidEmailAddress =
|
password = "",
|
||||||
_isEmailAddressPristine.combine(_emailAddress.map { it.isNotBlank() }) { isPristine, isValid ->
|
isEmailAddressPristine = true,
|
||||||
isPristine || isValid
|
isPasswordPristine = true,
|
||||||
}
|
isEmailAddressValid = true,
|
||||||
|
isPasswordValid = true,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
private val _isPasswordPristine = MutableStateFlow(true)
|
val emailAddress = _state.map { it.emailAddress }
|
||||||
private val _password = MutableStateFlow("")
|
val password = _state.map { it.password }
|
||||||
val password = _password.asStateFlow()
|
val isEmailAddressPristine = _state.map { it.isEmailAddressPristine }
|
||||||
val isValidPassword = _isPasswordPristine.combine(_password.map { it.isNotBlank() }) { isPristine, isValid ->
|
val isPasswordPristine = _state.map { it.isPasswordPristine }
|
||||||
isPristine || isValid
|
val isEmailAddressValid = _state.map { it.isEmailAddressValid }
|
||||||
|
val isPasswordValid = _state.map { it.isPasswordValid }
|
||||||
|
|
||||||
|
init {
|
||||||
|
_state.distinctUntilChangedBy { it.emailAddress }
|
||||||
|
.map { it.emailAddress.isNotBlank() && Patterns.EMAIL_ADDRESS.matcher(it.emailAddress).matches() }
|
||||||
|
.onEach { isEmailAddressValid ->
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
isEmailAddressPristine = false,
|
||||||
|
isEmailAddressValid = it.isEmailAddressPristine || isEmailAddressValid
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
|
_state.distinctUntilChangedBy { it.password }
|
||||||
|
.map { it.password.isNotBlank() }
|
||||||
|
.onEach { isPasswordValid ->
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
isPasswordPristine = false,
|
||||||
|
isPasswordValid = it.isPasswordPristine || isPasswordValid
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _toastMessages = MutableSharedFlow<String>()
|
private val _toastMessages = MutableSharedFlow<String>()
|
||||||
|
@ -51,37 +83,32 @@ class LoginScreenViewModel @Inject constructor(
|
||||||
fun onAction(action: LoginScreenStateAction) {
|
fun onAction(action: LoginScreenStateAction) {
|
||||||
when (action) {
|
when (action) {
|
||||||
is LoginScreenStateAction.UpdateEmailAddress -> {
|
is LoginScreenStateAction.UpdateEmailAddress -> {
|
||||||
_emailAddress.value = action.emailAddress
|
_state.update { it.copy(emailAddress = action.emailAddress) }
|
||||||
_isEmailAddressPristine.value = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
is LoginScreenStateAction.UpdatePassword -> {
|
is LoginScreenStateAction.UpdatePassword -> {
|
||||||
_password.value = action.password
|
_state.update { it.copy(password = action.password) }
|
||||||
_isPasswordPristine.value = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
is LoginScreenStateAction.ValidateCredentials -> {
|
is LoginScreenStateAction.ValidateCredentials -> {
|
||||||
val (emailAddress, password) = action
|
onValidateLoginCredentials(_state.value.emailAddress, _state.value.password)
|
||||||
onValidateLoginCredentials(emailAddress, password)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
is LoginScreenStateAction.SubmitLoginRequest -> {
|
is LoginScreenStateAction.SubmitLoginRequest -> {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val (emailAddress, password) = action
|
onSubmitLoginRequest(_state.value.emailAddress, _state.value.password)
|
||||||
onSubmitLoginRequest(emailAddress, password)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onValidateLoginCredentials(emailAddress: String, password: String) {
|
private fun onValidateLoginCredentials(emailAddress: String, password: String) {
|
||||||
_isEmailAddressPristine.value = false
|
_state.update { it.copy(isEmailAddressPristine = true, isPasswordPristine = true) }
|
||||||
_isPasswordPristine.value = false
|
|
||||||
|
|
||||||
val validationResult = validateLoginCredentials(emailAddress, password)
|
val validationResult = validateLoginCredentials(emailAddress, password)
|
||||||
when (validationResult) {
|
when (validationResult) {
|
||||||
is LoginCredentialsValidationResult.ValidCredentials -> {
|
is LoginCredentialsValidationResult.ValidCredentials -> {
|
||||||
onAction(LoginScreenStateAction.SubmitLoginRequest(emailAddress, password))
|
onAction(LoginScreenStateAction.SubmitLoginRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
|
@ -131,7 +158,7 @@ class LoginScreenViewModel @Inject constructor(
|
||||||
val emailAddress = credentials.id
|
val emailAddress = credentials.id
|
||||||
val password = credentials.password
|
val password = credentials.password
|
||||||
|
|
||||||
onAction(LoginScreenStateAction.SubmitLoginRequest(emailAddress, password))
|
onAction(LoginScreenStateAction.SubmitLoginRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
|
|
|
@ -29,7 +29,6 @@ import androidx.compose.material3.rememberTopAppBarState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.State
|
import androidx.compose.runtime.State
|
||||||
import androidx.compose.runtime.collectAsState
|
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
|
@ -49,17 +48,32 @@ import androidx.compose.ui.unit.sp
|
||||||
import androidx.credentials.CreatePasswordRequest
|
import androidx.credentials.CreatePasswordRequest
|
||||||
import androidx.credentials.CredentialManager
|
import androidx.credentials.CredentialManager
|
||||||
import androidx.credentials.exceptions.CreateCredentialException
|
import androidx.credentials.exceptions.CreateCredentialException
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@Composable()
|
@Composable()
|
||||||
fun RegistrationScreen(
|
fun RegistrationScreen(
|
||||||
viewModel: RegistrationScreenViewModel = hiltViewModel<RegistrationScreenViewModel>()
|
emailAddress: State<String>,
|
||||||
|
emailAddressError: State<String>,
|
||||||
|
isEmailAddressValid: State<Boolean>,
|
||||||
|
name: State<String>,
|
||||||
|
nameError: State<String>,
|
||||||
|
isNameValid: State<Boolean>,
|
||||||
|
password: State<String>,
|
||||||
|
passwordError: State<String>,
|
||||||
|
isPasswordValid: State<Boolean>,
|
||||||
|
confirmPassword: State<String>,
|
||||||
|
confirmPasswordError: State<String>,
|
||||||
|
isConfirmPasswordValid: State<Boolean>,
|
||||||
|
onAction: (RegistrationScreenAction) -> Unit,
|
||||||
|
onRegistrationMessage: State<Pair<String, String>?>,
|
||||||
|
navigateToConfirmationScreen: (String) -> Unit,
|
||||||
|
onNavigateUp: () -> Unit,
|
||||||
) {
|
) {
|
||||||
val localContext = LocalContext.current
|
val localContext = LocalContext.current
|
||||||
val onRegistrationMessage = viewModel.onRegistrationComplete.collectAsState(null)
|
|
||||||
LaunchedEffect(onRegistrationMessage.value) {
|
LaunchedEffect(onRegistrationMessage.value) {
|
||||||
val message = onRegistrationMessage.value
|
val message = onRegistrationMessage.value
|
||||||
|
|
||||||
if (message != null) {
|
if (message != null) {
|
||||||
val (username, password) = message
|
val (username, password) = message
|
||||||
val credentialManager = CredentialManager.create(localContext)
|
val credentialManager = CredentialManager.create(localContext)
|
||||||
|
@ -72,13 +86,13 @@ fun RegistrationScreen(
|
||||||
// TODO: Display a toast...
|
// TODO: Display a toast...
|
||||||
}
|
}
|
||||||
|
|
||||||
viewModel.navigateToConfirmationScreen(username)
|
navigateToConfirmationScreen(username)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
val scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState())
|
||||||
Scaffold(
|
Scaffold(
|
||||||
topBar = { RegistrationTopAppBar(onNavigateUp = viewModel::navigateUp) },
|
topBar = { RegistrationTopAppBar(onNavigateUp) },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
.nestedScroll(scrollBehavior.nestedScrollConnection)
|
||||||
|
@ -89,7 +103,19 @@ fun RegistrationScreen(
|
||||||
.padding(16.dp)
|
.padding(16.dp)
|
||||||
) {
|
) {
|
||||||
RegistrationComponent(
|
RegistrationComponent(
|
||||||
viewModel,
|
emailAddress = emailAddress,
|
||||||
|
emailAddressError = emailAddressError,
|
||||||
|
isEmailAddressValid = isEmailAddressValid,
|
||||||
|
name = name,
|
||||||
|
nameError = nameError,
|
||||||
|
isNameValid = isNameValid,
|
||||||
|
password = password,
|
||||||
|
passwordError = passwordError,
|
||||||
|
isPasswordValid = isPasswordValid,
|
||||||
|
confirmPassword = confirmPassword,
|
||||||
|
confirmPasswordError = confirmPasswordError,
|
||||||
|
isConfirmPasswordValid = isConfirmPasswordValid,
|
||||||
|
onAction = onAction
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -126,25 +152,21 @@ private fun RegistrationTopAppBar(onNavigateUp: () -> Unit, modifier: Modifier =
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RegistrationComponent(
|
private fun RegistrationComponent(
|
||||||
viewModel: RegistrationScreenViewModel,
|
emailAddress: State<String>,
|
||||||
|
emailAddressError: State<String>,
|
||||||
|
isEmailAddressValid: State<Boolean>,
|
||||||
|
name: State<String>,
|
||||||
|
nameError: State<String>,
|
||||||
|
isNameValid: State<Boolean>,
|
||||||
|
password: State<String>,
|
||||||
|
passwordError: State<String>,
|
||||||
|
isPasswordValid: State<Boolean>,
|
||||||
|
confirmPassword: State<String>,
|
||||||
|
confirmPasswordError: State<String>,
|
||||||
|
isConfirmPasswordValid: State<Boolean>,
|
||||||
|
onAction: (RegistrationScreenAction) -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val emailAddress = viewModel.emailAddress.collectAsState()
|
|
||||||
val emailAddressError = viewModel.emailAddressError.collectAsState()
|
|
||||||
val isInvalidEmailAddress = viewModel.isInvalidEmailAddress.collectAsState(false)
|
|
||||||
|
|
||||||
val name = viewModel.name.collectAsState()
|
|
||||||
val nameError = viewModel.nameError.collectAsState()
|
|
||||||
val isInvalidName = viewModel.isInvalidName.collectAsState(false)
|
|
||||||
|
|
||||||
val password = viewModel.password.collectAsState()
|
|
||||||
val passwordError = viewModel.passwordError.collectAsState()
|
|
||||||
val isInvalidPassword = viewModel.isInvalidPassword.collectAsState(false)
|
|
||||||
|
|
||||||
val confirmPassword = viewModel.confirmPassword.collectAsState()
|
|
||||||
val confirmPasswordError = viewModel.confirmPasswordError.collectAsState()
|
|
||||||
val isInvalidConfirmPassword = viewModel.isInvalidConfirmPassword.collectAsState(false)
|
|
||||||
|
|
||||||
Column(modifier = modifier.verticalScroll(rememberScrollState())) {
|
Column(modifier = modifier.verticalScroll(rememberScrollState())) {
|
||||||
OutlinedTextField(
|
OutlinedTextField(
|
||||||
value = emailAddress.value,
|
value = emailAddress.value,
|
||||||
|
@ -153,13 +175,13 @@ private fun RegistrationComponent(
|
||||||
supportingText = { Text(text = emailAddressError.value) },
|
supportingText = { Text(text = emailAddressError.value) },
|
||||||
leadingIcon = { Icon(Icons.Outlined.Mail, "Email") },
|
leadingIcon = { Icon(Icons.Outlined.Mail, "Email") },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
isError = isInvalidEmailAddress.value,
|
isError = !isEmailAddressValid.value,
|
||||||
keyboardOptions = KeyboardOptions(
|
keyboardOptions = KeyboardOptions(
|
||||||
capitalization = KeyboardCapitalization.None,
|
capitalization = KeyboardCapitalization.None,
|
||||||
keyboardType = KeyboardType.Email,
|
keyboardType = KeyboardType.Email,
|
||||||
imeAction = ImeAction.Next
|
imeAction = ImeAction.Next
|
||||||
),
|
),
|
||||||
onValueChange = { viewModel.onAction(RegistrationScreenAction.UpdateEmailAddress(it)) },
|
onValueChange = { onAction(RegistrationScreenAction.UpdateEmailAddress(it)) },
|
||||||
modifier = Modifier.fillMaxWidth()
|
modifier = Modifier.fillMaxWidth()
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -170,13 +192,13 @@ private fun RegistrationComponent(
|
||||||
supportingText = { Text(text = nameError.value) },
|
supportingText = { Text(text = nameError.value) },
|
||||||
leadingIcon = { Icon(Icons.Outlined.Person, "Name") },
|
leadingIcon = { Icon(Icons.Outlined.Person, "Name") },
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
isError = isInvalidName.value,
|
isError = !isNameValid.value,
|
||||||
keyboardOptions = KeyboardOptions(
|
keyboardOptions = KeyboardOptions(
|
||||||
capitalization = KeyboardCapitalization.Words,
|
capitalization = KeyboardCapitalization.Words,
|
||||||
keyboardType = KeyboardType.Text,
|
keyboardType = KeyboardType.Text,
|
||||||
imeAction = ImeAction.Next
|
imeAction = ImeAction.Next
|
||||||
),
|
),
|
||||||
onValueChange = { viewModel.onAction(RegistrationScreenAction.UpdateName(it)) },
|
onValueChange = { onAction(RegistrationScreenAction.UpdateName(it)) },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(PaddingValues(top = 4.dp))
|
.padding(PaddingValues(top = 4.dp))
|
||||||
|
@ -194,14 +216,14 @@ private fun RegistrationComponent(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
isError = isInvalidPassword.value,
|
isError = !isPasswordValid.value,
|
||||||
visualTransformation = PasswordVisualTransformation(),
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
keyboardOptions = KeyboardOptions(
|
keyboardOptions = KeyboardOptions(
|
||||||
capitalization = KeyboardCapitalization.None,
|
capitalization = KeyboardCapitalization.None,
|
||||||
keyboardType = KeyboardType.Password,
|
keyboardType = KeyboardType.Password,
|
||||||
imeAction = ImeAction.Next
|
imeAction = ImeAction.Next
|
||||||
),
|
),
|
||||||
onValueChange = { viewModel.onAction(RegistrationScreenAction.UpdatePassword(it)) },
|
onValueChange = { onAction(RegistrationScreenAction.UpdatePassword(it)) },
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(PaddingValues(top = 4.dp))
|
.padding(PaddingValues(top = 4.dp))
|
||||||
|
@ -219,22 +241,17 @@ private fun RegistrationComponent(
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
singleLine = true,
|
singleLine = true,
|
||||||
isError = isInvalidConfirmPassword.value,
|
isError = !isConfirmPasswordValid.value,
|
||||||
visualTransformation = PasswordVisualTransformation(),
|
visualTransformation = PasswordVisualTransformation(),
|
||||||
keyboardOptions = KeyboardOptions(
|
keyboardOptions = KeyboardOptions(
|
||||||
capitalization = KeyboardCapitalization.None,
|
capitalization = KeyboardCapitalization.None,
|
||||||
keyboardType = KeyboardType.Password,
|
keyboardType = KeyboardType.Password,
|
||||||
imeAction = ImeAction.Send
|
imeAction = ImeAction.Send
|
||||||
),
|
),
|
||||||
onValueChange = { viewModel.onAction(RegistrationScreenAction.UpdateConfirmPassword(it)) },
|
onValueChange = { onAction(RegistrationScreenAction.UpdateConfirmPassword(it)) },
|
||||||
keyboardActions = KeyboardActions(
|
keyboardActions = KeyboardActions(
|
||||||
onSend = {
|
onSend = {
|
||||||
viewModel.registerNewAccount(
|
onAction(RegistrationScreenAction.RegisterNewUser)
|
||||||
emailAddress.value,
|
|
||||||
name.value,
|
|
||||||
password.value,
|
|
||||||
confirmPassword.value
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
@ -266,31 +283,18 @@ private fun RegistrationComponent(
|
||||||
)
|
)
|
||||||
|
|
||||||
RegisterButton(
|
RegisterButton(
|
||||||
emailAddress,
|
onAction = onAction
|
||||||
name,
|
|
||||||
password,
|
|
||||||
confirmPassword,
|
|
||||||
registerNewAccount = viewModel::registerNewAccount
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RegisterButton(
|
private fun RegisterButton(
|
||||||
emailAddress: State<String>,
|
onAction: (RegistrationScreenAction) -> Unit
|
||||||
name: State<String>,
|
|
||||||
password: State<String>,
|
|
||||||
confirmPassword: State<String>,
|
|
||||||
registerNewAccount: (String, String, String, String) -> Unit
|
|
||||||
) {
|
) {
|
||||||
Button(
|
Button(
|
||||||
onClick = {
|
onClick = {
|
||||||
registerNewAccount(
|
onAction(RegistrationScreenAction.RegisterNewUser)
|
||||||
emailAddress.value,
|
|
||||||
name.value,
|
|
||||||
password.value,
|
|
||||||
confirmPassword.value
|
|
||||||
)
|
|
||||||
},
|
},
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
|
|
@ -6,10 +6,5 @@ sealed interface RegistrationScreenAction {
|
||||||
data class UpdatePassword(val password: String) : RegistrationScreenAction
|
data class UpdatePassword(val password: String) : RegistrationScreenAction
|
||||||
data class UpdateConfirmPassword(val confirmPassword: String) : RegistrationScreenAction
|
data class UpdateConfirmPassword(val confirmPassword: String) : RegistrationScreenAction
|
||||||
data object ResetFields : RegistrationScreenAction
|
data object ResetFields : RegistrationScreenAction
|
||||||
data class RegisterNewUser(
|
data object RegisterNewUser : RegistrationScreenAction
|
||||||
val emailAddress: String,
|
|
||||||
val name: String,
|
|
||||||
val password: String,
|
|
||||||
val confirmPassword: String
|
|
||||||
) : RegistrationScreenAction
|
|
||||||
}
|
}
|
|
@ -0,0 +1,20 @@
|
||||||
|
package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.register
|
||||||
|
|
||||||
|
data class RegistrationScreenState(
|
||||||
|
val emailAddress: String,
|
||||||
|
val name: String,
|
||||||
|
val password: String,
|
||||||
|
val confirmPassword: String,
|
||||||
|
val isEmailAddressPristine: Boolean,
|
||||||
|
val isNamePristine: Boolean,
|
||||||
|
val isPasswordPristine: Boolean,
|
||||||
|
val isConfirmPasswordPristine: Boolean,
|
||||||
|
val emailAddressError: String,
|
||||||
|
val nameError: String,
|
||||||
|
val passwordError: String,
|
||||||
|
val confirmPasswordError: String,
|
||||||
|
val isEmailAddressValid: Boolean,
|
||||||
|
val isNameValid: Boolean,
|
||||||
|
val isPasswordValid: Boolean,
|
||||||
|
val isConfirmPasswordValid: Boolean
|
||||||
|
)
|
|
@ -1,6 +1,6 @@
|
||||||
package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.register
|
package ing.bikeshedengineer.debtpirate.app.screen.auth.presentation.register
|
||||||
|
|
||||||
import androidx.credentials.CreatePasswordRequest
|
import android.util.Patterns
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
@ -13,8 +13,11 @@ import ing.bikeshedengineer.debtpirate.navigation.Navigator
|
||||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asSharedFlow
|
import kotlinx.coroutines.flow.asSharedFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.distinctUntilChangedBy
|
||||||
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@ -25,6 +28,144 @@ class RegistrationScreenViewModel @Inject constructor(
|
||||||
private val submitAccountRegistrationRequest: SubmitAccountRegistrationRequestUseCase,
|
private val submitAccountRegistrationRequest: SubmitAccountRegistrationRequestUseCase,
|
||||||
private val updateStoreData: UpdateStoreDataUseCase,
|
private val updateStoreData: UpdateStoreDataUseCase,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
private val _state = MutableStateFlow(
|
||||||
|
RegistrationScreenState(
|
||||||
|
emailAddress = "",
|
||||||
|
name = "",
|
||||||
|
password = "",
|
||||||
|
confirmPassword = "",
|
||||||
|
isEmailAddressPristine = true,
|
||||||
|
isNamePristine = true,
|
||||||
|
isPasswordPristine = true,
|
||||||
|
isConfirmPasswordPristine = true,
|
||||||
|
emailAddressError = "",
|
||||||
|
nameError = "",
|
||||||
|
passwordError = "",
|
||||||
|
confirmPasswordError = "",
|
||||||
|
isEmailAddressValid = false,
|
||||||
|
isNameValid = false,
|
||||||
|
isPasswordValid = false,
|
||||||
|
isConfirmPasswordValid = false
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
val emailAddress = _state.map { it.emailAddress }
|
||||||
|
val name = _state.map { it.name }
|
||||||
|
val password = _state.map { it.password }
|
||||||
|
val confirmPassword = _state.map { it.confirmPassword }
|
||||||
|
|
||||||
|
val isEmailAddressPristine = _state.map { it.isEmailAddressPristine }
|
||||||
|
val isNamePristine = _state.map { it.isNamePristine }
|
||||||
|
val isPasswordPristine = _state.map { it.isPasswordPristine }
|
||||||
|
val isConfirmPasswordPristine = _state.map { it.isConfirmPasswordPristine }
|
||||||
|
|
||||||
|
val emailAddressError = _state.map { it.emailAddressError }
|
||||||
|
val nameError = _state.map { it.nameError }
|
||||||
|
val passwordError = _state.map { it.passwordError }
|
||||||
|
val confirmPasswordError = _state.map { it.confirmPasswordError }
|
||||||
|
|
||||||
|
val isEmailAddressValid = _state.map { it.isEmailAddressValid }
|
||||||
|
val isNameValid = _state.map { it.isNameValid }
|
||||||
|
val isPasswordValid = _state.map { it.isPasswordValid }
|
||||||
|
val isConfirmPasswordValid = _state.map { it.isConfirmPasswordValid }
|
||||||
|
|
||||||
|
private val _onRegistrationComplete = MutableSharedFlow<Pair<String, String>?>()
|
||||||
|
val onRegistrationComplete = _onRegistrationComplete.asSharedFlow()
|
||||||
|
|
||||||
|
init {
|
||||||
|
_state.distinctUntilChangedBy { it.emailAddress }
|
||||||
|
.map { it.emailAddress.isNotBlank() && Patterns.EMAIL_ADDRESS.matcher(it.emailAddress).matches() }
|
||||||
|
.onEach { isEmailAddressValid ->
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
isEmailAddressPristine = false,
|
||||||
|
isEmailAddressValid = it.isEmailAddressPristine || isEmailAddressValid
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
|
_state.distinctUntilChangedBy { it.name }
|
||||||
|
.map { it.name.isNotBlank() }
|
||||||
|
.onEach { isNameValid ->
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
isNamePristine = false,
|
||||||
|
isNameValid = it.isNamePristine || isNameValid
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
|
_state.distinctUntilChangedBy { it.password }
|
||||||
|
.map { it.password.isNotBlank() }
|
||||||
|
.onEach { isPasswordValid ->
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
isPasswordPristine = false,
|
||||||
|
isPasswordValid = it.isPasswordPristine || isPasswordValid
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.launchIn(viewModelScope)
|
||||||
|
|
||||||
|
_state.distinctUntilChangedBy { it.confirmPassword }
|
||||||
|
.map { it.confirmPassword.isNotBlank() }
|
||||||
|
.onEach { isConfirmPasswordValid ->
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
isConfirmPasswordPristine = false,
|
||||||
|
isConfirmPasswordValid = it.isConfirmPasswordPristine || isConfirmPasswordValid
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.launchIn(viewModelScope)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onAction(action: RegistrationScreenAction) {
|
||||||
|
when (action) {
|
||||||
|
is RegistrationScreenAction.UpdateEmailAddress -> {
|
||||||
|
_state.update { it.copy(emailAddress = action.emailAddress) }
|
||||||
|
}
|
||||||
|
|
||||||
|
is RegistrationScreenAction.UpdateName -> {
|
||||||
|
_state.update { it.copy(name = action.name) }
|
||||||
|
}
|
||||||
|
|
||||||
|
is RegistrationScreenAction.UpdatePassword -> {
|
||||||
|
_state.update { it.copy(password = action.password) }
|
||||||
|
}
|
||||||
|
|
||||||
|
is RegistrationScreenAction.UpdateConfirmPassword -> {
|
||||||
|
_state.update { it.copy(confirmPassword = action.confirmPassword) }
|
||||||
|
}
|
||||||
|
|
||||||
|
is RegistrationScreenAction.ResetFields -> {
|
||||||
|
_state.update {
|
||||||
|
it.copy(
|
||||||
|
emailAddress = "",
|
||||||
|
isEmailAddressPristine = true,
|
||||||
|
name = "",
|
||||||
|
isNamePristine = true,
|
||||||
|
password = "",
|
||||||
|
isPasswordPristine = true,
|
||||||
|
confirmPassword = "",
|
||||||
|
isConfirmPasswordPristine = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
is RegistrationScreenAction.RegisterNewUser -> {
|
||||||
|
registerNewAccount(
|
||||||
|
_state.value.emailAddress,
|
||||||
|
_state.value.name,
|
||||||
|
_state.value.password,
|
||||||
|
_state.value.confirmPassword
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun navigateUp() {
|
fun navigateUp() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
navigator.navigateUp()
|
navigator.navigateUp()
|
||||||
|
@ -37,70 +178,6 @@ class RegistrationScreenViewModel @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _emailAddress = MutableStateFlow("")
|
|
||||||
val emailAddress = _emailAddress.asStateFlow()
|
|
||||||
|
|
||||||
private val _emailAddressError = MutableStateFlow("")
|
|
||||||
val emailAddressError = _emailAddressError.asStateFlow()
|
|
||||||
val isInvalidEmailAddress = _emailAddressError.asStateFlow().map { it.isNotEmpty() }
|
|
||||||
|
|
||||||
private val _name = MutableStateFlow("")
|
|
||||||
val name = _name.asStateFlow()
|
|
||||||
|
|
||||||
private val _nameError = MutableStateFlow("")
|
|
||||||
val nameError = _nameError.asStateFlow()
|
|
||||||
val isInvalidName = _nameError.asStateFlow().map { it.isNotEmpty() }
|
|
||||||
|
|
||||||
private val _password = MutableStateFlow("")
|
|
||||||
val password = _password.asStateFlow()
|
|
||||||
|
|
||||||
private val _passwordError = MutableStateFlow("")
|
|
||||||
val passwordError = _passwordError.asStateFlow()
|
|
||||||
val isInvalidPassword = _passwordError.asStateFlow().map { it.isNotEmpty() }
|
|
||||||
|
|
||||||
private val _confirmPassword = MutableStateFlow("")
|
|
||||||
val confirmPassword = _confirmPassword.asStateFlow()
|
|
||||||
|
|
||||||
private val _confirmPasswordError = MutableStateFlow("")
|
|
||||||
val confirmPasswordError = _confirmPasswordError.asStateFlow()
|
|
||||||
val isInvalidConfirmPassword = _confirmPasswordError.asStateFlow().map { it.isNotEmpty() }
|
|
||||||
|
|
||||||
private val _onRegistrationComplete = MutableSharedFlow<Pair<String, String>>()
|
|
||||||
val onRegistrationComplete = _onRegistrationComplete.asSharedFlow()
|
|
||||||
|
|
||||||
fun onAction(action: RegistrationScreenAction) {
|
|
||||||
when (action) {
|
|
||||||
is RegistrationScreenAction.UpdateEmailAddress -> {
|
|
||||||
_emailAddress.value = action.emailAddress
|
|
||||||
}
|
|
||||||
|
|
||||||
is RegistrationScreenAction.UpdateName -> {
|
|
||||||
_name.value = action.name
|
|
||||||
}
|
|
||||||
|
|
||||||
is RegistrationScreenAction.UpdatePassword -> {
|
|
||||||
_password.value = action.password
|
|
||||||
}
|
|
||||||
|
|
||||||
is RegistrationScreenAction.UpdateConfirmPassword -> {
|
|
||||||
_confirmPassword.value = action.confirmPassword
|
|
||||||
}
|
|
||||||
|
|
||||||
is RegistrationScreenAction.ResetFields -> {
|
|
||||||
resetFields()
|
|
||||||
}
|
|
||||||
|
|
||||||
is RegistrationScreenAction.RegisterNewUser -> {
|
|
||||||
registerNewAccount(
|
|
||||||
action.emailAddress,
|
|
||||||
action.name,
|
|
||||||
action.password,
|
|
||||||
action.confirmPassword
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun registerNewAccount(
|
fun registerNewAccount(
|
||||||
emailAddress: String,
|
emailAddress: String,
|
||||||
name: String,
|
name: String,
|
||||||
|
@ -140,37 +217,43 @@ class RegistrationScreenViewModel @Inject constructor(
|
||||||
val validationResults = validateNewAccount(emailAddress, name, password, confirmPassword)
|
val validationResults = validateNewAccount(emailAddress, name, password, confirmPassword)
|
||||||
validationResults.forEach {
|
validationResults.forEach {
|
||||||
if (it == NewAccountRegistrationValidationResult.EmptyEmailAddressField) {
|
if (it == NewAccountRegistrationValidationResult.EmptyEmailAddressField) {
|
||||||
_emailAddressError.value = "Enter an email address"
|
_state.update {
|
||||||
|
it.copy(emailAddressError = "Enter an email address")
|
||||||
|
}
|
||||||
} else if (it == NewAccountRegistrationValidationResult.InvalidEmailAddress) {
|
} else if (it == NewAccountRegistrationValidationResult.InvalidEmailAddress) {
|
||||||
_emailAddressError.value = "Enter a valid email address"
|
_state.update {
|
||||||
|
it.copy(emailAddressError = "Enter a valid email address")
|
||||||
|
}
|
||||||
} else if (it == NewAccountRegistrationValidationResult.ValidEmailAddress) {
|
} else if (it == NewAccountRegistrationValidationResult.ValidEmailAddress) {
|
||||||
_emailAddressError.value = ""
|
_state.update {
|
||||||
|
it.copy(emailAddressError = "")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (it == NewAccountRegistrationValidationResult.EmptyNameField) {
|
if (it == NewAccountRegistrationValidationResult.EmptyNameField) {
|
||||||
_nameError.value = "Enter your name"
|
_state.update { it.copy(nameError = "Enter your name") }
|
||||||
} else if (it == NewAccountRegistrationValidationResult.ValidName) {
|
} else if (it == NewAccountRegistrationValidationResult.ValidName) {
|
||||||
_nameError.value = ""
|
_state.update { it.copy(nameError = "") }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (it == NewAccountRegistrationValidationResult.EmptyPasswordField) {
|
if (it == NewAccountRegistrationValidationResult.EmptyPasswordField) {
|
||||||
_passwordError.value = "Enter a password"
|
_state.update { it.copy(passwordError = "Enter a password") }
|
||||||
} else if (it == NewAccountRegistrationValidationResult.PasswordTooShort) {
|
} else if (it == NewAccountRegistrationValidationResult.PasswordTooShort) {
|
||||||
_passwordError.value = "Password must be more than 8 characters"
|
_state.update { it.copy(passwordError = "Password must be more than 8 characters long") }
|
||||||
} else if (it == NewAccountRegistrationValidationResult.ValidPassword) {
|
} else if (it == NewAccountRegistrationValidationResult.ValidPassword) {
|
||||||
_passwordError.value = ""
|
_state.update { it.copy(passwordError = "") }
|
||||||
}
|
}
|
||||||
|
|
||||||
if (it == NewAccountRegistrationValidationResult.EmptyConfirmPasswordField) {
|
if (it == NewAccountRegistrationValidationResult.EmptyConfirmPasswordField) {
|
||||||
_confirmPasswordError.value = "Enter a password"
|
_state.update { it.copy(confirmPasswordError = "Enter a password") }
|
||||||
} else if (it == NewAccountRegistrationValidationResult.PasswordsDontMatch) {
|
} else if (it == NewAccountRegistrationValidationResult.PasswordsDontMatch) {
|
||||||
_confirmPasswordError.value = "Passwords don't match"
|
_state.update { it.copy(confirmPasswordError = "Passwords don't match") }
|
||||||
} else if (it == NewAccountRegistrationValidationResult.ValidConfirmPassword) {
|
} else if (it == NewAccountRegistrationValidationResult.ValidConfirmPassword) {
|
||||||
_confirmPasswordError.value = ""
|
_state.update { it.copy(confirmPasswordError = "") }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return validationResults.filter {
|
return validationResults.none {
|
||||||
when (it) {
|
when (it) {
|
||||||
NewAccountRegistrationValidationResult.ValidEmailAddress,
|
NewAccountRegistrationValidationResult.ValidEmailAddress,
|
||||||
NewAccountRegistrationValidationResult.ValidName,
|
NewAccountRegistrationValidationResult.ValidName,
|
||||||
|
@ -180,13 +263,6 @@ class RegistrationScreenViewModel @Inject constructor(
|
||||||
|
|
||||||
else -> true
|
else -> true
|
||||||
}
|
}
|
||||||
}.isEmpty()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resetFields() {
|
|
||||||
_emailAddress.value = ""
|
|
||||||
_name.value = ""
|
|
||||||
_password.value = ""
|
|
||||||
_confirmPassword.value = ""
|
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue