mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 08:01:20 +02:00
Basic music playback
This commit is contained in:
parent
e8eb975966
commit
ee0c73f0e2
10 changed files with 355 additions and 44 deletions
|
|
@ -51,9 +51,13 @@ class NavDrawerItemRepository
|
|||
|
||||
val builtins =
|
||||
if (seerrServerRepository.active.first()) {
|
||||
listOf(NavDrawerItem.Favorites, NavDrawerItem.Discover)
|
||||
listOf(
|
||||
NavDrawerItem.Favorites,
|
||||
NavDrawerItem.Discover,
|
||||
NavDrawerItem.NowPlaying,
|
||||
)
|
||||
} else {
|
||||
listOf(NavDrawerItem.Favorites)
|
||||
listOf(NavDrawerItem.Favorites, NavDrawerItem.NowPlaying)
|
||||
}
|
||||
|
||||
val libraries =
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
package com.github.damontecres.wholphin.data.model
|
||||
|
||||
import org.jellyfin.sdk.model.extensions.ticks
|
||||
import java.util.UUID
|
||||
import kotlin.time.Duration
|
||||
|
||||
data class AudioItem(
|
||||
val id: UUID,
|
||||
val albumId: UUID?,
|
||||
val title: String?,
|
||||
val albumTitle: String?,
|
||||
val artistNames: String?,
|
||||
val runtime: Duration?,
|
||||
) {
|
||||
companion object {
|
||||
fun from(item: BaseItem): AudioItem =
|
||||
AudioItem(
|
||||
id = item.id,
|
||||
albumId = item.data.albumId,
|
||||
title = item.title,
|
||||
albumTitle = item.data.album,
|
||||
artistNames = item.data.albumArtist,
|
||||
runtime = item.data.runTimeTicks?.ticks,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -8,15 +8,22 @@ import androidx.media3.common.util.UnstableApi
|
|||
import androidx.media3.datasource.okhttp.OkHttpDataSource
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||
import com.github.damontecres.wholphin.data.model.AudioItem
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.services.hilt.AuthOkHttpClient
|
||||
import com.github.damontecres.wholphin.ui.toServerString
|
||||
import com.github.damontecres.wholphin.util.profile.Codec
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.OkHttpClient
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.universalAudioApi
|
||||
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
|
|
@ -29,7 +36,10 @@ class MusicService
|
|||
@param:AuthOkHttpClient private val authOkHttpClient: OkHttpClient,
|
||||
private val api: ApiClient,
|
||||
) {
|
||||
private val player: Player by lazy {
|
||||
private val _state = MutableStateFlow(MusicServiceState.EMPTY)
|
||||
val state: StateFlow<MusicServiceState> = _state
|
||||
|
||||
val player: Player by lazy {
|
||||
ExoPlayer
|
||||
.Builder(context)
|
||||
.setMediaSourceFactory(
|
||||
|
|
@ -37,36 +47,129 @@ class MusicService
|
|||
OkHttpDataSource.Factory(authOkHttpClient),
|
||||
),
|
||||
).build()
|
||||
.also {
|
||||
it.addListener(MusicPlayerListener(it, _state))
|
||||
it.prepare()
|
||||
}
|
||||
}
|
||||
|
||||
fun addToQueue(
|
||||
suspend fun setQueue(
|
||||
items: List<BaseItem>,
|
||||
shuffled: Boolean,
|
||||
) {
|
||||
Timber.d("setQueue: %s items, shuffled=%s", items.size, shuffled)
|
||||
val mediaItems =
|
||||
items
|
||||
.filter { it.type == BaseItemKind.AUDIO }
|
||||
.map(::convert)
|
||||
withContext(Dispatchers.Main) {
|
||||
player.setMediaItems(mediaItems)
|
||||
player.shuffleModeEnabled = shuffled
|
||||
player.play()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun addToQueue(
|
||||
item: BaseItem,
|
||||
position: Int? = null,
|
||||
) {
|
||||
if (item.type == BaseItemKind.AUDIO) {
|
||||
val url =
|
||||
api.universalAudioApi.getUniversalAudioStreamUrl(
|
||||
itemId = item.id,
|
||||
container =
|
||||
listOf(
|
||||
Codec.Audio.OPUS,
|
||||
Codec.Audio.MP3,
|
||||
Codec.Audio.AAC,
|
||||
Codec.Audio.FLAC,
|
||||
),
|
||||
)
|
||||
val mediaItem =
|
||||
MediaItem
|
||||
.Builder()
|
||||
.setUri(url)
|
||||
.setMediaId(item.id.toServerString())
|
||||
.setTag(item)
|
||||
.build()
|
||||
player.addMediaItem(mediaItem)
|
||||
if (player.mediaItemCount == 1) {
|
||||
// Start playing if this was the first time added
|
||||
player.play()
|
||||
val mediaItem = convert(item)
|
||||
withContext(Dispatchers.Main) {
|
||||
player.addMediaItem(mediaItem)
|
||||
if (player.mediaItemCount == 1) {
|
||||
// Start playing if this was the first time added
|
||||
player.play()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun convert(audio: BaseItem): MediaItem {
|
||||
val url =
|
||||
api.universalAudioApi.getUniversalAudioStreamUrl(
|
||||
itemId = audio.id,
|
||||
container =
|
||||
listOf(
|
||||
Codec.Audio.OPUS,
|
||||
Codec.Audio.MP3,
|
||||
Codec.Audio.AAC,
|
||||
Codec.Audio.FLAC,
|
||||
),
|
||||
)
|
||||
return MediaItem
|
||||
.Builder()
|
||||
.setUri(url)
|
||||
.setMediaId(audio.id.toServerString())
|
||||
.setTag(AudioItem.from(audio))
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
data class MusicServiceState(
|
||||
val queue: List<AudioItem>,
|
||||
val currentIndex: Int,
|
||||
val isPlaying: Boolean,
|
||||
) {
|
||||
companion object {
|
||||
val EMPTY = MusicServiceState(emptyList(), 0, false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Listens to [Player] events and updates the [StateFlow]
|
||||
*/
|
||||
private class MusicPlayerListener(
|
||||
private val player: Player,
|
||||
private val state: MutableStateFlow<MusicServiceState>,
|
||||
) : Player.Listener {
|
||||
init {
|
||||
Timber.v("MusicPlayerListener init")
|
||||
state.update {
|
||||
it.copy(
|
||||
queue = PlayerMediaItemList(player),
|
||||
currentIndex = player.currentMediaItemIndex,
|
||||
isPlaying = player.isPlaying,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onIsPlayingChanged(isPlaying: Boolean) {
|
||||
Timber.v("MusicPlayerListener onIsPlayingChanged")
|
||||
state.update {
|
||||
it.copy(
|
||||
// TODO this is hack to force the UI to update, but is very inefficient
|
||||
queue = PlayerMediaItemList(player),
|
||||
isPlaying = isPlaying,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMediaItemTransition(
|
||||
mediaItem: MediaItem?,
|
||||
reason: Int,
|
||||
) {
|
||||
Timber.v("MusicPlayerListener onMediaItemTransition")
|
||||
state.update {
|
||||
it.copy(
|
||||
queue = PlayerMediaItemList(player),
|
||||
currentIndex = player.currentMediaItemIndex,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class PlayerMediaItemList(
|
||||
private val player: Player,
|
||||
) : AbstractList<AudioItem>() {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,19 +40,14 @@ import androidx.tv.material3.MaterialTheme
|
|||
import androidx.tv.material3.Text
|
||||
import coil3.compose.AsyncImage
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.ItemPlaybackRepository
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||
import com.github.damontecres.wholphin.services.BackdropService
|
||||
import com.github.damontecres.wholphin.services.ExtrasService
|
||||
import com.github.damontecres.wholphin.services.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.services.ImageUrlService
|
||||
import com.github.damontecres.wholphin.services.MediaReportService
|
||||
import com.github.damontecres.wholphin.services.MusicService
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.services.PeopleFavorites
|
||||
import com.github.damontecres.wholphin.services.StreamChoiceService
|
||||
import com.github.damontecres.wholphin.services.ThemeSongPlayer
|
||||
import com.github.damontecres.wholphin.services.TrailerService
|
||||
import com.github.damontecres.wholphin.services.UserPreferencesService
|
||||
import com.github.damontecres.wholphin.ui.DefaultItemFields
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
|
|
@ -79,10 +74,12 @@ import kotlinx.coroutines.flow.StateFlow
|
|||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.itemsApi
|
||||
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
|
||||
import org.jellyfin.sdk.model.api.ImageType
|
||||
import org.jellyfin.sdk.model.api.ItemSortBy
|
||||
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import kotlin.time.Duration
|
||||
|
||||
|
|
@ -94,17 +91,12 @@ class AlbumViewModel
|
|||
@param:ApplicationContext private val context: Context,
|
||||
private val navigationManager: NavigationManager,
|
||||
val serverRepository: ServerRepository,
|
||||
val itemPlaybackRepository: ItemPlaybackRepository,
|
||||
val streamChoiceService: StreamChoiceService,
|
||||
val mediaReportService: MediaReportService,
|
||||
private val themeSongPlayer: ThemeSongPlayer,
|
||||
private val favoriteWatchManager: FavoriteWatchManager,
|
||||
private val peopleFavorites: PeopleFavorites,
|
||||
private val trailerService: TrailerService,
|
||||
private val extrasService: ExtrasService,
|
||||
private val userPreferencesService: UserPreferencesService,
|
||||
private val backdropService: BackdropService,
|
||||
private val imageUrlService: ImageUrlService,
|
||||
private val musicService: MusicService,
|
||||
@Assisted val itemId: UUID,
|
||||
) : ViewModel() {
|
||||
@AssistedFactory
|
||||
|
|
@ -151,11 +143,43 @@ class AlbumViewModel
|
|||
loading = LoadingState.Success,
|
||||
)
|
||||
}
|
||||
if (album.data.imageTags?.contains(ImageType.BACKDROP) == true) {
|
||||
backdropService.submit(album)
|
||||
} else {
|
||||
val artistIds =
|
||||
album.data.albumArtists
|
||||
?.shuffled()
|
||||
?.take(50)
|
||||
?.map { it.id }
|
||||
api.itemsApi
|
||||
.getItems(
|
||||
ids = artistIds,
|
||||
imageTypes = listOf(ImageType.BACKDROP),
|
||||
).content.items
|
||||
.firstOrNull()
|
||||
?.let { backdropService.submit(BaseItem(it, false)) }
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
_state.update { it.copy(loading = LoadingState.Error(ex)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun play(
|
||||
shuffled: Boolean,
|
||||
startIndex: Int = 0,
|
||||
) {
|
||||
viewModelScope.launchIO {
|
||||
Timber.v("Playing album %s from %s", itemId, startIndex)
|
||||
val songs = state.value.songs as ApiRequestPager<*>
|
||||
val songsToAdd =
|
||||
(startIndex..songs.lastIndex)
|
||||
.mapNotNull { idx ->
|
||||
songs.getBlocking(idx)
|
||||
}
|
||||
musicService.setQueue(songsToAdd, shuffled)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class AlbumState(
|
||||
|
|
@ -178,6 +202,7 @@ fun AlbumDetails(
|
|||
creationCallback = { it.create(itemId) },
|
||||
),
|
||||
) {
|
||||
val scope = rememberCoroutineScope()
|
||||
val state by viewModel.state.collectAsState()
|
||||
|
||||
when (val loading = state.loading) {
|
||||
|
|
@ -215,19 +240,22 @@ fun AlbumDetails(
|
|||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
AlbumButtons(
|
||||
onClickPlay = {},
|
||||
onClickPlay = { viewModel.play(it, 0) },
|
||||
onClickAddToPlaylist = {},
|
||||
onClickGoToArtist = {},
|
||||
onClickMore = { },
|
||||
buttonOnFocusChanged = {},
|
||||
modifier = Modifier,
|
||||
modifier =
|
||||
Modifier.onFocusChanged {
|
||||
if (it.hasFocus) scope.launch { bringIntoViewRequester.bringIntoView() }
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
itemsIndexed(state.songs) { index, song ->
|
||||
SongListItem(
|
||||
song = song,
|
||||
onClick = {},
|
||||
onClick = { viewModel.play(false, index) },
|
||||
onClickAddToQueue = {},
|
||||
onClickAddToPlaylist = {},
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,124 @@
|
|||
package com.github.damontecres.wholphin.ui.detail.music
|
||||
|
||||
import android.content.Context
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.focus.FocusRequester
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.ui.compose.state.rememberNextButtonState
|
||||
import androidx.media3.ui.compose.state.rememberPlayPauseButtonState
|
||||
import androidx.media3.ui.compose.state.rememberPreviousButtonState
|
||||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.wholphin.services.BackdropService
|
||||
import com.github.damontecres.wholphin.services.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.services.ImageUrlService
|
||||
import com.github.damontecres.wholphin.services.MusicService
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.services.UserPreferencesService
|
||||
import com.github.damontecres.wholphin.ui.playback.PlaybackButtons
|
||||
import com.github.damontecres.wholphin.ui.roundSeconds
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@HiltViewModel(assistedFactory = NowPlayingViewModel.Factory::class)
|
||||
class NowPlayingViewModel
|
||||
@AssistedInject
|
||||
constructor(
|
||||
private val api: ApiClient,
|
||||
@param:ApplicationContext private val context: Context,
|
||||
private val navigationManager: NavigationManager,
|
||||
private val favoriteWatchManager: FavoriteWatchManager,
|
||||
private val userPreferencesService: UserPreferencesService,
|
||||
private val backdropService: BackdropService,
|
||||
private val imageUrlService: ImageUrlService,
|
||||
private val musicService: MusicService,
|
||||
) : ViewModel() {
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(): NowPlayingViewModel
|
||||
}
|
||||
|
||||
val state get() = musicService.state
|
||||
val player get() = musicService.player
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
@Composable
|
||||
fun NowPlayingPage(
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: NowPlayingViewModel =
|
||||
hiltViewModel<NowPlayingViewModel, NowPlayingViewModel.Factory>(
|
||||
creationCallback = { it.create() },
|
||||
),
|
||||
) {
|
||||
val state by viewModel.state.collectAsState()
|
||||
val player = viewModel.player
|
||||
val current = state.queue.getOrNull(state.currentIndex)
|
||||
|
||||
val playPauseState = rememberPlayPauseButtonState(player)
|
||||
val previousState = rememberPreviousButtonState(player)
|
||||
val nextState = rememberNextButtonState(player)
|
||||
|
||||
Column(modifier = modifier.padding(16.dp)) {
|
||||
Text(
|
||||
text = current?.title ?: "",
|
||||
)
|
||||
Row {
|
||||
PlaybackButtons(
|
||||
player = player,
|
||||
initialFocusRequester = remember { FocusRequester() },
|
||||
onControllerInteraction = {},
|
||||
onPlaybackActionClick = {},
|
||||
showPlay = playPauseState.showPlay,
|
||||
previousEnabled = previousState.isEnabled,
|
||||
nextEnabled = nextState.isEnabled,
|
||||
seekBack = 10.seconds,
|
||||
skipBackOnResume = null,
|
||||
seekForward = 30.seconds,
|
||||
)
|
||||
}
|
||||
if (state.queue.isEmpty()) {
|
||||
Text("No items")
|
||||
} else {
|
||||
LazyColumn(
|
||||
contentPadding = PaddingValues(16.dp),
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
) {
|
||||
itemsIndexed(state.queue) { index, song ->
|
||||
SongListItem(
|
||||
title = song.title,
|
||||
artist = song.artistNames,
|
||||
indexNumber = index + 1,
|
||||
runtime = song.runtime?.roundSeconds,
|
||||
showArtist = true,
|
||||
isPlaying = state.currentIndex == index,
|
||||
onClick = {
|
||||
player.seekTo(index, 0L)
|
||||
},
|
||||
onClickAddToQueue = {},
|
||||
onClickAddToPlaylist = {},
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -62,7 +62,7 @@ fun SongListItem(
|
|||
showArtist: Boolean = false,
|
||||
) = SongListItem(
|
||||
title = song?.title,
|
||||
artist = if (showArtist) song?.artistsString else null,
|
||||
artist = if (showArtist) song?.data?.albumArtist else null,
|
||||
indexNumber = song?.data?.indexNumber,
|
||||
runtime =
|
||||
song
|
||||
|
|
@ -88,13 +88,14 @@ fun SongListItem(
|
|||
onClickAddToPlaylist: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
showArtist: Boolean = false,
|
||||
isPlaying: Boolean = false,
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
modifier = modifier,
|
||||
) {
|
||||
ListItem(
|
||||
selected = false,
|
||||
selected = isPlaying,
|
||||
onClick = onClick,
|
||||
leadingContent = {
|
||||
Text(
|
||||
|
|
@ -121,7 +122,7 @@ fun SongListItem(
|
|||
text = runtime.toString(),
|
||||
)
|
||||
},
|
||||
scale = ListItemDefaults.scale(1f, 1f),
|
||||
scale = ListItemDefaults.scale(1f, 1f, .95f),
|
||||
modifier = Modifier.weight(1f),
|
||||
)
|
||||
Button(
|
||||
|
|
|
|||
|
|
@ -126,6 +126,9 @@ sealed class Destination(
|
|||
val item: DiscoverItem,
|
||||
) : Destination(false)
|
||||
|
||||
@Serializable
|
||||
data object NowPlaying : Destination(true)
|
||||
|
||||
@Serializable
|
||||
data object UpdateApp : Destination(true)
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import com.github.damontecres.wholphin.ui.detail.discover.DiscoverSeriesDetails
|
|||
import com.github.damontecres.wholphin.ui.detail.episode.EpisodeDetails
|
||||
import com.github.damontecres.wholphin.ui.detail.movie.MovieDetails
|
||||
import com.github.damontecres.wholphin.ui.detail.music.AlbumDetails
|
||||
import com.github.damontecres.wholphin.ui.detail.music.NowPlayingPage
|
||||
import com.github.damontecres.wholphin.ui.detail.series.SeriesDetails
|
||||
import com.github.damontecres.wholphin.ui.detail.series.SeriesOverview
|
||||
import com.github.damontecres.wholphin.ui.discover.DiscoverPage
|
||||
|
|
@ -270,6 +271,10 @@ fun DestinationContent(
|
|||
)
|
||||
}
|
||||
|
||||
Destination.NowPlaying -> {
|
||||
NowPlayingPage(modifier)
|
||||
}
|
||||
|
||||
Destination.UpdateApp -> {
|
||||
InstallUpdatePage(preferences, modifier)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -207,6 +207,11 @@ class NavDrawerViewModel
|
|||
setIndex(index)
|
||||
navigationManager.navigateToFromDrawer(item.destination)
|
||||
}
|
||||
|
||||
NavDrawerItem.NowPlaying -> {
|
||||
setIndex(index)
|
||||
navigationManager.navigateToFromDrawer(Destination.NowPlaying)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -246,6 +251,13 @@ sealed interface NavDrawerItem {
|
|||
|
||||
override fun name(context: Context): String = context.getString(R.string.discover)
|
||||
}
|
||||
|
||||
object NowPlaying : NavDrawerItem {
|
||||
override val id: String
|
||||
get() = "a_nowplaying"
|
||||
|
||||
override fun name(context: Context): String = context.getString(R.string.now_playing)
|
||||
}
|
||||
}
|
||||
|
||||
data class ServerNavDrawerItem(
|
||||
|
|
@ -625,6 +637,10 @@ fun NavigationDrawerScope.NavItem(
|
|||
else -> R.string.fa_film
|
||||
}
|
||||
}
|
||||
|
||||
NavDrawerItem.NowPlaying -> {
|
||||
R.string.fa_circle_play
|
||||
}
|
||||
}
|
||||
}
|
||||
val focused by interactionSource.collectIsFocusedAsState()
|
||||
|
|
|
|||
|
|
@ -496,6 +496,7 @@
|
|||
<string name="artists">Artists</string>
|
||||
<string name="songs">Songs</string>
|
||||
<string name="go_to_artist">Go to artist</string>
|
||||
<string name="now_playing">Now playing</string>
|
||||
|
||||
<string-array name="theme_song_volume">
|
||||
<item>Disabled</item>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue