mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-08-06 19:38:58 +00:00
refactor(MetaDetailsScreen): replace vertical scroll with lazy column
This commit is contained in:
parent
ed2d29ffc5
commit
83e475d23d
1 changed files with 371 additions and 120 deletions
|
|
@ -25,8 +25,9 @@ import androidx.compose.foundation.layout.padding
|
|||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.layout.statusBars
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.verticalScroll
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Add
|
||||
import androidx.compose.material.icons.filled.Check
|
||||
|
|
@ -111,6 +112,7 @@ import com.nuvio.app.features.watchprogress.buildPlaybackVideoId
|
|||
import com.nuvio.app.features.watchprogress.ContinueWatchingPreferencesRepository
|
||||
import com.nuvio.app.features.watching.application.WatchingActions
|
||||
import com.nuvio.app.features.watching.application.WatchingState
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import nuvio.composeapp.generated.resources.*
|
||||
import org.jetbrains.compose.resources.getString
|
||||
|
|
@ -189,20 +191,30 @@ fun MetaDetailsScreen(
|
|||
var pickerPending by remember(type, id) { mutableStateOf(false) }
|
||||
var pickerError by remember(type, id) { mutableStateOf<String?>(null) }
|
||||
var episodeImdbRatings by remember(type, id) { mutableStateOf<Map<Pair<Int, Int>, Double>>(emptyMap()) }
|
||||
var deferredMetaWorkAllowed by remember(type, id) { mutableStateOf(false) }
|
||||
|
||||
val shouldShowComments = commentsEnabled &&
|
||||
traktAuthUiState.mode == TraktConnectionMode.CONNECTED &&
|
||||
displayedMeta != null &&
|
||||
displayedMeta.type.lowercase().let { it == "movie" || it == "series" || it == "show" || it == "tv" }
|
||||
|
||||
LaunchedEffect(displayedMeta?.id, shouldShowComments) {
|
||||
if (!shouldShowComments || displayedMeta == null) {
|
||||
LaunchedEffect(displayedMeta?.id) {
|
||||
deferredMetaWorkAllowed = false
|
||||
if (displayedMeta != null) {
|
||||
delay(250)
|
||||
deferredMetaWorkAllowed = true
|
||||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(displayedMeta?.id, shouldShowComments, deferredMetaWorkAllowed) {
|
||||
if (displayedMeta == null || !shouldShowComments) {
|
||||
comments = emptyList()
|
||||
commentsCurrentPage = 0
|
||||
commentsPageCount = 0
|
||||
commentsError = null
|
||||
return@LaunchedEffect
|
||||
}
|
||||
if (!deferredMetaWorkAllowed) return@LaunchedEffect
|
||||
isCommentsLoading = true
|
||||
commentsError = null
|
||||
try {
|
||||
|
|
@ -216,8 +228,9 @@ fun MetaDetailsScreen(
|
|||
isCommentsLoading = false
|
||||
}
|
||||
|
||||
LaunchedEffect(displayedMeta?.id, displayedMeta?.videos) {
|
||||
LaunchedEffect(displayedMeta?.id, displayedMeta?.videos, deferredMetaWorkAllowed) {
|
||||
val metaForRatings = displayedMeta
|
||||
if (!deferredMetaWorkAllowed) return@LaunchedEffect
|
||||
if (metaForRatings == null || !metaForRatings.isSeriesLikeForEpisodeRatings()) {
|
||||
episodeImdbRatings = emptyMap()
|
||||
return@LaunchedEffect
|
||||
|
|
@ -462,7 +475,8 @@ fun MetaDetailsScreen(
|
|||
)
|
||||
}
|
||||
}
|
||||
LaunchedEffect(meta.type, debridWarmupTarget) {
|
||||
LaunchedEffect(meta.type, debridWarmupTarget, deferredMetaWorkAllowed) {
|
||||
if (!deferredMetaWorkAllowed) return@LaunchedEffect
|
||||
AddonStreamWarmupRepository.preload(
|
||||
type = meta.type,
|
||||
videoId = debridWarmupTarget.videoId,
|
||||
|
|
@ -509,11 +523,16 @@ fun MetaDetailsScreen(
|
|||
var heroTrailerReady by remember(meta.id, heroTrailerCandidate?.id) { mutableStateOf(false) }
|
||||
var heroTrailerFinished by remember(meta.id, heroTrailerCandidate?.id) { mutableStateOf(false) }
|
||||
val heroTrailerMuted by HeroTrailerAudioState.muted.collectAsStateWithLifecycle()
|
||||
LaunchedEffect(heroTrailerPlaybackEnabled, heroTrailerCandidate?.id, heroTrailerCandidate?.key) {
|
||||
LaunchedEffect(
|
||||
heroTrailerPlaybackEnabled,
|
||||
heroTrailerCandidate?.id,
|
||||
heroTrailerCandidate?.key,
|
||||
deferredMetaWorkAllowed,
|
||||
) {
|
||||
heroTrailerPlaybackSource = null
|
||||
heroTrailerReady = false
|
||||
heroTrailerFinished = false
|
||||
if (!heroTrailerPlaybackEnabled || heroTrailerCandidate == null) {
|
||||
if (!deferredMetaWorkAllowed || !heroTrailerPlaybackEnabled || heroTrailerCandidate == null) {
|
||||
return@LaunchedEffect
|
||||
}
|
||||
val resolvedSource = runCatching {
|
||||
|
|
@ -718,7 +737,7 @@ fun MetaDetailsScreen(
|
|||
savedProgress?.lastPositionMs,
|
||||
)
|
||||
}
|
||||
val scrollState = rememberScrollState()
|
||||
val listState = rememberLazyListState()
|
||||
val density = LocalDensity.current
|
||||
val safeAreaTopPx = with(density) {
|
||||
WindowInsets.statusBars
|
||||
|
|
@ -728,7 +747,20 @@ fun MetaDetailsScreen(
|
|||
}
|
||||
var heroHeightPx by remember(meta.id) { mutableIntStateOf(0) }
|
||||
val thresholdPx = (heroHeightPx - safeAreaTopPx).coerceAtLeast(0f)
|
||||
val headerTarget = if (heroHeightPx > 0 && scrollState.value > thresholdPx) 1f else 0f
|
||||
val detailScrollOffsetPx = if (listState.firstVisibleItemIndex == 0) {
|
||||
listState.firstVisibleItemScrollOffset.toFloat()
|
||||
} else {
|
||||
heroHeightPx.toFloat() + listState.firstVisibleItemScrollOffset
|
||||
}
|
||||
val heroScrollOffset = detailScrollOffsetPx.toInt()
|
||||
val headerTarget = if (
|
||||
heroHeightPx > 0 &&
|
||||
(listState.firstVisibleItemIndex > 0 || detailScrollOffsetPx > thresholdPx)
|
||||
) {
|
||||
1f
|
||||
} else {
|
||||
0f
|
||||
}
|
||||
val heroTrailerSourceUrl = heroTrailerPlaybackSource
|
||||
?.videoUrl
|
||||
?.takeIf { it.isNotBlank() && heroTrailerPlaybackEnabled && !heroTrailerFinished && !isLeavingDetails }
|
||||
|
|
@ -737,7 +769,7 @@ fun MetaDetailsScreen(
|
|||
?.takeIf { heroTrailerSourceUrl != null && it.isNotBlank() }
|
||||
val heroTrailerPlayWhenReady = heroTrailerSourceUrl != null &&
|
||||
!isLeavingDetails &&
|
||||
(heroHeightPx == 0 || scrollState.value <= thresholdPx)
|
||||
(heroHeightPx == 0 || detailScrollOffsetPx <= thresholdPx)
|
||||
val headerProgress by animateFloatAsState(
|
||||
targetValue = headerTarget,
|
||||
animationSpec = tween(
|
||||
|
|
@ -751,7 +783,7 @@ fun MetaDetailsScreen(
|
|||
val isTablet = maxWidth >= 720.dp
|
||||
val contentHorizontalPadding = if (isTablet) 32.dp else 18.dp
|
||||
val contentMaxWidth = detailTabletContentMaxWidth(maxWidth, isTablet)
|
||||
val cinematicEnabled = metaScreenSettingsUiState.cinematicBackground
|
||||
val cinematicEnabled = metaScreenSettingsUiState.cinematicBackground && deferredMetaWorkAllowed
|
||||
|
||||
Box(modifier = Modifier.fillMaxSize()) {
|
||||
if (cinematicEnabled) {
|
||||
|
|
@ -772,123 +804,120 @@ fun MetaDetailsScreen(
|
|||
)
|
||||
}
|
||||
}
|
||||
Column(
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
modifier = Modifier
|
||||
.fillMaxSize()
|
||||
.zIndex(1f)
|
||||
.verticalScroll(scrollState),
|
||||
.zIndex(1f),
|
||||
) {
|
||||
DetailHero(
|
||||
meta = meta,
|
||||
isTablet = isTablet,
|
||||
contentMaxWidth = contentMaxWidth,
|
||||
scrollOffset = scrollState.value,
|
||||
onHeightChanged = { heroHeightPx = it },
|
||||
heroTrailerSourceUrl = heroTrailerSourceUrl,
|
||||
heroTrailerSourceAudioUrl = heroTrailerSourceAudioUrl,
|
||||
heroTrailerReady = heroTrailerReady,
|
||||
heroTrailerPlayWhenReady = heroTrailerPlayWhenReady,
|
||||
heroTrailerMuted = heroTrailerMuted,
|
||||
onHeroTrailerMuteToggle = {
|
||||
HeroTrailerAudioState.toggleMuted()
|
||||
},
|
||||
onHeroTrailerReady = {
|
||||
if (!heroTrailerFinished) {
|
||||
heroTrailerReady = true
|
||||
}
|
||||
},
|
||||
onHeroTrailerEnded = {
|
||||
heroTrailerReady = false
|
||||
heroTrailerFinished = true
|
||||
},
|
||||
onHeroTrailerError = {
|
||||
heroTrailerReady = false
|
||||
heroTrailerFinished = true
|
||||
},
|
||||
)
|
||||
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = contentHorizontalPadding)
|
||||
.widthIn(max = if (isTablet) contentMaxWidth else Dp.Unspecified),
|
||||
verticalArrangement = Arrangement.spacedBy(20.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
ConfiguredMetaSections(
|
||||
settings = metaScreenSettingsUiState,
|
||||
item(key = "detail-hero") {
|
||||
DetailHero(
|
||||
meta = meta,
|
||||
isTablet = isTablet,
|
||||
playButtonLabel = playButtonLabel,
|
||||
isSaved = isSaved,
|
||||
isWatched = isWatched,
|
||||
onPrimaryPlayClick = onPrimaryPlayClick,
|
||||
onPrimaryPlayLongClick = onPrimaryPlayLongClick,
|
||||
onSaveClick = toggleSaved,
|
||||
onSaveLongClick = openLibraryListPicker,
|
||||
onWatchedClick = toggleWatched,
|
||||
showManualPlayOption = showManualPlayOption,
|
||||
preferredEpisodeSeasonNumber = seriesAction?.seasonNumber,
|
||||
preferredEpisodeNumber = seriesAction?.episodeNumber,
|
||||
hasProductionSection = hasProductionSection,
|
||||
hasTrailersSection = hasTrailersSection,
|
||||
hasEpisodes = hasEpisodes,
|
||||
hasAdditionalInfoSection = hasAdditionalInfoSection,
|
||||
hasCollectionSection = hasCollectionSection,
|
||||
hasMoreLikeThisSection = hasMoreLikeThisSection,
|
||||
shouldShowComments = shouldShowComments,
|
||||
comments = comments,
|
||||
isCommentsLoading = isCommentsLoading,
|
||||
isCommentsLoadingMore = isCommentsLoadingMore,
|
||||
commentsCurrentPage = commentsCurrentPage,
|
||||
commentsPageCount = commentsPageCount,
|
||||
commentsError = commentsError,
|
||||
episodeImdbRatings = episodeImdbRatings,
|
||||
onRetryComments = {
|
||||
detailsScope.launch {
|
||||
isCommentsLoading = true
|
||||
commentsError = null
|
||||
try {
|
||||
val result = TraktCommentsRepository.getCommentsPage(meta, page = 1, forceRefresh = true)
|
||||
comments = result.items
|
||||
commentsCurrentPage = result.currentPage
|
||||
commentsPageCount = result.pageCount
|
||||
} catch (e: Exception) {
|
||||
commentsError = e.message ?: getString(Res.string.details_comments_load_failed)
|
||||
}
|
||||
isCommentsLoading = false
|
||||
contentMaxWidth = contentMaxWidth,
|
||||
scrollOffset = heroScrollOffset,
|
||||
onHeightChanged = { heroHeightPx = it },
|
||||
heroTrailerSourceUrl = heroTrailerSourceUrl,
|
||||
heroTrailerSourceAudioUrl = heroTrailerSourceAudioUrl,
|
||||
heroTrailerReady = heroTrailerReady,
|
||||
heroTrailerPlayWhenReady = heroTrailerPlayWhenReady,
|
||||
heroTrailerMuted = heroTrailerMuted,
|
||||
onHeroTrailerMuteToggle = {
|
||||
HeroTrailerAudioState.toggleMuted()
|
||||
},
|
||||
onHeroTrailerReady = {
|
||||
if (!heroTrailerFinished) {
|
||||
heroTrailerReady = true
|
||||
}
|
||||
},
|
||||
onLoadMoreComments = {
|
||||
detailsScope.launch {
|
||||
isCommentsLoadingMore = true
|
||||
try {
|
||||
val nextPage = commentsCurrentPage + 1
|
||||
val result = TraktCommentsRepository.getCommentsPage(meta, page = nextPage)
|
||||
val existingIds = comments.map { it.id }.toSet()
|
||||
val newComments = result.items.filter { it.id !in existingIds }
|
||||
comments = comments + newComments
|
||||
commentsCurrentPage = result.currentPage
|
||||
commentsPageCount = result.pageCount
|
||||
} catch (_: Exception) { }
|
||||
isCommentsLoadingMore = false
|
||||
}
|
||||
onHeroTrailerEnded = {
|
||||
heroTrailerReady = false
|
||||
heroTrailerFinished = true
|
||||
},
|
||||
onHeroTrailerError = {
|
||||
heroTrailerReady = false
|
||||
heroTrailerFinished = true
|
||||
},
|
||||
onCommentClick = { review -> selectedComment = review },
|
||||
onTrailerClick = resolveTrailer,
|
||||
progressByVideoId = progressByVideoId,
|
||||
watchedKeys = watchedUiState.watchedKeys,
|
||||
blurUnwatchedEpisodes = metaScreenSettingsUiState.blurUnwatchedEpisodes,
|
||||
onEpisodeClick = onEpisodePlayClick,
|
||||
onEpisodeLongPress = { video -> selectedEpisodeForActions = video },
|
||||
onSeasonLongPress = { season -> selectedSeasonForActions = season },
|
||||
onOpenMeta = onOpenMeta,
|
||||
onCastClick = onCastClick,
|
||||
onCompanyClick = onCompanyClick,
|
||||
sharedTransitionScope = sharedTransitionScope,
|
||||
animatedVisibilityScope = animatedVisibilityScope,
|
||||
)
|
||||
}
|
||||
|
||||
configuredMetaSectionItems(
|
||||
settings = metaScreenSettingsUiState,
|
||||
meta = meta,
|
||||
isTablet = isTablet,
|
||||
contentHorizontalPadding = contentHorizontalPadding,
|
||||
contentMaxWidth = if (isTablet) contentMaxWidth else Dp.Unspecified,
|
||||
playButtonLabel = playButtonLabel,
|
||||
isSaved = isSaved,
|
||||
isWatched = isWatched,
|
||||
onPrimaryPlayClick = onPrimaryPlayClick,
|
||||
onPrimaryPlayLongClick = onPrimaryPlayLongClick,
|
||||
onSaveClick = toggleSaved,
|
||||
onSaveLongClick = openLibraryListPicker,
|
||||
onWatchedClick = toggleWatched,
|
||||
showManualPlayOption = showManualPlayOption,
|
||||
preferredEpisodeSeasonNumber = seriesAction?.seasonNumber,
|
||||
preferredEpisodeNumber = seriesAction?.episodeNumber,
|
||||
hasProductionSection = hasProductionSection,
|
||||
hasTrailersSection = hasTrailersSection,
|
||||
hasEpisodes = hasEpisodes,
|
||||
hasAdditionalInfoSection = hasAdditionalInfoSection,
|
||||
hasCollectionSection = hasCollectionSection,
|
||||
hasMoreLikeThisSection = hasMoreLikeThisSection,
|
||||
shouldShowComments = shouldShowComments,
|
||||
comments = comments,
|
||||
isCommentsLoading = isCommentsLoading,
|
||||
isCommentsLoadingMore = isCommentsLoadingMore,
|
||||
commentsCurrentPage = commentsCurrentPage,
|
||||
commentsPageCount = commentsPageCount,
|
||||
commentsError = commentsError,
|
||||
episodeImdbRatings = episodeImdbRatings,
|
||||
onRetryComments = {
|
||||
detailsScope.launch {
|
||||
isCommentsLoading = true
|
||||
commentsError = null
|
||||
try {
|
||||
val result = TraktCommentsRepository.getCommentsPage(meta, page = 1, forceRefresh = true)
|
||||
comments = result.items
|
||||
commentsCurrentPage = result.currentPage
|
||||
commentsPageCount = result.pageCount
|
||||
} catch (e: Exception) {
|
||||
commentsError = e.message ?: getString(Res.string.details_comments_load_failed)
|
||||
}
|
||||
isCommentsLoading = false
|
||||
}
|
||||
},
|
||||
onLoadMoreComments = {
|
||||
detailsScope.launch {
|
||||
isCommentsLoadingMore = true
|
||||
try {
|
||||
val nextPage = commentsCurrentPage + 1
|
||||
val result = TraktCommentsRepository.getCommentsPage(meta, page = nextPage)
|
||||
val existingIds = comments.map { it.id }.toSet()
|
||||
val newComments = result.items.filter { it.id !in existingIds }
|
||||
comments = comments + newComments
|
||||
commentsCurrentPage = result.currentPage
|
||||
commentsPageCount = result.pageCount
|
||||
} catch (_: Exception) { }
|
||||
isCommentsLoadingMore = false
|
||||
}
|
||||
},
|
||||
onCommentClick = { review -> selectedComment = review },
|
||||
onTrailerClick = resolveTrailer,
|
||||
progressByVideoId = progressByVideoId,
|
||||
watchedKeys = watchedUiState.watchedKeys,
|
||||
blurUnwatchedEpisodes = metaScreenSettingsUiState.blurUnwatchedEpisodes,
|
||||
onEpisodeClick = onEpisodePlayClick,
|
||||
onEpisodeLongPress = { video -> selectedEpisodeForActions = video },
|
||||
onSeasonLongPress = { season -> selectedSeasonForActions = season },
|
||||
onOpenMeta = onOpenMeta,
|
||||
onCastClick = onCastClick,
|
||||
onCompanyClick = onCompanyClick,
|
||||
sharedTransitionScope = sharedTransitionScope,
|
||||
animatedVisibilityScope = animatedVisibilityScope,
|
||||
)
|
||||
|
||||
item(key = "detail-bottom-spacer") {
|
||||
Spacer(modifier = Modifier.height(nuvioSafeBottomPadding(32.dp)))
|
||||
}
|
||||
}
|
||||
|
|
@ -901,7 +930,7 @@ fun MetaDetailsScreen(
|
|||
.fillMaxWidth()
|
||||
.height(132.dp)
|
||||
.graphicsLayer {
|
||||
translationY = heroHeightPx.toFloat() - scrollState.value
|
||||
translationY = heroHeightPx.toFloat() - detailScrollOffsetPx
|
||||
}
|
||||
.background(
|
||||
Brush.verticalGradient(
|
||||
|
|
@ -1263,6 +1292,228 @@ private fun MetaDetails.toMetaPreview(): MetaPreview =
|
|||
genres = genres,
|
||||
)
|
||||
|
||||
private fun LazyListScope.configuredMetaSectionItems(
|
||||
settings: MetaScreenSettingsUiState,
|
||||
meta: MetaDetails,
|
||||
isTablet: Boolean,
|
||||
contentHorizontalPadding: Dp,
|
||||
contentMaxWidth: Dp,
|
||||
playButtonLabel: String,
|
||||
isSaved: Boolean,
|
||||
isWatched: Boolean,
|
||||
onPrimaryPlayClick: () -> Unit,
|
||||
onPrimaryPlayLongClick: (() -> Unit)?,
|
||||
onSaveClick: () -> Unit,
|
||||
onSaveLongClick: (() -> Unit)?,
|
||||
onWatchedClick: () -> Unit,
|
||||
showManualPlayOption: Boolean,
|
||||
preferredEpisodeSeasonNumber: Int?,
|
||||
preferredEpisodeNumber: Int?,
|
||||
hasProductionSection: Boolean,
|
||||
hasTrailersSection: Boolean,
|
||||
hasEpisodes: Boolean,
|
||||
hasAdditionalInfoSection: Boolean,
|
||||
hasCollectionSection: Boolean,
|
||||
hasMoreLikeThisSection: Boolean,
|
||||
shouldShowComments: Boolean,
|
||||
comments: List<TraktCommentReview>,
|
||||
isCommentsLoading: Boolean,
|
||||
isCommentsLoadingMore: Boolean,
|
||||
commentsCurrentPage: Int,
|
||||
commentsPageCount: Int,
|
||||
commentsError: String?,
|
||||
episodeImdbRatings: Map<Pair<Int, Int>, Double>,
|
||||
onRetryComments: () -> Unit,
|
||||
onLoadMoreComments: () -> Unit,
|
||||
onCommentClick: (TraktCommentReview) -> Unit,
|
||||
onTrailerClick: (MetaTrailer) -> Unit,
|
||||
progressByVideoId: Map<String, WatchProgressEntry>,
|
||||
watchedKeys: Set<String>,
|
||||
blurUnwatchedEpisodes: Boolean,
|
||||
onEpisodeClick: (MetaVideo) -> Unit,
|
||||
onEpisodeLongPress: (MetaVideo) -> Unit,
|
||||
onSeasonLongPress: (Int) -> Unit,
|
||||
onOpenMeta: ((MetaPreview) -> Unit)?,
|
||||
onCastClick: ((MetaPerson, String?) -> Unit)?,
|
||||
onCompanyClick: ((MetaCompany, String) -> Unit)?,
|
||||
sharedTransitionScope: SharedTransitionScope?,
|
||||
animatedVisibilityScope: AnimatedVisibilityScope?,
|
||||
) {
|
||||
val enabledItems = settings.items.filter { it.enabled }
|
||||
fun sectionHasContent(key: MetaScreenSectionKey): Boolean =
|
||||
metaSectionHasContent(
|
||||
key = key,
|
||||
meta = meta,
|
||||
hasProductionSection = hasProductionSection,
|
||||
hasTrailersSection = hasTrailersSection,
|
||||
hasEpisodes = hasEpisodes,
|
||||
hasAdditionalInfoSection = hasAdditionalInfoSection,
|
||||
hasCollectionSection = hasCollectionSection,
|
||||
hasMoreLikeThisSection = hasMoreLikeThisSection,
|
||||
shouldShowComments = shouldShowComments,
|
||||
comments = comments,
|
||||
isCommentsLoading = isCommentsLoading,
|
||||
commentsError = commentsError,
|
||||
)
|
||||
|
||||
fun addSectionItem(
|
||||
key: String,
|
||||
sectionItems: List<MetaScreenSectionItem>,
|
||||
forceTabLayout: Boolean = settings.tabLayout,
|
||||
) {
|
||||
item(key = key) {
|
||||
DetailSectionContainer(
|
||||
horizontalPadding = contentHorizontalPadding,
|
||||
contentMaxWidth = contentMaxWidth,
|
||||
) {
|
||||
ConfiguredMetaSections(
|
||||
settings = settings.copy(
|
||||
items = sectionItems,
|
||||
tabLayout = forceTabLayout,
|
||||
),
|
||||
meta = meta,
|
||||
isTablet = isTablet,
|
||||
playButtonLabel = playButtonLabel,
|
||||
isSaved = isSaved,
|
||||
isWatched = isWatched,
|
||||
onPrimaryPlayClick = onPrimaryPlayClick,
|
||||
onPrimaryPlayLongClick = onPrimaryPlayLongClick,
|
||||
onSaveClick = onSaveClick,
|
||||
onSaveLongClick = onSaveLongClick,
|
||||
onWatchedClick = onWatchedClick,
|
||||
showManualPlayOption = showManualPlayOption,
|
||||
preferredEpisodeSeasonNumber = preferredEpisodeSeasonNumber,
|
||||
preferredEpisodeNumber = preferredEpisodeNumber,
|
||||
hasProductionSection = hasProductionSection,
|
||||
hasTrailersSection = hasTrailersSection,
|
||||
hasEpisodes = hasEpisodes,
|
||||
hasAdditionalInfoSection = hasAdditionalInfoSection,
|
||||
hasCollectionSection = hasCollectionSection,
|
||||
hasMoreLikeThisSection = hasMoreLikeThisSection,
|
||||
shouldShowComments = shouldShowComments,
|
||||
comments = comments,
|
||||
isCommentsLoading = isCommentsLoading,
|
||||
isCommentsLoadingMore = isCommentsLoadingMore,
|
||||
commentsCurrentPage = commentsCurrentPage,
|
||||
commentsPageCount = commentsPageCount,
|
||||
commentsError = commentsError,
|
||||
episodeImdbRatings = episodeImdbRatings,
|
||||
onRetryComments = onRetryComments,
|
||||
onLoadMoreComments = onLoadMoreComments,
|
||||
onCommentClick = onCommentClick,
|
||||
onTrailerClick = onTrailerClick,
|
||||
progressByVideoId = progressByVideoId,
|
||||
watchedKeys = watchedKeys,
|
||||
blurUnwatchedEpisodes = blurUnwatchedEpisodes,
|
||||
onEpisodeClick = onEpisodeClick,
|
||||
onEpisodeLongPress = onEpisodeLongPress,
|
||||
onSeasonLongPress = onSeasonLongPress,
|
||||
onOpenMeta = onOpenMeta,
|
||||
onCastClick = onCastClick,
|
||||
onCompanyClick = onCompanyClick,
|
||||
sharedTransitionScope = sharedTransitionScope,
|
||||
animatedVisibilityScope = animatedVisibilityScope,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!settings.tabLayout) {
|
||||
enabledItems
|
||||
.filter { sectionHasContent(it.key) }
|
||||
.forEach { section ->
|
||||
addSectionItem(
|
||||
key = "detail-section-${section.key.name}",
|
||||
sectionItems = listOf(section),
|
||||
forceTabLayout = false,
|
||||
)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
val processedGroups = mutableSetOf<Int>()
|
||||
enabledItems.forEach { section ->
|
||||
val groupId = section.tabGroup
|
||||
if (groupId == null) {
|
||||
if (sectionHasContent(section.key)) {
|
||||
addSectionItem(
|
||||
key = "detail-section-${section.key.name}",
|
||||
sectionItems = listOf(section),
|
||||
forceTabLayout = true,
|
||||
)
|
||||
}
|
||||
} else if (groupId !in processedGroups) {
|
||||
processedGroups.add(groupId)
|
||||
val groupMembers = enabledItems.filter { item ->
|
||||
item.tabGroup == groupId && sectionHasContent(item.key)
|
||||
}
|
||||
if (groupMembers.isNotEmpty()) {
|
||||
addSectionItem(
|
||||
key = "detail-section-group-$groupId",
|
||||
sectionItems = groupMembers,
|
||||
forceTabLayout = groupMembers.size > 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun DetailSectionContainer(
|
||||
horizontalPadding: Dp,
|
||||
contentMaxWidth: Dp,
|
||||
content: @Composable () -> Unit,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = horizontalPadding)
|
||||
.padding(bottom = 20.dp),
|
||||
contentAlignment = Alignment.Center,
|
||||
) {
|
||||
Box(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.then(
|
||||
if (contentMaxWidth == Dp.Unspecified) {
|
||||
Modifier
|
||||
} else {
|
||||
Modifier.widthIn(max = contentMaxWidth)
|
||||
},
|
||||
),
|
||||
) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun metaSectionHasContent(
|
||||
key: MetaScreenSectionKey,
|
||||
meta: MetaDetails,
|
||||
hasProductionSection: Boolean,
|
||||
hasTrailersSection: Boolean,
|
||||
hasEpisodes: Boolean,
|
||||
hasAdditionalInfoSection: Boolean,
|
||||
hasCollectionSection: Boolean,
|
||||
hasMoreLikeThisSection: Boolean,
|
||||
shouldShowComments: Boolean,
|
||||
comments: List<TraktCommentReview>,
|
||||
isCommentsLoading: Boolean,
|
||||
commentsError: String?,
|
||||
): Boolean =
|
||||
when (key) {
|
||||
MetaScreenSectionKey.ACTIONS -> true
|
||||
MetaScreenSectionKey.OVERVIEW -> true
|
||||
MetaScreenSectionKey.PRODUCTION -> hasProductionSection
|
||||
MetaScreenSectionKey.CAST -> meta.cast.isNotEmpty()
|
||||
MetaScreenSectionKey.COMMENTS -> shouldShowComments && (isCommentsLoading || comments.isNotEmpty() || !commentsError.isNullOrBlank())
|
||||
MetaScreenSectionKey.TRAILERS -> hasTrailersSection
|
||||
MetaScreenSectionKey.EPISODES -> hasEpisodes
|
||||
MetaScreenSectionKey.DETAILS -> hasAdditionalInfoSection
|
||||
MetaScreenSectionKey.COLLECTION -> !hasEpisodes && hasCollectionSection
|
||||
MetaScreenSectionKey.MORE_LIKE_THIS -> hasMoreLikeThisSection
|
||||
}
|
||||
|
||||
@Composable
|
||||
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||
private fun ConfiguredMetaSections(
|
||||
|
|
|
|||
Loading…
Reference in a new issue