Start to split up viewmodels for login and sign up

This commit is contained in:
Z. Charles Dziura 2024-08-19 22:16:55 -04:00
parent c8e8854ef2
commit ffd58cac7a
9 changed files with 128 additions and 75 deletions

View file

@ -67,6 +67,7 @@ dependencies {
implementation(libs.androidx.lifecycle.viewmodel.compose) implementation(libs.androidx.lifecycle.viewmodel.compose)
implementation(libs.lifecycle.runtime.compose) implementation(libs.lifecycle.runtime.compose)
implementation(libs.androidx.material.icons.extended) implementation(libs.androidx.material.icons.extended)
implementation(libs.lifecycle.viewmodel.ktx)
testImplementation(libs.junit) testImplementation(libs.junit)
androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core) androidTestImplementation(libs.androidx.espresso.core)

View file

@ -9,8 +9,9 @@ import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController import androidx.navigation.compose.rememberNavController
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import software.makeshift.debtpirate.screens.login.`LoginFormViewModel.kt`
import software.makeshift.debtpirate.screens.login.LoginScreen import software.makeshift.debtpirate.screens.login.LoginScreen
import software.makeshift.debtpirate.screens.login.LoginScreenViewModel import software.makeshift.debtpirate.screens.login.SignUpFormViewModel
import software.makeshift.debtpirate.ui.theme.DebtPirateTheme import software.makeshift.debtpirate.ui.theme.DebtPirateTheme
@Serializable @Serializable
@ -26,7 +27,10 @@ class MainActivity : ComponentActivity() {
DebtPirateTheme { DebtPirateTheme {
NavHost(navController = navController, startDestination = LoginRoute) { NavHost(navController = navController, startDestination = LoginRoute) {
composable<LoginRoute> { composable<LoginRoute> {
LoginScreen(viewModel = viewModel<LoginScreenViewModel>()) LoginScreen(
`loginFormViewModel.kt` = viewModel<`LoginFormViewModel.kt`>(),
viewModel = viewModel<SignUpFormViewModel>()
)
} }
} }
} }

View file

@ -1,5 +1,8 @@
package software.makeshift.debtpirate.screens.login package software.makeshift.debtpirate.screens.login
import android.util.Log
import androidx.compose.foundation.interaction.FocusInteraction
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
@ -15,6 +18,9 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
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.collectAsState
import androidx.compose.runtime.remember
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.res.stringResource
@ -28,11 +34,13 @@ import androidx.compose.ui.text.input.KeyboardType
import androidx.compose.ui.text.input.PasswordVisualTransformation import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.withLink import androidx.compose.ui.text.withLink
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import kotlinx.coroutines.flow.StateFlow
import software.makeshift.debtpirate.R import software.makeshift.debtpirate.R
@Composable @Composable
internal fun LoginForm( internal fun LoginForm(
state: LoginState, usernameState: StateFlow<String>,
passwordState: StateFlow<String>,
onUsernameUpdate: (String) -> Unit, onUsernameUpdate: (String) -> Unit,
onPasswordUpdate: (String) -> Unit, onPasswordUpdate: (String) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@ -40,8 +48,9 @@ internal fun LoginForm(
Column( Column(
modifier.verticalScroll(rememberScrollState()) modifier.verticalScroll(rememberScrollState())
) { ) {
val username = usernameState.collectAsState()
OutlinedTextField( OutlinedTextField(
value = state.username, value = username.value,
label = { Text(stringResource(id = R.string.login_screen__username)) }, label = { Text(stringResource(id = R.string.login_screen__username)) },
placeholder = { Text(stringResource(id = R.string.login_screen__username)) }, placeholder = { Text(stringResource(id = R.string.login_screen__username)) },
leadingIcon = { Icon(Icons.Outlined.Person, "person") }, leadingIcon = { Icon(Icons.Outlined.Person, "person") },
@ -52,11 +61,25 @@ internal fun LoginForm(
imeAction = ImeAction.Next imeAction = ImeAction.Next
), ),
onValueChange = onUsernameUpdate, onValueChange = onUsernameUpdate,
modifier = Modifier.fillMaxWidth() interactionSource = remember { MutableInteractionSource() }.also { source ->
LaunchedEffect(source) {
source.interactions.collect { interaction ->
when (interaction) {
is FocusInteraction -> Log.v(
"LoginScreen::LoginForm::usernameTextField",
"Clicked!"
)
}
}
}
},
modifier = Modifier
.fillMaxWidth()
) )
val password = passwordState.collectAsState()
OutlinedTextField( OutlinedTextField(
value = state.password, value = password.value,
label = { Text(stringResource(id = R.string.login_screen__password)) }, label = { Text(stringResource(id = R.string.login_screen__password)) },
placeholder = { Text(stringResource(id = R.string.login_screen__password)) }, placeholder = { Text(stringResource(id = R.string.login_screen__password)) },
leadingIcon = { Icon(Icons.Outlined.Lock, "password") }, leadingIcon = { Icon(Icons.Outlined.Lock, "password") },

View file

@ -0,0 +1,21 @@
package software.makeshift.debtpirate.screens.login
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
class LoginFormViewModel : ViewModel() {
private val _username = MutableStateFlow("")
val username = _username.asStateFlow()
private val _password = MutableStateFlow("")
val password = _password.asStateFlow()
fun updateUsername(username: String) {
_username.value = username
}
fun updateLoginPassword(password: String) {
_password.value = password
}
}

View file

@ -33,7 +33,8 @@ import software.makeshift.debtpirate.R
@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter")
@Composable @Composable
fun LoginScreen( fun LoginScreen(
viewModel: LoginScreenViewModel `loginFormViewModel.kt`: `LoginFormViewModel.kt`,
viewModel: SignUpFormViewModel
) { ) {
Scaffold(modifier = Modifier.fillMaxSize()) { Scaffold(modifier = Modifier.fillMaxSize()) {
Column { Column {
@ -44,6 +45,7 @@ fun LoginScreen(
) )
LoginAndSignUpForms( LoginAndSignUpForms(
`loginFormViewModel.kt`,
viewModel, viewModel,
modifier = Modifier modifier = Modifier
.weight(3f) .weight(3f)
@ -65,11 +67,12 @@ private fun Banner(modifier: Modifier = Modifier) {
@OptIn(ExperimentalLayoutApi::class) @OptIn(ExperimentalLayoutApi::class)
@Composable @Composable
private fun LoginAndSignUpForms( private fun LoginAndSignUpForms(
viewModel: LoginScreenViewModel, `loginFormViewModel.kt`: `LoginFormViewModel.kt`,
viewModel: SignUpFormViewModel,
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
Column(modifier = modifier) { Column(modifier = modifier) {
val pagerState = rememberPagerState(initialPage = 1) { 2 } val pagerState = rememberPagerState(initialPage = 0) { 2 }
SecondaryTabRow( SecondaryTabRow(
selectedTabIndex = pagerState.currentPage, selectedTabIndex = pagerState.currentPage,
@ -111,11 +114,11 @@ private fun LoginAndSignUpForms(
when (page) { when (page) {
0 -> { 0 -> {
val loginState = viewModel.loginState
LoginForm( LoginForm(
state = loginState, `loginFormViewModel.kt`.username,
viewModel::updateLoginUsername, `loginFormViewModel.kt`.password,
viewModel::updateLoginPassword, `loginFormViewModel.kt`::updateUsername,
`loginFormViewModel.kt`::updateLoginPassword,
formContainerModifier formContainerModifier
) )
} }
@ -126,9 +129,9 @@ private fun LoginAndSignUpForms(
state = signUpState, state = signUpState,
viewModel::updateFirstName, viewModel::updateFirstName,
viewModel::updateLastName, viewModel::updateLastName,
viewModel::updateSignUpEmail, viewModel::updateEmailAddress,
viewModel::updateSignUpPassword, viewModel::updatePassword,
viewModel::updateSignUpPasswordConfirmed, viewModel::updateConfirmPassword,
formContainerModifier formContainerModifier
) )
} }

View file

@ -1,58 +0,0 @@
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

@ -66,7 +66,9 @@ internal fun SignUpForm(
imeAction = ImeAction.Next imeAction = ImeAction.Next
), ),
onValueChange = onLastNameUpdate, onValueChange = onLastNameUpdate,
modifier = Modifier.fillMaxWidth() modifier = Modifier
.fillMaxWidth()
.padding(PaddingValues(top = 8.dp))
) )
OutlinedTextField( OutlinedTextField(

View file

@ -0,0 +1,55 @@
package software.makeshift.debtpirate.screens.login
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
data class SignUpState(
val firstName: String = "",
val lastName: String = "",
val email: String = "",
val password: String = "",
val passwordConfirmed: String = ""
)
class SignUpFormViewModel : ViewModel() {
private val _firstName = MutableStateFlow("")
val firstName = _firstName.asStateFlow()
private val _lastName = MutableStateFlow("")
val lastName = _lastName.asStateFlow()
private val _emailAddress = MutableStateFlow("")
val emailAddress = _emailAddress.asStateFlow()
private val _password = MutableStateFlow("")
val password = _password.asStateFlow()
private val _confirmPassword = MutableStateFlow("")
val confirmPassword = _confirmPassword.asStateFlow()
fun updateFirstName(firstName: String) {
_firstName.value = firstName
}
fun updateLastName(lastName: String) {
_lastName.value = lastName
}
fun updateEmailAddress(emailAddress: String) {
_emailAddress.value = emailAddress
}
fun updatePassword(password: String) {
_password.value = password
}
fun updateConfirmPassword(confirmPassword: String) {
_confirmPassword.value = confirmPassword
}
fun isValidPassword() {
}
}

View file

@ -16,6 +16,7 @@ activityCompose = "1.9.1"
composeBom = "2024.06.00" composeBom = "2024.06.00"
navigation = "2.8.0-beta01" navigation = "2.8.0-beta01"
iconsExtended = "1.7.0-beta07" iconsExtended = "1.7.0-beta07"
lifecycleViewModelKtx = "2.8.4"
[libraries] [libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@ -40,6 +41,7 @@ androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit
androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" } androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigation" }
androidx-navigation-testing = { group = "androidx.navigation", name = "navigation-testing", version.ref = "navigation" } androidx-navigation-testing = { group = "androidx.navigation", name = "navigation-testing", version.ref = "navigation" }
androidx-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended-android", version.ref = "iconsExtended" } androidx-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended-android", version.ref = "iconsExtended" }
lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycleViewModelKtx" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }