Skip to content

Commit 3d9394e

Browse files
Reuse stream composables
1 parent 77e254b commit 3d9394e

16 files changed

Lines changed: 264 additions & 273 deletions

File tree

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

Lines changed: 47 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,30 @@
11
package org.schabi.newpipe.local.history
22

3+
import android.app.Application
4+
import androidx.lifecycle.AndroidViewModel
35
import androidx.lifecycle.SavedStateHandle
4-
import androidx.lifecycle.ViewModel
56
import androidx.paging.Pager
67
import androidx.paging.PagingConfig
8+
import androidx.paging.map
9+
import kotlinx.coroutines.Dispatchers
710
import kotlinx.coroutines.flow.flatMapLatest
8-
import org.schabi.newpipe.App
11+
import kotlinx.coroutines.flow.flowOn
12+
import kotlinx.coroutines.flow.map
913
import org.schabi.newpipe.NewPipeDatabase
14+
import org.schabi.newpipe.database.stream.StreamStatisticsEntry
15+
import org.schabi.newpipe.extractor.Image
16+
import org.schabi.newpipe.ui.components.items.Stream
17+
import org.schabi.newpipe.util.Localization
18+
import org.schabi.newpipe.util.ServiceHelper
19+
import java.time.format.DateTimeFormatter
20+
import java.time.format.FormatStyle
1021

11-
class HistoryViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
12-
private val historyDao = NewPipeDatabase.getInstance(App.instance).streamHistoryDAO()
22+
class HistoryViewModel(
23+
application: Application,
24+
private val savedStateHandle: SavedStateHandle,
25+
) : AndroidViewModel(application) {
26+
private val historyDao = NewPipeDatabase.getInstance(getApplication()).streamHistoryDAO()
27+
private val dateTimeFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
1328

1429
val sortKey = savedStateHandle.getStateFlow(ORDER_KEY, SortKey.MOST_PLAYED)
1530
val historyItems = sortKey
@@ -21,11 +36,39 @@ class HistoryViewModel(private val savedStateHandle: SavedStateHandle) : ViewMod
2136
}
2237
}.flow
2338
}
39+
.map { pagingData ->
40+
pagingData.map {
41+
val thumbnails = listOfNotNull(
42+
it.streamEntity.thumbnailUrl?.let {
43+
Image(it, -1, -1, Image.ResolutionLevel.UNKNOWN)
44+
}
45+
)
46+
val detail = getHistoryDetailText(it, dateTimeFormatter)
47+
48+
Stream(
49+
it.streamEntity.serviceId, it.streamEntity.url, it.streamEntity.title,
50+
thumbnails, it.streamEntity.uploader, it.streamEntity.streamType,
51+
it.streamEntity.uploaderUrl, it.streamEntity.duration, detail
52+
)
53+
}
54+
}
55+
.flowOn(Dispatchers.IO)
2456

2557
fun updateOrder(sortKey: SortKey) {
2658
savedStateHandle[ORDER_KEY] = sortKey
2759
}
2860

