From b690b0a44d49cd9e5cc0f3204bd3cb7e962c2fe3 Mon Sep 17 00:00:00 2001 From: oxy Date: Sat, 18 May 2024 20:10:13 +0800 Subject: [PATCH] fix: disable programme gallery if the playlist is xtream vod or series, or the playlist is m3u but not refer to any epg. --- .../com/m3u/features/stream/StreamScreen.kt | 9 +- .../m3u/features/stream/StreamViewModel.kt | 15 +- .../features/stream/components/PlayerPanel.kt | 130 ++++++++++++------ .../material/components/PullPanelLayout.kt | 5 - .../com/m3u/material/components/TextFields.kt | 3 +- 5 files changed, 105 insertions(+), 57 deletions(-) diff --git a/features/stream/src/main/java/com/m3u/features/stream/StreamScreen.kt b/features/stream/src/main/java/com/m3u/features/stream/StreamScreen.kt index c5c61d711..f34dbeccf 100644 --- a/features/stream/src/main/java/com/m3u/features/stream/StreamScreen.kt +++ b/features/stream/src/main/java/com/m3u/features/stream/StreamScreen.kt @@ -54,7 +54,6 @@ import com.m3u.material.components.mask.MaskInterceptor import com.m3u.material.components.mask.MaskState import com.m3u.material.components.mask.rememberMaskState import com.m3u.material.components.rememberPullPanelLayoutState -import com.m3u.material.ktx.isTelevision import com.m3u.material.ktx.plus import com.m3u.ui.Player import com.m3u.ui.helper.LocalHelper @@ -78,8 +77,6 @@ fun StreamRoute( val context = LocalContext.current val configuration = LocalConfiguration.current - val tv = isTelevision() - val playerState: PlayerState by viewModel.playerState.collectAsStateWithLifecycle() val stream by viewModel.stream.collectAsStateWithLifecycle() val playlist by viewModel.playlist.collectAsStateWithLifecycle() @@ -92,6 +89,9 @@ fun StreamRoute( val volume by viewModel.volume.collectAsStateWithLifecycle() val isSeriesPlaylist by viewModel.isSeriesPlaylist.collectAsStateWithLifecycle() + val isProgrammeSupported by viewModel.isProgrammeSupported.collectAsStateWithLifecycle( + initialValue = false + ) val neighboring = viewModel.neighboring.collectAsLazyPagingItems() val programmes = viewModel.programmes.collectAsLazyPagingItems() @@ -188,7 +188,8 @@ fun StreamRoute( playlistTitle = playlist?.title.orEmpty(), streamId = stream?.id ?: -1, isPanelExpanded = isPanelExpanded, - isSeriesPlaylist = isSeriesPlaylist, + isChannelsSupported = !isSeriesPlaylist, + isProgrammeSupported = isProgrammeSupported, channels = neighboring, programmes = programmes, programmeRange = programmeRange diff --git a/features/stream/src/main/java/com/m3u/features/stream/StreamViewModel.kt b/features/stream/src/main/java/com/m3u/features/stream/StreamViewModel.kt index 94fd14096..de4977f6d 100644 --- a/features/stream/src/main/java/com/m3u/features/stream/StreamViewModel.kt +++ b/features/stream/src/main/java/com/m3u/features/stream/StreamViewModel.kt @@ -17,12 +17,14 @@ import com.m3u.core.architecture.dispatcher.M3uDispatchers.Main import com.m3u.core.architecture.logger.Logger import com.m3u.core.architecture.logger.Profiles import com.m3u.core.architecture.logger.install +import com.m3u.data.database.model.DataSource import com.m3u.data.database.model.Playlist import com.m3u.data.database.model.Programme import com.m3u.data.database.model.ProgrammeRange import com.m3u.data.database.model.Stream import com.m3u.data.database.model.epgUrlsOrXtreamXmlUrl import com.m3u.data.database.model.isSeries +import com.m3u.data.database.model.isVod import com.m3u.data.repository.playlist.PlaylistRepository import com.m3u.data.repository.programme.ProgrammeRepository import com.m3u.data.repository.stream.StreamRepository @@ -78,8 +80,7 @@ class StreamViewModel @Inject constructor( internal val stream: StateFlow = playerManager.stream internal val playlist: StateFlow = playerManager.playlist - internal val isSeriesPlaylist: StateFlow = playerManager - .playlist + internal val isSeriesPlaylist: StateFlow = playlist .map { it?.isSeries ?: false } .stateIn( scope = viewModelScope, @@ -87,6 +88,16 @@ class StreamViewModel @Inject constructor( started = SharingStarted.WhileSubscribed(5_000L) ) + internal val isProgrammeSupported: Flow = playlist.map { + it ?: return@map false + if (it.isSeries || it.isVod) return@map false + when (it.source) { + DataSource.Xtream -> true + DataSource.M3U -> it.epgUrls.isNotEmpty() + else -> false + } + } + internal val tracks: StateFlow>> = playerManager .tracks .map { all -> diff --git a/features/stream/src/main/java/com/m3u/features/stream/components/PlayerPanel.kt b/features/stream/src/main/java/com/m3u/features/stream/components/PlayerPanel.kt index af9e8d4c1..a0957eb42 100644 --- a/features/stream/src/main/java/com/m3u/features/stream/components/PlayerPanel.kt +++ b/features/stream/src/main/java/com/m3u/features/stream/components/PlayerPanel.kt @@ -1,8 +1,7 @@ package com.m3u.features.stream.components +import android.view.KeyEvent import androidx.compose.animation.AnimatedVisibility -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut import androidx.compose.foundation.basicMarquee import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column @@ -10,6 +9,9 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.statusBarsPadding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.items @@ -24,9 +26,13 @@ import androidx.compose.material3.surfaceColorAtElevation import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier +import androidx.compose.ui.focus.FocusDirection import androidx.compose.ui.graphics.RectangleShape +import androidx.compose.ui.input.key.onKeyEvent import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextOverflow import androidx.paging.compose.LazyPagingItems @@ -39,6 +45,7 @@ import com.m3u.data.database.model.Stream import com.m3u.data.service.MediaCommand import com.m3u.material.components.Background import com.m3u.material.ktx.isTelevision +import com.m3u.material.ktx.thenIf import com.m3u.material.model.LocalSpacing import com.m3u.material.shape.AbsoluteSmoothCornerShape import com.m3u.ui.FontFamilies @@ -54,7 +61,8 @@ internal fun PlayerPanel( title: String, playlistTitle: String, streamId: Int, - isSeriesPlaylist: Boolean, + isChannelsSupported: Boolean, + isProgrammeSupported: Boolean, isPanelExpanded: Boolean, channels: LazyPagingItems, programmes: LazyPagingItems, @@ -62,8 +70,9 @@ internal fun PlayerPanel( modifier: Modifier = Modifier ) { val configuration = LocalConfiguration.current - val useVertical = configuration.screenWidthDp < configuration.screenHeightDp val spacing = LocalSpacing.current + + val useVertical = configuration.screenWidthDp < configuration.screenHeightDp Background( shape = if (useVertical) RectangleShape else AbsoluteSmoothCornerShape( cornerRadiusTL = spacing.medium, @@ -71,68 +80,73 @@ internal fun PlayerPanel( ) ) { Column( + verticalArrangement = Arrangement.spacedBy(spacing.small), modifier = modifier .fillMaxSize() + .thenIf(!useVertical) { + Modifier.statusBarsPadding() + } .padding(vertical = spacing.medium) ) { AnimatedVisibility( visible = isPanelExpanded && useVertical, modifier = Modifier.padding(horizontal = spacing.medium) ) { - Text( - text = title.trim(), - style = MaterialTheme.typography.titleMedium, - fontWeight = FontWeight.ExtraBold, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.basicMarquee() - ) - } - AnimatedVisibility( - visible = isPanelExpanded && useVertical, - enter = fadeIn(), - exit = fadeOut(), - modifier = Modifier.padding(horizontal = spacing.medium) - ) { - Text( - text = playlistTitle.trim().uppercase(), - style = MaterialTheme.typography.labelMedium, - maxLines = 1, - color = LocalContentColor.current.copy(0.54f), - fontFamily = FontFamilies.LexendExa, - overflow = TextOverflow.Ellipsis, - modifier = Modifier.basicMarquee() - ) + Column { + Text( + text = title.trim(), + style = MaterialTheme.typography.titleMedium, + fontWeight = FontWeight.ExtraBold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.basicMarquee() + ) + Text( + text = playlistTitle.trim().uppercase(), + style = MaterialTheme.typography.labelMedium, + maxLines = 1, + color = LocalContentColor.current.copy(0.54f), + fontFamily = FontFamilies.LexendExa, + overflow = TextOverflow.Ellipsis, + modifier = Modifier.basicMarquee() + ) + } } - if (!isSeriesPlaylist) { + if (isChannelsSupported) { ChannelGallery( // TODO value = ChannelGalleryValue.PagingChannel(channels, streamId), - isPanelExpanded = isPanelExpanded + isPanelExpanded = isPanelExpanded, + vertical = !isProgrammeSupported ) } - ProgramGuide( - isPanelExpanded = isPanelExpanded, - programmes = programmes, - range = programmeRange, - modifier = Modifier - .fillMaxWidth() - .weight(1f) - ) + if (isProgrammeSupported) { + ProgramGuide( + isPanelExpanded = isPanelExpanded, + programmes = programmes, + range = programmeRange, + modifier = Modifier + .fillMaxWidth() + .weight(1f) + ) + } } } } +@OptIn(ExperimentalComposeUiApi::class) @Composable private fun ChannelGallery( value: ChannelGalleryValue, isPanelExpanded: Boolean, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, + vertical: Boolean = false ) { val spacing = LocalSpacing.current val lazyListState = rememberLazyListState() + val tv = isTelevision() ScrollToCurrentEffect( value = value, @@ -140,12 +154,7 @@ private fun ChannelGallery( lazyListState = lazyListState ) - LazyRow( - state = lazyListState, - horizontalArrangement = Arrangement.spacedBy(spacing.medium), - contentPadding = PaddingValues(spacing.medium), - modifier = modifier - ) { + val content: LazyListScope.() -> Unit = { when (value) { is ChannelGalleryValue.PagingChannel -> { val channels = value.channels @@ -168,6 +177,37 @@ private fun ChannelGallery( } } } + if (!vertical) { + LazyRow( + state = lazyListState, + horizontalArrangement = Arrangement.spacedBy(spacing.medium), + contentPadding = PaddingValues(spacing.medium), + modifier = modifier, + content = content + ) + } else { + val focusManager = LocalFocusManager.current + LazyColumn( + state = lazyListState, + verticalArrangement = Arrangement.spacedBy(spacing.medium), + contentPadding = PaddingValues(spacing.medium), + modifier = modifier + .fillMaxWidth() + .thenIf(tv) { + Modifier.onKeyEvent { + when (it.nativeKeyEvent.keyCode) { + KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT -> { + focusManager.moveFocus(FocusDirection.Exit) + true + } + + else -> false + } + } + }, + content = content + ) + } } private sealed class ChannelGalleryValue { diff --git a/material/src/main/java/com/m3u/material/components/PullPanelLayout.kt b/material/src/main/java/com/m3u/material/components/PullPanelLayout.kt index 42374bff7..c20f1be5f 100644 --- a/material/src/main/java/com/m3u/material/components/PullPanelLayout.kt +++ b/material/src/main/java/com/m3u/material/components/PullPanelLayout.kt @@ -86,11 +86,6 @@ fun PullPanelLayout( targetValue = offset, label = "offset" ) - LaunchedEffect(enabled) { - if (!enabled) { - state.collapse() - } - } SubcomposeLayout( modifier .draggable( diff --git a/material/src/main/java/com/m3u/material/components/TextFields.kt b/material/src/main/java/com/m3u/material/components/TextFields.kt index b6651e44e..77b113075 100644 --- a/material/src/main/java/com/m3u/material/components/TextFields.kt +++ b/material/src/main/java/com/m3u/material/components/TextFields.kt @@ -63,6 +63,7 @@ import com.m3u.material.ktx.InteractionType import com.m3u.material.ktx.interactionBorder import com.m3u.material.ktx.isTelevision import androidx.tv.material3.MaterialTheme as TvMaterialTheme +import androidx.tv.material3.Text as TvText @Composable fun TextField( @@ -376,7 +377,7 @@ private fun TvTextFieldImpl( ) { innerTextField() if (value.isEmpty()) { - androidx.tv.material3.Text( + TvText( modifier = Modifier.graphicsLayer { alpha = 0.6f }, text = placeholder, style = TvMaterialTheme.typography.titleSmall