From 82e5b6b1e93c0f42c0d1187e2bff1a24d144ae03 Mon Sep 17 00:00:00 2001 From: Isira Seneviratne Date: Mon, 22 Jul 2024 08:11:16 +0530 Subject: [PATCH] Remove playlist preview dependency on external HTTP calls --- .../newpipe/compose/playlist/Playlist.kt | 76 +++++++++++-------- .../compose/playlist/PlaylistHeader.kt | 18 ++--- .../newpipe/compose/playlist/PlaylistInfo.kt | 22 ++++++ .../newpipe/compose/stream/StreamList.kt | 6 +- .../newpipe/paging/PlaylistItemsSource.kt | 6 +- .../ui/components/common/LoadingIndicator.kt | 4 +- .../newpipe/viewmodels/PlaylistViewModel.kt | 12 ++- 7 files changed, 92 insertions(+), 52 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/compose/playlist/PlaylistInfo.kt diff --git a/app/src/main/java/org/schabi/newpipe/compose/playlist/Playlist.kt b/app/src/main/java/org/schabi/newpipe/compose/playlist/Playlist.kt index 87f9eef6627..7d649e49c63 100644 --- a/app/src/main/java/org/schabi/newpipe/compose/playlist/Playlist.kt +++ b/app/src/main/java/org/schabi/newpipe/compose/playlist/Playlist.kt @@ -10,62 +10,72 @@ import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.remember import androidx.compose.ui.tooling.preview.Preview -import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.paging.PagingData import androidx.paging.compose.collectAsLazyPagingItems -import org.schabi.newpipe.DownloaderImpl -import org.schabi.newpipe.compose.status.LoadingIndicator +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOf +import org.schabi.newpipe.compose.common.LoadingIndicator +import org.schabi.newpipe.compose.stream.StreamInfoItem import org.schabi.newpipe.compose.stream.StreamList import org.schabi.newpipe.compose.theme.AppTheme -import org.schabi.newpipe.extractor.NewPipe -import org.schabi.newpipe.extractor.ServiceList -import org.schabi.newpipe.util.KEY_SERVICE_ID -import org.schabi.newpipe.util.KEY_URL +import org.schabi.newpipe.extractor.stream.Description +import org.schabi.newpipe.extractor.stream.StreamInfoItem +import org.schabi.newpipe.extractor.stream.StreamType import org.schabi.newpipe.viewmodels.PlaylistViewModel @Composable fun Playlist(playlistViewModel: PlaylistViewModel = viewModel()) { Surface(color = MaterialTheme.colorScheme.background) { val playlistInfo by playlistViewModel.playlistInfo.collectAsState() + Playlist(playlistInfo, playlistViewModel.streamItems) + } +} - playlistInfo?.let { - val streams = playlistViewModel.streamItems.collectAsLazyPagingItems() - val totalDuration by remember { - derivedStateOf { - streams.itemSnapshotList.sumOf { it!!.duration } - } +@Composable +private fun Playlist( + playlistInfo: PlaylistInfo?, + streamFlow: Flow> +) { + playlistInfo?.let { + val streams = streamFlow.collectAsLazyPagingItems() + val totalDuration by remember { + derivedStateOf { + streams.itemSnapshotList.sumOf { it!!.duration } } + } - StreamList( - streams = streams, - gridHeader = { - item(span = { GridItemSpan(maxLineSpan) }) { - PlaylistHeader(it, totalDuration) - } - }, - listHeader = { - item { - PlaylistHeader(it, totalDuration) - } + StreamList( + streams = streams, + gridHeader = { + item(span = { GridItemSpan(maxLineSpan) }) { + PlaylistHeader(it, totalDuration) } - ) - } ?: LoadingIndicator() - } + }, + listHeader = { + item { + PlaylistHeader(it, totalDuration) + } + } + ) + } ?: LoadingIndicator() } @Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO) @Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable private fun PlaylistPreview() { - NewPipe.init(DownloaderImpl.init(null)) - - val params = mapOf( - KEY_SERVICE_ID to ServiceList.YouTube.serviceId, - KEY_URL to "https://www.youtube.com/playlist?list=PLAIcZs9N4171hRrG_4v32Ca2hLvSuQ6QI" + val description = Description("Example description", Description.PLAIN_TEXT) + val playlistInfo = PlaylistInfo( + "", 1, "", "Example playlist", description, listOf(), 1L, + null, "Uploader", listOf(), null ) + val stream = StreamInfoItem(streamType = StreamType.VIDEO_STREAM) + val streamFlow = flowOf(PagingData.from(listOf(stream))) + AppTheme { Surface(color = MaterialTheme.colorScheme.background) { - Playlist(PlaylistViewModel(SavedStateHandle(params))) + Playlist(playlistInfo, streamFlow) } } } diff --git a/app/src/main/java/org/schabi/newpipe/compose/playlist/PlaylistHeader.kt b/app/src/main/java/org/schabi/newpipe/compose/playlist/PlaylistHeader.kt index 0be5fe99ab8..fca84c0de37 100644 --- a/app/src/main/java/org/schabi/newpipe/compose/playlist/PlaylistHeader.kt +++ b/app/src/main/java/org/schabi/newpipe/compose/playlist/PlaylistHeader.kt @@ -34,14 +34,11 @@ import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.fragment.app.FragmentActivity import coil.compose.AsyncImage -import org.schabi.newpipe.DownloaderImpl import org.schabi.newpipe.R import org.schabi.newpipe.compose.common.DescriptionText import org.schabi.newpipe.compose.theme.AppTheme import org.schabi.newpipe.error.ErrorUtil -import org.schabi.newpipe.extractor.NewPipe import org.schabi.newpipe.extractor.ServiceList -import org.schabi.newpipe.extractor.playlist.PlaylistInfo import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper import org.schabi.newpipe.extractor.stream.Description import org.schabi.newpipe.util.Localization @@ -67,7 +64,7 @@ fun PlaylistHeader(playlistInfo: PlaylistInfo, totalDuration: Long) { NavigationHelper.openChannelFragment( (context as FragmentActivity).supportFragmentManager, playlistInfo.serviceId, playlistInfo.uploaderUrl, - playlistInfo.uploaderName + playlistInfo.uploaderName!! ) } catch (e: Exception) { ErrorUtil.showUiErrorSnackbar(context, "Opening channel fragment", e) @@ -108,14 +105,13 @@ fun PlaylistHeader(playlistInfo: PlaylistInfo, totalDuration: Long) { Text(text = "$count • $formattedDuration", style = MaterialTheme.typography.bodySmall) } - val description = playlistInfo.description ?: Description.EMPTY_DESCRIPTION - if (description != Description.EMPTY_DESCRIPTION) { + if (playlistInfo.description != Description.EMPTY_DESCRIPTION) { var isExpanded by rememberSaveable { mutableStateOf(false) } var isExpandable by rememberSaveable { mutableStateOf(false) } DescriptionText( modifier = Modifier.animateContentSize(), - description = description, + description = playlistInfo.description, maxLines = if (isExpanded) Int.MAX_VALUE else 5, style = MaterialTheme.typography.bodyMedium, overflow = TextOverflow.Ellipsis, @@ -144,10 +140,10 @@ fun PlaylistHeader(playlistInfo: PlaylistInfo, totalDuration: Long) { @Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES) @Composable private fun PlaylistHeaderPreview() { - NewPipe.init(DownloaderImpl.init(null)) - val playlistInfo = PlaylistInfo.getInfo( - ServiceList.YouTube, - "https://www.youtube.com/playlist?list=PLAIcZs9N4171hRrG_4v32Ca2hLvSuQ6QI" + val description = Description("Example description", Description.PLAIN_TEXT) + val playlistInfo = PlaylistInfo( + "", 1, "", "Example playlist", description, listOf(), 1L, + null, "Uploader", listOf(), null ) AppTheme { diff --git a/app/src/main/java/org/schabi/newpipe/compose/playlist/PlaylistInfo.kt b/app/src/main/java/org/schabi/newpipe/compose/playlist/PlaylistInfo.kt new file mode 100644 index 00000000000..ded4d7b7ecf --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/compose/playlist/PlaylistInfo.kt @@ -0,0 +1,22 @@ +package org.schabi.newpipe.compose.playlist + +import androidx.compose.runtime.Immutable +import org.schabi.newpipe.extractor.Image +import org.schabi.newpipe.extractor.Page +import org.schabi.newpipe.extractor.stream.Description +import org.schabi.newpipe.extractor.stream.StreamInfoItem + +@Immutable +class PlaylistInfo( + val id: String, + val serviceId: Int, + val url: String, + val name: String, + val description: Description, + val relatedItems: List, + val streamCount: Long, + val uploaderUrl: String?, + val uploaderName: String?, + val uploaderAvatars: List, + val nextPage: Page? +) diff --git a/app/src/main/java/org/schabi/newpipe/compose/stream/StreamList.kt b/app/src/main/java/org/schabi/newpipe/compose/stream/StreamList.kt index e9c161d812b..bcf9fc62eb7 100644 --- a/app/src/main/java/org/schabi/newpipe/compose/stream/StreamList.kt +++ b/app/src/main/java/org/schabi/newpipe/compose/stream/StreamList.kt @@ -26,10 +26,10 @@ import org.schabi.newpipe.util.NavigationHelper @Composable fun StreamList( streams: LazyPagingItems, + itemViewMode: ItemViewMode = determineItemViewMode(), gridHeader: LazyGridScope.() -> Unit = {}, listHeader: LazyListScope.() -> Unit = {} ) { - val mode = determineItemViewMode() val context = LocalContext.current val onClick = remember { { stream: StreamInfoItem -> @@ -54,7 +54,7 @@ fun StreamList( } } - if (mode == ItemViewMode.GRID) { + if (itemViewMode == ItemViewMode.GRID) { val gridState = rememberLazyGridState() LazyVerticalGridScrollbar(state = gridState) { @@ -82,7 +82,7 @@ fun StreamList( val stream = streams[it]!! val isSelected = selectedStream == stream - if (mode == ItemViewMode.CARD) { + if (itemViewMode == ItemViewMode.CARD) { StreamCardItem(stream, isSelected, onClick, onLongClick, onDismissPopup) } else { StreamListItem(stream, isSelected, onClick, onLongClick, onDismissPopup) diff --git a/app/src/main/java/org/schabi/newpipe/paging/PlaylistItemsSource.kt b/app/src/main/java/org/schabi/newpipe/paging/PlaylistItemsSource.kt index 400b5ab349d..369ede2bfdb 100644 --- a/app/src/main/java/org/schabi/newpipe/paging/PlaylistItemsSource.kt +++ b/app/src/main/java/org/schabi/newpipe/paging/PlaylistItemsSource.kt @@ -4,10 +4,11 @@ import androidx.paging.PagingSource import androidx.paging.PagingState import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import org.schabi.newpipe.compose.playlist.PlaylistInfo import org.schabi.newpipe.extractor.NewPipe import org.schabi.newpipe.extractor.Page -import org.schabi.newpipe.extractor.playlist.PlaylistInfo import org.schabi.newpipe.extractor.stream.StreamInfoItem +import org.schabi.newpipe.extractor.playlist.PlaylistInfo as ExtractorPlaylistInfo class PlaylistItemsSource( private val playlistInfo: PlaylistInfo, @@ -17,7 +18,8 @@ class PlaylistItemsSource( override suspend fun load(params: LoadParams): LoadResult { return params.key?.let { withContext(Dispatchers.IO) { - val response = PlaylistInfo.getMoreItems(service, playlistInfo.url, playlistInfo.nextPage) + val response = ExtractorPlaylistInfo + .getMoreItems(service, playlistInfo.url, playlistInfo.nextPage) LoadResult.Page(response.items, null, response.nextPage) } } ?: LoadResult.Page(playlistInfo.relatedItems, null, playlistInfo.nextPage) diff --git a/app/src/main/java/org/schabi/newpipe/ui/components/common/LoadingIndicator.kt b/app/src/main/java/org/schabi/newpipe/ui/components/common/LoadingIndicator.kt index 3bfe1dee489..4a6a88190ca 100644 --- a/app/src/main/java/org/schabi/newpipe/ui/components/common/LoadingIndicator.kt +++ b/app/src/main/java/org/schabi/newpipe/ui/components/common/LoadingIndicator.kt @@ -11,7 +11,9 @@ import androidx.compose.ui.Modifier @Composable fun LoadingIndicator(modifier: Modifier = Modifier) { CircularProgressIndicator( - modifier = modifier.fillMaxSize().wrapContentSize(Alignment.Center), + modifier = modifier + .fillMaxSize() + .wrapContentSize(Alignment.Center), color = MaterialTheme.colorScheme.primary, trackColor = MaterialTheme.colorScheme.surfaceVariant, ) diff --git a/app/src/main/java/org/schabi/newpipe/viewmodels/PlaylistViewModel.kt b/app/src/main/java/org/schabi/newpipe/viewmodels/PlaylistViewModel.kt index d45c393276f..7e480277fee 100644 --- a/app/src/main/java/org/schabi/newpipe/viewmodels/PlaylistViewModel.kt +++ b/app/src/main/java/org/schabi/newpipe/viewmodels/PlaylistViewModel.kt @@ -14,19 +14,27 @@ import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.stateIn +import org.schabi.newpipe.compose.playlist.PlaylistInfo import org.schabi.newpipe.extractor.NewPipe -import org.schabi.newpipe.extractor.playlist.PlaylistInfo +import org.schabi.newpipe.extractor.stream.Description import org.schabi.newpipe.paging.PlaylistItemsSource import org.schabi.newpipe.util.KEY_SERVICE_ID import org.schabi.newpipe.util.KEY_URL import org.schabi.newpipe.util.NO_SERVICE_ID +import org.schabi.newpipe.extractor.playlist.PlaylistInfo as ExtractorPlaylistInfo class PlaylistViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { private val serviceIdState = savedStateHandle.getStateFlow(KEY_SERVICE_ID, NO_SERVICE_ID) private val urlState = savedStateHandle.getStateFlow(KEY_URL, "") val playlistInfo = serviceIdState.combine(urlState) { id, url -> - PlaylistInfo.getInfo(NewPipe.getService(id), url) + val info = ExtractorPlaylistInfo.getInfo(NewPipe.getService(id), url) + val description = info.description ?: Description.EMPTY_DESCRIPTION + PlaylistInfo( + info.id, info.serviceId, info.url, info.name, description, info.relatedItems, + info.streamCount, info.uploaderUrl, info.uploaderName, info.uploaderAvatars, + info.nextPage + ) } .flowOn(Dispatchers.IO) .stateIn(viewModelScope, SharingStarted.Eagerly, null)