Skip to content

Commit 34dbfde

Browse files
Start implementing stream composable, grid layout
1 parent 2ec1254 commit 34dbfde

File tree

3 files changed

+147
-31
lines changed

3 files changed

+147
-31
lines changed

app/src/main/java/org/schabi/newpipe/compose/playlist/Playlist.kt

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package org.schabi.newpipe.compose.playlist
22

33
import android.content.res.Configuration
4-
import androidx.compose.foundation.lazy.LazyColumn
5-
import androidx.compose.material3.HorizontalDivider
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
68
import androidx.compose.material3.MaterialTheme
79
import androidx.compose.material3.Surface
8-
import androidx.compose.material3.Text
910
import androidx.compose.runtime.Composable
1011
import androidx.compose.runtime.collectAsState
1112
import androidx.compose.runtime.getValue
@@ -14,7 +15,9 @@ import androidx.compose.ui.unit.dp
1415
import androidx.lifecycle.SavedStateHandle
1516
import androidx.lifecycle.viewmodel.compose.viewModel
1617
import androidx.paging.compose.collectAsLazyPagingItems
18+
import my.nanihadesuka.compose.LazyVerticalGridScrollbar
1719
import org.schabi.newpipe.DownloaderImpl
20+
import org.schabi.newpipe.compose.stream.StreamGridItem
1821
import org.schabi.newpipe.compose.theme.AppTheme
1922
import org.schabi.newpipe.extractor.NewPipe
2023
import org.schabi.newpipe.extractor.ServiceList
@@ -30,14 +33,17 @@ fun Playlist(playlistViewModel: PlaylistViewModel = viewModel()) {
3033

3134
playlistInfo?.let {
3235
Surface(color = MaterialTheme.colorScheme.background) {
33-
LazyColumn {
34-
item {
35-
PlaylistHeader(playlistInfo = it, totalDuration = totalDuration)
36-
HorizontalDivider(thickness = 1.dp)
37-
}
36+
val gridState = rememberLazyGridState()
37+
38+
LazyVerticalGridScrollbar(state = gridState) {
39+
LazyVerticalGrid(state = gridState, columns = GridCells.Adaptive(164.dp)) {
40+
item(span = { GridItemSpan(maxLineSpan) }) {
41+
PlaylistHeader(playlistInfo = it, totalDuration = totalDuration)
42+
}
3843

39-
items(streams.itemCount) {
40-
Text(text = streams[it]!!.name)
44+
items(streams.itemCount) {
45+
StreamGridItem(streams[it]!!)
46+
}
4147
}
4248
}
4349
}

app/src/main/java/org/schabi/newpipe/compose/playlist/PlaylistHeader.kt

Lines changed: 1 addition & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,12 @@ import org.schabi.newpipe.R
3535
import org.schabi.newpipe.compose.theme.AppTheme
3636
import org.schabi.newpipe.compose.util.rememberParsedDescription
3737
import org.schabi.newpipe.error.ErrorUtil
38-
import org.schabi.newpipe.extractor.Image
3938
import org.schabi.newpipe.extractor.NewPipe
4039
import org.schabi.newpipe.extractor.ServiceList
4140
import org.schabi.newpipe.extractor.playlist.PlaylistInfo
4241
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper
4342
import org.schabi.newpipe.extractor.stream.Description
44-
import org.schabi.newpipe.extractor.stream.StreamInfoItem
45-
import org.schabi.newpipe.extractor.stream.StreamType
4643
import org.schabi.newpipe.util.Localization
47-
import org.schabi.newpipe.util.NO_SERVICE_ID
4844
import org.schabi.newpipe.util.NavigationHelper
4945
import org.schabi.newpipe.util.image.ImageStrategy
5046
import java.util.concurrent.TimeUnit
@@ -54,7 +50,7 @@ fun PlaylistHeader(playlistInfo: PlaylistInfo, totalDuration: Long) {
5450
val context = LocalContext.current
5551

5652
Column(
57-
modifier = Modifier.padding(4.dp),
53+
modifier = Modifier.padding(12.dp),
5854
verticalArrangement = Arrangement.spacedBy(4.dp)
5955
) {
6056
Text(
@@ -143,22 +139,6 @@ fun PlaylistHeader(playlistInfo: PlaylistInfo, totalDuration: Long) {
143139
}
144140
}
145141

146-
fun StreamInfoItem(
147-
serviceId: Int = NO_SERVICE_ID,
148-
url: String,
149-
name: String,
150-
streamType: StreamType = StreamType.NONE,
151-
uploaderName: String? = null,
152-
uploaderUrl: String? = null,
153-
uploaderAvatars: List<Image> = emptyList(),
154-
duration: Long,
155-
) = StreamInfoItem(serviceId, url, name, streamType).apply {
156-
this.uploaderName = uploaderName
157-
this.uploaderUrl = uploaderUrl
158-
this.uploaderAvatars = uploaderAvatars
159-
this.duration = duration
160-
}
161-
162142
@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO)
163143
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
164144
@Composable
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
package org.schabi.newpipe.compose.stream
2+
3+
import android.content.Context
4+
import android.content.res.Configuration
5+
import androidx.compose.foundation.clickable
6+
import androidx.compose.foundation.layout.Column
7+
import androidx.compose.foundation.layout.padding
8+
import androidx.compose.foundation.layout.size
9+
import androidx.compose.material3.MaterialTheme
10+
import androidx.compose.material3.Surface
11+
import androidx.compose.material3.Text
12+
import androidx.compose.runtime.Composable
13+
import androidx.compose.ui.Modifier
14+
import androidx.compose.ui.platform.LocalContext
15+
import androidx.compose.ui.res.painterResource
16+
import androidx.compose.ui.text.style.TextOverflow
17+
import androidx.compose.ui.tooling.preview.Preview
18+
import androidx.compose.ui.tooling.preview.PreviewParameter
19+
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
20+
import androidx.compose.ui.unit.dp
21+
import androidx.fragment.app.FragmentActivity
22+
import coil.compose.AsyncImage
23+
import org.schabi.newpipe.R
24+
import org.schabi.newpipe.compose.theme.AppTheme
25+
import org.schabi.newpipe.extractor.Image
26+
import org.schabi.newpipe.extractor.stream.StreamInfoItem
27+
import org.schabi.newpipe.extractor.stream.StreamType
28+
import org.schabi.newpipe.util.Localization
29+
import org.schabi.newpipe.util.NO_SERVICE_ID
30+
import org.schabi.newpipe.util.NavigationHelper
31+
import org.schabi.newpipe.util.image.ImageStrategy
32+
import java.util.concurrent.TimeUnit
33+
34+
@Composable
35+
fun StreamGridItem(stream: StreamInfoItem) {
36+
val context = LocalContext.current
37+
38+
Column(
39+
modifier = Modifier
40+
.clickable {
41+
NavigationHelper.openVideoDetailFragment(
42+
context, (context as FragmentActivity).supportFragmentManager,
43+
stream.serviceId, stream.url, stream.name, null, false
44+
)
45+
}
46+
.padding(12.dp)
47+
) {
48+
AsyncImage(
49+
model = ImageStrategy.choosePreferredImage(stream.thumbnails),
50+
contentDescription = null,
51+
placeholder = painterResource(R.drawable.placeholder_thumbnail_video),
52+
error = painterResource(R.drawable.placeholder_thumbnail_video),
53+
modifier = Modifier.size(width = 164.dp, height = 92.dp)
54+
)
55+
56+
Text(
57+
text = stream.name,
58+
overflow = TextOverflow.Ellipsis,
59+
style = MaterialTheme.typography.titleSmall,
60+
maxLines = 2
61+
)
62+
63+
Text(text = stream.uploaderName.orEmpty(), style = MaterialTheme.typography.bodySmall)
64+
65+
Text(text = getStreamInfoDetail(context, stream), style = MaterialTheme.typography.bodySmall)
66+
}
67+
}
68+
69+
private fun getStreamInfoDetail(context: Context, stream: StreamInfoItem): String {
70+
val views = if (stream.viewCount >= 0) {
71+
when (stream.streamType) {
72+
StreamType.AUDIO_LIVE_STREAM -> Localization.listeningCount(context, stream.viewCount)
73+
StreamType.LIVE_STREAM -> Localization.shortWatchingCount(context, stream.viewCount)
74+
else -> Localization.shortViewCount(context, stream.viewCount)
75+
}
76+
} else {
77+
""
78+
}
79+
val date =
80+
Localization.relativeTimeOrTextual(context, stream.uploadDate, stream.textualUploadDate)
81+
82+
return if (views.isEmpty()) {
83+
date
84+
} else if (date.isNullOrEmpty()) {
85+
views
86+
} else {
87+
"$views$date"
88+
}
89+
}
90+
91+
fun StreamInfoItem(
92+
serviceId: Int = NO_SERVICE_ID,
93+
url: String = "",
94+
name: String = "Stream",
95+
streamType: StreamType,
96+
uploaderName: String? = "Uploader",
97+
uploaderUrl: String? = null,
98+
uploaderAvatars: List<Image> = emptyList(),
99+
duration: Long = TimeUnit.HOURS.toSeconds(1),
100+
viewCount: Long = 10,
101+
textualUploadDate: String = "1 month ago"
102+
) = StreamInfoItem(serviceId, url, name, streamType).apply {
103+
this.uploaderName = uploaderName
104+
this.uploaderUrl = uploaderUrl
105+
this.uploaderAvatars = uploaderAvatars
106+
this.duration = duration
107+
this.viewCount = viewCount
108+
this.textualUploadDate = textualUploadDate
109+
}
110+
111+
private class StreamItemPreviewProvider : PreviewParameterProvider<StreamInfoItem> {
112+
override val values = sequenceOf(
113+
StreamInfoItem(streamType = StreamType.NONE),
114+
StreamInfoItem(streamType = StreamType.LIVE_STREAM),
115+
StreamInfoItem(streamType = StreamType.AUDIO_LIVE_STREAM),
116+
)
117+
}
118+
119+
@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO)
120+
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
121+
@Composable
122+
private fun StreamGridItemPreview(
123+
@PreviewParameter(StreamItemPreviewProvider::class) stream: StreamInfoItem
124+
) {
125+
AppTheme {
126+
Surface(color = MaterialTheme.colorScheme.background) {
127+
StreamGridItem(stream)
128+
}
129+
}
130+
}

0 commit comments

Comments
 (0)