mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Smoother now playing backdrop transition
This commit is contained in:
parent
8ddd3fc7eb
commit
b70a7bc7bb
5 changed files with 75 additions and 7 deletions
|
|
@ -114,7 +114,7 @@ class BackdropService
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun extractColorsFromBackdrop(imageUrl: String?): ExtractedColors =
|
||||
suspend fun extractColorsFromBackdrop(imageUrl: String?): ExtractedColors =
|
||||
withContext(Dispatchers.IO) {
|
||||
if (imageUrl.isNullOrBlank()) {
|
||||
return@withContext ExtractedColors.DEFAULT
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ import coil3.compose.AsyncImage
|
|||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.AudioItem
|
||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||
import com.github.damontecres.wholphin.preferences.BackdropStyle
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.services.rememberQueue
|
||||
import com.github.damontecres.wholphin.ui.components.BasicDialog
|
||||
|
|
@ -61,6 +62,7 @@ import com.github.damontecres.wholphin.ui.components.DialogParams
|
|||
import com.github.damontecres.wholphin.ui.components.DialogPopup
|
||||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.findActivity
|
||||
import com.github.damontecres.wholphin.ui.nav.Backdrop
|
||||
import com.github.damontecres.wholphin.ui.playback.BottomDialog
|
||||
import com.github.damontecres.wholphin.ui.playback.BottomDialogItem
|
||||
import com.github.damontecres.wholphin.ui.playback.PlaybackKeyHandler
|
||||
|
|
@ -159,6 +161,15 @@ fun NowPlayingPage(
|
|||
}
|
||||
|
||||
Box(modifier) {
|
||||
Backdrop(
|
||||
backdrop = state.backdropResult,
|
||||
drawerIsOpen = false,
|
||||
backdropStyle = BackdropStyle.BACKDROP_DYNAMIC_COLOR,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
enableTopScrim = false,
|
||||
useExistingImageAsPlaceholder = true,
|
||||
crossfadeDuration = 1.5.seconds,
|
||||
)
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package com.github.damontecres.wholphin.ui.detail.music
|
||||
|
||||
import androidx.compose.runtime.Stable
|
||||
import com.github.damontecres.wholphin.services.BackdropResult
|
||||
import com.github.damontecres.wholphin.services.MusicServiceState
|
||||
import org.jellyfin.sdk.model.api.LyricDto
|
||||
|
||||
|
|
@ -10,6 +11,7 @@ data class NowPlayingState(
|
|||
val lyrics: LyricDto? = null,
|
||||
val currentLyricIndex: Int? = null,
|
||||
val visualizerPermissions: Boolean = false,
|
||||
val backdropResult: BackdropResult = BackdropResult.NONE,
|
||||
) {
|
||||
val hasLyrics: Boolean get() = lyrics != null && lyrics.lyrics.isNotEmpty()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,9 @@ import com.github.damontecres.wholphin.data.model.BaseItem
|
|||
import com.github.damontecres.wholphin.preferences.AppPreference
|
||||
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.ImageUrlService
|
||||
import com.github.damontecres.wholphin.services.MusicService
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.services.NowPlayingStatus
|
||||
|
|
@ -45,6 +47,7 @@ import kotlinx.coroutines.sync.withLock
|
|||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.lyricsApi
|
||||
import org.jellyfin.sdk.api.client.extensions.userLibraryApi
|
||||
import org.jellyfin.sdk.model.api.ImageType
|
||||
import org.jellyfin.sdk.model.api.LyricDto
|
||||
import org.jellyfin.sdk.model.extensions.ticks
|
||||
import timber.log.Timber
|
||||
|
|
@ -63,6 +66,7 @@ class NowPlayingViewModel
|
|||
private val api: ApiClient,
|
||||
private val musicService: MusicService,
|
||||
private val backdropService: BackdropService,
|
||||
private val imageUrlService: ImageUrlService,
|
||||
private val preferencesDataStore: DataStore<AppPreferences>,
|
||||
val navigationManager: NavigationManager,
|
||||
val userPreferencesService: UserPreferencesService,
|
||||
|
|
@ -123,6 +127,7 @@ class NowPlayingViewModel
|
|||
controllerViewState.pulseControls()
|
||||
}
|
||||
viewModelScope.launchDefault {
|
||||
backdropService.clearBackdrop()
|
||||
updateBackdrop(getCurrent())
|
||||
}
|
||||
playbackLoop()
|
||||
|
|
@ -230,19 +235,18 @@ class NowPlayingViewModel
|
|||
val backdropItem =
|
||||
getBackdropItemForAlbum(api, BaseItem(it, false))
|
||||
if (backdropItem != null) {
|
||||
backdropService.submit(backdropItem)
|
||||
doUpdateBackdrop(backdropItem)
|
||||
} else {
|
||||
backdropService.clearBackdrop()
|
||||
clearBackdrop()
|
||||
}
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex, "Error fetching backdrop")
|
||||
backdropService.clearBackdrop()
|
||||
clearBackdrop()
|
||||
}
|
||||
} else {
|
||||
backdropService.clearBackdrop()
|
||||
clearBackdrop()
|
||||
}
|
||||
// TODO needs smoother transition
|
||||
delay(60.seconds)
|
||||
try {
|
||||
val index = Random.nextInt(state.value.musicServiceState.queueSize)
|
||||
|
|
@ -257,6 +261,27 @@ class NowPlayingViewModel
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun doUpdateBackdrop(item: BaseItem) {
|
||||
val imageUrl = imageUrlService.getItemImageUrl(item, ImageType.BACKDROP)
|
||||
val (primaryColor, secondaryColor, tertiaryColor) =
|
||||
backdropService.extractColorsFromBackdrop(
|
||||
imageUrl,
|
||||
)
|
||||
val backdropResult =
|
||||
BackdropResult(
|
||||
itemId = item.id.toString(),
|
||||
imageUrl = imageUrl,
|
||||
primaryColor = primaryColor,
|
||||
secondaryColor = secondaryColor,
|
||||
tertiaryColor = tertiaryColor,
|
||||
)
|
||||
state.update { it.copy(backdropResult = backdropResult) }
|
||||
}
|
||||
|
||||
private fun clearBackdrop() {
|
||||
state.update { it.copy(backdropResult = BackdropResult.NONE) }
|
||||
}
|
||||
|
||||
fun moveQueue(
|
||||
index: Int,
|
||||
direction: MoveDirection,
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
@file:OptIn(ExperimentalCoilApi::class)
|
||||
|
||||
package com.github.damontecres.wholphin.ui.nav
|
||||
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
|
|
@ -24,11 +26,15 @@ import androidx.compose.ui.platform.LocalContext
|
|||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import coil3.annotation.ExperimentalCoilApi
|
||||
import coil3.compose.AsyncImage
|
||||
import coil3.compose.useExistingImageAsPlaceholder
|
||||
import coil3.request.ImageRequest
|
||||
import coil3.request.transitionFactory
|
||||
import com.github.damontecres.wholphin.preferences.BackdropStyle
|
||||
import com.github.damontecres.wholphin.services.BackdropResult
|
||||
import com.github.damontecres.wholphin.ui.CrossFadeFactory
|
||||
import kotlin.time.Duration
|
||||
import kotlin.time.Duration.Companion.milliseconds
|
||||
|
||||
@Composable
|
||||
|
|
@ -38,8 +44,31 @@ fun Backdrop(
|
|||
viewModel: ApplicationContentViewModel = hiltViewModel(),
|
||||
modifier: Modifier = Modifier,
|
||||
enableTopScrim: Boolean = true,
|
||||
useExistingImageAsPlaceholder: Boolean = false,
|
||||
crossfadeDuration: Duration = 800.milliseconds,
|
||||
) {
|
||||
val backdrop by viewModel.backdropService.backdropFlow.collectAsStateWithLifecycle()
|
||||
Backdrop(
|
||||
backdrop = backdrop,
|
||||
drawerIsOpen = drawerIsOpen,
|
||||
backdropStyle = backdropStyle,
|
||||
modifier = modifier,
|
||||
enableTopScrim = enableTopScrim,
|
||||
useExistingImageAsPlaceholder = useExistingImageAsPlaceholder,
|
||||
crossfadeDuration = crossfadeDuration,
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun Backdrop(
|
||||
backdrop: BackdropResult,
|
||||
drawerIsOpen: Boolean,
|
||||
backdropStyle: BackdropStyle,
|
||||
modifier: Modifier = Modifier,
|
||||
enableTopScrim: Boolean = true,
|
||||
useExistingImageAsPlaceholder: Boolean = false,
|
||||
crossfadeDuration: Duration = 800.milliseconds,
|
||||
) {
|
||||
val baseBackgroundColor = MaterialTheme.colorScheme.background
|
||||
if (backdrop.hasColors &&
|
||||
(backdropStyle == BackdropStyle.BACKDROP_DYNAMIC_COLOR || backdropStyle == BackdropStyle.UNRECOGNIZED)
|
||||
|
|
@ -117,7 +146,8 @@ fun Backdrop(
|
|||
ImageRequest
|
||||
.Builder(LocalContext.current)
|
||||
.data(backdrop.imageUrl)
|
||||
.transitionFactory(CrossFadeFactory(800.milliseconds))
|
||||
.useExistingImageAsPlaceholder(useExistingImageAsPlaceholder)
|
||||
.transitionFactory(CrossFadeFactory(crossfadeDuration))
|
||||
.build(),
|
||||
contentDescription = null,
|
||||
contentScale = ContentScale.Fit,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue