Create playlist header composable
This commit is contained in:
parent
10dd5710ee
commit
68b3dd5546
|
@ -0,0 +1,161 @@
|
|||
package org.schabi.newpipe.compose.playlist
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.fragment.app.FragmentActivity
|
||||
import coil.compose.AsyncImage
|
||||
import org.schabi.newpipe.DownloaderImpl
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.compose.common.DescriptionText
|
||||
import org.schabi.newpipe.compose.theme.AppTheme
|
||||
import org.schabi.newpipe.error.ErrorUtil
|
||||
import org.schabi.newpipe.extractor.NewPipe
|
||||
import org.schabi.newpipe.extractor.ServiceList
|
||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo
|
||||
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper
|
||||
import org.schabi.newpipe.extractor.stream.Description
|
||||
import org.schabi.newpipe.util.NavigationHelper
|
||||
import org.schabi.newpipe.util.image.ImageStrategy
|
||||
|
||||
@Composable
|
||||
fun PlaylistHeader(playlistInfo: PlaylistInfo, totalDuration: Long) {
|
||||
val context = LocalContext.current
|
||||
|
||||
Column(
|
||||
modifier = Modifier.padding(4.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||
) {
|
||||
Text(
|
||||
text = playlistInfo.name,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceBetween
|
||||
) {
|
||||
Row(
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
modifier = Modifier.apply {
|
||||
if (playlistInfo.uploaderName != null && playlistInfo.uploaderUrl != null) {
|
||||
clickable {
|
||||
try {
|
||||
NavigationHelper.openChannelFragment(
|
||||
(context as FragmentActivity).supportFragmentManager,
|
||||
playlistInfo.serviceId, playlistInfo.uploaderUrl,
|
||||
playlistInfo.uploaderName
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
ErrorUtil.showUiErrorSnackbar(context, "Opening channel fragment", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
) {
|
||||
val imageModifier = Modifier
|
||||
.size(24.dp)
|
||||
.clip(CircleShape)
|
||||
val isMix = YoutubeParsingHelper.isYoutubeMixId(playlistInfo.id) ||
|
||||
YoutubeParsingHelper.isYoutubeMusicMixId(playlistInfo.id)
|
||||
|
||||
if (playlistInfo.serviceId == ServiceList.YouTube.serviceId && isMix) {
|
||||
Image(
|
||||
painter = painterResource(R.drawable.ic_radio),
|
||||
contentDescription = null,
|
||||
modifier = imageModifier
|
||||
)
|
||||
} else {
|
||||
AsyncImage(
|
||||
model = ImageStrategy.choosePreferredImage(playlistInfo.uploaderAvatars),
|
||||
contentDescription = null,
|
||||
placeholder = painterResource(R.drawable.placeholder_person),
|
||||
error = painterResource(R.drawable.placeholder_person),
|
||||
modifier = imageModifier
|
||||
)
|
||||
}
|
||||
|
||||
val uploader = playlistInfo.uploaderName.orEmpty()
|
||||
.ifEmpty { stringResource(R.string.playlist_no_uploader) }
|
||||
Text(text = uploader, style = MaterialTheme.typography.bodySmall)
|
||||
}
|
||||
|
||||
Text(
|
||||
text = playlistInfo.streamCount.toString(),
|
||||
style = MaterialTheme.typography.bodySmall
|
||||
)
|
||||
}
|
||||
|
||||
val description = playlistInfo.description ?: Description.EMPTY_DESCRIPTION
|
||||
if (description != Description.EMPTY_DESCRIPTION) {
|
||||
var isExpanded by rememberSaveable { mutableStateOf(false) }
|
||||
var isExpandable by rememberSaveable { mutableStateOf(false) }
|
||||
|
||||
DescriptionText(
|
||||
description = description,
|
||||
maxLines = if (isExpanded) Int.MAX_VALUE else 5,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
onTextLayout = {
|
||||
if (it.hasVisualOverflow) {
|
||||
isExpandable = true
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if (isExpandable) {
|
||||
TextButton(
|
||||
onClick = { isExpanded = !isExpanded },
|
||||
modifier = Modifier.align(Alignment.End)
|
||||
) {
|
||||
Text(
|
||||
text = stringResource(if (isExpanded) R.string.show_less else R.string.show_more)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||
@Composable
|
||||
fun PlaylistHeaderPreview() {
|
||||
NewPipe.init(DownloaderImpl.init(null))
|
||||
val playlistInfo = PlaylistInfo.getInfo(
|
||||
ServiceList.YouTube,
|
||||
"https://www.youtube.com/playlist?list=PLAIcZs9N4171hRrG_4v32Ca2hLvSuQ6QI"
|
||||
)
|
||||
|
||||
AppTheme {
|
||||
Surface(color = MaterialTheme.colorScheme.background) {
|
||||
PlaylistHeader(playlistInfo = playlistInfo, totalDuration = 1000)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -500,7 +500,7 @@ public class PlaylistFragment extends BaseListInfoFragment<StreamInfoItem, Playl
|
|||
final boolean isDurationComplete) {
|
||||
if (activity != null && headerBinding != null) {
|
||||
playlistOverallDurationSeconds += list.stream()
|
||||
.mapToLong(x -> x.getDuration())
|
||||
.mapToLong(StreamInfoItem::getDuration)
|
||||
.sum();
|
||||
headerBinding.playlistStreamCount.setText(
|
||||
Localization.concatenateStrings(
|
||||
|
|
|
@ -15,7 +15,6 @@ import android.view.ViewGroup;
|
|||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.recyclerview.widget.ItemTouchHelper;
|
||||
import androidx.recyclerview.widget.RecyclerView;
|
||||
|
||||
|
@ -36,10 +35,10 @@ import org.schabi.newpipe.local.holder.LocalBookmarkPlaylistItemHolder;
|
|||
import org.schabi.newpipe.local.holder.RemoteBookmarkPlaylistItemHolder;
|
||||
import org.schabi.newpipe.local.playlist.LocalPlaylistManager;
|
||||
import org.schabi.newpipe.local.playlist.RemotePlaylistManager;
|
||||
import org.schabi.newpipe.util.debounce.DebounceSavable;
|
||||
import org.schabi.newpipe.util.debounce.DebounceSaver;
|
||||
import org.schabi.newpipe.util.NavigationHelper;
|
||||
import org.schabi.newpipe.util.OnClickGesture;
|
||||
import org.schabi.newpipe.util.debounce.DebounceSavable;
|
||||
import org.schabi.newpipe.util.debounce.DebounceSaver;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
@ -134,20 +133,14 @@ public final class BookmarkFragment extends BaseLocalListFragment<List<PlaylistL
|
|||
itemListAdapter.setSelectedListener(new OnClickGesture<>() {
|
||||
@Override
|
||||
public void selected(final LocalItem selectedItem) {
|
||||
final FragmentManager fragmentManager = getFM();
|
||||
final var fragmentManager = getFM();
|
||||
|
||||
if (selectedItem instanceof PlaylistMetadataEntry) {
|
||||
final PlaylistMetadataEntry entry = ((PlaylistMetadataEntry) selectedItem);
|
||||
if (selectedItem instanceof PlaylistMetadataEntry entry) {
|
||||
NavigationHelper.openLocalPlaylistFragment(fragmentManager, entry.getUid(),
|
||||
entry.name);
|
||||
|
||||
} else if (selectedItem instanceof PlaylistRemoteEntity) {
|
||||
final PlaylistRemoteEntity entry = ((PlaylistRemoteEntity) selectedItem);
|
||||
NavigationHelper.openPlaylistFragment(
|
||||
fragmentManager,
|
||||
entry.getServiceId(),
|
||||
entry.getUrl(),
|
||||
entry.getName());
|
||||
} else if (selectedItem instanceof PlaylistRemoteEntity entry) {
|
||||
NavigationHelper.openPlaylistFragment(fragmentManager, entry.getServiceId(),
|
||||
entry.getUrl(), entry.getName());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ import androidx.compose.ui.Modifier
|
|||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.ParagraphStyle
|
||||
import androidx.compose.ui.text.SpanStyle
|
||||
import androidx.compose.ui.text.TextLayoutResult
|
||||
import androidx.compose.ui.text.TextLinkStyles
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.fromHtml
|
||||
|
@ -21,6 +22,7 @@ fun DescriptionText(
|
|||
modifier: Modifier = Modifier,
|
||||
overflow: TextOverflow = TextOverflow.Clip,
|
||||
maxLines: Int = Int.MAX_VALUE,
|
||||
onTextLayout: (TextLayoutResult) -> Unit = {},
|
||||
style: TextStyle = LocalTextStyle.current
|
||||
) {
|
||||
// TODO: Handle links and hashtags, Markdown.
|
||||
|
@ -38,6 +40,7 @@ fun DescriptionText(
|
|||
text = parsedDescription,
|
||||
maxLines = maxLines,
|
||||
style = style,
|
||||
overflow = overflow
|
||||
overflow = overflow,
|
||||
onTextLayout = onTextLayout
|
||||
)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue