Fixes to v1.0.9 (#2072)
## Description As usual, a PR to fix various smaller bugs in the latest release. - Fixes not showing the Videos tab on favorites page - Fixes the Random button not working for photos - Long nav drawer item names use a marquee scroll if needed - Fixes a crash when clicking "View More" on a studios rows on the home page ### Related issues Fixes #2070 Fixes #2069 Fixes #2135 ### Testing Emulator ## Screenshots N/A ## AI or LLM usage None
This commit is contained in:
parent
811eaed87e
commit
5f2f9a8106
9 changed files with 120 additions and 52 deletions
|
|
@ -260,6 +260,7 @@ class HomeDataService
|
||||||
Success(
|
Success(
|
||||||
title,
|
title,
|
||||||
studios,
|
studios,
|
||||||
|
rowType = row,
|
||||||
viewOptions = row.viewOptions,
|
viewOptions = row.viewOptions,
|
||||||
showViewMore = studios.size >= limit,
|
showViewMore = studios.size >= limit,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -198,10 +198,10 @@ val CollectionType.baseItemKinds: List<BaseItemKind>
|
||||||
listOf(BaseItemKind.SERIES, BaseItemKind.SEASON, BaseItemKind.EPISODE)
|
listOf(BaseItemKind.SERIES, BaseItemKind.SEASON, BaseItemKind.EPISODE)
|
||||||
}
|
}
|
||||||
|
|
||||||
CollectionType.HOMEVIDEOS -> {
|
CollectionType.PHOTOS,
|
||||||
listOf(BaseItemKind.VIDEO)
|
CollectionType.HOMEVIDEOS,
|
||||||
listOf(BaseItemKind.PHOTO)
|
-> {
|
||||||
listOf(BaseItemKind.PHOTO_ALBUM)
|
listOf(BaseItemKind.VIDEO, BaseItemKind.PHOTO, BaseItemKind.PHOTO_ALBUM)
|
||||||
}
|
}
|
||||||
|
|
||||||
CollectionType.MUSIC -> {
|
CollectionType.MUSIC -> {
|
||||||
|
|
@ -224,10 +224,6 @@ val CollectionType.baseItemKinds: List<BaseItemKind>
|
||||||
listOf(BaseItemKind.PLAYLIST)
|
listOf(BaseItemKind.PLAYLIST)
|
||||||
}
|
}
|
||||||
|
|
||||||
CollectionType.PHOTOS -> {
|
|
||||||
listOf(BaseItemKind.PHOTO, BaseItemKind.PHOTO_ALBUM)
|
|
||||||
}
|
|
||||||
|
|
||||||
CollectionType.LIVETV -> {
|
CollectionType.LIVETV -> {
|
||||||
listOf(
|
listOf(
|
||||||
BaseItemKind.TV_CHANNEL,
|
BaseItemKind.TV_CHANNEL,
|
||||||
|
|
|
||||||
|
|
@ -67,6 +67,7 @@ import com.github.damontecres.wholphin.ui.rememberInt
|
||||||
import com.github.damontecres.wholphin.ui.showToast
|
import com.github.damontecres.wholphin.ui.showToast
|
||||||
import com.github.damontecres.wholphin.ui.toServerString
|
import com.github.damontecres.wholphin.ui.toServerString
|
||||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||||
|
import com.github.damontecres.wholphin.ui.util.goToRandom
|
||||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||||
import com.github.damontecres.wholphin.util.BlockingList
|
import com.github.damontecres.wholphin.util.BlockingList
|
||||||
import com.github.damontecres.wholphin.util.DataLoadingState
|
import com.github.damontecres.wholphin.util.DataLoadingState
|
||||||
|
|
@ -302,11 +303,19 @@ class CollectionFolderViewModel
|
||||||
override fun onClickRandom() {
|
override fun onClickRandom() {
|
||||||
viewModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
try {
|
try {
|
||||||
val random =
|
val state = state.value
|
||||||
(state.value.items.successValue as? BlockingList<BaseItem?>)?.randomBlocking()
|
val list = state.items.successValue as? BlockingList<BaseItem?>
|
||||||
Timber.d("Got random item: %s", random?.id)
|
if (list != null) {
|
||||||
random?.destination()?.let {
|
goToRandom(
|
||||||
navigateTo(random.destination())
|
navigateTo = ::navigateTo,
|
||||||
|
parentId = itemId,
|
||||||
|
filter = state.filter,
|
||||||
|
sortAndDirection = state.sortAndDirection,
|
||||||
|
recursive = recursive,
|
||||||
|
list = list,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Timber.w("state.value.items is not successful: %s", state.items)
|
||||||
}
|
}
|
||||||
} catch (ex: CancellationException) {
|
} catch (ex: CancellationException) {
|
||||||
throw ex
|
throw ex
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ import com.github.damontecres.wholphin.ui.main.settings.Library
|
||||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||||
import com.github.damontecres.wholphin.ui.nav.NavDrawerItem
|
import com.github.damontecres.wholphin.ui.nav.NavDrawerItem
|
||||||
import com.github.damontecres.wholphin.ui.showToast
|
import com.github.damontecres.wholphin.ui.showToast
|
||||||
|
import com.github.damontecres.wholphin.ui.util.goToRandom
|
||||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||||
import com.github.damontecres.wholphin.util.BlockingList
|
import com.github.damontecres.wholphin.util.BlockingList
|
||||||
import com.github.damontecres.wholphin.util.DataLoadingState
|
import com.github.damontecres.wholphin.util.DataLoadingState
|
||||||
|
|
@ -700,11 +701,25 @@ class FavoritesViewModel
|
||||||
viewModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
try {
|
try {
|
||||||
collectionStateFor(type)?.let { collectionState ->
|
collectionStateFor(type)?.let { collectionState ->
|
||||||
val random =
|
val list =
|
||||||
(collectionState.items.successValue as? BlockingList<BaseItem?>)?.randomBlocking()
|
collectionState.items.successValue as? BlockingList<BaseItem?>
|
||||||
Timber.d("Got random item: %s", random?.id)
|
if (list != null) {
|
||||||
random?.destination()?.let {
|
goToRandom(
|
||||||
navigateTo(random.destination())
|
navigateTo = ::navigateTo,
|
||||||
|
parentId = libraryDisplayItemId(type),
|
||||||
|
filter =
|
||||||
|
collectionState.filter.copy(
|
||||||
|
includeItemTypes = listOf(type),
|
||||||
|
),
|
||||||
|
sortAndDirection = collectionState.sortAndDirection,
|
||||||
|
recursive = true,
|
||||||
|
list = list,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Timber.w(
|
||||||
|
"state.value.items is not successful: %s",
|
||||||
|
collectionState.items,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (ex: CancellationException) {
|
} catch (ex: CancellationException) {
|
||||||
|
|
|
||||||
|
|
@ -134,7 +134,7 @@ sealed class Destination(
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Slideshow(
|
data class Slideshow(
|
||||||
val parentId: UUID,
|
val parentId: UUID?,
|
||||||
val index: Int,
|
val index: Int,
|
||||||
val filter: CollectionFolderFilter,
|
val filter: CollectionFolderFilter,
|
||||||
val sortAndDirection: SortAndDirection,
|
val sortAndDirection: SortAndDirection,
|
||||||
|
|
|
||||||
|
|
@ -80,6 +80,7 @@ import com.github.damontecres.wholphin.services.SetupDestination
|
||||||
import com.github.damontecres.wholphin.services.SetupNavigationManager
|
import com.github.damontecres.wholphin.services.SetupNavigationManager
|
||||||
import com.github.damontecres.wholphin.ui.FontAwesome
|
import com.github.damontecres.wholphin.ui.FontAwesome
|
||||||
import com.github.damontecres.wholphin.ui.components.TimeDisplay
|
import com.github.damontecres.wholphin.ui.components.TimeDisplay
|
||||||
|
import com.github.damontecres.wholphin.ui.enableMarquee
|
||||||
import com.github.damontecres.wholphin.ui.ifElse
|
import com.github.damontecres.wholphin.ui.ifElse
|
||||||
import com.github.damontecres.wholphin.ui.launchDefault
|
import com.github.damontecres.wholphin.ui.launchDefault
|
||||||
import com.github.damontecres.wholphin.ui.preferences.PreferenceScreenOption
|
import com.github.damontecres.wholphin.ui.preferences.PreferenceScreenOption
|
||||||
|
|
@ -573,6 +574,7 @@ fun NavigationDrawerScope.ProfileIcon(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||||
) {
|
) {
|
||||||
|
val focused by interactionSource.collectIsFocusedAsState()
|
||||||
NavigationDrawerItem(
|
NavigationDrawerItem(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
selected = false,
|
selected = false,
|
||||||
|
|
@ -595,7 +597,7 @@ fun NavigationDrawerScope.ProfileIcon(
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier,
|
modifier = Modifier.enableMarquee(focused),
|
||||||
text = user.name ?: user.id.toString(),
|
text = user.name ?: user.id.toString(),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
)
|
)
|
||||||
|
|
@ -639,7 +641,7 @@ fun NavigationDrawerScope.IconNavItem(
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier,
|
modifier = Modifier.enableMarquee(focused),
|
||||||
text = text,
|
text = text,
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
)
|
)
|
||||||
|
|
@ -733,7 +735,7 @@ fun NavigationDrawerScope.NavItem(
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
modifier = Modifier,
|
modifier = Modifier.enableMarquee(focused),
|
||||||
text = library.name(context),
|
text = library.name(context),
|
||||||
maxLines = 1,
|
maxLines = 1,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -67,7 +67,6 @@ import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||||
import com.github.damontecres.wholphin.ui.components.TextButton
|
import com.github.damontecres.wholphin.ui.components.TextButton
|
||||||
import com.github.damontecres.wholphin.ui.dimAndBlur
|
import com.github.damontecres.wholphin.ui.dimAndBlur
|
||||||
import com.github.damontecres.wholphin.ui.ifElse
|
|
||||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||||
import com.github.damontecres.wholphin.ui.rememberInt
|
import com.github.damontecres.wholphin.ui.rememberInt
|
||||||
import com.github.damontecres.wholphin.ui.serverSupportedMessage
|
import com.github.damontecres.wholphin.ui.serverSupportedMessage
|
||||||
|
|
@ -104,6 +103,8 @@ private fun SwitchServerContentInternal(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
val resources = LocalResources.current
|
val resources = LocalResources.current
|
||||||
|
val focusRequesters =
|
||||||
|
remember(state.servers.size) { List(state.servers.size + 1) { FocusRequester() } }
|
||||||
var showAddServer by remember { mutableStateOf(false) }
|
var showAddServer by remember { mutableStateOf(false) }
|
||||||
var showDeleteDialog by remember { mutableStateOf<JellyfinServer?>(null) }
|
var showDeleteDialog by remember { mutableStateOf<JellyfinServer?>(null) }
|
||||||
var focusedIndex by rememberInt(0)
|
var focusedIndex by rememberInt(0)
|
||||||
|
|
@ -147,10 +148,13 @@ private fun SwitchServerContentInternal(
|
||||||
val firstServerFocus = remember { FocusRequester() }
|
val firstServerFocus = remember { FocusRequester() }
|
||||||
LaunchedEffect(state.loading) {
|
LaunchedEffect(state.loading) {
|
||||||
val state = state
|
val state = state
|
||||||
if (state.loading == LoadingState.Success && state.servers.isNotEmpty()) {
|
when (state.loading) {
|
||||||
firstServerFocus.tryRequestFocus()
|
is LoadingState.Error,
|
||||||
} else if (state.loading == LoadingState.Pending) {
|
LoadingState.Loading,
|
||||||
firstServerFocus.tryRequestFocus()
|
LoadingState.Pending,
|
||||||
|
-> Unit
|
||||||
|
|
||||||
|
LoadingState.Success -> focusRequesters.firstOrNull()?.tryRequestFocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LazyRow(
|
LazyRow(
|
||||||
|
|
@ -191,10 +195,7 @@ private fun SwitchServerContentInternal(
|
||||||
Modifier
|
Modifier
|
||||||
.onFocusChanged {
|
.onFocusChanged {
|
||||||
if (it.isFocused) focusedIndex = index
|
if (it.isFocused) focusedIndex = index
|
||||||
}.ifElse(
|
}.focusRequester(focusRequesters[index]),
|
||||||
index == 0,
|
|
||||||
Modifier.focusRequester(firstServerFocus),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// Add Server card - always rightmost
|
// Add Server card - always rightmost
|
||||||
|
|
@ -205,10 +206,7 @@ private fun SwitchServerContentInternal(
|
||||||
Modifier
|
Modifier
|
||||||
.onFocusChanged {
|
.onFocusChanged {
|
||||||
if (it.isFocused) focusedIndex = -1
|
if (it.isFocused) focusedIndex = -1
|
||||||
}.ifElse(
|
}.focusRequester(focusRequesters.last()),
|
||||||
state.servers.isEmpty(),
|
|
||||||
Modifier.focusRequester(firstServerFocus),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -317,12 +315,12 @@ private fun AddServerDialog(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val firstDiscoveredServerFocusRequester = remember { FocusRequester() }
|
val buttonFocusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
// Default focus to first discovered server if available
|
// Default focus to first discovered server if available
|
||||||
LaunchedEffect(state.discoveredServers.isNotEmpty(), showEnterAddress) {
|
LaunchedEffect(showEnterAddress) {
|
||||||
if (!showEnterAddress && state.discoveredServers.isNotEmpty()) {
|
if (!showEnterAddress) {
|
||||||
firstDiscoveredServerFocusRequester.tryRequestFocus()
|
buttonFocusRequester.tryRequestFocus()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -369,12 +367,8 @@ private fun AddServerDialog(
|
||||||
key = { state.discoveredServers[it].url },
|
key = { state.discoveredServers[it].url },
|
||||||
) { index ->
|
) { index ->
|
||||||
val server = state.discoveredServers[index]
|
val server = state.discoveredServers[index]
|
||||||
val focusRequester =
|
val focusRequester = remember { FocusRequester() }
|
||||||
if (index == 0) {
|
LaunchedEffect(Unit) { if (index == 0) focusRequester.tryRequestFocus() }
|
||||||
firstDiscoveredServerFocusRequester
|
|
||||||
} else {
|
|
||||||
remember { FocusRequester() }
|
|
||||||
}
|
|
||||||
|
|
||||||
ListItem(
|
ListItem(
|
||||||
enabled = true,
|
enabled = true,
|
||||||
|
|
|
||||||
|
|
@ -143,13 +143,16 @@ class SlideshowViewModel
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
serverRepository.currentUser?.let { user ->
|
serverRepository.currentUser?.let { user ->
|
||||||
|
|
||||||
val filter =
|
val filter =
|
||||||
playbackEffectDao
|
slideshowSettings.parentId?.let {
|
||||||
.getPlaybackEffect(
|
playbackEffectDao
|
||||||
user.rowId,
|
.getPlaybackEffect(
|
||||||
slideshowSettings.parentId,
|
user.rowId,
|
||||||
BaseItemKind.PHOTO_ALBUM,
|
slideshowSettings.parentId,
|
||||||
)?.videoFilter
|
BaseItemKind.PHOTO_ALBUM,
|
||||||
|
)?.videoFilter
|
||||||
|
}
|
||||||
if (filter != null) {
|
if (filter != null) {
|
||||||
Timber.v("Got filter for album %s", slideshowSettings.parentId)
|
Timber.v("Got filter for album %s", slideshowSettings.parentId)
|
||||||
albumImageFilter = filter
|
albumImageFilter = filter
|
||||||
|
|
@ -441,7 +444,7 @@ class SlideshowViewModel
|
||||||
fun saveGalleryFilter() {
|
fun saveGalleryFilter() {
|
||||||
viewModelScope.launchIO(ExceptionHandler(autoToast = true)) {
|
viewModelScope.launchIO(ExceptionHandler(autoToast = true)) {
|
||||||
val vf = _imageFilter.value
|
val vf = _imageFilter.value
|
||||||
if (vf != null) {
|
if (slideshowSettings.parentId != null) {
|
||||||
albumImageFilter = vf
|
albumImageFilter = vf
|
||||||
serverRepository.currentUser?.let { user ->
|
serverRepository.currentUser?.let { user ->
|
||||||
playbackEffectDao
|
playbackEffectDao
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
package com.github.damontecres.wholphin.ui.util
|
||||||
|
|
||||||
|
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||||
|
import com.github.damontecres.wholphin.data.model.CollectionFolderFilter
|
||||||
|
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
||||||
|
import com.github.damontecres.wholphin.ui.data.SortAndDirection
|
||||||
|
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||||
|
import com.github.damontecres.wholphin.util.BlockingList
|
||||||
|
import org.jellyfin.sdk.model.api.BaseItemKind
|
||||||
|
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||||
|
import timber.log.Timber
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to a random item in the given list
|
||||||
|
*/
|
||||||
|
suspend fun goToRandom(
|
||||||
|
navigateTo: (Destination) -> Unit,
|
||||||
|
parentId: String,
|
||||||
|
filter: GetItemsFilter,
|
||||||
|
sortAndDirection: SortAndDirection,
|
||||||
|
recursive: Boolean,
|
||||||
|
list: BlockingList<BaseItem?>,
|
||||||
|
) {
|
||||||
|
val randomIndex = Random.nextInt(list.size)
|
||||||
|
val random = list.getBlocking(randomIndex)
|
||||||
|
|
||||||
|
Timber.d("Got random item #%s: %s", randomIndex, random?.id)
|
||||||
|
if (random != null) {
|
||||||
|
val destination =
|
||||||
|
if (random.type == BaseItemKind.PHOTO) {
|
||||||
|
Destination.Slideshow(
|
||||||
|
parentId = parentId.toUUIDOrNull(),
|
||||||
|
index = randomIndex,
|
||||||
|
filter =
|
||||||
|
CollectionFolderFilter(
|
||||||
|
filter = filter,
|
||||||
|
),
|
||||||
|
sortAndDirection = sortAndDirection,
|
||||||
|
recursive = recursive,
|
||||||
|
startSlideshow = false,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
random.destination()
|
||||||
|
}
|
||||||
|
navigateTo(destination)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue