Gasolinera app
This commit is contained in:
@@ -12,6 +12,7 @@ import androidx.activity.compose.rememberLauncherForActivityResult
|
|||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
import androidx.activity.enableEdgeToEdge
|
import androidx.activity.enableEdgeToEdge
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.foundation.clickable
|
import androidx.compose.foundation.clickable
|
||||||
import androidx.compose.foundation.layout.*
|
import androidx.compose.foundation.layout.*
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
@@ -23,18 +24,20 @@ import androidx.compose.material3.pulltorefresh.PullToRefreshBox
|
|||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import com.example.prueba.data.GasStationInfo
|
import com.example.prueba.data.GasStationInfo
|
||||||
import com.example.prueba.data.GasStationRepository
|
import com.example.prueba.data.GasStationRepository
|
||||||
|
import com.example.prueba.data.PriceCategory
|
||||||
import com.example.prueba.ui.theme.PruebaTheme
|
import com.example.prueba.ui.theme.PruebaTheme
|
||||||
import com.example.prueba.utils.FavoriteStorage
|
import com.example.prueba.utils.FavoriteStorage
|
||||||
import com.example.prueba.utils.FuelRecord
|
import com.example.prueba.utils.FuelRecord
|
||||||
import com.example.prueba.utils.FuelStorage
|
import com.example.prueba.utils.FuelStorage
|
||||||
import com.example.prueba.utils.LocationStorage
|
import com.example.prueba.utils.LocationStorage
|
||||||
import com.google.android.gms.location.*
|
import com.google.android.gms.location.*
|
||||||
import com.google.android.gms.tasks.CancellationTokenSource
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@@ -68,6 +71,8 @@ fun GasStationApp(repository: GasStationRepository) {
|
|||||||
var isLoading by remember { mutableStateOf(false) }
|
var isLoading by remember { mutableStateOf(false) }
|
||||||
var isRefreshing by remember { mutableStateOf(false) }
|
var isRefreshing by remember { mutableStateOf(false) }
|
||||||
var selectedTab by remember { mutableIntStateOf(0) }
|
var selectedTab by remember { mutableIntStateOf(0) }
|
||||||
|
var searchQuery by remember { mutableStateOf("") }
|
||||||
|
var isSearchActive by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
var hasPermission by remember {
|
var hasPermission by remember {
|
||||||
mutableStateOf(
|
mutableStateOf(
|
||||||
@@ -104,17 +109,21 @@ fun GasStationApp(repository: GasStationRepository) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(hasPermission) {
|
LaunchedEffect(hasPermission, searchQuery, selectedTab) {
|
||||||
if (hasPermission) {
|
if (hasPermission) {
|
||||||
val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
|
val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
|
||||||
LocationStorage.getLastLocation(context)?.let { lastLoc ->
|
val loc = lastLocation ?: LocationStorage.getLastLocation(context)
|
||||||
scope.launch {
|
|
||||||
updateAddress(lastLoc)
|
loc?.let {
|
||||||
stations = repository.getCheapestAndNearest(context, lastLoc)
|
stations = repository.getCheapestAndNearest(
|
||||||
}
|
context = context,
|
||||||
|
userLocation = it,
|
||||||
|
municipalityQuery = if (selectedTab == 0) searchQuery.ifBlank { null } else null,
|
||||||
|
onlyFavorites = (selectedTab == 1)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
isLoading = true
|
if (selectedTab != 2) isLoading = true
|
||||||
val locationRequest = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 10000)
|
val locationRequest = LocationRequest.Builder(Priority.PRIORITY_HIGH_ACCURACY, 10000)
|
||||||
.setMinUpdateDistanceMeters(500f)
|
.setMinUpdateDistanceMeters(500f)
|
||||||
.build()
|
.build()
|
||||||
@@ -124,11 +133,15 @@ fun GasStationApp(repository: GasStationRepository) {
|
|||||||
val location = result.lastLocation ?: return
|
val location = result.lastLocation ?: return
|
||||||
lastLocation = location
|
lastLocation = location
|
||||||
LocationStorage.saveLocation(context, location)
|
LocationStorage.saveLocation(context, location)
|
||||||
|
if (searchQuery.isBlank()) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
updateAddress(location)
|
updateAddress(location)
|
||||||
stations = repository.getCheapestAndNearest(context, location)
|
stations = repository.getCheapestAndNearest(context, location)
|
||||||
isLoading = false
|
isLoading = false
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
isLoading = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,10 +158,32 @@ fun GasStationApp(repository: GasStationRepository) {
|
|||||||
Scaffold(
|
Scaffold(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
topBar = {
|
topBar = {
|
||||||
|
Column {
|
||||||
CenterAlignedTopAppBar(
|
CenterAlignedTopAppBar(
|
||||||
title = {
|
title = {
|
||||||
|
if (isSearchActive) {
|
||||||
|
TextField(
|
||||||
|
value = searchQuery,
|
||||||
|
onValueChange = { searchQuery = it },
|
||||||
|
placeholder = { Text("Buscar municipio...") },
|
||||||
|
modifier = Modifier.fillMaxWidth().padding(horizontal = 16.dp),
|
||||||
|
singleLine = true,
|
||||||
|
colors = TextFieldDefaults.colors(
|
||||||
|
focusedContainerColor = Color.Transparent,
|
||||||
|
unfocusedContainerColor = Color.Transparent
|
||||||
|
),
|
||||||
|
trailingIcon = {
|
||||||
|
IconButton(onClick = {
|
||||||
|
searchQuery = ""
|
||||||
|
isSearchActive = false
|
||||||
|
}) {
|
||||||
|
Icon(Icons.Default.Close, contentDescription = "Cerrar")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
Column(horizontalAlignment = Alignment.CenterHorizontally) {
|
||||||
Text("Gasolineras Baratas", style = MaterialTheme.typography.titleSmall)
|
Text("Gasolineras Diésel", style = MaterialTheme.typography.titleSmall)
|
||||||
Text(
|
Text(
|
||||||
text = currentAddress,
|
text = currentAddress,
|
||||||
style = MaterialTheme.typography.bodyMedium,
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
@@ -157,13 +192,22 @@ fun GasStationApp(repository: GasStationRepository) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
actions = {
|
||||||
|
if (!isSearchActive && selectedTab != 2) {
|
||||||
|
IconButton(onClick = { isSearchActive = true }) {
|
||||||
|
Icon(Icons.Default.Search, contentDescription = "Buscar")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
bottomBar = {
|
bottomBar = {
|
||||||
NavigationBar {
|
NavigationBar {
|
||||||
NavigationBarItem(
|
NavigationBarItem(
|
||||||
icon = { Icon(Icons.Default.Place, "Gasolineras") },
|
icon = { Icon(Icons.Default.Place, "Gasolineras") },
|
||||||
label = { Text("Gasolineras") },
|
label = { Text("Diésel") },
|
||||||
selected = selectedTab == 0,
|
selected = selectedTab == 0,
|
||||||
onClick = { selectedTab = 0 }
|
onClick = { selectedTab = 0 }
|
||||||
)
|
)
|
||||||
@@ -203,7 +247,7 @@ fun GasStationApp(repository: GasStationRepository) {
|
|||||||
lastLocation?.let { loc ->
|
lastLocation?.let { loc ->
|
||||||
isRefreshing = true
|
isRefreshing = true
|
||||||
scope.launch {
|
scope.launch {
|
||||||
stations = repository.getCheapestAndNearest(context, loc, forceRefresh = true)
|
stations = repository.getCheapestAndNearest(context, loc, forceRefresh = true, municipalityQuery = if(searchQuery.isNotBlank()) searchQuery else null)
|
||||||
isRefreshing = false
|
isRefreshing = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -223,22 +267,23 @@ fun GasStationApp(repository: GasStationRepository) {
|
|||||||
repository = repository,
|
repository = repository,
|
||||||
lastLocation = lastLocation,
|
lastLocation = lastLocation,
|
||||||
onLogClick = { showFuelDialog = it },
|
onLogClick = { showFuelDialog = it },
|
||||||
onUpdateStations = { stations = it }
|
onUpdateStations = { stations = it },
|
||||||
|
searchQuery = searchQuery
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
val favoriteStations = stations.filter { it.isFavorite }
|
if (stations.isEmpty()) {
|
||||||
if (favoriteStations.isEmpty()) {
|
|
||||||
Text("No tienes gasolineras favoritas marcadas", modifier = Modifier.align(Alignment.Center))
|
Text("No tienes gasolineras favoritas marcadas", modifier = Modifier.align(Alignment.Center))
|
||||||
} else {
|
} else {
|
||||||
GasStationList(
|
GasStationList(
|
||||||
stations = favoriteStations,
|
stations = stations,
|
||||||
context = context,
|
context = context,
|
||||||
scope = scope,
|
scope = scope,
|
||||||
repository = repository,
|
repository = repository,
|
||||||
lastLocation = lastLocation,
|
lastLocation = lastLocation,
|
||||||
onLogClick = { showFuelDialog = it },
|
onLogClick = { showFuelDialog = it },
|
||||||
onUpdateStations = { stations = it }
|
onUpdateStations = { stations = it },
|
||||||
|
searchQuery = ""
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -262,7 +307,6 @@ fun GasStationApp(repository: GasStationRepository) {
|
|||||||
showFuelDialog?.let { station ->
|
showFuelDialog?.let { station ->
|
||||||
FuelLogDialog(
|
FuelLogDialog(
|
||||||
stationName = station.name,
|
stationName = station.name,
|
||||||
pricePerLiter = station.price,
|
|
||||||
onDismiss = { showFuelDialog = null },
|
onDismiss = { showFuelDialog = null },
|
||||||
onConfirm = { liters, total ->
|
onConfirm = { liters, total ->
|
||||||
val record = FuelRecord(
|
val record = FuelRecord(
|
||||||
@@ -284,7 +328,6 @@ fun GasStationApp(repository: GasStationRepository) {
|
|||||||
editRecordDialog?.let { record ->
|
editRecordDialog?.let { record ->
|
||||||
FuelLogDialog(
|
FuelLogDialog(
|
||||||
stationName = record.stationName,
|
stationName = record.stationName,
|
||||||
pricePerLiter = record.pricePerLiter,
|
|
||||||
initialLiters = record.liters.toString(),
|
initialLiters = record.liters.toString(),
|
||||||
initialTotal = record.totalPrice.toString(),
|
initialTotal = record.totalPrice.toString(),
|
||||||
onDismiss = { editRecordDialog = null },
|
onDismiss = { editRecordDialog = null },
|
||||||
@@ -311,7 +354,8 @@ fun GasStationList(
|
|||||||
repository: GasStationRepository,
|
repository: GasStationRepository,
|
||||||
lastLocation: android.location.Location?,
|
lastLocation: android.location.Location?,
|
||||||
onLogClick: (GasStationInfo) -> Unit,
|
onLogClick: (GasStationInfo) -> Unit,
|
||||||
onUpdateStations: (List<GasStationInfo>) -> Unit
|
onUpdateStations: (List<GasStationInfo>) -> Unit,
|
||||||
|
searchQuery: String
|
||||||
) {
|
) {
|
||||||
LazyColumn(contentPadding = PaddingValues(vertical = 8.dp)) {
|
LazyColumn(contentPadding = PaddingValues(vertical = 8.dp)) {
|
||||||
items(stations) { station ->
|
items(stations) { station ->
|
||||||
@@ -325,9 +369,10 @@ fun GasStationList(
|
|||||||
onLogClick = { onLogClick(station) },
|
onLogClick = { onLogClick(station) },
|
||||||
onFavoriteClick = {
|
onFavoriteClick = {
|
||||||
FavoriteStorage.toggleFavorite(context, station.id)
|
FavoriteStorage.toggleFavorite(context, station.id)
|
||||||
lastLocation?.let { loc ->
|
val loc = lastLocation ?: LocationStorage.getLastLocation(context)
|
||||||
|
loc?.let {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
onUpdateStations(repository.getCheapestAndNearest(context, loc))
|
onUpdateStations(repository.getCheapestAndNearest(context, it, municipalityQuery = if(searchQuery.isNotBlank()) searchQuery else null))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -386,7 +431,6 @@ fun FuelHistoryScreen(records: List<FuelRecord>, onDelete: (FuelRecord) -> Unit,
|
|||||||
@Composable
|
@Composable
|
||||||
fun FuelLogDialog(
|
fun FuelLogDialog(
|
||||||
stationName: String,
|
stationName: String,
|
||||||
pricePerLiter: Double,
|
|
||||||
initialLiters: String = "",
|
initialLiters: String = "",
|
||||||
initialTotal: String = "",
|
initialTotal: String = "",
|
||||||
onDismiss: () -> Unit,
|
onDismiss: () -> Unit,
|
||||||
@@ -431,6 +475,21 @@ fun FuelLogDialog(
|
|||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun GasStationItem(station: GasStationInfo, onClick: () -> Unit, onLogClick: () -> Unit, onFavoriteClick: () -> Unit) {
|
fun GasStationItem(station: GasStationInfo, onClick: () -> Unit, onLogClick: () -> Unit, onFavoriteClick: () -> 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
|
||||||
|
}
|
||||||
|
|
||||||
Card(
|
Card(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
@@ -446,6 +505,11 @@ fun GasStationItem(station: GasStationInfo, onClick: () -> Unit, onLogClick: ()
|
|||||||
) {
|
) {
|
||||||
Column(modifier = Modifier.weight(1f)) {
|
Column(modifier = Modifier.weight(1f)) {
|
||||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
|
Surface(
|
||||||
|
color = brandColor,
|
||||||
|
modifier = Modifier.size(12.dp).padding(end = 8.dp),
|
||||||
|
shape = androidx.compose.foundation.shape.CircleShape
|
||||||
|
) {}
|
||||||
Text(
|
Text(
|
||||||
text = "${station.name} - ${station.municipality}",
|
text = "${station.name} - ${station.municipality}",
|
||||||
style = MaterialTheme.typography.titleMedium,
|
style = MaterialTheme.typography.titleMedium,
|
||||||
@@ -471,9 +535,9 @@ fun GasStationItem(station: GasStationInfo, onClick: () -> Unit, onLogClick: ()
|
|||||||
shape = MaterialTheme.shapes.medium
|
shape = MaterialTheme.shapes.medium
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = "${String.format(Locale.getDefault(), "%.1f", station.distance / 1000)} km",
|
text = "${String.format(Locale.getDefault(), "%.1f", station.distance / 1000)} km (recta)",
|
||||||
modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp),
|
modifier = Modifier.padding(horizontal = 10.dp, vertical = 6.dp),
|
||||||
style = MaterialTheme.typography.labelLarge,
|
style = MaterialTheme.typography.labelMedium,
|
||||||
color = MaterialTheme.colorScheme.onSecondaryContainer
|
color = MaterialTheme.colorScheme.onSecondaryContainer
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -482,14 +546,24 @@ fun GasStationItem(station: GasStationInfo, onClick: () -> Unit, onLogClick: ()
|
|||||||
Row(
|
Row(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.SpaceBetween,
|
||||||
verticalAlignment = Alignment.CenterVertically
|
verticalAlignment = Alignment.Bottom
|
||||||
) {
|
) {
|
||||||
|
Column {
|
||||||
Text(
|
Text(
|
||||||
text = "${station.price} €/L",
|
text = "Diésel A: ${station.price} €/L",
|
||||||
style = MaterialTheme.typography.headlineSmall,
|
style = MaterialTheme.typography.titleLarge,
|
||||||
color = MaterialTheme.colorScheme.primary,
|
color = priceColor,
|
||||||
fontWeight = androidx.compose.ui.text.font.FontWeight.Bold
|
fontWeight = FontWeight.Bold
|
||||||
)
|
)
|
||||||
|
station.pricePremium?.let { premium ->
|
||||||
|
Text(
|
||||||
|
text = "Premium: $premium €/L",
|
||||||
|
style = MaterialTheme.typography.bodyMedium,
|
||||||
|
color = MaterialTheme.colorScheme.secondary,
|
||||||
|
fontWeight = FontWeight.Medium
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
Button(
|
Button(
|
||||||
onClick = onLogClick,
|
onClick = onLogClick,
|
||||||
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 4.dp),
|
contentPadding = PaddingValues(horizontal = 12.dp, vertical = 4.dp),
|
||||||
|
|||||||
@@ -147,15 +147,22 @@ class MainScreen(carContext: CarContext) : Screen(carContext) {
|
|||||||
|
|
||||||
for (station in gasStations) {
|
for (station in gasStations) {
|
||||||
val distanceStr = String.format(Locale.getDefault(), "%.1f km", station.distance / 1000)
|
val distanceStr = String.format(Locale.getDefault(), "%.1f km", station.distance / 1000)
|
||||||
listBuilder.addItem(
|
|
||||||
Row.Builder()
|
val rowBuilder = Row.Builder()
|
||||||
.setTitle("${station.name} - ${station.municipality}")
|
.setTitle("${station.name} - ${station.municipality}")
|
||||||
.addText("${station.price} €/L • $distanceStr")
|
.addText("A: ${station.price} €/L" + (station.pricePremium?.let { " • P: $it €/L" } ?: ""))
|
||||||
.addText(station.address)
|
.addText("$distanceStr (recta) • ${station.address}")
|
||||||
.setOnClickListener {
|
.setOnClickListener {
|
||||||
val pane = Pane.Builder()
|
val paneBuilder = Pane.Builder()
|
||||||
.addRow(Row.Builder().setTitle("Precio").addText("${station.price} €/L").build())
|
.addRow(Row.Builder().setTitle("Diésel A").addText("${station.price} €/L").build())
|
||||||
.addRow(Row.Builder().setTitle("Distancia").addText(distanceStr).build())
|
|
||||||
|
station.pricePremium?.let {
|
||||||
|
paneBuilder.addRow(Row.Builder().setTitle("Diésel Premium").addText("$it €/L").build())
|
||||||
|
}
|
||||||
|
|
||||||
|
val pane = paneBuilder
|
||||||
|
.addRow(Row.Builder().setTitle("Ubicación").addText("${station.municipality}\n${station.address}").build())
|
||||||
|
.addRow(Row.Builder().setTitle("Distancia").addText("$distanceStr (en línea recta)").build())
|
||||||
.addAction(
|
.addAction(
|
||||||
Action.Builder()
|
Action.Builder()
|
||||||
.setTitle(if (station.isFavorite) "Quitar Favorito" else "Añadir Favorito")
|
.setTitle(if (station.isFavorite) "Quitar Favorito" else "Añadir Favorito")
|
||||||
@@ -178,15 +185,14 @@ class MainScreen(carContext: CarContext) : Screen(carContext) {
|
|||||||
)
|
)
|
||||||
.addAction(
|
.addAction(
|
||||||
Action.Builder()
|
Action.Builder()
|
||||||
.setTitle("Registrar Repostaje")
|
.setTitle("Registrar")
|
||||||
.setOnClickListener {
|
.setOnClickListener {
|
||||||
// Guardar record simplificado desde el coche
|
|
||||||
val record = FuelRecord(
|
val record = FuelRecord(
|
||||||
date = System.currentTimeMillis(),
|
date = System.currentTimeMillis(),
|
||||||
stationName = station.name,
|
stationName = station.name,
|
||||||
address = station.address,
|
address = station.address,
|
||||||
municipality = station.municipality,
|
municipality = station.municipality,
|
||||||
liters = 0.0, // Se editará en el móvil después
|
liters = 0.0,
|
||||||
totalPrice = 0.0,
|
totalPrice = 0.0,
|
||||||
pricePerLiter = station.price
|
pricePerLiter = station.price
|
||||||
)
|
)
|
||||||
@@ -207,8 +213,8 @@ class MainScreen(carContext: CarContext) : Screen(carContext) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.build()
|
|
||||||
)
|
listBuilder.addItem(rowBuilder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
return ListTemplate.Builder()
|
return ListTemplate.Builder()
|
||||||
|
|||||||
@@ -15,7 +15,13 @@ class GasStationRepository {
|
|||||||
.build()
|
.build()
|
||||||
.create(GasolineraApi::class.java)
|
.create(GasolineraApi::class.java)
|
||||||
|
|
||||||
suspend fun getCheapestAndNearest(context: Context, userLocation: Location, forceRefresh: Boolean = false): List<GasStationInfo> {
|
suspend fun getCheapestAndNearest(
|
||||||
|
context: Context,
|
||||||
|
userLocation: Location,
|
||||||
|
forceRefresh: Boolean = false,
|
||||||
|
municipalityQuery: String? = null,
|
||||||
|
onlyFavorites: Boolean = false
|
||||||
|
): List<GasStationInfo> {
|
||||||
return try {
|
return try {
|
||||||
if (cachedResponse == null || forceRefresh) {
|
if (cachedResponse == null || forceRefresh) {
|
||||||
cachedResponse = api.getGasolineras()
|
cachedResponse = api.getGasolineras()
|
||||||
@@ -23,12 +29,15 @@ class GasStationRepository {
|
|||||||
|
|
||||||
val favorites = FavoriteStorage.getFavoriteIds(context)
|
val favorites = FavoriteStorage.getFavoriteIds(context)
|
||||||
|
|
||||||
cachedResponse?.listaEESS?.mapNotNull {
|
val allStations = cachedResponse?.listaEESS?.mapNotNull {
|
||||||
val lat = it.latitud.replace(",", ".").toDoubleOrNull()
|
val lat = it.latitud.replace(",", ".").toDoubleOrNull()
|
||||||
val lon = it.longitud.replace(",", ".").toDoubleOrNull()
|
val lon = it.longitud.replace(",", ".").toDoubleOrNull()
|
||||||
val price = it.precioDiesel.replace(",", ".").toDoubleOrNull() ?: it.precioGasolina95.replace(",", ".").toDoubleOrNull()
|
|
||||||
|
|
||||||
if (lat != null && lon != null && price != null) {
|
if (lat != null && lon != null) {
|
||||||
|
val dieselPrice = it.precioDiesel.replace(",", ".").toDoubleOrNull()
|
||||||
|
val premiumPrice = it.precioDieselPremium.replace(",", ".").toDoubleOrNull()
|
||||||
|
|
||||||
|
if (dieselPrice != null || premiumPrice != null) {
|
||||||
val stationLoc = Location("").apply {
|
val stationLoc = Location("").apply {
|
||||||
latitude = lat
|
latitude = lat
|
||||||
longitude = lon
|
longitude = lon
|
||||||
@@ -39,31 +48,57 @@ class GasStationRepository {
|
|||||||
name = it.rotulo,
|
name = it.rotulo,
|
||||||
address = it.direccion,
|
address = it.direccion,
|
||||||
municipality = it.municipio,
|
municipality = it.municipio,
|
||||||
price = price,
|
price = dieselPrice ?: 0.0,
|
||||||
|
pricePremium = premiumPrice,
|
||||||
distance = distance,
|
distance = distance,
|
||||||
latitude = lat,
|
latitude = lat,
|
||||||
longitude = lon,
|
longitude = lon,
|
||||||
isFavorite = favorites.contains(it.id)
|
isFavorite = favorites.contains(it.id)
|
||||||
)
|
)
|
||||||
} else null
|
} else null
|
||||||
}?.sortedBy { it.distance }
|
} else null
|
||||||
?.take(50)
|
} ?: emptyList()
|
||||||
?.sortedWith(compareByDescending<GasStationInfo> { it.isFavorite }.thenBy { it.price })
|
|
||||||
?.take(10) ?: emptyList()
|
val filteredStations = when {
|
||||||
|
onlyFavorites -> allStations.filter { it.isFavorite }
|
||||||
|
.sortedBy { it.distance }
|
||||||
|
!municipalityQuery.isNullOrBlank() -> allStations.filter { it.municipality.contains(municipalityQuery, ignoreCase = true) }
|
||||||
|
.sortedBy { it.distance }
|
||||||
|
else -> allStations.sortedBy { it.distance }.take(50)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filteredStations.isEmpty()) return emptyList()
|
||||||
|
|
||||||
|
val minPrice = filteredStations.minOf { it.price }
|
||||||
|
val avgPrice = filteredStations.map { it.price }.average()
|
||||||
|
|
||||||
|
filteredStations.map { station ->
|
||||||
|
val category = when {
|
||||||
|
station.price <= minPrice * 1.01 -> PriceCategory.CHEAP
|
||||||
|
station.price <= avgPrice -> PriceCategory.NORMAL
|
||||||
|
else -> PriceCategory.EXPENSIVE
|
||||||
|
}
|
||||||
|
station.copy(priceCategory = category)
|
||||||
|
}.sortedWith(compareByDescending<GasStationInfo> { it.isFavorite }.thenBy { it.price })
|
||||||
|
.take(if (onlyFavorites) 50 else 15)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
emptyList()
|
emptyList()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum class PriceCategory { CHEAP, NORMAL, EXPENSIVE }
|
||||||
|
|
||||||
data class GasStationInfo(
|
data class GasStationInfo(
|
||||||
val id: String,
|
val id: String,
|
||||||
val name: String,
|
val name: String,
|
||||||
val address: String,
|
val address: String,
|
||||||
val municipality: String,
|
val municipality: String,
|
||||||
val price: Double,
|
val price: Double,
|
||||||
|
val pricePremium: Double? = null,
|
||||||
val distance: Float,
|
val distance: Float,
|
||||||
val latitude: Double,
|
val latitude: Double,
|
||||||
val longitude: Double,
|
val longitude: Double,
|
||||||
val isFavorite: Boolean = false
|
val isFavorite: Boolean = false,
|
||||||
|
val priceCategory: PriceCategory = PriceCategory.NORMAL
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ data class Gasolinera(
|
|||||||
val municipio: String,
|
val municipio: String,
|
||||||
@SerializedName("Precio Gasoleo A")
|
@SerializedName("Precio Gasoleo A")
|
||||||
val precioDiesel: String,
|
val precioDiesel: String,
|
||||||
|
@SerializedName("Precio Gasoleo Premium")
|
||||||
|
val precioDieselPremium: String,
|
||||||
@SerializedName("Precio Gasolina 95 E5")
|
@SerializedName("Precio Gasolina 95 E5")
|
||||||
val precioGasolina95: String,
|
val precioGasolina95: String,
|
||||||
@SerializedName("Latitud")
|
@SerializedName("Latitud")
|
||||||
|
|||||||
Reference in New Issue
Block a user