mirror of
https://github.com/TeamNewPipe/NewPipe
synced 2025-02-03 12:49:14 +00:00
Start implementing stream composable, grid layout
This commit is contained in:
parent
8603b0df6e
commit
bf1c9ba7b5
@ -1,11 +1,12 @@
|
|||||||
package org.schabi.newpipe.compose.playlist
|
package org.schabi.newpipe.compose.playlist
|
||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.grid.GridCells
|
||||||
import androidx.compose.material3.HorizontalDivider
|
import androidx.compose.foundation.lazy.grid.GridItemSpan
|
||||||
|
import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
|
||||||
|
import androidx.compose.foundation.lazy.grid.rememberLazyGridState
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Surface
|
import androidx.compose.material3.Surface
|
||||||
import androidx.compose.material3.Text
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.collectAsState
|
import androidx.compose.runtime.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@ -14,7 +15,9 @@ import androidx.compose.ui.unit.dp
|
|||||||
import androidx.lifecycle.SavedStateHandle
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
import androidx.paging.compose.collectAsLazyPagingItems
|
import androidx.paging.compose.collectAsLazyPagingItems
|
||||||
|
import my.nanihadesuka.compose.LazyVerticalGridScrollbar
|
||||||
import org.schabi.newpipe.DownloaderImpl
|
import org.schabi.newpipe.DownloaderImpl
|
||||||
|
import org.schabi.newpipe.compose.stream.StreamGridItem
|
||||||
import org.schabi.newpipe.compose.theme.AppTheme
|
import org.schabi.newpipe.compose.theme.AppTheme
|
||||||
import org.schabi.newpipe.extractor.NewPipe
|
import org.schabi.newpipe.extractor.NewPipe
|
||||||
import org.schabi.newpipe.extractor.ServiceList
|
import org.schabi.newpipe.extractor.ServiceList
|
||||||
@ -30,14 +33,17 @@ fun Playlist(playlistViewModel: PlaylistViewModel = viewModel()) {
|
|||||||
|
|
||||||
playlistInfo?.let {
|
playlistInfo?.let {
|
||||||
Surface(color = MaterialTheme.colorScheme.background) {
|
Surface(color = MaterialTheme.colorScheme.background) {
|
||||||
LazyColumn {
|
val gridState = rememberLazyGridState()
|
||||||
item {
|
|
||||||
|
LazyVerticalGridScrollbar(state = gridState) {
|
||||||
|
LazyVerticalGrid(state = gridState, columns = GridCells.Adaptive(164.dp)) {
|
||||||
|
item(span = { GridItemSpan(maxLineSpan) }) {
|
||||||
PlaylistHeader(playlistInfo = it, totalDuration = totalDuration)
|
PlaylistHeader(playlistInfo = it, totalDuration = totalDuration)
|
||||||
HorizontalDivider(thickness = 1.dp)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
items(streams.itemCount) {
|
items(streams.itemCount) {
|
||||||
Text(text = streams[it]!!.name)
|
StreamGridItem(streams[it]!!)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,16 +35,12 @@ import org.schabi.newpipe.R
|
|||||||
import org.schabi.newpipe.compose.common.DescriptionText
|
import org.schabi.newpipe.compose.common.DescriptionText
|
||||||
import org.schabi.newpipe.compose.theme.AppTheme
|
import org.schabi.newpipe.compose.theme.AppTheme
|
||||||
import org.schabi.newpipe.error.ErrorUtil
|
import org.schabi.newpipe.error.ErrorUtil
|
||||||
import org.schabi.newpipe.extractor.Image
|
|
||||||
import org.schabi.newpipe.extractor.NewPipe
|
import org.schabi.newpipe.extractor.NewPipe
|
||||||
import org.schabi.newpipe.extractor.ServiceList
|
import org.schabi.newpipe.extractor.ServiceList
|
||||||
import org.schabi.newpipe.extractor.playlist.PlaylistInfo
|
import org.schabi.newpipe.extractor.playlist.PlaylistInfo
|
||||||
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper
|
import org.schabi.newpipe.extractor.services.youtube.YoutubeParsingHelper
|
||||||
import org.schabi.newpipe.extractor.stream.Description
|
import org.schabi.newpipe.extractor.stream.Description
|
||||||
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
|
||||||
import org.schabi.newpipe.extractor.stream.StreamType
|
|
||||||
import org.schabi.newpipe.util.Localization
|
import org.schabi.newpipe.util.Localization
|
||||||
import org.schabi.newpipe.util.NO_SERVICE_ID
|
|
||||||
import org.schabi.newpipe.util.NavigationHelper
|
import org.schabi.newpipe.util.NavigationHelper
|
||||||
import org.schabi.newpipe.util.image.ImageStrategy
|
import org.schabi.newpipe.util.image.ImageStrategy
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@ -54,7 +50,7 @@ fun PlaylistHeader(playlistInfo: PlaylistInfo, totalDuration: Long) {
|
|||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = Modifier.padding(4.dp),
|
modifier = Modifier.padding(12.dp),
|
||||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
@ -148,22 +144,6 @@ fun PlaylistHeader(playlistInfo: PlaylistInfo, totalDuration: Long) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun StreamInfoItem(
|
|
||||||
serviceId: Int = NO_SERVICE_ID,
|
|
||||||
url: String,
|
|
||||||
name: String,
|
|
||||||
streamType: StreamType = StreamType.NONE,
|
|
||||||
uploaderName: String? = null,
|
|
||||||
uploaderUrl: String? = null,
|
|
||||||
uploaderAvatars: List<Image> = emptyList(),
|
|
||||||
duration: Long,
|
|
||||||
) = StreamInfoItem(serviceId, url, name, streamType).apply {
|
|
||||||
this.uploaderName = uploaderName
|
|
||||||
this.uploaderUrl = uploaderUrl
|
|
||||||
this.uploaderAvatars = uploaderAvatars
|
|
||||||
this.duration = duration
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||||
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
@Composable
|
@Composable
|
||||||
|
@ -0,0 +1,130 @@
|
|||||||
|
package org.schabi.newpipe.compose.stream
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.size
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Surface
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.res.painterResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
|
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.fragment.app.FragmentActivity
|
||||||
|
import coil.compose.AsyncImage
|
||||||
|
import org.schabi.newpipe.R
|
||||||
|
import org.schabi.newpipe.compose.theme.AppTheme
|
||||||
|
import org.schabi.newpipe.extractor.Image
|
||||||
|
import org.schabi.newpipe.extractor.stream.StreamInfoItem
|
||||||
|
import org.schabi.newpipe.extractor.stream.StreamType
|
||||||
|
import org.schabi.newpipe.util.Localization
|
||||||
|
import org.schabi.newpipe.util.NO_SERVICE_ID
|
||||||
|
import org.schabi.newpipe.util.NavigationHelper
|
||||||
|
import org.schabi.newpipe.util.image.ImageStrategy
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun StreamGridItem(stream: StreamInfoItem) {
|
||||||
|
val context = LocalContext.current
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = Modifier
|
||||||
|
.clickable {
|
||||||
|
NavigationHelper.openVideoDetailFragment(
|
||||||
|
context, (context as FragmentActivity).supportFragmentManager,
|
||||||
|
stream.serviceId, stream.url, stream.name, null, false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.padding(12.dp)
|
||||||
|
) {
|
||||||
|
AsyncImage(
|
||||||
|
model = ImageStrategy.choosePreferredImage(stream.thumbnails),
|
||||||
|
contentDescription = null,
|
||||||
|
placeholder = painterResource(R.drawable.placeholder_thumbnail_video),
|
||||||
|
error = painterResource(R.drawable.placeholder_thumbnail_video),
|
||||||
|
modifier = Modifier.size(width = 164.dp, height = 92.dp)
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(
|
||||||
|
text = stream.name,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
style = MaterialTheme.typography.titleSmall,
|
||||||
|
maxLines = 2
|
||||||
|
)
|
||||||
|
|
||||||
|
Text(text = stream.uploaderName.orEmpty(), style = MaterialTheme.typography.bodySmall)
|
||||||
|
|
||||||
|
Text(text = getStreamInfoDetail(context, stream), style = MaterialTheme.typography.bodySmall)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getStreamInfoDetail(context: Context, stream: StreamInfoItem): String {
|
||||||
|
val views = if (stream.viewCount >= 0) {
|
||||||
|
when (stream.streamType) {
|
||||||
|
StreamType.AUDIO_LIVE_STREAM -> Localization.listeningCount(context, stream.viewCount)
|
||||||
|
StreamType.LIVE_STREAM -> Localization.shortWatchingCount(context, stream.viewCount)
|
||||||
|
else -> Localization.shortViewCount(context, stream.viewCount)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
""
|
||||||
|
}
|
||||||
|
val date =
|
||||||
|
Localization.relativeTimeOrTextual(context, stream.uploadDate, stream.textualUploadDate)
|
||||||
|
|
||||||
|
return if (views.isEmpty()) {
|
||||||
|
date
|
||||||
|
} else if (date.isNullOrEmpty()) {
|
||||||
|
views
|
||||||
|
} else {
|
||||||
|
"$views • $date"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun StreamInfoItem(
|
||||||
|
serviceId: Int = NO_SERVICE_ID,
|
||||||
|
url: String = "",
|
||||||
|
name: String = "Stream",
|
||||||
|
streamType: StreamType,
|
||||||
|
uploaderName: String? = "Uploader",
|
||||||
|
uploaderUrl: String? = null,
|
||||||
|
uploaderAvatars: List<Image> = emptyList(),
|
||||||
|
duration: Long = TimeUnit.HOURS.toSeconds(1),
|
||||||
|
viewCount: Long = 10,
|
||||||
|
textualUploadDate: String = "1 month ago"
|
||||||
|
) = StreamInfoItem(serviceId, url, name, streamType).apply {
|
||||||
|
this.uploaderName = uploaderName
|
||||||
|
this.uploaderUrl = uploaderUrl
|
||||||
|
this.uploaderAvatars = uploaderAvatars
|
||||||
|
this.duration = duration
|
||||||
|
this.viewCount = viewCount
|
||||||
|
this.textualUploadDate = textualUploadDate
|
||||||
|
}
|
||||||
|
|
||||||
|
private class StreamItemPreviewProvider : PreviewParameterProvider<StreamInfoItem> {
|
||||||
|
override val values = sequenceOf(
|
||||||
|
StreamInfoItem(streamType = StreamType.NONE),
|
||||||
|
StreamInfoItem(streamType = StreamType.LIVE_STREAM),
|
||||||
|
StreamInfoItem(streamType = StreamType.AUDIO_LIVE_STREAM),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview(name = "Light mode", uiMode = Configuration.UI_MODE_NIGHT_NO)
|
||||||
|
@Preview(name = "Dark mode", uiMode = Configuration.UI_MODE_NIGHT_YES)
|
||||||
|
@Composable
|
||||||
|
private fun StreamGridItemPreview(
|
||||||
|
@PreviewParameter(StreamItemPreviewProvider::class) stream: StreamInfoItem
|
||||||
|
) {
|
||||||
|
AppTheme {
|
||||||
|
Surface(color = MaterialTheme.colorScheme.background) {
|
||||||
|
StreamGridItem(stream)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user