Skip to content

Commit d9a06a8

Browse files
Add DescriptionText composable, remove playlist preview dependency on external HTTP calls
1 parent ff45dfd commit d9a06a8

File tree

12 files changed

+148
-83
lines changed

12 files changed

+148
-83
lines changed

app/src/main/java/org/schabi/newpipe/compose/comment/Comment.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,8 @@ import androidx.paging.PagingConfig
4242
import androidx.paging.cachedIn
4343
import coil.compose.AsyncImage
4444
import org.schabi.newpipe.R
45+
import org.schabi.newpipe.compose.common.DescriptionText
4546
import org.schabi.newpipe.compose.theme.AppTheme
46-
import org.schabi.newpipe.compose.util.rememberParsedDescription
4747
import org.schabi.newpipe.extractor.Page
4848
import org.schabi.newpipe.extractor.comments.CommentsInfoItem
4949
import org.schabi.newpipe.extractor.stream.Description
@@ -101,8 +101,8 @@ fun Comment(comment: CommentsInfoItem) {
101101
Text(text = nameAndDate, color = MaterialTheme.colorScheme.secondary)
102102
}
103103

104-
Text(
105-
text = rememberParsedDescription(comment.commentText),
104+
DescriptionText(
105+
description = comment.commentText,
106106
// If the comment is expanded, we display all its content
107107
// otherwise we only display the first two lines
108108
maxLines = if (isExpanded) Int.MAX_VALUE else 2,

app/src/main/java/org/schabi/newpipe/compose/comment/CommentRepliesHeader.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ import androidx.compose.ui.unit.dp
2525
import androidx.fragment.app.FragmentActivity
2626
import coil.compose.AsyncImage
2727
import org.schabi.newpipe.R
28+
import org.schabi.newpipe.compose.common.DescriptionText
2829
import org.schabi.newpipe.compose.theme.AppTheme
29-
import org.schabi.newpipe.compose.util.rememberParsedDescription
3030
import org.schabi.newpipe.extractor.comments.CommentsInfoItem
3131
import org.schabi.newpipe.extractor.stream.Description
3232
import org.schabi.newpipe.util.Localization
@@ -102,8 +102,8 @@ fun CommentRepliesHeader(comment: CommentsInfoItem) {
102102
}
103103
}
104104

105-
Text(
106-
text = rememberParsedDescription(comment.commentText),
105+
DescriptionText(
106+
description = comment.commentText,
107107
style = MaterialTheme.typography.bodyMedium
108108
)
109109
}

app/src/main/java/org/schabi/newpipe/compose/comment/CommentSection.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ import kotlinx.coroutines.flow.Flow
3131
import kotlinx.coroutines.flow.flowOf
3232
import my.nanihadesuka.compose.LazyColumnScrollbar
3333
import org.schabi.newpipe.R
34-
import org.schabi.newpipe.compose.status.LoadingIndicator
34+
import org.schabi.newpipe.compose.common.LoadingIndicator
3535
import org.schabi.newpipe.compose.theme.AppTheme
3636
import org.schabi.newpipe.extractor.comments.CommentsInfoItem
3737
import org.schabi.newpipe.extractor.stream.Description
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package org.schabi.newpipe.compose.common
2+
3+
import androidx.compose.material3.LocalTextStyle
4+
import androidx.compose.material3.Text
5+
import androidx.compose.runtime.Composable
6+
import androidx.compose.runtime.remember
7+
import androidx.compose.ui.Modifier
8+
import androidx.compose.ui.text.AnnotatedString
9+
import androidx.compose.ui.text.ParagraphStyle
10+
import androidx.compose.ui.text.SpanStyle
11+
import androidx.compose.ui.text.TextLayoutResult
12+
import androidx.compose.ui.text.TextLinkStyles
13+
import androidx.compose.ui.text.TextStyle
14+
import androidx.compose.ui.text.fromHtml
15+
import androidx.compose.ui.text.style.TextDecoration
16+
import androidx.compose.ui.text.style.TextOverflow
17+
import org.schabi.newpipe.extractor.stream.Description
18+
19+
@Composable
20+
fun DescriptionText(
21+
description: Description,
22+
modifier: Modifier = Modifier,
23+
overflow: TextOverflow = TextOverflow.Clip,
24+
maxLines: Int = Int.MAX_VALUE,
25+
onTextLayout: (TextLayoutResult) -> Unit = {},
26+
style: TextStyle = LocalTextStyle.current
27+
) {
28+
// TODO: Handle links and hashtags, Markdown.
29+
val parsedDescription = remember(description) {
30+
if (description.type == Description.HTML) {
31+
val styles = TextLinkStyles(SpanStyle(textDecoration = TextDecoration.Underline))
32+
AnnotatedString.fromHtml(description.content, styles)
33+
} else {
34+
AnnotatedString(description.content, ParagraphStyle())
35+
}
36+
}
37+
38+
Text(
39+
modifier = modifier,
40+
text = parsedDescription,
41+
maxLines = maxLines,
42+
style = style,
43+
overflow = overflow,
44+
onTextLayout = onTextLayout
45+
)
46+
}

app/src/main/java/org/schabi/newpipe/compose/status/LoadingIndicator.kt renamed to app/src/main/java/org/schabi/newpipe/compose/common/LoadingIndicator.kt

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package org.schabi.newpipe.compose.status
1+
package org.schabi.newpipe.compose.common
22

33
import androidx.compose.foundation.layout.fillMaxSize
44
import androidx.compose.foundation.layout.wrapContentSize
@@ -11,7 +11,9 @@ import androidx.compose.ui.Modifier
1111
@Composable
1212
fun LoadingIndicator(modifier: Modifier = Modifier) {
1313
CircularProgressIndicator(
14-
modifier = modifier.fillMaxSize().wrapContentSize(Alignment.Center),
14+
modifier = modifier
15+
.fillMaxSize()
16+
.wrapContentSize(Alignment.Center),
1517
color = MaterialTheme.colorScheme.primary,
1618
trackColor = MaterialTheme.colorScheme.surfaceVariant,
1719
)

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

+43-33
Original file line numberDiff line numberDiff line change
@@ -10,62 +10,72 @@ import androidx.compose.runtime.derivedStateOf
1010
import androidx.compose.runtime.getValue
1111
import androidx.compose.runtime.remember
1212
import androidx.compose.ui.tooling.preview.Preview
13-
import androidx.lifecycle.SavedStateHandle
1413
import androidx.lifecycle.viewmodel.compose.viewModel
14+
import androidx.paging.PagingData
1515
import androidx.paging.compose.collectAsLazyPagingItems
16-
import org.schabi.newpipe.DownloaderImpl
17-
import org.schabi.newpipe.compose.status.LoadingIndicator
16+
import kotlinx.coroutines.flow.Flow
17+
import kotlinx.coroutines.flow.flowOf
18+
import org.schabi.newpipe.compose.common.LoadingIndicator
19+
import org.schabi.newpipe.compose.stream.StreamInfoItem
1820
import org.schabi.newpipe.compose.stream.StreamList
1921
import org.schabi.newpipe.compose.theme.AppTheme
20-
import org.schabi.newpipe.extractor.NewPipe
21-
import org.schabi.newpipe.extractor.ServiceList
22-
import org.schabi.newpipe.util.KEY_SERVICE_ID
23-
import org.schabi.newpipe.util.KEY_URL
22+
import org.schabi.newpipe.extractor.stream.Description
23+
import org.schabi.newpipe.extractor.stream.StreamInfoItem
24+
import org.schabi.newpipe.extractor.stream.StreamType
2425
import org.schabi.newpipe.viewmodels.PlaylistViewModel
2526

2627
@Composable
2728
fun Playlist(playlistViewModel: PlaylistViewModel = viewModel()) {
2829
Surface(color = MaterialTheme.colorScheme.background) {
2930
val playlistInfo by playlistViewModel.playlistInfo.collectAsState()
31+
Playlist(playlistInfo, playlistViewModel.streamItems)
32+
}
33+
}
3034

31-
playlistInfo?.let {
32-
val streams = playlistViewModel.streamItems.collectAsLazyPagingItems()
33-
val totalDuration by remember {
34-
derivedStateOf {
35-
streams.itemSnapshotList.sumOf { it!!.duration }
36-
}
35+
@Composable
36+
private fun Playlist(
37+
playlistInfo: PlaylistInfo?,
38+
streamFlow: Flow<PagingData<StreamInfoItem>>
39+
) {
40+
playlistInfo?.let {
41+
val streams = streamFlow.collectAsLazyPagingItems()
42+
val totalDuration by remember {
43+
derivedStateOf {
44+
streams.itemSnapshotList.sumOf { it!!.duration }
3745
}
46+
}
3847

39-
StreamList(
40-
streams = streams,
41-
gridHeader = {
42-
item(span = { GridItemSpan(maxLineSpan) }) {
43-
PlaylistHeader(it, totalDuration)
44-
}
45-
},
46-
listHeader = {
47-
item {
48-
PlaylistHeader(it, totalDuration)
49-
}
48+
StreamList(
49+
streams = streams,
50+
gridHeader = {
51+
item(span = { GridItemSpan(maxLineSpan) }) {
52+
PlaylistHeader(it, totalDuration)
5053
}
51-
)
52-
} ?: LoadingIndicator()
53-
}
54+
},
55+
listHeader = {
56+
item {
57+
PlaylistHeader(it, totalDuration)
58+
}
59+
}
60+
)
61+
} ?: LoadingIndicator()
5462
}
5563

5664
@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO)
5765
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
5866
@Composable
5967
private fun PlaylistPreview() {
60-
NewPipe.init(DownloaderImpl.init(null))
61-
62-
val params = mapOf(
63-
KEY_SERVICE_ID to ServiceList.YouTube.serviceId,
64-
KEY_URL to "https://www.youtube.com/playlist?list=PLAIcZs9N4171hRrG_4v32Ca2hLvSuQ6QI"
68+
val description = Description("Example description", Description.PLAIN_TEXT)
69+
val playlistInfo = PlaylistInfo(
70+
"", 1, "", "Example playlist", description, listOf(), 1L,
71+
null, "Uploader", listOf(), null
6572
)
73+
val stream = StreamInfoItem(streamType = StreamType.VIDEO_STREAM)
74+
val streamFlow = flowOf(PagingData.from(listOf(stream)))
75+
6676
AppTheme {
6777
Surface(color = MaterialTheme.colorScheme.background) {
68-
Playlist(PlaylistViewModel(SavedStateHandle(params)))
78+
Playlist(playlistInfo, streamFlow)
6979
}
7080
}
7181
}

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

+9-13
Original file line numberDiff line numberDiff line change
@@ -34,14 +34,11 @@ import androidx.compose.ui.tooling.preview.Preview
3434
import androidx.compose.ui.unit.dp
3535
import androidx.fragment.app.FragmentActivity
3636
import coil.compose.AsyncImage
37-
import org.schabi.newpipe.DownloaderImpl
3837
import org.schabi.newpipe.R
38+
import org.schabi.newpipe.compose.common.DescriptionText
3939
import org.schabi.newpipe.compose.theme.AppTheme
40-
import org.schabi.newpipe.compose.util.rememberParsedDescription
4140
import org.schabi.newpipe.error.ErrorUtil
42-
import org.schabi.newpipe.extractor.NewPipe
4341
import org.schabi.newpipe.extractor.ServiceList
44-
import org.schabi.newpipe.extractor.playlist.PlaylistInfo
4542
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper
4643
import org.schabi.newpipe.extractor.stream.Description
4744
import org.schabi.newpipe.util.Localization
@@ -67,7 +64,7 @@ fun PlaylistHeader(playlistInfo: PlaylistInfo, totalDuration: Long) {
6764
NavigationHelper.openChannelFragment(
6865
(context as FragmentActivity).supportFragmentManager,
6966
playlistInfo.serviceId, playlistInfo.uploaderUrl,
70-
playlistInfo.uploaderName
67+
playlistInfo.uploaderName!!
7168
)
7269
} catch (e: Exception) {
7370
ErrorUtil.showUiErrorSnackbar(context, "Opening channel fragment", e)
@@ -108,14 +105,13 @@ fun PlaylistHeader(playlistInfo: PlaylistInfo, totalDuration: Long) {
108105
Text(text = "$count$formattedDuration", style = MaterialTheme.typography.bodySmall)
109106
}
110107

111-
val description = playlistInfo.description ?: Description.EMPTY_DESCRIPTION
112-
if (description != Description.EMPTY_DESCRIPTION) {
108+
if (playlistInfo.description != Description.EMPTY_DESCRIPTION) {
113109
var isExpanded by rememberSaveable { mutableStateOf(false) }
114110
var isExpandable by rememberSaveable { mutableStateOf(false) }
115111

116-
Text(
112+
DescriptionText(
117113
modifier = Modifier.animateContentSize(),
118-
text = rememberParsedDescription(description),
114+
description = playlistInfo.description,
119115
maxLines = if (isExpanded) Int.MAX_VALUE else 5,
120116
style = MaterialTheme.typography.bodyMedium,
121117
overflow = TextOverflow.Ellipsis,
@@ -144,10 +140,10 @@ fun PlaylistHeader(playlistInfo: PlaylistInfo, totalDuration: Long) {
144140
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
145141
@Composable
146142
private fun PlaylistHeaderPreview() {
147-
NewPipe.init(DownloaderImpl.init(null))
148-
val playlistInfo = PlaylistInfo.getInfo(
149-
ServiceList.YouTube,
150-
"https://www.youtube.com/playlist?list=PLAIcZs9N4171hRrG_4v32Ca2hLvSuQ6QI"
143+
val description = Description("Example description", Description.PLAIN_TEXT)
144+
val playlistInfo = PlaylistInfo(
145+
"", 1, "", "Example playlist", description, listOf(), 1L,
146+
null, "Uploader", listOf(), null
151147
)
152148

153149
AppTheme {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package org.schabi.newpipe.compose.playlist
2+
3+
import androidx.compose.runtime.Immutable
4+
import org.schabi.newpipe.extractor.Image
5+
import org.schabi.newpipe.extractor.Page
6+
import org.schabi.newpipe.extractor.stream.Description
7+
import org.schabi.newpipe.extractor.stream.StreamInfoItem
8+
9+
@Immutable
10+
class PlaylistInfo(
11+
val id: String,
12+
val serviceId: Int,
13+
val url: String,
14+
val name: String,
15+
val description: Description,
16+
val relatedItems: List<StreamInfoItem>,
17+
val streamCount: Long,
18+
val uploaderUrl: String?,
19+
val uploaderName: String?,
20+
val uploaderAvatars: List<Image>,
21+
val nextPage: Page?
22+
)

app/src/main/java/org/schabi/newpipe/compose/stream/StreamList.kt

+3-3
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,10 @@ import org.schabi.newpipe.util.NavigationHelper
2626
@Composable
2727
fun StreamList(
2828
streams: LazyPagingItems<StreamInfoItem>,
29+
itemViewMode: ItemViewMode = determineItemViewMode(),
2930
gridHeader: LazyGridScope.() -> Unit = {},
3031
listHeader: LazyListScope.() -> Unit = {}
3132
) {
32-
val mode = determineItemViewMode()
3333
val context = LocalContext.current
3434
val onClick = remember {
3535
{ stream: StreamInfoItem ->
@@ -54,7 +54,7 @@ fun StreamList(
5454
}
5555
}
5656

57-
if (mode == ItemViewMode.GRID) {
57+
if (itemViewMode == ItemViewMode.GRID) {
5858
val gridState = rememberLazyGridState()
5959

6060
LazyVerticalGridScrollbar(state = gridState) {
@@ -82,7 +82,7 @@ fun StreamList(
8282
val stream = streams[it]!!
8383
val isSelected = selectedStream == stream
8484

85-
if (mode == ItemViewMode.CARD) {
85+
if (itemViewMode == ItemViewMode.CARD) {
8686
StreamCardItem(stream, isSelected, onClick, onLongClick, onDismissPopup)
8787
} else {
8888
StreamListItem(stream, isSelected, onClick, onLongClick, onDismissPopup)

app/src/main/java/org/schabi/newpipe/compose/util/Utils.kt

-21
Original file line numberDiff line numberDiff line change
@@ -2,33 +2,12 @@ package org.schabi.newpipe.compose.util
22

33
import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
44
import androidx.compose.runtime.Composable
5-
import androidx.compose.runtime.remember
65
import androidx.compose.ui.platform.LocalContext
7-
import androidx.compose.ui.text.AnnotatedString
8-
import androidx.compose.ui.text.ParagraphStyle
9-
import androidx.compose.ui.text.SpanStyle
10-
import androidx.compose.ui.text.TextLinkStyles
11-
import androidx.compose.ui.text.fromHtml
12-
import androidx.compose.ui.text.style.TextDecoration
136
import androidx.preference.PreferenceManager
147
import androidx.window.core.layout.WindowWidthSizeClass
158
import org.schabi.newpipe.R
16-
import org.schabi.newpipe.extractor.stream.Description
179
import org.schabi.newpipe.info_list.ItemViewMode
1810

19-
@Composable
20-
fun rememberParsedDescription(description: Description): AnnotatedString {
21-
// TODO: Handle links and hashtags, Markdown.
22-
return remember(description) {
23-
if (description.type == Description.HTML) {
24-
val styles = TextLinkStyles(SpanStyle(textDecoration = TextDecoration.Underline))
25-
AnnotatedString.fromHtml(description.content, styles)
26-
} else {
27-
AnnotatedString(description.content, ParagraphStyle())
28-
}
29-
}
30-
}
31-
3211
@Composable
3312
fun determineItemViewMode(): ItemViewMode {
3413
val context = LocalContext.current

app/src/main/java/org/schabi/newpipe/paging/PlaylistItemsSource.kt

+4-2
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,11 @@ import androidx.paging.PagingSource
44
import androidx.paging.PagingState
55
import kotlinx.coroutines.Dispatchers
66
import kotlinx.coroutines.withContext
7+
import org.schabi.newpipe.compose.playlist.PlaylistInfo
78
import org.schabi.newpipe.extractor.NewPipe
89
import org.schabi.newpipe.extractor.Page
9-
import org.schabi.newpipe.extractor.playlist.PlaylistInfo
1010
import org.schabi.newpipe.extractor.stream.StreamInfoItem
11+
import org.schabi.newpipe.extractor.playlist.PlaylistInfo as ExtractorPlaylistInfo
1112

1213
class PlaylistItemsSource(
1314
private val playlistInfo: PlaylistInfo,
@@ -17,7 +18,8 @@ class PlaylistItemsSource(
1718
override suspend fun load(params: LoadParams<Page>): LoadResult<Page, StreamInfoItem> {
1819
return params.key?.let {
1920
withContext(Dispatchers.IO) {
20-
val response = PlaylistInfo.getMoreItems(service, playlistInfo.url, playlistInfo.nextPage)
21+
val response = ExtractorPlaylistInfo
22+
.getMoreItems(service, playlistInfo.url, playlistInfo.nextPage)
2123
LoadResult.Page(response.items, null, response.nextPage)
2224
}
2325
} ?: LoadResult.Page(playlistInfo.relatedItems, null, playlistInfo.nextPage)

0 commit comments

Comments
 (0)