Various bug fixes (#2010)
## Description Collecting minor bug fixes in one PR: - If error occurs fetching a series, show the error on the UI instead of loading forever - Use song's artists on Now Playing page - Fixes settings wrap around scrolling when an update is available - Fix backdrop not clearing for items without backdrop - Fix not picking an ABI-specific APK when updating a regular release (maybe related to #1966) - Add "Go To" for album context menus - Fix handling of next/previous media remote button presses during video playback ### Related issues Fixes #1990 Fixes #1965 ### Testing Emulator mostly ## Screenshots N/A ## AI or LLM usage None
This commit is contained in:
parent
a38eb3e3d8
commit
b99f21efa8
14 changed files with 453 additions and 27 deletions
|
|
@ -42,7 +42,7 @@ data class AudioItem(
|
|||
?.id,
|
||||
title = item.title,
|
||||
albumTitle = item.data.album,
|
||||
artistNames = item.data.albumArtist,
|
||||
artistNames = item.data.artists?.joinToString(", "),
|
||||
runtime = item.data.runTimeTicks?.ticks,
|
||||
imageUrl = imageUrl,
|
||||
hasLyrics = item.data.hasLyrics == true,
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ class BackdropService
|
|||
itemId: String,
|
||||
imageUrl: String?,
|
||||
) = withContext(WholphinDispatchers.IO) {
|
||||
if (backdropFlow.firstOrNull()?.imageUrl != imageUrl) {
|
||||
if (backdropFlow.firstOrNull()?.imageUrl != imageUrl || imageUrl == null) {
|
||||
_backdropFlow.update {
|
||||
it.copy(
|
||||
itemId = itemId,
|
||||
|
|
@ -110,7 +110,7 @@ class BackdropService
|
|||
val dynamicEnabled =
|
||||
backdropStyle == BackdropStyle.BACKDROP_DYNAMIC_COLOR ||
|
||||
backdropStyle == BackdropStyle.UNRECOGNIZED
|
||||
val (primaryColor, secondaryColor, tertiaryColor) =
|
||||
val colors =
|
||||
if (dynamicEnabled) {
|
||||
extractColorsFromBackdrop(imageUrl)
|
||||
} else {
|
||||
|
|
@ -121,9 +121,9 @@ class BackdropService
|
|||
BackdropResult(
|
||||
itemId = itemId,
|
||||
imageUrl = imageUrl,
|
||||
primaryColor = primaryColor,
|
||||
secondaryColor = secondaryColor,
|
||||
tertiaryColor = tertiaryColor,
|
||||
primaryColor = colors?.primary ?: it.primaryColor,
|
||||
secondaryColor = colors?.secondary ?: it.secondaryColor,
|
||||
tertiaryColor = colors?.tertiary ?: it.tertiaryColor,
|
||||
)
|
||||
} else {
|
||||
it
|
||||
|
|
@ -131,10 +131,10 @@ class BackdropService
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun extractColorsFromBackdrop(imageUrl: String?): ExtractedColors =
|
||||
suspend fun extractColorsFromBackdrop(imageUrl: String?): ExtractedColors? =
|
||||
withContext(WholphinDispatchers.IO) {
|
||||
if (imageUrl.isNullOrBlank()) {
|
||||
return@withContext ExtractedColors.DEFAULT
|
||||
return@withContext null
|
||||
}
|
||||
extractedColorCache.get(imageUrl)?.let {
|
||||
return@withContext it
|
||||
|
|
|
|||
|
|
@ -210,9 +210,7 @@ class UpdateChecker
|
|||
okHttpClient.newCall(request).execute().use {
|
||||
if (it.isSuccessful) {
|
||||
Timber.v("Request successful for ${release.downloadUrl}")
|
||||
withContext(WholphinDispatchers.Main) {
|
||||
callback.contentLength(it.body.contentLength())
|
||||
}
|
||||
callback.contentLength(it.body.contentLength())
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||
val contentValues =
|
||||
ContentValues().apply {
|
||||
|
|
@ -397,9 +395,7 @@ suspend fun copyTo(
|
|||
while (bytes >= 0) {
|
||||
out.write(buffer, 0, bytes)
|
||||
bytesCopied += bytes
|
||||
withContext(WholphinDispatchers.Main) {
|
||||
callback.bytesDownloaded(bytesCopied)
|
||||
}
|
||||
callback.bytesDownloaded(bytesCopied)
|
||||
bytes = input.read(buffer)
|
||||
}
|
||||
return@withContext bytesCopied
|
||||
|
|
@ -416,7 +412,12 @@ fun getDownloadUrl(
|
|||
buildList {
|
||||
add("$ASSET_NAME${releaseSuffix}$abiSuffix.apk")
|
||||
add("$ASSET_NAME$releaseSuffix.apk")
|
||||
if (!debug) add("$ASSET_NAME.apk")
|
||||
if (!debug) {
|
||||
if (abiSuffix.isNotNullOrBlank()) {
|
||||
add("$ASSET_NAME$abiSuffix.apk")
|
||||
}
|
||||
add("$ASSET_NAME.apk")
|
||||
}
|
||||
}
|
||||
var preferredAsset: JsonObject? = null
|
||||
outer@ for (name in preferredNames) {
|
||||
|
|
|
|||
|
|
@ -66,6 +66,7 @@ sealed interface ContextMenu {
|
|||
val canRemoveFromQueue: Boolean,
|
||||
val actions: MusicContextActions,
|
||||
val showRemoveFromPlaylist: Boolean = false,
|
||||
val showGoTo: Boolean = false,
|
||||
) : ContextMenu
|
||||
|
||||
data class ForQueue(
|
||||
|
|
@ -758,6 +759,18 @@ fun buildContextForMusic(
|
|||
buildList {
|
||||
val item = music.item
|
||||
val index = music.index
|
||||
// Songs should not show Go to
|
||||
if (music.showGoTo && item.type != BaseItemKind.AUDIO) {
|
||||
add(
|
||||
DialogItem(
|
||||
resources.getString(R.string.go_to),
|
||||
Icons.Default.ArrowForward,
|
||||
dismissOnClick = true,
|
||||
) {
|
||||
actions.navigateTo(item.destination())
|
||||
},
|
||||
)
|
||||
}
|
||||
add(
|
||||
DialogItem(
|
||||
resources.getString(R.string.play),
|
||||
|
|
|
|||
|
|
@ -609,6 +609,7 @@ fun AlbumDetailsPage(
|
|||
),
|
||||
canRemoveFromQueue = false,
|
||||
actions = moreDialogActions,
|
||||
showGoTo = true,
|
||||
)
|
||||
},
|
||||
cardContent = { index: Int, item: BaseItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit ->
|
||||
|
|
|
|||
|
|
@ -568,6 +568,7 @@ fun ArtistDetailsPage(
|
|||
),
|
||||
canRemoveFromQueue = false,
|
||||
actions = moreDialogActions,
|
||||
showGoTo = true,
|
||||
)
|
||||
},
|
||||
cardContent = { index: Int, album: BaseItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit ->
|
||||
|
|
@ -606,6 +607,7 @@ fun ArtistDetailsPage(
|
|||
),
|
||||
canRemoveFromQueue = false,
|
||||
actions = moreDialogActions,
|
||||
showGoTo = true,
|
||||
)
|
||||
},
|
||||
cardContent = { index: Int, album: BaseItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit ->
|
||||
|
|
@ -704,6 +706,7 @@ fun ArtistDetailsPage(
|
|||
),
|
||||
canRemoveFromQueue = false,
|
||||
actions = moreDialogActions,
|
||||
showGoTo = true,
|
||||
)
|
||||
},
|
||||
cardContent = { index: Int, item: BaseItem?, mod: Modifier, onClick: () -> Unit, onLongClick: () -> Unit ->
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ import androidx.compose.ui.platform.LocalContext
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.ui.compose.state.rememberCurrentMediaItemState
|
||||
import androidx.tv.material3.Button
|
||||
|
|
@ -127,6 +128,12 @@ fun NowPlayingPage(
|
|||
onPlaybackDialogTypeClick = { },
|
||||
getDurationMs = { player.duration },
|
||||
dpadSeekMode = preferences.playbackPreferences.dpadSeekMode,
|
||||
onSeekToNext = {
|
||||
if (player.isCommandAvailable(Player.COMMAND_SEEK_TO_NEXT)) player.seekToNext()
|
||||
},
|
||||
onPSeekToPrevious = {
|
||||
if (player.isCommandAvailable(Player.COMMAND_SEEK_TO_PREVIOUS)) player.seekToPrevious()
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import com.github.damontecres.wholphin.preferences.AppPreferences
|
|||
import com.github.damontecres.wholphin.preferences.updateMusicPreferences
|
||||
import com.github.damontecres.wholphin.services.BackdropResult
|
||||
import com.github.damontecres.wholphin.services.BackdropService
|
||||
import com.github.damontecres.wholphin.services.ExtractedColors
|
||||
import com.github.damontecres.wholphin.services.ImageUrlService
|
||||
import com.github.damontecres.wholphin.services.MusicService
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
|
|
@ -278,7 +279,7 @@ class NowPlayingViewModel
|
|||
val (primaryColor, secondaryColor, tertiaryColor) =
|
||||
backdropService.extractColorsFromBackdrop(
|
||||
imageUrl,
|
||||
)
|
||||
) ?: ExtractedColors.DEFAULT
|
||||
val backdropResult =
|
||||
BackdropResult(
|
||||
itemId = itemId.toString(),
|
||||
|
|
|
|||
|
|
@ -124,10 +124,18 @@ class SeriesViewModel
|
|||
Timber.v("Start")
|
||||
addCloseable { themeSongPlayer.stop() }
|
||||
val series =
|
||||
api.userLibraryApi
|
||||
.getItem(seriesId)
|
||||
.content
|
||||
.let { BaseItem(it) }
|
||||
try {
|
||||
api.userLibraryApi
|
||||
.getItem(seriesId)
|
||||
.content
|
||||
.let { BaseItem(it) }
|
||||
} catch (ex: CancellationException) {
|
||||
throw ex
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex, "Error fetching series %s", seriesId)
|
||||
_state.update { it.copy(series = DataLoadingState.Error(ex)) }
|
||||
return@launchIO
|
||||
}
|
||||
viewModelScope.launchDefault {
|
||||
mediaManagementService.collectCanDelete(flowOf(series)) { canDelete ->
|
||||
_state.update { it.copy(canDeleteSeries = canDelete) }
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ class PlaybackKeyHandler(
|
|||
private val isDpadSeekVisible: () -> Boolean = { false },
|
||||
private val onDpadSeek: (Long) -> Unit = { },
|
||||
private val dpadSeekMode: DpadSeekMode,
|
||||
private val onSeekToNext: () -> Unit,
|
||||
private val onPSeekToPrevious: () -> Unit,
|
||||
) {
|
||||
private var leftHandledByRepeat = false
|
||||
private var rightHandledByRepeat = false
|
||||
|
|
@ -89,11 +91,11 @@ class PlaybackKeyHandler(
|
|||
}
|
||||
|
||||
Key.MediaNext -> {
|
||||
if (player.isCommandAvailable(Player.COMMAND_SEEK_TO_NEXT)) player.seekToNext()
|
||||
onSeekToNext.invoke()
|
||||
}
|
||||
|
||||
Key.MediaPrevious -> {
|
||||
if (player.isCommandAvailable(Player.COMMAND_SEEK_TO_PREVIOUS)) player.seekToPrevious()
|
||||
onPSeekToPrevious.invoke()
|
||||
}
|
||||
|
||||
Key.Captions -> {
|
||||
|
|
|
|||
|
|
@ -269,6 +269,8 @@ fun PlaybackPageContent(
|
|||
},
|
||||
onDpadSeek = onDpadSeek,
|
||||
dpadSeekMode = prefs.dpadSeekMode,
|
||||
onSeekToNext = viewModel::playNextUp,
|
||||
onPSeekToPrevious = viewModel::playPrevious,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -226,6 +226,8 @@ fun PreferencesContent(
|
|||
) {
|
||||
if (showUpdate) {
|
||||
item {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val focused = interactionSource.collectIsFocusedAsState().value
|
||||
val updateFocusRequester = remember { FocusRequester() }
|
||||
LaunchedEffect(Unit) {
|
||||
if (focusedIndex.first == 0 && focusedIndex.second == 0) {
|
||||
|
|
@ -240,11 +242,20 @@ fun PreferencesContent(
|
|||
viewModel.navigationManager.navigateTo(Destination.UpdateApp)
|
||||
},
|
||||
summary = release?.version?.toString(),
|
||||
interactionSource = interactionSource,
|
||||
modifier =
|
||||
Modifier
|
||||
.focusRequester(updateFocusRequester)
|
||||
.focusRequester(firstFocusRequester)
|
||||
.playSoundOnFocus(movementSounds),
|
||||
.playSoundOnFocus(movementSounds)
|
||||
.lazyListWrapScrolling(
|
||||
state = state,
|
||||
focused = focused,
|
||||
isFirst = true,
|
||||
isLast = false,
|
||||
firstFocusRequester = firstFocusRequester,
|
||||
lastFocusRequester = lastFocusRequester,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue