From 6078ab50109af6018c0cc5f9e0be8af36dff0090 Mon Sep 17 00:00:00 2001 From: Pedro Javier Date: Mon, 10 Aug 2026 10:10:50 +0200 Subject: [PATCH] Nueva opcion si esta en nevera, congelador u otro lugar --- README.md | 2 + .../example/despensapp/data/model/Product.kt | 3 + .../data/repository/PantryRepository.kt | 83 ++++++++++---- .../despensapp/ui/screens/AddProductScreen.kt | 45 ++++++++ .../despensapp/ui/screens/PantryScreen.kt | 104 +++++++++++++----- .../ui/screens/ProductDetailScreen.kt | 47 ++++++++ .../despensapp/ui/screens/SettingsScreen.kt | 54 +++++++++ app/src/main/res/values/strings.xml | 11 ++ 8 files changed, 302 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 1572cba..a17106a 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,12 @@ Aplicación Android moderna y colaborativa diseñada para gestionar el inventari ## 🚀 Novedades Recientes * **Seguimiento de Cambios:** Ahora puedes ver qué miembro de la familia realizó la última modificación en cada producto. +* **Easy Family Sync:** Invita a miembros de tu familia compartiendo un código directamente desde los ajustes. * **Historial de Precios:** Seguimiento de la evolución de precios por supermercado. * **Comparativa de Supermercados:** Identifica dónde encontraste el producto por última vez y a qué precio. * **Configuración de Alertas:** Frecuencia de notificaciones personalizable (12h, 24h, 48h o Semanal). * **Gestión de Imágenes:** Sube tus propias fotos desde la galería o haz una foto directamente si el producto no tiene imagen en Open Food Facts. +* **Inventario por Zonas (Smart Zoned Inventory):** Organiza tus productos por ubicación (Despensa, Nevera, Congelador, Baño, etc.) para un control más preciso. * **Valoración Financiera:** Visualiza el valor total de tu despensa y el presupuesto necesario para reponer lo que falta. ## 🛠 Funcionalidades Principales diff --git a/app/src/main/java/com/example/despensapp/data/model/Product.kt b/app/src/main/java/com/example/despensapp/data/model/Product.kt index 8981a43..d40d88a 100644 --- a/app/src/main/java/com/example/despensapp/data/model/Product.kt +++ b/app/src/main/java/com/example/despensapp/data/model/Product.kt @@ -7,8 +7,11 @@ data class Product( val minQuantity: Int, val unit: String = "unidad", val category: String = "General", + val location: String = "Despensa", val price: Double = 0.0, val lastSupermarket: String? = null, + val bestPrice: Double? = null, + val bestPriceSupermarket: String? = null, val imageUrl: String? = null, val expirationDate: String? = null, val createdAt: String? = null, diff --git a/app/src/main/java/com/example/despensapp/data/repository/PantryRepository.kt b/app/src/main/java/com/example/despensapp/data/repository/PantryRepository.kt index 057a7da..30ab9ff 100644 --- a/app/src/main/java/com/example/despensapp/data/repository/PantryRepository.kt +++ b/app/src/main/java/com/example/despensapp/data/repository/PantryRepository.kt @@ -51,11 +51,18 @@ class PantryRepository { return withContext(Dispatchers.IO) { try { val connection = getConnection() - val sql = if (familyCode != null) { - "SELECT * FROM productos WHERE visible = 1 AND codigo_familia = ?" - } else { - "SELECT * FROM productos WHERE visible = 1" - } + val sql = """ + SELECT p.*, h.precio as mejor_precio, h.supermercado as mejor_supermercado + FROM productos p + LEFT JOIN historial_precios h ON h.id = ( + SELECT id FROM historial_precios + WHERE id_producto = p.id + ORDER BY precio ASC, fecha DESC + LIMIT 1 + ) + WHERE p.visible = 1 ${if (familyCode != null) "AND p.codigo_familia = ?" else ""} + """.trimIndent() + val statement = connection.prepareStatement(sql) if (familyCode != null) { statement.setString(1, familyCode) @@ -72,8 +79,11 @@ class PantryRepository { minQuantity = resultSet.getInt("cantidad_minima"), unit = resultSet.getString("unidad") ?: "unidad", category = resultSet.getString("categoria"), + location = resultSet.getString("ubicacion") ?: "Despensa", price = resultSet.getDouble("precio"), lastSupermarket = resultSet.getString("ultimo_supermercado"), + bestPrice = if (resultSet.getObject("mejor_precio") != null) resultSet.getDouble("mejor_precio") else null, + bestPriceSupermarket = resultSet.getString("mejor_supermercado"), imageUrl = resultSet.getString("imagen_url"), expirationDate = resultSet.getString("fecha_caducidad"), createdAt = resultSet.getString("fecha_alta"), @@ -140,6 +150,7 @@ class PantryRepository { minCantidad: Int, unidad: String, category: String, + location: String, price: Double, supermarket: String? = null, fechaCaducidad: String?, @@ -149,19 +160,20 @@ class PantryRepository { return withContext(Dispatchers.IO) { try { val connection = getConnection() - val sql = "UPDATE productos SET nombre = ?, cantidad = ?, cantidad_minima = ?, unidad = ?, categoria = ?, precio = ?, ultimo_supermercado = ?, fecha_caducidad = ?, imagen_url = ?, visible = 1, modificado_por = ? WHERE id = ?" + val sql = "UPDATE productos SET nombre = ?, cantidad = ?, cantidad_minima = ?, unidad = ?, categoria = ?, ubicacion = ?, precio = ?, ultimo_supermercado = ?, fecha_caducidad = ?, imagen_url = ?, visible = 1, modificado_por = ? WHERE id = ?" val statement = connection.prepareStatement(sql) statement.setString(1, nombre) statement.setInt(2, cantidad) statement.setInt(3, minCantidad) statement.setString(4, unidad) statement.setString(5, category) - statement.setDouble(6, price) - statement.setString(7, supermarket) - statement.setString(8, fechaCaducidad) - statement.setString(9, imageUrl) - statement.setString(10, userName) - statement.setInt(11, productId) + statement.setString(6, location) + statement.setDouble(7, price) + statement.setString(8, supermarket) + statement.setString(9, fechaCaducidad) + statement.setString(10, imageUrl) + statement.setString(11, userName) + statement.setInt(12, productId) val rowsUpdated = statement.executeUpdate() @@ -184,6 +196,7 @@ class PantryRepository { minCantidad: Int, unidad: String, categoria: String, + location: String, price: Double, supermarket: String? = null, imageUrl: String?, @@ -194,14 +207,15 @@ class PantryRepository { try { val connection = getConnection() val sql = """ - INSERT INTO productos (barcode, nombre, cantidad, cantidad_minima, unidad, categoria, precio, ultimo_supermercado, imagen_url, fecha_caducidad, visible, modificado_por) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?) + INSERT INTO productos (barcode, nombre, cantidad, cantidad_minima, unidad, categoria, ubicacion, precio, ultimo_supermercado, imagen_url, fecha_caducidad, visible, modificado_por) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?) ON DUPLICATE KEY UPDATE nombre = VALUES(nombre), cantidad = VALUES(cantidad), cantidad_minima = VALUES(cantidad_minima), unidad = VALUES(unidad), categoria = VALUES(categoria), + ubicacion = VALUES(ubicacion), precio = VALUES(precio), ultimo_supermercado = VALUES(ultimo_supermercado), imagen_url = IF(VALUES(imagen_url) IS NULL, imagen_url, VALUES(imagen_url)), @@ -217,11 +231,12 @@ class PantryRepository { statement.setInt(4, minCantidad) statement.setString(5, unidad) statement.setString(6, categoria) - statement.setDouble(7, price) - statement.setString(8, supermarket) - statement.setString(9, imageUrl) - statement.setString(10, fechaCaducidad) - statement.setString(11, userName) + statement.setString(7, location) + statement.setDouble(8, price) + statement.setString(9, supermarket) + statement.setString(10, imageUrl) + statement.setString(11, fechaCaducidad) + statement.setString(12, userName) val rowsInserted = statement.executeUpdate() @@ -275,7 +290,17 @@ class PantryRepository { return withContext(Dispatchers.IO) { try { val connection = getConnection() - val sql = "SELECT * FROM productos WHERE id = ?" + val sql = """ + SELECT p.*, h.precio as mejor_precio, h.supermercado as mejor_supermercado + FROM productos p + LEFT JOIN historial_precios h ON h.id = ( + SELECT id FROM historial_precios + WHERE id_producto = p.id + ORDER BY precio ASC, fecha DESC + LIMIT 1 + ) + WHERE p.id = ? + """.trimIndent() val statement = connection.prepareStatement(sql) statement.setInt(1, productId) val resultSet = statement.executeQuery() @@ -289,8 +314,11 @@ class PantryRepository { minQuantity = resultSet.getInt("cantidad_minima"), unit = resultSet.getString("unidad") ?: "unidad", category = resultSet.getString("categoria"), + location = resultSet.getString("ubicacion") ?: "Despensa", price = resultSet.getDouble("precio"), lastSupermarket = resultSet.getString("ultimo_supermercado"), + bestPrice = if (resultSet.getObject("mejor_precio") != null) resultSet.getDouble("mejor_precio") else null, + bestPriceSupermarket = resultSet.getString("mejor_supermercado"), imageUrl = resultSet.getString("imagen_url"), expirationDate = resultSet.getString("fecha_caducidad"), createdAt = resultSet.getString("fecha_alta"), @@ -309,7 +337,17 @@ class PantryRepository { return withContext(Dispatchers.IO) { try { val connection = getConnection() - val sql = "SELECT * FROM productos WHERE barcode = ?" + val sql = """ + SELECT p.*, h.precio as mejor_precio, h.supermercado as mejor_supermercado + FROM productos p + LEFT JOIN historial_precios h ON h.id = ( + SELECT id FROM historial_precios + WHERE id_producto = p.id + ORDER BY precio ASC, fecha DESC + LIMIT 1 + ) + WHERE p.barcode = ? + """.trimIndent() val statement = connection.prepareStatement(sql) statement.setString(1, barcode) val resultSet = statement.executeQuery() @@ -323,8 +361,11 @@ class PantryRepository { minQuantity = resultSet.getInt("cantidad_minima"), unit = resultSet.getString("unidad") ?: "unidad", category = resultSet.getString("categoria"), + location = resultSet.getString("ubicacion") ?: "Despensa", price = resultSet.getDouble("precio"), lastSupermarket = resultSet.getString("ultimo_supermercado"), + bestPrice = if (resultSet.getObject("mejor_precio") != null) resultSet.getDouble("mejor_precio") else null, + bestPriceSupermarket = resultSet.getString("mejor_supermercado"), imageUrl = resultSet.getString("imagen_url"), expirationDate = resultSet.getString("fecha_caducidad"), createdAt = resultSet.getString("fecha_alta"), diff --git a/app/src/main/java/com/example/despensapp/ui/screens/AddProductScreen.kt b/app/src/main/java/com/example/despensapp/ui/screens/AddProductScreen.kt index 9ddf4fe..6daa120 100644 --- a/app/src/main/java/com/example/despensapp/ui/screens/AddProductScreen.kt +++ b/app/src/main/java/com/example/despensapp/ui/screens/AddProductScreen.kt @@ -77,6 +77,7 @@ fun AddProductScreen(onProductAdded: () -> Unit) { var productPrice by remember { mutableStateOf(0.0) } var productImageUrl by remember { mutableStateOf(null) } var expirationDate by remember { mutableStateOf(null) } + var selectedLocation by remember { mutableStateOf("Despensa") } var quantity by remember { mutableStateOf(1) } var minQuantity by remember { mutableStateOf(1) } var selectedUnit by remember { mutableStateOf("unidad") } @@ -348,6 +349,49 @@ fun AddProductScreen(onProductAdded: () -> Unit) { Spacer(modifier = Modifier.height(8.dp)) + // Selector de Ubicación + val locationMap = listOf( + "Despensa" to stringResource(R.string.location_despensa), + "Nevera" to stringResource(R.string.location_nevera), + "Congelador" to stringResource(R.string.location_congelador), + "Baño/Aseo" to stringResource(R.string.location_bano), + "Otros" to stringResource(R.string.location_otro) + ) + var locationExpanded by remember { mutableStateOf(false) } + + ExposedDropdownMenuBox( + expanded = locationExpanded, + onExpandedChange = { locationExpanded = !locationExpanded }, + modifier = Modifier.fillMaxWidth() + ) { + OutlinedTextField( + value = locationMap.find { it.first == selectedLocation }?.second ?: selectedLocation, + onValueChange = {}, + readOnly = true, + label = { Text(stringResource(R.string.pantry_location_label)) }, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = locationExpanded) }, + modifier = Modifier.menuAnchor().fillMaxWidth(), + colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(), + leadingIcon = { Icon(Icons.Default.Place, contentDescription = null) } + ) + ExposedDropdownMenu( + expanded = locationExpanded, + onDismissRequest = { locationExpanded = false } + ) { + locationMap.forEach { (key, label) -> + DropdownMenuItem( + text = { Text(label) }, + onClick = { + selectedLocation = key + locationExpanded = false + } + ) + } + } + } + + Spacer(modifier = Modifier.height(8.dp)) + // Selector de Unidad ExposedDropdownMenuBox( expanded = unitsExpanded, @@ -418,6 +462,7 @@ fun AddProductScreen(onProductAdded: () -> Unit) { minCantidad = minQuantity, unidad = selectedUnit, categoria = productCategory, + location = selectedLocation, price = productPrice, supermarket = selectedSupermarket.ifEmpty { null }, imageUrl = productImageUrl, diff --git a/app/src/main/java/com/example/despensapp/ui/screens/PantryScreen.kt b/app/src/main/java/com/example/despensapp/ui/screens/PantryScreen.kt index f5eb908..39688bc 100644 --- a/app/src/main/java/com/example/despensapp/ui/screens/PantryScreen.kt +++ b/app/src/main/java/com/example/despensapp/ui/screens/PantryScreen.kt @@ -60,6 +60,7 @@ fun PantryScreen( // Filters and Search var searchQuery by remember { mutableStateOf("") } var selectedCategory by remember { mutableStateOf(null) } + var selectedLocation by remember { mutableStateOf(null) } var showOnlyNearExpiry by remember { mutableStateOf(false) } var showShoppingList by remember { mutableStateOf(false) } @@ -82,10 +83,11 @@ fun PantryScreen( val filteredProducts = allProducts.filter { product -> val matchesSearch = product.name.contains(searchQuery, ignoreCase = true) val matchesCategory = selectedCategory == null || product.category == selectedCategory + val matchesLocation = selectedLocation == null || product.location == selectedLocation val matchesExpiry = !showOnlyNearExpiry || isNearExpiry(product.expirationDate) val matchesLowStock = !showShoppingList || product.isLowStock - matchesSearch && matchesCategory && matchesExpiry && matchesLowStock + matchesSearch && matchesCategory && matchesLocation && matchesExpiry && matchesLowStock } val lowStockCount = allProducts.count { it.isLowStock } @@ -261,6 +263,36 @@ fun PantryScreen( ) } + // Filtros de Ubicación + val locations = listOf( + "Despensa" to stringResource(R.string.location_despensa), + "Nevera" to stringResource(R.string.location_nevera), + "Congelador" to stringResource(R.string.location_congelador), + "Baño/Aseo" to stringResource(R.string.location_bano), + "Otros" to stringResource(R.string.location_otro) + ) + + ScrollableTabRow( + selectedTabIndex = if (selectedLocation == null) 0 else locations.indexOfFirst { it.first == selectedLocation } + 1, + edgePadding = 16.dp, + divider = {}, + containerColor = Color.Transparent, + indicator = {} + ) { + Tab( + selected = selectedLocation == null, + onClick = { selectedLocation = null }, + text = { Text(stringResource(R.string.pantry_filter_all)) } + ) + locations.forEach { (key, label) -> + Tab( + selected = selectedLocation == key, + onClick = { selectedLocation = key }, + text = { Text(label) } + ) + } + } + // Categorías Scrollable ScrollableTabRow( selectedTabIndex = if (selectedCategory == null) 0 else categories.indexOf(selectedCategory) + 1, @@ -327,8 +359,6 @@ fun PantryScreen( product = product, onItemClick = { onNavigateToDetail(product.id) }, onNavigateToPriceScanner = onNavigateToPriceScanner, - showPriceUpdater = showShoppingList, - onUpdatePrice = { onNavigateToPriceScanner(product.id) }, onQuantityChange = { newQty -> scope.launch { val success = repository.updateProductQuantity(product.id, newQty, userName) @@ -407,8 +437,6 @@ fun ProductItem( product: Product, onItemClick: () -> Unit, onNavigateToPriceScanner: (Int) -> Unit = {}, - showPriceUpdater: Boolean = false, - onUpdatePrice: () -> Unit = {}, onQuantityChange: (Int) -> Unit ) { Card( @@ -485,6 +513,20 @@ fun ProductItem( fontSize = 14.sp, color = Color.Gray ) + // Ubicación Badge + Surface( + modifier = Modifier.padding(top = 2.dp), + shape = MaterialTheme.shapes.extraSmall, + color = MaterialTheme.colorScheme.tertiaryContainer.copy(alpha = 0.5f), + contentColor = MaterialTheme.colorScheme.onTertiaryContainer + ) { + Text( + text = product.location, + fontSize = 10.sp, + modifier = Modifier.padding(horizontal = 4.dp, vertical = 2.dp), + fontWeight = FontWeight.Bold + ) + } } // Botones de acción arriba a la derecha en columna (+ arriba, - abajo) @@ -498,16 +540,19 @@ fun ProductItem( Spacer(modifier = Modifier.height(4.dp)) - // Nuevo Botón: Registrar solo Precio (Historial) - IconButton( + // Nuevo Botón: Registrar solo Precio (Historial) - Ahora más visible + FilledTonalIconButton( onClick = { onNavigateToPriceScanner(product.id) }, - modifier = Modifier.size(36.dp) + modifier = Modifier.size(36.dp), + colors = IconButtonDefaults.filledTonalIconButtonColors( + containerColor = MaterialTheme.colorScheme.primaryContainer, + contentColor = MaterialTheme.colorScheme.onPrimaryContainer + ) ) { Icon( - Icons.Default.LocalOffer, + Icons.Default.Sell, contentDescription = stringResource(R.string.pantry_update_price), - tint = MaterialTheme.colorScheme.primary, - modifier = Modifier.size(20.dp) + modifier = Modifier.size(18.dp) ) } @@ -564,6 +609,28 @@ fun ProductItem( ) } } + + // Mejor Precio Histórico (con Fallback al precio actual si no hay historial) + val displayBestPrice = product.bestPrice ?: if (product.price > 0) product.price else null + val displayBestSupermarket = product.bestPriceSupermarket ?: product.lastSupermarket + + if (displayBestPrice != null && displayBestSupermarket != null) { + Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(top = 2.dp)) { + Icon( + Icons.Default.Stars, + contentDescription = null, + modifier = Modifier.size(12.dp), + tint = Color(0xFF4CAF50) // Verde éxito + ) + Spacer(modifier = Modifier.width(4.dp)) + Text( + text = stringResource(R.string.pantry_best_price, displayBestPrice, displayBestSupermarket), + fontSize = 11.sp, + color = Color(0xFF388E3C), + fontWeight = FontWeight.Bold + ) + } + } if (product.createdAt != null) { Row(verticalAlignment = Alignment.CenterVertically) { @@ -633,21 +700,6 @@ fun ProductItem( } } } - - // Botón de OCR Precio (Solo en modo Lista Compra) - if (showPriceUpdater) { - Spacer(modifier = Modifier.width(8.dp)) - IconButton( - onClick = onUpdatePrice, - modifier = Modifier.size(32.dp) - ) { - Icon( - Icons.Default.QrCodeScanner, - contentDescription = stringResource(R.string.pantry_update_price), - tint = MaterialTheme.colorScheme.primary - ) - } - } } } } diff --git a/app/src/main/java/com/example/despensapp/ui/screens/ProductDetailScreen.kt b/app/src/main/java/com/example/despensapp/ui/screens/ProductDetailScreen.kt index 5d78283..c0d6986 100644 --- a/app/src/main/java/com/example/despensapp/ui/screens/ProductDetailScreen.kt +++ b/app/src/main/java/com/example/despensapp/ui/screens/ProductDetailScreen.kt @@ -58,6 +58,7 @@ fun ProductDetailScreen(productId: Int, onBack: () -> Unit) { var editMinQuantity by remember { mutableStateOf(0) } var editUnit by remember { mutableStateOf("") } var editCategory by remember { mutableStateOf("") } + var editLocation by remember { mutableStateOf("Despensa") } var editPrice by remember { mutableStateOf(0.0) } var editSupermarket by remember { mutableStateOf("") } var editExpirationDate by remember { mutableStateOf(null) } @@ -202,6 +203,7 @@ fun ProductDetailScreen(productId: Int, onBack: () -> Unit) { editMinQuantity = p.minQuantity editUnit = p.unit editCategory = p.category + editLocation = p.location editPrice = p.price editSupermarket = p.lastSupermarket ?: "" editExpirationDate = p.expirationDate @@ -256,6 +258,7 @@ fun ProductDetailScreen(productId: Int, onBack: () -> Unit) { minCantidad = editMinQuantity, unidad = editUnit, category = editCategory, + location = editLocation, price = editPrice, supermarket = editSupermarket.ifEmpty { null }, fechaCaducidad = editExpirationDate, @@ -346,6 +349,7 @@ fun ProductDetailScreen(productId: Int, onBack: () -> Unit) { Spacer(modifier = Modifier.height(24.dp)) DetailRow(icon = Icons.Default.Inventory, label = stringResource(R.string.detail_stock_label), value = stringResource(R.string.pantry_units_label, p.quantity, p.unit)) + DetailRow(icon = Icons.Default.Place, label = stringResource(R.string.pantry_location_label), value = p.location) DetailRow(icon = Icons.Default.Payments, label = "Precio unitario", value = stringResource(R.string.pantry_price_label, p.price)) if (p.lastSupermarket != null) { @@ -459,6 +463,49 @@ fun ProductDetailScreen(productId: Int, onBack: () -> Unit) { Spacer(modifier = Modifier.height(8.dp)) + // Selector de Ubicación + val locationMap = listOf( + "Despensa" to stringResource(R.string.location_despensa), + "Nevera" to stringResource(R.string.location_nevera), + "Congelador" to stringResource(R.string.location_congelador), + "Baño/Aseo" to stringResource(R.string.location_bano), + "Otros" to stringResource(R.string.location_otro) + ) + var locationExpanded by remember { mutableStateOf(false) } + + ExposedDropdownMenuBox( + expanded = locationExpanded, + onExpandedChange = { locationExpanded = !locationExpanded }, + modifier = Modifier.fillMaxWidth() + ) { + OutlinedTextField( + value = locationMap.find { it.first == editLocation }?.second ?: editLocation, + onValueChange = {}, + readOnly = true, + label = { Text(stringResource(R.string.pantry_location_label)) }, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = locationExpanded) }, + modifier = Modifier.menuAnchor().fillMaxWidth(), + colors = ExposedDropdownMenuDefaults.outlinedTextFieldColors(), + leadingIcon = { Icon(Icons.Default.Place, contentDescription = null) } + ) + ExposedDropdownMenu( + expanded = locationExpanded, + onDismissRequest = { locationExpanded = false } + ) { + locationMap.forEach { (key, label) -> + DropdownMenuItem( + text = { Text(label) }, + onClick = { + editLocation = key + locationExpanded = false + } + ) + } + } + } + + Spacer(modifier = Modifier.height(8.dp)) + SupermarketDropdown( selectedSupermarket = editSupermarket, onSupermarketChange = { editSupermarket = it }, diff --git a/app/src/main/java/com/example/despensapp/ui/screens/SettingsScreen.kt b/app/src/main/java/com/example/despensapp/ui/screens/SettingsScreen.kt index 2d56aa5..2b5a2b2 100644 --- a/app/src/main/java/com/example/despensapp/ui/screens/SettingsScreen.kt +++ b/app/src/main/java/com/example/despensapp/ui/screens/SettingsScreen.kt @@ -1,10 +1,12 @@ package com.example.despensapp.ui.screens +import android.content.Intent import androidx.compose.foundation.layout.* import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.selection.selectableGroup import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.filled.ArrowBack +import androidx.compose.material.icons.filled.Share import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -12,6 +14,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import com.example.despensapp.R import com.example.despensapp.data.session.UserSession @@ -120,6 +123,57 @@ fun SettingsScreen(onBack: () -> Unit) { ) } } + + Spacer(modifier = Modifier.height(24.dp)) + HorizontalDivider() + Spacer(modifier = Modifier.height(16.dp)) + + Text( + text = stringResource(R.string.settings_family_title), + style = MaterialTheme.typography.titleMedium, + modifier = Modifier.padding(bottom = 16.dp) + ) + + val familyCode = remember { UserSession.getFamilyCode(context) ?: "DEFAULT" } + + Card( + modifier = Modifier.fillMaxWidth(), + colors = CardDefaults.cardColors( + containerColor = MaterialTheme.colorScheme.surfaceVariant + ) + ) { + Column( + modifier = Modifier.padding(16.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = stringResource(R.string.settings_family_code_label), + style = MaterialTheme.typography.bodyMedium + ) + Text( + text = familyCode, + style = MaterialTheme.typography.headlineMedium, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.primary, + modifier = Modifier.padding(vertical = 8.dp) + ) + + Button( + onClick = { + val shareIntent = Intent(Intent.ACTION_SEND).apply { + type = "text/plain" + putExtra(Intent.EXTRA_TEXT, context.getString(R.string.settings_family_invite_message, familyCode)) + } + context.startActivity(Intent.createChooser(shareIntent, context.getString(R.string.share))) + }, + modifier = Modifier.fillMaxWidth() + ) { + Icon(Icons.Default.Share, contentDescription = null) + Spacer(Modifier.width(8.dp)) + Text(stringResource(R.string.settings_family_invite_button)) + } + } + } } } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index fef3533..786a402 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -70,6 +70,13 @@ Historial de Precios No hay historial de precios registrado Visto en: %1$s + Mejor: %1$.2f € (%2$s) + Ubicación + Despensa + Nevera + Congelador + Baño/Aseo + Otros MERCADONA @@ -150,6 +157,10 @@ Una vez a la semana Configuración guardada correctamente Habilitar desbloqueo biométrico + Mi Familia + Tu Código de Familia: + Invitar Familiar + ¡Hola! Únete a nuestra despensa compartida en Despensapp. \n\nUsa este código al registrarte: *%1$s* \n\n¡Así sabremos qué falta en casa en tiempo real! 🥫🛒 Desbloqueo de Despensapp Usa tu huella o rostro para entrar Tu dispositivo no soporta biometría o no está configurada