1
0
mirror of https://github.com/TeamNewPipe/NewPipe synced 2025-01-02 13:30:31 +00:00

Use UI state classes in playlist

This commit is contained in:
Isira Seneviratne 2024-12-29 09:33:29 +05:30
parent 52a2accea9
commit b644160eb1
2 changed files with 66 additions and 40 deletions

View File

@ -5,11 +5,11 @@ import androidx.compose.foundation.lazy.grid.GridItemSpan
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
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.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import androidx.paging.PagingData
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.playlist.PlaylistHeader
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.viewmodels.PlaylistViewModel
import org.schabi.newpipe.viewmodels.util.Resource
@Composable
fun PlaylistScreen(playlistViewModel: PlaylistViewModel = viewModel()) {
Surface(color = MaterialTheme.colorScheme.background) {
val playlistInfo by playlistViewModel.playlistInfo.collectAsState()
PlaylistScreen(playlistInfo, playlistViewModel.streamItems)
val uiState by playlistViewModel.uiState.collectAsStateWithLifecycle()
PlaylistScreen(uiState, playlistViewModel.streamItems)
}
}
@Composable
private fun PlaylistScreen(
playlistInfo: PlaylistInfo?,
uiState: Resource<PlaylistInfo>,
streamFlow: Flow<PagingData<StreamInfoItem>>
) {
playlistInfo?.let {
val streams = streamFlow.collectAsLazyPagingItems()
when (uiState) {
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
// happen. As such, the duration initially displayed will be the incomplete duration if more
// items can be loaded.
val totalDuration by remember {
derivedStateOf {
streams.itemSnapshotList.sumOf { it!!.duration }
// 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
// items can be loaded.
val totalDuration by remember {
derivedStateOf {
streams.itemSnapshotList.sumOf { it!!.duration }
}
}
ItemList(
items = streams,
gridHeader = {
item(span = { GridItemSpan(maxLineSpan) }) {
PlaylistHeader(info, totalDuration)
}
},
listHeader = {
item {
PlaylistHeader(info, totalDuration)
}
}
)
}
ItemList(
items = streams,
gridHeader = {
item(span = { GridItemSpan(maxLineSpan) }) {
PlaylistHeader(it, totalDuration)
}
},
listHeader = {
item {
PlaylistHeader(it, totalDuration)
}
}
)
} ?: LoadingIndicator()
is Resource.Loading -> {
LoadingIndicator()
}
is Resource.Error -> {
// TODO use error panel instead
EmptyStateComposable(
EmptyStateSpec.DisabledComments.copy(
descriptionText = { "Could not load streams" }
)
)
}
}
}
@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO)
@ -81,7 +100,7 @@ private fun PlaylistPreview() {
AppTheme {
Surface(color = MaterialTheme.colorScheme.background) {
PlaylistScreen(playlistInfo, streamFlow)
PlaylistScreen(Resource.Success(playlistInfo), streamFlow)
}
}
}

View File

@ -10,7 +10,7 @@ import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.SharingStarted
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.filterIsInstance
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flowOn
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_URL
import org.schabi.newpipe.util.NO_SERVICE_ID
import org.schabi.newpipe.viewmodels.util.Resource
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 ->
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
)
val uiState = serviceIdState.combine(urlState) { id, url ->
try {
val extractorInfo = ExtractorPlaylistInfo.getInfo(NewPipe.getService(id), url)
val description = extractorInfo.description ?: Description.EMPTY_DESCRIPTION
val info = PlaylistInfo(
extractorInfo.id, extractorInfo.serviceId, extractorInfo.url, extractorInfo.name,
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)
.stateIn(viewModelScope, SharingStarted.Eagerly, null)
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), Resource.Loading)
@OptIn(ExperimentalCoroutinesApi::class)
val streamItems = playlistInfo
.filterNotNull()
val streamItems = uiState
.filterIsInstance<Resource.Success<PlaylistInfo>>()
.flatMapLatest {
Pager(PagingConfig(pageSize = 20, enablePlaceholders = false)) {
PlaylistItemsSource(it)
PlaylistItemsSource(it.data)
}.flow
}
.cachedIn(viewModelScope)