Skip to content

Commit 6c80cbd

Browse files
Create playlist header composable
1 parent f74af0a commit 6c80cbd

File tree

3 files changed

+164
-15
lines changed

3 files changed

+164
-15
lines changed
Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
package org.schabi.newpipe.compose.playlist
2+
3+
import android.content.res.Configuration
4+
import androidx.compose.foundation.Image
5+
import androidx.compose.foundation.clickable
6+
import androidx.compose.foundation.layout.Arrangement
7+
import androidx.compose.foundation.layout.Column
8+
import androidx.compose.foundation.layout.Row
9+
import androidx.compose.foundation.layout.fillMaxWidth
10+
import androidx.compose.foundation.layout.padding
11+
import androidx.compose.foundation.layout.size
12+
import androidx.compose.foundation.shape.CircleShape
13+
import androidx.compose.material3.MaterialTheme
14+
import androidx.compose.material3.Surface
15+
import androidx.compose.material3.Text
16+
import androidx.compose.material3.TextButton
17+
import androidx.compose.runtime.Composable
18+
import androidx.compose.runtime.getValue
19+
import androidx.compose.runtime.mutableStateOf
20+
import androidx.compose.runtime.saveable.rememberSaveable
21+
import androidx.compose.runtime.setValue
22+
import androidx.compose.ui.Alignment
23+
import androidx.compose.ui.Modifier
24+
import androidx.compose.ui.draw.clip
25+
import androidx.compose.ui.platform.LocalContext
26+
import androidx.compose.ui.res.painterResource
27+
import androidx.compose.ui.res.stringResource
28+
import androidx.compose.ui.text.style.TextOverflow
29+
import androidx.compose.ui.tooling.preview.Preview
30+
import androidx.compose.ui.unit.dp
31+
import androidx.fragment.app.FragmentActivity
32+
import coil.compose.AsyncImage
33+
import org.schabi.newpipe.DownloaderImpl
34+
import org.schabi.newpipe.R
35+
import org.schabi.newpipe.compose.theme.AppTheme
36+
import org.schabi.newpipe.compose.util.rememberParsedDescription
37+
import org.schabi.newpipe.error.ErrorUtil
38+
import org.schabi.newpipe.extractor.NewPipe
39+
import org.schabi.newpipe.extractor.ServiceList
40+
import org.schabi.newpipe.extractor.playlist.PlaylistInfo
41+
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper
42+
import org.schabi.newpipe.extractor.stream.Description
43+
import org.schabi.newpipe.util.NavigationHelper
44+
import org.schabi.newpipe.util.image.ImageStrategy
45+
46+
@Composable
47+
fun PlaylistHeader(playlistInfo: PlaylistInfo, totalDuration: Long) {
48+
val context = LocalContext.current
49+
50+
Column(
51+
modifier = Modifier.padding(4.dp),
52+
verticalArrangement = Arrangement.spacedBy(4.dp)
53+
) {
54+
Text(
55+
text = playlistInfo.name,
56+
style = MaterialTheme.typography.titleMedium
57+
)
58+
59+
Row(
60+
modifier = Modifier.fillMaxWidth(),
61+
horizontalArrangement = Arrangement.SpaceBetween
62+
) {
63+
Row(
64+
verticalAlignment = Alignment.CenterVertically,
65+
horizontalArrangement = Arrangement.spacedBy(4.dp),
66+
modifier = Modifier.apply {
67+
if (playlistInfo.uploaderName != null && playlistInfo.uploaderUrl != null) {
68+
clickable {
69+
try {
70+
NavigationHelper.openChannelFragment(
71+
(context as FragmentActivity).supportFragmentManager,
72+
playlistInfo.serviceId, playlistInfo.uploaderUrl,
73+
playlistInfo.uploaderName
74+
)
75+
} catch (e: Exception) {
76+
ErrorUtil.showUiErrorSnackbar(context, "Opening channel fragment", e)
77+
}
78+
}
79+
}
80+
}
81+
) {
82+
val imageModifier = Modifier
83+
.size(24.dp)
84+
.clip(CircleShape)
85+
val isMix = YoutubeParsingHelper.isYoutubeMixId(playlistInfo.id) ||
86+
YoutubeParsingHelper.isYoutubeMusicMixId(playlistInfo.id)
87+
88+
if (playlistInfo.serviceId == ServiceList.YouTube.serviceId && isMix) {
89+
Image(
90+
painter = painterResource(R.drawable.ic_radio),
91+
contentDescription = null,
92+
modifier = imageModifier
93+
)
94+
} else {
95+
AsyncImage(
96+
model = ImageStrategy.choosePreferredImage(playlistInfo.uploaderAvatars),
97+
contentDescription = null,
98+
placeholder = painterResource(R.drawable.placeholder_person),
99+
error = painterResource(R.drawable.placeholder_person),
100+
modifier = imageModifier
101+
)
102+
}
103+
104+
val uploader = playlistInfo.uploaderName.orEmpty()
105+
.ifEmpty { stringResource(R.string.playlist_no_uploader) }
106+
Text(text = uploader, style = MaterialTheme.typography.bodySmall)
107+
}
108+
109+
Text(
110+
text = playlistInfo.streamCount.toString(),
111+
style = MaterialTheme.typography.bodySmall
112+
)
113+
}
114+
115+
val description = playlistInfo.description ?: Description.EMPTY_DESCRIPTION
116+
if (description != Description.EMPTY_DESCRIPTION) {
117+
var isExpanded by rememberSaveable { mutableStateOf(false) }
118+
val parsedDescription = rememberParsedDescription(description)
119+
120+
Text(
121+
text = parsedDescription,
122+
maxLines = if (isExpanded) Int.MAX_VALUE else 5,
123+
style = MaterialTheme.typography.bodyMedium,
124+
overflow = TextOverflow.Ellipsis,
125+
)
126+
127+
if (parsedDescription.lineSequence().take(6).count() > 5) {
128+
TextButton(
129+
onClick = { isExpanded = !isExpanded },
130+
modifier = Modifier.align(Alignment.End)
131+
) {
132+
Text(
133+
text = stringResource(if (isExpanded) R.string.show_less else R.string.show_more)
134+
)
135+
}
136+
}
137+
}
138+
}
139+
}
140+
141+
@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO)
142+
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
143+
@Composable
144+
fun PlaylistHeaderPreview() {
145+
NewPipe.init(DownloaderImpl.init(null))
146+
val playlistInfo = PlaylistInfo.getInfo(
147+
ServiceList.YouTube,
148+
"https://www.youtube.com/playlist?list=PLAIcZs9N4171hRrG_4v32Ca2hLvSuQ6QI"
149+
)
150+
151+
AppTheme {
152+
Surface(color = MaterialTheme.colorScheme.background) {
153+
PlaylistHeader(playlistInfo = playlistInfo, totalDuration = 1000)
154+
}
155+
}
156+
}

