mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-01-04 22:40:32 +00:00
Use UI state classes in playlist
This commit is contained in:
parent
52a2accea9
commit
b644160eb1
@ -5,11 +5,11 @@ import androidx.compose.foundation.lazy.grid.GridItemSpan
|
|||||||
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
|
||||||
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.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.paging.PagingData
|
import androidx.paging.PagingData
|
||||||
import androidx.paging.compose.collectAsLazyPagingItems
|
import androidx.paging.compose.collectAsLazyPagingItems
|
||||||
@ -23,48 +23,67 @@ import org.schabi.newpipe.ui.components.items.ItemList
|
|||||||
import org.schabi.newpipe.ui.components.items.stream.StreamInfoItem
|
import org.schabi.newpipe.ui.components.items.stream.StreamInfoItem
|
||||||
import org.schabi.newpipe.ui.components.playlist.PlaylistHeader
|
import org.schabi.newpipe.ui.components.playlist.PlaylistHeader
|
||||||
import org.schabi.newpipe.ui.components.playlist.PlaylistInfo
|
import org.schabi.newpipe.ui.components.playlist.PlaylistInfo
|
||||||
|
import org.schabi.newpipe.ui.emptystate.EmptyStateComposable
|
||||||
|
import org.schabi.newpipe.ui.emptystate.EmptyStateSpec
|
||||||
import org.schabi.newpipe.ui.theme.AppTheme
|
import org.schabi.newpipe.ui.theme.AppTheme
|
||||||
import org.schabi.newpipe.viewmodels.PlaylistViewModel
|
import org.schabi.newpipe.viewmodels.PlaylistViewModel
|
||||||
|
import org.schabi.newpipe.viewmodels.util.Resource
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun PlaylistScreen(playlistViewModel: PlaylistViewModel = viewModel()) {
|
fun PlaylistScreen(playlistViewModel: PlaylistViewModel = viewModel()) {
|
||||||
Surface(color = MaterialTheme.colorScheme.background) {
|
Surface(color = MaterialTheme.colorScheme.background) {
|
||||||
val playlistInfo by playlistViewModel.playlistInfo.collectAsState()
|
val uiState by playlistViewModel.uiState.collectAsStateWithLifecycle()
|
||||||
PlaylistScreen(playlistInfo, playlistViewModel.streamItems)
|
PlaylistScreen(uiState, playlistViewModel.streamItems)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun PlaylistScreen(
|
private fun PlaylistScreen(
|
||||||
playlistInfo: PlaylistInfo?,
|
uiState: Resource<PlaylistInfo>,
|
||||||
streamFlow: Flow<PagingData<StreamInfoItem>>
|
streamFlow: Flow<PagingData<StreamInfoItem>>
|
||||||
) {
|
) {
|
||||||
playlistInfo?.let {
|
when (uiState) {
|
||||||
val streams = streamFlow.collectAsLazyPagingItems()
|
is Resource.Success -> {
|
||||||
|
val info = uiState.data
|
||||||
|
val streams = streamFlow.collectAsLazyPagingItems()
|
||||||
|
|
||||||
// Paging's load states only indicate when loading is currently happening, not if it can/will
|
// Paging's load states only indicate when loading is currently happening, not if it can/will
|
||||||
// happen. As such, the duration initially displayed will be the incomplete duration if more
|
// happen. As such, the duration initially displayed will be the incomplete duration if more
|
||||||
// items can be loaded.
|
// items can be loaded.
|
||||||
val totalDuration by remember {
|
val totalDuration by remember {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
streams.itemSnapshotList.sumOf { it!!.duration }
|
streams.itemSnapshotList.sumOf { it!!.duration }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ItemList(
|
||||||
|
items = streams,
|
||||||
|
gridHeader = {
|
||||||
|
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||||
|
PlaylistHeader(info, totalDuration)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
listHeader = {
|
||||||
|
item {
|
||||||
|
PlaylistHeader(info, totalDuration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemList(
|
is Resource.Loading -> {
|
||||||
items = streams,
|
LoadingIndicator()
|
||||||
gridHeader = {
|
}
|
||||||
item(span = { GridItemSpan(maxLineSpan) }) {
|
|
||||||
PlaylistHeader(it, totalDuration)
|
is Resource.Error -> {
|
||||||
}
|
// TODO use error panel instead
|
||||||
},
|
EmptyStateComposable(
|
||||||
listHeader = {
|
EmptyStateSpec.DisabledComments.copy(
|
||||||
item {
|
descriptionText = { "Could not load streams" }
|
||||||
PlaylistHeader(it, totalDuration)
|
)
|
||||||
}
|
)
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
} ?: LoadingIndicator()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||||
@ -81,7 +100,7 @@ private fun PlaylistPreview() {
|
|||||||
|
|
||||||
AppTheme {
|
AppTheme {
|
||||||
Surface(color = MaterialTheme.colorScheme.background) {
|
Surface(color = MaterialTheme.colorScheme.background) {
|
||||||
PlaylistScreen(playlistInfo, streamFlow)
|
PlaylistScreen(Resource.Success(playlistInfo), streamFlow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,7 @@ import kotlinx.coroutines.Dispatchers
|
|||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.filterNotNull
|
import kotlinx.coroutines.flow.filterIsInstance
|
||||||
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
|
||||||
@ -21,30 +21,37 @@ import org.schabi.newpipe.ui.components.playlist.PlaylistInfo
|
|||||||
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.viewmodels.util.Resource
|
||||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo as ExtractorPlaylistInfo
|
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 uiState = serviceIdState.combine(urlState) { id, url ->
|
||||||
val info = ExtractorPlaylistInfo.getInfo(NewPipe.getService(id), url)
|
try {
|
||||||
val description = info.description ?: Description.EMPTY_DESCRIPTION
|
val extractorInfo = ExtractorPlaylistInfo.getInfo(NewPipe.getService(id), url)
|
||||||
PlaylistInfo(
|
val description = extractorInfo.description ?: Description.EMPTY_DESCRIPTION
|
||||||
info.id, info.serviceId, info.url, info.name, description, info.relatedItems,
|
val info = PlaylistInfo(
|
||||||
info.streamCount, info.uploaderUrl, info.uploaderName, info.uploaderAvatars,
|
extractorInfo.id, extractorInfo.serviceId, extractorInfo.url, extractorInfo.name,
|
||||||
info.nextPage
|
description, extractorInfo.relatedItems, extractorInfo.streamCount,
|
||||||
)
|
extractorInfo.uploaderUrl, extractorInfo.uploaderName, extractorInfo.uploaderAvatars,
|
||||||
|
extractorInfo.nextPage
|
||||||
|
)
|
||||||
|
Resource.Success(info)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Resource.Error(e)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.flowOn(Dispatchers.IO)
|
.flowOn(Dispatchers.IO)
|
||||||
.stateIn(viewModelScope, SharingStarted.Eagerly, null)
|
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), Resource.Loading)
|
||||||
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
val streamItems = playlistInfo
|
val streamItems = uiState
|
||||||
.filterNotNull()
|
.filterIsInstance<Resource.Success<PlaylistInfo>>()
|
||||||
.flatMapLatest {
|
.flatMapLatest {
|
||||||
Pager(PagingConfig(pageSize = 20, enablePlaceholders = false)) {
|
Pager(PagingConfig(pageSize = 20, enablePlaceholders = false)) {
|
||||||
PlaylistItemsSource(it)
|
PlaylistItemsSource(it.data)
|
||||||
}.flow
|
}.flow
|
||||||
}
|
}
|
||||||
.cachedIn(viewModelScope)
|
.cachedIn(viewModelScope)
|
||||||
|
Loading…
Reference in New Issue
Block a user