61+
fun getHistoryDetailText(
62+
entry: StreamStatisticsEntry,
63+
dateTimeFormatter: DateTimeFormatter,
64+
): String {
65+
return Localization.concatenateStrings(
66+
Localization.shortViewCount(getApplication(), entry.watchCount),
67+
dateTimeFormatter.format(entry.latestAccessDate),
68+
ServiceHelper.getNameOfServiceById(entry.streamEntity.serviceId),
69+
)
70+
}
71+
2972
companion object {
3073
private const val ORDER_KEY = "order"
3174
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
package org.schabi.newpipe.ui.components.items
2+
3+
import android.os.Parcelable
4+
import kotlinx.parcelize.Parcelize
5+
import org.schabi.newpipe.App
6+
import org.schabi.newpipe.database.stream.model.StreamEntity
7+
import org.schabi.newpipe.extractor.Image
8+
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem
9+
import org.schabi.newpipe.extractor.stream.StreamInfoItem
10+
import org.schabi.newpipe.extractor.stream.StreamType
11+
import org.schabi.newpipe.util.Localization
12+
import org.schabi.newpipe.util.NO_SERVICE_ID
13+
import org.schabi.newpipe.util.image.ImageStrategy
14+
import java.util.concurrent.TimeUnit
15+
16+
sealed class Info
17+
18+
@Parcelize
19+
class Stream(
20+
val serviceId: Int = NO_SERVICE_ID,
21+
val url: String = "",
22+
val name: String = "",
23+
val thumbnails: List<Image> = emptyList(),
24+
val uploaderName: String = "",
25+
val type: StreamType,
26+
val uploaderUrl: String? = null,
27+
val duration: Long = TimeUnit.HOURS.toSeconds(1),
28+
val detailText: String = "",
29+
) : Info(), Parcelable {
30+
31+
constructor(item: StreamInfoItem) : this(
32+
item.serviceId, item.url, item.name, item.thumbnails, item.uploaderName.orEmpty(),
33+
item.streamType, item.uploaderUrl, item.duration, getStreamDetailText(item)
34+
)
35+
36+
constructor(entry: StreamEntity, detailText: String) : this(
37+
entry.serviceId, entry.url, entry.title,
38+
ImageStrategy.dbUrlToImageList(entry.thumbnailUrl), entry.uploader,
39+
entry.streamType, entry.uploaderUrl, entry.duration, detailText
40+
)
41+
42+
fun toStreamInfoItem(): StreamInfoItem {
43+
val item = StreamInfoItem(serviceId, url, name, type)
44+
item.duration = duration
45+
item.uploaderName = uploaderName
46+
item.uploaderUrl = uploaderUrl
47+
item.thumbnails = thumbnails
48+
return item
49+
}
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+
}
76+
}
77+
78+
class Playlist(
79+
val serviceId: Int = NO_SERVICE_ID,
80+
val url: String = "",
81+
val name: String = "",
82+
val thumbnails: List<Image> = emptyList(),
83+
val uploaderName: String = "",
84+
val streamCount: Long = 10,
85+
) : Info() {
86+
87+
constructor(item: PlaylistInfoItem) : this(
88+
item.serviceId, item.url, item.name, item.thumbnails, item.uploaderName.orEmpty(),
89+
item.streamCount
90+
)
91+
}

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

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,12 @@ import androidx.compose.runtime.Composable
77
import androidx.compose.runtime.getValue
88
import androidx.compose.runtime.mutableStateOf
99
import androidx.compose.runtime.remember
10+
import androidx.compose.runtime.saveable.rememberSaveable
1011
import androidx.compose.runtime.setValue
1112
import androidx.compose.ui.Modifier
1213
import androidx.compose.ui.input.nestedscroll.nestedScroll
1314
import androidx.compose.ui.platform.LocalContext
1415
import androidx.compose.ui.platform.rememberNestedScrollInteropConnection
15-
import org.schabi.newpipe.extractor.InfoItem
16-
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem
17-
import org.schabi.newpipe.extractor.stream.StreamInfoItem
1816
import org.schabi.newpipe.info_list.ItemViewMode
1917
import org.schabi.newpipe.ktx.findFragmentActivity
2018
import org.schabi.newpipe.ui.components.common.LazyColumnThemedScrollbar
@@ -25,19 +23,19 @@ import org.schabi.newpipe.util.NavigationHelper
2523

