Moved stream display to separate composable for reusability

This commit is contained in:
Isira Seneviratne 2024-07-08 22:24:30 +05:30
parent 462ed5c79a
commit 5e33b69316
2 changed files with 97 additions and 83 deletions

View File

@ -1,12 +1,7 @@
package org.schabi.newpipe.compose.playlist package org.schabi.newpipe.compose.playlist
import android.content.res.Configuration import android.content.res.Configuration
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.GridItemSpan import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -14,96 +9,47 @@ import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf 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.platform.LocalContext
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.lifecycle.SavedStateHandle
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.viewmodel.compose.viewModel import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.paging.Pager
import androidx.paging.PagingConfig
import androidx.paging.PagingData
import androidx.paging.compose.collectAsLazyPagingItems import androidx.paging.compose.collectAsLazyPagingItems
import kotlinx.coroutines.flow.Flow
import my.nanihadesuka.compose.LazyColumnScrollbar
import my.nanihadesuka.compose.LazyVerticalGridScrollbar
import org.schabi.newpipe.DownloaderImpl import org.schabi.newpipe.DownloaderImpl
import org.schabi.newpipe.compose.status.LoadingIndicator import org.schabi.newpipe.compose.status.LoadingIndicator
import org.schabi.newpipe.compose.stream.StreamCardItem import org.schabi.newpipe.compose.stream.StreamList
import org.schabi.newpipe.compose.stream.StreamGridItem
import org.schabi.newpipe.compose.stream.StreamListItem
import org.schabi.newpipe.compose.theme.AppTheme import org.schabi.newpipe.compose.theme.AppTheme
import org.schabi.newpipe.compose.util.determineItemViewMode
import org.schabi.newpipe.extractor.NewPipe 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.util.KEY_SERVICE_ID
import org.schabi.newpipe.extractor.stream.StreamInfoItem import org.schabi.newpipe.util.KEY_URL
import org.schabi.newpipe.info_list.ItemViewMode
import org.schabi.newpipe.paging.PlaylistItemsSource
import org.schabi.newpipe.util.NavigationHelper
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()
playlistInfo?.let { playlistInfo?.let {
LoadedPlaylist(it, playlistViewModel.streamItems) val streams = playlistViewModel.streamItems.collectAsLazyPagingItems()
} ?: LoadingIndicator() val totalDuration by remember {
} derivedStateOf {
} streams.itemSnapshotList.sumOf { it!!.duration }
@Composable
private fun LoadedPlaylist(playlistInfo: PlaylistInfo, flow: Flow<PagingData<StreamInfoItem>>) {
val streams = flow.collectAsLazyPagingItems()
val mode = determineItemViewMode()
val context = LocalContext.current
val totalDuration by remember {
derivedStateOf {
streams.itemSnapshotList.sumOf { it!!.duration }
}
}
val onClick = { stream: StreamInfoItem ->
NavigationHelper.openVideoDetailFragment(
context, (context as FragmentActivity).supportFragmentManager,
stream.serviceId, stream.url, stream.name, null, false
)
}
if (mode == ItemViewMode.GRID) {
val gridState = rememberLazyGridState()
LazyVerticalGridScrollbar(state = gridState) {
LazyVerticalGrid(state = gridState, columns = GridCells.Adaptive(250.dp)) {
item(span = { GridItemSpan(maxLineSpan) }) {
PlaylistHeader(playlistInfo, totalDuration)
}
items(streams.itemCount) {
StreamGridItem(streams[it]!!, onClick)
} }
} }
}
} else {
// Card or list views
val listState = rememberLazyListState()
LazyColumnScrollbar(state = listState) { StreamList(
LazyColumn(state = listState) { streams = streams,
item { gridHeader = {
PlaylistHeader(playlistInfo, totalDuration) item(span = { GridItemSpan(maxLineSpan) }) {
} PlaylistHeader(it, totalDuration)
}
items(streams.itemCount) { },
val stream = streams[it]!! listHeader = {
if (mode == ItemViewMode.CARD) { item {
StreamCardItem(stream, onClick) PlaylistHeader(it, totalDuration)
} else {
StreamListItem(stream, onClick)
} }
} }
} )
} } ?: LoadingIndicator()
} }
} }
@ -113,17 +59,13 @@ private fun LoadedPlaylist(playlistInfo: PlaylistInfo, flow: Flow<PagingData<Str
private fun PlaylistPreview() { private fun PlaylistPreview() {
NewPipe.init(DownloaderImpl.init(null)) NewPipe.init(DownloaderImpl.init(null))
val playlistInfo = PlaylistInfo.getInfo( val params = mapOf(
ServiceList.YouTube, KEY_SERVICE_ID to ServiceList.YouTube.serviceId,
"https://www.youtube.com/playlist?list=PLAIcZs9N4171hRrG_4v32Ca2hLvSuQ6QI" KEY_URL to "https://www.youtube.com/playlist?list=PLAIcZs9N4171hRrG_4v32Ca2hLvSuQ6QI"
) )
val streams = Pager(PagingConfig(pageSize = 20, enablePlaceholders = false)) {
PlaylistItemsSource(playlistInfo)
}.flow
AppTheme { AppTheme {
Surface(color = MaterialTheme.colorScheme.background) { Surface(color = MaterialTheme.colorScheme.background) {
LoadedPlaylist(playlistInfo, streams) Playlist(PlaylistViewModel(SavedStateHandle(params)))
} }
} }
} }

View File

@ -0,0 +1,72 @@
package org.schabi.newpipe.compose.stream
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.lazy.grid.GridCells
import androidx.compose.foundation.lazy.grid.LazyGridScope
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.fragment.app.FragmentActivity
import androidx.paging.compose.LazyPagingItems
import my.nanihadesuka.compose.LazyColumnScrollbar
import my.nanihadesuka.compose.LazyVerticalGridScrollbar
import org.schabi.newpipe.compose.util.determineItemViewMode
import org.schabi.newpipe.extractor.stream.StreamInfoItem
import org.schabi.newpipe.info_list.ItemViewMode
import org.schabi.newpipe.util.NavigationHelper
@Composable
fun StreamList(
streams: LazyPagingItems<StreamInfoItem>,
gridHeader: LazyGridScope.() -> Unit = {},
listHeader: LazyListScope.() -> Unit = {}
) {
val mode = determineItemViewMode()
val context = LocalContext.current
val onClick = remember {
{ stream: StreamInfoItem ->
NavigationHelper.openVideoDetailFragment(
context, (context as FragmentActivity).supportFragmentManager,
stream.serviceId, stream.url, stream.name, null, false
)
}
}
// TODO: Handle long-click by showing a dropdown menu instead of a dialog.
if (mode == ItemViewMode.GRID) {
val gridState = rememberLazyGridState()
LazyVerticalGridScrollbar(state = gridState) {
LazyVerticalGrid(state = gridState, columns = GridCells.Adaptive(250.dp)) {
gridHeader()
items(streams.itemCount) {
StreamGridItem(streams[it]!!, onClick)
}
}
}
} else {
// Card or list views
val listState = rememberLazyListState()
LazyColumnScrollbar(state = listState) {
LazyColumn(state = listState) {
listHeader()
items(streams.itemCount) {
val stream = streams[it]!!
if (mode == ItemViewMode.CARD) {
StreamCardItem(stream, onClick)
} else {
StreamListItem(stream, onClick)
}
}
}
}
}
}