Basic show/hide now playing nav drawer item

This commit is contained in:
Damontecres 2026-02-24 13:09:59 -05:00
parent cd06c0380e
commit 1d47220991
No known key found for this signature in database
4 changed files with 82 additions and 17 deletions

View file

@ -179,7 +179,7 @@ private class MusicPlayerListener(
Timber.v("MusicPlayerListener init") Timber.v("MusicPlayerListener init")
state.update { state.update {
it.copy( it.copy(
queue = PlayerMediaItemList(player), queue = PlayerMediaItemList(player, player.mediaItemCount),
currentIndex = player.currentMediaItemIndex, currentIndex = player.currentMediaItemIndex,
isPlaying = player.isPlaying, isPlaying = player.isPlaying,
) )
@ -215,7 +215,7 @@ private class MusicPlayerListener(
if (reason == Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED) { if (reason == Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED) {
state.update { state.update {
it.copy( it.copy(
queue = PlayerMediaItemList(player), queue = PlayerMediaItemList(player, player.mediaItemCount),
currentIndex = player.currentMediaItemIndex, currentIndex = player.currentMediaItemIndex,
) )
} }
@ -225,15 +225,28 @@ private class MusicPlayerListener(
private class PlayerMediaItemList( private class PlayerMediaItemList(
private val player: Player, private val player: Player,
override val size: Int,
) : AbstractList<AudioItem>() { ) : AbstractList<AudioItem>() {
override fun get(index: Int): AudioItem { override fun get(index: Int): AudioItem {
// Timber.v("get %s", index) // Timber.v("get %s", index)
return player.getMediaItemAt(index).localConfiguration?.tag as AudioItem return player.getMediaItemAt(index).localConfiguration?.tag as AudioItem
} }
override val size: Int override fun equals(other: Any?): Boolean {
get() { if (this === other) return true
// Timber.v("size %s", player.mediaItemCount) if (other !is PlayerMediaItemList) return false
return player.mediaItemCount if (!super.equals(other)) return false
if (size != other.size) return false
if (player != other.player) return false
return true
}
override fun hashCode(): Int {
var result = super.hashCode()
result = 31 * result + size
result = 31 * result + player.hashCode()
return result
} }
} }

View file

@ -7,6 +7,7 @@ import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.data.model.JellyfinUser import com.github.damontecres.wholphin.data.model.JellyfinUser
import com.github.damontecres.wholphin.data.model.NavPinType import com.github.damontecres.wholphin.data.model.NavPinType
import com.github.damontecres.wholphin.services.hilt.DefaultCoroutineScope import com.github.damontecres.wholphin.services.hilt.DefaultCoroutineScope
import com.github.damontecres.wholphin.ui.launchDefault
import com.github.damontecres.wholphin.ui.main.settings.Library import com.github.damontecres.wholphin.ui.main.settings.Library
import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.nav.NavDrawerItem import com.github.damontecres.wholphin.ui.nav.NavDrawerItem
@ -14,8 +15,10 @@ import com.github.damontecres.wholphin.ui.nav.ServerNavDrawerItem
import com.github.damontecres.wholphin.util.supportedCollectionTypes import com.github.damontecres.wholphin.util.supportedCollectionTypes
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
@ -40,6 +43,7 @@ class NavDrawerService
private val serverRepository: ServerRepository, private val serverRepository: ServerRepository,
private val serverPreferencesDao: ServerPreferencesDao, private val serverPreferencesDao: ServerPreferencesDao,
private val seerrServerRepository: SeerrServerRepository, private val seerrServerRepository: SeerrServerRepository,
private val musicService: MusicService,
) { ) {
private val _state = MutableStateFlow(NavDrawerItemState.EMPTY) private val _state = MutableStateFlow(NavDrawerItemState.EMPTY)
val state: StateFlow<NavDrawerItemState> = _state val state: StateFlow<NavDrawerItemState> = _state
@ -65,6 +69,23 @@ class NavDrawerService
.onEach { discoverActive -> .onEach { discoverActive ->
_state.update { it.copy(discoverEnabled = discoverActive) } _state.update { it.copy(discoverEnabled = discoverActive) }
}.launchIn(coroutineScope) }.launchIn(coroutineScope)
coroutineScope.launchDefault {
musicService.state.collectLatest {
Timber.v("MusicService updated")
if (it.isPlaying) {
_state.update {
it.copy(nowPlayingEnabled = true)
}
} else {
// Don't immediately remove the now playing
// TODO need better now playing state to distinguish between paused & stopped
delay(30_000)
_state.update {
it.copy(nowPlayingEnabled = false)
}
}
}
}
} }
suspend fun getAllUserLibraries( suspend fun getAllUserLibraries(
@ -175,9 +196,10 @@ data class NavDrawerItemState(
val items: List<NavDrawerItem>, val items: List<NavDrawerItem>,
val moreItems: List<NavDrawerItem>, val moreItems: List<NavDrawerItem>,
val discoverEnabled: Boolean, val discoverEnabled: Boolean,
val nowPlayingEnabled: Boolean,
) { ) {
companion object { companion object {
val EMPTY = NavDrawerItemState(emptyList(), emptyList(), false) val EMPTY = NavDrawerItemState(emptyList(), emptyList(), false, false)
} }
} }

View file

@ -190,10 +190,11 @@ class RecommendedMusicViewModel
override fun update( override fun update(
@StringRes title: Int, @StringRes title: Int,
row: HomeRowLoadingState, row: HomeRowLoadingState,
) { ): HomeRowLoadingState {
rows.update { current -> rows.update { current ->
current.toMutableList().apply { set(rowTitles[title]!!, row) } current.toMutableList().apply { set(rowTitles[title]!!, row) }
} }
return row
} }
companion object { companion object {

View file

@ -24,6 +24,7 @@ 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
import androidx.compose.material.icons.filled.KeyboardArrowLeft import androidx.compose.material.icons.filled.KeyboardArrowLeft
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Search
import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Settings
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -178,9 +179,11 @@ class NavDrawerViewModel
if (key is Destination) { if (key is Destination) {
val index = val index =
if (key is Destination.Home) { if (key is Destination.Home) {
-1 HOME_INDEX
} else if (key is Destination.Search) { } else if (key is Destination.Search) {
-2 SEARCH_INDEX
} else if (key is Destination.NowPlaying) {
NOW_PLAYING_INDEX
} else { } else {
val idx = asDestinations.indexOf(key) val idx = asDestinations.indexOf(key)
if (idx >= 0) { if (idx >= 0) {
@ -242,6 +245,10 @@ data class ServerNavDrawerItem(
} }
} }
private const val HOME_INDEX = -1
private const val SEARCH_INDEX = -2
private const val NOW_PLAYING_INDEX = -3
/** /**
* Display the left side navigation drawer with [DestinationContent] on the right * Display the left side navigation drawer with [DestinationContent] on the right
*/ */
@ -353,32 +360,54 @@ fun NavDrawer(
IconNavItem( IconNavItem(
text = stringResource(R.string.search), text = stringResource(R.string.search),
icon = Icons.Default.Search, icon = Icons.Default.Search,
selected = selectedIndex == -2, selected = selectedIndex == SEARCH_INDEX,
drawerOpen = isOpen, drawerOpen = isOpen,
interactionSource = interactionSource, interactionSource = interactionSource,
onClick = { onClick = {
viewModel.setIndex(-2) viewModel.setIndex(SEARCH_INDEX)
viewModel.navigationManager.navigateToFromDrawer(Destination.Search) viewModel.navigationManager.navigateToFromDrawer(Destination.Search)
}, },
modifier = modifier =
Modifier Modifier
.focusRequester(searchFocusRequester) .focusRequester(searchFocusRequester)
.ifElse( .ifElse(
selectedIndex == -2, selectedIndex == SEARCH_INDEX,
Modifier.focusRequester(focusRequester), Modifier.focusRequester(focusRequester),
), ),
) )
} }
if (state.nowPlayingEnabled) {
item {
val interactionSource = remember { MutableInteractionSource() }
IconNavItem(
text = stringResource(R.string.now_playing),
icon = Icons.Default.PlayArrow,
selected = selectedIndex == NOW_PLAYING_INDEX,
drawerOpen = isOpen,
interactionSource = interactionSource,
onClick = {
viewModel.setIndex(NOW_PLAYING_INDEX)
viewModel.navigationManager.navigateToFromDrawer(Destination.NowPlaying)
},
modifier =
Modifier
.ifElse(
selectedIndex == NOW_PLAYING_INDEX,
Modifier.focusRequester(focusRequester),
).animateItem(),
)
}
}
item { item {
val interactionSource = remember { MutableInteractionSource() } val interactionSource = remember { MutableInteractionSource() }
IconNavItem( IconNavItem(
text = stringResource(R.string.home), text = stringResource(R.string.home),
icon = Icons.Default.Home, icon = Icons.Default.Home,
selected = selectedIndex == -1, selected = selectedIndex == HOME_INDEX,
drawerOpen = isOpen, drawerOpen = isOpen,
interactionSource = interactionSource, interactionSource = interactionSource,
onClick = { onClick = {
viewModel.setIndex(-1) viewModel.setIndex(HOME_INDEX)
if (destination is Destination.Home) { if (destination is Destination.Home) {
viewModel.navigationManager.reloadHome() viewModel.navigationManager.reloadHome()
} else { } else {
@ -388,7 +417,7 @@ fun NavDrawer(
modifier = modifier =
Modifier Modifier
.ifElse( .ifElse(
selectedIndex == -1, selectedIndex == HOME_INDEX,
Modifier.focusRequester(focusRequester), Modifier.focusRequester(focusRequester),
), ),
) )