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 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<AuthRoute>() { AuthScreen() }
NavHost(navController = navController, startDestination = LoginRoute) {
composable<LoginRoute>() { LoginScreen(navController = navController) }
composable<RegistrationRoute>() { RegistrationScreen() }
}
}
}

View file

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

View file

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

View file

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

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 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<AuthScreenViewModel>()
fun LoginScreen(
navController: NavController,
viewModel: LoginScreenViewModel = hiltViewModel<LoginScreenViewModel>()
) {
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<String>,
onUpdateUsername: (String) -> Unit,
emailAddress: State<String>,
onUpdateEmailAddress: (String) -> Unit,
password: State<String>,
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()
) {

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.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<PrefsDataStore>,
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<AuthNavigationEvent?>(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
}
}

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>
<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>