mirror of
https://github.com/FluxaMedia/fluxa.git
synced 2026-07-26 20:02:14 +00:00
Fix shared UI boundary violations from the CMP player overlay migration
checkSharedUiBoundary was failing on master because the July 17 move of player overlays into shared/commonMain kept imports of data/player domain types (Meta, Stream, Video, IntroTimestamps, UserProfile, Chapter, MediaTrack, TorrentStreamStatus, TrailerCue). Relocate the plain value types into the shared-safe com.fluxa.app.shared.feature.player package, and replace the remaining domain-object usages with small UI models (StreamSourceUiModel, SkipSegmentUiModel, NextEpisodePreviewUiModel) mapped at the Android call sites. Also drops the dead, UserProfile-only QuickSettingsSidebar/TrackSidebar composables that had no callers.
This commit is contained in:
parent
5cb1bb75db
commit
d5f3819ebe
43 changed files with 131 additions and 107 deletions
|
|
@ -81,7 +81,7 @@ class AndroidCatalogHomeDataSource(
|
||||||
val items: List<com.fluxa.app.data.remote.Meta>,
|
val items: List<com.fluxa.app.data.remote.Meta>,
|
||||||
val logoUrl: String?,
|
val logoUrl: String?,
|
||||||
val trailerUrl: String?,
|
val trailerUrl: String?,
|
||||||
val trailerSubtitleCues: List<com.fluxa.app.player.TrailerCue>
|
val trailerSubtitleCues: List<com.fluxa.app.shared.feature.player.TrailerCue>
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun billboardResolution(): Flow<BillboardResolution> = combine(
|
private fun billboardResolution(): Flow<BillboardResolution> = combine(
|
||||||
|
|
@ -109,7 +109,7 @@ class AndroidCatalogHomeDataSource(
|
||||||
billboardPool: List<com.fluxa.app.data.remote.Meta>,
|
billboardPool: List<com.fluxa.app.data.remote.Meta>,
|
||||||
billboardLogo: String?,
|
billboardLogo: String?,
|
||||||
billboardTrailerUrl: String?,
|
billboardTrailerUrl: String?,
|
||||||
billboardTrailerSubtitleCues: List<com.fluxa.app.player.TrailerCue>,
|
billboardTrailerSubtitleCues: List<com.fluxa.app.shared.feature.player.TrailerCue>,
|
||||||
filter: String
|
filter: String
|
||||||
): BillboardResolution {
|
): BillboardResolution {
|
||||||
val filteredPool = billboardPool.filter { it.matchesFilter(filter) }
|
val filteredPool = billboardPool.filter { it.matchesFilter(filter) }
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,7 @@ import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
|
||||||
import androidx.media3.ui.AspectRatioFrameLayout
|
import androidx.media3.ui.AspectRatioFrameLayout
|
||||||
import androidx.media3.ui.PlayerView
|
import androidx.media3.ui.PlayerView
|
||||||
import com.fluxa.app.player.TrailerCue
|
import com.fluxa.app.shared.feature.player.TrailerCue
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.isActive
|
import kotlinx.coroutines.isActive
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
package com.fluxa.app.ui.catalog
|
package com.fluxa.app.ui.catalog
|
||||||
|
|
||||||
import com.fluxa.app.player.TrailerCue
|
import com.fluxa.app.shared.feature.player.TrailerCue
|
||||||
import com.fluxa.app.player.TrailerResolveResult
|
import com.fluxa.app.shared.feature.player.TrailerResolveResult
|
||||||
import com.fluxa.app.player.TrailerResult
|
import com.fluxa.app.shared.feature.player.TrailerResult
|
||||||
import com.fluxa.app.player.TrailerSubtitle
|
import com.fluxa.app.shared.feature.player.TrailerSubtitle
|
||||||
|
|
||||||
import android.util.LruCache
|
import android.util.LruCache
|
||||||
import com.fluxa.app.core.rust.FluxaCoreNative
|
import com.fluxa.app.core.rust.FluxaCoreNative
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
package com.fluxa.app.ui.catalog
|
package com.fluxa.app.ui.catalog
|
||||||
|
|
||||||
import com.fluxa.app.data.remote.Meta
|
import com.fluxa.app.data.remote.Meta
|
||||||
import com.fluxa.app.player.TrailerCue
|
import com.fluxa.app.shared.feature.player.TrailerCue
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
|
|
|
||||||
|
|
@ -165,7 +165,7 @@ class HomeViewModel @Inject constructor(
|
||||||
val billboardWatchlist: StateFlow<Boolean> = billboardState.watchlist
|
val billboardWatchlist: StateFlow<Boolean> = billboardState.watchlist
|
||||||
val billboardNextEpisode: StateFlow<String?> = billboardState.nextEpisode
|
val billboardNextEpisode: StateFlow<String?> = billboardState.nextEpisode
|
||||||
val billboardTrailerUrl: StateFlow<String?> = billboardState.trailerUrl
|
val billboardTrailerUrl: StateFlow<String?> = billboardState.trailerUrl
|
||||||
val billboardTrailerSubtitleCues: StateFlow<List<com.fluxa.app.player.TrailerCue>> = billboardState.trailerSubtitleCues
|
val billboardTrailerSubtitleCues: StateFlow<List<com.fluxa.app.shared.feature.player.TrailerCue>> = billboardState.trailerSubtitleCues
|
||||||
val billboardSeasonPosterUrl: StateFlow<String?> = billboardState.seasonPosterUrl
|
val billboardSeasonPosterUrl: StateFlow<String?> = billboardState.seasonPosterUrl
|
||||||
|
|
||||||
private val _isDirectLoading = MutableStateFlow(false)
|
private val _isDirectLoading = MutableStateFlow(false)
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import com.fluxa.app.player.LibassDebugLog
|
||||||
import com.fluxa.app.player.LibassRenderThread
|
import com.fluxa.app.player.LibassRenderThread
|
||||||
import com.fluxa.app.player.LibassVideoFrame
|
import com.fluxa.app.player.LibassVideoFrame
|
||||||
import com.fluxa.app.player.MediaPlayerController
|
import com.fluxa.app.player.MediaPlayerController
|
||||||
import com.fluxa.app.player.MediaTrack
|
import com.fluxa.app.shared.feature.player.MediaTrack
|
||||||
import com.fluxa.app.player.MkvNativeAssExtractor
|
import com.fluxa.app.player.MkvNativeAssExtractor
|
||||||
import com.fluxa.app.player.NativeAssTrack
|
import com.fluxa.app.player.NativeAssTrack
|
||||||
import com.fluxa.app.player.NativeLibassRenderer
|
import com.fluxa.app.player.NativeLibassRenderer
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ import com.fluxa.app.player.PlayerEngine
|
||||||
import com.fluxa.app.player.PlayerEngineRequest
|
import com.fluxa.app.player.PlayerEngineRequest
|
||||||
import com.fluxa.app.player.TorrentStreamManager
|
import com.fluxa.app.player.TorrentStreamManager
|
||||||
import com.fluxa.app.player.TorrentStreamResult
|
import com.fluxa.app.player.TorrentStreamResult
|
||||||
import com.fluxa.app.player.TorrentStreamStatus
|
import com.fluxa.app.shared.feature.player.TorrentStreamStatus
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ internal fun extractSeasonEpisode(videoId: String?): Pair<Int, Int>? {
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun PlayerUIContent(
|
internal fun PlayerUIContent(
|
||||||
content: PlayerContentUiModel, lang: String, duration: Long, position: Long, bufferedFraction: Float, chapters: List<com.fluxa.app.player.Chapter> = emptyList(), isPlaying: Boolean, isBuffering: Boolean, hasStartedPlaying: Boolean, deviceType: DeviceType,
|
content: PlayerContentUiModel, lang: String, duration: Long, position: Long, bufferedFraction: Float, chapters: List<com.fluxa.app.shared.feature.player.Chapter> = emptyList(), isPlaying: Boolean, isBuffering: Boolean, hasStartedPlaying: Boolean, deviceType: DeviceType,
|
||||||
onPlayPause: () -> Unit, onSeek: (Long) -> Unit, onToggleSubtitles: () -> Unit, onToggleAspect: () -> Unit, onSpeedChange: (Float) -> Unit, playbackSpeed: Float, playPauseFocusRequester: FocusRequester, seekbarFocusRequester: FocusRequester,
|
onPlayPause: () -> Unit, onSeek: (Long) -> Unit, onToggleSubtitles: () -> Unit, onToggleAspect: () -> Unit, onSpeedChange: (Float) -> Unit, playbackSpeed: Float, playPauseFocusRequester: FocusRequester, seekbarFocusRequester: FocusRequester,
|
||||||
isScrubbing: Boolean, scrubPosition: Long, onScrubbingChange: (Boolean, Long) -> Unit, onScrubSeek: (Long) -> Unit = {},
|
isScrubbing: Boolean, scrubPosition: Long, onScrubbingChange: (Boolean, Long) -> Unit, onScrubSeek: (Long) -> Unit = {},
|
||||||
isSwitchingAudioSource: Boolean = false, detailedStatus: String = "", episodeMetaLine: String? = null, streamDetailLine: String? = null, subtitlesEnabled: Boolean = false, technicalInfo: String? = null,
|
isSwitchingAudioSource: Boolean = false, detailedStatus: String = "", episodeMetaLine: String? = null, streamDetailLine: String? = null, subtitlesEnabled: Boolean = false, technicalInfo: String? = null,
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ import com.fluxa.app.player.NativeAssTrack
|
||||||
import com.fluxa.app.player.PlayerEngine
|
import com.fluxa.app.player.PlayerEngine
|
||||||
import com.fluxa.app.player.PlayerEngineRequest
|
import com.fluxa.app.player.PlayerEngineRequest
|
||||||
import com.fluxa.app.player.TorrentStreamManager
|
import com.fluxa.app.player.TorrentStreamManager
|
||||||
import com.fluxa.app.player.TorrentStreamStatus
|
import com.fluxa.app.shared.feature.player.TorrentStreamStatus
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ import com.fluxa.app.data.remote.Meta
|
||||||
import com.fluxa.app.data.remote.Stream
|
import com.fluxa.app.data.remote.Stream
|
||||||
import com.fluxa.app.data.remote.Video
|
import com.fluxa.app.data.remote.Video
|
||||||
import com.fluxa.app.data.repository.TraktIntegration
|
import com.fluxa.app.data.repository.TraktIntegration
|
||||||
import com.fluxa.app.shared.feature.player.withCurrentEpisodeArtwork
|
import com.fluxa.app.data.remote.withCurrentEpisodeArtwork
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
|
|
||||||
|
|
@ -36,16 +36,14 @@ import androidx.media3.ui.PlayerView
|
||||||
import com.fluxa.app.R
|
import com.fluxa.app.R
|
||||||
import com.fluxa.app.data.local.*
|
import com.fluxa.app.data.local.*
|
||||||
import com.fluxa.app.data.local.UserProfile
|
import com.fluxa.app.data.local.UserProfile
|
||||||
import com.fluxa.app.data.remote.IntroTimestamps
|
|
||||||
import com.fluxa.app.data.remote.Stream
|
import com.fluxa.app.data.remote.Stream
|
||||||
import com.fluxa.app.data.remote.Video
|
import com.fluxa.app.shared.feature.player.MediaTrack
|
||||||
import com.fluxa.app.player.MediaTrack
|
|
||||||
import com.fluxa.app.player.ExternalSubtitleTrack
|
import com.fluxa.app.player.ExternalSubtitleTrack
|
||||||
import com.fluxa.app.player.NativeAssTrack
|
import com.fluxa.app.player.NativeAssTrack
|
||||||
import com.fluxa.app.player.MpvAndroidSurfaceView
|
import com.fluxa.app.player.MpvAndroidSurfaceView
|
||||||
import com.fluxa.app.player.MpvEmbeddedPlayer
|
import com.fluxa.app.player.MpvEmbeddedPlayer
|
||||||
import com.fluxa.app.player.PlayerEngine
|
import com.fluxa.app.player.PlayerEngine
|
||||||
import com.fluxa.app.player.TorrentStreamStatus
|
import com.fluxa.app.shared.feature.player.TorrentStreamStatus
|
||||||
import com.fluxa.app.core.rust.FluxaCoreNative
|
import com.fluxa.app.core.rust.FluxaCoreNative
|
||||||
import com.fluxa.app.shared.feature.player.ArtisticLoadingOverlay
|
import com.fluxa.app.shared.feature.player.ArtisticLoadingOverlay
|
||||||
import com.fluxa.app.shared.feature.player.MarkSegmentSidebar
|
import com.fluxa.app.shared.feature.player.MarkSegmentSidebar
|
||||||
|
|
@ -118,7 +116,7 @@ internal fun BoxScope.PlayerPlaybackSurface(
|
||||||
seekBackwardMs: Long,
|
seekBackwardMs: Long,
|
||||||
hasPreviousEpisode: Boolean,
|
hasPreviousEpisode: Boolean,
|
||||||
hasNextEpisode: Boolean,
|
hasNextEpisode: Boolean,
|
||||||
nextEpisode: Video?,
|
nextEpisode: NextEpisodePreviewUiModel?,
|
||||||
onPlayPrevious: () -> Unit,
|
onPlayPrevious: () -> Unit,
|
||||||
onPlayNext: () -> Unit,
|
onPlayNext: () -> Unit,
|
||||||
onCast: () -> Unit,
|
onCast: () -> Unit,
|
||||||
|
|
@ -128,11 +126,11 @@ internal fun BoxScope.PlayerPlaybackSurface(
|
||||||
onClose: () -> Unit,
|
onClose: () -> Unit,
|
||||||
onNextEpisodeCardShown: () -> Unit,
|
onNextEpisodeCardShown: () -> Unit,
|
||||||
timelinePosition: () -> Long,
|
timelinePosition: () -> Long,
|
||||||
skipSegments: List<IntroTimestamps>,
|
skipSegments: List<SkipSegmentUiModel>,
|
||||||
chapters: List<com.fluxa.app.player.Chapter> = emptyList(),
|
chapters: List<com.fluxa.app.shared.feature.player.Chapter> = emptyList(),
|
||||||
dismissedSkipSegments: Set<String>,
|
dismissedSkipSegments: Set<String>,
|
||||||
onSkipSegment: (IntroTimestamps) -> Unit,
|
onSkipSegment: (SkipSegmentUiModel) -> Unit,
|
||||||
onDismissSegment: (IntroTimestamps) -> Unit,
|
onDismissSegment: (SkipSegmentUiModel) -> Unit,
|
||||||
showSegmentSkipFeedback: Boolean,
|
showSegmentSkipFeedback: Boolean,
|
||||||
holdSpeedVisible: Boolean,
|
holdSpeedVisible: Boolean,
|
||||||
showVolumeBar: Boolean,
|
showVolumeBar: Boolean,
|
||||||
|
|
@ -502,7 +500,7 @@ internal fun PlayerSettingsPanel(
|
||||||
)
|
)
|
||||||
} else if (activeSettingsTab == 4) {
|
} else if (activeSettingsTab == 4) {
|
||||||
SourceSidebar(
|
SourceSidebar(
|
||||||
streams = currentStreams,
|
streams = currentStreams.map { it.toSourceUiModel() },
|
||||||
currentUrl = currentUrl.orEmpty(),
|
currentUrl = currentUrl.orEmpty(),
|
||||||
deviceType = deviceType,
|
deviceType = deviceType,
|
||||||
lang = lang,
|
lang = lang,
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,7 @@ import com.fluxa.app.data.remote.Meta
|
||||||
import com.fluxa.app.data.remote.Stream
|
import com.fluxa.app.data.remote.Stream
|
||||||
import com.fluxa.app.player.*
|
import com.fluxa.app.player.*
|
||||||
import com.fluxa.app.player.MediaPlayerController
|
import com.fluxa.app.player.MediaPlayerController
|
||||||
import com.fluxa.app.player.MediaTrack
|
import com.fluxa.app.shared.feature.player.MediaTrack
|
||||||
import com.fluxa.app.shared.feature.player.dismissKey
|
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
|
||||||
|
|
@ -52,12 +52,11 @@ import com.fluxa.app.data.local.*
|
||||||
import com.fluxa.app.data.local.UserProfile
|
import com.fluxa.app.data.local.UserProfile
|
||||||
import com.fluxa.app.data.remote.Meta
|
import com.fluxa.app.data.remote.Meta
|
||||||
import com.fluxa.app.data.repository.IntroDbSubmitResult
|
import com.fluxa.app.data.repository.IntroDbSubmitResult
|
||||||
import com.fluxa.app.player.MediaTrack
|
import com.fluxa.app.shared.feature.player.MediaTrack
|
||||||
import com.fluxa.app.player.MpvEmbeddedPlayer
|
import com.fluxa.app.player.MpvEmbeddedPlayer
|
||||||
import com.fluxa.app.player.PlayerEngine
|
import com.fluxa.app.player.PlayerEngine
|
||||||
import com.fluxa.app.player.TorrentStreamStatus
|
import com.fluxa.app.shared.feature.player.TorrentStreamStatus
|
||||||
import com.fluxa.app.shared.feature.player.PlayerTopIconButton
|
import com.fluxa.app.shared.feature.player.PlayerTopIconButton
|
||||||
import com.fluxa.app.shared.feature.player.dismissKey
|
|
||||||
import com.fluxa.app.shared.feature.player.playerInputControls
|
import com.fluxa.app.shared.feature.player.playerInputControls
|
||||||
import com.fluxa.app.shared.feature.player.playerText
|
import com.fluxa.app.shared.feature.player.playerText
|
||||||
|
|
||||||
|
|
@ -288,7 +287,7 @@ internal fun PlayerScreenContent(
|
||||||
seekBackwardMs = seekBackwardMs,
|
seekBackwardMs = seekBackwardMs,
|
||||||
hasPreviousEpisode = state.previousEpisodePending != null,
|
hasPreviousEpisode = state.previousEpisodePending != null,
|
||||||
hasNextEpisode = state.nextEpisodePending != null,
|
hasNextEpisode = state.nextEpisodePending != null,
|
||||||
nextEpisode = state.nextEpisodePending,
|
nextEpisode = state.nextEpisodePending?.toNextEpisodePreviewUiModel(),
|
||||||
onPlayPrevious = playPrevious,
|
onPlayPrevious = playPrevious,
|
||||||
onPlayNext = playNext,
|
onPlayNext = playNext,
|
||||||
onCast = smartCast,
|
onCast = smartCast,
|
||||||
|
|
@ -310,11 +309,11 @@ internal fun PlayerScreenContent(
|
||||||
state.nextEpisodePending?.let { viewModel.onNextEpisodeCardShown(meta, it.id, activeProfile) }
|
state.nextEpisodePending?.let { viewModel.onNextEpisodeCardShown(meta, it.id, activeProfile) }
|
||||||
},
|
},
|
||||||
timelinePosition = { if (useMpvBackend) state.engine.timeline.position else state.timelinePosition },
|
timelinePosition = { if (useMpvBackend) state.engine.timeline.position else state.timelinePosition },
|
||||||
skipSegments = state.skipSegments,
|
skipSegments = state.skipSegments.map { it.toSkipSegmentUiModel() },
|
||||||
chapters = state.chapters,
|
chapters = state.chapters,
|
||||||
dismissedSkipSegments = state.dismissedSkipSegments,
|
dismissedSkipSegments = state.dismissedSkipSegments,
|
||||||
onSkipSegment = { segment ->
|
onSkipSegment = { segment ->
|
||||||
if (segment.type == "outro") playNext() else seekSafely(segment.endTime)
|
if (segment.type == "outro") playNext() else seekSafely(segment.endMs)
|
||||||
state.dismissedSkipSegments = state.dismissedSkipSegments + segment.dismissKey()
|
state.dismissedSkipSegments = state.dismissedSkipSegments + segment.dismissKey()
|
||||||
state.introAutoSkipped = true
|
state.introAutoSkipped = true
|
||||||
state.segmentSkipFeedbackVersion += 1
|
state.segmentSkipFeedbackVersion += 1
|
||||||
|
|
|
||||||
|
|
@ -79,7 +79,7 @@ import coil3.compose.AsyncImage
|
||||||
import com.fluxa.app.R
|
import com.fluxa.app.R
|
||||||
import com.fluxa.app.player.*
|
import com.fluxa.app.player.*
|
||||||
import com.fluxa.app.player.MediaPlayerController
|
import com.fluxa.app.player.MediaPlayerController
|
||||||
import com.fluxa.app.player.MediaTrack
|
import com.fluxa.app.shared.feature.player.MediaTrack
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.awaitAll
|
import kotlinx.coroutines.awaitAll
|
||||||
|
|
|
||||||
|
|
@ -78,7 +78,7 @@ internal class PlayerScreenState(
|
||||||
var currentEpisodeMetaLine by mutableStateOf<String?>(null)
|
var currentEpisodeMetaLine by mutableStateOf<String?>(null)
|
||||||
var currentEpisodeArtwork by mutableStateOf<String?>(null)
|
var currentEpisodeArtwork by mutableStateOf<String?>(null)
|
||||||
var skipSegments by mutableStateOf<List<IntroTimestamps>>(emptyList())
|
var skipSegments by mutableStateOf<List<IntroTimestamps>>(emptyList())
|
||||||
var chapters by mutableStateOf<List<com.fluxa.app.player.Chapter>>(emptyList())
|
var chapters by mutableStateOf<List<com.fluxa.app.shared.feature.player.Chapter>>(emptyList())
|
||||||
var dismissedSkipSegments by mutableStateOf<Set<String>>(emptySet())
|
var dismissedSkipSegments by mutableStateOf<Set<String>>(emptySet())
|
||||||
var introAutoSkipped by mutableStateOf(false)
|
var introAutoSkipped by mutableStateOf(false)
|
||||||
var preferredBingeGroupForNextEpisode by mutableStateOf<String?>(null)
|
var preferredBingeGroupForNextEpisode by mutableStateOf<String?>(null)
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ import com.fluxa.app.player.PlayerEngine
|
||||||
import com.fluxa.app.player.PlayerEngineRequest
|
import com.fluxa.app.player.PlayerEngineRequest
|
||||||
import com.fluxa.app.player.TorrentStreamManager
|
import com.fluxa.app.player.TorrentStreamManager
|
||||||
import com.fluxa.app.player.TorrentStreamResult
|
import com.fluxa.app.player.TorrentStreamResult
|
||||||
import com.fluxa.app.player.TorrentStreamStatus
|
import com.fluxa.app.shared.feature.player.TorrentStreamStatus
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,9 @@ import com.fluxa.app.data.local.*
|
||||||
import com.fluxa.app.data.local.UserProfile
|
import com.fluxa.app.data.local.UserProfile
|
||||||
import com.fluxa.app.data.remote.Meta
|
import com.fluxa.app.data.remote.Meta
|
||||||
import com.fluxa.app.data.remote.Stream
|
import com.fluxa.app.data.remote.Stream
|
||||||
import com.fluxa.app.player.MediaTrack
|
import com.fluxa.app.shared.feature.player.MediaTrack
|
||||||
import com.fluxa.app.player.PlayerEngine
|
import com.fluxa.app.player.PlayerEngine
|
||||||
import com.fluxa.app.shared.feature.player.withCurrentEpisodeArtwork
|
import com.fluxa.app.data.remote.withCurrentEpisodeArtwork
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import com.fluxa.app.domain.discovery.*
|
||||||
|
|
||||||
import com.fluxa.app.core.rust.FluxaCoreNative
|
import com.fluxa.app.core.rust.FluxaCoreNative
|
||||||
import com.fluxa.app.core.rust.models.SubtitleTrackRef
|
import com.fluxa.app.core.rust.models.SubtitleTrackRef
|
||||||
import com.fluxa.app.player.MediaTrack
|
import com.fluxa.app.shared.feature.player.MediaTrack
|
||||||
import com.fluxa.app.player.resolveAudioLanguagePreference
|
import com.fluxa.app.player.resolveAudioLanguagePreference
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.fluxa.app.player
|
package com.fluxa.app.player
|
||||||
|
|
||||||
|
import com.fluxa.app.shared.feature.player.Chapter
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import okhttp3.mockwebserver.MockResponse
|
import okhttp3.mockwebserver.MockResponse
|
||||||
import okhttp3.mockwebserver.MockWebServer
|
import okhttp3.mockwebserver.MockWebServer
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import com.fluxa.app.data.repository.*
|
||||||
import com.fluxa.app.domain.discovery.*
|
import com.fluxa.app.domain.discovery.*
|
||||||
|
|
||||||
import androidx.media3.common.C
|
import androidx.media3.common.C
|
||||||
import com.fluxa.app.player.MediaTrack
|
import com.fluxa.app.shared.feature.player.MediaTrack
|
||||||
import org.junit.Assert.assertEquals
|
import org.junit.Assert.assertEquals
|
||||||
import org.junit.Assert.assertSame
|
import org.junit.Assert.assertSame
|
||||||
import org.junit.Assert.assertTrue
|
import org.junit.Assert.assertTrue
|
||||||
|
|
|
||||||
|
|
@ -475,3 +475,9 @@ object MetaDetailSerializer : KSerializer<MetaDetail> {
|
||||||
return metaDetailFromJson(json.decodeJsonElement())
|
return metaDetailFromJson(json.decodeJsonElement())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Meta.withCurrentEpisodeArtwork(artwork: String?): Meta {
|
||||||
|
val episodeArtwork = artwork?.takeIf { it.isNotBlank() } ?: return this
|
||||||
|
if (type != "series") return this
|
||||||
|
return copy(continueWatchingPoster = episodeArtwork, continueWatchingBackground = episodeArtwork)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package com.fluxa.app.ui.catalog
|
||||||
|
|
||||||
|
import com.fluxa.app.data.remote.IntroTimestamps
|
||||||
|
import com.fluxa.app.data.remote.Video
|
||||||
|
|
||||||
|
data class SkipSegmentUiModel(
|
||||||
|
val type: String,
|
||||||
|
val startMs: Long,
|
||||||
|
val endMs: Long
|
||||||
|
) {
|
||||||
|
fun dismissKey(): String = "$type:$startMs:$endMs"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun IntroTimestamps.toSkipSegmentUiModel(): SkipSegmentUiModel = SkipSegmentUiModel(
|
||||||
|
type = type,
|
||||||
|
startMs = startTime,
|
||||||
|
endMs = endTime
|
||||||
|
)
|
||||||
|
|
||||||
|
fun IntroTimestamps.dismissKey(): String {
|
||||||
|
return "$type:$startTime:$endTime"
|
||||||
|
}
|
||||||
|
|
||||||
|
data class NextEpisodePreviewUiModel(
|
||||||
|
val id: String,
|
||||||
|
val thumbnail: String?,
|
||||||
|
val season: Int?,
|
||||||
|
val number: Int?,
|
||||||
|
val name: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Video.toNextEpisodePreviewUiModel(): NextEpisodePreviewUiModel = NextEpisodePreviewUiModel(
|
||||||
|
id = id,
|
||||||
|
thumbnail = thumbnail,
|
||||||
|
season = season,
|
||||||
|
number = number,
|
||||||
|
name = name
|
||||||
|
)
|
||||||
|
|
@ -2,6 +2,18 @@ package com.fluxa.app.ui.catalog
|
||||||
|
|
||||||
import com.fluxa.app.data.remote.Stream
|
import com.fluxa.app.data.remote.Stream
|
||||||
|
|
||||||
|
data class StreamSourceUiModel(
|
||||||
|
val playableUrl: String?,
|
||||||
|
val header: String,
|
||||||
|
val body: String?
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Stream.toSourceUiModel(): StreamSourceUiModel = StreamSourceUiModel(
|
||||||
|
playableUrl = playableUrl,
|
||||||
|
header = streamSourceHeader(),
|
||||||
|
body = streamRawBody()
|
||||||
|
)
|
||||||
|
|
||||||
fun Stream.streamSourceHeader(): String {
|
fun Stream.streamSourceHeader(): String {
|
||||||
return name
|
return name
|
||||||
?.takeIf { it.isNotBlank() }
|
?.takeIf { it.isNotBlank() }
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package com.fluxa.app.player
|
package com.fluxa.app.player
|
||||||
|
|
||||||
|
import com.fluxa.app.shared.feature.player.MediaTrack
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import androidx.media3.common.*
|
import androidx.media3.common.*
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package com.fluxa.app.player
|
package com.fluxa.app.player
|
||||||
|
|
||||||
|
import com.fluxa.app.shared.feature.player.Chapter
|
||||||
|
|
||||||
import com.fluxa.app.core.rust.FluxaStreamingNative
|
import com.fluxa.app.core.rust.FluxaStreamingNative
|
||||||
import com.google.gson.Gson
|
import com.google.gson.Gson
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
package com.fluxa.app.player
|
package com.fluxa.app.player
|
||||||
|
|
||||||
|
import com.fluxa.app.shared.feature.player.Chapter
|
||||||
|
import com.fluxa.app.shared.feature.player.MediaTrack
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.media.AudioFormat
|
import android.media.AudioFormat
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,9 @@
|
||||||
|
|
||||||
package com.fluxa.app.player
|
package com.fluxa.app.player
|
||||||
|
|
||||||
|
import com.fluxa.app.shared.feature.player.Chapter
|
||||||
|
import com.fluxa.app.shared.feature.player.MediaTrack
|
||||||
|
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.exoplayer.SeekParameters
|
import androidx.media3.exoplayer.SeekParameters
|
||||||
import androidx.media3.common.C
|
import androidx.media3.common.C
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package com.fluxa.app.player
|
package com.fluxa.app.player
|
||||||
|
|
||||||
|
import com.fluxa.app.shared.feature.player.TorrentStreamStatus
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
import android.net.NetworkCapabilities
|
import android.net.NetworkCapabilities
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package com.fluxa.app.player
|
package com.fluxa.app.player
|
||||||
|
|
||||||
|
import com.fluxa.app.shared.feature.player.Chapter
|
||||||
|
|
||||||
import com.fluxa.app.data.remote.IntroTimestamps
|
import com.fluxa.app.data.remote.IntroTimestamps
|
||||||
|
|
||||||
private fun classifyChapterSkipType(title: String): String? {
|
private fun classifyChapterSkipType(title: String): String? {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.fluxa.app.player
|
package com.fluxa.app.shared.feature.player
|
||||||
|
|
||||||
data class Chapter(
|
data class Chapter(
|
||||||
val title: String,
|
val title: String,
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.fluxa.app.player
|
package com.fluxa.app.shared.feature.player
|
||||||
|
|
||||||
data class MediaTrack(
|
data class MediaTrack(
|
||||||
val id: String,
|
val id: String,
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.fluxa.app.player
|
package com.fluxa.app.shared.feature.player
|
||||||
|
|
||||||
data class TorrentStreamStatus(
|
data class TorrentStreamStatus(
|
||||||
val bufferProgress: Int = 0,
|
val bufferProgress: Int = 0,
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
package com.fluxa.app.player
|
package com.fluxa.app.shared.feature.player
|
||||||
|
|
||||||
data class TrailerSubtitle(
|
data class TrailerSubtitle(
|
||||||
val languageTag: String,
|
val languageTag: String,
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package com.fluxa.app.player
|
package com.fluxa.app.player
|
||||||
|
|
||||||
|
import com.fluxa.app.shared.feature.player.TrailerPolicy
|
||||||
|
|
||||||
import kotlin.test.Test
|
import kotlin.test.Test
|
||||||
import kotlin.test.assertEquals
|
import kotlin.test.assertEquals
|
||||||
import kotlin.test.assertNull
|
import kotlin.test.assertNull
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,7 @@ import com.fluxa.app.shared.feature.search.SearchUiState
|
||||||
import com.fluxa.app.shared.feature.player.PlayerControlsSurface
|
import com.fluxa.app.shared.feature.player.PlayerControlsSurface
|
||||||
import com.fluxa.app.shared.feature.player.PlayerRenderAction
|
import com.fluxa.app.shared.feature.player.PlayerRenderAction
|
||||||
import com.fluxa.app.shared.feature.player.PlayerRenderState
|
import com.fluxa.app.shared.feature.player.PlayerRenderState
|
||||||
import com.fluxa.app.player.TrailerCue
|
import com.fluxa.app.shared.feature.player.TrailerCue
|
||||||
import com.fluxa.app.ui.catalog.CatalogCard
|
import com.fluxa.app.ui.catalog.CatalogCard
|
||||||
import com.fluxa.app.ui.catalog.FluxaColors
|
import com.fluxa.app.ui.catalog.FluxaColors
|
||||||
import com.fluxa.app.ui.catalog.PosterActionSheet
|
import com.fluxa.app.ui.catalog.PosterActionSheet
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,6 @@ package com.fluxa.app.shared
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.staticCompositionLocalOf
|
import androidx.compose.runtime.staticCompositionLocalOf
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import com.fluxa.app.player.TrailerCue
|
import com.fluxa.app.shared.feature.player.TrailerCue
|
||||||
|
|
||||||
val LocalHeroTrailerSurface = staticCompositionLocalOf<(@Composable (String, List<TrailerCue>, (String) -> Unit, Modifier) -> Unit)?> { null }
|
val LocalHeroTrailerSurface = staticCompositionLocalOf<(@Composable (String, List<TrailerCue>, (String) -> Unit, Modifier) -> Unit)?> { null }
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
package com.fluxa.app.shared.feature.catalog
|
package com.fluxa.app.shared.feature.catalog
|
||||||
|
|
||||||
import com.fluxa.app.player.TrailerCue
|
import com.fluxa.app.shared.feature.player.TrailerCue
|
||||||
import com.fluxa.app.ui.catalog.CatalogCardUiModel
|
import com.fluxa.app.ui.catalog.CatalogCardUiModel
|
||||||
import kotlinx.coroutines.flow.Flow
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
package com.fluxa.app.shared.feature.player
|
package com.fluxa.app.shared.feature.player
|
||||||
|
|
||||||
import com.fluxa.app.common.AppStrings
|
import com.fluxa.app.common.AppStrings
|
||||||
import com.fluxa.app.player.Chapter
|
|
||||||
import com.fluxa.app.ui.catalog.DeviceType
|
import com.fluxa.app.ui.catalog.DeviceType
|
||||||
import com.fluxa.app.ui.catalog.FluxaColors
|
import com.fluxa.app.ui.catalog.FluxaColors
|
||||||
import com.fluxa.app.ui.catalog.FluxaDimensions
|
import com.fluxa.app.ui.catalog.FluxaDimensions
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
package com.fluxa.app.shared.feature.player
|
package com.fluxa.app.shared.feature.player
|
||||||
|
|
||||||
import com.fluxa.app.common.AppStrings
|
import com.fluxa.app.common.AppStrings
|
||||||
import com.fluxa.app.data.remote.Video
|
|
||||||
import com.fluxa.app.player.TorrentStreamStatus
|
|
||||||
import com.fluxa.app.ui.catalog.BufferSnapshot
|
import com.fluxa.app.ui.catalog.BufferSnapshot
|
||||||
import com.fluxa.app.ui.catalog.DeviceType
|
import com.fluxa.app.ui.catalog.DeviceType
|
||||||
import com.fluxa.app.ui.catalog.FluxaColors
|
import com.fluxa.app.ui.catalog.FluxaColors
|
||||||
import com.fluxa.app.ui.catalog.FluxaDimensions
|
import com.fluxa.app.ui.catalog.FluxaDimensions
|
||||||
import com.fluxa.app.ui.catalog.FluxaIcons
|
import com.fluxa.app.ui.catalog.FluxaIcons
|
||||||
|
import com.fluxa.app.ui.catalog.NextEpisodePreviewUiModel
|
||||||
import com.fluxa.app.ui.catalog.PlaybackSnapshot
|
import com.fluxa.app.ui.catalog.PlaybackSnapshot
|
||||||
|
|
||||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||||
|
|
@ -66,7 +65,7 @@ fun playerStatusText(lang: String?, value: String): String {
|
||||||
fun SkipSegmentCard(
|
fun SkipSegmentCard(
|
||||||
deviceType: DeviceType,
|
deviceType: DeviceType,
|
||||||
type: String,
|
type: String,
|
||||||
nextEpisode: Video? = null,
|
nextEpisode: NextEpisodePreviewUiModel? = null,
|
||||||
lang: String? = "en",
|
lang: String? = "en",
|
||||||
autoAdvanceSeconds: Int? = null,
|
autoAdvanceSeconds: Int? = null,
|
||||||
onSkip: () -> Unit,
|
onSkip: () -> Unit,
|
||||||
|
|
@ -126,7 +125,7 @@ fun SkipSegmentCard(
|
||||||
@Composable
|
@Composable
|
||||||
private fun NextEpisodeSkipCard(
|
private fun NextEpisodeSkipCard(
|
||||||
deviceType: DeviceType,
|
deviceType: DeviceType,
|
||||||
episode: Video,
|
episode: NextEpisodePreviewUiModel,
|
||||||
lang: String?,
|
lang: String?,
|
||||||
autoAdvanceSeconds: Int? = null,
|
autoAdvanceSeconds: Int? = null,
|
||||||
onSkip: () -> Unit
|
onSkip: () -> Unit
|
||||||
|
|
@ -203,7 +202,7 @@ private fun NextEpisodeSkipCard(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun nextEpisodeSubtitle(lang: String?, episode: Video): String {
|
private fun nextEpisodeSubtitle(lang: String?, episode: NextEpisodePreviewUiModel): String {
|
||||||
val season = episode.season ?: 1
|
val season = episode.season ?: 1
|
||||||
val number = episode.number ?: 0
|
val number = episode.number ?: 0
|
||||||
val name = episode.name.orEmpty().trim()
|
val name = episode.name.orEmpty().trim()
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
package com.fluxa.app.shared.feature.player
|
package com.fluxa.app.shared.feature.player
|
||||||
|
|
||||||
import com.fluxa.app.data.remote.IntroTimestamps
|
|
||||||
import com.fluxa.app.data.remote.Video
|
|
||||||
import com.fluxa.app.ui.catalog.DeviceType
|
import com.fluxa.app.ui.catalog.DeviceType
|
||||||
import com.fluxa.app.ui.catalog.FluxaDimensions
|
import com.fluxa.app.ui.catalog.FluxaDimensions
|
||||||
import com.fluxa.app.ui.catalog.FluxaIcons
|
import com.fluxa.app.ui.catalog.FluxaIcons
|
||||||
|
import com.fluxa.app.ui.catalog.NextEpisodePreviewUiModel
|
||||||
|
import com.fluxa.app.ui.catalog.SkipSegmentUiModel
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||||
|
|
@ -51,26 +51,26 @@ import androidx.compose.ui.zIndex
|
||||||
@Composable
|
@Composable
|
||||||
fun PlayerSkipSegmentOverlay(
|
fun PlayerSkipSegmentOverlay(
|
||||||
currentPosition: Long,
|
currentPosition: Long,
|
||||||
skipSegments: List<IntroTimestamps>,
|
skipSegments: List<SkipSegmentUiModel>,
|
||||||
dismissedSkipSegments: Set<String>,
|
dismissedSkipSegments: Set<String>,
|
||||||
hasStartedPlaying: Boolean,
|
hasStartedPlaying: Boolean,
|
||||||
showControls: Boolean,
|
showControls: Boolean,
|
||||||
deviceType: DeviceType,
|
deviceType: DeviceType,
|
||||||
nextEpisode: Video?,
|
nextEpisode: NextEpisodePreviewUiModel?,
|
||||||
nextEpisodeThresholdReached: Boolean,
|
nextEpisodeThresholdReached: Boolean,
|
||||||
autoSkipSegments: Boolean,
|
autoSkipSegments: Boolean,
|
||||||
autoPlayCountdownSeconds: Int? = null,
|
autoPlayCountdownSeconds: Int? = null,
|
||||||
lang: String,
|
lang: String,
|
||||||
onSkipSegment: (IntroTimestamps) -> Unit,
|
onSkipSegment: (SkipSegmentUiModel) -> Unit,
|
||||||
onPlayNextEpisode: () -> Unit,
|
onPlayNextEpisode: () -> Unit,
|
||||||
onDismissSegment: (IntroTimestamps) -> Unit,
|
onDismissSegment: (SkipSegmentUiModel) -> Unit,
|
||||||
onNextEpisodeCardShown: () -> Unit,
|
onNextEpisodeCardShown: () -> Unit,
|
||||||
modifier: Modifier = Modifier
|
modifier: Modifier = Modifier
|
||||||
) {
|
) {
|
||||||
val activeSkipSegment = remember(currentPosition, skipSegments, dismissedSkipSegments, hasStartedPlaying, autoSkipSegments) {
|
val activeSkipSegment = remember(currentPosition, skipSegments, dismissedSkipSegments, hasStartedPlaying, autoSkipSegments) {
|
||||||
if (!hasStartedPlaying || autoSkipSegments) null
|
if (!hasStartedPlaying || autoSkipSegments) null
|
||||||
else skipSegments.find { segment ->
|
else skipSegments.find { segment ->
|
||||||
currentPosition in segment.startTime until segment.endTime &&
|
currentPosition in segment.startMs until segment.endMs &&
|
||||||
segment.dismissKey() !in dismissedSkipSegments
|
segment.dismissKey() !in dismissedSkipSegments
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -307,7 +307,3 @@ fun BoxScope.PlayerTransientOverlays(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun IntroTimestamps.dismissKey(): String {
|
|
||||||
return "$type:$startTime:$endTime"
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
package com.fluxa.app.shared.feature.player
|
package com.fluxa.app.shared.feature.player
|
||||||
|
|
||||||
import com.fluxa.app.common.AppStrings
|
import com.fluxa.app.common.AppStrings
|
||||||
import com.fluxa.app.player.MediaTrack
|
|
||||||
import com.fluxa.app.ui.catalog.DeviceType
|
import com.fluxa.app.ui.catalog.DeviceType
|
||||||
import com.fluxa.app.ui.catalog.FluxaIcons
|
import com.fluxa.app.ui.catalog.FluxaIcons
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,10 @@
|
||||||
package com.fluxa.app.shared.feature.player
|
package com.fluxa.app.shared.feature.player
|
||||||
|
|
||||||
import com.fluxa.app.common.AppStrings
|
import com.fluxa.app.common.AppStrings
|
||||||
import com.fluxa.app.data.remote.Meta
|
|
||||||
import com.fluxa.app.data.remote.Stream
|
|
||||||
import com.fluxa.app.ui.catalog.DeviceType
|
import com.fluxa.app.ui.catalog.DeviceType
|
||||||
import com.fluxa.app.ui.catalog.FluxaIcons
|
import com.fluxa.app.ui.catalog.FluxaIcons
|
||||||
import com.fluxa.app.ui.catalog.LocalDeviceType
|
import com.fluxa.app.ui.catalog.LocalDeviceType
|
||||||
import com.fluxa.app.ui.catalog.streamRawBody
|
import com.fluxa.app.ui.catalog.StreamSourceUiModel
|
||||||
import com.fluxa.app.ui.catalog.streamSourceHeader
|
|
||||||
|
|
||||||
import androidx.compose.animation.animateContentSize
|
import androidx.compose.animation.animateContentSize
|
||||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||||
|
|
@ -50,7 +47,7 @@ import androidx.compose.ui.zIndex
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SourceSidebar(streams: List<Stream>, currentUrl: String, deviceType: DeviceType, lang: String = "en", onSelect: (String) -> Unit, onClose: (() -> Unit)? = null) {
|
fun SourceSidebar(streams: List<StreamSourceUiModel>, currentUrl: String, deviceType: DeviceType, lang: String = "en", onSelect: (String) -> Unit, onClose: (() -> Unit)? = null) {
|
||||||
PlayerSidebarShell(
|
PlayerSidebarShell(
|
||||||
title = AppStrings.t(lang, "player.source_selection_title"),
|
title = AppStrings.t(lang, "player.source_selection_title"),
|
||||||
deviceType = deviceType,
|
deviceType = deviceType,
|
||||||
|
|
@ -58,14 +55,14 @@ fun SourceSidebar(streams: List<Stream>, currentUrl: String, deviceType: DeviceT
|
||||||
cardWidth = 420.dp
|
cardWidth = 420.dp
|
||||||
) {
|
) {
|
||||||
LazyColumn(verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
LazyColumn(verticalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||||
items(streams, key = { it.playableUrl ?: (it.title.orEmpty() + it.name.orEmpty()) }) { stream ->
|
items(streams, key = { it.playableUrl ?: it.header }) { stream ->
|
||||||
val playableUrl = stream.playableUrl
|
val playableUrl = stream.playableUrl
|
||||||
TrackItem(
|
TrackItem(
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItem(),
|
||||||
title = stream.streamSourceHeader(),
|
title = stream.header,
|
||||||
isSelected = stream.playableUrl == currentUrl,
|
isSelected = stream.playableUrl == currentUrl,
|
||||||
onClick = { playableUrl?.let(onSelect) },
|
onClick = { playableUrl?.let(onSelect) },
|
||||||
subtitle = stream.streamRawBody(),
|
subtitle = stream.body,
|
||||||
deviceType = deviceType,
|
deviceType = deviceType,
|
||||||
leadingIcon = FluxaIcons.PlayArrow
|
leadingIcon = FluxaIcons.PlayArrow
|
||||||
)
|
)
|
||||||
|
|
@ -314,9 +311,3 @@ fun TrackItem(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Meta.withCurrentEpisodeArtwork(artwork: String?): Meta {
|
|
||||||
val episodeArtwork = artwork?.takeIf { it.isNotBlank() } ?: return this
|
|
||||||
if (type != "series") return this
|
|
||||||
return copy(continueWatchingPoster = episodeArtwork, continueWatchingBackground = episodeArtwork)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,6 @@
|
||||||
package com.fluxa.app.shared.feature.player
|
package com.fluxa.app.shared.feature.player
|
||||||
|
|
||||||
import com.fluxa.app.common.AppStrings
|
import com.fluxa.app.common.AppStrings
|
||||||
import com.fluxa.app.data.local.UserProfile
|
|
||||||
import com.fluxa.app.player.MediaTrack
|
|
||||||
import com.fluxa.app.ui.catalog.DeviceType
|
import com.fluxa.app.ui.catalog.DeviceType
|
||||||
import com.fluxa.app.ui.catalog.FluxaColors
|
import com.fluxa.app.ui.catalog.FluxaColors
|
||||||
import com.fluxa.app.ui.catalog.FluxaIcons
|
import com.fluxa.app.ui.catalog.FluxaIcons
|
||||||
|
|
@ -55,33 +53,6 @@ fun TrackSidebar(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
|
||||||
fun QuickSettingsSidebar(profile: UserProfile?, onUpdateProfile: (UserProfile) -> Unit, currentOffset: Long, onOffsetChange: (Long) -> Unit, deviceType: DeviceType, lang: String = "en", onClose: () -> Unit) {
|
|
||||||
PlayerSidebarShell(
|
|
||||||
title = AppStrings.t(lang, "player.quick_settings_title"),
|
|
||||||
deviceType = deviceType,
|
|
||||||
onClose = onClose
|
|
||||||
) {
|
|
||||||
Text(AppStrings.t(lang, "player.subtitle_sync_title"), color = Color.White.copy(alpha = 0.62f), fontSize = 12.sp, fontWeight = FontWeight.Bold, modifier = Modifier.padding(bottom = 12.dp))
|
|
||||||
Box(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth()
|
|
||||||
.clip(RoundedCornerShape(22.dp))
|
|
||||||
.background(Color.White.copy(alpha = 0.04f))
|
|
||||||
.border(1.dp, Color.White.copy(alpha = 0.08f), RoundedCornerShape(22.dp))
|
|
||||||
.padding(18.dp)
|
|
||||||
) {
|
|
||||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(12.dp)) {
|
|
||||||
SeekIconButton(FluxaIcons.Remove, deviceType) { onOffsetChange(currentOffset - 500) }
|
|
||||||
Text(text = "${if (currentOffset >= 0) "+" else ""}${currentOffset / 1000.0}s", color = Color.White, fontSize = 18.sp, fontWeight = FontWeight.Black, modifier = Modifier.weight(1f), textAlign = TextAlign.Center)
|
|
||||||
SeekIconButton(FluxaIcons.Add, deviceType) { onOffsetChange(currentOffset + 500) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Spacer(Modifier.height(8.dp))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PlayerPremiumToggle(title: String, desc: String, isEnabled: Boolean, onToggle: () -> Unit) {
|
fun PlayerPremiumToggle(title: String, desc: String, isEnabled: Boolean, onToggle: () -> Unit) {
|
||||||
Box(modifier = Modifier.fillMaxWidth().height(82.dp).clip(RoundedCornerShape(22.dp)).background(Color.White.copy(alpha = 0.05f)).border(1.dp, Color.White.copy(alpha = 0.08f), RoundedCornerShape(22.dp)).clickable { onToggle() }.padding(horizontal = 20.dp), contentAlignment = Alignment.CenterStart) {
|
Box(modifier = Modifier.fillMaxWidth().height(82.dp).clip(RoundedCornerShape(22.dp)).background(Color.White.copy(alpha = 0.05f)).border(1.dp, Color.White.copy(alpha = 0.08f), RoundedCornerShape(22.dp)).clickable { onToggle() }.padding(horizontal = 20.dp), contentAlignment = Alignment.CenterStart) {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue