mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Few UI improvments & fixes (#1229)
## Description - Add indicator to mark the chosen version on the choose version dialog - Add an option for album's context menu to add it to the now playing queue - Fix how season cards refresh on the series detail page so the images don't glitch ### Related issues Related to #1220 ### Testing Emulator mostly ## Screenshots The indicator shows which version will play, whether explicitly chosen by the user or implicitly picked by Wholphin.  ## AI or LLM usage None
This commit is contained in:
parent
ff37eff27d
commit
97e10f57ca
17 changed files with 117 additions and 34 deletions
|
|
@ -279,6 +279,7 @@ class MusicService
|
|||
onMain { player.addMediaItems(mediaItems) }
|
||||
}
|
||||
updateQueueSize()
|
||||
start()
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -64,6 +64,7 @@ import com.github.damontecres.wholphin.services.BackdropService
|
|||
import com.github.damontecres.wholphin.services.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.services.MediaManagementService
|
||||
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.ThemeSongPlayer
|
||||
import com.github.damontecres.wholphin.services.UserPreferencesService
|
||||
|
|
@ -80,6 +81,7 @@ import com.github.damontecres.wholphin.ui.detail.MoreDialogActions
|
|||
import com.github.damontecres.wholphin.ui.detail.PlaylistDialog
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForHome
|
||||
import com.github.damontecres.wholphin.ui.detail.music.addToQueue
|
||||
import com.github.damontecres.wholphin.ui.equalsNotNull
|
||||
import com.github.damontecres.wholphin.ui.launchDefault
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
|
|
@ -139,6 +141,7 @@ class CollectionFolderViewModel
|
|||
private val themeSongPlayer: ThemeSongPlayer,
|
||||
private val userPreferencesService: UserPreferencesService,
|
||||
private val mediaManagementService: MediaManagementService,
|
||||
private val musicService: MusicService,
|
||||
val mediaReportService: MediaReportService,
|
||||
@Assisted itemId: String,
|
||||
@Assisted initialSortAndDirection: SortAndDirection?,
|
||||
|
|
@ -532,6 +535,11 @@ class CollectionFolderViewModel
|
|||
item: BaseItem,
|
||||
appPreferences: AppPreferences,
|
||||
): Boolean = mediaManagementService.canDelete(item, appPreferences)
|
||||
|
||||
fun addToQueue(
|
||||
item: BaseItem,
|
||||
index: Int,
|
||||
) = addToQueue(api, musicService, item, index)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -776,6 +784,9 @@ fun CollectionFolderGrid(
|
|||
onClickGoTo = {
|
||||
onClickItem.invoke(position, it)
|
||||
},
|
||||
onClickAddToQueue = {
|
||||
viewModel.addToQueue(it, 0)
|
||||
},
|
||||
),
|
||||
),
|
||||
onDismissRequest = { moreDialog.makeAbsent() },
|
||||
|
|
|
|||
|
|
@ -76,6 +76,8 @@ import kotlinx.coroutines.launch
|
|||
import org.jellyfin.sdk.model.api.MediaSourceInfo
|
||||
import org.jellyfin.sdk.model.api.MediaStream
|
||||
import org.jellyfin.sdk.model.api.MediaStreamType
|
||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||
import java.util.UUID
|
||||
|
||||
/**
|
||||
* Parameters for rendering a [DialogPopup]
|
||||
|
|
@ -597,6 +599,7 @@ fun ConfirmDeleteDialog(
|
|||
fun chooseVersionParams(
|
||||
context: Context,
|
||||
sources: List<MediaSourceInfo>,
|
||||
chosenSourceId: UUID?,
|
||||
onClick: (Int) -> Unit,
|
||||
): DialogParams =
|
||||
DialogParams(
|
||||
|
|
@ -604,6 +607,7 @@ fun chooseVersionParams(
|
|||
title = context.getString(R.string.choose_stream, context.getString(R.string.version)),
|
||||
items =
|
||||
sources.filter { it.id.isNotNullOrBlank() }.mapIndexed { index, source ->
|
||||
val uuid = source.id?.toUUIDOrNull()
|
||||
val videoStream =
|
||||
source.mediaStreams?.firstOrNull { it.type == MediaStreamType.VIDEO }
|
||||
val title = source.name ?: source.path ?: source.id ?: ""
|
||||
|
|
@ -611,6 +615,9 @@ fun chooseVersionParams(
|
|||
headlineContent = {
|
||||
Text(text = title)
|
||||
},
|
||||
leadingContent = {
|
||||
SelectedLeadingContent(uuid != null && uuid == chosenSourceId)
|
||||
},
|
||||
supportingContent = {
|
||||
videoStream?.displayTitle?.let { Text(text = it) }
|
||||
},
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ import com.github.damontecres.wholphin.services.BackdropService
|
|||
import com.github.damontecres.wholphin.services.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.services.MediaManagementService
|
||||
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.deleteItem
|
||||
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
|
||||
|
|
@ -35,6 +36,7 @@ import com.github.damontecres.wholphin.ui.detail.MoreDialogActions
|
|||
import com.github.damontecres.wholphin.ui.detail.PlaylistDialog
|
||||
import com.github.damontecres.wholphin.ui.detail.PlaylistLoadingState
|
||||
import com.github.damontecres.wholphin.ui.detail.buildMoreDialogItemsForHome
|
||||
import com.github.damontecres.wholphin.ui.detail.music.addToQueue
|
||||
import com.github.damontecres.wholphin.ui.launchDefault
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.main.HomePageContent
|
||||
|
|
@ -48,6 +50,7 @@ import kotlinx.coroutines.Deferred
|
|||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.model.api.MediaType
|
||||
import java.util.UUID
|
||||
|
||||
|
|
@ -56,9 +59,11 @@ import java.util.UUID
|
|||
*/
|
||||
abstract class RecommendedViewModel(
|
||||
@param:ApplicationContext val context: Context,
|
||||
val api: ApiClient,
|
||||
val navigationManager: NavigationManager,
|
||||
val favoriteWatchManager: FavoriteWatchManager,
|
||||
val mediaReportService: MediaReportService,
|
||||
private val musicService: MusicService,
|
||||
private val backdropService: BackdropService,
|
||||
private val mediaManagementService: MediaManagementService,
|
||||
) : ViewModel() {
|
||||
|
|
@ -147,6 +152,11 @@ abstract class RecommendedViewModel(
|
|||
item: BaseItem,
|
||||
appPreferences: AppPreferences,
|
||||
): Boolean = mediaManagementService.canDelete(item, appPreferences)
|
||||
|
||||
fun addToQueue(
|
||||
item: BaseItem,
|
||||
index: Int,
|
||||
) = addToQueue(api, musicService, item, index)
|
||||
}
|
||||
|
||||
@Composable
|
||||
|
|
@ -244,6 +254,9 @@ fun RecommendedContent(
|
|||
},
|
||||
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
|
||||
onClickDelete = { showDeleteDialog = RowColumnItem(position, item) },
|
||||
onClickAddToQueue = {
|
||||
viewModel.addToQueue(it, 0)
|
||||
},
|
||||
),
|
||||
),
|
||||
onDismissRequest = { moreDialog.makeAbsent() },
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.github.damontecres.wholphin.services.BackdropService
|
|||
import com.github.damontecres.wholphin.services.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.services.MediaManagementService
|
||||
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.SuggestionService
|
||||
import com.github.damontecres.wholphin.services.SuggestionsResource
|
||||
|
|
@ -55,7 +56,8 @@ class RecommendedMovieViewModel
|
|||
@AssistedInject
|
||||
constructor(
|
||||
@ApplicationContext context: Context,
|
||||
private val api: ApiClient,
|
||||
api: ApiClient,
|
||||
musicService: MusicService,
|
||||
private val serverRepository: ServerRepository,
|
||||
private val preferencesDataStore: DataStore<AppPreferences>,
|
||||
private val suggestionService: SuggestionService,
|
||||
|
|
@ -67,9 +69,11 @@ class RecommendedMovieViewModel
|
|||
mediaManagementService: MediaManagementService,
|
||||
) : RecommendedViewModel(
|
||||
context,
|
||||
api,
|
||||
navigationManager,
|
||||
favoriteWatchManager,
|
||||
mediaReportService,
|
||||
musicService,
|
||||
backdropService,
|
||||
mediaManagementService,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import com.github.damontecres.wholphin.services.BackdropService
|
|||
import com.github.damontecres.wholphin.services.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.services.MediaManagementService
|
||||
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.SuggestionService
|
||||
import com.github.damontecres.wholphin.services.SuggestionsResource
|
||||
|
|
@ -54,7 +55,8 @@ class RecommendedMusicViewModel
|
|||
@AssistedInject
|
||||
constructor(
|
||||
@ApplicationContext context: Context,
|
||||
private val api: ApiClient,
|
||||
api: ApiClient,
|
||||
musicService: MusicService,
|
||||
private val serverRepository: ServerRepository,
|
||||
private val preferencesDataStore: DataStore<AppPreferences>,
|
||||
private val suggestionService: SuggestionService,
|
||||
|
|
@ -66,9 +68,11 @@ class RecommendedMusicViewModel
|
|||
mediaManagementService: MediaManagementService,
|
||||
) : RecommendedViewModel(
|
||||
context,
|
||||
api,
|
||||
navigationManager,
|
||||
favoriteWatchManager,
|
||||
mediaReportService,
|
||||
musicService,
|
||||
backdropService,
|
||||
mediaManagementService,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.github.damontecres.wholphin.services.FavoriteWatchManager
|
|||
import com.github.damontecres.wholphin.services.LatestNextUpService
|
||||
import com.github.damontecres.wholphin.services.MediaManagementService
|
||||
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.SuggestionService
|
||||
import com.github.damontecres.wholphin.services.SuggestionsResource
|
||||
|
|
@ -58,7 +59,8 @@ class RecommendedTvShowViewModel
|
|||
@AssistedInject
|
||||
constructor(
|
||||
@ApplicationContext context: Context,
|
||||
private val api: ApiClient,
|
||||
api: ApiClient,
|
||||
musicService: MusicService,
|
||||
private val serverRepository: ServerRepository,
|
||||
private val preferencesDataStore: DataStore<AppPreferences>,
|
||||
private val lastestNextUpService: LatestNextUpService,
|
||||
|
|
@ -71,9 +73,11 @@ class RecommendedTvShowViewModel
|
|||
mediaManagementService: MediaManagementService,
|
||||
) : RecommendedViewModel(
|
||||
context,
|
||||
api,
|
||||
navigationManager,
|
||||
favoriteWatchManager,
|
||||
mediaReportService,
|
||||
musicService,
|
||||
backdropService,
|
||||
mediaManagementService,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.github.damontecres.wholphin.ui.detail
|
|||
import android.content.Context
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.ArrowForward
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.ArrowForward
|
||||
import androidx.compose.material.icons.filled.Delete
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
|
|
@ -32,6 +33,7 @@ data class MoreDialogActions(
|
|||
val onClickDelete: (BaseItem) -> Unit,
|
||||
val onClickGoTo: (BaseItem) -> Unit = { navigateTo(it.destination()) },
|
||||
val onClickRemoveFromNextUp: (BaseItem) -> Unit = {},
|
||||
val onClickAddToQueue: (BaseItem) -> Unit = {},
|
||||
)
|
||||
|
||||
enum class ClearChosenStreams {
|
||||
|
|
@ -331,6 +333,16 @@ fun buildMoreDialogItemsForHome(
|
|||
)
|
||||
}
|
||||
}
|
||||
if (item.type == BaseItemKind.MUSIC_ALBUM) {
|
||||
add(
|
||||
DialogItem(
|
||||
context.getString(R.string.add_to_queue),
|
||||
Icons.Default.Add,
|
||||
) {
|
||||
actions.onClickAddToQueue(item)
|
||||
},
|
||||
)
|
||||
}
|
||||
add(
|
||||
DialogItem(
|
||||
text = R.string.add_to_playlist,
|
||||
|
|
|
|||
|
|
@ -430,6 +430,7 @@ fun PlaylistDetails(
|
|||
},
|
||||
onSendMediaInfo = viewModel::sendMediaReport,
|
||||
onClickDelete = { showDeleteDialog = it },
|
||||
onClickAddToQueue = { viewModel.addToQueue(it, 0) },
|
||||
)
|
||||
|
||||
PlaylistDetailsContent(
|
||||
|
|
|
|||
|
|
@ -124,6 +124,7 @@ fun CollectionDetails(
|
|||
},
|
||||
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
|
||||
onClickDelete = { item -> showDeleteDialog = Pair(position, item) },
|
||||
onClickAddToQueue = { viewModel.addToQueue(it, 0) },
|
||||
),
|
||||
)
|
||||
moreDialog =
|
||||
|
|
@ -244,6 +245,7 @@ fun CollectionDetails(
|
|||
},
|
||||
onSendMediaInfo = viewModel.mediaReportService::sendReportFor,
|
||||
onClickDelete = { item -> showDeleteDialog = Pair(null, item) },
|
||||
onClickAddToQueue = { viewModel.addToQueue(it, 0) },
|
||||
),
|
||||
)
|
||||
moreDialog =
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import com.github.damontecres.wholphin.services.ImageUrlService
|
|||
import com.github.damontecres.wholphin.services.KeyValueService
|
||||
import com.github.damontecres.wholphin.services.MediaManagementService
|
||||
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.ThemeSongPlayer
|
||||
import com.github.damontecres.wholphin.services.UserPreferencesService
|
||||
|
|
@ -27,6 +28,7 @@ import com.github.damontecres.wholphin.ui.SlimItemFields
|
|||
import com.github.damontecres.wholphin.ui.collectLatestIn
|
||||
import com.github.damontecres.wholphin.ui.data.RowColumn
|
||||
import com.github.damontecres.wholphin.ui.data.SortAndDirection
|
||||
import com.github.damontecres.wholphin.ui.detail.music.addToQueue
|
||||
import com.github.damontecres.wholphin.ui.formatTypeName
|
||||
import com.github.damontecres.wholphin.ui.launchDefault
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
|
|
@ -86,6 +88,7 @@ class CollectionViewModel
|
|||
private val keyValueService: KeyValueService,
|
||||
private val libraryDisplayInfoDao: LibraryDisplayInfoDao,
|
||||
private val imageUrlService: ImageUrlService,
|
||||
private val musicService: MusicService,
|
||||
val mediaReportService: MediaReportService,
|
||||
@Assisted private val itemId: UUID,
|
||||
) : ViewModel() {
|
||||
|
|
@ -462,6 +465,11 @@ class CollectionViewModel
|
|||
}
|
||||
}
|
||||
|
||||
fun addToQueue(
|
||||
item: BaseItem,
|
||||
index: Int,
|
||||
) = addToQueue(api, musicService, item, index)
|
||||
|
||||
companion object {
|
||||
val typesInCollection =
|
||||
listOf(
|
||||
|
|
|
|||
|
|
@ -176,6 +176,7 @@ fun EpisodeDetails(
|
|||
chooseVersionParams(
|
||||
context,
|
||||
ep.data.mediaSources!!,
|
||||
chosenStreams?.source?.id?.toUUIDOrNull(),
|
||||
) { idx ->
|
||||
val source = ep.data.mediaSources!![idx]
|
||||
viewModel.savePlayVersion(
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@ fun MovieDetails(
|
|||
chooseVersionParams(
|
||||
context,
|
||||
movie.data.mediaSources!!,
|
||||
chosenStreams?.source?.id?.toUUIDOrNull(),
|
||||
) { idx ->
|
||||
val source = movie.data.mediaSources!![idx]
|
||||
viewModel.savePlayVersion(
|
||||
|
|
|
|||
|
|
@ -81,35 +81,7 @@ abstract class MusicViewModel(
|
|||
fun addToQueue(
|
||||
item: BaseItem,
|
||||
index: Int,
|
||||
) {
|
||||
viewModelScope.launchIO {
|
||||
Timber.v("addToQueue %s %s", item.type, item.id)
|
||||
when (item.type) {
|
||||
BaseItemKind.AUDIO -> {
|
||||
musicService.addAllToQueue(BlockingList.of(listOf(item)), 0)
|
||||
}
|
||||
|
||||
BaseItemKind.MUSIC_ALBUM -> {
|
||||
val pager = getPagerForAlbum(api, item.id)
|
||||
musicService.addAllToQueue(pager, 0)
|
||||
}
|
||||
|
||||
BaseItemKind.MUSIC_ARTIST -> {
|
||||
val pager = getPagerForArtist(api, item.id)
|
||||
musicService.addAllToQueue(pager, 0)
|
||||
}
|
||||
|
||||
BaseItemKind.PLAYLIST -> {
|
||||
val pager = getPagerForPlaylist(api, item.id)
|
||||
musicService.addAllToQueue(pager, 0)
|
||||
}
|
||||
|
||||
else -> {
|
||||
Timber.w("Unknown item type to queue for music: %s", item.type)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
) = addToQueue(api, musicService, item, index)
|
||||
|
||||
fun startInstantMix(itemId: UUID) {
|
||||
viewModelScope.launchIO {
|
||||
|
|
@ -140,3 +112,38 @@ abstract class MusicViewModel(
|
|||
|
||||
internal abstract fun init()
|
||||
}
|
||||
|
||||
fun ViewModel.addToQueue(
|
||||
api: ApiClient,
|
||||
musicService: MusicService,
|
||||
item: BaseItem,
|
||||
index: Int,
|
||||
) {
|
||||
viewModelScope.launchIO {
|
||||
Timber.v("addToQueue %s %s", item.type, item.id)
|
||||
when (item.type) {
|
||||
BaseItemKind.AUDIO -> {
|
||||
musicService.addAllToQueue(BlockingList.of(listOf(item)), 0)
|
||||
}
|
||||
|
||||
BaseItemKind.MUSIC_ALBUM -> {
|
||||
val pager = getPagerForAlbum(api, item.id)
|
||||
musicService.addAllToQueue(pager, 0)
|
||||
}
|
||||
|
||||
BaseItemKind.MUSIC_ARTIST -> {
|
||||
val pager = getPagerForArtist(api, item.id)
|
||||
musicService.addAllToQueue(pager, 0)
|
||||
}
|
||||
|
||||
BaseItemKind.PLAYLIST -> {
|
||||
val pager = getPagerForPlaylist(api, item.id)
|
||||
musicService.addAllToQueue(pager, 0)
|
||||
}
|
||||
|
||||
else -> {
|
||||
Timber.w("Unknown item type to queue for music: %s", item.type)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -225,6 +225,7 @@ fun SeriesOverview(
|
|||
chooseVersionParams(
|
||||
context,
|
||||
ep.data.mediaSources!!,
|
||||
chosenStreams?.source?.id?.toUUIDOrNull(),
|
||||
) { idx ->
|
||||
val source = ep.data.mediaSources!![idx]
|
||||
viewModel.savePlayVersion(
|
||||
|
|
|
|||
|
|
@ -287,8 +287,7 @@ class SeriesViewModel
|
|||
item.value?.let { item ->
|
||||
if (loading.value == LoadingState.Success) {
|
||||
viewModelScope.launchIO {
|
||||
val seasons = getSeasons(item, null).await()
|
||||
this@SeriesViewModel.seasons.setValueOnMain(seasons)
|
||||
(seasons.value as? ApiRequestPager<*>)?.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -107,6 +107,13 @@ abstract class RequestPager<T>(
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun refresh() {
|
||||
cachedPages.asMap().keys.forEachIndexed { index, pageNum ->
|
||||
if (DEBUG) Timber.v("refresh: pageNum=%s", pageNum)
|
||||
fetchPageBlocking(pageNum * pageSize, index == 0)
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract suspend fun fetchPage(
|
||||
pageNumber: Int,
|
||||
includeTotalCount: Boolean,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue