Skip to content

Commit b644160

Browse files
Use UI state classes in playlist
1 parent 52a2acc commit b644160

2 files changed

Lines changed: 66 additions & 40 deletions

File tree

app/src/main/java/org/schabi/newpipe/ui/screens/PlaylistScreen.kt

Lines changed: 46 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ import androidx.compose.foundation.lazy.grid.GridItemSpan
55
import androidx.compose.material3.MaterialTheme
66
import androidx.compose.material3.Surface
77
import androidx.compose.runtime.Composable
8-
import androidx.compose.runtime.collectAsState
98
import androidx.compose.runtime.derivedStateOf
109
import androidx.compose.runtime.getValue
1110
import androidx.compose.runtime.remember
1211
import androidx.compose.ui.tooling.preview.Preview
12+
import androidx.lifecycle.compose.collectAsStateWithLifecycle
1313
import androidx.lifecycle.viewmodel.compose.viewModel
1414
import androidx.paging.PagingData
1515
import androidx.paging.compose.collectAsLazyPagingItems
@@ -23,48 +23,67 @@ import org.schabi.newpipe.ui.components.items.ItemList
2323
import org.schabi.newpipe.ui.components.items.stream.StreamInfoItem
2424
import org.schabi.newpipe.ui.components.playlist.PlaylistHeader
2525
import org.schabi.newpipe.ui.components.playlist.PlaylistInfo
26+
import org.schabi.newpipe.ui.emptystate.EmptyStateComposable
27+
import org.schabi.newpipe.ui.emptystate.EmptyStateSpec
2628
import org.schabi.newpipe.ui.theme.AppTheme
2729
import org.schabi.newpipe.viewmodels.PlaylistViewModel
30+
import org.schabi.newpipe.viewmodels.util.Resource
2831

2932
@Composable
3033
fun PlaylistScreen(playlistViewModel: PlaylistViewModel = viewModel()) {
3134
Surface(color = MaterialTheme.colorScheme.background) {
32-
val playlistInfo by playlistViewModel.playlistInfo.collectAsState()
33-
PlaylistScreen(playlistInfo, playlistViewModel.streamItems)
35+
val uiState by playlistViewModel.uiState.collectAsStateWithLifecycle()
36+
PlaylistScreen(uiState, playlistViewModel.streamItems)
3437
}
3538
}
3639

