diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..f0c6ad0
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d61cb0f..9d52d90 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,15 +3,13 @@
xmlns:tools="http://schemas.android.com/tools">
+
-
+ android:required="true" />
-
+
-
+
+
diff --git a/app/src/main/java/com/example/prueba/MainActivity.kt b/app/src/main/java/com/example/prueba/MainActivity.kt
index 973b02b..f0ff678 100644
--- a/app/src/main/java/com/example/prueba/MainActivity.kt
+++ b/app/src/main/java/com/example/prueba/MainActivity.kt
@@ -1,6 +1,7 @@
package com.example.prueba
import android.Manifest
+import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
@@ -10,10 +11,12 @@ import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.Image
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.AltRoute
import androidx.compose.material.icons.filled.*
@@ -23,7 +26,9 @@ import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
@@ -37,14 +42,13 @@ import com.example.prueba.data.PriceCategory
import com.example.prueba.ui.GasStationViewModel
import com.example.prueba.ui.theme.PruebaTheme
import com.example.prueba.utils.FuelRecord
-import android.content.Context
import org.osmdroid.config.Configuration
import org.osmdroid.tileprovider.tilesource.TileSourceFactory
import org.osmdroid.util.GeoPoint
import org.osmdroid.views.MapView
import org.osmdroid.views.overlay.Marker
import org.osmdroid.views.overlay.Polyline
-import android.preference.PreferenceManager
+import java.io.File
import java.util.Locale
class MainActivity : ComponentActivity() {
@@ -52,8 +56,9 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
// OSMDroid configuration
- Configuration.getInstance().load(this, getSharedPreferences("osmdroid", Context.MODE_PRIVATE))
- Configuration.getInstance().userAgentValue = "GasolinerasApp/1.0 (Android; Contact: support@example.com)"
+ val osmConfig = Configuration.getInstance()
+ osmConfig.userAgentValue = "Mozilla/5.0 (Android) GasolinerasApp/1.0"
+ osmConfig.load(this, getSharedPreferences("osmdroid_prefs", MODE_PRIVATE))
enableEdgeToEdge()
setContent {
@@ -73,6 +78,8 @@ fun GasStationApp(viewModel: GasStationViewModel = viewModel()) {
var showFuelDialog by remember { mutableStateOf(null) }
var editRecordDialog by remember { mutableStateOf(null) }
+ var selectedStationForMap by remember { mutableStateOf(null) }
+ var showHistoryFilter by remember { mutableStateOf(false) }
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission()
@@ -148,14 +155,36 @@ fun GasStationApp(viewModel: GasStationViewModel = viewModel()) {
}
},
actions = {
- if (!uiState.isRouteActive && uiState.selectedTab == 0) {
+ // Filtro de favoritos (solo en lista o mapa)
+ if (uiState.selectedTab == 0 || uiState.selectedTab == 2) {
+ IconButton(onClick = { viewModel.toggleFavoritesFilter() }) {
+ Icon(
+ imageVector = if (uiState.showOnlyFavorites) Icons.Default.Favorite else Icons.Default.FavoriteBorder,
+ contentDescription = stringResource(R.string.show_favorites),
+ tint = if (uiState.showOnlyFavorites) Color.Red else LocalContentColor.current
+ )
+ }
+ }
+
+ // Filtro de historial
+ if (uiState.selectedTab == 1) {
+ IconButton(onClick = { showHistoryFilter = true }) {
+ Icon(
+ imageVector = if (uiState.filterMonth != null || uiState.filterYear != null) Icons.Default.FilterList else Icons.Default.FilterListOff,
+ contentDescription = stringResource(R.string.filter_history),
+ tint = if (uiState.filterMonth != null || uiState.filterYear != null) MaterialTheme.colorScheme.primary else LocalContentColor.current
+ )
+ }
+ }
+
+ if (!uiState.isRouteActive && (uiState.selectedTab == 0 || uiState.selectedTab == 2)) {
IconButton(onClick = { viewModel.setRouteActive(true) }) {
Icon(Icons.AutoMirrored.Filled.AltRoute, stringResource(R.string.on_route))
}
}
}
)
- if (uiState.selectedTab == 0) {
+ if (uiState.selectedTab == 0 || uiState.selectedTab == 2) {
FuelTypeSelector(
selectedType = uiState.selectedFuelType,
onTypeSelected = { viewModel.selectFuelType(it) }
@@ -205,31 +234,115 @@ fun GasStationApp(viewModel: GasStationViewModel = viewModel()) {
} else if (uiState.stations.isEmpty()) {
Text(stringResource(R.string.no_results), modifier = Modifier.align(Alignment.Center))
} else {
- GasStationList(
- stations = uiState.stations,
- visibleCount = uiState.visibleStationsCount,
- selectedFuelType = uiState.selectedFuelType,
- onLogClick = { showFuelDialog = it },
- onShowMore = { viewModel.showMoreStations() },
- onShowLess = { viewModel.resetStationLimit() }
- )
+ GasStationList(
+ stations = uiState.stations,
+ visibleCount = uiState.visibleStationsCount,
+ selectedFuelType = uiState.selectedFuelType,
+ favoriteIds = uiState.favoriteIds,
+ onLogClick = { showFuelDialog = it },
+ onShowMore = { viewModel.showMoreStations() },
+ onShowLess = { viewModel.resetStationLimit() },
+ onToggleFavorite = { viewModel.toggleFavorite(it) }
+ )
}
}
}
1 -> {
FuelHistoryScreen(
records = uiState.fuelRecords,
+ filterMonth = uiState.filterMonth,
+ filterYear = uiState.filterYear,
onDelete = { record -> viewModel.deleteFuelRecord(record) },
onEdit = { record -> editRecordDialog = record }
)
}
2 -> {
- GasStationMap(
- stations = uiState.stations,
- routePoints = uiState.routePoints,
- userLocation = uiState.lastLocation,
- selectedFuelType = uiState.selectedFuelType
- )
+ Box(modifier = Modifier.fillMaxSize()) {
+ GasStationMap(
+ stations = uiState.stations,
+ routePoints = uiState.routePoints,
+ userLocation = uiState.lastLocation,
+ selectedFuelType = uiState.selectedFuelType,
+ favoriteIds = uiState.favoriteIds,
+ onMapMoved = { lat, lon -> viewModel.updateMapCenter(lat, lon) },
+ onStationSelected = { selectedStationForMap = it }
+ )
+
+ // Botón flotante para buscar en la zona del mapa
+ Button(
+ onClick = { viewModel.searchAtMapCenter() },
+ modifier = Modifier
+ .align(Alignment.TopCenter)
+ .padding(top = 16.dp),
+ elevation = ButtonDefaults.buttonElevation(defaultElevation = 8.dp)
+ ) {
+ Icon(Icons.Default.Search, null)
+ Spacer(modifier = Modifier.width(8.dp))
+ Text(stringResource(R.string.search_this_area))
+ }
+
+ // Tarjeta informativa al seleccionar una gasolinera
+ selectedStationForMap?.let { station ->
+ Card(
+ modifier = Modifier
+ .align(Alignment.BottomCenter)
+ .padding(bottom = 16.dp, start = 16.dp, end = 16.dp)
+ .fillMaxWidth(),
+ elevation = CardDefaults.cardElevation(defaultElevation = 12.dp)
+ ) {
+ Column(modifier = Modifier.padding(16.dp)) {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.Top
+ ) {
+ Column(modifier = Modifier.weight(1f)) {
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Text(station.name, style = MaterialTheme.typography.titleMedium, modifier = Modifier.weight(1f))
+ IconButton(onClick = { viewModel.toggleFavorite(station.id) }) {
+ Icon(
+ imageVector = if (uiState.favoriteIds.contains(station.id)) Icons.Default.Favorite else Icons.Default.FavoriteBorder,
+ contentDescription = null,
+ tint = if (uiState.favoriteIds.contains(station.id)) Color.Red else LocalContentColor.current
+ )
+ }
+ }
+ Text(station.address, style = MaterialTheme.typography.bodySmall)
+ val price = station.getPriceFor(uiState.selectedFuelType)
+ Text(
+ text = "${uiState.selectedFuelType}: $price €/L",
+ style = MaterialTheme.typography.bodyLarge,
+ color = MaterialTheme.colorScheme.primary,
+ fontWeight = FontWeight.Bold
+ )
+ }
+ IconButton(onClick = { selectedStationForMap = null }) {
+ Icon(Icons.Default.Close, null)
+ }
+ }
+ Spacer(modifier = Modifier.height(12.dp))
+ Button(
+ onClick = {
+ val uri = "waze://?ll=${station.latitude},${station.longitude}&navigate=yes"
+ val intent = Intent(Intent.ACTION_VIEW, Uri.parse(uri))
+ try {
+ context.startActivity(intent)
+ } catch (e: Exception) {
+ // Fallback a Google Maps si Waze no está instalado
+ val gmapsUri = "google.navigation:q=${station.latitude},${station.longitude}"
+ context.startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(gmapsUri)))
+ }
+ },
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Icon(Icons.Default.Directions, null)
+ Spacer(modifier = Modifier.width(8.dp))
+ Text(stringResource(R.string.go_to_waze))
+ }
+ }
+ }
+ }
+ }
}
}
}
@@ -258,16 +371,17 @@ fun GasStationApp(viewModel: GasStationViewModel = viewModel()) {
FuelType.BIODIESEL -> stringResource(R.string.biodiesel_label)
},
onDismiss = { showFuelDialog = null },
- onConfirm = { liters, total, type, pricePerLiter ->
+ onConfirm = { liters, total, type, pricePerLiter, date, odo ->
val record = FuelRecord(
- date = System.currentTimeMillis(),
+ date = date,
stationName = station.name,
address = station.address,
municipality = station.municipality,
liters = liters,
totalPrice = total,
pricePerLiter = pricePerLiter,
- fuelType = type
+ fuelType = type,
+ odometer = odo
)
viewModel.saveFuelRecord(record)
showFuelDialog = null
@@ -275,22 +389,41 @@ fun GasStationApp(viewModel: GasStationViewModel = viewModel()) {
)
}
- editRecordDialog?.let { record ->
- FuelLogDialog(
- stationName = record.stationName,
- initialLiters = record.liters.toString(),
- initialTotal = record.totalPrice.toString(),
- initialType = record.fuelType,
- onDismiss = { editRecordDialog = null },
- onConfirm = { liters, total, type, pricePerLiter ->
- val newRecord = record.copy(
- liters = liters,
- totalPrice = total,
- fuelType = type,
- pricePerLiter = pricePerLiter
- )
- viewModel.updateFuelRecord(record, newRecord)
- editRecordDialog = null
+ if (editRecordDialog != null) {
+ editRecordDialog?.let { record ->
+ FuelLogDialog(
+ stationName = record.stationName,
+ initialLiters = record.liters.toString(),
+ initialTotal = record.totalPrice.toString(),
+ initialType = record.fuelType,
+ initialDate = record.date,
+ initialOdometer = record.odometer?.toString() ?: "",
+ onDismiss = { editRecordDialog = null },
+ onConfirm = { liters, total, type, pricePerLiter, date, odo ->
+ val newRecord = record.copy(
+ date = date,
+ liters = liters,
+ totalPrice = total,
+ fuelType = type,
+ pricePerLiter = pricePerLiter,
+ odometer = odo
+ )
+ viewModel.updateFuelRecord(record, newRecord)
+ editRecordDialog = null
+ }
+ )
+ }
+ }
+
+ if (showHistoryFilter) {
+ HistoryFilterDialog(
+ records = uiState.fuelRecords,
+ currentMonth = uiState.filterMonth,
+ currentYear = uiState.filterYear,
+ onDismiss = { showHistoryFilter = false },
+ onConfirm = { m, y ->
+ viewModel.setHistoryFilters(m, y)
+ showHistoryFilter = false
}
)
}
@@ -303,23 +436,52 @@ fun GasStationMap(
stations: List,
routePoints: List,
userLocation: android.location.Location?,
- selectedFuelType: FuelType
+ selectedFuelType: FuelType,
+ favoriteIds: Set,
+ onMapMoved: (Double, Double) -> Unit,
+ onStationSelected: (GasStationInfo) -> Unit
) {
AndroidView(
factory = { context ->
MapView(context).apply {
- setTileSource(TileSourceFactory.MAPNIK)
+ setTileSource(org.osmdroid.tileprovider.tilesource.XYTileSource(
+ "CartoVoyager", 0, 20, 256, ".png",
+ arrayOf("https://a.basemaps.cartocdn.com/rastertiles/voyager/",
+ "https://b.basemaps.cartocdn.com/rastertiles/voyager/",
+ "https://c.basemaps.cartocdn.com/rastertiles/voyager/")
+ ))
setMultiTouchControls(true)
+ setLayerType(android.view.View.LAYER_TYPE_SOFTWARE, null)
+
controller.setZoom(15.0)
userLocation?.let {
controller.setCenter(GeoPoint(it.latitude, it.longitude))
}
+
+ // Detectar movimiento del mapa
+ addMapListener(object : org.osmdroid.events.MapListener {
+ override fun onScroll(event: org.osmdroid.events.ScrollEvent?): Boolean {
+ onMapMoved(mapCenter.latitude, mapCenter.longitude)
+ return true
+ }
+ override fun onZoom(event: org.osmdroid.events.ZoomEvent?): Boolean {
+ onMapMoved(mapCenter.latitude, mapCenter.longitude)
+ return true
+ }
+ })
}
},
update = { mapView ->
mapView.overlays.clear()
- // 1. Trazar la ruta (Polilínea)
+ // 1. Centrar en el usuario si no hay ruta y es la primera vez que entra al mapa
+ if (routePoints.isEmpty() && userLocation != null && mapView.tag == null) {
+ mapView.controller.setCenter(GeoPoint(userLocation.latitude, userLocation.longitude))
+ mapView.controller.setZoom(15.0)
+ mapView.tag = "centered"
+ }
+
+ // 2. Trazar la ruta (Polilínea)
if (routePoints.isNotEmpty()) {
val line = Polyline(mapView)
line.setPoints(routePoints.map { GeoPoint(it.latitude, it.longitude) })
@@ -327,33 +489,60 @@ fun GasStationMap(
line.outlinePaint.strokeWidth = 10f
mapView.overlays.add(line)
- // Zoom para ajustar a la ruta la primera vez
- if (mapView.tag == null) {
+ // Zoom para ajustar a la ruta si es nueva
+ if (mapView.tag != "route_zoomed") {
val points = routePoints.map { GeoPoint(it.latitude, it.longitude) }
- mapView.zoomToBoundingBox(org.osmdroid.util.BoundingBox.fromGeoPoints(points), true)
- mapView.tag = "zoomed"
+ if (points.isNotEmpty()) {
+ mapView.zoomToBoundingBox(org.osmdroid.util.BoundingBox.fromGeoPoints(points), true)
+ mapView.tag = "route_zoomed"
+ }
}
+ } else if (mapView.tag == "route_zoomed") {
+ // Si se desactivó la ruta, permitir volver a centrar en el usuario
+ mapView.tag = null
}
- // 2. Marcador de usuario
+ // 3. Marcador de usuario
userLocation?.let {
val userMarker = Marker(mapView)
userMarker.position = GeoPoint(it.latitude, it.longitude)
userMarker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
userMarker.title = "Tu ubicación"
+ userMarker.icon = ContextCompat.getDrawable(mapView.context, org.osmdroid.library.R.drawable.person)
mapView.overlays.add(userMarker)
}
- // 3. Marcadores de gasolineras
- stations.forEach { station ->
- val marker = Marker(mapView)
- marker.position = GeoPoint(station.latitude, station.longitude)
- marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
-
- val price = station.getPriceFor(selectedFuelType) ?: 0.0
+ // 4. Marcadores de gasolineras con colores
+ stations.forEach { station ->
+ val marker = Marker(mapView)
+ marker.position = GeoPoint(station.latitude, station.longitude)
+ marker.setAnchor(Marker.ANCHOR_CENTER, Marker.ANCHOR_BOTTOM)
+
+ val price = station.getPriceFor(selectedFuelType) ?: 0.0
marker.title = "${station.name}: $price €/L"
marker.subDescription = station.address
+ val isFavorite = favoriteIds.contains(station.id)
+
+ marker.setOnMarkerClickListener { m, _ ->
+ onStationSelected(station)
+ m.showInfoWindow()
+ true
+ }
+
+ val iconRes = org.osmdroid.library.R.drawable.marker_default
+ val drawable = ContextCompat.getDrawable(mapView.context, iconRes)?.mutate()
+ drawable?.let {
+ val color = when {
+ isFavorite -> android.graphics.Color.MAGENTA // Color especial para favoritos
+ station.priceCategory == PriceCategory.CHEAP -> android.graphics.Color.rgb(46, 125, 50) // Verde
+ station.priceCategory == PriceCategory.EXPENSIVE -> android.graphics.Color.rgb(198, 40, 40) // Rojo
+ else -> android.graphics.Color.rgb(251, 192, 45) // Amarillo
+ }
+ it.setTint(color)
+ marker.icon = it
+ }
+
mapView.overlays.add(marker)
}
@@ -405,9 +594,11 @@ fun GasStationList(
stations: List,
visibleCount: Int,
selectedFuelType: FuelType,
+ favoriteIds: Set,
onLogClick: (GasStationInfo) -> Unit,
onShowMore: () -> Unit,
- onShowLess: () -> Unit
+ onShowLess: () -> Unit,
+ onToggleFavorite: (String) -> Unit
) {
val context = LocalContext.current
val visibleStations = stations.take(visibleCount)
@@ -417,11 +608,13 @@ fun GasStationList(
GasStationItem(
station = station,
selectedFuelType = selectedFuelType,
+ isFavorite = favoriteIds.contains(station.id),
onClick = {
val uri = Uri.parse("geo:${station.latitude},${station.longitude}?q=${station.latitude},${station.longitude}(${Uri.encode(station.name)})")
context.startActivity(Intent(Intent.ACTION_VIEW, uri))
},
- onLogClick = { onLogClick(station) }
+ onLogClick = { onLogClick(station) },
+ onToggleFavorite = { onToggleFavorite(station.id) }
)
}
@@ -454,17 +647,133 @@ fun GasStationList(
}
@Composable
-fun FuelHistoryScreen(records: List, onDelete: (FuelRecord) -> Unit, onEdit: (FuelRecord) -> Unit) {
- if (records.isEmpty()) {
- Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { Text(stringResource(R.string.no_records)) }
- } else {
- LazyColumn(contentPadding = PaddingValues(16.dp)) {
- items(records) { record ->
+fun FuelHistoryScreen(
+ records: List,
+ filterMonth: Int?,
+ filterYear: Int?,
+ onDelete: (FuelRecord) -> Unit,
+ onEdit: (FuelRecord) -> Unit
+) {
+ val context = LocalContext.current
+ val calInstance = java.util.Calendar.getInstance()
+ val nowMonth = calInstance.get(java.util.Calendar.MONTH)
+ val nowYear = calInstance.get(java.util.Calendar.YEAR)
+
+ val allRecordsSorted = records.sortedByDescending { it.date }
+ val filteredRecords = allRecordsSorted.filter {
+ val cal = java.util.Calendar.getInstance().apply { timeInMillis = it.date }
+ val mMatch = filterMonth == null || cal.get(java.util.Calendar.MONTH) == filterMonth
+ val yMatch = filterYear == null || cal.get(java.util.Calendar.YEAR) == filterYear
+ mMatch && yMatch
+ }
+
+ val displayMonth = filterMonth ?: nowMonth
+ val displayYear = filterYear ?: nowYear
+
+ val monthlyTotal = records.filter {
+ val cal = java.util.Calendar.getInstance().apply { timeInMillis = it.date }
+ cal.get(java.util.Calendar.MONTH) == displayMonth && cal.get(java.util.Calendar.YEAR) == displayYear
+ }.sumOf { it.totalPrice }
+
+ val yearlyTotal = records.filter {
+ val cal = java.util.Calendar.getInstance().apply { timeInMillis = it.date }
+ cal.get(java.util.Calendar.YEAR) == displayYear
+ }.sumOf { it.totalPrice }
+
+ LazyColumn(contentPadding = PaddingValues(16.dp)) {
+ item {
+ Column {
+ if (filterMonth != null || filterYear != null) {
+ val monthResId = if (filterMonth != null) context.resources.getIdentifier("month_$filterMonth", "string", context.packageName) else 0
+ val monthName = if (monthResId != 0) stringResource(monthResId) else stringResource(R.string.all_months)
+ val yearName = filterYear?.toString() ?: stringResource(R.string.all_years)
+ Text(
+ text = "Viendo: $monthName $yearName",
+ style = MaterialTheme.typography.labelLarge,
+ color = MaterialTheme.colorScheme.primary,
+ modifier = Modifier.padding(bottom = 8.dp)
+ )
+ }
+ Row(modifier = Modifier.fillMaxWidth().padding(bottom = 16.dp), horizontalArrangement = Arrangement.spacedBy(16.dp)) {
+ Card(
+ modifier = Modifier.weight(1f),
+ colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.primaryContainer)
+ ) {
+ Column(modifier = Modifier.padding(12.dp)) {
+ Text(stringResource(R.string.monthly_spend), style = MaterialTheme.typography.labelMedium)
+ Text("${String.format(Locale.getDefault(), "%.2f", monthlyTotal)} €", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
+ }
+ }
+ Card(
+ modifier = Modifier.weight(1f),
+ colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.secondaryContainer)
+ ) {
+ Column(modifier = Modifier.padding(12.dp)) {
+ Text(stringResource(R.string.yearly_spend), style = MaterialTheme.typography.labelMedium)
+ Text("${String.format(Locale.getDefault(), "%.2f", yearlyTotal)} €", style = MaterialTheme.typography.titleLarge, fontWeight = FontWeight.Bold)
+ }
+ }
+ }
+ }
+ }
+
+ if (filteredRecords.isEmpty()) {
+ item {
+ Box(modifier = Modifier.fillParentMaxSize(), contentAlignment = Alignment.Center) {
+ Text(stringResource(R.string.no_records))
+ }
+ }
+ } else {
+ items(filteredRecords) { record ->
+ // Buscar el registro inmediatamente anterior en la lista completa (cronológicamente)
+ val fullIndex = allRecordsSorted.indexOf(record)
+ var consumption: Double? = null
+ if (record.odometer != null && fullIndex != -1 && fullIndex < allRecordsSorted.size - 1) {
+ val prevRecord = allRecordsSorted[fullIndex + 1]
+ if (prevRecord.odometer != null && record.odometer > prevRecord.odometer) {
+ val kmDiff = record.odometer - prevRecord.odometer
+ consumption = (record.liters / kmDiff) * 100
+ }
+ }
+
Card(modifier = Modifier.fillMaxWidth().padding(vertical = 4.dp), colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surfaceVariant)) {
Column(modifier = Modifier.padding(16.dp)) {
Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
Column(modifier = Modifier.weight(1f)) {
- Text(record.stationName, style = MaterialTheme.typography.titleMedium)
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ val isRepsol = record.stationName.contains("REPSOL", ignoreCase = true)
+ val (bColor, bInitial) = when {
+ isRepsol -> Color(0xFFE30613) to "R"
+ record.stationName.contains("CEPSA", ignoreCase = true) -> Color(0xFFEC0000) to "C"
+ record.stationName.contains("BP", ignoreCase = true) -> Color(0xFF00A94F) to "B"
+ record.stationName.contains("GALP", ignoreCase = true) -> Color(0xFFFF6B00) to "G"
+ record.stationName.contains("SHELL", ignoreCase = true) -> Color(0xFFFFD500) to "S"
+ record.stationName.contains("MOEVE", ignoreCase = true) -> Color(0xFF555555) to "M"
+ record.stationName.contains("BALLENOIL", ignoreCase = true) -> Color(0xFF003399) to "B"
+ record.stationName.contains("PLENERGY", ignoreCase = true) -> Color(0xFF2E7D32) to "P"
+ else -> MaterialTheme.colorScheme.surfaceVariant to (record.stationName.firstOrNull()?.toString()?.uppercase() ?: "?")
+ }
+ Surface(
+ color = bColor,
+ modifier = Modifier.size(28.dp),
+ shape = CircleShape
+ ) {
+ if (isRepsol) {
+ Image(
+ painter = painterResource(id = R.drawable.repsol_logo),
+ contentDescription = "Repsol",
+ modifier = Modifier.fillMaxSize(),
+ contentScale = ContentScale.Crop
+ )
+ } else {
+ Box(contentAlignment = Alignment.Center) {
+ Text(bInitial, style = MaterialTheme.typography.labelSmall, color = Color.White, fontWeight = FontWeight.Bold)
+ }
+ }
+ }
+ Spacer(modifier = Modifier.width(8.dp))
+ Text(record.stationName, style = MaterialTheme.typography.titleMedium)
+ }
Text("${record.municipality} - ${record.address}", style = MaterialTheme.typography.bodySmall)
}
Row {
@@ -474,9 +783,29 @@ fun FuelHistoryScreen(records: List, onDelete: (FuelRecord) -> Unit,
}
}
Text(android.text.format.DateFormat.format("dd/MM/yyyy", record.date).toString(), style = MaterialTheme.typography.labelSmall)
+
Spacer(modifier = Modifier.height(4.dp))
- Text("${record.fuelType}: ${record.liters} L x ${record.pricePerLiter} €/L", style = MaterialTheme.typography.bodyMedium)
- Text("Total: ${record.totalPrice} €", style = MaterialTheme.typography.titleLarge, color = MaterialTheme.colorScheme.primary)
+ Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween) {
+ Column {
+ Text("${record.fuelType}: ${record.liters} L x ${record.pricePerLiter} €/L", style = MaterialTheme.typography.bodyMedium)
+ Text("Total: ${record.totalPrice} €", style = MaterialTheme.typography.titleLarge, color = MaterialTheme.colorScheme.primary)
+ }
+ consumption?.let {
+ Column(horizontalAlignment = Alignment.End) {
+ Text(stringResource(R.string.consumption), style = MaterialTheme.typography.labelSmall)
+ Text(
+ text = stringResource(R.string.l_per_100km, String.format(Locale.getDefault(), "%.1f", it)),
+ style = MaterialTheme.typography.titleMedium,
+ color = MaterialTheme.colorScheme.tertiary,
+ fontWeight = FontWeight.Bold
+ )
+ }
+ }
+ }
+
+ record.odometer?.let {
+ Text("KM: ${String.format(Locale.getDefault(), "%.0f", it)}", style = MaterialTheme.typography.labelSmall, color = MaterialTheme.colorScheme.secondary)
+ }
}
}
}
@@ -484,6 +813,74 @@ fun FuelHistoryScreen(records: List, onDelete: (FuelRecord) -> Unit,
}
}
+@Composable
+fun HistoryFilterDialog(
+ records: List,
+ currentMonth: Int?,
+ currentYear: Int?,
+ onDismiss: () -> Unit,
+ onConfirm: (Int?, Int?) -> Unit
+) {
+ var selectedMonth by remember { mutableStateOf(currentMonth) }
+ var selectedYear by remember { mutableStateOf(currentYear) }
+
+ val years = records.map {
+ java.util.Calendar.getInstance().apply { timeInMillis = it.date }.get(java.util.Calendar.YEAR)
+ }.distinct().sortedDescending()
+
+ AlertDialog(
+ onDismissRequest = onDismiss,
+ title = { Text(stringResource(R.string.filter_history)) },
+ text = {
+ Column {
+ Text(stringResource(R.string.month), style = MaterialTheme.typography.labelMedium)
+ val months = (0..11)
+ val context = LocalContext.current
+ SecondaryScrollableTabRow(
+ selectedTabIndex = if (selectedMonth == null) 0 else selectedMonth!! + 1,
+ edgePadding = 0.dp,
+ divider = {},
+ containerColor = Color.Transparent
+ ) {
+ Tab(selected = selectedMonth == null, onClick = { selectedMonth = null }) {
+ Text(stringResource(R.string.all_months), modifier = Modifier.padding(8.dp))
+ }
+ months.forEach { m ->
+ val mResId = context.resources.getIdentifier("month_$m", "string", context.packageName)
+ Tab(selected = selectedMonth == m, onClick = { selectedMonth = m }) {
+ Text(if (mResId != 0) stringResource(mResId) else "", modifier = Modifier.padding(8.dp))
+ }
+ }
+ }
+
+ Spacer(modifier = Modifier.height(16.dp))
+ Text(stringResource(R.string.year), style = MaterialTheme.typography.labelMedium)
+ SecondaryScrollableTabRow(
+ selectedTabIndex = if (selectedYear == null) 0 else years.indexOf(selectedYear!!) + 1,
+ edgePadding = 0.dp,
+ divider = {},
+ containerColor = Color.Transparent
+ ) {
+ Tab(selected = selectedYear == null, onClick = { selectedYear = null }) {
+ Text(stringResource(R.string.all_years), modifier = Modifier.padding(8.dp))
+ }
+ years.forEach { y ->
+ Tab(selected = selectedYear == y, onClick = { selectedYear = y }) {
+ Text(y.toString(), modifier = Modifier.padding(8.dp))
+ }
+ }
+ }
+ }
+ },
+ confirmButton = {
+ Button(onClick = { onConfirm(selectedMonth, selectedYear) }) { Text("Filtrar") }
+ },
+ dismissButton = {
+ TextButton(onClick = { onConfirm(null, null) }) { Text("Limpiar") }
+ }
+ )
+}
+
@Composable
fun FuelLogDialog(
stationName: String,
@@ -499,12 +896,17 @@ fun FuelLogDialog(
initialLiters: String = "",
initialTotal: String = "",
initialType: String = "Diésel A",
+ initialDate: Long = System.currentTimeMillis(),
+ initialOdometer: String = "",
onDismiss: () -> Unit,
- onConfirm: (Double, Double, String, Double) -> Unit
+ onConfirm: (Double, Double, String, Double, Long, Double?) -> Unit
) {
val context = LocalContext.current
var liters by remember { mutableStateOf(initialLiters) }
var total by remember { mutableStateOf(initialTotal) }
+ var odometer by remember { mutableStateOf(initialOdometer) }
+ var selectedDate by remember { mutableStateOf(initialDate) }
+ var showDatePicker by remember { mutableStateOf(false) }
val dieselLabel = stringResource(R.string.diesel_a_label)
val premiumLabel = stringResource(R.string.premium_label)
@@ -538,6 +940,24 @@ fun FuelLogDialog(
}
}
+ if (showDatePicker) {
+ val datePickerState = rememberDatePickerState(initialSelectedDateMillis = selectedDate)
+ DatePickerDialog(
+ onDismissRequest = { showDatePicker = false },
+ confirmButton = {
+ TextButton(onClick = {
+ datePickerState.selectedDateMillis?.let { selectedDate = it }
+ showDatePicker = false
+ }) { Text("OK") }
+ },
+ dismissButton = {
+ TextButton(onClick = { showDatePicker = false }) { Text(stringResource(R.string.cancel)) }
+ }
+ ) {
+ DatePicker(state = datePickerState)
+ }
+ }
+
AlertDialog(
onDismissRequest = onDismiss,
title = { Text(stringResource(R.string.log_fuel)) },
@@ -546,6 +966,18 @@ fun FuelLogDialog(
Text(stationName, style = MaterialTheme.typography.bodyLarge)
Spacer(modifier = Modifier.height(16.dp))
+ // Date Selection
+ Text(stringResource(R.string.date_label), style = MaterialTheme.typography.labelMedium)
+ OutlinedButton(
+ onClick = { showDatePicker = true },
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Icon(Icons.Default.DateRange, null)
+ Spacer(modifier = Modifier.width(8.dp))
+ Text(android.text.format.DateFormat.format("dd/MM/yyyy", selectedDate).toString())
+ }
+
+ Spacer(modifier = Modifier.height(8.dp))
Text(stringResource(R.string.fuel_type), style = MaterialTheme.typography.labelMedium)
FlowRow(
modifier = Modifier.fillMaxWidth(),
@@ -646,13 +1078,20 @@ fun FuelLogDialog(
label = { Text(stringResource(R.string.total_price)) },
modifier = Modifier.fillMaxWidth()
)
+ OutlinedTextField(
+ value = odometer,
+ onValueChange = { odometer = it },
+ label = { Text(stringResource(R.string.odometer)) },
+ modifier = Modifier.fillMaxWidth()
+ )
}
},
confirmButton = {
Button(onClick = {
val l = liters.toDoubleOrNull() ?: 0.0
val t = total.toDoubleOrNull() ?: 0.0
- if (l > 0 && t > 0) onConfirm(l, t, selectedType, if (l > 0) t / l else currentPrice)
+ val o = odometer.toDoubleOrNull()
+ if (l > 0 && t > 0) onConfirm(l, t, selectedType, if (l > 0) t / l else currentPrice, selectedDate, o)
}) { Text(stringResource(R.string.save)) }
},
dismissButton = { TextButton(onClick = onDismiss) { Text(stringResource(R.string.cancel)) } }
@@ -660,20 +1099,31 @@ fun FuelLogDialog(
}
@Composable
-fun GasStationItem(station: GasStationInfo, selectedFuelType: FuelType, onClick: () -> Unit, onLogClick: () -> Unit) {
+fun GasStationItem(
+ station: GasStationInfo,
+ selectedFuelType: FuelType,
+ isFavorite: Boolean,
+ onClick: () -> Unit,
+ onLogClick: () -> Unit,
+ onToggleFavorite: () -> Unit
+) {
val priceColor = when (station.priceCategory) {
PriceCategory.CHEAP -> Color(0xFF2E7D32)
PriceCategory.NORMAL -> MaterialTheme.colorScheme.primary
PriceCategory.EXPENSIVE -> Color(0xFFC62828)
}
- val brandColor = when {
- station.name.contains("REPSOL", ignoreCase = true) -> Color(0xFFE30613)
- station.name.contains("CEPSA", ignoreCase = true) -> Color(0xFFEC0000)
- station.name.contains("BP", ignoreCase = true) -> Color(0xFF00A94F)
- station.name.contains("GALP", ignoreCase = true) -> Color(0xFFFF6B00)
- station.name.contains("SHELL", ignoreCase = true) -> Color(0xFFFFD500)
- else -> MaterialTheme.colorScheme.surfaceVariant
+ val isRepsol = station.name.contains("REPSOL", ignoreCase = true)
+ val (brandColor, brandInitial) = when {
+ isRepsol -> Color(0xFFE30613) to "R"
+ station.name.contains("CEPSA", ignoreCase = true) -> Color(0xFFEC0000) to "C"
+ station.name.contains("BP", ignoreCase = true) -> Color(0xFF00A94F) to "B"
+ station.name.contains("GALP", ignoreCase = true) -> Color(0xFFFF6B00) to "G"
+ station.name.contains("SHELL", ignoreCase = true) -> Color(0xFFFFD500) to "S"
+ station.name.contains("MOEVE", ignoreCase = true) -> Color(0xFF555555) to "M"
+ station.name.contains("BALLENOIL", ignoreCase = true) -> Color(0xFF003399) to "B"
+ station.name.contains("PLENERGY", ignoreCase = true) -> Color(0xFF2E7D32) to "P"
+ else -> MaterialTheme.colorScheme.surfaceVariant to (station.name.firstOrNull()?.toString()?.uppercase() ?: "?")
}
Card(modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp, vertical = 6.dp).clickable { onClick() }, elevation = CardDefaults.cardElevation(defaultElevation = 2.dp)) {
@@ -681,8 +1131,39 @@ fun GasStationItem(station: GasStationInfo, selectedFuelType: FuelType, onClick:
Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.SpaceBetween) {
Column(modifier = Modifier.weight(1f)) {
Row(verticalAlignment = Alignment.CenterVertically) {
- Surface(color = brandColor, modifier = Modifier.size(12.dp).padding(end = 8.dp), shape = androidx.compose.foundation.shape.CircleShape) {}
+ Surface(
+ color = brandColor,
+ modifier = Modifier.size(32.dp),
+ shape = CircleShape
+ ) {
+ if (isRepsol) {
+ Image(
+ painter = painterResource(id = R.drawable.repsol_logo),
+ contentDescription = "Repsol",
+ modifier = Modifier.fillMaxSize(),
+ contentScale = ContentScale.Crop
+ )
+ } else {
+ Box(contentAlignment = Alignment.Center) {
+ Text(
+ text = brandInitial,
+ style = MaterialTheme.typography.titleSmall,
+ color = if (brandColor == MaterialTheme.colorScheme.surfaceVariant) MaterialTheme.colorScheme.onSurface else Color.White,
+ fontWeight = FontWeight.Bold
+ )
+ }
+ }
+ }
+ Spacer(modifier = Modifier.width(12.dp))
Text(text = "${station.name} - ${station.municipality}", style = MaterialTheme.typography.titleMedium, maxLines = 1, modifier = Modifier.weight(1f))
+ IconButton(onClick = onToggleFavorite, modifier = Modifier.size(24.dp)) {
+ Icon(
+ imageVector = if (isFavorite) Icons.Default.Favorite else Icons.Default.FavoriteBorder,
+ contentDescription = null,
+ tint = if (isFavorite) Color.Red else LocalContentColor.current,
+ modifier = Modifier.size(18.dp)
+ )
+ }
}
Text(text = station.address, style = MaterialTheme.typography.bodySmall, maxLines = 1)
if (station.horario.isNotEmpty()) {
diff --git a/app/src/main/java/com/example/prueba/data/GasStationRepository.kt b/app/src/main/java/com/example/prueba/data/GasStationRepository.kt
index 3579af3..a0c5e97 100644
--- a/app/src/main/java/com/example/prueba/data/GasStationRepository.kt
+++ b/app/src/main/java/com/example/prueba/data/GasStationRepository.kt
@@ -18,7 +18,7 @@ class GasStationRepository {
private val client = OkHttpClient.Builder()
.addInterceptor { chain ->
val request = chain.request().newBuilder()
- .header("User-Agent", "GasolinerasApp/1.0 (Android; Contact: support@example.com)")
+ .header("User-Agent", "Mozilla/5.0 (Android) GasolinerasApp/1.0")
.build()
chain.proceed(request)
}
@@ -138,8 +138,8 @@ class GasStationRepository {
} else null
} ?: emptyList()
- // Radio máximo: 30km local / 400km viaje
- val finalMaxDist = if (destinationLocation == null) 30.0 else 400.0
+ // Radio máximo: 30km local / 1000km viaje
+ val finalMaxDist = if (destinationLocation == null) 30.0 else 1000.0
val filtered = allStations.filter { (it.distance / 1000.0) <= finalMaxDist }
if (filtered.isEmpty()) return@withContext emptyList() to emptyList()
diff --git a/app/src/main/java/com/example/prueba/ui/GasStationViewModel.kt b/app/src/main/java/com/example/prueba/ui/GasStationViewModel.kt
index 525183b..ec6a7e6 100644
--- a/app/src/main/java/com/example/prueba/ui/GasStationViewModel.kt
+++ b/app/src/main/java/com/example/prueba/ui/GasStationViewModel.kt
@@ -10,6 +10,7 @@ import com.example.prueba.R
import com.example.prueba.data.FuelType
import com.example.prueba.data.GasStationInfo
import com.example.prueba.data.GasStationRepository
+import com.example.prueba.utils.FavoriteStorage
import com.example.prueba.utils.FuelRecord
import com.example.prueba.utils.FuelStorage
import com.example.prueba.utils.LocationStorage
@@ -34,7 +35,12 @@ data class GasStationUiState(
val destinationQuery: String = "",
val isRouteActive: Boolean = false,
val destinationLocation: Location? = null,
- val routePoints: List = emptyList()
+ val routePoints: List = emptyList(),
+ val mapCenterLocation: Location? = null,
+ val favoriteIds: Set = emptySet(),
+ val showOnlyFavorites: Boolean = false,
+ val filterMonth: Int? = null, // null for all
+ val filterYear: Int? = null // null for all
)
class GasStationViewModel(
@@ -56,6 +62,28 @@ class GasStationViewModel(
fun selectTab(index: Int) {
_uiState.update { it.copy(selectedTab = index, errorMessage = null, visibleStationsCount = 15) }
+ if (index == 0 || index == 2) {
+ loadFavorites()
+ }
+ }
+
+ fun toggleFavorite(stationId: String) {
+ FavoriteStorage.toggleFavorite(context, stationId)
+ loadFavorites()
+ }
+
+ fun toggleFavoritesFilter() {
+ _uiState.update { it.copy(showOnlyFavorites = !it.showOnlyFavorites) }
+ refreshStations()
+ }
+
+ fun setHistoryFilters(month: Int?, year: Int?) {
+ _uiState.update { it.copy(filterMonth = month, filterYear = year) }
+ }
+
+ private fun loadFavorites() {
+ val favs = FavoriteStorage.getFavorites(context)
+ _uiState.update { it.copy(favoriteIds = favs) }
}
fun selectFuelType(type: FuelType) {
@@ -177,18 +205,39 @@ class GasStationViewModel(
_uiState.update { it.copy(visibleStationsCount = 15) }
}
+ fun updateMapCenter(lat: Double, lon: Double) {
+ val loc = Location("").apply {
+ latitude = lat
+ longitude = lon
+ }
+ _uiState.update { it.copy(mapCenterLocation = loc) }
+ }
+
+ fun searchAtMapCenter() {
+ val center = _uiState.value.mapCenterLocation ?: return
+ viewModelScope.launch {
+ _uiState.update { it.copy(isLoading = true, errorMessage = null) }
+ val (stations, _) = repository.getCheapestAndNearest(
+ userLocation = center,
+ fuelType = _uiState.value.selectedFuelType
+ )
+ _uiState.update { it.copy(stations = stations, isLoading = false) }
+ }
+ }
+
fun refreshStations(forceRefresh: Boolean = false) {
val state = _uiState.value
val loc = state.lastLocation ?: return
- if (state.selectedTab != 0) return
+ // Refrescar solo en pestañas de datos
+ if (state.selectedTab != 0 && state.selectedTab != 2) return
viewModelScope.launch {
try {
if (forceRefresh) _uiState.update { it.copy(isRefreshing = true, errorMessage = null) }
else if (state.stations.isEmpty()) _uiState.update { it.copy(isLoading = true, errorMessage = null) }
- // 1. Fast load
+ // 1. Carga Rápida (Línea recta)
val (fastStations, points) = repository.getCheapestAndNearest(
userLocation = loc,
getRealDistances = false,
@@ -197,23 +246,27 @@ class GasStationViewModel(
destinationLocation = if (state.isRouteActive) state.destinationLocation else null
)
- if (fastStations.isEmpty() && forceRefresh) {
- _uiState.update { it.copy(errorMessage = context.getString(R.string.no_results)) }
- }
+ // Aplicar el filtro de favoritos ANTES de actualizar la UI para evitar parpadeos
+ val finalFast = if (_uiState.value.showOnlyFavorites) {
+ fastStations.filter { _uiState.value.favoriteIds.contains(it.id) }
+ } else fastStations
- _uiState.update { it.copy(stations = fastStations, routePoints = points, isLoading = false) }
+ _uiState.update { it.copy(stations = finalFast, routePoints = points, isLoading = false) }
- // 2. Precision load
+ // 2. Carga de Precisión (Carretera)
val (realStations, _) = repository.getCheapestAndNearest(
userLocation = loc,
getRealDistances = true,
- forceRefresh = false, // don't force twice
+ forceRefresh = false,
fuelType = state.selectedFuelType,
destinationLocation = if (state.isRouteActive) state.destinationLocation else null
)
if (realStations.isNotEmpty()) {
- _uiState.update { it.copy(stations = realStations, isRefreshing = false) }
+ val finalReal = if (_uiState.value.showOnlyFavorites) {
+ realStations.filter { _uiState.value.favoriteIds.contains(it.id) }
+ } else realStations
+ _uiState.update { it.copy(stations = finalReal, isRefreshing = false) }
} else {
_uiState.update { it.copy(isRefreshing = false) }
}
@@ -230,15 +283,20 @@ class GasStationViewModel(
}
init {
+ loadFavorites()
viewModelScope.launch {
combine(
_uiState.map { it.lastLocation }.distinctUntilChanged(),
_uiState.map { it.selectedTab }.distinctUntilChanged(),
_uiState.map { it.selectedFuelType }.distinctUntilChanged(),
_uiState.map { it.isRouteActive }.distinctUntilChanged(),
- _uiState.map { it.destinationLocation }.distinctUntilChanged()
- ) { lastLoc, tab, _, _, _ ->
- if (lastLoc != null && tab == 0) {
+ _uiState.map { it.destinationLocation }.distinctUntilChanged(),
+ _uiState.map { it.showOnlyFavorites }.distinctUntilChanged()
+ ) { args ->
+ val lastLoc = args[0] as? Location
+ val tab = args[1] as Int
+ // Ahora refresca tanto en pestaña 0 (Lista) como en pestaña 2 (Mapa)
+ if (lastLoc != null && (tab == 0 || tab == 2)) {
refreshStations()
}
}.collect()
diff --git a/app/src/main/java/com/example/prueba/utils/FavoriteStorage.kt b/app/src/main/java/com/example/prueba/utils/FavoriteStorage.kt
new file mode 100644
index 0000000..5665fd2
--- /dev/null
+++ b/app/src/main/java/com/example/prueba/utils/FavoriteStorage.kt
@@ -0,0 +1,30 @@
+package com.example.prueba.utils
+
+import android.content.Context
+
+object FavoriteStorage {
+ private const val PREFS_NAME = "favorite_prefs"
+ private const val KEY_FAVORITES = "favorite_ids"
+
+ fun toggleFavorite(context: Context, stationId: String) {
+ val favorites = getFavorites(context).toMutableSet()
+ if (favorites.contains(stationId)) {
+ favorites.remove(stationId)
+ } else {
+ favorites.add(stationId)
+ }
+ saveFavorites(context, favorites)
+ }
+
+ fun getFavorites(context: Context): Set {
+ return context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+ .getStringSet(KEY_FAVORITES, emptySet()) ?: emptySet()
+ }
+
+ private fun saveFavorites(context: Context, favorites: Set) {
+ context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
+ .edit()
+ .putStringSet(KEY_FAVORITES, favorites)
+ .apply()
+ }
+}
diff --git a/app/src/main/java/com/example/prueba/utils/FuelStorage.kt b/app/src/main/java/com/example/prueba/utils/FuelStorage.kt
index 8fdf339..4767eff 100644
--- a/app/src/main/java/com/example/prueba/utils/FuelStorage.kt
+++ b/app/src/main/java/com/example/prueba/utils/FuelStorage.kt
@@ -12,7 +12,8 @@ data class FuelRecord(
val liters: Double,
val totalPrice: Double,
val pricePerLiter: Double,
- val fuelType: String = "Diésel A"
+ val fuelType: String = "Diésel A",
+ val odometer: Double? = null
)
object FuelStorage {
diff --git a/app/src/main/res/drawable/repsol_logo.jpeg b/app/src/main/res/drawable/repsol_logo.jpeg
new file mode 100644
index 0000000..6ad28c1
Binary files /dev/null and b/app/src/main/res/drawable/repsol_logo.jpeg differ
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 3b05225..a808c61 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -36,4 +36,34 @@
GNL
Diésel B
Biodiesel
+ Buscar en esta zona
+ Ir en Waze
+ Favoritos
+ Añadir a favoritos
+ Quitar de favoritos
+ Mostrar solo favoritos
+ Fecha
+ Seleccionar fecha
+ Gasto este mes
+ Gasto este año
+ Filtrar historial
+ Todos los meses
+ Todos los años
+ Mes
+ Año
+ Enero
+ Febrero
+ Marzo
+ Abril
+ Mayo
+ Junio
+ Julio
+ Agosto
+ Septiembre
+ Octubre
+ Noviembre
+ Diciembre
+ Kilómetros totales (Opcional)
+ Consumo
+ %1$s L/100km
\ No newline at end of file