Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 94 additions & 35 deletions app/src/main/java/com/matedroid/ui/screens/charges/ChargesScreen.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.material.icons.filled.BatteryChargingFull
import androidx.compose.material.icons.filled.CalendarMonth
import androidx.compose.material.icons.filled.Check
import androidx.compose.material.icons.filled.Clear
import androidx.compose.material.icons.filled.DateRange
import androidx.compose.material.icons.filled.ElectricBolt
Expand All @@ -39,12 +40,15 @@ import androidx.compose.material.icons.filled.Schedule
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FilterChipDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Scaffold
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
Expand Down Expand Up @@ -153,6 +157,10 @@ fun ChargesScreen(
initialScrollOffset = uiState.scrollOffset,
palette = palette,
onDateFilterSelected = { viewModel.setDateFilter(it) },
availableLocations = uiState.availableLocations,
selectedLocations = uiState.selectedLocations,
onLocationFilterToggled = { viewModel.setLocationFilter(it) },
onLocationFilterCleared = { viewModel.clearLocationFilter() },
onChargeTypeFilterSelected = { viewModel.setChargeTypeFilter(it) },
onChargeClick = { chargeId, scrollIndex, scrollOffset ->
viewModel.saveScrollPosition(scrollIndex, scrollOffset)
Expand All @@ -176,6 +184,10 @@ private fun ChargesContent(
teslamateBaseUrl: String,
selectedDateFilter: DateFilter,
selectedChargeTypeFilter: ChargeTypeFilter,
availableLocations: List<String>,
selectedLocations: Set<String>,
onLocationFilterToggled: (String) -> Unit,
onLocationFilterCleared: () -> Unit,
initialScrollPosition: Int,
initialScrollOffset: Int,
palette: CarColorPalette,
Expand Down Expand Up @@ -204,11 +216,25 @@ private fun ChargesContent(
}

item {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
ChargeTypeFilterChips(
selectedFilter = selectedChargeTypeFilter,
palette = palette,
onFilterSelected = onChargeTypeFilterSelected
)
onFilterSelected = onChargeTypeFilterSelected,
modifier = Modifier.weight(1f),
palette = palette
)
LocationFilterDropdown(
availableLocations = availableLocations,
selectedLocations = selectedLocations,
onLocationToggled = onLocationFilterToggled,
onClearAll = onLocationFilterCleared,
palette = palette
)
}
}

item {
Expand Down Expand Up @@ -316,9 +342,11 @@ private fun DateFilterChips(
private fun ChargeTypeFilterChips(
selectedFilter: ChargeTypeFilter,
palette: CarColorPalette,
modifier: Modifier = Modifier,
onFilterSelected: (ChargeTypeFilter) -> Unit
) {
LazyRow(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
items(ChargeTypeFilter.entries.toList()) { filter ->
Expand Down Expand Up @@ -877,39 +905,70 @@ private fun ChargesChartPage(
}
}

/*
@Composable
private fun ChartLegend(
palette: CarColorPalette
private fun LocationFilterDropdown(
availableLocations: List<String>,
selectedLocations: Set<String>,
palette: CarColorPalette,
onLocationToggled: (String) -> Unit,
onClearAll: () -> Unit
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
LegendItem(color = Color(0xFF4CAF50) , label = stringResource(R.string.charging_ac))
Spacer(modifier = Modifier.width(24.dp))
LegendItem(color = Color(0xFFFF9800), label = stringResource(R.string.charging_dc))
}
}

@Composable
private fun LegendItem(color: Color, label: String) {
Row(verticalAlignment = Alignment.CenterVertically) {
Box(
modifier = Modifier
.size(12.dp)
.clip(RoundedCornerShape(2.dp))
.background(color)
)
Spacer(modifier = Modifier.width(6.dp))
Text(
text = label,
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant
var expanded by remember { mutableStateOf(false) }
var searchText by remember { mutableStateOf("") }
val hasSelection = selectedLocations.isNotEmpty()

Box {
FilterChip(
selected = hasSelection,
onClick = { expanded = true },
label = {
Text(
if (hasSelection && selectedLocations.size == 1)
selectedLocations.first()
else if (hasSelection)
"${selectedLocations.size} ubicaciones"
else
stringResource(R.string.filter_location)
)
},
leadingIcon = { Icon(Icons.Default.LocationOn, null, Modifier.size(16.dp)) },
trailingIcon = if (hasSelection) {
{
Icon(
Icons.Default.Clear,
contentDescription = null,
modifier = Modifier.size(16.dp).clickable { onClearAll() }
)
}
} else null
)
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false; searchText = "" }
) {
OutlinedTextField(
value = searchText,
onValueChange = { searchText = it },
placeholder = { Text(stringResource(R.string.search)) },
modifier = Modifier.padding(8.dp).fillMaxWidth(),
singleLine = true
)
val filtered = availableLocations.filter {
it.contains(searchText, ignoreCase = true)
}
filtered.forEach { location ->
val isSelected = location in selectedLocations
DropdownMenuItem(
text = { Text(location) },
leadingIcon = {
if (isSelected)
Icon(Icons.Default.Check, null, tint = palette.accent)
else
Spacer(Modifier.size(24.dp))
},
onClick = { onLocationToggled(location) }
)
}
}
}
}
*/
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ enum class ChargeTypeFilter(val label: String) {
DC("DC")
}

data class LocationFilter(val name: String) // null name = All locations

data class ChargeChartData(
val label: String,
val count: Int,
Expand All @@ -64,6 +66,8 @@ data class ChargesUiState(
val isRefreshing: Boolean = false,
val charges: List<ChargeData> = emptyList(),
val dcChargeIds: Set<Int> = emptySet(),
val availableLocations: List<String> = emptyList(),
val selectedLocations: Set<String> = emptySet(), // null = All locations
val processedChargeIds: Set<Int> = emptySet(), // Charges that have aggregate data
val chartData: List<ChargeChartData> = emptyList(),
val chartGranularity: ChartGranularity = ChartGranularity.MONTHLY,
Expand Down Expand Up @@ -164,6 +168,18 @@ class ChargesViewModel @Inject constructor(
applyFiltersAndUpdateState()
}

fun setLocationFilter(location: String) {
val current = _uiState.value.selectedLocations
val updated = if (location in current) current - location else current + location
_uiState.update { it.copy(selectedLocations = updated) }
applyFiltersAndUpdateState()
}

fun clearLocationFilter() {
_uiState.update { it.copy(selectedLocations = emptySet()) }
applyFiltersAndUpdateState()
}

fun clearError() {
_uiState.update { it.copy(error = null) }
}
Expand Down Expand Up @@ -259,15 +275,28 @@ class ChargesViewModel @Inject constructor(
ChargeTypeFilter.AC -> allCharges.filter { it.chargeId !in dcChargeIds }
}

// Extract unique locations from the complete set
val locations = allCharges.mapNotNull { it.address }.distinct().sorted()
// Apply location filter to displayCharges
val locationFilter = state.selectedLocations
val displayChargesFiltered = if (locationFilter.isNotEmpty())
displayCharges.filter { it.address in locationFilter }
else displayCharges
// Apply location filter to Stats
val chargesForStatsFiltered = if (locationFilter.isNotEmpty())
chargesForStats.filter { it.address in locationFilter }
else chargesForStats

// Calculate summary and chart data from filtered charges
val summary = calculateSummary(chargesForStats)
val chartData = calculateChartData(chargesForStats, granularity, state.startDate)
val summary = calculateSummary(chargesForStatsFiltered)
val chartData = calculateChartData(chargesForStatsFiltered, granularity, state.startDate)

_uiState.update {
it.copy(
isLoading = false,
isRefreshing = false,
charges = displayCharges,
charges = displayChargesFiltered,
availableLocations = locations,
summary = summary,
chartData = chartData
)
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/com/matedroid/ui/theme/Color.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ val SurfaceVariantLight = Color(0xFFE8EAEC)
val OnSurfaceVariantLight = Color(0xFF44474A)
val OutlineLight = Color(0xFF74777A)
val OutlineVariantLight = Color(0xFFC4C6C8)
val SurfaceContainerLight = Color(0xFFF0F1F3)
val SurfaceContainerHighLight = Color(0xFFECEDEF)
val SurfaceContainerHighestLight = Color(0xFFE6E7E9)
val ErrorLight = StatusError
Expand All @@ -54,6 +55,7 @@ val SurfaceVariantDark = Color(0xFF44474A)
val OnSurfaceVariantDark = Color(0xFFCACCCE)
val OutlineDark = Color(0xFF8E9194)
val OutlineVariantDark = Color(0xFF44474A)
val SurfaceContainerDark = Color(0xFF242629)
val SurfaceContainerHighDark = Color(0xFF2B2D31)
val SurfaceContainerHighestDark = Color(0xFF35373B)
val ErrorDark = Color(0xFFFFB4AB)
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/com/matedroid/ui/theme/Theme.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ private val DarkColorScheme = darkColorScheme(
onSurfaceVariant = OnSurfaceVariantDark,
outline = OutlineDark,
outlineVariant = OutlineVariantDark,
surfaceContainer = SurfaceContainerDark,
surfaceContainerHigh = SurfaceContainerHighDark,
surfaceContainerHighest = SurfaceContainerHighestDark,
error = ErrorDark,
Expand All @@ -50,6 +51,7 @@ private val LightColorScheme = lightColorScheme(
onSurfaceVariant = OnSurfaceVariantLight,
outline = OutlineLight,
outlineVariant = OutlineVariantLight,
surfaceContainer = SurfaceContainerLight,
surfaceContainerHigh = SurfaceContainerHighLight,
surfaceContainerHighest = SurfaceContainerHighestLight,
error = ErrorLight,
Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values-ca/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,10 @@
<!-- Section title for charge history list -->
<string name="charge_history">Historial de càrregues</string>

<!-- Filter location -->
<string name="filter_location">Ubicació</string>
<string name="search">Cerca</string>

<!-- Empty state when no charges match filters -->
<string name="no_charges_found">No s\'han trobat càrregues per al període seleccionat</string>

Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values-es/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,10 @@
<!-- Section title for charge history list -->
<string name="charge_history">Historial de cargas</string>

<!-- Filter location -->
<string name="filter_location">Ubicación</string>
<string name="search">Buscar</string>

<!-- Empty state when no charges match filters -->
<string name="no_charges_found">No se encontraron cargas para el período seleccionado</string>

Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values-it/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,10 @@
<!-- Section title for charge history list -->
<string name="charge_history">Cronologia ricariche</string>

<!-- Filter location -->
<string name="filter_location">Posizione</string>
<string name="search">Ricerca</string>

<!-- Empty state when no charges match filters -->
<string name="no_charges_found">Nessuna ricarica trovata per il periodo selezionato</string>

Expand Down
4 changes: 4 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -359,6 +359,10 @@
<!-- Section title for charge history list -->
<string name="charge_history">Charge History</string>

<!-- Filter location -->
<string name="filter_location">Location</string>
<string name="search">Search</string>

<!-- Empty state when no charges match filters -->
<string name="no_charges_found">No charges found for selected period</string>

Expand Down
Loading