mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-01-10 01:10:33 +00:00
Remove playlist preview dependency on external HTTP calls
This commit is contained in:
parent
b9556a1331
commit
82e5b6b1e9
@ -10,26 +10,35 @@ 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun Playlist(
|
||||||
|
playlistInfo: PlaylistInfo?,
|
||||||
|
streamFlow: Flow<PagingData<StreamInfoItem>>
|
||||||
|
) {
|
||||||
playlistInfo?.let {
|
playlistInfo?.let {
|
||||||
val streams = playlistViewModel.streamItems.collectAsLazyPagingItems()
|
val streams = streamFlow.collectAsLazyPagingItems()
|
||||||
val totalDuration by remember {
|
val totalDuration by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
streams.itemSnapshotList.sumOf { it!!.duration }
|
streams.itemSnapshotList.sumOf { it!!.duration }
|
||||||
@ -50,22 +59,23 @@ fun Playlist(playlistViewModel: PlaylistViewModel = viewModel()) {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
} ?: LoadingIndicator()
|
} ?: 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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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?
|
||||||
|
)
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
|
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user