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:
KhooLy 2026-07-20 15:37:30 +03:00
parent 5cb1bb75db
commit d5f3819ebe
43 changed files with 131 additions and 107 deletions

View file

@ -81,7 +81,7 @@ class AndroidCatalogHomeDataSource(
val items: List<com.fluxa.app.data.remote.Meta>,
val logoUrl: 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(
@ -109,7 +109,7 @@ class AndroidCatalogHomeDataSource(
billboardPool: List<com.fluxa.app.data.remote.Meta>,
billboardLogo: String?,
billboardTrailerUrl: String?,
billboardTrailerSubtitleCues: List<com.fluxa.app.player.TrailerCue>,
billboardTrailerSubtitleCues: List<com.fluxa.app.shared.feature.player.TrailerCue>,
filter: String
): BillboardResolution {
val filteredPool = billboardPool.filter { it.matchesFilter(filter) }

View file

@ -14,7 +14,7 @@ import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.ui.AspectRatioFrameLayout
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.isActive

View file

@ -1,9 +1,9 @@
package com.fluxa.app.ui.catalog
import com.fluxa.app.player.TrailerCue
import com.fluxa.app.player.TrailerResolveResult
import com.fluxa.app.player.TrailerResult
import com.fluxa.app.player.TrailerSubtitle
import com.fluxa.app.shared.feature.player.TrailerCue
import com.fluxa.app.shared.feature.player.TrailerResolveResult
import com.fluxa.app.shared.feature.player.TrailerResult
import com.fluxa.app.shared.feature.player.TrailerSubtitle
import android.util.LruCache
import com.fluxa.app.core.rust.FluxaCoreNative

View file

@ -1,7 +1,7 @@
package com.fluxa.app.ui.catalog
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.StateFlow
import kotlinx.coroutines.flow.asStateFlow

View file

@ -165,7 +165,7 @@ class HomeViewModel @Inject constructor(
val billboardWatchlist: StateFlow<Boolean> = billboardState.watchlist
val billboardNextEpisode: StateFlow<String?> = billboardState.nextEpisode
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
private val _isDirectLoading = MutableStateFlow(false)

View file

@ -19,7 +19,7 @@ import com.fluxa.app.player.LibassDebugLog
import com.fluxa.app.player.LibassRenderThread
import com.fluxa.app.player.LibassVideoFrame
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.NativeAssTrack
import com.fluxa.app.player.NativeLibassRenderer

View file

@ -26,7 +26,7 @@ import com.fluxa.app.player.PlayerEngine
import com.fluxa.app.player.PlayerEngineRequest
import com.fluxa.app.player.TorrentStreamManager
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.Dispatchers
import kotlinx.coroutines.delay

View file

@ -29,7 +29,7 @@ internal fun extractSeasonEpisode(videoId: String?): Pair<Int, Int>? {
@Composable
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,
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,

View file

@ -27,7 +27,7 @@ import com.fluxa.app.player.NativeAssTrack
import com.fluxa.app.player.PlayerEngine
import com.fluxa.app.player.PlayerEngineRequest
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.delay
import kotlinx.coroutines.launch

View file

@ -17,7 +17,7 @@ import com.fluxa.app.data.remote.Meta
import com.fluxa.app.data.remote.Stream
import com.fluxa.app.data.remote.Video
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.delay
import kotlinx.coroutines.launch

View file

@ -36,16 +36,14 @@ import androidx.media3.ui.PlayerView
import com.fluxa.app.R
import com.fluxa.app.data.local.*
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.Video
import com.fluxa.app.player.MediaTrack
import com.fluxa.app.shared.feature.player.MediaTrack
import com.fluxa.app.player.ExternalSubtitleTrack
import com.fluxa.app.player.NativeAssTrack
import com.fluxa.app.player.MpvAndroidSurfaceView
import com.fluxa.app.player.MpvEmbeddedPlayer
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.shared.feature.player.ArtisticLoadingOverlay
import com.fluxa.app.shared.feature.player.MarkSegmentSidebar
@ -118,7 +116,7 @@ internal fun BoxScope.PlayerPlaybackSurface(
seekBackwardMs: Long,
hasPreviousEpisode: Boolean,
hasNextEpisode: Boolean,
nextEpisode: Video?,
nextEpisode: NextEpisodePreviewUiModel?,
onPlayPrevious: () -> Unit,
onPlayNext: () -> Unit,
onCast: () -> Unit,
@ -128,11 +126,11 @@ internal fun BoxScope.PlayerPlaybackSurface(
onClose: () -> Unit,
onNextEpisodeCardShown: () -> Unit,
timelinePosition: () -> Long,
skipSegments: List<IntroTimestamps>,
chapters: List<com.fluxa.app.player.Chapter> = emptyList(),
skipSegments: List<SkipSegmentUiModel>,
chapters: List<com.fluxa.app.shared.feature.player.Chapter> = emptyList(),
dismissedSkipSegments: Set<String>,
onSkipSegment: (IntroTimestamps) -> Unit,
onDismissSegment: (IntroTimestamps) -> Unit,
onSkipSegment: (SkipSegmentUiModel) -> Unit,
onDismissSegment: (SkipSegmentUiModel) -> Unit,
showSegmentSkipFeedback: Boolean,
holdSpeedVisible: Boolean,
showVolumeBar: Boolean,
@ -502,7 +500,7 @@ internal fun PlayerSettingsPanel(
)
} else if (activeSettingsTab == 4) {
SourceSidebar(
streams = currentStreams,
streams = currentStreams.map { it.toSourceUiModel() },
currentUrl = currentUrl.orEmpty(),
deviceType = deviceType,
lang = lang,

View file

@ -25,8 +25,7 @@ import com.fluxa.app.data.remote.Meta
import com.fluxa.app.data.remote.Stream
import com.fluxa.app.player.*
import com.fluxa.app.player.MediaPlayerController
import com.fluxa.app.player.MediaTrack
import com.fluxa.app.shared.feature.player.dismissKey
import com.fluxa.app.shared.feature.player.MediaTrack
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.flow.MutableStateFlow

View file

@ -52,12 +52,11 @@ import com.fluxa.app.data.local.*
import com.fluxa.app.data.local.UserProfile
import com.fluxa.app.data.remote.Meta
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.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.dismissKey
import com.fluxa.app.shared.feature.player.playerInputControls
import com.fluxa.app.shared.feature.player.playerText
@ -288,7 +287,7 @@ internal fun PlayerScreenContent(
seekBackwardMs = seekBackwardMs,
hasPreviousEpisode = state.previousEpisodePending != null,
hasNextEpisode = state.nextEpisodePending != null,
nextEpisode = state.nextEpisodePending,
nextEpisode = state.nextEpisodePending?.toNextEpisodePreviewUiModel(),
onPlayPrevious = playPrevious,
onPlayNext = playNext,
onCast = smartCast,
@ -310,11 +309,11 @@ internal fun PlayerScreenContent(
state.nextEpisodePending?.let { viewModel.onNextEpisodeCardShown(meta, it.id, activeProfile) }
},
timelinePosition = { if (useMpvBackend) state.engine.timeline.position else state.timelinePosition },
skipSegments = state.skipSegments,
skipSegments = state.skipSegments.map { it.toSkipSegmentUiModel() },
chapters = state.chapters,
dismissedSkipSegments = state.dismissedSkipSegments,
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.introAutoSkipped = true
state.segmentSkipFeedbackVersion += 1

View file

@ -79,7 +79,7 @@ import coil3.compose.AsyncImage
import com.fluxa.app.R
import com.fluxa.app.player.*
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.async
import kotlinx.coroutines.awaitAll

View file

@ -78,7 +78,7 @@ internal class PlayerScreenState(
var currentEpisodeMetaLine by mutableStateOf<String?>(null)
var currentEpisodeArtwork by mutableStateOf<String?>(null)
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 introAutoSkipped by mutableStateOf(false)
var preferredBingeGroupForNextEpisode by mutableStateOf<String?>(null)

View file

@ -22,7 +22,7 @@ import com.fluxa.app.player.PlayerEngine
import com.fluxa.app.player.PlayerEngineRequest
import com.fluxa.app.player.TorrentStreamManager
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.Dispatchers
import kotlinx.coroutines.delay

View file

@ -10,9 +10,9 @@ import com.fluxa.app.data.local.*
import com.fluxa.app.data.local.UserProfile
import com.fluxa.app.data.remote.Meta
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.shared.feature.player.withCurrentEpisodeArtwork
import com.fluxa.app.data.remote.withCurrentEpisodeArtwork
import java.util.Locale
@Composable

View file

@ -7,7 +7,7 @@ import com.fluxa.app.domain.discovery.*
import com.fluxa.app.core.rust.FluxaCoreNative
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 java.util.Locale

View file

@ -1,5 +1,6 @@
package com.fluxa.app.player
import com.fluxa.app.shared.feature.player.Chapter
import kotlinx.coroutines.runBlocking
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer

View file

@ -6,7 +6,7 @@ import com.fluxa.app.data.repository.*
import com.fluxa.app.domain.discovery.*
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.assertSame
import org.junit.Assert.assertTrue

View file

@ -475,3 +475,9 @@ object MetaDetailSerializer : KSerializer<MetaDetail> {
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)
}

View file

@ -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
)

View file

@ -2,6 +2,18 @@ package com.fluxa.app.ui.catalog
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 {
return name
?.takeIf { it.isNotBlank() }

View file

@ -1,5 +1,7 @@
package com.fluxa.app.player
import com.fluxa.app.shared.feature.player.MediaTrack
import android.content.Context
import android.util.Log
import androidx.media3.common.*

View file

@ -1,5 +1,7 @@
package com.fluxa.app.player
import com.fluxa.app.shared.feature.player.Chapter
import com.fluxa.app.core.rust.FluxaStreamingNative
import com.google.gson.Gson
import kotlinx.coroutines.Dispatchers

View file

@ -1,5 +1,8 @@
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.graphics.Color
import android.media.AudioFormat

View file

@ -2,6 +2,9 @@
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.SeekParameters
import androidx.media3.common.C

View file

@ -1,5 +1,7 @@
package com.fluxa.app.player
import com.fluxa.app.shared.feature.player.TorrentStreamStatus
import android.content.Context
import android.net.ConnectivityManager
import android.net.NetworkCapabilities

View file

@ -1,5 +1,7 @@
package com.fluxa.app.player
import com.fluxa.app.shared.feature.player.Chapter
import com.fluxa.app.data.remote.IntroTimestamps
private fun classifyChapterSkipType(title: String): String? {

View file

@ -1,4 +1,4 @@
package com.fluxa.app.player
package com.fluxa.app.shared.feature.player
data class Chapter(
val title: String,

View file

@ -1,4 +1,4 @@
package com.fluxa.app.player
package com.fluxa.app.shared.feature.player
data class MediaTrack(
val id: String,

View file

@ -1,4 +1,4 @@
package com.fluxa.app.player
package com.fluxa.app.shared.feature.player
data class TorrentStreamStatus(
val bufferProgress: Int = 0,

View file

@ -1,4 +1,4 @@
package com.fluxa.app.player
package com.fluxa.app.shared.feature.player
data class TrailerSubtitle(
val languageTag: String,

View file

@ -1,5 +1,7 @@
package com.fluxa.app.player
import com.fluxa.app.shared.feature.player.TrailerPolicy
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNull

View file

@ -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.PlayerRenderAction
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.FluxaColors
import com.fluxa.app.ui.catalog.PosterActionSheet

View file

@ -3,6 +3,6 @@ package com.fluxa.app.shared
import androidx.compose.runtime.Composable
import androidx.compose.runtime.staticCompositionLocalOf
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 }

View file

@ -1,6 +1,6 @@
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 kotlinx.coroutines.flow.Flow

View file

@ -2,7 +2,6 @@
package com.fluxa.app.shared.feature.player
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.FluxaColors
import com.fluxa.app.ui.catalog.FluxaDimensions

View file

@ -1,13 +1,12 @@
package com.fluxa.app.shared.feature.player
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.DeviceType
import com.fluxa.app.ui.catalog.FluxaColors
import com.fluxa.app.ui.catalog.FluxaDimensions
import com.fluxa.app.ui.catalog.FluxaIcons
import com.fluxa.app.ui.catalog.NextEpisodePreviewUiModel
import com.fluxa.app.ui.catalog.PlaybackSnapshot
import androidx.compose.animation.core.FastOutSlowInEasing
@ -66,7 +65,7 @@ fun playerStatusText(lang: String?, value: String): String {
fun SkipSegmentCard(
deviceType: DeviceType,
type: String,
nextEpisode: Video? = null,
nextEpisode: NextEpisodePreviewUiModel? = null,
lang: String? = "en",
autoAdvanceSeconds: Int? = null,
onSkip: () -> Unit,
@ -126,7 +125,7 @@ fun SkipSegmentCard(
@Composable
private fun NextEpisodeSkipCard(
deviceType: DeviceType,
episode: Video,
episode: NextEpisodePreviewUiModel,
lang: String?,
autoAdvanceSeconds: Int? = null,
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 number = episode.number ?: 0
val name = episode.name.orEmpty().trim()

View file

@ -1,10 +1,10 @@
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.FluxaDimensions
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.core.FastOutSlowInEasing
@ -51,26 +51,26 @@ import androidx.compose.ui.zIndex
@Composable
fun PlayerSkipSegmentOverlay(
currentPosition: Long,
skipSegments: List<IntroTimestamps>,
skipSegments: List<SkipSegmentUiModel>,
dismissedSkipSegments: Set<String>,
hasStartedPlaying: Boolean,
showControls: Boolean,
deviceType: DeviceType,
nextEpisode: Video?,
nextEpisode: NextEpisodePreviewUiModel?,
nextEpisodeThresholdReached: Boolean,
autoSkipSegments: Boolean,
autoPlayCountdownSeconds: Int? = null,
lang: String,
onSkipSegment: (IntroTimestamps) -> Unit,
onSkipSegment: (SkipSegmentUiModel) -> Unit,
onPlayNextEpisode: () -> Unit,
onDismissSegment: (IntroTimestamps) -> Unit,
onDismissSegment: (SkipSegmentUiModel) -> Unit,
onNextEpisodeCardShown: () -> Unit,
modifier: Modifier = Modifier
) {
val activeSkipSegment = remember(currentPosition, skipSegments, dismissedSkipSegments, hasStartedPlaying, autoSkipSegments) {
if (!hasStartedPlaying || autoSkipSegments) null
else skipSegments.find { segment ->
currentPosition in segment.startTime until segment.endTime &&
currentPosition in segment.startMs until segment.endMs &&
segment.dismissKey() !in dismissedSkipSegments
}
}
@ -307,7 +307,3 @@ fun BoxScope.PlayerTransientOverlays(
}
}
}
fun IntroTimestamps.dismissKey(): String {
return "$type:$startTime:$endTime"
}

View file

@ -1,7 +1,6 @@
package com.fluxa.app.shared.feature.player
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.FluxaIcons

View file

@ -1,13 +1,10 @@
package com.fluxa.app.shared.feature.player
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.FluxaIcons
import com.fluxa.app.ui.catalog.LocalDeviceType
import com.fluxa.app.ui.catalog.streamRawBody
import com.fluxa.app.ui.catalog.streamSourceHeader
import com.fluxa.app.ui.catalog.StreamSourceUiModel
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.FastOutSlowInEasing
@ -50,7 +47,7 @@ import androidx.compose.ui.zIndex
import kotlinx.coroutines.delay
@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(
title = AppStrings.t(lang, "player.source_selection_title"),
deviceType = deviceType,
@ -58,14 +55,14 @@ fun SourceSidebar(streams: List<Stream>, currentUrl: String, deviceType: DeviceT
cardWidth = 420.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
TrackItem(
modifier = Modifier.animateItem(),
title = stream.streamSourceHeader(),
title = stream.header,
isSelected = stream.playableUrl == currentUrl,
onClick = { playableUrl?.let(onSelect) },
subtitle = stream.streamRawBody(),
subtitle = stream.body,
deviceType = deviceType,
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)
}

View file

@ -1,8 +1,6 @@
package com.fluxa.app.shared.feature.player
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.FluxaColors
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
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) {