Fix glitchy nav drawer scrolling when opening (#848)

## Description
The nav drawer would always glitch a little when it gained focus due it
to trying to keep the selected page in view. #842 made this even worst.

This PR fixes that issue by hoisting the list scroll state up higher
which ensures it is remembered between recompositions. This change also
eliminates the need for workarounds to try and scroll it
programmatically which is also a tiny performance boost.

### Related issues
Follow up to #842 

### Testing
Emulator & nvidia shield

## Screenshots
N/A

## AI or LLM usage
None
This commit is contained in:
Ray 2026-02-08 16:21:38 -05:00 committed by GitHub
parent 72e5e6ae86
commit 63b37ef3f4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 6 additions and 32 deletions

View file

@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.saveable.rememberSerializable import androidx.compose.runtime.saveable.rememberSerializable
@ -231,6 +232,7 @@ fun ApplicationContent(
) )
} }
} }
val navDrawerListState = rememberLazyListState()
NavDisplay( NavDisplay(
backStack = navigationManager.backStack, backStack = navigationManager.backStack,
onBack = { navigationManager.goBack() }, onBack = { navigationManager.goBack() },
@ -257,6 +259,7 @@ fun ApplicationContent(
user = user, user = user,
server = server, server = server,
drawerState = drawerState, drawerState = drawerState,
navDrawerListState = navDrawerListState,
onClearBackdrop = viewModel::clearBackdrop, onClearBackdrop = viewModel::clearBackdrop,
modifier = Modifier.fillMaxSize(), modifier = Modifier.fillMaxSize(),
) )

View file

@ -18,8 +18,8 @@ import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListState
import androidx.compose.foundation.lazy.itemsIndexed import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material.icons.filled.ArrowDropDown
import androidx.compose.material.icons.filled.Home import androidx.compose.material.icons.filled.Home
@ -41,7 +41,6 @@ import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
@ -88,12 +87,10 @@ import com.github.damontecres.wholphin.ui.spacedByWithFooter
import com.github.damontecres.wholphin.ui.theme.LocalTheme import com.github.damontecres.wholphin.ui.theme.LocalTheme
import com.github.damontecres.wholphin.ui.toServerString import com.github.damontecres.wholphin.ui.toServerString
import com.github.damontecres.wholphin.ui.tryRequestFocus import com.github.damontecres.wholphin.ui.tryRequestFocus
import com.github.damontecres.wholphin.util.ExceptionHandler
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.ApiClient
import org.jellyfin.sdk.api.client.extensions.imageApi import org.jellyfin.sdk.api.client.extensions.imageApi
@ -272,6 +269,7 @@ fun NavDrawer(
user: JellyfinUser, user: JellyfinUser,
server: JellyfinServer, server: JellyfinServer,
drawerState: DrawerState, drawerState: DrawerState,
navDrawerListState: LazyListState,
onClearBackdrop: () -> Unit, onClearBackdrop: () -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
viewModel: NavDrawerViewModel = viewModel: NavDrawerViewModel =
@ -320,8 +318,6 @@ fun NavDrawer(
visibilityThreshold = IntOffset.VisibilityThreshold, visibilityThreshold = IntOffset.VisibilityThreshold,
), ),
) )
val config = LocalConfiguration.current
val heightInPx = remember { with(density) { config.screenHeightDp.dp.roundToPx() } }
ModalNavigationDrawer( ModalNavigationDrawer(
modifier = modifier, modifier = modifier,
@ -329,28 +325,8 @@ fun NavDrawer(
drawerContent = { drawerValue -> drawerContent = { drawerValue ->
val isOpen = drawerValue.isOpen val isOpen = drawerValue.isOpen
val spacedBy = 4.dp val spacedBy = 4.dp
val listState = rememberLazyListState()
val searchFocusRequester = remember { FocusRequester() } val searchFocusRequester = remember { FocusRequester() }
suspend fun scrollToSelected() {
val target = selectedIndex + 2
try {
if (target !in
listState.firstVisibleItemIndex..<listState.layoutInfo.visibleItemsInfo.lastIndex
) {
val mult =
if ((target - 2) < listState.layoutInfo.totalItemsCount / 2) -1 else 1
listState.animateScrollToItem(selectedIndex + 2, mult * (heightInPx / 2))
}
} catch (ex: Exception) {
Timber.w(ex, "Error scrolling to %s", target)
}
}
LaunchedEffect(selectedIndex) {
scrollToSelected()
}
ProvideTextStyle(MaterialTheme.typography.labelMedium) { ProvideTextStyle(MaterialTheme.typography.labelMedium) {
Column( Column(
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
@ -374,7 +350,7 @@ fun NavDrawer(
modifier = Modifier, modifier = Modifier,
) )
LazyColumn( LazyColumn(
state = listState, state = navDrawerListState,
contentPadding = PaddingValues(0.dp), contentPadding = PaddingValues(0.dp),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedByWithFooter(spacedBy), verticalArrangement = Arrangement.spacedByWithFooter(spacedBy),
@ -389,11 +365,6 @@ fun NavDrawer(
focusRequester.tryRequestFocus() focusRequester.tryRequestFocus()
} }
} }
onExit = {
scope.launch(ExceptionHandler()) {
scrollToSelected()
}
}
}.fillMaxHeight(), }.fillMaxHeight(),
) { ) {
item { item {