feat: add scroll to top functionality across root screens

This commit is contained in:
tapframe 2026-05-16 21:59:07 +05:30
parent 3c61e0d39e
commit 59bfb3f26b
6 changed files with 119 additions and 29 deletions

View file

@ -181,6 +181,8 @@ import com.nuvio.app.features.watchprogress.WatchProgressRepository
import com.nuvio.app.features.watchprogress.nextUpDismissKey import com.nuvio.app.features.watchprogress.nextUpDismissKey
import com.nuvio.app.features.watching.application.WatchingActions import com.nuvio.app.features.watching.application.WatchingActions
import com.nuvio.app.features.watching.application.WatchingState import com.nuvio.app.features.watching.application.WatchingState
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
@ -544,8 +546,11 @@ private fun MainAppContent(
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
var selectedTab by rememberSaveable { mutableStateOf(AppScreenTab.Home) } var selectedTab by rememberSaveable { mutableStateOf(AppScreenTab.Home) }
var searchFocusRequestCount by remember { mutableStateOf(0) } var searchFocusRequestCount by remember { mutableStateOf(0) }
val homeScrollToTopRequests = remember { MutableSharedFlow<Unit>(extraBufferCapacity = 1) }
val searchScrollToTopRequests = remember { MutableSharedFlow<Unit>(extraBufferCapacity = 1) }
val libraryScrollToTopRequests = remember { MutableSharedFlow<Unit>(extraBufferCapacity = 1) }
val settingsRootActionRequests = remember { MutableSharedFlow<Unit>(extraBufferCapacity = 1) }
val currentBackStackEntry by navController.currentBackStackEntryAsState() val currentBackStackEntry by navController.currentBackStackEntryAsState()
val nativeRequestedTab by remember { NativeTabBridge.requestedTab }.collectAsStateWithLifecycle()
val liquidGlassNativeTabBarEnabled by remember { val liquidGlassNativeTabBarEnabled by remember {
ThemeSettingsRepository.liquidGlassNativeTabBarEnabled ThemeSettingsRepository.liquidGlassNativeTabBarEnabled
}.collectAsStateWithLifecycle() }.collectAsStateWithLifecycle()
@ -602,9 +607,28 @@ private fun MainAppContent(
.sorted() .sorted()
} }
LaunchedEffect(nativeRequestedTab) { fun handleRootTabClick(tab: AppScreenTab) {
if (liquidGlassNativeTabBarSupported && liquidGlassNativeTabBarEnabled) { if (selectedTab != tab) {
selectedTab = nativeRequestedTab.toAppScreenTab() selectedTab = tab
return
}
when (tab) {
AppScreenTab.Home -> homeScrollToTopRequests.tryEmit(Unit)
AppScreenTab.Search -> {
searchFocusRequestCount++
searchScrollToTopRequests.tryEmit(Unit)
}
AppScreenTab.Library -> libraryScrollToTopRequests.tryEmit(Unit)
AppScreenTab.Settings -> settingsRootActionRequests.tryEmit(Unit)
}
}
LaunchedEffect(liquidGlassNativeTabBarSupported, liquidGlassNativeTabBarEnabled) {
NativeTabBridge.requestedTabs.collectLatest { requestedTab ->
if (liquidGlassNativeTabBarSupported && liquidGlassNativeTabBarEnabled) {
handleRootTabClick(requestedTab.toAppScreenTab())
}
} }
} }
@ -1059,35 +1083,29 @@ private fun MainAppContent(
NuvioNavigationBar { NuvioNavigationBar {
NavItem( NavItem(
selected = selectedTab == AppScreenTab.Home, selected = selectedTab == AppScreenTab.Home,
onClick = { selectedTab = AppScreenTab.Home }, onClick = { handleRootTabClick(AppScreenTab.Home) },
icon = Icons.Filled.Home, icon = Icons.Filled.Home,
contentDescription = stringResource(Res.string.compose_nav_home), contentDescription = stringResource(Res.string.compose_nav_home),
) )
NavItem( NavItem(
selected = selectedTab == AppScreenTab.Search, selected = selectedTab == AppScreenTab.Search,
onClick = { onClick = { handleRootTabClick(AppScreenTab.Search) },
if (selectedTab == AppScreenTab.Search) {
searchFocusRequestCount++
} else {
selectedTab = AppScreenTab.Search
}
},
icon = Res.drawable.sidebar_search, icon = Res.drawable.sidebar_search,
contentDescription = stringResource(Res.string.compose_nav_search), contentDescription = stringResource(Res.string.compose_nav_search),
) )
NavItem( NavItem(
selected = selectedTab == AppScreenTab.Library, selected = selectedTab == AppScreenTab.Library,
onClick = { selectedTab = AppScreenTab.Library }, onClick = { handleRootTabClick(AppScreenTab.Library) },
icon = Res.drawable.sidebar_library, icon = Res.drawable.sidebar_library,
contentDescription = stringResource(Res.string.compose_nav_library), contentDescription = stringResource(Res.string.compose_nav_library),
) )
NavItem( NavItem(
selected = selectedTab == AppScreenTab.Settings, selected = selectedTab == AppScreenTab.Settings,
onClick = { selectedTab = AppScreenTab.Settings }, onClick = { handleRootTabClick(AppScreenTab.Settings) },
) { ) {
ProfileSwitcherTab( ProfileSwitcherTab(
selected = selectedTab == AppScreenTab.Settings, selected = selectedTab == AppScreenTab.Settings,
onClick = { selectedTab = AppScreenTab.Settings }, onClick = { handleRootTabClick(AppScreenTab.Settings) },
onProfileSelected = onProfileSelected, onProfileSelected = onProfileSelected,
onAddProfileRequested = onSwitchProfile, onAddProfileRequested = onSwitchProfile,
) )
@ -1106,6 +1124,11 @@ private fun MainAppContent(
.padding(innerPadding), .padding(innerPadding),
selectedTab = selectedTab, selectedTab = selectedTab,
searchFocusRequestCount = searchFocusRequestCount, searchFocusRequestCount = searchFocusRequestCount,
rootActionsEnabled = tabsRouteActive,
homeScrollToTopRequests = homeScrollToTopRequests,
searchScrollToTopRequests = searchScrollToTopRequests,
libraryScrollToTopRequests = libraryScrollToTopRequests,
settingsRootActionRequests = settingsRootActionRequests,
animateHomeCollectionGifs = tabsRouteActive, animateHomeCollectionGifs = tabsRouteActive,
onCatalogClick = onCatalogClick, onCatalogClick = onCatalogClick,
onPosterClick = { meta -> onPosterClick = { meta ->
@ -1160,13 +1183,7 @@ private fun MainAppContent(
if (isTabletLayout && !useNativeBottomTabs) { if (isTabletLayout && !useNativeBottomTabs) {
TabletFloatingTopBar( TabletFloatingTopBar(
selectedTab = selectedTab, selectedTab = selectedTab,
onTabSelected = { tab -> onTabSelected = ::handleRootTabClick,
if (tab == AppScreenTab.Search && selectedTab == AppScreenTab.Search) {
searchFocusRequestCount++
} else {
selectedTab = tab
}
},
onProfileSelected = onProfileSelected, onProfileSelected = onProfileSelected,
onAddProfileRequested = onSwitchProfile, onAddProfileRequested = onSwitchProfile,
) )
@ -2196,6 +2213,11 @@ private fun AppTabHost(
selectedTab: AppScreenTab, selectedTab: AppScreenTab,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
searchFocusRequestCount: Int = 0, searchFocusRequestCount: Int = 0,
rootActionsEnabled: Boolean = true,
homeScrollToTopRequests: Flow<Unit>,
searchScrollToTopRequests: Flow<Unit>,
libraryScrollToTopRequests: Flow<Unit>,
settingsRootActionRequests: Flow<Unit>,
animateHomeCollectionGifs: Boolean = true, animateHomeCollectionGifs: Boolean = true,
onCatalogClick: ((HomeCatalogSection) -> Unit)? = null, onCatalogClick: ((HomeCatalogSection) -> Unit)? = null,
onPosterClick: ((MetaPreview) -> Unit)? = null, onPosterClick: ((MetaPreview) -> Unit)? = null,
@ -2228,6 +2250,7 @@ private fun AppTabHost(
HomeScreen( HomeScreen(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
animateCollectionGifs = animateHomeCollectionGifs, animateCollectionGifs = animateHomeCollectionGifs,
scrollToTopRequests = homeScrollToTopRequests,
onCatalogClick = onCatalogClick, onCatalogClick = onCatalogClick,
onPosterClick = onPosterClick, onPosterClick = onPosterClick,
onPosterLongClick = onPosterLongClick, onPosterLongClick = onPosterLongClick,
@ -2244,12 +2267,14 @@ private fun AppTabHost(
onPosterClick = onPosterClick, onPosterClick = onPosterClick,
onPosterLongClick = onPosterLongClick, onPosterLongClick = onPosterLongClick,
searchFocusRequestCount = searchFocusRequestCount, searchFocusRequestCount = searchFocusRequestCount,
scrollToTopRequests = searchScrollToTopRequests,
) )
} }
AppScreenTab.Library -> { AppScreenTab.Library -> {
LibraryScreen( LibraryScreen(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
scrollToTopRequests = libraryScrollToTopRequests,
onPosterClick = onLibraryPosterClick, onPosterClick = onLibraryPosterClick,
onSectionViewAllClick = onLibrarySectionViewAllClick, onSectionViewAllClick = onLibrarySectionViewAllClick,
) )
@ -2258,6 +2283,8 @@ private fun AppTabHost(
AppScreenTab.Settings -> { AppScreenTab.Settings -> {
SettingsScreen( SettingsScreen(
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
rootActionRequests = settingsRootActionRequests,
rootActionsEnabled = rootActionsEnabled,
onSwitchProfile = onSwitchProfile, onSwitchProfile = onSwitchProfile,
onHomescreenClick = onHomescreenSettingsClick, onHomescreenClick = onHomescreenSettingsClick,
onMetaScreenClick = onMetaScreenSettingsClick, onMetaScreenClick = onMetaScreenSettingsClick,

View file

@ -1,8 +1,8 @@
package com.nuvio.app.core.ui package com.nuvio.app.core.ui
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asSharedFlow
internal enum class NativeNavigationTab { internal enum class NativeNavigationTab {
Home, Home,
@ -18,11 +18,11 @@ internal enum class NativeNavigationTab {
} }
internal object NativeTabBridge { internal object NativeTabBridge {
private val _requestedTab = MutableStateFlow(NativeNavigationTab.Home) private val _requestedTabs = MutableSharedFlow<NativeNavigationTab>(extraBufferCapacity = 1)
val requestedTab: StateFlow<NativeNavigationTab> = _requestedTab.asStateFlow() val requestedTabs: SharedFlow<NativeNavigationTab> = _requestedTabs.asSharedFlow()
fun requestTab(tabName: String) { fun requestTab(tabName: String) {
_requestedTab.value = NativeNavigationTab.fromName(tabName) _requestedTabs.tryEmit(NativeNavigationTab.fromName(tabName))
} }
fun publishSelectedTab(tab: NativeNavigationTab) { fun publishSelectedTab(tab: NativeNavigationTab) {

View file

@ -60,6 +60,8 @@ import com.nuvio.app.features.home.components.HomeCollectionRowSection
import com.nuvio.app.features.watchprogress.ContinueWatchingSectionStyle import com.nuvio.app.features.watchprogress.ContinueWatchingSectionStyle
import kotlinx.coroutines.async import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.sync.withPermit
import com.nuvio.app.features.home.components.ContinueWatchingLayout import com.nuvio.app.features.home.components.ContinueWatchingLayout
@ -72,6 +74,7 @@ import org.jetbrains.compose.resources.stringResource
fun HomeScreen( fun HomeScreen(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
animateCollectionGifs: Boolean = true, animateCollectionGifs: Boolean = true,
scrollToTopRequests: Flow<Unit> = emptyFlow(),
onCatalogClick: ((HomeCatalogSection) -> Unit)? = null, onCatalogClick: ((HomeCatalogSection) -> Unit)? = null,
onPosterClick: ((MetaPreview) -> Unit)? = null, onPosterClick: ((MetaPreview) -> Unit)? = null,
onPosterLongClick: ((MetaPreview) -> Unit)? = null, onPosterLongClick: ((MetaPreview) -> Unit)? = null,
@ -107,6 +110,12 @@ fun HomeScreen(
}.collectAsStateWithLifecycle() }.collectAsStateWithLifecycle()
var observedOfflineState by remember { mutableStateOf(false) } var observedOfflineState by remember { mutableStateOf(false) }
LaunchedEffect(scrollToTopRequests) {
scrollToTopRequests.collect {
homeListState.animateScrollToItem(0)
}
}
LaunchedEffect(networkStatusUiState.condition) { LaunchedEffect(networkStatusUiState.condition) {
when (networkStatusUiState.condition) { when (networkStatusUiState.condition) {
NetworkCondition.NoInternet, NetworkCondition.NoInternet,

View file

@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -32,6 +33,8 @@ import com.nuvio.app.features.home.components.HomeEmptyStateCard
import com.nuvio.app.features.home.components.HomePosterCard import com.nuvio.app.features.home.components.HomePosterCard
import com.nuvio.app.features.home.components.HomeSkeletonRow import com.nuvio.app.features.home.components.HomeSkeletonRow
import com.nuvio.app.features.profiles.ProfileRepository import com.nuvio.app.features.profiles.ProfileRepository
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import nuvio.composeapp.generated.resources.* import nuvio.composeapp.generated.resources.*
import org.jetbrains.compose.resources.getString import org.jetbrains.compose.resources.getString
@ -46,6 +49,7 @@ private data class LibraryRemovalTarget(
@Composable @Composable
fun LibraryScreen( fun LibraryScreen(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
scrollToTopRequests: Flow<Unit> = emptyFlow(),
onPosterClick: ((LibraryItem) -> Unit)? = null, onPosterClick: ((LibraryItem) -> Unit)? = null,
onSectionViewAllClick: ((LibrarySection) -> Unit)? = null, onSectionViewAllClick: ((LibrarySection) -> Unit)? = null,
) { ) {
@ -57,6 +61,7 @@ fun LibraryScreen(
var pendingRemovalTarget by remember { mutableStateOf<LibraryRemovalTarget?>(null) } var pendingRemovalTarget by remember { mutableStateOf<LibraryRemovalTarget?>(null) }
var observedOfflineState by remember { mutableStateOf(false) } var observedOfflineState by remember { mutableStateOf(false) }
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
val listState = rememberLazyListState()
val isTraktSource = uiState.sourceMode == LibrarySourceMode.TRAKT val isTraktSource = uiState.sourceMode == LibrarySourceMode.TRAKT
val retryLibraryLoad: () -> Unit = { val retryLibraryLoad: () -> Unit = {
NetworkStatusRepository.requestRefresh(force = true) NetworkStatusRepository.requestRefresh(force = true)
@ -89,9 +94,16 @@ fun LibraryScreen(
} }
} }
LaunchedEffect(scrollToTopRequests) {
scrollToTopRequests.collect {
listState.animateScrollToItem(0)
}
}
NuvioScreen( NuvioScreen(
modifier = modifier, modifier = modifier,
horizontalPadding = 0.dp, horizontalPadding = 0.dp,
listState = listState,
) { ) {
stickyHeader { stickyHeader {
androidx.compose.foundation.layout.Column( androidx.compose.foundation.layout.Column(

View file

@ -57,7 +57,9 @@ import com.nuvio.app.features.home.components.homeSectionHorizontalPaddingForWid
import com.nuvio.app.features.home.components.HomeSkeletonRow import com.nuvio.app.features.home.components.HomeSkeletonRow
import com.nuvio.app.features.watched.WatchedRepository import com.nuvio.app.features.watched.WatchedRepository
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.filter import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import nuvio.composeapp.generated.resources.Res import nuvio.composeapp.generated.resources.Res
@ -83,6 +85,7 @@ fun SearchScreen(
onPosterClick: ((MetaPreview) -> Unit)? = null, onPosterClick: ((MetaPreview) -> Unit)? = null,
onPosterLongClick: ((MetaPreview) -> Unit)? = null, onPosterLongClick: ((MetaPreview) -> Unit)? = null,
searchFocusRequestCount: Int = 0, searchFocusRequestCount: Int = 0,
scrollToTopRequests: Flow<Unit> = emptyFlow(),
) { ) {
val focusRequester = remember { FocusRequester() } val focusRequester = remember { FocusRequester() }
@ -115,6 +118,12 @@ fun SearchScreen(
} }
} }
LaunchedEffect(scrollToTopRequests) {
scrollToTopRequests.collect {
listState.animateScrollToItem(0)
}
}
val addonRefreshKey = remember(addonsUiState.addons) { val addonRefreshKey = remember(addonsUiState.addons) {
addonsUiState.addons.mapNotNull { addon -> addonsUiState.addons.mapNotNull { addon ->
val manifest = addon.manifest ?: return@mapNotNull null val manifest = addon.manifest ?: return@mapNotNull null

View file

@ -80,6 +80,9 @@ import com.nuvio.app.features.watchprogress.ContinueWatchingPreferencesUiState
import nuvio.composeapp.generated.resources.Res import nuvio.composeapp.generated.resources.Res
import nuvio.composeapp.generated.resources.compose_settings_page_root import nuvio.composeapp.generated.resources.compose_settings_page_root
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jetbrains.compose.resources.stringResource import org.jetbrains.compose.resources.stringResource
@ -90,6 +93,8 @@ private const val SettingsSearchRevealHapticDelayMillis = 90L
@Composable @Composable
fun SettingsScreen( fun SettingsScreen(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
rootActionRequests: Flow<Unit> = emptyFlow(),
rootActionsEnabled: Boolean = true,
onSwitchProfile: (() -> Unit)? = null, onSwitchProfile: (() -> Unit)? = null,
onHomescreenClick: () -> Unit = {}, onHomescreenClick: () -> Unit = {},
onMetaScreenClick: () -> Unit = {}, onMetaScreenClick: () -> Unit = {},
@ -200,17 +205,31 @@ fun SettingsScreen(
} }
var currentPage by rememberSaveable { mutableStateOf(SettingsPage.Root.name) } var currentPage by rememberSaveable { mutableStateOf(SettingsPage.Root.name) }
val scrollToTopRequests = remember { MutableSharedFlow<Unit>(extraBufferCapacity = 1) }
val page = remember(currentPage) { SettingsPage.valueOf(currentPage) } val page = remember(currentPage) { SettingsPage.valueOf(currentPage) }
val previousPage = page.previousPage() val previousPage = page.previousPage()
LaunchedEffect(rootActionRequests, rootActionsEnabled, page) {
rootActionRequests.collect {
if (!rootActionsEnabled) return@collect
val pageToOpen = page.previousPage()
if (pageToOpen != null) {
currentPage = pageToOpen.name
} else {
scrollToTopRequests.tryEmit(Unit)
}
}
}
PlatformBackHandler( PlatformBackHandler(
enabled = previousPage != null, enabled = rootActionsEnabled && previousPage != null,
onBack = { previousPage?.let { currentPage = it.name } }, onBack = { previousPage?.let { currentPage = it.name } },
) )
if (maxWidth >= 768.dp) { if (maxWidth >= 768.dp) {
TabletSettingsScreen( TabletSettingsScreen(
page = page, page = page,
scrollToTopRequests = scrollToTopRequests,
onPageChange = { currentPage = it.name }, onPageChange = { currentPage = it.name },
showLoadingOverlay = playerSettingsUiState.showLoadingOverlay, showLoadingOverlay = playerSettingsUiState.showLoadingOverlay,
holdToSpeedEnabled = playerSettingsUiState.holdToSpeedEnabled, holdToSpeedEnabled = playerSettingsUiState.holdToSpeedEnabled,
@ -259,6 +278,7 @@ fun SettingsScreen(
} else { } else {
MobileSettingsScreen( MobileSettingsScreen(
page = page, page = page,
scrollToTopRequests = scrollToTopRequests,
onPageChange = { currentPage = it.name }, onPageChange = { currentPage = it.name },
showLoadingOverlay = playerSettingsUiState.showLoadingOverlay, showLoadingOverlay = playerSettingsUiState.showLoadingOverlay,
holdToSpeedEnabled = playerSettingsUiState.holdToSpeedEnabled, holdToSpeedEnabled = playerSettingsUiState.holdToSpeedEnabled,
@ -317,6 +337,7 @@ fun SettingsScreen(
@Composable @Composable
private fun MobileSettingsScreen( private fun MobileSettingsScreen(
page: SettingsPage, page: SettingsPage,
scrollToTopRequests: Flow<Unit>,
onPageChange: (SettingsPage) -> Unit, onPageChange: (SettingsPage) -> Unit,
showLoadingOverlay: Boolean, showLoadingOverlay: Boolean,
holdToSpeedEnabled: Boolean, holdToSpeedEnabled: Boolean,
@ -427,6 +448,12 @@ private fun MobileSettingsScreen(
} }
} }
LaunchedEffect(scrollToTopRequests) {
scrollToTopRequests.collect {
listState.animateScrollToItem(0)
}
}
NuvioScreen( NuvioScreen(
modifier = Modifier.nestedScroll(rootSearchRevealConnection), modifier = Modifier.nestedScroll(rootSearchRevealConnection),
listState = listState, listState = listState,
@ -624,6 +651,7 @@ private fun rememberSettingsRootSearchRevealConnection(
@Composable @Composable
private fun TabletSettingsScreen( private fun TabletSettingsScreen(
page: SettingsPage, page: SettingsPage,
scrollToTopRequests: Flow<Unit>,
onPageChange: (SettingsPage) -> Unit, onPageChange: (SettingsPage) -> Unit,
showLoadingOverlay: Boolean, showLoadingOverlay: Boolean,
holdToSpeedEnabled: Boolean, holdToSpeedEnabled: Boolean,
@ -773,6 +801,11 @@ private fun TabletSettingsScreen(
rootSearchRevealAnimating = false rootSearchRevealAnimating = false
} }
} }
LaunchedEffect(scrollToTopRequests) {
scrollToTopRequests.collect {
listState.animateScrollToItem(0)
}
}
LazyColumn( LazyColumn(
state = listState, state = listState,
modifier = Modifier modifier = Modifier