diff --git a/app/shared/src/androidMain/kotlin/ui/subject/episode/details/EpisodeDetails.android.kt b/app/shared/src/androidMain/kotlin/ui/subject/episode/details/EpisodeDetails.android.kt index f9078cc43c..81cad595ba 100644 --- a/app/shared/src/androidMain/kotlin/ui/subject/episode/details/EpisodeDetails.android.kt +++ b/app/shared/src/androidMain/kotlin/ui/subject/episode/details/EpisodeDetails.android.kt @@ -207,7 +207,7 @@ private fun PreviewEpisodeDetailsImpl( ) }, mediaSelectorState = mediaSelectorState, - mediaSourceResultListPresentation = TestMediaSourceResultListPresentation, + mediaSourceResultListPresentation = { TestMediaSourceResultListPresentation }, authState = authState, onSwitchEpisode = {}, onRefreshMediaSources = {}, diff --git a/app/shared/src/androidMain/kotlin/ui/subject/episode/details/EpisodePlayMediaSelectorSheet.android.kt b/app/shared/src/androidMain/kotlin/ui/subject/episode/details/EpisodePlayMediaSelectorSheet.android.kt index dcc0192eb0..a13bfdd5b1 100644 --- a/app/shared/src/androidMain/kotlin/ui/subject/episode/details/EpisodePlayMediaSelectorSheet.android.kt +++ b/app/shared/src/androidMain/kotlin/ui/subject/episode/details/EpisodePlayMediaSelectorSheet.android.kt @@ -26,7 +26,7 @@ import me.him188.ani.utils.platform.annotations.TestOnly private fun PreviewEpisodePlayMediaSelectorSheet() = ProvideFoundationCompositionLocalsForPreview { EpisodePlayMediaSelector( rememberTestMediaSelectorState(), - TestMediaSourceResultListPresentation, + { TestMediaSourceResultListPresentation }, onDismissRequest = {}, onRefresh = {}, onRestartSource = {}, @@ -39,7 +39,7 @@ private fun PreviewEpisodePlayMediaSelectorSheet() = ProvideFoundationCompositio private fun PreviewEpisodePlayMediaSelectorSheet2() = ProvideFoundationCompositionLocalsForPreview { EpisodePlayMediaSelector( rememberTestMediaSelectorState(), - TestMediaSourceResultListPresentation, + { TestMediaSourceResultListPresentation }, onDismissRequest = {}, onRefresh = {}, onRestartSource = {}, @@ -52,7 +52,7 @@ private fun PreviewEpisodePlayMediaSelectorSheet2() = ProvideFoundationCompositi private fun PreviewEpisodePlayMediaSelectorSheet3() = ProvideFoundationCompositionLocalsForPreview { EpisodePlayMediaSelector( rememberTestMediaSelectorState(), - TestMediaSourceResultListPresentation, + { TestMediaSourceResultListPresentation }, onDismissRequest = {}, onRefresh = {}, onRestartSource = {}, diff --git a/app/shared/src/commonMain/kotlin/ui/subject/episode/EpisodePage.kt b/app/shared/src/commonMain/kotlin/ui/subject/episode/EpisodePage.kt index 34af436a42..8432929549 100644 --- a/app/shared/src/commonMain/kotlin/ui/subject/episode/EpisodePage.kt +++ b/app/shared/src/commonMain/kotlin/ui/subject/episode/EpisodePage.kt @@ -398,7 +398,7 @@ private fun EpisodeSceneTabletVeryWide( page.danmakuStatistics, vm.videoStatisticsFlow, page.mediaSelectorState, - page.mediaSourceResultListPresentation, + { page.mediaSourceResultListPresentation }, vm.authState, onSwitchEpisode = { episodeId -> if (!vm.episodeSelectorState.selectEpisodeId(episodeId)) { @@ -513,7 +513,7 @@ private fun EpisodeSceneContentPhone( page.danmakuStatistics, vm.videoStatisticsFlow, page.mediaSelectorState, - page.mediaSourceResultListPresentation, + { page.mediaSourceResultListPresentation }, vm.authState, onSwitchEpisode = { episodeId -> if (!vm.episodeSelectorState.selectEpisodeId(episodeId)) { diff --git a/app/shared/src/commonMain/kotlin/ui/subject/episode/EpisodeViewModel.kt b/app/shared/src/commonMain/kotlin/ui/subject/episode/EpisodeViewModel.kt index b8038655b1..5617464496 100644 --- a/app/shared/src/commonMain/kotlin/ui/subject/episode/EpisodeViewModel.kt +++ b/app/shared/src/commonMain/kotlin/ui/subject/episode/EpisodeViewModel.kt @@ -559,8 +559,19 @@ class EpisodeViewModel( ).distinctUntilChanged(), settingsRepository.danmakuEnabled.flow, settingsRepository.danmakuConfig.flow, - mediaSourceResultsFlow, - ) { subjectEpisodeBundle, loadError, fetchSelect, danmakuStatistics, danmakuEnabled, danmakuConfig, mediaSourceResultsPresentation -> + episodeSession.fetchSelectFlow.map { fetchSelect -> + if (fetchSelect != null) MediaSelectorState( + fetchSelect.mediaSelector, + mediaSourceInfoProvider, + backgroundScope, + ) else { + // TODO: 2025/1/22 We should not use createTestMediaSelectorState + @OptIn(TestOnly::class) + createTestMediaSelectorState(backgroundScope) + } + }, + mediaSourceResultsFlow.map { MediaSourceResultListPresentation(it) }, + ) { subjectEpisodeBundle, loadError, fetchSelect, danmakuStatistics, danmakuEnabled, danmakuConfig, mediaSelectorState, mediaSourceResultsPresentation -> val (subject, episode) = if (subjectEpisodeBundle == null) { SubjectPresentation.Placeholder to EpisodePresentation.Placeholder @@ -576,16 +587,8 @@ class EpisodeViewModel( } EpisodePageState( - mediaSelectorState = if (fetchSelect != null) MediaSelectorState( - fetchSelect.mediaSelector, - mediaSourceInfoProvider, - backgroundScope, - ) else { - // TODO: 2025/1/22 We should not use createTestMediaSelectorState - @OptIn(TestOnly::class) - createTestMediaSelectorState(backgroundScope) - }, - mediaSourceResultListPresentation = MediaSourceResultListPresentation(mediaSourceResultsPresentation), + mediaSelectorState = mediaSelectorState, + mediaSourceResultListPresentation = mediaSourceResultsPresentation, danmakuStatistics = danmakuStatistics, subjectPresentation = subject, episodePresentation = episode, diff --git a/app/shared/src/commonMain/kotlin/ui/subject/episode/details/EpisodeDetails.kt b/app/shared/src/commonMain/kotlin/ui/subject/episode/details/EpisodeDetails.kt index 4256d4a47c..be5b5a6a2b 100644 --- a/app/shared/src/commonMain/kotlin/ui/subject/episode/details/EpisodeDetails.kt +++ b/app/shared/src/commonMain/kotlin/ui/subject/episode/details/EpisodeDetails.kt @@ -127,7 +127,7 @@ fun EpisodeDetails( danmakuStatistics: DanmakuStatistics, videoStatisticsFlow: Flow, mediaSelectorState: MediaSelectorState, - mediaSourceResultListPresentation: MediaSourceResultListPresentation, + mediaSourceResultListPresentation: () -> MediaSourceResultListPresentation, authState: AuthState, onSwitchEpisode: (episodeId: Int) -> Unit, onRefreshMediaSources: () -> Unit, diff --git a/app/shared/src/commonMain/kotlin/ui/subject/episode/details/EpisodePlayMediaSelector.kt b/app/shared/src/commonMain/kotlin/ui/subject/episode/details/EpisodePlayMediaSelector.kt index b3ce2d7064..bcb525b36d 100644 --- a/app/shared/src/commonMain/kotlin/ui/subject/episode/details/EpisodePlayMediaSelector.kt +++ b/app/shared/src/commonMain/kotlin/ui/subject/episode/details/EpisodePlayMediaSelector.kt @@ -31,7 +31,7 @@ import me.him188.ani.datasources.api.Media @Composable fun EpisodePlayMediaSelector( mediaSelector: MediaSelectorState, - sourceResults: MediaSourceResultListPresentation, + sourceResults: () -> MediaSourceResultListPresentation, onDismissRequest: () -> Unit, onRefresh: () -> Unit, onRestartSource: (MediaSourceResultPresentation) -> Unit, @@ -43,7 +43,7 @@ fun EpisodePlayMediaSelector( mediaSelector, sourceResults = { MediaSourceResultsView( - sourceResults, mediaSelector, + sourceResults(), mediaSelector, onRefresh = onRefresh, onRestartSource = onRestartSource, ) diff --git a/app/shared/src/commonMain/kotlin/ui/subject/episode/mediaFetch/MediaSelectorState.kt b/app/shared/src/commonMain/kotlin/ui/subject/episode/mediaFetch/MediaSelectorState.kt index 560625782d..5188ec57ce 100644 --- a/app/shared/src/commonMain/kotlin/ui/subject/episode/mediaFetch/MediaSelectorState.kt +++ b/app/shared/src/commonMain/kotlin/ui/subject/episode/mediaFetch/MediaSelectorState.kt @@ -10,6 +10,7 @@ package me.him188.ani.app.ui.subject.episode.mediaFetch import androidx.compose.runtime.Composable +import androidx.compose.runtime.Immutable import androidx.compose.runtime.Stable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue @@ -114,11 +115,13 @@ fun MediaPreferenceItemState.preferOrRemove(value: T?): Job { /** * Wraps [MediaSelector] to provide states for UI. */ +@Stable class MediaSelectorState( private val mediaSelector: MediaSelector, val mediaSourceInfoProvider: MediaSourceInfoProvider, private val backgroundScope: CoroutineScope, ) { + @Immutable data class Presentation( val filteredCandidates: List, val preferredCandidates: List, diff --git a/app/shared/src/commonMain/kotlin/ui/subject/episode/mediaFetch/MediaSelectorView.kt b/app/shared/src/commonMain/kotlin/ui/subject/episode/mediaFetch/MediaSelectorView.kt index d338aaf24a..8167fed5cb 100644 --- a/app/shared/src/commonMain/kotlin/ui/subject/episode/mediaFetch/MediaSelectorView.kt +++ b/app/shared/src/commonMain/kotlin/ui/subject/episode/mediaFetch/MediaSelectorView.kt @@ -1,5 +1,5 @@ /* - * Copyright (C) 2024 OpenAni and contributors. + * Copyright (C) 2024-2025 OpenAni and contributors. * * 此源代码的使用受 GNU AFFERO GENERAL PUBLIC LICENSE version 3 许可证的约束, 可以在以下链接找到该许可证. * Use of this source code is governed by the GNU AGPLv3 license, which can be found at the following link. @@ -69,8 +69,13 @@ fun MediaSelectorView( stickyHeaderBackgroundColor: Color = Color.Unspecified, itemProgressBar: @Composable RowScope.(MediaGroup) -> Unit = { group -> val presentation by state.presentationFlow.collectAsStateWithLifecycle() + val showIndicator by remember(group) { + derivedStateOf { + group.list.any { it.original === presentation.selected } + } + } FastLinearProgressIndicator( - group.list.any { it.original === presentation.selected }, + showIndicator, Modifier.fillMaxWidth().padding(horizontal = 4.dp), delayMillis = 300, )