Various fixes

This commit is contained in:
Damontecres 2026-03-04 16:03:40 -05:00
parent d8a58a6d96
commit 2879cd477c
No known key found for this signature in database
7 changed files with 72 additions and 22 deletions

View file

@ -149,15 +149,15 @@ class MusicService
shuffled: Boolean, shuffled: Boolean,
) { ) {
Timber.d("setQueue: %s items, shuffled=%s", items.size, shuffled) Timber.d("setQueue: %s items, shuffled=%s", items.size, shuffled)
start()
val mediaItems = val mediaItems =
items items
.filter { it.type == BaseItemKind.AUDIO } .filter { it.type == BaseItemKind.AUDIO }
.map(::convert) .map(::convert)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
updateQueueSize()
player.setMediaItems(mediaItems) player.setMediaItems(mediaItems)
player.shuffleModeEnabled = shuffled player.shuffleModeEnabled = shuffled
updateQueueSize()
start()
} }
} }

View file

@ -19,6 +19,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.HomeRowViewOptions
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.BackdropService
import com.github.damontecres.wholphin.services.FavoriteWatchManager import com.github.damontecres.wholphin.services.FavoriteWatchManager
@ -105,13 +106,14 @@ abstract class RecommendedViewModel(
fun update( fun update(
@StringRes title: Int, @StringRes title: Int,
viewOptions: HomeRowViewOptions = HomeRowViewOptions(),
block: suspend () -> List<BaseItem>, block: suspend () -> List<BaseItem>,
): Deferred<HomeRowLoadingState> = ): Deferred<HomeRowLoadingState> =
viewModelScope.async(Dispatchers.IO) { viewModelScope.async(Dispatchers.IO) {
val titleStr = context.getString(title) val titleStr = context.getString(title)
val row = val row =
try { try {
HomeRowLoadingState.Success(titleStr, block.invoke()) HomeRowLoadingState.Success(titleStr, block.invoke(), viewOptions)
} catch (ex: Exception) { } catch (ex: Exception) {
HomeRowLoadingState.Error(titleStr, null, ex) HomeRowLoadingState.Error(titleStr, null, ex)
} }

View file

@ -9,6 +9,7 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.data.model.HomeRowViewOptions
import com.github.damontecres.wholphin.preferences.AppPreference import com.github.damontecres.wholphin.preferences.AppPreference
import com.github.damontecres.wholphin.preferences.AppPreferences import com.github.damontecres.wholphin.preferences.AppPreferences
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
@ -18,6 +19,8 @@ import com.github.damontecres.wholphin.services.MediaReportService
import com.github.damontecres.wholphin.services.NavigationManager import com.github.damontecres.wholphin.services.NavigationManager
import com.github.damontecres.wholphin.services.SuggestionService import com.github.damontecres.wholphin.services.SuggestionService
import com.github.damontecres.wholphin.services.SuggestionsResource import com.github.damontecres.wholphin.services.SuggestionsResource
import com.github.damontecres.wholphin.ui.AspectRatio
import com.github.damontecres.wholphin.ui.Cards
import com.github.damontecres.wholphin.ui.SlimItemFields import com.github.damontecres.wholphin.ui.SlimItemFields
import com.github.damontecres.wholphin.ui.data.RowColumn import com.github.damontecres.wholphin.ui.data.RowColumn
import com.github.damontecres.wholphin.ui.setValueOnMain import com.github.damontecres.wholphin.ui.setValueOnMain
@ -31,6 +34,7 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Deferred
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.firstOrNull
@ -88,7 +92,15 @@ class RecommendedMusicViewModel
?.maxItemsPerRow ?.maxItemsPerRow
?: AppPreference.HomePageItems.defaultValue.toInt() ?: AppPreference.HomePageItems.defaultValue.toInt()
update(R.string.recently_released) { val jobs = mutableListOf<Deferred<HomeRowLoadingState>>()
val viewOptions =
HomeRowViewOptions(
aspectRatio = AspectRatio.SQUARE,
heightDp = Cards.HEIGHT_EPISODE,
showTitles = true,
)
update(R.string.recently_released, viewOptions) {
val request = val request =
GetItemsRequest( GetItemsRequest(
parentId = parentId, parentId = parentId,
@ -103,9 +115,9 @@ class RecommendedMusicViewModel
enableTotalRecordCount = false, enableTotalRecordCount = false,
) )
GetItemsRequestHandler.execute(api, request).toBaseItems(api, false) GetItemsRequestHandler.execute(api, request).toBaseItems(api, false)
} }.also(jobs::add)
update(R.string.recently_added) { update(R.string.recently_added, viewOptions) {
val request = val request =
GetItemsRequest( GetItemsRequest(
parentId = parentId, parentId = parentId,
@ -120,9 +132,9 @@ class RecommendedMusicViewModel
enableTotalRecordCount = false, enableTotalRecordCount = false,
) )
GetItemsRequestHandler.execute(api, request).toBaseItems(api, false) GetItemsRequestHandler.execute(api, request).toBaseItems(api, false)
} }.also(jobs::add)
update(R.string.top_unwatched) { update(R.string.top_unwatched, viewOptions) {
val request = val request =
GetItemsRequest( GetItemsRequest(
parentId = parentId, parentId = parentId,
@ -138,7 +150,7 @@ class RecommendedMusicViewModel
enableTotalRecordCount = false, enableTotalRecordCount = false,
) )
GetItemsRequestHandler.execute(api, request).toBaseItems(api, false) GetItemsRequestHandler.execute(api, request).toBaseItems(api, false)
} }.also(jobs::add)
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
try { try {
@ -181,9 +193,14 @@ class RecommendedMusicViewModel
} }
} }
if (loading.value == LoadingState.Loading || loading.value == LoadingState.Pending) { for (i in 0..<jobs.size) {
val result = jobs[i].await()
if (result.completed) {
Timber.v("First success")
loading.setValueOnMain(LoadingState.Success) loading.setValueOnMain(LoadingState.Success)
} }
break
}
} }
} }

View file

@ -20,10 +20,14 @@ import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.CollectionFolderFilter import com.github.damontecres.wholphin.data.model.CollectionFolderFilter
import com.github.damontecres.wholphin.data.model.GetItemsFilter import com.github.damontecres.wholphin.data.model.GetItemsFilter
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.services.MusicService
import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid import com.github.damontecres.wholphin.ui.components.CollectionFolderGrid
import com.github.damontecres.wholphin.ui.components.ErrorMessage import com.github.damontecres.wholphin.ui.components.ErrorMessage
import com.github.damontecres.wholphin.ui.components.GenreCardGrid import com.github.damontecres.wholphin.ui.components.GenreCardGrid
@ -33,16 +37,35 @@ import com.github.damontecres.wholphin.ui.components.ViewOptionsSquare
import com.github.damontecres.wholphin.ui.data.AlbumSortOptions import com.github.damontecres.wholphin.ui.data.AlbumSortOptions
import com.github.damontecres.wholphin.ui.data.ArtistSortOptions import com.github.damontecres.wholphin.ui.data.ArtistSortOptions
import com.github.damontecres.wholphin.ui.data.SongSortOptions import com.github.damontecres.wholphin.ui.data.SongSortOptions
import com.github.damontecres.wholphin.ui.launchDefault
import com.github.damontecres.wholphin.ui.logTab import com.github.damontecres.wholphin.ui.logTab
import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel import com.github.damontecres.wholphin.ui.preferences.PreferencesViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.BaseItemKind
import javax.inject.Inject
@HiltViewModel
class CollectionFolderMusicViewModel
@Inject
constructor(
private val musicService: MusicService,
) : ViewModel() {
fun play(item: BaseItem) {
if (item.type == BaseItemKind.AUDIO) {
viewModelScope.launchDefault {
musicService.setQueue(listOf(item), false)
}
}
}
}
@Composable @Composable
fun CollectionFolderMusic( fun CollectionFolderMusic(
preferences: UserPreferences, preferences: UserPreferences,
destination: Destination.MediaItem, destination: Destination.MediaItem,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
viewModel: CollectionFolderMusicViewModel = hiltViewModel(),
preferencesViewModel: PreferencesViewModel = hiltViewModel(), preferencesViewModel: PreferencesViewModel = hiltViewModel(),
) { ) {
val rememberedTabIndex = val rememberedTabIndex =
@ -174,7 +197,7 @@ fun CollectionFolderMusic(
3 -> { 3 -> {
GenreCardGrid( GenreCardGrid(
itemId = destination.itemId, itemId = destination.itemId,
includeItemTypes = listOf(BaseItemKind.MUSIC_ALBUM, BaseItemKind.AUDIO), includeItemTypes = listOf(BaseItemKind.MUSIC_ALBUM),
modifier = modifier =
Modifier Modifier
.fillMaxSize() .fillMaxSize()
@ -187,7 +210,7 @@ fun CollectionFolderMusic(
CollectionFolderGrid( CollectionFolderGrid(
preferences = preferences, preferences = preferences,
onClickItem = { _, item -> onClickItem = { _, item ->
preferencesViewModel.navigationManager.navigateTo(item.destination()) viewModel.play(item)
}, },
itemId = destination.itemId, itemId = destination.itemId,
viewModelKey = "${destination.itemId}_songs", viewModelKey = "${destination.itemId}_songs",

View file

@ -370,6 +370,9 @@ fun AlbumDetailsPage(
) )
}, },
favorite = album.favorite, favorite = album.favorite,
buttonOnFocusChanged = {
if (it.isFocused) scope.launch { bringIntoViewRequester.bringIntoView() }
},
modifier = modifier =
Modifier Modifier
.onFocusChanged { .onFocusChanged {
@ -389,7 +392,7 @@ fun AlbumDetailsPage(
itemsIndexed(state.songs) { index, song -> itemsIndexed(state.songs) { index, song ->
Box( Box(
modifier = Modifier.fillMaxWidth(), modifier = Modifier.fillMaxWidth(),
contentAlignment = Alignment.Center, contentAlignment = Alignment.TopStart,
) { ) {
SongListItem( SongListItem(
song = song, song = song,
@ -415,9 +418,7 @@ fun AlbumDetailsPage(
isPlaying = song != null && currentMusic.currentItemId == song.id, isPlaying = song != null && currentMusic.currentItemId == song.id,
modifier = modifier =
Modifier Modifier
// .padding(horizontal = 16.dp)
.fillMaxWidth(.75f) .fillMaxWidth(.75f)
.align(Alignment.Center)
.ifElse( .ifElse(
index == 0, index == 0,
Modifier Modifier

View file

@ -377,6 +377,9 @@ fun ArtistDetailsPage(
) )
}, },
favorite = artist.favorite, favorite = artist.favorite,
buttonOnFocusChanged = {
if (it.isFocused) scope.launch { bringIntoViewRequester.bringIntoView() }
},
modifier = modifier =
Modifier Modifier
.onFocusChanged { .onFocusChanged {
@ -415,7 +418,7 @@ fun ArtistDetailsPage(
) )
} }
}, },
modifier = Modifier.padding(horizontal = 16.dp), modifier = Modifier.fillMaxWidth(.75f),
showArtist = false, showArtist = false,
isPlaying = song != null && currentMusic.currentItemId == song.id, isPlaying = song != null && currentMusic.currentItemId == song.id,
) )

View file

@ -11,8 +11,10 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
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.focusRestorer
import androidx.compose.ui.focus.onFocusChanged
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.R
@ -24,6 +26,7 @@ import kotlin.time.Duration
fun MusicExpandableButtons( fun MusicExpandableButtons(
actions: MusicButtonActions, actions: MusicButtonActions,
favorite: Boolean, favorite: Boolean,
buttonOnFocusChanged: (FocusState) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
val firstFocus = remember { FocusRequester() } val firstFocus = remember { FocusRequester() }
@ -43,7 +46,8 @@ fun MusicExpandableButtons(
onClick = { actions.onClickPlay.invoke(false) }, onClick = { actions.onClickPlay.invoke(false) },
modifier = modifier =
Modifier Modifier
.focusRequester(firstFocus), .focusRequester(firstFocus)
.onFocusChanged(buttonOnFocusChanged),
) )
} }
item("shuffle") { item("shuffle") {
@ -51,7 +55,7 @@ fun MusicExpandableButtons(
title = R.string.shuffle, title = R.string.shuffle,
iconStringRes = R.string.fa_shuffle, iconStringRes = R.string.fa_shuffle,
onClick = { actions.onClickPlay.invoke(true) }, onClick = { actions.onClickPlay.invoke(true) },
modifier = Modifier, modifier = Modifier.onFocusChanged(buttonOnFocusChanged),
) )
} }
item("instant_mix") { item("instant_mix") {
@ -59,7 +63,7 @@ fun MusicExpandableButtons(
title = R.string.instant_mix, title = R.string.instant_mix,
iconStringRes = R.string.fa_compass, iconStringRes = R.string.fa_compass,
onClick = actions.onClickInstantMix, onClick = actions.onClickInstantMix,
modifier = Modifier, modifier = Modifier.onFocusChanged(buttonOnFocusChanged),
) )
} }
item("favorite") { item("favorite") {
@ -68,7 +72,7 @@ fun MusicExpandableButtons(
iconStringRes = R.string.fa_heart, iconStringRes = R.string.fa_heart,
onClick = actions.onClickFavorite, onClick = actions.onClickFavorite,
iconColor = if (favorite) Color.Red else Color.Unspecified, iconColor = if (favorite) Color.Red else Color.Unspecified,
modifier = Modifier, modifier = Modifier.onFocusChanged(buttonOnFocusChanged),
) )
} }
item("more") { item("more") {
@ -77,7 +81,7 @@ fun MusicExpandableButtons(
resume = Duration.ZERO, resume = Duration.ZERO,
icon = Icons.Default.MoreVert, icon = Icons.Default.MoreVert,
onClick = { actions.onClickMore.invoke() }, onClick = { actions.onClickMore.invoke() },
modifier = Modifier, modifier = Modifier.onFocusChanged(buttonOnFocusChanged),
) )
} }
} }