From 1d4722099146d0f7c4c4800624fec1c67d56d88e Mon Sep 17 00:00:00 2001 From: Damontecres Date: Tue, 24 Feb 2026 13:09:59 -0500 Subject: [PATCH] Basic show/hide now playing nav drawer item --- .../wholphin/services/MusicService.kt | 27 ++++++++--- .../wholphin/services/NavDrawerService.kt | 24 +++++++++- .../ui/components/RecommendedMusic.kt | 3 +- .../damontecres/wholphin/ui/nav/NavDrawer.kt | 45 +++++++++++++++---- 4 files changed, 82 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/MusicService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/MusicService.kt index dee8b027..89260aa0 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/MusicService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/MusicService.kt @@ -179,7 +179,7 @@ private class MusicPlayerListener( Timber.v("MusicPlayerListener init") state.update { it.copy( - queue = PlayerMediaItemList(player), + queue = PlayerMediaItemList(player, player.mediaItemCount), currentIndex = player.currentMediaItemIndex, isPlaying = player.isPlaying, ) @@ -215,7 +215,7 @@ private class MusicPlayerListener( if (reason == Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED) { state.update { it.copy( - queue = PlayerMediaItemList(player), + queue = PlayerMediaItemList(player, player.mediaItemCount), currentIndex = player.currentMediaItemIndex, ) } @@ -225,15 +225,28 @@ private class MusicPlayerListener( private class PlayerMediaItemList( private val player: Player, + override val size: Int, ) : AbstractList() { override fun get(index: Int): AudioItem { // Timber.v("get %s", index) return player.getMediaItemAt(index).localConfiguration?.tag as AudioItem } - override val size: Int - get() { -// Timber.v("size %s", player.mediaItemCount) - return player.mediaItemCount - } + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is PlayerMediaItemList) return false + 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 + } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/NavDrawerService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/NavDrawerService.kt index adb21ff7..94551700 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/NavDrawerService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/NavDrawerService.kt @@ -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.NavPinType 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.nav.Destination 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 dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -40,6 +43,7 @@ class NavDrawerService private val serverRepository: ServerRepository, private val serverPreferencesDao: ServerPreferencesDao, private val seerrServerRepository: SeerrServerRepository, + private val musicService: MusicService, ) { private val _state = MutableStateFlow(NavDrawerItemState.EMPTY) val state: StateFlow = _state @@ -65,6 +69,23 @@ class NavDrawerService .onEach { discoverActive -> _state.update { it.copy(discoverEnabled = discoverActive) } }.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( @@ -175,9 +196,10 @@ data class NavDrawerItemState( val items: List, val moreItems: List, val discoverEnabled: Boolean, + val nowPlayingEnabled: Boolean, ) { companion object { - val EMPTY = NavDrawerItemState(emptyList(), emptyList(), false) + val EMPTY = NavDrawerItemState(emptyList(), emptyList(), false, false) } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedMusic.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedMusic.kt index 77a82e3b..e24f9f1b 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedMusic.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/components/RecommendedMusic.kt @@ -190,10 +190,11 @@ class RecommendedMusicViewModel override fun update( @StringRes title: Int, row: HomeRowLoadingState, - ) { + ): HomeRowLoadingState { rows.update { current -> current.toMutableList().apply { set(rowTitles[title]!!, row) } } + return row } companion object { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/NavDrawer.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/NavDrawer.kt index f0276ffd..c2641474 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/NavDrawer.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/NavDrawer.kt @@ -24,6 +24,7 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ArrowDropDown import androidx.compose.material.icons.filled.Home 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.Settings import androidx.compose.runtime.Composable @@ -178,9 +179,11 @@ class NavDrawerViewModel if (key is Destination) { val index = if (key is Destination.Home) { - -1 + HOME_INDEX } else if (key is Destination.Search) { - -2 + SEARCH_INDEX + } else if (key is Destination.NowPlaying) { + NOW_PLAYING_INDEX } else { val idx = asDestinations.indexOf(key) 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 */ @@ -353,32 +360,54 @@ fun NavDrawer( IconNavItem( text = stringResource(R.string.search), icon = Icons.Default.Search, - selected = selectedIndex == -2, + selected = selectedIndex == SEARCH_INDEX, drawerOpen = isOpen, interactionSource = interactionSource, onClick = { - viewModel.setIndex(-2) + viewModel.setIndex(SEARCH_INDEX) viewModel.navigationManager.navigateToFromDrawer(Destination.Search) }, modifier = Modifier .focusRequester(searchFocusRequester) .ifElse( - selectedIndex == -2, + selectedIndex == SEARCH_INDEX, 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 { val interactionSource = remember { MutableInteractionSource() } IconNavItem( text = stringResource(R.string.home), icon = Icons.Default.Home, - selected = selectedIndex == -1, + selected = selectedIndex == HOME_INDEX, drawerOpen = isOpen, interactionSource = interactionSource, onClick = { - viewModel.setIndex(-1) + viewModel.setIndex(HOME_INDEX) if (destination is Destination.Home) { viewModel.navigationManager.reloadHome() } else { @@ -388,7 +417,7 @@ fun NavDrawer( modifier = Modifier .ifElse( - selectedIndex == -1, + selectedIndex == HOME_INDEX, Modifier.focusRequester(focusRequester), ), )