Skip to content

Commit f99fc13

Browse files
Reuse ItemList composable
1 parent 3d9394e commit f99fc13

5 files changed

Lines changed: 139 additions & 204 deletions

File tree

app/src/main/java/org/schabi/newpipe/local/history/HistoryViewModel.kt

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,10 @@ import kotlinx.coroutines.flow.flowOn
1212
import kotlinx.coroutines.flow.map
1313
import org.schabi.newpipe.NewPipeDatabase
1414
import org.schabi.newpipe.database.stream.StreamStatisticsEntry
15-
import org.schabi.newpipe.extractor.Image
1615
import org.schabi.newpipe.ui.components.items.Stream
1716
import org.schabi.newpipe.util.Localization
1817
import org.schabi.newpipe.util.ServiceHelper
18+
import org.schabi.newpipe.util.image.ImageStrategy
1919
import java.time.format.DateTimeFormatter
2020
import java.time.format.FormatStyle
2121

@@ -38,11 +38,7 @@ class HistoryViewModel(
3838
}
3939
.map { pagingData ->
4040
pagingData.map {
41-
val thumbnails = listOfNotNull(
42-
it.streamEntity.thumbnailUrl?.let {
43-
Image(it, -1, -1, Image.ResolutionLevel.UNKNOWN)
44-
}
45-
)
41+
val thumbnails = ImageStrategy.dbUrlToImageList(it.streamEntity.thumbnailUrl)
4642
val detail = getHistoryDetailText(it, dateTimeFormatter)
4743

4844
Stream(

app/src/main/java/org/schabi/newpipe/ui/components/items/Info.kt

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,11 @@ package org.schabi.newpipe.ui.components.items
22

33
import android.os.Parcelable
44
import kotlinx.parcelize.Parcelize
5-
import org.schabi.newpipe.App
65
import org.schabi.newpipe.database.stream.model.StreamEntity
76
import org.schabi.newpipe.extractor.Image
87
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem
98
import org.schabi.newpipe.extractor.stream.StreamInfoItem
109
import org.schabi.newpipe.extractor.stream.StreamType
11-
import org.schabi.newpipe.util.Localization
1210
import org.schabi.newpipe.util.NO_SERVICE_ID
1311
import org.schabi.newpipe.util.image.ImageStrategy
1412
import java.util.concurrent.TimeUnit
@@ -28,9 +26,9 @@ class Stream(
2826
val detailText: String = "",
2927
) : Info(), Parcelable {
3028

31-
constructor(item: StreamInfoItem) : this(
29+
constructor(item: StreamInfoItem, detailText: String) : this(
3230
item.serviceId, item.url, item.name, item.thumbnails, item.uploaderName.orEmpty(),
33-
item.streamType, item.uploaderUrl, item.duration, getStreamDetailText(item)
31+
item.streamType, item.uploaderUrl, item.duration, detailText
3432
)
3533

3634
constructor(entry: StreamEntity, detailText: String) : this(
@@ -47,32 +45,6 @@ class Stream(
4745
item.thumbnails = thumbnails
4846
return item
4947
}
50-
51-
companion object {
52-
fun getStreamDetailText(stream: StreamInfoItem): String {
53-
val context = App.instance
54-
val count = stream.viewCount
55-
val views = if (count >= 0) {
56-
when (stream.streamType) {
57-
StreamType.AUDIO_LIVE_STREAM -> Localization.listeningCount(context, count)
58-
StreamType.LIVE_STREAM -> Localization.shortWatchingCount(context, count)
59-
else -> Localization.shortViewCount(context, count)
60-
}
61-
} else {
62-
""
63-
}
64-
val date =
65-
Localization.relativeTimeOrTextual(context, stream.uploadDate, stream.textualUploadDate)
66-
67-
return if (views.isEmpty()) {
68-
date.orEmpty()
69-
} else if (date.isNullOrEmpty()) {
70-
views
71-
} else {
72-
"$views$date"
73-
}
74-
}
75-
}
7648
}
7749

7850
class Playlist(
@@ -89,3 +61,5 @@ class Playlist(
8961
item.streamCount
9062
)
9163
}
64+
65+
object Unknown : Info()

app/src/main/java/org/schabi/newpipe/ui/components/items/ItemList.kt

Lines changed: 65 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
package org.schabi.newpipe.ui.components.items
22

33
import androidx.compose.foundation.lazy.LazyColumn
4-
import androidx.compose.foundation.lazy.LazyListScope
4+
import androidx.compose.foundation.lazy.grid.GridCells
5+
import androidx.compose.foundation.lazy.grid.GridItemSpan
6+
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
7+
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
58
import androidx.compose.foundation.lazy.rememberLazyListState
9+
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
610
import androidx.compose.runtime.Composable
711
import androidx.compose.runtime.getValue
812
import androidx.compose.runtime.mutableStateOf
@@ -13,19 +17,29 @@ import androidx.compose.ui.Modifier
1317
import androidx.compose.ui.input.nestedscroll.nestedScroll
1418
import androidx.compose.ui.platform.LocalContext
1519
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
20+
import androidx.compose.ui.unit.dp
21+
import androidx.paging.LoadState
22+
import androidx.paging.compose.LazyPagingItems
23+
import androidx.window.core.layout.WindowWidthSizeClass
24+
import my.nanihadesuka.compose.LazyVerticalGridScrollbar
1625
import org.schabi.newpipe.info_list.ItemViewMode
1726
import org.schabi.newpipe.ktx.findFragmentActivity
1827
import org.schabi.newpipe.ui.components.common.LazyColumnThemedScrollbar
28+
import org.schabi.newpipe.ui.components.common.defaultThemedScrollbarSettings
1929
import org.schabi.newpipe.ui.components.items.playlist.PlaylistListItem
30+
import org.schabi.newpipe.ui.components.items.stream.StreamCardItem
31+
import org.schabi.newpipe.ui.components.items.stream.StreamGridItem
2032
import org.schabi.newpipe.ui.components.items.stream.StreamListItem
33+
import org.schabi.newpipe.ui.emptystate.EmptyStateComposable
34+
import org.schabi.newpipe.ui.emptystate.EmptyStateSpec
2135
import org.schabi.newpipe.util.DependentPreferenceHelper
2236
import org.schabi.newpipe.util.NavigationHelper
2337

2438
@Composable
2539
fun ItemList(
26-
items: List<Info>,
40+
items: LazyPagingItems<out Info>,
2741
mode: ItemViewMode = determineItemViewMode(),
28-
listHeader: LazyListScope.() -> Unit = {}
42+
header: @Composable () -> Unit = {},
2943
) {
3044
val context = LocalContext.current
3145
val onClick = remember {
@@ -60,23 +74,64 @@ fun ItemList(
6074
val showProgress = DependentPreferenceHelper.getPositionsInListsEnabled(context)
6175
val nestedScrollModifier = Modifier.nestedScroll(rememberNestedScrollInteropConnection())
6276

63-
if (mode == ItemViewMode.GRID) {
64-
// TODO: Implement grid layout using LazyVerticalGrid and LazyVerticalGridScrollbar.
77+
if (items.loadState.refresh is LoadState.NotLoading && items.itemCount == 0) {
78+
EmptyStateComposable(EmptyStateSpec.NoVideos)
79+
} else if (mode == ItemViewMode.GRID) {
80+
val state = rememberLazyGridState()
81+
82+
LazyVerticalGridScrollbar(state = state, settings = defaultThemedScrollbarSettings()) {
83+
val windowSizeClass = currentWindowAdaptiveInfo().windowSizeClass
84+
val isCompact = windowSizeClass.windowWidthSizeClass == WindowWidthSizeClass.COMPACT
85+
val minSize = if (isCompact) 150.dp else 250.dp
86+
87+
LazyVerticalGrid(
88+
modifier = nestedScrollModifier,
89+
state = state,
90+
columns = GridCells.Adaptive(minSize)
91+
) {
92+
item(span = { GridItemSpan(maxLineSpan) }) {
93+
header()
94+
}
95+
96+
items(items.itemCount) {
97+
val item = items[it]!!
98+
val isSelected = selectedStream == item
99+
100+
// TODO: Implement playlist and channel grid items.
101+
if (item is Stream) {
102+
StreamGridItem(
103+
item, showProgress, isSelected, isCompact, onClick, onLongClick,
104+
onDismissPopup
105+
)
106+
}
107+
}
108+
}
109+
}
65110
} else {
66111
val state = rememberLazyListState()
67112

68113
LazyColumnThemedScrollbar(state = state) {
69114
LazyColumn(modifier = nestedScrollModifier, state = state) {
70-
listHeader()
115+
item {
116+
header()
117+
}
71118

72-
items(items.size) {
119+
items(items.itemCount) {
73120
val item = items[it]
74121

122+
// TODO: Implement playlist and channel items.
75123
if (item is Stream) {
76124
val isSelected = selectedStream == item
77-
StreamListItem(
78-
item, showProgress, isSelected, onClick, onLongClick, onDismissPopup
79-
)
125+
126+
if (mode == ItemViewMode.CARD) {
127+
StreamCardItem(
128+
item, showProgress, isSelected, onClick, onLongClick, onDismissPopup
129+
)
130+
} else {
131+
StreamListItem(
132+
item, showProgress, isSelected, onClick, onLongClick, onDismissPopup
133+
)
134+
}
80135
} else if (item is Playlist) {
81136
PlaylistListItem(item, onClick)
82137
}

app/src/main/java/org/schabi/newpipe/ui/components/video/RelatedItems.kt

Lines changed: 64 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package org.schabi.newpipe.ui.components.video
22

3+
import android.content.Context
34
import android.content.res.Configuration
45
import androidx.compose.foundation.layout.Arrangement
56
import androidx.compose.foundation.layout.Row
@@ -21,80 +22,100 @@ import androidx.compose.ui.res.stringResource
2122
import androidx.compose.ui.tooling.preview.Preview
2223
import androidx.compose.ui.unit.dp
2324
import androidx.core.content.edit
25+
import androidx.paging.PagingData
26+
import androidx.paging.compose.collectAsLazyPagingItems
2427
import androidx.preference.PreferenceManager
28+
import kotlinx.coroutines.flow.flowOf
2529
import org.schabi.newpipe.R
2630
import org.schabi.newpipe.extractor.Image
2731
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem
32+
import org.schabi.newpipe.extractor.stream.StreamInfo
2833
import org.schabi.newpipe.extractor.stream.StreamInfoItem
2934
import org.schabi.newpipe.extractor.stream.StreamType
3035
import org.schabi.newpipe.info_list.ItemViewMode
3136
import org.schabi.newpipe.ui.components.items.ItemList
3237
import org.schabi.newpipe.ui.components.items.Playlist
3338
import org.schabi.newpipe.ui.components.items.Stream
34-
import org.schabi.newpipe.ui.emptystate.EmptyStateComposable
35-
import org.schabi.newpipe.ui.emptystate.EmptyStateSpec
39+
import org.schabi.newpipe.ui.components.items.Unknown
3640
import org.schabi.newpipe.ui.theme.AppTheme
41+
import org.schabi.newpipe.util.Localization
3742
import org.schabi.newpipe.util.NO_SERVICE_ID
3843
import java.util.concurrent.TimeUnit
39-
import org.schabi.newpipe.extractor.stream.StreamInfo as ExtractorStreamInfo
4044

4145
@Composable
42-
fun RelatedItems(info: ExtractorStreamInfo) {
43-
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(LocalContext.current)
46+
fun RelatedItems(info: StreamInfo) {
47+
val context = LocalContext.current
48+
val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context)
4449
val key = stringResource(R.string.auto_queue_key)
4550
// TODO: AndroidX DataStore might be a better option.
4651
var isAutoQueueEnabled by rememberSaveable {
4752
mutableStateOf(sharedPreferences.getBoolean(key, false))
4853
}
54+
val displayItems = info.relatedItems.map {
55+
if (it is StreamInfoItem) {
56+
Stream(it, getStreamDetailText(context, it))
57+
} else if (it is PlaylistInfoItem) {
58+
Playlist(it)
59+
} else {
60+
Unknown
61+
}
62+
}
4963

5064
ItemList(
51-
items = info.relatedItems.map {
52-
if (it is StreamInfoItem) {
53-
Stream(it)
54-
} else if (it is PlaylistInfoItem) {
55-
Playlist(it)
56-
} else {
57-
throw IllegalArgumentException()
58-
}
59-
},
65+
items = flowOf(PagingData.from(displayItems)).collectAsLazyPagingItems(),
6066
mode = ItemViewMode.LIST,
61-
listHeader = {
62-
item {
67+
header = {
68+
Row(
69+
modifier = Modifier
70+
.fillMaxWidth()
71+
.padding(start = 12.dp, end = 12.dp),
72+
horizontalArrangement = Arrangement.SpaceBetween,
73+
verticalAlignment = Alignment.CenterVertically,
74+
) {
75+
Text(text = stringResource(R.string.auto_queue_description))
76+
6377
Row(
64-
modifier = Modifier
65-
.fillMaxWidth()
66-
.padding(start = 12.dp, end = 12.dp),
67-
horizontalArrangement = Arrangement.SpaceBetween,
68-
verticalAlignment = Alignment.CenterVertically,
78+
horizontalArrangement = Arrangement.spacedBy(4.dp),
79+
verticalAlignment = Alignment.CenterVertically
6980
) {
70-
Text(text = stringResource(R.string.auto_queue_description))
71-
72-
Row(
73-
horizontalArrangement = Arrangement.spacedBy(4.dp),
74-
verticalAlignment = Alignment.CenterVertically
75-
) {
76-
Text(text = stringResource(R.string.auto_queue_toggle))
77-
Switch(
78-
checked = isAutoQueueEnabled,
79-
onCheckedChange = {
80-
isAutoQueueEnabled = it
81-
sharedPreferences.edit {
82-
putBoolean(key, it)
83-
}
81+
Text(text = stringResource(R.string.auto_queue_toggle))
82+
Switch(
83+
checked = isAutoQueueEnabled,
84+
onCheckedChange = {
85+
isAutoQueueEnabled = it
86+
sharedPreferences.edit {
87+
putBoolean(key, it)
8488
}
85-
)
86-
}
87-
}
88-
}
89-
if (info.relatedItems.isEmpty()) {
90-
item {
91-
EmptyStateComposable(EmptyStateSpec.NoVideos)
89+
}
90+
)
9291
}
9392
}
9493
}
9594
)
9695
}
9796

97+
private fun getStreamDetailText(context: Context, stream: StreamInfoItem): String {
98+
val count = stream.viewCount
99+
val views = if (count >= 0) {
100+
when (stream.streamType) {
101+
StreamType.AUDIO_LIVE_STREAM -> Localization.listeningCount(context, count)
102+
StreamType.LIVE_STREAM -> Localization.shortWatchingCount(context, count)
103+
else -> Localization.shortViewCount(context, count)
104+
}
105+
} else {
106+
""
107+
}
108+
val date = Localization.relativeTimeOrTextual(context, stream.uploadDate, stream.textualUploadDate)
109+
110+
return if (views.isEmpty()) {
111+
date.orEmpty()
112+
} else if (date.isNullOrEmpty()) {
113+
views
114+
} else {
115+
"$views$date"
116+
}
117+
}
118+
98119
private fun StreamInfoItem(
99120
serviceId: Int = NO_SERVICE_ID,
100121
url: String = "",
@@ -119,7 +140,7 @@ private fun StreamInfoItem(
119140
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
120141
@Composable
121142
private fun RelatedItemsPreview() {
122-
val info = ExtractorStreamInfo(NO_SERVICE_ID, "", "", StreamType.VIDEO_STREAM, "", "", 0)
143+
val info = StreamInfo(NO_SERVICE_ID, "", "", StreamType.VIDEO_STREAM, "", "", 0)
123144
info.relatedItems = listOf(
124145
StreamInfoItem(streamType = StreamType.NONE),
125146
StreamInfoItem(streamType = StreamType.LIVE_STREAM),

0 commit comments

Comments
 (0)