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.
- `LoginScreen.kt`: Inicio de sesión con validación de credenciales encriptadas.
- `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.
---
+1
View File
@@ -42,6 +42,7 @@ dependencies {
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.material.icons.extended)
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview)
@@ -4,48 +4,55 @@ import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
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.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import com.example.saludapp.ui.HomeScreen
import com.example.saludapp.ui.LoginScreen
import com.example.saludapp.ui.RegisterScreen
import com.example.saludapp.ui.StartScreen
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() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// Habilitar diseño de borde a borde (edge-to-edge)
enableEdgeToEdge()
setContent {
SaludappTheme {
// Estado para controlar en qué pantalla se encuentra el usuario
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) {
"start" -> StartScreen {
currentScreen = "login"
}
"login" -> LoginScreen(
onLoginSuccess = { currentScreen = "home" },
onLoginSuccess = { name ->
loggedInUserName = name
currentScreen = "home"
},
onNavigateToRegister = { currentScreen = "register" }
)
"register" -> RegisterScreen(
onRegisterSuccess = { currentScreen = "login" },
onBackToLogin = { currentScreen = "login" }
)
"home" -> {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
) {
Text("Bienvenido a Saludapp")
}
"home" -> HomeScreen(
userName = loggedInUserName,
onLogout = {
loggedInUserName = ""
currentScreen = "start"
}
)
}
}
}
@@ -7,11 +7,20 @@ import java.sql.Connection
import java.sql.DriverManager
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 {
// Configuración de la conexión al servidor externo
private const val URL = "jdbc:mariadb://mariadb.peta9.com:3306/saludapp_db"
private const val USER = "saludapp_dba"
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 {
val bytes = password.toByteArray()
val md = MessageDigest.getInstance("SHA-256")
@@ -19,14 +28,22 @@ object DatabaseHelper {
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
try {
// Cargar el driver de MariaDB y establecer conexión
Class.forName("org.mariadb.jdbc.Driver")
connection = DriverManager.getConnection(URL, USER, PASSWORD)
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 ->
statement.setString(1, user)
statement.setString(2, user)
@@ -34,21 +51,31 @@ object DatabaseHelper {
val resultSet = statement.executeQuery()
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) {
e.printStackTrace()
} finally {
// Asegurar que la conexión se cierra siempre
try {
connection?.close()
} catch (e: SQLException) {
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) {
var connection: Connection? = null
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
@Composable
fun LoginScreen(onLoginSuccess: () -> Unit, onNavigateToRegister: () -> Unit) {
fun LoginScreen(onLoginSuccess: (String) -> Unit, onNavigateToRegister: () -> Unit) {
var email by remember { mutableStateOf("") }
var password by remember { mutableStateOf("") }
var isLoading by remember { mutableStateOf(false) }
@@ -73,10 +73,10 @@ fun LoginScreen(onLoginSuccess: () -> Unit, onNavigateToRegister: () -> Unit) {
scope.launch {
isLoading = true
errorMessage = null
val success = DatabaseHelper.validateLogin(email, password)
val userName = DatabaseHelper.validateLogin(email, password)
isLoading = false
if (success) {
onLoginSuccess()
if (userName != null) {
onLoginSuccess(userName)
} else {
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-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
androidx-compose-material3 = { group = "androidx.compose.material3", name = "material3" }
androidx-compose-material-icons-extended = { group = "androidx.compose.material", name = "material-icons-extended" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }