From c8e8854ef24e76393ee1e633e8477299225c29a2 Mon Sep 17 00:00:00 2001 From: "Z. Charles Dziura" Date: Mon, 19 Aug 2024 16:26:50 -0400 Subject: [PATCH] Reworking signup state as flows --- .../makeshift/debtpirate/MainActivity.kt | 9 +- .../{usecases => screens}/login/LoginForm.kt | 20 ++- .../login/LoginScreen.kt | 42 ++++-- .../screens/login/LoginViewModel.kt | 58 ++++++++ .../debtpirate/screens/login/SignUpForm.kt | 134 ++++++++++++++++++ .../usecases/login/LoginViewModel.kt | 36 ----- app/app/src/main/res/values/strings.xml | 4 + 7 files changed, 241 insertions(+), 62 deletions(-) rename app/app/src/main/java/software/makeshift/debtpirate/{usecases => screens}/login/LoginForm.kt (90%) rename app/app/src/main/java/software/makeshift/debtpirate/{usecases => screens}/login/LoginScreen.kt (68%) create mode 100644 app/app/src/main/java/software/makeshift/debtpirate/screens/login/LoginViewModel.kt create mode 100644 app/app/src/main/java/software/makeshift/debtpirate/screens/login/SignUpForm.kt delete mode 100644 app/app/src/main/java/software/makeshift/debtpirate/usecases/login/LoginViewModel.kt diff --git a/app/app/src/main/java/software/makeshift/debtpirate/MainActivity.kt b/app/app/src/main/java/software/makeshift/debtpirate/MainActivity.kt index 6d6af29..ccae70e 100644 --- a/app/app/src/main/java/software/makeshift/debtpirate/MainActivity.kt +++ b/app/app/src/main/java/software/makeshift/debtpirate/MainActivity.kt @@ -4,14 +4,14 @@ import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge -import androidx.activity.viewModels +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable import androidx.navigation.compose.rememberNavController import kotlinx.serialization.Serializable +import software.makeshift.debtpirate.screens.login.LoginScreen +import software.makeshift.debtpirate.screens.login.LoginScreenViewModel import software.makeshift.debtpirate.ui.theme.DebtPirateTheme -import software.makeshift.debtpirate.usecases.login.LoginScreen -import software.makeshift.debtpirate.usecases.login.LoginScreenViewModel @Serializable object LoginRoute @@ -26,8 +26,7 @@ class MainActivity : ComponentActivity() { DebtPirateTheme { NavHost(navController = navController, startDestination = LoginRoute) { composable { - val viewModel: LoginScreenViewModel by viewModels() - LoginScreen(viewModel) + LoginScreen(viewModel = viewModel()) } } } diff --git a/app/app/src/main/java/software/makeshift/debtpirate/usecases/login/LoginForm.kt b/app/app/src/main/java/software/makeshift/debtpirate/screens/login/LoginForm.kt similarity index 90% rename from app/app/src/main/java/software/makeshift/debtpirate/usecases/login/LoginForm.kt rename to app/app/src/main/java/software/makeshift/debtpirate/screens/login/LoginForm.kt index c37e000..5c2204c 100644 --- a/app/app/src/main/java/software/makeshift/debtpirate/usecases/login/LoginForm.kt +++ b/app/app/src/main/java/software/makeshift/debtpirate/screens/login/LoginForm.kt @@ -1,11 +1,12 @@ -package software.makeshift.debtpirate.usecases.login +package software.makeshift.debtpirate.screens.login import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues -import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Lock import androidx.compose.material.icons.outlined.Person @@ -30,20 +31,17 @@ import androidx.compose.ui.unit.dp import software.makeshift.debtpirate.R @Composable -fun LoginForm( +internal fun LoginForm( state: LoginState, onUsernameUpdate: (String) -> Unit, - onPasswordUpdate: (String) -> Unit + onPasswordUpdate: (String) -> Unit, + modifier: Modifier = Modifier, ) { - val (username, password) = state - Column( - modifier = Modifier - .padding(16.dp) - .fillMaxSize() + modifier.verticalScroll(rememberScrollState()) ) { OutlinedTextField( - value = username, + value = state.username, label = { Text(stringResource(id = R.string.login_screen__username)) }, placeholder = { Text(stringResource(id = R.string.login_screen__username)) }, leadingIcon = { Icon(Icons.Outlined.Person, "person") }, @@ -58,7 +56,7 @@ fun LoginForm( ) OutlinedTextField( - value = password, + value = state.password, label = { Text(stringResource(id = R.string.login_screen__password)) }, placeholder = { Text(stringResource(id = R.string.login_screen__password)) }, leadingIcon = { Icon(Icons.Outlined.Lock, "password") }, diff --git a/app/app/src/main/java/software/makeshift/debtpirate/usecases/login/LoginScreen.kt b/app/app/src/main/java/software/makeshift/debtpirate/screens/login/LoginScreen.kt similarity index 68% rename from app/app/src/main/java/software/makeshift/debtpirate/usecases/login/LoginScreen.kt rename to app/app/src/main/java/software/makeshift/debtpirate/screens/login/LoginScreen.kt index 9762a9e..9f9cc25 100644 --- a/app/app/src/main/java/software/makeshift/debtpirate/usecases/login/LoginScreen.kt +++ b/app/app/src/main/java/software/makeshift/debtpirate/screens/login/LoginScreen.kt @@ -1,16 +1,19 @@ @file:OptIn(ExperimentalMaterial3Api::class) -package software.makeshift.debtpirate.usecases.login +package software.makeshift.debtpirate.screens.login 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.ExperimentalLayoutApi import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.imeNestedScroll import androidx.compose.foundation.layout.imePadding +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.pager.HorizontalPager import androidx.compose.foundation.pager.rememberPagerState import androidx.compose.material3.ExperimentalMaterial3Api @@ -19,6 +22,7 @@ import androidx.compose.material3.SecondaryTabRow import androidx.compose.material3.Tab import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -40,9 +44,7 @@ fun LoginScreen( ) LoginAndSignUpForms( - loginState = viewModel.loginState, - onUsernameUpdate = viewModel::updateLoginUsername, - onPasswordUpdate = viewModel::updateLoginPassword, + viewModel, modifier = Modifier .weight(3f) ) @@ -60,15 +62,14 @@ private fun Banner(modifier: Modifier = Modifier) { } } +@OptIn(ExperimentalLayoutApi::class) @Composable private fun LoginAndSignUpForms( - loginState: LoginState, - onUsernameUpdate: (String) -> Unit, - onPasswordUpdate: (String) -> Unit, + viewModel: LoginScreenViewModel, modifier: Modifier = Modifier ) { Column(modifier = modifier) { - val pagerState = rememberPagerState(initialPage = 0) { 2 } + val pagerState = rememberPagerState(initialPage = 1) { 2 } SecondaryTabRow( selectedTabIndex = pagerState.currentPage, @@ -102,13 +103,34 @@ private fun LoginAndSignUpForms( .fillMaxSize() .imePadding() ) { page -> + val formContainerModifier = Modifier + .padding(16.dp) + .fillMaxSize() + .imePadding() + .imeNestedScroll() + when (page) { 0 -> { - LoginForm(state = loginState, onUsernameUpdate, onPasswordUpdate) + val loginState = viewModel.loginState + LoginForm( + state = loginState, + viewModel::updateLoginUsername, + viewModel::updateLoginPassword, + formContainerModifier + ) } 1 -> { - Text("Page 2!") + val signUpState = viewModel.signUpState.collectAsState() + SignUpForm( + state = signUpState, + viewModel::updateFirstName, + viewModel::updateLastName, + viewModel::updateSignUpEmail, + viewModel::updateSignUpPassword, + viewModel::updateSignUpPasswordConfirmed, + formContainerModifier + ) } } } diff --git a/app/app/src/main/java/software/makeshift/debtpirate/screens/login/LoginViewModel.kt b/app/app/src/main/java/software/makeshift/debtpirate/screens/login/LoginViewModel.kt new file mode 100644 index 0000000..e8e7021 --- /dev/null +++ b/app/app/src/main/java/software/makeshift/debtpirate/screens/login/LoginViewModel.kt @@ -0,0 +1,58 @@ +package software.makeshift.debtpirate.screens.login + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow + +data class LoginState(val username: String = "", val password: String = "") + +data class SignUpState( + val firstName: String = "", + val lastName: String = "", + val email: String = "", + val password: String = "", + val passwordConfirmed: String = "" +) + +class LoginScreenViewModel : ViewModel() { + var loginState by mutableStateOf(LoginState()) + private set + + private val _signUpState = MutableStateFlow(SignUpState()) + val signUpState = _signUpState.asStateFlow() + + fun updateLoginUsername(username: String) { + val (_, password) = loginState + loginState = LoginState(username, password) + } + + fun updateLoginPassword(password: String) { + val (username, _) = loginState + loginState = LoginState(username, password) + } + + fun updateFirstName(firstName: String) { + _signUpState.value = _signUpState.value.copy(firstName = firstName) + } + + fun updateLastName(lastName: String) { + _signUpState.value = _signUpState.value.copy(lastName = lastName) + } + + fun updateSignUpEmail(email: String) { + _signUpState.value = _signUpState.value.copy(email = email) + } + + fun updateSignUpPassword(password: String) { + _signUpState.value = _signUpState.value.copy(password = password) + } + + fun updateSignUpPasswordConfirmed(password: String) { + _signUpState.value = _signUpState.value.copy(passwordConfirmed = password) + } + + fun isValidPassword() {} +} \ No newline at end of file diff --git a/app/app/src/main/java/software/makeshift/debtpirate/screens/login/SignUpForm.kt b/app/app/src/main/java/software/makeshift/debtpirate/screens/login/SignUpForm.kt new file mode 100644 index 0000000..33fc6a7 --- /dev/null +++ b/app/app/src/main/java/software/makeshift/debtpirate/screens/login/SignUpForm.kt @@ -0,0 +1,134 @@ +package software.makeshift.debtpirate.screens.login + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.foundation.verticalScroll +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Lock +import androidx.compose.material.icons.outlined.Mail +import androidx.compose.material.icons.outlined.Password +import androidx.compose.material.icons.outlined.Person +import androidx.compose.material3.Button +import androidx.compose.material3.Icon +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.State +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.input.ImeAction +import androidx.compose.ui.text.input.KeyboardCapitalization +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.unit.dp +import software.makeshift.debtpirate.R + +@Composable +internal fun SignUpForm( + state: State, + onFirstNameUpdate: (String) -> Unit, + onLastNameUpdate: (String) -> Unit, + onEmailUpdate: (String) -> Unit, + onPasswordUpdate: (String) -> Unit, + onPasswordConfirmedUpdate: (String) -> Unit, + modifier: Modifier = Modifier +) { + Column(modifier.verticalScroll(rememberScrollState())) + { + OutlinedTextField( + value = state.value.firstName, + label = { Text(stringResource(id = R.string.login_screen__first_name)) }, + placeholder = { Text(stringResource(id = R.string.login_screen__first_name)) }, + leadingIcon = { Icon(Icons.Outlined.Person, "person") }, + singleLine = true, + keyboardOptions = KeyboardOptions( + capitalization = KeyboardCapitalization.Words, + keyboardType = KeyboardType.Text, + imeAction = ImeAction.Next + ), + onValueChange = onFirstNameUpdate, + modifier = Modifier.fillMaxWidth() + ) + + OutlinedTextField( + value = state.value.lastName, + label = { Text(stringResource(id = R.string.login_screen__last_name)) }, + placeholder = { Text(stringResource(id = R.string.login_screen__last_name)) }, + leadingIcon = { Icon(Icons.Outlined.Person, "person") }, + singleLine = true, + keyboardOptions = KeyboardOptions( + capitalization = KeyboardCapitalization.Words, + keyboardType = KeyboardType.Text, + imeAction = ImeAction.Next + ), + onValueChange = onLastNameUpdate, + modifier = Modifier.fillMaxWidth() + ) + + OutlinedTextField( + value = state.value.email, + label = { Text(stringResource(id = R.string.login_screen__email)) }, + placeholder = { Text(stringResource(id = R.string.login_screen__email)) }, + leadingIcon = { Icon(Icons.Outlined.Mail, "email") }, + singleLine = true, + keyboardOptions = KeyboardOptions( + capitalization = KeyboardCapitalization.Unspecified, + keyboardType = KeyboardType.Email, + imeAction = ImeAction.Next + ), + onValueChange = onEmailUpdate, + modifier = Modifier + .fillMaxWidth() + .padding(PaddingValues(top = 8.dp)) + ) + + OutlinedTextField( + value = state.value.password, + label = { Text(stringResource(id = R.string.login_screen__password)) }, + placeholder = { Text(stringResource(id = R.string.login_screen__password)) }, + leadingIcon = { Icon(Icons.Outlined.Lock, "password") }, + singleLine = true, + keyboardOptions = KeyboardOptions( + capitalization = KeyboardCapitalization.Unspecified, + keyboardType = KeyboardType.Password, + imeAction = ImeAction.Next + ), + visualTransformation = PasswordVisualTransformation(), + onValueChange = onPasswordUpdate, + modifier = Modifier + .fillMaxWidth() + .padding(PaddingValues(top = 8.dp)) + ) + + OutlinedTextField( + value = state.value.passwordConfirmed, + label = { Text(stringResource(id = R.string.login_screen__confirm_password)) }, + placeholder = { Text(stringResource(id = R.string.login_screen__confirm_password)) }, + leadingIcon = { Icon(Icons.Outlined.Password, "confirm password") }, + singleLine = true, + keyboardOptions = KeyboardOptions( + capitalization = KeyboardCapitalization.Unspecified, + keyboardType = KeyboardType.Password, + imeAction = ImeAction.Send + ), + visualTransformation = PasswordVisualTransformation(), + onValueChange = onPasswordConfirmedUpdate, + modifier = Modifier + .fillMaxWidth() + .padding(PaddingValues(top = 8.dp)) + ) + + Button( + onClick = { /*TODO*/ }, + modifier = Modifier + .padding(PaddingValues(top = 32.dp)) + .fillMaxWidth() + ) { + Text(stringResource(id = R.string.login_screen__signup)) + } + } +} \ No newline at end of file diff --git a/app/app/src/main/java/software/makeshift/debtpirate/usecases/login/LoginViewModel.kt b/app/app/src/main/java/software/makeshift/debtpirate/usecases/login/LoginViewModel.kt deleted file mode 100644 index f6ec6d4..0000000 --- a/app/app/src/main/java/software/makeshift/debtpirate/usecases/login/LoginViewModel.kt +++ /dev/null @@ -1,36 +0,0 @@ -package software.makeshift.debtpirate.usecases.login - -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import androidx.lifecycle.ViewModel - -data class LoginState(val username: String = "", val password: String = "") - -data class SignUpState( - val username: String? = null, - val password: String? = null, - val passwordConfirmed: String? = null, - val email: String? = null -) - -class LoginScreenViewModel : ViewModel() { - var loginState by mutableStateOf(LoginState()) - private set - - val loginUsername: String - get() = loginState.username - - fun updateLoginUsername(username: String) { - val (_, password) = loginState - loginState = LoginState(username, password) - } - - val loginPassword: String - get() = loginState.password - - fun updateLoginPassword(password: String) { - val (username, _) = loginState - loginState = LoginState(username, password) - } -} \ 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 b5e28f5..f0cdae3 100644 --- a/app/app/src/main/res/values/strings.xml +++ b/app/app/src/main/res/values/strings.xml @@ -5,7 +5,11 @@ Login Sign Up Username + Email Address Password + Confirm Password + First Name + Last Name Forgot Password? \ No newline at end of file