fix(drives): preserve filters and scroll position on navigation (#58)

Fixes issue where date/distance filters and scroll position were reset
when navigating back from drive details:

- Move date filter state from composable to ViewModel
- Add scroll position tracking in ViewModel
- Only apply default 7-day filter on first initialization
- Restore scroll position when returning to drives list

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Davide Ferrari
2026-01-16 09:50:17 +01:00
committed by GitHub
parent 83ab18ea3f
commit 2a6c5a6c78
3 changed files with 65 additions and 34 deletions
+4
View File
@@ -18,6 +18,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Over 150 km: shows weather every 35 km
- Weather icons for: Clear, Partly Cloudy, Fog, Drizzle, Rain, Snow, Thunderstorm
### Fixed
- **Drives**: Date and distance filters now persist when navigating to drive details and back
- **Drives**: Scroll position is now preserved when returning from drive details
## [0.9.4] - 2026-01-14
### Fixed
@@ -17,6 +17,7 @@ import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape
@@ -68,18 +69,9 @@ import com.matedroid.ui.components.BarChartData
import com.matedroid.ui.components.InteractiveBarChart
import com.matedroid.ui.theme.CarColorPalette
import com.matedroid.ui.theme.CarColorPalettes
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
enum class DriveDateFilter(val label: String, val days: Long?) {
LAST_7_DAYS("Last 7 days", 7),
LAST_30_DAYS("Last 30 days", 30),
LAST_90_DAYS("Last 90 days", 90),
LAST_YEAR("Last year", 365),
ALL_TIME("All time", null)
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun DrivesScreen(
@@ -91,16 +83,26 @@ fun DrivesScreen(
) {
val uiState by viewModel.uiState.collectAsState()
val snackbarHostState = remember { SnackbarHostState() }
var selectedFilter by remember { mutableStateOf(DriveDateFilter.LAST_7_DAYS) }
val isDarkTheme = isSystemInDarkTheme()
val palette = CarColorPalettes.forExteriorColor(exteriorColor, isDarkTheme)
// Remember scroll state and restore from ViewModel
val listState = rememberLazyListState(
initialFirstVisibleItemIndex = uiState.scrollPosition,
initialFirstVisibleItemScrollOffset = uiState.scrollOffset
)
// Initialize ViewModel with carId (only loads data on first call)
LaunchedEffect(carId) {
viewModel.setCarId(carId)
// Apply default 7-day filter on initial load
val endDate = LocalDate.now()
val startDate = endDate.minusDays(7)
viewModel.setDateFilter(startDate, endDate)
}
// Save scroll position when it changes
LaunchedEffect(listState.firstVisibleItemIndex, listState.firstVisibleItemScrollOffset) {
viewModel.saveScrollPosition(
listState.firstVisibleItemIndex,
listState.firstVisibleItemScrollOffset
)
}
LaunchedEffect(uiState.error) {
@@ -110,17 +112,6 @@ fun DrivesScreen(
}
}
fun applyDateFilter(filter: DriveDateFilter) {
selectedFilter = filter
if (filter.days != null) {
val endDate = LocalDate.now()
val startDate = endDate.minusDays(filter.days)
viewModel.setDateFilter(startDate, endDate)
} else {
viewModel.clearDateFilter()
}
}
Scaffold(
topBar = {
TopAppBar(
@@ -160,11 +151,12 @@ fun DrivesScreen(
chartData = uiState.chartData,
chartGranularity = uiState.chartGranularity,
summary = uiState.summary,
selectedDateFilter = selectedFilter,
selectedDateFilter = uiState.dateFilter,
selectedDistanceFilter = uiState.distanceFilter,
units = uiState.units,
palette = palette,
onDateFilterSelected = { applyDateFilter(it) },
listState = listState,
onDateFilterSelected = { viewModel.setDateFilter(it) },
onDistanceFilterSelected = { viewModel.setDistanceFilter(it) },
onDriveClick = onNavigateToDriveDetail
)
@@ -184,11 +176,13 @@ private fun DrivesContent(
selectedDistanceFilter: DriveDistanceFilter,
units: Units?,
palette: CarColorPalette,
listState: androidx.compose.foundation.lazy.LazyListState,
onDateFilterSelected: (DriveDateFilter) -> Unit,
onDistanceFilterSelected: (DriveDistanceFilter) -> Unit,
onDriveClick: (driveId: Int) -> Unit
) {
LazyColumn(
state = listState,
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(16.dp),
verticalArrangement = Arrangement.spacedBy(12.dp)
@@ -55,6 +55,14 @@ data class DriveChartData(
val sortKey: Long
)
enum class DriveDateFilter(val label: String, val days: Long?) {
LAST_7_DAYS("Last 7 days", 7),
LAST_30_DAYS("Last 30 days", 30),
LAST_90_DAYS("Last 90 days", 90),
LAST_YEAR("Last year", 365),
ALL_TIME("All time", null)
}
data class DrivesUiState(
val isLoading: Boolean = true,
val isRefreshing: Boolean = false,
@@ -66,7 +74,10 @@ data class DrivesUiState(
val endDate: LocalDate? = null,
val summary: DrivesSummary = DrivesSummary(),
val units: Units? = null,
val distanceFilter: DriveDistanceFilter = DriveDistanceFilter.ALL
val distanceFilter: DriveDistanceFilter = DriveDistanceFilter.ALL,
val dateFilter: DriveDateFilter = DriveDateFilter.LAST_7_DAYS,
val scrollPosition: Int = 0,
val scrollOffset: Int = 0
)
data class DrivesSummary(
@@ -90,6 +101,7 @@ class DrivesViewModel @Inject constructor(
private var carId: Int? = null
private var showShortDrivesCharges: Boolean = false
private var allDrives: List<DriveData> = emptyList()
private var isInitialized: Boolean = false
companion object {
private const val MIN_DURATION_MINUTES = 1
@@ -97,18 +109,39 @@ class DrivesViewModel @Inject constructor(
}
fun setCarId(id: Int) {
if (carId == id && isInitialized) {
// Already initialized with this car, don't reload
return
}
carId = id
loadUnits(id)
// Only apply default filter on first initialization
if (!isInitialized) {
isInitialized = true
applyDateFilterEnum(_uiState.value.dateFilter)
}
}
fun setDateFilter(startDate: LocalDate?, endDate: LocalDate?) {
_uiState.update { it.copy(startDate = startDate, endDate = endDate) }
loadDrives(startDate, endDate)
fun setDateFilter(filter: DriveDateFilter) {
_uiState.update { it.copy(dateFilter = filter) }
applyDateFilterEnum(filter)
}
fun clearDateFilter() {
_uiState.update { it.copy(startDate = null, endDate = null) }
loadDrives(null, null)
private fun applyDateFilterEnum(filter: DriveDateFilter) {
if (filter.days != null) {
val endDate = LocalDate.now()
val startDate = endDate.minusDays(filter.days)
_uiState.update { it.copy(startDate = startDate, endDate = endDate) }
loadDrives(startDate, endDate)
} else {
_uiState.update { it.copy(startDate = null, endDate = null) }
loadDrives(null, null)
}
}
fun saveScrollPosition(index: Int, offset: Int) {
_uiState.update { it.copy(scrollPosition = index, scrollOffset = offset) }
}
fun setDistanceFilter(filter: DriveDistanceFilter) {