mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
More button & queue management
This commit is contained in:
parent
11e28a605c
commit
b48eb9898c
6 changed files with 265 additions and 116 deletions
|
|
@ -15,6 +15,7 @@ import com.github.damontecres.wholphin.data.model.BaseItem
|
||||||
import com.github.damontecres.wholphin.services.hilt.AuthOkHttpClient
|
import com.github.damontecres.wholphin.services.hilt.AuthOkHttpClient
|
||||||
import com.github.damontecres.wholphin.ui.DefaultItemFields
|
import com.github.damontecres.wholphin.ui.DefaultItemFields
|
||||||
import com.github.damontecres.wholphin.ui.toServerString
|
import com.github.damontecres.wholphin.ui.toServerString
|
||||||
|
import com.github.damontecres.wholphin.util.BlockingList
|
||||||
import com.github.damontecres.wholphin.util.profile.Codec
|
import com.github.damontecres.wholphin.util.profile.Codec
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
@ -75,6 +76,33 @@ class MusicService
|
||||||
setQueue(items, false)
|
setQueue(items, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replace the queue with the given list and starting playing the song as startIndex as soon as its ready
|
||||||
|
*
|
||||||
|
* Fetches each item in a blocking way and adds to the queue
|
||||||
|
*/
|
||||||
|
suspend fun setQueue(
|
||||||
|
items: BlockingList<BaseItem?>,
|
||||||
|
startIndex: Int,
|
||||||
|
shuffled: Boolean,
|
||||||
|
) = withContext(Dispatchers.IO) {
|
||||||
|
Timber.d("setQueue: %s items, startIndex=%s, shuffled=%s", items.size, startIndex, shuffled)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
player.setMediaItems(emptyList())
|
||||||
|
player.shuffleModeEnabled = shuffled
|
||||||
|
player.play()
|
||||||
|
}
|
||||||
|
(startIndex..items.lastIndex).forEach {
|
||||||
|
val item = items.getBlocking(it)
|
||||||
|
if (item != null && item.type == BaseItemKind.AUDIO) {
|
||||||
|
val mediaItem = convert(item)
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
player.addMediaItem(mediaItem)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun setQueue(
|
suspend fun setQueue(
|
||||||
items: List<BaseItem>,
|
items: List<BaseItem>,
|
||||||
shuffled: Boolean,
|
shuffled: Boolean,
|
||||||
|
|
@ -136,6 +164,8 @@ data class MusicServiceState(
|
||||||
companion object {
|
companion object {
|
||||||
val EMPTY = MusicServiceState(emptyList(), 0, false)
|
val EMPTY = MusicServiceState(emptyList(), 0, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val currentItemId: UUID? get() = if (isPlaying) queue.getOrNull(currentIndex)?.id else null
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,30 @@
|
||||||
package com.github.damontecres.wholphin.ui.detail.music
|
package com.github.damontecres.wholphin.ui.detail.music
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.compose.foundation.focusGroup
|
|
||||||
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.Column
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
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.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.LazyRow
|
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.relocation.BringIntoViewRequester
|
import androidx.compose.foundation.relocation.BringIntoViewRequester
|
||||||
import androidx.compose.foundation.relocation.bringIntoViewRequester
|
import androidx.compose.foundation.relocation.bringIntoViewRequester
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.filled.AccountCircle
|
|
||||||
import androidx.compose.material.icons.filled.PlayArrow
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
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.FocusState
|
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.focus.focusRestorer
|
|
||||||
import androidx.compose.ui.focus.onFocusChanged
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.text.style.TextOverflow
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
|
@ -52,9 +47,9 @@ import com.github.damontecres.wholphin.services.MusicService
|
||||||
import com.github.damontecres.wholphin.services.NavigationManager
|
import com.github.damontecres.wholphin.services.NavigationManager
|
||||||
import com.github.damontecres.wholphin.services.UserPreferencesService
|
import com.github.damontecres.wholphin.services.UserPreferencesService
|
||||||
import com.github.damontecres.wholphin.ui.DefaultItemFields
|
import com.github.damontecres.wholphin.ui.DefaultItemFields
|
||||||
|
import com.github.damontecres.wholphin.ui.components.DialogParams
|
||||||
|
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
||||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||||
import com.github.damontecres.wholphin.ui.components.ExpandableFaButton
|
|
||||||
import com.github.damontecres.wholphin.ui.components.ExpandablePlayButton
|
|
||||||
import com.github.damontecres.wholphin.ui.components.GenreText
|
import com.github.damontecres.wholphin.ui.components.GenreText
|
||||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||||
import com.github.damontecres.wholphin.ui.components.OverviewText
|
import com.github.damontecres.wholphin.ui.components.OverviewText
|
||||||
|
|
@ -85,7 +80,6 @@ import org.jellyfin.sdk.model.api.ItemSortBy
|
||||||
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
import org.jellyfin.sdk.model.api.request.GetItemsRequest
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import kotlin.time.Duration
|
|
||||||
|
|
||||||
@HiltViewModel(assistedFactory = AlbumViewModel.Factory::class)
|
@HiltViewModel(assistedFactory = AlbumViewModel.Factory::class)
|
||||||
class AlbumViewModel
|
class AlbumViewModel
|
||||||
|
|
@ -93,7 +87,7 @@ class AlbumViewModel
|
||||||
constructor(
|
constructor(
|
||||||
private val api: ApiClient,
|
private val api: ApiClient,
|
||||||
@param:ApplicationContext private val context: Context,
|
@param:ApplicationContext private val context: Context,
|
||||||
private val navigationManager: NavigationManager,
|
val navigationManager: NavigationManager,
|
||||||
val serverRepository: ServerRepository,
|
val serverRepository: ServerRepository,
|
||||||
val mediaReportService: MediaReportService,
|
val mediaReportService: MediaReportService,
|
||||||
private val favoriteWatchManager: FavoriteWatchManager,
|
private val favoriteWatchManager: FavoriteWatchManager,
|
||||||
|
|
@ -111,6 +105,8 @@ class AlbumViewModel
|
||||||
private val _state = MutableStateFlow(AlbumState.EMPTY)
|
private val _state = MutableStateFlow(AlbumState.EMPTY)
|
||||||
val state: StateFlow<AlbumState> = _state
|
val state: StateFlow<AlbumState> = _state
|
||||||
|
|
||||||
|
val currentMusic = musicService.state
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
try {
|
try {
|
||||||
|
|
@ -169,8 +165,10 @@ class AlbumViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setFavorite(favorite: Boolean) =
|
fun setFavorite(
|
||||||
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
itemId: UUID,
|
||||||
|
favorite: Boolean,
|
||||||
|
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||||
favoriteWatchManager.setFavorite(itemId, favorite)
|
favoriteWatchManager.setFavorite(itemId, favorite)
|
||||||
val album =
|
val album =
|
||||||
api.userLibraryApi
|
api.userLibraryApi
|
||||||
|
|
@ -187,12 +185,27 @@ class AlbumViewModel
|
||||||
viewModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
Timber.v("Playing album %s from %s", itemId, startIndex)
|
Timber.v("Playing album %s from %s", itemId, startIndex)
|
||||||
val songs = state.value.songs as ApiRequestPager<*>
|
val songs = state.value.songs as ApiRequestPager<*>
|
||||||
val songsToAdd =
|
musicService.setQueue(songs, startIndex, shuffled)
|
||||||
(startIndex..songs.lastIndex)
|
}
|
||||||
.mapNotNull { idx ->
|
}
|
||||||
songs.getBlocking(idx)
|
|
||||||
|
fun addToQueue(
|
||||||
|
itemId: UUID,
|
||||||
|
index: Int,
|
||||||
|
) {
|
||||||
|
viewModelScope.launchIO {
|
||||||
|
val songs = state.value.songs as ApiRequestPager<*>
|
||||||
|
if (itemId == this@AlbumViewModel.itemId) {
|
||||||
|
songs.indices.forEach {
|
||||||
|
songs.getBlocking(it)?.let {
|
||||||
|
musicService.addToQueue(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
songs.getBlocking(index)?.let {
|
||||||
|
musicService.addToQueue(it)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
musicService.setQueue(songsToAdd, shuffled)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -225,8 +238,22 @@ fun AlbumDetailsPage(
|
||||||
creationCallback = { it.create(itemId) },
|
creationCallback = { it.create(itemId) },
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
|
val context = LocalContext.current
|
||||||
val scope = rememberCoroutineScope()
|
val scope = rememberCoroutineScope()
|
||||||
val state by viewModel.state.collectAsState()
|
val state by viewModel.state.collectAsState()
|
||||||
|
val currentMusic by viewModel.currentMusic.collectAsState()
|
||||||
|
|
||||||
|
var moreDialog by remember { mutableStateOf<DialogParams?>(null) }
|
||||||
|
val moreDialogActions =
|
||||||
|
remember {
|
||||||
|
MusicMoreDialogActions(
|
||||||
|
onNavigate = { viewModel.navigationManager.navigateTo(it) },
|
||||||
|
onClickPlay = { index, _ -> viewModel.play(false, index) },
|
||||||
|
onClickAddToQueue = { index, itemId -> viewModel.addToQueue(itemId, index) },
|
||||||
|
onClickFavorite = { itemId, favorite -> viewModel.setFavorite(itemId, favorite) },
|
||||||
|
onClickAddPlaylist = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
when (val loading = state.loading) {
|
when (val loading = state.loading) {
|
||||||
is LoadingState.Error -> {
|
is LoadingState.Error -> {
|
||||||
|
|
@ -271,9 +298,25 @@ fun AlbumDetailsPage(
|
||||||
MusicButtonActions(
|
MusicButtonActions(
|
||||||
onClickPlay = { viewModel.play(it, 0) },
|
onClickPlay = { viewModel.play(it, 0) },
|
||||||
onClickInstantMix = viewModel::startInstantMix,
|
onClickInstantMix = viewModel::startInstantMix,
|
||||||
onClickFavorite = { viewModel.setFavorite(!album.favorite) },
|
onClickFavorite = {
|
||||||
|
viewModel.setFavorite(
|
||||||
|
album.id,
|
||||||
|
!album.favorite,
|
||||||
|
)
|
||||||
|
},
|
||||||
onClickMore = {
|
onClickMore = {
|
||||||
// TODO
|
moreDialog =
|
||||||
|
DialogParams(
|
||||||
|
fromLongClick = false,
|
||||||
|
title = album.name + " (${album.data.productionYear ?: ""})",
|
||||||
|
items =
|
||||||
|
buildMoreDialogForMusic(
|
||||||
|
context = context,
|
||||||
|
actions = moreDialogActions,
|
||||||
|
item = album,
|
||||||
|
index = 0,
|
||||||
|
),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
|
@ -295,16 +338,41 @@ fun AlbumDetailsPage(
|
||||||
SongListItem(
|
SongListItem(
|
||||||
song = song,
|
song = song,
|
||||||
onClick = { viewModel.play(false, index) },
|
onClick = { viewModel.play(false, index) },
|
||||||
onClickAddToQueue = {},
|
onLongClick = {
|
||||||
onClickAddToPlaylist = {},
|
if (song != null) {
|
||||||
|
moreDialog =
|
||||||
|
DialogParams(
|
||||||
|
fromLongClick = true,
|
||||||
|
title = song.name ?: "",
|
||||||
|
items =
|
||||||
|
buildMoreDialogForMusic(
|
||||||
|
context = context,
|
||||||
|
actions = moreDialogActions,
|
||||||
|
item = song,
|
||||||
|
index = index,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
modifier = Modifier.padding(horizontal = 16.dp),
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
showArtist = false,
|
showArtist = false,
|
||||||
|
isPlaying = song != null && currentMusic.currentItemId == song.id,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
moreDialog?.let { params ->
|
||||||
|
DialogPopup(
|
||||||
|
showDialog = true,
|
||||||
|
title = params.title,
|
||||||
|
dialogItems = params.items,
|
||||||
|
onDismissRequest = { moreDialog = null },
|
||||||
|
dismissOnClick = true,
|
||||||
|
waitToLoad = params.fromLongClick,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -388,57 +456,3 @@ fun AlbumHeader(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun AlbumButtons(
|
|
||||||
onClickPlay: (Boolean) -> Unit,
|
|
||||||
onClickAddToPlaylist: () -> Unit,
|
|
||||||
onClickGoToArtist: () -> Unit,
|
|
||||||
onClickMore: () -> Unit,
|
|
||||||
buttonOnFocusChanged: (FocusState) -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
|
||||||
) {
|
|
||||||
val firstFocus = remember { FocusRequester() }
|
|
||||||
LazyRow(
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
|
||||||
contentPadding = PaddingValues(8.dp),
|
|
||||||
modifier =
|
|
||||||
modifier
|
|
||||||
.focusGroup()
|
|
||||||
.focusRestorer(firstFocus),
|
|
||||||
) {
|
|
||||||
item {
|
|
||||||
ExpandablePlayButton(
|
|
||||||
title = R.string.play,
|
|
||||||
resume = Duration.ZERO,
|
|
||||||
icon = Icons.Default.PlayArrow,
|
|
||||||
onClick = { onClickPlay.invoke(false) },
|
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.onFocusChanged(buttonOnFocusChanged)
|
|
||||||
.focusRequester(firstFocus),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
ExpandableFaButton(
|
|
||||||
title = R.string.shuffle,
|
|
||||||
iconStringRes = R.string.fa_shuffle,
|
|
||||||
onClick = { onClickPlay.invoke(true) },
|
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.onFocusChanged(buttonOnFocusChanged),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
item {
|
|
||||||
ExpandablePlayButton(
|
|
||||||
title = R.string.go_to_artist,
|
|
||||||
resume = Duration.ZERO,
|
|
||||||
icon = Icons.Default.AccountCircle,
|
|
||||||
onClick = { onClickGoToArtist.invoke() },
|
|
||||||
modifier =
|
|
||||||
Modifier
|
|
||||||
.onFocusChanged(buttonOnFocusChanged),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -113,8 +113,7 @@ fun NowPlayingPage(
|
||||||
onClick = {
|
onClick = {
|
||||||
player.seekTo(index, 0L)
|
player.seekTo(index, 0L)
|
||||||
},
|
},
|
||||||
onClickAddToQueue = {},
|
onLongClick = {},
|
||||||
onClickAddToPlaylist = {},
|
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,27 @@
|
||||||
package com.github.damontecres.wholphin.ui.detail.music
|
package com.github.damontecres.wholphin.ui.detail.music
|
||||||
|
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.tv.material3.Button
|
import androidx.tv.material3.Icon
|
||||||
import androidx.tv.material3.ButtonDefaults
|
|
||||||
import androidx.tv.material3.ListItem
|
import androidx.tv.material3.ListItem
|
||||||
import androidx.tv.material3.ListItemDefaults
|
import androidx.tv.material3.ListItemDefaults
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
import com.github.damontecres.wholphin.data.model.BaseItem
|
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||||
import com.github.damontecres.wholphin.ui.PreviewTvSpec
|
import com.github.damontecres.wholphin.ui.PreviewTvSpec
|
||||||
|
import com.github.damontecres.wholphin.ui.enableMarquee
|
||||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||||
import com.github.damontecres.wholphin.ui.letNotEmpty
|
import com.github.damontecres.wholphin.ui.letNotEmpty
|
||||||
import com.github.damontecres.wholphin.ui.roundMinutes
|
import com.github.damontecres.wholphin.ui.roundMinutes
|
||||||
|
|
@ -31,8 +36,7 @@ fun SongListDisplay(
|
||||||
songs: List<BaseItem?>,
|
songs: List<BaseItem?>,
|
||||||
showArtist: Boolean,
|
showArtist: Boolean,
|
||||||
onClick: (Int, BaseItem) -> Unit,
|
onClick: (Int, BaseItem) -> Unit,
|
||||||
onClickAddToQueue: (Int, BaseItem) -> Unit,
|
onLongClick: (Int, BaseItem) -> Unit,
|
||||||
onClickAddToPlaylist: (Int, BaseItem) -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
LazyColumn(
|
LazyColumn(
|
||||||
|
|
@ -43,8 +47,7 @@ fun SongListDisplay(
|
||||||
SongListItem(
|
SongListItem(
|
||||||
song = song,
|
song = song,
|
||||||
onClick = { song?.let { onClick.invoke(index, song) } },
|
onClick = { song?.let { onClick.invoke(index, song) } },
|
||||||
onClickAddToQueue = { song?.let { onClick.invoke(index, song) } },
|
onLongClick = { song?.let { onLongClick.invoke(index, song) } },
|
||||||
onClickAddToPlaylist = { song?.let { onClick.invoke(index, song) } },
|
|
||||||
showArtist = showArtist,
|
showArtist = showArtist,
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
)
|
)
|
||||||
|
|
@ -56,10 +59,10 @@ fun SongListDisplay(
|
||||||
fun SongListItem(
|
fun SongListItem(
|
||||||
song: BaseItem?,
|
song: BaseItem?,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onClickAddToQueue: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
onClickAddToPlaylist: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
showArtist: Boolean = false,
|
showArtist: Boolean = false,
|
||||||
|
isPlaying: Boolean = false,
|
||||||
) = SongListItem(
|
) = SongListItem(
|
||||||
title = song?.title,
|
title = song?.title,
|
||||||
artist = if (showArtist) song?.data?.albumArtist else null,
|
artist = if (showArtist) song?.data?.albumArtist else null,
|
||||||
|
|
@ -71,10 +74,10 @@ fun SongListItem(
|
||||||
?.ticks
|
?.ticks
|
||||||
?.roundMinutes,
|
?.roundMinutes,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
onClickAddToQueue = onClickAddToQueue,
|
onLongClick = onLongClick,
|
||||||
onClickAddToPlaylist = onClickAddToPlaylist,
|
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
showArtist = showArtist,
|
showArtist = showArtist,
|
||||||
|
isPlaying = isPlaying,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -84,27 +87,42 @@ fun SongListItem(
|
||||||
indexNumber: Int?,
|
indexNumber: Int?,
|
||||||
runtime: Duration?,
|
runtime: Duration?,
|
||||||
onClick: () -> Unit,
|
onClick: () -> Unit,
|
||||||
onClickAddToQueue: () -> Unit,
|
onLongClick: () -> Unit,
|
||||||
onClickAddToPlaylist: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
showArtist: Boolean = false,
|
showArtist: Boolean = false,
|
||||||
isPlaying: Boolean = false,
|
isPlaying: Boolean = false,
|
||||||
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
) {
|
) {
|
||||||
|
val focused by interactionSource.collectIsFocusedAsState()
|
||||||
ListItem(
|
ListItem(
|
||||||
selected = isPlaying,
|
selected = isPlaying,
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
interactionSource = interactionSource,
|
||||||
leadingContent = {
|
leadingContent = {
|
||||||
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
) {
|
||||||
Text(
|
Text(
|
||||||
text = indexNumber?.toString() ?: "",
|
text = indexNumber?.toString() ?: "",
|
||||||
)
|
)
|
||||||
|
if (isPlaying) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.PlayArrow,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
headlineContent = {
|
headlineContent = {
|
||||||
Text(
|
Text(
|
||||||
text = title ?: "",
|
text = title ?: "",
|
||||||
|
maxLines = 1,
|
||||||
|
modifier = Modifier.enableMarquee(focused),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
supportingContent =
|
supportingContent =
|
||||||
|
|
@ -125,14 +143,6 @@ fun SongListItem(
|
||||||
scale = ListItemDefaults.scale(1f, 1f, .95f),
|
scale = ListItemDefaults.scale(1f, 1f, .95f),
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
)
|
)
|
||||||
Button(
|
|
||||||
onClick = onClickAddToQueue,
|
|
||||||
shape = ButtonDefaults.shape(shape = RoundedCornerShape(8.dp)),
|
|
||||||
) {
|
|
||||||
Text(
|
|
||||||
text = "Add to queue",
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -149,21 +159,20 @@ fun SongListItemPreview() {
|
||||||
indexNumber = 1,
|
indexNumber = 1,
|
||||||
runtime = 2.minutes + 30.seconds,
|
runtime = 2.minutes + 30.seconds,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
onClickAddToQueue = { },
|
onLongClick = { },
|
||||||
onClickAddToPlaylist = {},
|
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
showArtist = false,
|
showArtist = false,
|
||||||
)
|
)
|
||||||
SongListItem(
|
SongListItem(
|
||||||
title = "Song title",
|
title = "Song title",
|
||||||
artist = "Artists",
|
artist = "Artists",
|
||||||
indexNumber = 1,
|
indexNumber = 2,
|
||||||
runtime = 2.minutes + 30.seconds,
|
runtime = 2.minutes + 30.seconds,
|
||||||
onClick = {},
|
onClick = {},
|
||||||
onClickAddToQueue = { },
|
onLongClick = { },
|
||||||
onClickAddToPlaylist = {},
|
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
showArtist = true,
|
showArtist = true,
|
||||||
|
isPlaying = true,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
package com.github.damontecres.wholphin.ui.detail.music
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Add
|
||||||
|
import androidx.compose.material.icons.filled.ArrowForward
|
||||||
|
import androidx.compose.material.icons.filled.PlayArrow
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import com.github.damontecres.wholphin.R
|
||||||
|
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||||
|
import com.github.damontecres.wholphin.ui.components.DialogItem
|
||||||
|
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||||
|
import org.jellyfin.sdk.model.UUID
|
||||||
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
|
|
||||||
|
data class MusicMoreDialogActions(
|
||||||
|
val onNavigate: (Destination) -> Unit,
|
||||||
|
val onClickPlay: (Int, UUID) -> Unit,
|
||||||
|
val onClickAddToQueue: (Int, UUID) -> Unit,
|
||||||
|
val onClickFavorite: (UUID, Boolean) -> Unit,
|
||||||
|
val onClickAddPlaylist: (UUID) -> Unit,
|
||||||
|
val onClickGoToAlbum: (UUID) -> Unit = {
|
||||||
|
onNavigate.invoke(Destination.MediaItem(itemId = it, type = BaseItemKind.MUSIC_ALBUM))
|
||||||
|
},
|
||||||
|
val onClickGoToArtist: (UUID) -> Unit = {
|
||||||
|
onNavigate.invoke(Destination.MediaItem(itemId = it, type = BaseItemKind.MUSIC_ARTIST))
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
fun buildMoreDialogForMusic(
|
||||||
|
context: Context,
|
||||||
|
actions: MusicMoreDialogActions,
|
||||||
|
item: BaseItem,
|
||||||
|
index: Int,
|
||||||
|
): List<DialogItem> =
|
||||||
|
buildList {
|
||||||
|
add(
|
||||||
|
DialogItem(
|
||||||
|
context.getString(R.string.play),
|
||||||
|
Icons.Default.PlayArrow,
|
||||||
|
iconColor = Color.Green.copy(alpha = .8f),
|
||||||
|
) {
|
||||||
|
actions.onClickPlay(index, item.id)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
add(
|
||||||
|
DialogItem(
|
||||||
|
context.getString(R.string.add_to_queue),
|
||||||
|
Icons.Default.Add,
|
||||||
|
) {
|
||||||
|
actions.onClickAddToQueue(index, item.id)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
add(
|
||||||
|
DialogItem(
|
||||||
|
text = R.string.add_to_playlist,
|
||||||
|
iconStringRes = R.string.fa_list_ul,
|
||||||
|
) {
|
||||||
|
actions.onClickAddPlaylist.invoke(item.id)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
add(
|
||||||
|
DialogItem(
|
||||||
|
text = if (item.favorite) R.string.remove_favorite else R.string.add_favorite,
|
||||||
|
iconStringRes = R.string.fa_heart,
|
||||||
|
iconColor = if (item.favorite) Color.Red else Color.Unspecified,
|
||||||
|
) {
|
||||||
|
actions.onClickFavorite.invoke(item.id, !item.favorite)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if (item.type == BaseItemKind.AUDIO && item.data.albumId != null) {
|
||||||
|
add(
|
||||||
|
DialogItem(
|
||||||
|
context.getString(R.string.go_to_album),
|
||||||
|
Icons.Default.ArrowForward,
|
||||||
|
) {
|
||||||
|
actions.onClickGoToAlbum.invoke(item.data.albumId!!)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if ((item.type == BaseItemKind.AUDIO || item.type == BaseItemKind.MUSIC_ALBUM) && item.data.artistItems?.isNotEmpty() == true) {
|
||||||
|
add(
|
||||||
|
DialogItem(
|
||||||
|
context.getString(R.string.go_to_artist),
|
||||||
|
Icons.Default.ArrowForward,
|
||||||
|
) {
|
||||||
|
actions.onClickGoToArtist.invoke(
|
||||||
|
item.data.artistItems!!
|
||||||
|
.first()
|
||||||
|
.id,
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -498,6 +498,8 @@
|
||||||
<string name="go_to_artist">Go to artist</string>
|
<string name="go_to_artist">Go to artist</string>
|
||||||
<string name="now_playing">Now playing</string>
|
<string name="now_playing">Now playing</string>
|
||||||
<string name="instant_mix">Instant mix</string>
|
<string name="instant_mix">Instant mix</string>
|
||||||
|
<string name="go_to_album">Go to album</string>
|
||||||
|
<string name="add_to_queue">Add to queue</string>
|
||||||
|
|
||||||
<string-array name="theme_song_volume">
|
<string-array name="theme_song_volume">
|
||||||
<item>Disabled</item>
|
<item>Disabled</item>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue