From 40ff448d59fe61fe793b48cdd321060184bf7670 Mon Sep 17 00:00:00 2001 From: "Z. Charles Dziura" Date: Thu, 31 Oct 2024 14:37:35 -0400 Subject: [PATCH] Navigate to registration screen when button is clicked --- .../debtpirate/app/host/MainActivity.kt | 13 ++- .../data/remote/model/AuthLoginRequest.kt | 2 +- .../usecase/SubmitLoginCredentialsUseCase.kt | 4 +- .../ValidateLoginCredentialsUseCase.kt | 14 ++- .../auth/navigation/AuthNavigationEvent.kt | 6 ++ .../{AuthScreen.kt => login/LoginScreen.kt} | 88 ++++++++++++------- .../LoginScreenViewModel.kt} | 27 ++++-- .../register/RegistrationScreen.kt | 39 ++++++++ .../register/RegistrationScreenViewModel.kt | 4 + app/app/src/main/res/values/strings.xml | 3 +- 10 files changed, 143 insertions(+), 57 deletions(-) create mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/navigation/AuthNavigationEvent.kt rename app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/{AuthScreen.kt => login/LoginScreen.kt} (71%) rename app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/{AuthScreenViewModel.kt => login/LoginScreenViewModel.kt} (77%) create mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/register/RegistrationScreen.kt create mode 100644 app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/register/RegistrationScreenViewModel.kt diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/host/MainActivity.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/host/MainActivity.kt index a3e1fda..a54097d 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/host/MainActivity.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/app/host/MainActivity.kt @@ -9,11 +9,15 @@ import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import dagger.hilt.android.AndroidEntryPoint import ing.bikeshedengineer.debtpirate.theme.DebtPirateTheme -import ing.bikeshedengineer.debtpirate.auth.presentation.AuthScreen +import ing.bikeshedengineer.debtpirate.auth.presentation.login.LoginScreen +import ing.bikeshedengineer.debtpirate.auth.presentation.register.RegistrationScreen import kotlinx.serialization.Serializable @Serializable -object AuthRoute +object LoginRoute + +@Serializable +object RegistrationRoute @AndroidEntryPoint class MainActivity : ComponentActivity() { @@ -25,8 +29,9 @@ class MainActivity : ComponentActivity() { val navController = rememberNavController() DebtPirateTheme { - NavHost(navController = navController, startDestination = AuthRoute) { - composable() { AuthScreen() } + NavHost(navController = navController, startDestination = LoginRoute) { + composable() { LoginScreen(navController = navController) } + composable() { RegistrationScreen() } } } } diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/data/remote/model/AuthLoginRequest.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/data/remote/model/AuthLoginRequest.kt index f4e5463..9e740a1 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/data/remote/model/AuthLoginRequest.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/data/remote/model/AuthLoginRequest.kt @@ -1,3 +1,3 @@ package ing.bikeshedengineer.debtpirate.auth.data.remote.model -data class AuthLoginRequest(val username: String, val password: String) +data class AuthLoginRequest(val emailAddress: String, val password: String) diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/domain/usecase/SubmitLoginCredentialsUseCase.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/domain/usecase/SubmitLoginCredentialsUseCase.kt index b8f380e..0e8de2d 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/domain/usecase/SubmitLoginCredentialsUseCase.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/domain/usecase/SubmitLoginCredentialsUseCase.kt @@ -8,8 +8,8 @@ import ing.bikeshedengineer.debtpirate.auth.data.remote.repository.AuthRepositor class SubmitLoginCredentialsUseCase( private val authRepository: AuthRepository ) { - suspend operator fun invoke(username: String, password: String): AuthLoginResponse { - val credentials = AuthLoginRequest(username, password) + suspend operator fun invoke(emailAddress: String, password: String): AuthLoginResponse { + val credentials = AuthLoginRequest(emailAddress, password) try { val response = authRepository.submitLoginRequest(credentials) diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/domain/usecase/ValidateLoginCredentialsUseCase.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/domain/usecase/ValidateLoginCredentialsUseCase.kt index fad3ab4..5e2e437 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/domain/usecase/ValidateLoginCredentialsUseCase.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/domain/usecase/ValidateLoginCredentialsUseCase.kt @@ -1,21 +1,19 @@ package ing.bikeshedengineer.debtpirate.auth.domain.usecase -import android.util.Log - sealed class LoginCredentialsValidationResult { object EmptyCredentials : LoginCredentialsValidationResult() - object EmptyUsernameField : LoginCredentialsValidationResult() + object EmptyEmailAddressField : LoginCredentialsValidationResult() object EmptyPasswordField : LoginCredentialsValidationResult() object ValidCredentials : LoginCredentialsValidationResult() } class ValidateLoginCredentialsUseCase { - operator fun invoke(username: String, password: String): LoginCredentialsValidationResult { - return if (username.isEmpty() && password.isEmpty()) { + operator fun invoke(email: String, password: String): LoginCredentialsValidationResult { + return if (email.isEmpty() && password.isEmpty()) { LoginCredentialsValidationResult.EmptyCredentials; - } else if (username.isEmpty() && password.isNotEmpty()) { - LoginCredentialsValidationResult.EmptyUsernameField; - } else if (username.isNotEmpty() && password.isEmpty()) { + } else if (email.isEmpty() && password.isNotEmpty()) { + LoginCredentialsValidationResult.EmptyEmailAddressField; + } else if (email.isNotEmpty() && password.isEmpty()) { LoginCredentialsValidationResult.EmptyPasswordField } else { LoginCredentialsValidationResult.ValidCredentials diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/navigation/AuthNavigationEvent.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/navigation/AuthNavigationEvent.kt new file mode 100644 index 0000000..f924fe9 --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/navigation/AuthNavigationEvent.kt @@ -0,0 +1,6 @@ +package ing.bikeshedengineer.debtpirate.auth.navigation + +sealed class AuthNavigationEvent { + object NavigateToRegistrationScreen : AuthNavigationEvent() + object NavigateToLoginScreen : AuthNavigationEvent() +} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/AuthScreen.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreen.kt similarity index 71% rename from app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/AuthScreen.kt rename to app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreen.kt index 77d7429..c957222 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/AuthScreen.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreen.kt @@ -1,4 +1,4 @@ -package ing.bikeshedengineer.debtpirate.auth.presentation +package ing.bikeshedengineer.debtpirate.auth.presentation.login import android.annotation.SuppressLint import androidx.compose.foundation.background @@ -15,8 +15,8 @@ import androidx.compose.foundation.text.KeyboardActions import androidx.compose.foundation.text.KeyboardOptions import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Mail import androidx.compose.material.icons.outlined.Password -import androidx.compose.material.icons.outlined.Person import androidx.compose.material.icons.outlined.PersonAdd import androidx.compose.material3.Button import androidx.compose.material3.Icon @@ -25,11 +25,13 @@ import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.State import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardCapitalization @@ -38,17 +40,34 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavController +import ing.bikeshedengineer.debtpirate.R +import ing.bikeshedengineer.debtpirate.app.host.RegistrationRoute +import ing.bikeshedengineer.debtpirate.auth.navigation.AuthNavigationEvent @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @Composable -fun AuthScreen( - viewModel: AuthScreenViewModel = hiltViewModel() +fun LoginScreen( + navController: NavController, + viewModel: LoginScreenViewModel = hiltViewModel() ) { + LaunchedEffect(viewModel.navigationEvent) { + viewModel.navigationEvent.collect { event -> + when (event) { + is AuthNavigationEvent.NavigateToRegistrationScreen -> { + navController.navigate(RegistrationRoute) + } + + else -> {} + } + } + } + Scaffold( modifier = Modifier.fillMaxSize() ) { Column { - AuthScreenTopAppBar( + LoginScreenTopAppBar( modifier = Modifier .fillMaxWidth() .weight(1f) @@ -59,11 +78,11 @@ fun AuthScreen( .weight(3f) .padding(16.dp) ) { - val username = viewModel.username.collectAsState() + val emailAddress = viewModel.emailAddress.collectAsState() val password = viewModel.password.collectAsState() LoginComponent( - username = username, - onUpdateUsername = viewModel::updateUsername, + emailAddress = emailAddress, + onUpdateEmailAddress = viewModel::updateUsername, password = password, onUpdatePassword = viewModel::updatePassword, submitLoginRequest = viewModel::submitLoginRequest @@ -71,16 +90,26 @@ fun AuthScreen( Separator(modifier = Modifier.padding(PaddingValues(top = 24.dp, bottom = 24.dp))) - RegisterButton() + RegisterButton(viewModel = viewModel) } } } } +@Composable +private fun LoginScreenTopAppBar(modifier: Modifier = Modifier) { + Box(modifier = modifier.background(Color.Green)) { + Text( + text = "Hello from Login! I'm in a box!", + modifier = Modifier.align(Alignment.Center) + ) + } +} + @Composable private fun LoginComponent( - username: State, - onUpdateUsername: (String) -> Unit, + emailAddress: State, + onUpdateEmailAddress: (String) -> Unit, password: State, onUpdatePassword: (String) -> Unit, submitLoginRequest: () -> Unit, @@ -88,25 +117,30 @@ private fun LoginComponent( ) { Column(modifier.verticalScroll(rememberScrollState())) { OutlinedTextField( - value = username.value, - label = { Text("Username") }, - placeholder = { Text("Username") }, - leadingIcon = { Icon(Icons.Outlined.Person, "person") }, + value = emailAddress.value, + label = { Text(text = stringResource(R.string.auth__email)) }, + placeholder = { Text(text = stringResource(R.string.auth__email)) }, + leadingIcon = { Icon(Icons.Outlined.Mail, stringResource(R.string.auth__email)) }, singleLine = true, keyboardOptions = KeyboardOptions( capitalization = KeyboardCapitalization.None, - keyboardType = KeyboardType.Text, + keyboardType = KeyboardType.Email, imeAction = ImeAction.Next ), - onValueChange = onUpdateUsername, + onValueChange = onUpdateEmailAddress, modifier = Modifier.fillMaxWidth() ) OutlinedTextField( value = password.value, - label = { Text("Password") }, - placeholder = { Text("Password") }, - leadingIcon = { Icon(Icons.Outlined.Password, "password") }, + label = { Text(text = stringResource(R.string.auth__password)) }, + placeholder = { Text(text = stringResource(R.string.auth__password)) }, + leadingIcon = { + Icon( + Icons.Outlined.Password, + stringResource(R.string.auth__password) + ) + }, singleLine = true, visualTransformation = PasswordVisualTransformation(), keyboardOptions = KeyboardOptions( @@ -133,16 +167,6 @@ private fun LoginComponent( } } -@Composable -private fun AuthScreenTopAppBar(modifier: Modifier = Modifier) { - Box(modifier = modifier.background(Color.Green)) { - Text( - text = "Hello from Login! I'm in a box!", - modifier = Modifier.align(Alignment.Center) - ) - } -} - @Composable private fun Separator(modifier: Modifier = Modifier) { Row(modifier = modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { @@ -172,9 +196,9 @@ private fun Separator(modifier: Modifier = Modifier) { } @Composable -private fun RegisterButton() { +private fun RegisterButton(viewModel: LoginScreenViewModel) { OutlinedButton( - onClick = { /*TODO*/ }, + onClick = { viewModel.onRegisterButtonClick() }, modifier = Modifier .fillMaxWidth() ) { diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/AuthScreenViewModel.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreenViewModel.kt similarity index 77% rename from app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/AuthScreenViewModel.kt rename to app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreenViewModel.kt index 993357a..dd338e4 100644 --- a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/AuthScreenViewModel.kt +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/login/LoginScreenViewModel.kt @@ -1,16 +1,16 @@ -package ing.bikeshedengineer.debtpirate.auth.presentation +package ing.bikeshedengineer.debtpirate.auth.presentation.login -import android.util.Log import androidx.datastore.core.DataStore import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import dagger.hilt.android.lifecycle.HiltViewModel import ing.bikeshedengineer.debtpirate.PrefsDataStore -import ing.bikeshedengineer.debtpirate.auth.data.remote.model.AuthLoginRequest +import ing.bikeshedengineer.debtpirate.app.host.RegistrationRoute import ing.bikeshedengineer.debtpirate.auth.data.remote.repository.AuthRepository import ing.bikeshedengineer.debtpirate.auth.domain.usecase.LoginCredentialsValidationResult import ing.bikeshedengineer.debtpirate.auth.domain.usecase.SubmitLoginCredentialsUseCase import ing.bikeshedengineer.debtpirate.auth.domain.usecase.ValidateLoginCredentialsUseCase +import ing.bikeshedengineer.debtpirate.auth.navigation.AuthNavigationEvent import ing.bikeshedengineer.debtpirate.domain.model.Token import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.asStateFlow @@ -34,8 +34,9 @@ enum class InvalidReason { PasswordTooShort } + @HiltViewModel -class AuthScreenViewModel @Inject constructor( +class LoginScreenViewModel @Inject constructor( private val authRepository: AuthRepository, private val prefsStore: DataStore, private val submitLoginCredentials: SubmitLoginCredentialsUseCase, @@ -43,14 +44,17 @@ class AuthScreenViewModel @Inject constructor( ) : ViewModel() { // private val storeLoginData = StoreLoginDataUseCase(dataStore) - private val _username = MutableStateFlow("") - val username = _username.asStateFlow() + private val _navigationEvent = MutableStateFlow(null) + val navigationEvent = _navigationEvent.asStateFlow() + + private val _emailAddress = MutableStateFlow("") + val emailAddress = _emailAddress.asStateFlow() private val _password = MutableStateFlow("") val password = _password.asStateFlow() fun updateUsername(username: String) { - _username.value = username + _emailAddress.value = username } fun updatePassword(password: String) { @@ -58,16 +62,17 @@ class AuthScreenViewModel @Inject constructor( } fun submitLoginRequest() { - when (validateLoginCredentials(username.value, password.value)) { + when (validateLoginCredentials(emailAddress.value, password.value)) { is LoginCredentialsValidationResult.ValidCredentials -> { viewModelScope.launch { try { - val result = submitLoginCredentials(username.value, password.value) + val result = submitLoginCredentials(emailAddress.value, password.value) } catch (err: Throwable) { // TODO... } } } + else -> { // TODO... } @@ -94,4 +99,8 @@ class AuthScreenViewModel @Inject constructor( } } } + + fun onRegisterButtonClick() { + _navigationEvent.value = AuthNavigationEvent.NavigateToRegistrationScreen + } } \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/register/RegistrationScreen.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/register/RegistrationScreen.kt new file mode 100644 index 0000000..6ecd6f8 --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/register/RegistrationScreen.kt @@ -0,0 +1,39 @@ +package ing.bikeshedengineer.debtpirate.auth.presentation.register + +import android.annotation.SuppressLint +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color + +@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") +@Composable() +fun RegistrationScreen() { + Scaffold( + modifier = Modifier.fillMaxSize() + ) { + Column { + RegistrationScreenTopAppBar( + modifier = Modifier.fillMaxWidth() + .weight(1f) + ) + } + } +} + +@Composable +private fun RegistrationScreenTopAppBar(modifier: Modifier = Modifier) { + Box(modifier = modifier.background(Color.Green)) { + Text( + text = "Hello from Registration! I'm in a box!", + modifier = Modifier.align(Alignment.Center) + ) + } +} \ No newline at end of file diff --git a/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/register/RegistrationScreenViewModel.kt b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/register/RegistrationScreenViewModel.kt new file mode 100644 index 0000000..e4ddddd --- /dev/null +++ b/app/app/src/main/java/ing/bikeshedengineer/debtpirate/auth/presentation/register/RegistrationScreenViewModel.kt @@ -0,0 +1,4 @@ +package ing.bikeshedengineer.debtpirate.auth.presentation.register + +class RegistrationScreenViewModel { +} \ No newline at end of file diff --git a/app/app/src/main/res/values/strings.xml b/app/app/src/main/res/values/strings.xml index ff01224..d218441 100644 --- a/app/app/src/main/res/values/strings.xml +++ b/app/app/src/main/res/values/strings.xml @@ -1,4 +1,5 @@ Debt Pirate - MainActivity + Email + Password \ No newline at end of file