mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-18 07:51:46 +00:00
feat: add scroll to top functionality across root screens
This commit is contained in:
parent
3c61e0d39e
commit
59bfb3f26b
6 changed files with 119 additions and 29 deletions
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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(
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue