Reworking signup state as flows

This commit is contained in:
Z. Charles Dziura 2024-08-19 16:26:50 -04:00
parent 66bdb88b05
commit c8e8854ef2
7 changed files with 241 additions and 62 deletions

View file

@ -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<LoginRoute> {
val viewModel: LoginScreenViewModel by viewModels()
LoginScreen(viewModel)
LoginScreen(viewModel = viewModel<LoginScreenViewModel>())
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -5,7 +5,11 @@
<string name="login_screen__login">Login</string>
<string name="login_screen__signup">Sign Up</string>
<string name="login_screen__username">Username</string>
<string name="login_screen__email">Email Address</string>
<string name="login_screen__password">Password</string>
<string name="login_screen__confirm_password">Confirm Password</string>
<string name="login_screen__first_name">First Name</string>
<string name="login_screen__last_name">Last Name</string>
<string name="login_screen__forgot_password">Forgot Password?</string>
</resources>