mirror of
https://github.com/vide/matedroid.git
synced 2026-01-20 00:03:17 +08:00
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:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user