1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2025-01-10 09:20:31 +00:00

Remove playlist preview dependency on external HTTP calls

This commit is contained in:
Isira Seneviratne 2024-07-22 08:11:16 +05:30
parent b9556a1331
commit 82e5b6b1e9
7 changed files with 93 additions and 53 deletions

View File

@ -10,62 +10,72 @@ import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.paging.PagingData
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import org.schabi.newpipe.DownloaderImpl import kotlinx.coroutines.flow.Flow
import org.schabi.newpipe.compose.status.LoadingIndicator 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.stream.StreamList
import org.schabi.newpipe.compose.theme.AppTheme import org.schabi.newpipe.compose.theme.AppTheme
import org.schabi.newpipe.extractor.NewPipe import org.schabi.newpipe.extractor.stream.Description
import org.schabi.newpipe.extractor.ServiceList import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.util.KEY_SERVICE_ID import org.schabi.newpipe.extractor.stream.StreamType
import org.schabi.newpipe.util.KEY_URL
import org.schabi.newpipe.viewmodels.PlaylistViewModel import org.schabi.newpipe.viewmodels.PlaylistViewModel
@Composable @Composable
fun Playlist(playlistViewModel: PlaylistViewModel = viewModel()) { fun Playlist(playlistViewModel: PlaylistViewModel = viewModel()) {
Surface(color = MaterialTheme.colorScheme.background) { Surface(color = MaterialTheme.colorScheme.background) {
val playlistInfo by playlistViewModel.playlistInfo.collectAsState() val playlistInfo by playlistViewModel.playlistInfo.collectAsState()
Playlist(playlistInfo, playlistViewModel.streamItems)
}
}
playlistInfo?.let { @Composable
val streams = playlistViewModel.streamItems.collectAsLazyPagingItems() private fun Playlist(
val totalDuration by remember { playlistInfo: PlaylistInfo?,
derivedStateOf { streamFlow: Flow<PagingData<StreamInfoItem>>
streams.itemSnapshotList.sumOf { it!!.duration } ) {
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( } ?: LoadingIndicator()
streams = streams,
gridHeader = {
item(span = { GridItemSpan(maxLineSpan) }) {
PlaylistHeader(it, totalDuration)
}
},
listHeader = {
item {
PlaylistHeader(it, totalDuration)
}
}
)
} ?: LoadingIndicator()
}
} }
@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO) @Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO)
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES) @Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable @Composable
private fun PlaylistPreview() { private fun PlaylistPreview() {
NewPipe.init(DownloaderImpl.init(null)) val description = Description("Example description", Description.PLAIN_TEXT)
val playlistInfo = PlaylistInfo(
val params = mapOf( "", 1, "", "Example playlist", description, listOf(), 1L,
KEY_SERVICE_ID to ServiceList.YouTube.serviceId, null, "Uploader", listOf(), null
KEY_URL to "https://www.youtube.com/playlist?list=PLAIcZs9N4171hRrG_4v32Ca2hLvSuQ6QI"
) )
val stream = StreamInfoItem(streamType = StreamType.VIDEO_STREAM)
val streamFlow = flowOf(PagingData.from(listOf(stream)))
AppTheme { AppTheme {
Surface(color = MaterialTheme.colorScheme.background) { Surface(color = MaterialTheme.colorScheme.background) {
Playlist(PlaylistViewModel(SavedStateHandle(params))) Playlist(playlistInfo, streamFlow)
} }
} }
} }

View File