app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -500,7 +500,7 @@ private void setStreamCountAndOverallDuration(final List<StreamInfoItem> list,
500500
final boolean isDurationComplete) {
501501
if (activity != null && headerBinding != null) {
502502
playlistOverallDurationSeconds += list.stream()
503-
.mapToLong(x -> x.getDuration())
503+
.mapToLong(StreamInfoItem::getDuration)
504504
.sum();
505505
headerBinding.playlistStreamCount.setText(
506506
Localization.concatenateStrings(

app/src/main/java/org/schabi/newpipe/local/bookmark/BookmarkFragment.java

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
import androidx.annotation.NonNull;
1616
import androidx.annotation.Nullable;
1717
import androidx.appcompat.app.AlertDialog;
18-
import androidx.fragment.app.FragmentManager;
1918
import androidx.recyclerview.widget.ItemTouchHelper;
2019
import androidx.recyclerview.widget.RecyclerView;
2120

@@ -36,10 +35,10 @@
3635
import org.schabi.newpipe.local.holder.RemoteBookmarkPlaylistItemHolder;
3736
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
3837
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
39-
import org.schabi.newpipe.util.debounce.DebounceSavable;
40-
import org.schabi.newpipe.util.debounce.DebounceSaver;
4138
import org.schabi.newpipe.util.NavigationHelper;
4239
import org.schabi.newpipe.util.OnClickGesture;
40+
import org.schabi.newpipe.util.debounce.DebounceSavable;
41+
import org.schabi.newpipe.util.debounce.DebounceSaver;
4342

4443
import java.util.ArrayList;
4544
import java.util.List;
@@ -134,20 +133,14 @@ protected void initListeners() {
134133
itemListAdapter.setSelectedListener(new OnClickGesture<>() {
135134
@Override
136135
public void selected(final LocalItem selectedItem) {
137-
final FragmentManager fragmentManager = getFM();
136+
final var fragmentManager = getFM();
138137

139-
if (selectedItem instanceof PlaylistMetadataEntry) {
140-
final PlaylistMetadataEntry entry = ((PlaylistMetadataEntry) selectedItem);
138+
if (selectedItem instanceof PlaylistMetadataEntry entry) {
141139
NavigationHelper.openLocalPlaylistFragment(fragmentManager, entry.getUid(),
142140
entry.name);
143-
144-
} else if (selectedItem instanceof PlaylistRemoteEntity) {
145-
final PlaylistRemoteEntity entry = ((PlaylistRemoteEntity) selectedItem);
146-
NavigationHelper.openPlaylistFragment(
147-
fragmentManager,
148-
entry.getServiceId(),
149-
entry.getUrl(),
150-
entry.getName());
141+
} else if (selectedItem instanceof PlaylistRemoteEntity entry) {
142+
NavigationHelper.openPlaylistFragment(fragmentManager, entry.getServiceId(),
143+
entry.getUrl(), entry.getName());
151144
}
152145
}
153146

0 commit comments

Comments
 (0)