diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 9c8b76e..483ad25 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -50,6 +50,8 @@ dependencies { implementation(libs.androidx.compose.ui.tooling.preview) implementation(libs.androidx.compose.material3) implementation(libs.androidx.compose.foundation.layout) + implementation(libs.androidx.navigation.compose) + implementation(libs.androidx.lifecycle.viewmodel.compose) testImplementation(libs.junit) androidTestImplementation(libs.androidx.junit) androidTestImplementation(libs.androidx.espresso.core) diff --git a/app/src/main/java/com/example/mvvm/MainActivity.kt b/app/src/main/java/com/example/mvvm/MainActivity.kt index 6196f47..77aef49 100644 --- a/app/src/main/java/com/example/mvvm/MainActivity.kt +++ b/app/src/main/java/com/example/mvvm/MainActivity.kt @@ -13,8 +13,10 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview +import androidx.navigation.compose.rememberNavController import com.example.mvvm.ui.login.ui.LoginScreen import com.example.mvvm.ui.login.ui.LoginViewModel +import com.example.mvvm.ui.navigation.AuthNavGraph import com.example.mvvm.ui.theme.MVVMTheme class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { @@ -26,7 +28,8 @@ class MainActivity : ComponentActivity() { modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { - LoginScreen(LoginViewModel()) + val navController = rememberNavController() + AuthNavGraph(navController = navController) } } } diff --git a/app/src/main/java/com/example/mvvm/ui/forgotPassword/ForgotPasswordScreen.kt b/app/src/main/java/com/example/mvvm/ui/forgotPassword/ForgotPasswordScreen.kt new file mode 100644 index 0000000..8788396 --- /dev/null +++ b/app/src/main/java/com/example/mvvm/ui/forgotPassword/ForgotPasswordScreen.kt @@ -0,0 +1,17 @@ +package com.example.mvvm.ui.forgotPassword + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color + +@Composable +fun ForgotPasswordScreen() { + Box( + modifier = Modifier + .fillMaxSize() + .background(Color.Red), + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mvvm/ui/login/ui/LoginDestination.kt b/app/src/main/java/com/example/mvvm/ui/login/ui/LoginDestination.kt new file mode 100644 index 0000000..e392cc9 --- /dev/null +++ b/app/src/main/java/com/example/mvvm/ui/login/ui/LoginDestination.kt @@ -0,0 +1,40 @@ +package com.example.mvvm.ui.login.ui + +import androidx.compose.foundation.Image +import androidx.compose.foundation.clickable +import com.example.mvvm.R +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.material3.Text +import androidx.compose.material3.TextField +import androidx.compose.material3.TextFieldDefaults +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.input.PasswordVisualTransformation +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import kotlinx.coroutines.launch + +sealed interface LoginDestination { + data object ForgotPassword : LoginDestination +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mvvm/ui/login/ui/loginScreen.kt b/app/src/main/java/com/example/mvvm/ui/login/ui/loginScreen.kt index 8929e4d..c93f3c6 100644 --- a/app/src/main/java/com/example/mvvm/ui/login/ui/loginScreen.kt +++ b/app/src/main/java/com/example/mvvm/ui/login/ui/loginScreen.kt @@ -16,6 +16,7 @@ import androidx.compose.material3.Button import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.Text +import androidx.compose.material3.TextButton import androidx.compose.material3.TextField import androidx.compose.material3.TextFieldDefaults import androidx.compose.runtime.Composable @@ -76,7 +77,12 @@ fun Login( Spacer(modifier = Modifier.height(8.dp)) Password(password) { viewModel.onLoginChanged(email, it) } Spacer(modifier = Modifier.padding((8.dp))) - ForgotPassword(Modifier.align(Alignment.End)) // column organiza a lo que hay debajo, la propiedad align() de los hijos de una Column espera un Alignment.Horizontal, no un Alignment completo (que es bidimensional). + ForgotPassword( + modifier = Modifier.align(Alignment.End), + onClick = { + viewModel.onClickedForgotPassword() + } + ) // column organiza a lo que hay debajo, la propiedad align() de los hijos de una Column espera un Alignment.Horizontal, no un Alignment completo (que es bidimensional). Spacer(modifier = Modifier.padding((16.dp))) LoginButton(enabled ) { coroutine.launch { @@ -92,18 +98,23 @@ fun Login( } @Composable -fun ForgotPassword(modifier: Modifier) { - Text( - text = "olvidaste la contraseƱa?", - modifier = modifier.clickable {}, - fontSize = 12.sp, - fontWeight = FontWeight.Bold, //Negritas - color = Color( - 0xFF0A70C0 - ), - - ) - +fun ForgotPassword( + modifier: Modifier, + onClick: () -> Unit, +) { + TextButton( + modifier = modifier, + onClick = onClick, + ) { + Text( + text = "olvidaste la contraseƱa?", + fontSize = 12.sp, + fontWeight = FontWeight.Bold, //Negritas + color = Color( + 0xFF0A70C0 + ), + ) + } } @Composable diff --git a/app/src/main/java/com/example/mvvm/ui/login/ui/loginViewModel.kt b/app/src/main/java/com/example/mvvm/ui/login/ui/loginViewModel.kt index e7e9451..46e2a7d 100644 --- a/app/src/main/java/com/example/mvvm/ui/login/ui/loginViewModel.kt +++ b/app/src/main/java/com/example/mvvm/ui/login/ui/loginViewModel.kt @@ -2,12 +2,20 @@ package com.example.mvvm.ui.login.ui import android.util.Patterns import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.compose.viewModel +import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.receiveAsFlow +import kotlinx.coroutines.launch class LoginViewModel : ViewModel() { + private val _destinations = Channel(Channel.BUFFERED) + val destinations = _destinations.receiveAsFlow() + private val _email = MutableStateFlow("") // crea un flujo mutable val email = _email.asStateFlow() //inmutable creamos ese puente para que la UI pueda verlo private val _password = MutableStateFlow("") @@ -17,6 +25,12 @@ class LoginViewModel : ViewModel() { private val _loading = MutableStateFlow(false) val loading = _loading.asStateFlow() + fun onClickedForgotPassword() { + viewModelScope.launch { + _destinations.send(element = LoginDestination.ForgotPassword) + } + } + fun onLoginChanged( newEmail: String, newPassword: String diff --git a/app/src/main/java/com/example/mvvm/ui/navigation/AuthNavGraph.kt b/app/src/main/java/com/example/mvvm/ui/navigation/AuthNavGraph.kt new file mode 100644 index 0000000..b3a0c9a --- /dev/null +++ b/app/src/main/java/com/example/mvvm/ui/navigation/AuthNavGraph.kt @@ -0,0 +1,50 @@ +package com.example.mvvm.ui.navigation + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavController +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import com.example.mvvm.ui.forgotPassword.ForgotPasswordScreen +import com.example.mvvm.ui.login.ui.ForgotPassword +import com.example.mvvm.ui.login.ui.LoginDestination +import com.example.mvvm.ui.login.ui.LoginScreen +import com.example.mvvm.ui.login.ui.LoginViewModel +import com.example.mvvm.ui.registration.RegistrationScreen + +@Composable +internal fun AuthNavGraph( + navController: NavHostController, +) { + NavHost( + navController = navController, + startDestination = AuthScreen.Login.route, + ) { + composable(route = AuthScreen.Login.route) { backStackEntry -> + val viewModel: LoginViewModel = viewModel(viewModelStoreOwner = backStackEntry) + + LoginScreen( + viewModel = viewModel, + ) + + LaunchedEffect(viewModel) { + viewModel.destinations.collect { event -> + when (event) { + LoginDestination.ForgotPassword -> + navController.navigate(route = AuthScreen.ForgotPassword.route) + } + } + } + + } + composable(route = AuthScreen.ForgotPassword.route) { backStackEntry -> + ForgotPasswordScreen() + } + composable(route = AuthScreen.Registration.route) { backStackEntry -> + RegistrationScreen() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mvvm/ui/navigation/AuthScreen.kt b/app/src/main/java/com/example/mvvm/ui/navigation/AuthScreen.kt new file mode 100644 index 0000000..f76bbfb --- /dev/null +++ b/app/src/main/java/com/example/mvvm/ui/navigation/AuthScreen.kt @@ -0,0 +1,20 @@ +package com.example.mvvm.ui.navigation + +sealed interface AuthScreen { + val route: String + + data object ForgotPassword : AuthScreen { + override val route: String + get() = "forgot_password" + } + + data object Login : AuthScreen { + override val route: String + get() = "login" + } + + data object Registration : AuthScreen { + override val route: String + get() = "registration" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/mvvm/ui/registration/RegistrationScreen.kt b/app/src/main/java/com/example/mvvm/ui/registration/RegistrationScreen.kt new file mode 100644 index 0000000..b6ed877 --- /dev/null +++ b/app/src/main/java/com/example/mvvm/ui/registration/RegistrationScreen.kt @@ -0,0 +1,17 @@ +package com.example.mvvm.ui.registration + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color + +@Composable +internal fun RegistrationScreen() { + Box( + modifier = Modifier + .fillMaxSize() + .background(Color.Blue), + ) +} \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 18cd69b..ba3018f 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -6,8 +6,10 @@ junit = "4.13.2" junitVersion = "1.1.5" espressoCore = "3.5.1" lifecycleRuntimeKtx = "2.6.1" +lifecycleViewModelCompose = "2.6.1" activityCompose = "1.8.0" composeBom = "2024.09.00" +navigationCompose = "2.7.3" foundationLayout = "1.9.4" [libraries] @@ -16,6 +18,7 @@ junit = { group = "junit", name = "junit", version.ref = "junit" } androidx-junit = { group = "androidx.test.ext", name = "junit", version.ref = "junitVersion" } androidx-espresso-core = { group = "androidx.test.espresso", name = "espresso-core", version.ref = "espressoCore" } androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } +androidx-lifecycle-viewmodel-compose = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-compose", version.ref = "lifecycleViewModelCompose" } androidx-activity-compose = { group = "androidx.activity", name = "activity-compose", version.ref = "activityCompose" } androidx-compose-bom = { group = "androidx.compose", name = "compose-bom", version.ref = "composeBom" } androidx-compose-ui = { group = "androidx.compose.ui", name = "ui" } @@ -26,6 +29,7 @@ androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui- androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-compose-foundation-layout = { group = "androidx.compose.foundation", name = "foundation-layout", version.ref = "foundationLayout" } +androidx-navigation-compose = { group = "androidx.navigation", name = "navigation-compose", version.ref = "navigationCompose" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" }