@ -34,14 +34,11 @@ import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.fragment.app.FragmentActivity import androidx.fragment.app.FragmentActivity
import coil.compose.AsyncImage import coil.compose.AsyncImage
import org.schabi.newpipe.DownloaderImpl
import org.schabi.newpipe.R import org.schabi.newpipe.R
import org.schabi.newpipe.compose.common.DescriptionText import org.schabi.newpipe.compose.common.DescriptionText
import org.schabi.newpipe.compose.theme.AppTheme import org.schabi.newpipe.compose.theme.AppTheme
import org.schabi.newpipe.error.ErrorUtil import org.schabi.newpipe.error.ErrorUtil
import org.schabi.newpipe.extractor.NewPipe
import org.schabi.newpipe.extractor.ServiceList 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.services.youtube.YoutubeParsingHelper
import org.schabi.newpipe.extractor.stream.Description import org.schabi.newpipe.extractor.stream.Description
import org.schabi.newpipe.util.Localization import org.schabi.newpipe.util.Localization
@ -67,7 +64,7 @@ fun PlaylistHeader(playlistInfo: PlaylistInfo, totalDuration: Long) {
NavigationHelper.openChannelFragment( NavigationHelper.openChannelFragment(
(context as FragmentActivity).supportFragmentManager, (context as FragmentActivity).supportFragmentManager,
playlistInfo.serviceId, playlistInfo.uploaderUrl, playlistInfo.serviceId, playlistInfo.uploaderUrl,
playlistInfo.uploaderName playlistInfo.uploaderName!!
) )
} catch (e: Exception) { } catch (e: Exception) {
ErrorUtil.showUiErrorSnackbar(context, "Opening channel fragment", e) 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) Text(text = "$count$formattedDuration", style = MaterialTheme.typography.bodySmall)
} }
val description = playlistInfo.description ?: Description.EMPTY_DESCRIPTION if (playlistInfo.description != Description.EMPTY_DESCRIPTION) {
if (description != Description.EMPTY_DESCRIPTION) {
var isExpanded by rememberSaveable { mutableStateOf(false) } var isExpanded by rememberSaveable { mutableStateOf(false) }
var isExpandable by rememberSaveable { mutableStateOf(false) } var isExpandable by rememberSaveable { mutableStateOf(false) }
DescriptionText( DescriptionText(
modifier = Modifier.animateContentSize(), modifier = Modifier.animateContentSize(),
description = description, description = playlistInfo.description,
maxLines = if (isExpanded) Int.MAX_VALUE else 5, maxLines = if (isExpanded) Int.MAX_VALUE else 5,
style = MaterialTheme.typography.bodyMedium, style = MaterialTheme.typography.bodyMedium,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
@ -144,10 +140,10 @@ fun PlaylistHeader(playlistInfo: PlaylistInfo, totalDuration: Long) {
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES) @Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
@Composable @Composable
private fun PlaylistHeaderPreview() { private fun PlaylistHeaderPreview() {
NewPipe.init(DownloaderImpl.init(null)) val description = Description("Example description", Description.PLAIN_TEXT)
val playlistInfo = PlaylistInfo.getInfo( val playlistInfo = PlaylistInfo(
ServiceList.YouTube, "", 1, "", "Example playlist", description, listOf(), 1L,
"https://www.youtube.com/playlist?list=PLAIcZs9N4171hRrG_4v32Ca2hLvSuQ6QI" null, "Uploader", listOf(), null
) )
AppTheme { AppTheme {

View File

@ -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<StreamInfoItem>,
val streamCount: Long,
val uploaderUrl: String?,
val uploaderName: String?,
val uploaderAvatars: List<Image>,
val nextPage: Page?
)

View File

@ -26,10 +26,10 @@ import org.schabi.newpipe.util.NavigationHelper
@Composable @Composable
fun StreamList( fun StreamList(
streams: LazyPagingItems<StreamInfoItem>, streams: LazyPagingItems<StreamInfoItem>,
itemViewMode: ItemViewMode = determineItemViewMode(),
gridHeader: LazyGridScope.() -> Unit = {}, gridHeader: LazyGridScope.() -> Unit = {},
listHeader: LazyListScope.() -> Unit = {} listHeader: LazyListScope.() -> Unit = {}
) { ) {
val mode = determineItemViewMode()
val context = LocalContext.current val context = LocalContext.current
val onClick = remember { val onClick = remember {
{ stream: StreamInfoItem -> { stream: StreamInfoItem ->
@ -54,7 +54,7 @@ fun StreamList(
} }
} }
if (mode == ItemViewMode.GRID) { if (itemViewMode == ItemViewMode.GRID) {
val gridState = rememberLazyGridState() val gridState = rememberLazyGridState()
LazyVerticalGridScrollbar(state = gridState) { LazyVerticalGridScrollbar(state = gridState) {
@ -82,7 +82,7 @@ fun StreamList(
val stream = streams[it]!! val stream = streams[it]!!
val isSelected = selectedStream == stream val isSelected = selectedStream == stream
if (mode == ItemViewMode.CARD) { if (itemViewMode == ItemViewMode.CARD) {
StreamCardItem(stream, isSelected, onClick, onLongClick, onDismissPopup) StreamCardItem(stream, isSelected, onClick, onLongClick, onDismissPopup)
} else { } else {
StreamListItem(stream, isSelected, onClick, onLongClick, onDismissPopup) StreamListItem(stream, isSelected, onClick, onLongClick, onDismissPopup)

View File

@ -4,10 +4,11 @@ import androidx.paging.PagingSource
import androidx.paging.PagingState import androidx.paging.PagingState
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.schabi.newpipe.compose.playlist.PlaylistInfo
import org.schabi.newpipe.extractor.NewPipe import org.schabi.newpipe.extractor.NewPipe
import org.schabi.newpipe.extractor.Page 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.stream.StreamInfoItem
import org.schabi.newpipe.extractor.playlist.PlaylistInfo as ExtractorPlaylistInfo
class PlaylistItemsSource( class PlaylistItemsSource(
private val playlistInfo: PlaylistInfo, private val playlistInfo: PlaylistInfo,
@ -17,7 +18,8 @@ class PlaylistItemsSource(
override suspend fun load(params: LoadParams<Page>): LoadResult<Page, StreamInfoItem> { override suspend fun load(params: LoadParams<Page>): LoadResult<Page, StreamInfoItem> {
return params.key?.let { return params.key?.let {
withContext(Dispatchers.IO) { 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(response.items, null, response.nextPage)
} }
} ?: LoadResult.Page(playlistInfo.relatedItems, null, playlistInfo.nextPage) } ?: LoadResult.Page(playlistInfo.relatedItems, null, playlistInfo.nextPage)

View File

@ -11,7 +11,9 @@ import androidx.compose.ui.Modifier
@Composable @Composable
fun LoadingIndicator(modifier: Modifier = Modifier) { fun LoadingIndicator(modifier: Modifier = Modifier) {
CircularProgressIndicator( CircularProgressIndicator(
modifier = modifier.fillMaxSize().wrapContentSize(Alignment.Center), modifier = modifier
.fillMaxSize()
.wrapContentSize(Alignment.Center),
color = MaterialTheme.colorScheme.primary, color = MaterialTheme.colorScheme.primary,
trackColor = MaterialTheme.colorScheme.surfaceVariant, trackColor = MaterialTheme.colorScheme.surfaceVariant,
) )

View File

@ -14,19 +14,27 @@ import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import org.schabi.newpipe.compose.playlist.PlaylistInfo
import org.schabi.newpipe.extractor.NewPipe 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.paging.PlaylistItemsSource
import org.schabi.newpipe.util.KEY_SERVICE_ID import org.schabi.newpipe.util.KEY_SERVICE_ID
import org.schabi.newpipe.util.KEY_URL import org.schabi.newpipe.util.KEY_URL
import org.schabi.newpipe.util.NO_SERVICE_ID import org.schabi.newpipe.util.NO_SERVICE_ID
import org.schabi.newpipe.extractor.playlist.PlaylistInfo as ExtractorPlaylistInfo
class PlaylistViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { class PlaylistViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
private val serviceIdState = savedStateHandle.getStateFlow(KEY_SERVICE_ID, NO_SERVICE_ID) private val serviceIdState = savedStateHandle.getStateFlow(KEY_SERVICE_ID, NO_SERVICE_ID)
private val urlState = savedStateHandle.getStateFlow(KEY_URL, "") private val urlState = savedStateHandle.getStateFlow(KEY_URL, "")
val playlistInfo = serviceIdState.combine(urlState) { id, 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) .flowOn(Dispatchers.IO)
.stateIn(viewModelScope, SharingStarted.Eagerly, null) .stateIn(viewModelScope, SharingStarted.Eagerly, null)