Use LazyColumn for all nav drawer items

This commit is contained in:
Damontecres 2025-10-07 00:21:18 -04:00
parent ffc704bdce
commit 46073cbd74
No known key found for this signature in database
2 changed files with 117 additions and 66 deletions

View file

@ -5,6 +5,7 @@ import android.media.AudioManager
import android.view.KeyEvent import android.view.KeyEvent
import androidx.compose.foundation.MarqueeAnimationMode import androidx.compose.foundation.MarqueeAnimationMode
import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.basicMarquee
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -17,6 +18,8 @@ import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.onFocusChanged import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.input.key.onPreviewKeyEvent import androidx.compose.ui.input.key.onPreviewKeyEvent
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.media3.common.Player import androidx.media3.common.Player
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
@ -26,6 +29,7 @@ import timber.log.Timber
import kotlin.contracts.ExperimentalContracts import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind import kotlin.contracts.InvocationKind
import kotlin.contracts.contract import kotlin.contracts.contract
import kotlin.math.min
import kotlin.time.Duration import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds import kotlin.time.Duration.Companion.milliseconds
import kotlin.time.Duration.Companion.minutes import kotlin.time.Duration.Companion.minutes
@ -199,3 +203,29 @@ inline fun <T : Collection<*>, R> T.letNotEmpty(block: (T) -> R): R? {
} }
return if (this.isNotEmpty()) block(this) else null return if (this.isNotEmpty()) block(this) else null
} }
// Adapted from https://stackoverflow.com/a/69196765
fun Arrangement.spacedByWithFooter(space: Dp) =
object : Arrangement.Vertical {
override val spacing = space
override fun Density.arrange(
totalSize: Int,
sizes: IntArray,
outPositions: IntArray,
) {
if (sizes.isEmpty()) return
val spacePx = space.roundToPx()
var occupied = 0
sizes.forEachIndexed { index, size ->
if (index == sizes.lastIndex) {
outPositions[index] = totalSize - size
} else {
outPositions[index] = min(occupied, totalSize - size)
}
val lastSpace = min(spacePx, totalSize - outPositions[index] - size)
occupied = outPositions[index] + size + lastSpace
}
}
}

View file

@ -7,7 +7,6 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@ -27,8 +26,8 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusProperties
import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.focus.focusRestorer
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.LocalContext import androidx.compose.ui.platform.LocalContext
@ -60,6 +59,9 @@ import com.github.damontecres.dolphin.data.ServerRepository
import com.github.damontecres.dolphin.data.model.BaseItem import com.github.damontecres.dolphin.data.model.BaseItem
import com.github.damontecres.dolphin.preferences.UserPreferences import com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.ui.FontAwesome import com.github.damontecres.dolphin.ui.FontAwesome
import com.github.damontecres.dolphin.ui.ifElse
import com.github.damontecres.dolphin.ui.spacedByWithFooter
import com.github.damontecres.dolphin.ui.tryRequestFocus
import com.github.damontecres.dolphin.util.ExceptionHandler import com.github.damontecres.dolphin.util.ExceptionHandler
import com.github.damontecres.dolphin.util.supportedCollectionTypes import com.github.damontecres.dolphin.util.supportedCollectionTypes
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@ -124,6 +126,7 @@ fun NavDrawer(
} }
val libraries by viewModel.libraries.observeAsState(listOf()) val libraries by viewModel.libraries.observeAsState(listOf())
val selectedIndex by viewModel.selectedIndex.observeAsState(-1) val selectedIndex by viewModel.selectedIndex.observeAsState(-1)
val focusRequester = remember { FocusRequester() }
NavigationDrawer( NavigationDrawer(
modifier = modifier =
@ -132,76 +135,94 @@ fun NavDrawer(
drawerState = drawerState, drawerState = drawerState,
drawerContent = { drawerContent = {
ProvideTextStyle(MaterialTheme.typography.labelMedium) { ProvideTextStyle(MaterialTheme.typography.labelMedium) {
Column( LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp), state = listState,
contentPadding = PaddingValues(0.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedByWithFooter(8.dp),
modifier = modifier =
Modifier Modifier
.focusGroup() .focusGroup()
// TODO fix focus restorer .focusProperties {
.focusRestorer() onEnter = {
.fillMaxHeight() focusRequester.tryRequestFocus()
}
}.fillMaxHeight()
.background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)), .background(MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp)),
) { ) {
IconNavItem( item {
text = user?.name ?: "", IconNavItem(
subtext = server?.name ?: server?.url, text = user?.name ?: "",
icon = Icons.Default.AccountCircle, subtext = server?.name ?: server?.url,
selected = false, icon = Icons.Default.AccountCircle,
onClick = { selected = false,
navigationManager.navigateTo(Destination.UserList) onClick = {
}, navigationManager.navigateTo(Destination.UserList)
) },
IconNavItem( )
text = "Search", }
icon = Icons.Default.Search, item {
selected = selectedIndex == -2, IconNavItem(
onClick = { text = "Search",
viewModel.setIndex(-2) icon = Icons.Default.Search,
navigationManager.navigateToFromDrawer(Destination.Search) selected = selectedIndex == -2,
}, onClick = {
) viewModel.setIndex(-2)
IconNavItem( navigationManager.navigateToFromDrawer(Destination.Search)
text = "Home", },
icon = Icons.Default.Home, modifier =
selected = selectedIndex == -1, Modifier.ifElse(
onClick = { selectedIndex == -2,
viewModel.setIndex(-1) Modifier.focusRequester(focusRequester),
if (destination is Destination.Main) { ),
navigationManager.reloadHome() )
} else { }
navigationManager.goToHome() item {
} IconNavItem(
}, text = "Home",
) icon = Icons.Default.Home,
LazyColumn( selected = selectedIndex == -1,
state = listState, onClick = {
contentPadding = PaddingValues(0.dp), viewModel.setIndex(-1)
horizontalAlignment = Alignment.CenterHorizontally, if (destination is Destination.Main) {
verticalArrangement = Arrangement.spacedBy(8.dp), navigationManager.reloadHome()
modifier = } else {
Modifier navigationManager.goToHome()
.weight(1f), }
) { },
itemsIndexed(libraries) { index, it -> modifier =
LibraryNavItem( Modifier.ifElse(
library = it, selectedIndex == -1,
selected = selectedIndex == index, Modifier.focusRequester(focusRequester),
onClick = { ),
viewModel.setIndex(index) )
navigationManager.navigateToFromDrawer(it.destination()) }
}, itemsIndexed(libraries) { index, it ->
) LibraryNavItem(
} library = it,
selected = selectedIndex == index,
onClick = {
viewModel.setIndex(index)
navigationManager.navigateToFromDrawer(it.destination())
},
modifier =
Modifier.ifElse(
selectedIndex == index,
Modifier.focusRequester(focusRequester),
),
)
}
item {
IconNavItem(
text = "Settings",
icon = Icons.Default.Settings,
selected = false,
onClick = {
navigationManager.navigateTo(Destination.Settings)
},
modifier = Modifier,
)
} }
IconNavItem(
text = "Settings",
icon = Icons.Default.Settings,
selected = false,
onClick = {
navigationManager.navigateTo(Destination.Settings)
},
modifier = Modifier,
)
} }
} }
}, },