2624
@Composable
2725
fun ItemList(
28-
items: List<InfoItem>,
26+
items: List<Info>,
2927
mode: ItemViewMode = determineItemViewMode(),
3028
listHeader: LazyListScope.() -> Unit = {}
3129
) {
3230
val context = LocalContext.current
3331
val onClick = remember {
34-
{ item: InfoItem ->
32+
{ item: Info ->
3533
val fragmentManager = context.findFragmentActivity().supportFragmentManager
36-
if (item is StreamInfoItem) {
34+
if (item is Stream) {
3735
NavigationHelper.openVideoDetailFragment(
3836
context, fragmentManager, item.serviceId, item.url, item.name, null, false
3937
)
40-
} else if (item is PlaylistInfoItem) {
38+
} else if (item is Playlist) {
4139
NavigationHelper.openPlaylistFragment(
4240
fragmentManager, item.serviceId, item.url, item.name
4341
)
@@ -47,9 +45,9 @@ fun ItemList(
4745

4846
// Handle long clicks for stream items
4947
// TODO: Adjust the menu display depending on where it was triggered
50-
var selectedStream by remember { mutableStateOf<StreamInfoItem?>(null) }
48+
var selectedStream by rememberSaveable { mutableStateOf<Stream?>(null) }
5149
val onLongClick = remember {
52-
{ stream: StreamInfoItem ->
50+
{ stream: Stream ->
5351
selectedStream = stream
5452
}
5553
}
@@ -74,12 +72,12 @@ fun ItemList(
7472
items(items.size) {
7573
val item = items[it]
7674

77-
if (item is StreamInfoItem) {
75+
if (item is Stream) {
7876
val isSelected = selectedStream == item
7977
StreamListItem(
8078
item, showProgress, isSelected, onClick, onLongClick, onDismissPopup
8179
)
82-
} else if (item is PlaylistInfoItem) {
80+
} else if (item is Playlist) {
8381
PlaylistListItem(item, onClick)
8482
}
8583
}

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

Lines changed: 0 additions & 76 deletions
This file was deleted.

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

Lines changed: 0 additions & 25 deletions
This file was deleted.

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

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,14 @@ import androidx.compose.ui.Modifier
1717
import androidx.compose.ui.text.style.TextOverflow
1818
import androidx.compose.ui.tooling.preview.Preview
1919
import androidx.compose.ui.unit.dp
20-
import org.schabi.newpipe.extractor.InfoItem
21-
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem
20+
import org.schabi.newpipe.ui.components.items.Playlist
2221
import org.schabi.newpipe.ui.theme.AppTheme
2322
import org.schabi.newpipe.util.NO_SERVICE_ID
2423

2524
@Composable
2625
fun PlaylistListItem(
27-
playlist: PlaylistInfoItem,
28-
onClick: (InfoItem) -> Unit = {},
26+
playlist: Playlist,
27+
onClick: (Playlist) -> Unit = {},
2928
) {
3029
Row(
3130
modifier = Modifier
@@ -49,7 +48,7 @@ fun PlaylistListItem(
4948
)
5049

5150
Text(
52-
text = playlist.uploaderName.orEmpty(),
51+
text = playlist.uploaderName,
5352
style = MaterialTheme.typography.bodySmall
5453
)
5554
}
@@ -60,8 +59,7 @@ fun PlaylistListItem(
6059
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
6160
@Composable
6261
private fun PlaylistListItemPreview() {
63-
val playlist = PlaylistInfoItem(NO_SERVICE_ID, "", "Playlist")
64-
playlist.uploaderName = "Uploader"
62+
val playlist = Playlist(NO_SERVICE_ID, "", "Playlist", uploaderName = "Uploader")
6563

6664
AppTheme {
6765
Surface(color = MaterialTheme.colorScheme.background) {

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ import androidx.compose.ui.res.painterResource
2020
import androidx.compose.ui.unit.dp
2121
import coil3.compose.AsyncImage
2222
import org.schabi.newpipe.R
23-
import org.schabi.newpipe.extractor.playlist.PlaylistInfoItem
23+
import org.schabi.newpipe.ui.components.items.Playlist
2424
import org.schabi.newpipe.util.Localization
2525
import org.schabi.newpipe.util.image.ImageStrategy
2626

2727
@Composable
2828
fun PlaylistThumbnail(
29-
playlist: PlaylistInfoItem,
29+
playlist: Playlist,
3030
modifier: Modifier = Modifier,
3131
contentScale: ContentScale = ContentScale.Fit
3232
) {

0 commit comments

Comments
 (0)