Start to split up viewmodels for login and sign up
This commit is contained in:
parent
c8e8854ef2
commit
ffd58cac7a
9 changed files with 128 additions and 75 deletions
|
@ -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)
|
||||||
|
|
|
@ -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>()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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") },
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {}
|
|
||||||
}
|
|
|
@ -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(
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -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" }
|
||||||
|
|
Loading…
Add table
Reference in a new issue