Skip to content

Commit 45ff19a

Browse files
committed
Implement scroll-to-position behavior in OverviewScreen and TableScreen using SharedViewModel to track the last drill-down period.
* **SharedViewModel.kt**: Added `lastDrillDownPeriodStart` StateFlow and a setter to track the start time of the most recent drill-down period. * **OverviewScreen.kt**: Updated the lifecycle observer to scroll the list to the previously viewed period upon returning from a drill-down view. * **TableScreen.kt**: Refactored `LaunchedEffect` to handle scrolling to the specific aggregated period when returning from a drill-down, while maintaining the highlighting logic for new measurements.
1 parent 3bfe0aa commit 45ff19a

3 files changed

Lines changed: 51 additions & 20 deletions

File tree

android_app/app/src/main/java/com/health/openscale/ui/screen/overview/OverviewScreen.kt

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ import androidx.compose.runtime.remember
8181
import androidx.compose.runtime.rememberCoroutineScope
8282
import androidx.compose.runtime.saveable.rememberSaveable
8383
import androidx.compose.runtime.setValue
84+
import androidx.compose.runtime.snapshotFlow
8485
import androidx.compose.ui.Alignment
8586
import androidx.compose.ui.Modifier
8687
import androidx.compose.ui.draw.clip
@@ -130,6 +131,8 @@ import com.health.openscale.ui.screen.settings.BluetoothViewModel
130131
import com.health.openscale.ui.shared.SharedViewModel
131132
import com.health.openscale.ui.shared.TopBarAction
132133
import kotlinx.coroutines.delay
134+
import kotlinx.coroutines.flow.filter
135+
import kotlinx.coroutines.flow.first
133136
import kotlinx.coroutines.launch
134137
import java.text.DateFormat
135138
import java.util.Date
@@ -279,7 +282,7 @@ fun OverviewScreen(
279282

280283
// ── Top bar ───────────────────────────────────────────────────────────────
281284
val lifecycleOwner = LocalLifecycleOwner.current
282-
DisposableEffect(lifecycleOwner, selectedUserId, bluetoothAction, timeFilterAction, isDrillDown) {
285+
DisposableEffect(lifecycleOwner, selectedUserId, bluetoothAction, timeFilterAction, isDrillDown, aggregatedItems) {
283286
fun updateTopBar() {
284287
if (isDrillDown) {
285288
val fmt = DateFormat.getDateInstance(DateFormat.MEDIUM, Locale.getDefault())
@@ -297,7 +300,17 @@ fun OverviewScreen(
297300
}
298301
updateTopBar()
299302
val observer = LifecycleEventObserver { _, event ->
300-
if (event == Lifecycle.Event.ON_RESUME) updateTopBar()
303+
if (event == Lifecycle.Event.ON_RESUME) {
304+
updateTopBar()
305+
val target = sharedViewModel.lastDrillDownPeriodStart.value ?: return@LifecycleEventObserver
306+
val idx = aggregatedItems.indexOfFirst { it.periodStartMillis == target }
307+
if (idx >= 0) {
308+
scope.launch {
309+
listState.scrollToItem(idx)
310+
sharedViewModel.setLastDrillDownPeriodStart(null)
311+
}
312+
}
313+
}
301314
}
302315
lifecycleOwner.lifecycle.addObserver(observer)
303316
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
@@ -568,11 +581,10 @@ fun OverviewScreen(
568581
measurementWithValues = enrichedItem.measurementWithValues,
569582
processedValuesForDisplay = enrichedItem.valuesWithTrend,
570583
userEvaluationContext = userEvalContext,
571-
onClick = {
584+
onClick = {
572585
currentSelectedAggregatedTs = ts
573-
navController.navigate(
574-
Routes.overviewDrillDown(periodStart, periodEnd)
575-
)
586+
sharedViewModel.setLastDrillDownPeriodStart(periodStart)
587+
navController.navigate(Routes.overviewDrillDown(periodStart, periodEnd))
576588
},
577589
onEdit = {
578590
currentSelectedAggregatedTs = ts

android_app/app/src/main/java/com/health/openscale/ui/screen/table/TableScreen.kt

Lines changed: 26 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ import androidx.compose.runtime.remember
6868
import androidx.compose.runtime.rememberCoroutineScope
6969
import androidx.compose.runtime.saveable.rememberSaveable
7070
import androidx.compose.runtime.setValue
71+
import androidx.compose.runtime.snapshotFlow
7172
import androidx.compose.ui.Alignment
7273
import androidx.compose.ui.Modifier
7374
import androidx.compose.ui.platform.LocalContext
@@ -560,19 +561,32 @@ fun TableScreen(
560561
}
561562

562563
// ── Scroll + highlight ────────────────────────────────────────────────────
563-
val latestMeasurementId = remember(tableData) { tableData.firstOrNull()?.measurementId }
564564
var highlightedItemId by remember { mutableStateOf<Int?>(null) }
565565
val lazyListState = rememberLazyListState()
566566

567-
LaunchedEffect(latestMeasurementId) {
568-
if (latestMeasurementId != null && tableData.isNotEmpty() && !isDrillDown) {
569-
kotlinx.coroutines.delay(500)
570-
if (latestMeasurementId == tableData.firstOrNull()?.measurementId) {
571-
highlightedItemId = latestMeasurementId
572-
lazyListState.animateScrollToItem(0)
573-
kotlinx.coroutines.delay(1500)
574-
highlightedItemId = null
575-
}
567+
val lastDrillDownPeriodStart by sharedViewModel.lastDrillDownPeriodStart.collectAsState()
568+
569+
LaunchedEffect(lastDrillDownPeriodStart) {
570+
val target = lastDrillDownPeriodStart ?: return@LaunchedEffect
571+
572+
snapshotFlow { tableData }
573+
.filter { it.isNotEmpty() }
574+
.first()
575+
576+
val idx = tableData.indexOfFirst { it.periodStartMillis == target }
577+
if (idx >= 0) {
578+
lazyListState.scrollToItem(idx)
579+
sharedViewModel.setLastDrillDownPeriodStart(null)
580+
return@LaunchedEffect
581+
}
582+
583+
val latestId = tableData.firstOrNull()?.measurementId ?: return@LaunchedEffect
584+
kotlinx.coroutines.delay(500)
585+
if (latestId == tableData.firstOrNull()?.measurementId) {
586+
highlightedItemId = latestId
587+
lazyListState.animateScrollToItem(0)
588+
kotlinx.coroutines.delay(1500)
589+
highlightedItemId = null
576590
}
577591
}
578592

@@ -826,11 +840,9 @@ fun TableScreen(
826840
if (isInSelectionMode) {
827841
toggleKey(key)
828842
} else if (rowData.isAggregated) {
843+
sharedViewModel.setLastDrillDownPeriodStart(rowData.periodStartMillis)
829844
navController.navigate(
830-
Routes.tableDrillDown(
831-
startMillis = rowData.periodStartMillis!!,
832-
endMillis = rowData.periodEndMillis!!,
833-
)
845+
Routes.tableDrillDown(rowData.periodStartMillis!!, rowData.periodEndMillis!!)
834846
)
835847
} else {
836848
navController.navigate(

android_app/app/src/main/java/com/health/openscale/ui/shared/SharedViewModel.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,6 +437,13 @@ class SharedViewModel @Inject constructor(
437437
// Drill-down flow — raw, non-aggregated, fixed time window
438438
// -------------------------------------------------------------------------
439439

440+
private val _lastDrillDownPeriodStart = MutableStateFlow<Long?>(null)
441+
val lastDrillDownPeriodStart: StateFlow<Long?> = _lastDrillDownPeriodStart.asStateFlow()
442+
443+
fun setLastDrillDownPeriodStart(periodStart: Long?) {
444+
_lastDrillDownPeriodStart.value = periodStart
445+
}
446+
440447
/**
441448
* Cache of drill-down StateFlows keyed by "$startMillis-$endMillis".
442449
*

0 commit comments

Comments
 (0)