Navigate to registration screen when button is clicked

This commit is contained in:
Z. Charles Dziura 2024-10-31 14:37:35 -04:00
parent fac5a55251
commit 40ff448d59
10 changed files with 143 additions and 57 deletions

View file

@ -9,11 +9,15 @@ import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import ing.bikeshedengineer.debtpirate.theme.DebtPirateTheme 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 import kotlinx.serialization.Serializable
@Serializable @Serializable
object AuthRoute object LoginRoute
@Serializable
object RegistrationRoute
@AndroidEntryPoint @AndroidEntryPoint
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
@ -25,8 +29,9 @@ class MainActivity : ComponentActivity() {
val navController = rememberNavController() val navController = rememberNavController()
DebtPirateTheme { DebtPirateTheme {
NavHost(navController = navController, startDestination = AuthRoute) { NavHost(navController = navController, startDestination = LoginRoute) {
composable<AuthRoute>() { AuthScreen() } composable<LoginRoute>() { LoginScreen(navController = navController) }
composable<RegistrationRoute>() { RegistrationScreen() }
} }
} }
} }

View file

@ -1,3 +1,3 @@
package ing.bikeshedengineer.debtpirate.auth.data.remote.model 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)

View file

@ -8,8 +8,8 @@ import ing.bikeshedengineer.debtpirate.auth.data.remote.repository.AuthRepositor
class SubmitLoginCredentialsUseCase( class SubmitLoginCredentialsUseCase(
private val authRepository: AuthRepository private val authRepository: AuthRepository
) { ) {
suspend operator fun invoke(username: String, password: String): AuthLoginResponse { suspend operator fun invoke(emailAddress: String, password: String): AuthLoginResponse {
val credentials = AuthLoginRequest(username, password) val credentials = AuthLoginRequest(emailAddress, password)
try { try {
val response = authRepository.submitLoginRequest(credentials) val response = authRepository.submitLoginRequest(credentials)

View file

@ -1,21 +1,19 @@
package ing.bikeshedengineer.debtpirate.auth.domain.usecase package ing.bikeshedengineer.debtpirate.auth.domain.usecase
import android.util.Log
sealed class LoginCredentialsValidationResult { sealed class LoginCredentialsValidationResult {
object EmptyCredentials : LoginCredentialsValidationResult() object EmptyCredentials : LoginCredentialsValidationResult()
object EmptyUsernameField : LoginCredentialsValidationResult() object EmptyEmailAddressField : LoginCredentialsValidationResult()
object EmptyPasswordField : LoginCredentialsValidationResult() object EmptyPasswordField : LoginCredentialsValidationResult()
object ValidCredentials : LoginCredentialsValidationResult() object ValidCredentials : LoginCredentialsValidationResult()
} }
class ValidateLoginCredentialsUseCase { class ValidateLoginCredentialsUseCase {
operator fun invoke(username: String, password: String): LoginCredentialsValidationResult { operator fun invoke(email: String, password: String): LoginCredentialsValidationResult {
return if (username.isEmpty() && password.isEmpty()) { return if (email.isEmpty() && password.isEmpty()) {
LoginCredentialsValidationResult.EmptyCredentials; LoginCredentialsValidationResult.EmptyCredentials;
} else if (username.isEmpty() && password.isNotEmpty()) { } else if (email.isEmpty() && password.isNotEmpty()) {
LoginCredentialsValidationResult.EmptyUsernameField; LoginCredentialsValidationResult.EmptyEmailAddressField;
} else if (username.isNotEmpty() && password.isEmpty()) { } else if (email.isNotEmpty() && password.isEmpty()) {
LoginCredentialsValidationResult.EmptyPasswordField LoginCredentialsValidationResult.EmptyPasswordField
} else { } else {
LoginCredentialsValidationResult.ValidCredentials LoginCredentialsValidationResult.ValidCredentials

View file

@ -0,0 +1,6 @@
package ing.bikeshedengineer.debtpirate.auth.navigation
sealed class AuthNavigationEvent {
object NavigateToRegistrationScreen : AuthNavigationEvent()
object NavigateToLoginScreen : AuthNavigationEvent()
}

View file

@ -1,4 +1,4 @@
package ing.bikeshedengineer.debtpirate.auth.presentation package ing.bikeshedengineer.debtpirate.auth.presentation.login
import android.annotation.SuppressLint import android.annotation.SuppressLint
import androidx.compose.foundation.background 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.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons 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.Password
import androidx.compose.material.icons.outlined.Person
import androidx.compose.material.icons.outlined.PersonAdd import androidx.compose.material.icons.outlined.PersonAdd
import androidx.compose.material3.Button import androidx.compose.material3.Button
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@ -25,11 +25,13 @@ import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.State import androidx.compose.runtime.State
import androidx.compose.runtime.collectAsState 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.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
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
@ -38,17 +40,34 @@ import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel 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") @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@Composable @Composable
fun AuthScreen( fun LoginScreen(
viewModel: AuthScreenViewModel = hiltViewModel<AuthScreenViewModel>() navController: NavController,
viewModel: LoginScreenViewModel = hiltViewModel<LoginScreenViewModel>()
) { ) {
LaunchedEffect(viewModel.navigationEvent) {
viewModel.navigationEvent.collect { event ->
when (event) {
is AuthNavigationEvent.NavigateToRegistrationScreen -> {
navController.navigate(RegistrationRoute)
}
else -> {}
}
}
}
Scaffold( Scaffold(
modifier = Modifier.fillMaxSize() modifier = Modifier.fillMaxSize()
) { ) {
Column { Column {
AuthScreenTopAppBar( LoginScreenTopAppBar(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.weight(1f) .weight(1f)
@ -59,11 +78,11 @@ fun AuthScreen(
.weight(3f) .weight(3f)
.padding(16.dp) .padding(16.dp)
) { ) {
val username = viewModel.username.collectAsState() val emailAddress = viewModel.emailAddress.collectAsState()
val password = viewModel.password.collectAsState() val password = viewModel.password.collectAsState()
LoginComponent( LoginComponent(
username = username, emailAddress = emailAddress,
onUpdateUsername = viewModel::updateUsername, onUpdateEmailAddress = viewModel::updateUsername,
password = password, password = password,
onUpdatePassword = viewModel::updatePassword, onUpdatePassword = viewModel::updatePassword,
submitLoginRequest = viewModel::submitLoginRequest submitLoginRequest = viewModel::submitLoginRequest
@ -71,16 +90,26 @@ fun AuthScreen(
Separator(modifier = Modifier.padding(PaddingValues(top = 24.dp, bottom = 24.dp))) 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 @Composable
private fun LoginComponent( private fun LoginComponent(
username: State<String>, emailAddress: State<String>,
onUpdateUsername: (String) -> Unit, onUpdateEmailAddress: (String) -> Unit,
password: State<String>, password: State<String>,
onUpdatePassword: (String) -> Unit, onUpdatePassword: (String) -> Unit,
submitLoginRequest: () -> Unit, submitLoginRequest: () -> Unit,
@ -88,25 +117,30 @@ private fun LoginComponent(
) { ) {
Column(modifier.verticalScroll(rememberScrollState())) { Column(modifier.verticalScroll(rememberScrollState())) {
OutlinedTextField( OutlinedTextField(
value = username.value, value = emailAddress.value,
label = { Text("Username") }, label = { Text(text = stringResource(R.string.auth__email)) },
placeholder = { Text("Username") }, placeholder = { Text(text = stringResource(R.string.auth__email)) },
leadingIcon = { Icon(Icons.Outlined.Person, "person") }, leadingIcon = { Icon(Icons.Outlined.Mail, stringResource(R.string.auth__email)) },
singleLine = true, singleLine = true,
keyboardOptions = KeyboardOptions( keyboardOptions = KeyboardOptions(
capitalization = KeyboardCapitalization.None, capitalization = KeyboardCapitalization.None,
keyboardType = KeyboardType.Text, keyboardType = KeyboardType.Email,
imeAction = ImeAction.Next imeAction = ImeAction.Next
), ),
onValueChange = onUpdateUsername, onValueChange = onUpdateEmailAddress,
modifier = Modifier.fillMaxWidth() modifier = Modifier.fillMaxWidth()
) )
OutlinedTextField( OutlinedTextField(
value = password.value, value = password.value,
label = { Text("Password") }, label = { Text(text = stringResource(R.string.auth__password)) },
placeholder = { Text("Password") }, placeholder = { Text(text = stringResource(R.string.auth__password)) },
leadingIcon = { Icon(Icons.Outlined.Password, "password") }, leadingIcon = {
Icon(
Icons.Outlined.Password,
stringResource(R.string.auth__password)
)
},
singleLine = true, singleLine = true,
visualTransformation = PasswordVisualTransformation(), visualTransformation = PasswordVisualTransformation(),
keyboardOptions = KeyboardOptions( 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 @Composable
private fun Separator(modifier: Modifier = Modifier) { private fun Separator(modifier: Modifier = Modifier) {
Row(modifier = modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { Row(modifier = modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) {
@ -172,9 +196,9 @@ private fun Separator(modifier: Modifier = Modifier) {
} }
@Composable @Composable
private fun RegisterButton() { private fun RegisterButton(viewModel: LoginScreenViewModel) {
OutlinedButton( OutlinedButton(
onClick = { /*TODO*/ }, onClick = { viewModel.onRegisterButtonClick() },
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
) { ) {

View file

@ -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.datastore.core.DataStore
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
import ing.bikeshedengineer.debtpirate.PrefsDataStore 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.data.remote.repository.AuthRepository
import ing.bikeshedengineer.debtpirate.auth.domain.usecase.LoginCredentialsValidationResult import ing.bikeshedengineer.debtpirate.auth.domain.usecase.LoginCredentialsValidationResult
import ing.bikeshedengineer.debtpirate.auth.domain.usecase.SubmitLoginCredentialsUseCase import ing.bikeshedengineer.debtpirate.auth.domain.usecase.SubmitLoginCredentialsUseCase
import ing.bikeshedengineer.debtpirate.auth.domain.usecase.ValidateLoginCredentialsUseCase import ing.bikeshedengineer.debtpirate.auth.domain.usecase.ValidateLoginCredentialsUseCase
import ing.bikeshedengineer.debtpirate.auth.navigation.AuthNavigationEvent
import ing.bikeshedengineer.debtpirate.domain.model.Token import ing.bikeshedengineer.debtpirate.domain.model.Token
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
@ -34,8 +34,9 @@ enum class InvalidReason {
PasswordTooShort PasswordTooShort
} }
@HiltViewModel @HiltViewModel
class AuthScreenViewModel @Inject constructor( class LoginScreenViewModel @Inject constructor(
private val authRepository: AuthRepository, private val authRepository: AuthRepository,
private val prefsStore: DataStore<PrefsDataStore>, private val prefsStore: DataStore<PrefsDataStore>,
private val submitLoginCredentials: SubmitLoginCredentialsUseCase, private val submitLoginCredentials: SubmitLoginCredentialsUseCase,
@ -43,14 +44,17 @@ class AuthScreenViewModel @Inject constructor(
) : ViewModel() { ) : ViewModel() {
// private val storeLoginData = StoreLoginDataUseCase(dataStore) // private val storeLoginData = StoreLoginDataUseCase(dataStore)
private val _username = MutableStateFlow("") private val _navigationEvent = MutableStateFlow<AuthNavigationEvent?>(null)
val username = _username.asStateFlow() val navigationEvent = _navigationEvent.asStateFlow()
private val _emailAddress = MutableStateFlow("")
val emailAddress = _emailAddress.asStateFlow()
private val _password = MutableStateFlow("") private val _password = MutableStateFlow("")
val password = _password.asStateFlow() val password = _password.asStateFlow()
fun updateUsername(username: String) { fun updateUsername(username: String) {
_username.value = username _emailAddress.value = username
} }
fun updatePassword(password: String) { fun updatePassword(password: String) {
@ -58,16 +62,17 @@ class AuthScreenViewModel @Inject constructor(
} }
fun submitLoginRequest() { fun submitLoginRequest() {
when (validateLoginCredentials(username.value, password.value)) { when (validateLoginCredentials(emailAddress.value, password.value)) {
is LoginCredentialsValidationResult.ValidCredentials -> { is LoginCredentialsValidationResult.ValidCredentials -> {
viewModelScope.launch { viewModelScope.launch {
try { try {
val result = submitLoginCredentials(username.value, password.value) val result = submitLoginCredentials(emailAddress.value, password.value)
} catch (err: Throwable) { } catch (err: Throwable) {
// TODO... // TODO...
} }
} }
} }
else -> { else -> {
// TODO... // TODO...
} }
@ -94,4 +99,8 @@ class AuthScreenViewModel @Inject constructor(
} }
} }
} }
fun onRegisterButtonClick() {
_navigationEvent.value = AuthNavigationEvent.NavigateToRegistrationScreen
}
} }

View file

@ -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)
)
}
}

View file

@ -0,0 +1,4 @@
package ing.bikeshedengineer.debtpirate.auth.presentation.register
class RegistrationScreenViewModel {
}

View file

@ -1,4 +1,5 @@
<resources> <resources>
<string name="app_name">Debt Pirate</string> <string name="app_name">Debt Pirate</string>
<string name="title_activity_main">MainActivity</string> <string name="auth__email">Email</string>
<string name="auth__password">Password</string>
</resources> </resources>