mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Handle shuffling playlists
This commit is contained in:
parent
9148ca0719
commit
a93f183f5f
6 changed files with 65 additions and 4 deletions
|
|
@ -64,6 +64,7 @@ class PlaylistCreator
|
||||||
suspend fun createFromPlaylistId(
|
suspend fun createFromPlaylistId(
|
||||||
playlistId: UUID,
|
playlistId: UUID,
|
||||||
startIndex: Int?,
|
startIndex: Int?,
|
||||||
|
shuffled: Boolean,
|
||||||
): Playlist {
|
): Playlist {
|
||||||
val request =
|
val request =
|
||||||
GetPlaylistItemsRequest(
|
GetPlaylistItemsRequest(
|
||||||
|
|
@ -73,6 +74,10 @@ class PlaylistCreator
|
||||||
limit = Playlist.MAX_SIZE,
|
limit = Playlist.MAX_SIZE,
|
||||||
)
|
)
|
||||||
val items = GetPlaylistItemsRequestHandler.execute(api, request).content.items
|
val items = GetPlaylistItemsRequestHandler.execute(api, request).content.items
|
||||||
return Playlist(items.map { BaseItem.from(it, api) }, 0)
|
var baseItems = items.map { BaseItem.from(it, api) }
|
||||||
|
if (shuffled) {
|
||||||
|
baseItems = baseItems.shuffled()
|
||||||
|
}
|
||||||
|
return Playlist(baseItems, 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.alpha
|
import androidx.compose.ui.draw.alpha
|
||||||
import androidx.compose.ui.draw.drawWithContent
|
import androidx.compose.ui.draw.drawWithContent
|
||||||
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.focus.focusRestorer
|
||||||
import androidx.compose.ui.focus.onFocusChanged
|
import androidx.compose.ui.focus.onFocusChanged
|
||||||
|
|
@ -51,6 +52,7 @@ import androidx.tv.material3.MaterialTheme
|
||||||
import androidx.tv.material3.Text
|
import androidx.tv.material3.Text
|
||||||
import androidx.tv.material3.surfaceColorAtElevation
|
import androidx.tv.material3.surfaceColorAtElevation
|
||||||
import coil3.compose.AsyncImage
|
import coil3.compose.AsyncImage
|
||||||
|
import com.github.damontecres.dolphin.R
|
||||||
import com.github.damontecres.dolphin.data.model.BaseItem
|
import com.github.damontecres.dolphin.data.model.BaseItem
|
||||||
import com.github.damontecres.dolphin.data.model.Library
|
import com.github.damontecres.dolphin.data.model.Library
|
||||||
import com.github.damontecres.dolphin.ui.DefaultItemFields
|
import com.github.damontecres.dolphin.ui.DefaultItemFields
|
||||||
|
|
@ -59,6 +61,8 @@ import com.github.damontecres.dolphin.ui.components.DialogItem
|
||||||
import com.github.damontecres.dolphin.ui.components.DialogParams
|
import com.github.damontecres.dolphin.ui.components.DialogParams
|
||||||
import com.github.damontecres.dolphin.ui.components.DialogPopup
|
import com.github.damontecres.dolphin.ui.components.DialogPopup
|
||||||
import com.github.damontecres.dolphin.ui.components.ErrorMessage
|
import com.github.damontecres.dolphin.ui.components.ErrorMessage
|
||||||
|
import com.github.damontecres.dolphin.ui.components.ExpandableFaButton
|
||||||
|
import com.github.damontecres.dolphin.ui.components.ExpandablePlayButton
|
||||||
import com.github.damontecres.dolphin.ui.components.LoadingPage
|
import com.github.damontecres.dolphin.ui.components.LoadingPage
|
||||||
import com.github.damontecres.dolphin.ui.components.OverviewText
|
import com.github.damontecres.dolphin.ui.components.OverviewText
|
||||||
import com.github.damontecres.dolphin.ui.enableMarquee
|
import com.github.damontecres.dolphin.ui.enableMarquee
|
||||||
|
|
@ -81,6 +85,7 @@ import org.jellyfin.sdk.model.api.request.GetPlaylistItemsRequest
|
||||||
import org.jellyfin.sdk.model.extensions.ticks
|
import org.jellyfin.sdk.model.extensions.ticks
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
import kotlin.time.Duration
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel
|
||||||
class PlaylistViewModel
|
class PlaylistViewModel
|
||||||
|
|
@ -148,6 +153,16 @@ fun PlaylistDetails(
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
|
onClickPlay = { shuffle ->
|
||||||
|
viewModel.navigationManager.navigateTo(
|
||||||
|
Destination.Playback(
|
||||||
|
itemId = it.id,
|
||||||
|
positionMs = 0L,
|
||||||
|
startIndex = 0,
|
||||||
|
shuffle = shuffle,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
},
|
||||||
onLongClickIndex = { index, item ->
|
onLongClickIndex = { index, item ->
|
||||||
longClickDialog =
|
longClickDialog =
|
||||||
DialogParams(
|
DialogParams(
|
||||||
|
|
@ -200,6 +215,7 @@ fun PlaylistDetailsContent(
|
||||||
items: List<BaseItem?>,
|
items: List<BaseItem?>,
|
||||||
onClickIndex: (Int, BaseItem) -> Unit,
|
onClickIndex: (Int, BaseItem) -> Unit,
|
||||||
onLongClickIndex: (Int, BaseItem) -> Unit,
|
onLongClickIndex: (Int, BaseItem) -> Unit,
|
||||||
|
onClickPlay: (shuffle: Boolean) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
focusRequester: FocusRequester = remember { FocusRequester() },
|
focusRequester: FocusRequester = remember { FocusRequester() },
|
||||||
) {
|
) {
|
||||||
|
|
@ -208,6 +224,8 @@ fun PlaylistDetailsContent(
|
||||||
val focus = remember { FocusRequester() }
|
val focus = remember { FocusRequester() }
|
||||||
val focusedItem = items.getOrNull(focusedIndex)
|
val focusedItem = items.getOrNull(focusedIndex)
|
||||||
|
|
||||||
|
val playButtonFocusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
Box(
|
Box(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
) {
|
) {
|
||||||
|
|
@ -248,6 +266,7 @@ fun PlaylistDetailsContent(
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
) {
|
) {
|
||||||
Row(
|
Row(
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
horizontalArrangement = Arrangement.spacedBy(32.dp),
|
horizontalArrangement = Arrangement.spacedBy(32.dp),
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
|
|
@ -256,6 +275,8 @@ fun PlaylistDetailsContent(
|
||||||
) {
|
) {
|
||||||
PlaylistDetailsHeader(
|
PlaylistDetailsHeader(
|
||||||
focusedItem = focusedItem,
|
focusedItem = focusedItem,
|
||||||
|
onClickPlay = onClickPlay,
|
||||||
|
playButtonFocusRequester = playButtonFocusRequester,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.padding(start = 16.dp)
|
.padding(start = 16.dp)
|
||||||
|
|
@ -283,7 +304,9 @@ fun PlaylistDetailsContent(
|
||||||
// .fillMaxWidth(.8f)
|
// .fillMaxWidth(.8f)
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.background(
|
.background(
|
||||||
MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp),
|
MaterialTheme.colorScheme
|
||||||
|
.surfaceColorAtElevation(1.dp)
|
||||||
|
.copy(alpha = .75f),
|
||||||
shape = RoundedCornerShape(16.dp),
|
shape = RoundedCornerShape(16.dp),
|
||||||
).focusRequester(focusRequester)
|
).focusRequester(focusRequester)
|
||||||
.focusGroup()
|
.focusGroup()
|
||||||
|
|
@ -315,6 +338,9 @@ fun PlaylistDetailsContent(
|
||||||
if (it.isFocused) {
|
if (it.isFocused) {
|
||||||
focusedIndex = index
|
focusedIndex = index
|
||||||
}
|
}
|
||||||
|
}.focusProperties {
|
||||||
|
left = playButtonFocusRequester
|
||||||
|
previous = playButtonFocusRequester
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -328,12 +354,30 @@ fun PlaylistDetailsContent(
|
||||||
@Composable
|
@Composable
|
||||||
fun PlaylistDetailsHeader(
|
fun PlaylistDetailsHeader(
|
||||||
focusedItem: BaseItem?,
|
focusedItem: BaseItem?,
|
||||||
|
onClickPlay: (shuffle: Boolean) -> Unit,
|
||||||
|
playButtonFocusRequester: FocusRequester,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
) {
|
) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
modifier = Modifier.focusRequester(playButtonFocusRequester),
|
||||||
|
) {
|
||||||
|
ExpandablePlayButton(
|
||||||
|
title = R.string.play,
|
||||||
|
resume = Duration.ZERO,
|
||||||
|
icon = Icons.Default.PlayArrow,
|
||||||
|
onClick = { onClickPlay.invoke(false) },
|
||||||
|
)
|
||||||
|
ExpandableFaButton(
|
||||||
|
title = R.string.shuffle,
|
||||||
|
iconStringRes = R.string.fa_shuffle,
|
||||||
|
onClick = { onClickPlay.invoke(true) },
|
||||||
|
)
|
||||||
|
}
|
||||||
Text(
|
Text(
|
||||||
text = focusedItem?.title ?: "",
|
text = focusedItem?.title ?: "",
|
||||||
color = MaterialTheme.colorScheme.onSurface,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ sealed class Destination(
|
||||||
val positionMs: Long,
|
val positionMs: Long,
|
||||||
@Transient val item: BaseItem? = null,
|
@Transient val item: BaseItem? = null,
|
||||||
val startIndex: Int? = null,
|
val startIndex: Int? = null,
|
||||||
|
val shuffle: Boolean = false,
|
||||||
) : Destination(true) {
|
) : Destination(true) {
|
||||||
override fun toString(): String = "Playback(itemId=$itemId, positionMs=$positionMs)"
|
override fun toString(): String = "Playback(itemId=$itemId, positionMs=$positionMs)"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
package com.github.damontecres.dolphin.ui.playback
|
package com.github.damontecres.dolphin.ui.playback
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.widget.Toast
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
import androidx.core.net.toUri
|
import androidx.core.net.toUri
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
|
@ -144,6 +145,7 @@ class PlaybackViewModel
|
||||||
playlistCreator.createFromPlaylistId(
|
playlistCreator.createFromPlaylistId(
|
||||||
queriedItem.id,
|
queriedItem.id,
|
||||||
destination.startIndex,
|
destination.startIndex,
|
||||||
|
destination.shuffle,
|
||||||
)
|
)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
this@PlaybackViewModel.playlist.value = playlist
|
this@PlaybackViewModel.playlist.value = playlist
|
||||||
|
|
@ -154,7 +156,10 @@ class PlaybackViewModel
|
||||||
queriedItem
|
queriedItem
|
||||||
}
|
}
|
||||||
|
|
||||||
play(base, destination.positionMs)
|
val played = play(base, destination.positionMs)
|
||||||
|
if (!played) {
|
||||||
|
playUpNextUp()
|
||||||
|
}
|
||||||
|
|
||||||
if (!isPlaylist && queriedItem.type == BaseItemKind.EPISODE) {
|
if (!isPlaylist && queriedItem.type == BaseItemKind.EPISODE) {
|
||||||
val playlist =
|
val playlist =
|
||||||
|
|
@ -174,7 +179,11 @@ class PlaybackViewModel
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
Timber.i("Playing ${base.id}")
|
Timber.i("Playing ${base.id}")
|
||||||
if (base.type !in supportItemKinds) {
|
if (base.type !in supportItemKinds) {
|
||||||
showToast(context, "Unsupported type '${base.type}', skipping...")
|
showToast(
|
||||||
|
context,
|
||||||
|
"Unsupported type '${base.type}', skipping...",
|
||||||
|
Toast.LENGTH_SHORT,
|
||||||
|
)
|
||||||
return@withContext false
|
return@withContext false
|
||||||
}
|
}
|
||||||
dto = base
|
dto = base
|
||||||
|
|
|
||||||
|
|
@ -31,4 +31,5 @@
|
||||||
<string name="fa_eye" translatable="false"></string>
|
<string name="fa_eye" translatable="false"></string>
|
||||||
<string name="fa_eye_slash" translatable="false"></string>
|
<string name="fa_eye_slash" translatable="false"></string>
|
||||||
<string name="fa_arrow_left_arrow_right" translatable="false"></string>
|
<string name="fa_arrow_left_arrow_right" translatable="false"></string>
|
||||||
|
<string name="fa_shuffle" translatable="false"></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -75,5 +75,6 @@
|
||||||
<string name="updates">Updates</string>
|
<string name="updates">Updates</string>
|
||||||
<string name="no_update_available">No update available</string>
|
<string name="no_update_available">No update available</string>
|
||||||
<string name="clear_image_cache">Clear image cache</string>
|
<string name="clear_image_cache">Clear image cache</string>
|
||||||
|
<string name="shuffle">Shuffle</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue