diff --git a/app/app/build.gradle.kts b/app/app/build.gradle.kts index 7ccecd9..d6e209f 100644 --- a/app/app/build.gradle.kts +++ b/app/app/build.gradle.kts @@ -67,6 +67,7 @@ dependencies { implementation(libs.androidx.lifecycle.viewmodel.compose) implementation(libs.lifecycle.runtime.compose) implementation(libs.androidx.material.icons.extended) + implementation(libs.lifecycle.viewmodel.ktx) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) 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 ccae70e..9de4bc6 100644 --- a/app/app/src/main/java/software/makeshift/debtpirate/MainActivity.kt +++ b/app/app/src/main/java/software/makeshift/debtpirate/MainActivity.kt @@ -9,8 +9,9 @@ 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.`LoginFormViewModel.kt` 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 @Serializable @@ -26,7 +27,10 @@ class MainActivity : ComponentActivity() { DebtPirateTheme { NavHost(navController = navController, startDestination = LoginRoute) { composable { - LoginScreen(viewModel = viewModel()) + LoginScreen( + `loginFormViewModel.kt` = viewModel<`LoginFormViewModel.kt`>(), + viewModel = viewModel() + ) } } } diff --git a/app/app/src/main/java/software/makeshift/debtpirate/screens/login/LoginForm.kt b/app/app/src/main/java/software/makeshift/debtpirate/screens/login/LoginForm.kt index 5c2204c..9e5a266 100644 --- a/app/app/src/main/java/software/makeshift/debtpirate/screens/login/LoginForm.kt +++ b/app/app/src/main/java/software/makeshift/debtpirate/screens/login/LoginForm.kt @@ -1,5 +1,8 @@ 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.PaddingValues import androidx.compose.foundation.layout.fillMaxWidth @@ -15,6 +18,9 @@ import androidx.compose.material3.Icon import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.Text 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.graphics.Color 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.withLink import androidx.compose.ui.unit.dp +import kotlinx.coroutines.flow.StateFlow import software.makeshift.debtpirate.R @Composable internal fun LoginForm( - state: LoginState, + usernameState: StateFlow, + passwordState: StateFlow, onUsernameUpdate: (String) -> Unit, onPasswordUpdate: (String) -> Unit, modifier: Modifier = Modifier, @@ -40,8 +48,9 @@ internal fun LoginForm( Column( modifier.verticalScroll(rememberScrollState()) ) { + val username = usernameState.collectAsState() OutlinedTextField( - value = state.username, + value = username.value, label = { Text(stringResource(id = R.string.login_screen__username)) }, placeholder = { Text(stringResource(id = R.string.login_screen__username)) }, leadingIcon = { Icon(Icons.Outlined.Person, "person") }, @@ -52,11 +61,25 @@ internal fun LoginForm( imeAction = ImeAction.Next ), 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( - value = state.password, + value = password.value, 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/screens/login/LoginFormViewModel.kt b/app/app/src/main/java/software/makeshift/debtpirate/screens/login/LoginFormViewModel.kt new file mode 100644 index 0000000..1412e74 --- /dev/null +++ b/app/app/src/main/java/software/makeshift/debtpirate/screens/login/LoginFormViewModel.kt @@ -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 + } +} \ No newline at end of file diff --git a/app/app/src/main/java/software/makeshift/debtpirate/screens/login/LoginScreen.kt b/app/app/src/main/java/software/makeshift/debtpirate/screens/login/LoginScreen.kt index 9f9cc25..9c884d6 100644 --- a/app/app/src/main/java/software/makeshift/debtpirate/screens/login/LoginScreen.kt +++ b/app/app/src/main/java/software/makeshift/debtpirate/screens/login/LoginScreen.kt @@ -33,7 +33,8 @@ import software.makeshift.debtpirate.R @SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") @Composable fun LoginScreen( - viewModel: LoginScreenViewModel + `loginFormViewModel.kt`: `LoginFormViewModel.kt`, + viewModel: SignUpFormViewModel ) { Scaffold(modifier = Modifier.fillMaxSize()) { Column { @@ -44,6 +45,7 @@ fun LoginScreen( ) LoginAndSignUpForms( + `loginFormViewModel.kt`, viewModel, modifier = Modifier .weight(3f) @@ -65,11 +67,12 @@ private fun Banner(modifier: Modifier = Modifier) { @OptIn(ExperimentalLayoutApi::class) @Composable private fun LoginAndSignUpForms( - viewModel: LoginScreenViewModel, + `loginFormViewModel.kt`: `LoginFormViewModel.kt`, + viewModel: SignUpFormViewModel, modifier: Modifier = Modifier ) { Column(modifier = modifier) { - val pagerState = rememberPagerState(initialPage = 1) { 2 } + val pagerState = rememberPagerState(initialPage = 0) { 2 } SecondaryTabRow( selectedTabIndex = pagerState.currentPage, @@ -111,11 +114,11 @@ private fun LoginAndSignUpForms( when (page) { 0 -> { - val loginState = viewModel.loginState LoginForm( - state = loginState, - viewModel::updateLoginUsername, - viewModel::updateLoginPassword, + `loginFormViewModel.kt`.username, + `loginFormViewModel.kt`.password, + `loginFormViewModel.kt`::updateUsername, + `loginFormViewModel.kt`::updateLoginPassword, formContainerModifier ) } @@ -126,9 +129,9 @@ private fun LoginAndSignUpForms( state = signUpState, viewModel::updateFirstName, viewModel::updateLastName, - viewModel::updateSignUpEmail, - viewModel::updateSignUpPassword, - viewModel::updateSignUpPasswordConfirmed, + viewModel::updateEmailAddress, + viewModel::updatePassword, + viewModel::updateConfirmPassword, 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 deleted file mode 100644 index e8e7021..0000000 --- a/app/app/src/main/java/software/makeshift/debtpirate/screens/login/LoginViewModel.kt +++ /dev/null @@ -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() {} -} \ 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 index 33fc6a7..9765910 100644 --- 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 @@ -66,7 +66,9 @@ internal fun SignUpForm( imeAction = ImeAction.Next ), onValueChange = onLastNameUpdate, - modifier = Modifier.fillMaxWidth() + modifier = Modifier + .fillMaxWidth() + .padding(PaddingValues(top = 8.dp)) ) OutlinedTextField( diff --git a/app/app/src/main/java/software/makeshift/debtpirate/screens/login/SignUpFormViewModel.kt b/app/app/src/main/java/software/makeshift/debtpirate/screens/login/SignUpFormViewModel.kt new file mode 100644 index 0000000..a37606f --- /dev/null +++ b/app/app/src/main/java/software/makeshift/debtpirate/screens/login/SignUpFormViewModel.kt @@ -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() { + + } +} \ No newline at end of file diff --git a/app/gradle/libs.versions.toml b/app/gradle/libs.versions.toml index e3a02ce..c438642 100644 --- a/app/gradle/libs.versions.toml +++ b/app/gradle/libs.versions.toml @@ -16,6 +16,7 @@ activityCompose = "1.9.1" composeBom = "2024.06.00" navigation = "2.8.0-beta01" iconsExtended = "1.7.0-beta07" +lifecycleViewModelKtx = "2.8.4" [libraries] 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-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" } +lifecycle-viewmodel-ktx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "lifecycleViewModelKtx" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" }