Compare commits

...
2 Commits
Author SHA1 Message Date
pedro f6db58f299 Comentar codigo aplicacion 2026-07-13 13:48:45 +02:00
pedro 25d6ef6cdb Cambiar la pantalla de login de despues de iniciar sesion 2026-07-13 13:41:20 +02:00
7 changed files with 194 additions and 21 deletions
+1
View File
@@ -41,6 +41,7 @@ Saludapp es una aplicación Android moderna enfocada en la salud y el bienestar,
- `StartScreen.kt`: Pantalla de bienvenida. - `StartScreen.kt`: Pantalla de bienvenida.
- `LoginScreen.kt`: Inicio de sesión con validación de credenciales encriptadas. - `LoginScreen.kt`: Inicio de sesión con validación de credenciales encriptadas.
- `RegisterScreen.kt`: Registro de nuevos usuarios con soporte para nombre completo. - `RegisterScreen.kt`: Registro de nuevos usuarios con soporte para nombre completo.
- `HomeScreen.kt`: Dashboard principal con resumen de salud (pasos, sueño, agua, etc.).
- `theme/`: Configuración estética de Material 3. - `theme/`: Configuración estética de Material 3.
--- ---
+1
View File
@@ -42,6 +42,7 @@ dependencies {
implementation(platform(libs.androidx.compose.bom)) implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.activity.compose) implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.material3) implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.material.icons.extended)
implementation(libs.androidx.compose.ui) implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics) implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview) implementation(libs.androidx.compose.ui.tooling.preview)
@@ -4,48 +4,55 @@ import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import com.example.saludapp.ui.HomeScreen
import androidx.compose.ui.Modifier
import com.example.saludapp.ui.LoginScreen import com.example.saludapp.ui.LoginScreen
import com.example.saludapp.ui.RegisterScreen import com.example.saludapp.ui.RegisterScreen
import com.example.saludapp.ui.StartScreen import com.example.saludapp.ui.StartScreen
import com.example.saludapp.ui.theme.SaludappTheme import com.example.saludapp.ui.theme.SaludappTheme
/**
* Actividad principal de la aplicación.
* Gestiona el flujo de navegación entre pantallas y el estado de la sesión.
*/
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// Habilitar diseño de borde a borde (edge-to-edge)
enableEdgeToEdge() enableEdgeToEdge()
setContent { setContent {
SaludappTheme { SaludappTheme {
// Estado para controlar en qué pantalla se encuentra el usuario
var currentScreen by remember { mutableStateOf("start") } var currentScreen by remember { mutableStateOf("start") }
// Almacena el nombre del usuario logueado para mostrarlo en el Dashboard
var loggedInUserName by remember { mutableStateOf("") }
// Enrutador simple basado en el estado 'currentScreen'
when (currentScreen) { when (currentScreen) {
"start" -> StartScreen { "start" -> StartScreen {
currentScreen = "login" currentScreen = "login"
} }
"login" -> LoginScreen( "login" -> LoginScreen(
onLoginSuccess = { currentScreen = "home" }, onLoginSuccess = { name ->
loggedInUserName = name
currentScreen = "home"
},
onNavigateToRegister = { currentScreen = "register" } onNavigateToRegister = { currentScreen = "register" }
) )
"register" -> RegisterScreen( "register" -> RegisterScreen(
onRegisterSuccess = { currentScreen = "login" }, onRegisterSuccess = { currentScreen = "login" },
onBackToLogin = { currentScreen = "login" } onBackToLogin = { currentScreen = "login" }
) )
"home" -> { "home" -> HomeScreen(
Box( userName = loggedInUserName,
modifier = Modifier.fillMaxSize(), onLogout = {
contentAlignment = Alignment.Center loggedInUserName = ""
) { currentScreen = "start"
Text("Bienvenido a Saludapp")
} }
} )
} }
} }
} }
@@ -7,11 +7,20 @@ import java.sql.Connection
import java.sql.DriverManager import java.sql.DriverManager
import java.sql.SQLException import java.sql.SQLException
/**
* Objeto encargado de gestionar todas las operaciones con la base de datos MariaDB.
* Implementa seguridad mediante hashing de contraseñas y operaciones asíncronas.
*/
object DatabaseHelper { object DatabaseHelper {
// Configuración de la conexión al servidor externo
private const val URL = "jdbc:mariadb://mariadb.peta9.com:3306/saludapp_db" private const val URL = "jdbc:mariadb://mariadb.peta9.com:3306/saludapp_db"
private const val USER = "saludapp_dba" private const val USER = "saludapp_dba"
private const val PASSWORD = "Pedro@110387" private const val PASSWORD = "Pedro@110387"
/**
* Encripta la contraseña utilizando el algoritmo SHA-256 para mayor seguridad.
* Retorna la representación hexadecimal del hash.
*/
private fun hashPassword(password: String): String { private fun hashPassword(password: String): String {
val bytes = password.toByteArray() val bytes = password.toByteArray()
val md = MessageDigest.getInstance("SHA-256") val md = MessageDigest.getInstance("SHA-256")
@@ -19,14 +28,22 @@ object DatabaseHelper {
return digest.fold("") { str, it -> str + "%02x".format(it) } return digest.fold("") { str, it -> str + "%02x".format(it) }
} }
suspend fun validateLogin(user: String, pass: String): Boolean = withContext(Dispatchers.IO) { /**
* Valida las credenciales de un usuario.
* @param user Nombre de usuario o correo electrónico.
* @param pass Contraseña en texto plano (será hasheada antes de comparar).
* @return El nombre completo del usuario si tiene éxito, null de lo contrario.
*/
suspend fun validateLogin(user: String, pass: String): String? = withContext(Dispatchers.IO) {
var connection: Connection? = null var connection: Connection? = null
try { try {
// Cargar el driver de MariaDB y establecer conexión
Class.forName("org.mariadb.jdbc.Driver") Class.forName("org.mariadb.jdbc.Driver")
connection = DriverManager.getConnection(URL, USER, PASSWORD) connection = DriverManager.getConnection(URL, USER, PASSWORD)
val hashedPass = hashPassword(pass) val hashedPass = hashPassword(pass)
val query = "SELECT COUNT(*) FROM usuarios WHERE (username = ? OR email = ?) AND password = ?" // Consulta preparada para evitar ataques de inyección SQL
val query = "SELECT nombre_completo FROM usuarios WHERE (username = ? OR email = ?) AND password = ?"
connection.prepareStatement(query).use { statement -> connection.prepareStatement(query).use { statement ->
statement.setString(1, user) statement.setString(1, user)
statement.setString(2, user) statement.setString(2, user)
@@ -34,21 +51,31 @@ object DatabaseHelper {
val resultSet = statement.executeQuery() val resultSet = statement.executeQuery()
if (resultSet.next()) { if (resultSet.next()) {
return@withContext resultSet.getInt(1) > 0 // Retorna el nombre real del usuario guardado en la DB
return@withContext resultSet.getString("nombre_completo") ?: user
} }
} }
} catch (e: Exception) { } catch (e: Exception) {
e.printStackTrace() e.printStackTrace()
} finally { } finally {
// Asegurar que la conexión se cierra siempre
try { try {
connection?.close() connection?.close()
} catch (e: SQLException) { } catch (e: SQLException) {
e.printStackTrace() e.printStackTrace()
} }
} }
return@withContext false return@withContext null
} }
/**
* Registra un nuevo usuario en la base de datos.
* @param user Nombre de usuario único.
* @param email Correo electrónico único.
* @param pass Contraseña en texto plano (se guardará como hash SHA-256).
* @param fullName Nombre completo del usuario.
* @return true si el registro fue exitoso, false en caso contrario.
*/
suspend fun registerUser(user: String, email: String, pass: String, fullName: String): Boolean = withContext(Dispatchers.IO) { suspend fun registerUser(user: String, email: String, pass: String, fullName: String): Boolean = withContext(Dispatchers.IO) {
var connection: Connection? = null var connection: Connection? = null
try { try {
@@ -0,0 +1,136 @@
package com.example.saludapp.ui
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.DirectionsRun
import androidx.compose.material.icons.automirrored.filled.ExitToApp
import androidx.compose.material.icons.filled.*
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
/**
* Pantalla principal (Dashboard) que se muestra tras un inicio de sesión exitoso.
* @param userName Nombre del usuario para el mensaje de bienvenida.
* @param onLogout Acción a ejecutar cuando el usuario decide cerrar sesión.
*/
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun HomeScreen(userName: String, onLogout: () -> Unit) {
Scaffold(
topBar = {
TopAppBar(
title = { Text("Saludapp", fontWeight = FontWeight.Bold) },
actions = {
// Botón para cerrar la sesión actual
IconButton(onClick = onLogout) {
Icon(Icons.AutoMirrored.Filled.ExitToApp, contentDescription = "Cerrar sesión")
}
},
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.onPrimaryContainer
)
)
}
) { padding ->
Column(
modifier = Modifier
.fillMaxSize()
.padding(padding)
.padding(16.dp)
) {
// Mensaje de bienvenida personalizado
Text(
text = "Hola, $userName 👋",
style = MaterialTheme.typography.headlineMedium,
fontWeight = FontWeight.Bold
)
Text(
text = "Aquí tienes tu resumen de hoy",
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
Spacer(modifier = Modifier.height(24.dp))
// Cuadrícula de tarjetas con estadísticas de salud
LazyVerticalGrid(
columns = GridCells.Fixed(2),
horizontalArrangement = Arrangement.spacedBy(16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
modifier = Modifier.fillMaxSize()
) {
item {
StatCard("Pasos", "8,432", Icons.AutoMirrored.Filled.DirectionsRun, MaterialTheme.colorScheme.primary)
}
item {
StatCard("Sueño", "7h 20m", Icons.Default.Bedtime, MaterialTheme.colorScheme.secondary)
}
item {
StatCard("Agua", "1.5L", Icons.Default.LocalDrink, MaterialTheme.colorScheme.tertiary)
}
item {
StatCard("Calorías", "1,200 kcal", Icons.Default.Whatshot, MaterialTheme.colorScheme.error)
}
item {
StatCard("Corazón", "72 bpm", Icons.Default.Favorite, MaterialTheme.colorScheme.primary)
}
item {
StatCard("Peso", "70 kg", Icons.Default.MonitorWeight, MaterialTheme.colorScheme.secondary)
}
}
}
}
}
/**
* Componente reutilizable para mostrar una tarjeta de estadística.
* @param title Título del dato (ej. "Agua").
* @param value Valor actual (ej. "1.5L").
* @param icon Icono representativo.
* @param color Color temático para la tarjeta y el icono.
*/
@Composable
fun StatCard(title: String, value: String, icon: ImageVector, color: androidx.compose.ui.graphics.Color) {
Card(
modifier = Modifier
.fillMaxWidth()
.height(140.dp),
colors = CardDefaults.cardColors(containerColor = color.copy(alpha = 0.1f))
) {
Column(
modifier = Modifier
.padding(16.dp)
.fillMaxSize(),
verticalArrangement = Arrangement.SpaceBetween
) {
Icon(
imageVector = icon,
contentDescription = null,
tint = color,
modifier = Modifier.size(32.dp)
)
Column {
Text(
text = value,
style = MaterialTheme.typography.titleLarge,
fontWeight = FontWeight.Bold,
color = color
)
Text(
text = title,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant
)
}
}
}
}
@@ -11,7 +11,7 @@ import com.example.saludapp.data.DatabaseHelper
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Composable @Composable
fun LoginScreen(onLoginSuccess: () -> Unit, onNavigateToRegister: () -> Unit) { fun LoginScreen(onLoginSuccess: (String) -> Unit, onNavigateToRegister: () -> Unit) {
var email by remember { mutableStateOf("") } var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") } var password by remember { mutableStateOf("") }
var isLoading by remember { mutableStateOf(false) } var isLoading by remember { mutableStateOf(false) }
@@ -73,10 +73,10 @@ fun LoginScreen(onLoginSuccess: () -> Unit, onNavigateToRegister: () -> Unit) {
scope.launch { scope.launch {
isLoading = true isLoading = true
errorMessage = null errorMessage = null
val success = DatabaseHelper.validateLogin(email, password) val userName = DatabaseHelper.validateLogin(email, password)
isLoading = false isLoading = false
if (success) { if (userName != null) {
onLoginSuccess() onLoginSuccess(userName)
} else { } else {
errorMessage = "Usuario o contraseña incorrectos" errorMessage = "Usuario o contraseña incorrectos"
} }
+1
View File
@@ -26,6 +26,7 @@ androidx-compose-ui-tooling-preview = { group = "androidx.compose.ui", name = "u
androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" } androidx-compose-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-manifest" }
androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" } androidx-compose-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" } androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" }
[plugins] [plugins]
android-application = { id = "com.android.application", version.ref = "agp" } android-application = { id = "com.android.application", version.ref = "agp" }