3740
@Composable
3841
private fun PlaylistScreen(
39-
playlistInfo: PlaylistInfo?,
42+
uiState: Resource<PlaylistInfo>,
4043
streamFlow: Flow<PagingData<StreamInfoItem>>
4144
) {
42-
playlistInfo?.let {
43-
val streams = streamFlow.collectAsLazyPagingItems()
45+
when (uiState) {
46+
is Resource.Success -> {
47+
val info = uiState.data
48+
val streams = streamFlow.collectAsLazyPagingItems()
4449

45-
// Paging's load states only indicate when loading is currently happening, not if it can/will
46-
// happen. As such, the duration initially displayed will be the incomplete duration if more
47-
// items can be loaded.
48-
val totalDuration by remember {
49-
derivedStateOf {
50-
streams.itemSnapshotList.sumOf { it!!.duration }
50+
// Paging's load states only indicate when loading is currently happening, not if it can/will
51+
// happen. As such, the duration initially displayed will be the incomplete duration if more
52+
// items can be loaded.
53+
val totalDuration by remember {
54+
derivedStateOf {
55+
streams.itemSnapshotList.sumOf { it!!.duration }
56+
}
5157
}
52-
}
5358

54-
ItemList(
55-
items = streams,
56-
gridHeader = {
57-
item(span = { GridItemSpan(maxLineSpan) }) {
58-
PlaylistHeader(it, totalDuration)
59-
}
60-
},
61-
listHeader = {
62-
item {
63-
PlaylistHeader(it, totalDuration)
59+
ItemList(
60+
items = streams,
61+
gridHeader = {
62+
item(span = { GridItemSpan(maxLineSpan) }) {
63+
PlaylistHeader(info, totalDuration)
64+
}
65+
},
66+
listHeader = {
67+
item {
68+
PlaylistHeader(info, totalDuration)
69+
}
6470
}
65-
}
66-
)
67-
} ?: LoadingIndicator()
71+
)
72+
}
73+
74+
is Resource.Loading -> {
75+
LoadingIndicator()
76+
}
77+
78+
is Resource.Error -> {
79+
// TODO use error panel instead
80+
EmptyStateComposable(
81+
EmptyStateSpec.DisabledComments.copy(
82+
descriptionText = { "Could not load streams" }
83+
)
84+
)
85+
}
86+
}
6887
}
6988

7089
@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO)
@@ -81,7 +100,7 @@ private fun PlaylistPreview() {
81100

82101
AppTheme {
83102
Surface(color = MaterialTheme.colorScheme.background) {
84-
PlaylistScreen(playlistInfo, streamFlow)
103+
PlaylistScreen(Resource.Success(playlistInfo), streamFlow)
85104
}
86105
}
87106
}

app/src/main/java/org/schabi/newpipe/viewmodels/PlaylistViewModel.kt

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import kotlinx.coroutines.Dispatchers
1010
import kotlinx.coroutines.ExperimentalCoroutinesApi
1111
import kotlinx.coroutines.flow.SharingStarted
1212
import kotlinx.coroutines.flow.combine
13-
import kotlinx.coroutines.flow.filterNotNull
13+
import kotlinx.coroutines.flow.filterIsInstance
1414
import kotlinx.coroutines.flow.flatMapLatest
1515
import kotlinx.coroutines.flow.flowOn
1616
import kotlinx.coroutines.flow.stateIn
@@ -21,30 +21,37 @@ import org.schabi.newpipe.ui.components.playlist.PlaylistInfo
2121
import org.schabi.newpipe.util.KEY_SERVICE_ID
2222
import org.schabi.newpipe.util.KEY_URL
2323
import org.schabi.newpipe.util.NO_SERVICE_ID
24+
import org.schabi.newpipe.viewmodels.util.Resource
2425
import org.schabi.newpipe.extractor.playlist.PlaylistInfo as ExtractorPlaylistInfo
2526

2627
class PlaylistViewModel(savedStateHandle: SavedStateHandle) : ViewModel() {
2728
private val serviceIdState = savedStateHandle.getStateFlow(KEY_SERVICE_ID, NO_SERVICE_ID)
2829
private val urlState = savedStateHandle.getStateFlow(KEY_URL, "")
2930

30-
val playlistInfo = serviceIdState.combine(urlState) { id, url ->
31-
val info = ExtractorPlaylistInfo.getInfo(NewPipe.getService(id), url)
32-
val description = info.description ?: Description.EMPTY_DESCRIPTION
33-
PlaylistInfo(
34-
info.id, info.serviceId, info.url, info.name, description, info.relatedItems,
35-
info.streamCount, info.uploaderUrl, info.uploaderName, info.uploaderAvatars,
36-
info.nextPage
37-
)
31+
val uiState = serviceIdState.combine(urlState) { id, url ->
32+
try {
33+
val extractorInfo = ExtractorPlaylistInfo.getInfo(NewPipe.getService(id), url)
34+
val description = extractorInfo.description ?: Description.EMPTY_DESCRIPTION
35+
val info = PlaylistInfo(
36+
extractorInfo.id, extractorInfo.serviceId, extractorInfo.url, extractorInfo.name,
37+
description, extractorInfo.relatedItems, extractorInfo.streamCount,
38+
extractorInfo.uploaderUrl, extractorInfo.uploaderName, extractorInfo.uploaderAvatars,
39+
extractorInfo.nextPage
40+
)
41+
Resource.Success(info)
42+
} catch (e: Exception) {
43+
Resource.Error(e)
44+
}
3845
}
3946
.flowOn(Dispatchers.IO)
40-
.stateIn(viewModelScope, SharingStarted.Eagerly, null)
47+
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(), Resource.Loading)
4148

4249
@OptIn(ExperimentalCoroutinesApi::class)
43-
val streamItems = playlistInfo
44-
.filterNotNull()
50+
val streamItems = uiState
51+
.filterIsInstance<Resource.Success<PlaylistInfo>>()
4552
.flatMapLatest {
4653
Pager(PagingConfig(pageSize = 20, enablePlaceholders = false)) {
47-
PlaylistItemsSource(it)
54+
PlaylistItemsSource(it.data)
4855
}.flow
4956
}
5057
.cachedIn(viewModelScope)

0 commit comments

Comments
 (0)