add currently plaing indicator to playlist and make playlist use rounded corners

This commit is contained in:
Christian Schabesberger 2024-09-04 14:02:10 +02:00
parent a47ea8e078
commit 9f1c06928a
4 changed files with 160 additions and 130 deletions

View File

@ -20,22 +20,19 @@
package net.newpipe.newplayer.ui.videoplayer
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.itemsIndexed
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Surface
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
@ -57,6 +54,7 @@ import net.newpipe.newplayer.utils.rememberReorderHapticFeedback
import sh.calvin.reorderable.ReorderableItem
import sh.calvin.reorderable.rememberReorderableLazyListState
val ITEM_CORNER_SHAPE = RoundedCornerShape(10.dp)
@Composable
fun StreamSelectUI(
@ -85,34 +83,36 @@ fun StreamSelectUI(
}
}
) { innerPadding ->
if (isChapterSelect) {
LazyColumn(
modifier = Modifier
.padding(innerPadding)
.fillMaxSize(),
contentPadding = PaddingValues(start = 8.dp, end = 4.dp)
) {
Box(modifier = Modifier.padding(innerPadding)) {
if (isChapterSelect) {
LazyColumn(
modifier = Modifier
.padding(start = 5.dp, end = 5.dp)
.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(5.dp),
) {
items(uiState.chapters.size) { chapterIndex ->
val chapter = uiState.chapters[chapterIndex]
ChapterItem(
id = chapterIndex,
chapterTitle = chapter.chapterTitle ?: "",
chapterStartInMs = chapter.chapterStartInMs,
thumbnail = chapter.thumbnail,
onClicked = {
viewModel.chapterSelected(chapter)
}
)
}
items(uiState.chapters.size) { chapterIndex ->
val chapter = uiState.chapters[chapterIndex]
ChapterItem(
id = chapterIndex,
chapterTitle = chapter.chapterTitle ?: "",
chapterStartInMs = chapter.chapterStartInMs,
thumbnail = chapter.thumbnail,
onClicked = {
viewModel.chapterSelected(chapter)
}
)
}
} else {
ReorderableStreamItemsList(
padding = PaddingValues(start = 5.dp, end = 5.dp),
viewModel = viewModel,
uiState = uiState
)
}
} else {
ReorderableStreamItemsList(
innerPadding = innerPadding,
viewModel = viewModel,
uiState = uiState
)
}
}
}
@ -120,7 +120,7 @@ fun StreamSelectUI(
@Composable
fun ReorderableStreamItemsList(
innerPadding: PaddingValues,
padding: PaddingValues,
viewModel: VideoPlayerViewModel,
uiState: VideoPlayerUIState
) {
@ -135,8 +135,9 @@ fun ReorderableStreamItemsList(
LazyColumn(
modifier = Modifier
.padding(innerPadding)
.padding(padding)
.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(5.dp),
state = lazyListState
) {
itemsIndexed(uiState.playList, key = {_, item -> item.uniqueId}) { index, playlistItem ->
@ -145,16 +146,13 @@ fun ReorderableStreamItemsList(
key = playlistItem.uniqueId
) { isDragging ->
StreamItem(
uniqueId = playlistItem.uniqueId,
title = playlistItem.title,
creator = playlistItem.creator,
thumbnail = playlistItem.thumbnail,
lengthInMs = playlistItem.lengthInS.toLong() * 1000,
playlistItem = playlistItem,
onClicked = { viewModel.streamSelected(0) },
reorderableScope = this@ReorderableItem,
haptic = haptic,
onDragFinished = viewModel::onStreamItemDragFinished,
isDragging = isDragging
isDragging = isDragging,
isCurrentlyPlaying = playlistItem.uniqueId == uiState.currentlyPlaying.uniqueId
)
}
}
@ -227,7 +225,8 @@ fun VideoPlayerStreamSelectUIPreview() {
thumbnail = null,
uniqueId = 2
)
)
),
currentlyPlaying = PlaylistItem.DUMMY.copy(uniqueId = 1)
)
)
}

View File

@ -33,6 +33,7 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@ -44,6 +45,7 @@ import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
import net.newpipe.newplayer.R
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
import net.newpipe.newplayer.ui.videoplayer.ITEM_CORNER_SHAPE
import net.newpipe.newplayer.utils.BitmapThumbnail
import net.newpipe.newplayer.utils.OnlineThumbnail
import net.newpipe.newplayer.utils.Thumbnail
@ -65,39 +67,19 @@ fun ChapterItem(
Row(
modifier = modifier
.height(80.dp)
.padding(5.dp)
.clip(ITEM_CORNER_SHAPE)
.clickable { onClicked(id) }
) {
val contentDescription = stringResource(R.string.chapter_thumbnail)
if (thumbnail != null) {
when (thumbnail) {
is OnlineThumbnail -> AsyncImage(
model = thumbnail.url,
contentDescription = contentDescription
)
is BitmapThumbnail -> Image(
bitmap = thumbnail.img,
contentDescription = contentDescription
)
is VectorThumbnail -> Image(
imageVector = thumbnail.vec,
contentDescription = contentDescription
)
}
AsyncImage(
model = thumbnail,
contentDescription = contentDescription
)
} else {
Image(
painterResource(R.drawable.tiny_placeholder),
contentDescription = stringResource(R.string.chapter_thumbnail)
)
}
Thumbnail(
thumbnail = thumbnail,
contentDescription = contentDescription,
shape = ITEM_CORNER_SHAPE
)
Column(
modifier = Modifier.padding(start = 8.dp),
modifier = Modifier
.padding(start = 8.dp, top = 5.dp, bottom = 5.dp)
.weight(1f),
horizontalAlignment = Alignment.Start,
) {
Text(

View File

@ -38,6 +38,7 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.DragHandle
import androidx.compose.material3.Icon
@ -49,6 +50,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
@ -59,8 +61,10 @@ import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil.compose.AsyncImage
import net.newpipe.newplayer.R
import net.newpipe.newplayer.playerInternals.PlaylistItem
import net.newpipe.newplayer.ui.CONTROLLER_UI_BACKGROUND_COLOR
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
import net.newpipe.newplayer.ui.videoplayer.ITEM_CORNER_SHAPE
import net.newpipe.newplayer.ui.videoplayer.gesture_ui.SEEK_ANIMATION_FADE_IN
import net.newpipe.newplayer.ui.videoplayer.gesture_ui.SEEK_ANIMATION_FADE_OUT
import net.newpipe.newplayer.utils.BitmapThumbnail
@ -73,56 +77,26 @@ import net.newpipe.newplayer.utils.getLocale
import net.newpipe.newplayer.utils.getTimeStringFromMs
import sh.calvin.reorderable.ReorderableCollectionItemScope
@Composable
private fun Thumbnail(thumbnail: Thumbnail?, contentDescription: String) {
if (thumbnail != null) {
when (thumbnail) {
is OnlineThumbnail -> AsyncImage(
modifier = Modifier.fillMaxSize(),
model = thumbnail.url,
contentDescription = contentDescription
)
is BitmapThumbnail -> Image(
modifier = Modifier.fillMaxSize(),
bitmap = thumbnail.img,
contentDescription = contentDescription
)
is VectorThumbnail -> Image(
modifier = Modifier.fillMaxSize(),
imageVector = thumbnail.vec,
contentDescription = contentDescription
)
}
} else {
Image(
painter = painterResource(R.drawable.tiny_placeholder),
contentDescription = contentDescription
)
}
}
@Composable
fun StreamItem(
modifier: Modifier = Modifier,
uniqueId: Long,
title: String,
creator: String?,
thumbnail: Thumbnail?,
lengthInMs: Long,
playlistItem: PlaylistItem,
onClicked: (Long) -> Unit,
onDragFinished: () -> Unit,
reorderableScope: ReorderableCollectionItemScope?,
haptic: ReorderHapticFeedback?,
isDragging: Boolean
isDragging: Boolean,
isCurrentlyPlaying: Boolean
) {
val locale = getLocale()!!
val interactionSource = remember { MutableInteractionSource() }
Box(modifier = modifier.height(60.dp).clickable {
onClicked(uniqueId)
}) {
Box(modifier = modifier
.height(60.dp)
.clip(ITEM_CORNER_SHAPE)
.clickable {
onClicked(playlistItem.uniqueId)
}) {
AnimatedVisibility(
visible = isDragging,
@ -132,13 +106,25 @@ fun StreamItem(
Surface(
modifier = Modifier.fillMaxSize(),
color = MaterialTheme.colorScheme.background,
shadowElevation = 8.dp
shadowElevation = 8.dp,
shape = ITEM_CORNER_SHAPE
) {}
}
AnimatedVisibility(
visible = isCurrentlyPlaying,
enter = fadeIn(animationSpec = tween(200)),
exit = fadeOut(animationSpec = tween(400))
) {
Surface(
modifier = Modifier.fillMaxSize(),
color = Color.White.copy(alpha = 0.2f),
) {}
}
Row(
modifier = modifier
.padding(5.dp)
.padding(0.dp)
) {
Box(
modifier = Modifier
@ -146,9 +132,14 @@ fun StreamItem(
.fillMaxSize()
) {
val contentDescription = stringResource(R.string.stream_item_thumbnail)
Thumbnail(thumbnail, contentDescription)
Thumbnail(
thumbnail = playlistItem.thumbnail,
contentDescription = contentDescription,
shape = ITEM_CORNER_SHAPE
)
Surface(
color = CONTROLLER_UI_BACKGROUND_COLOR,
shape = ITEM_CORNER_SHAPE,
modifier = Modifier
.wrapContentSize()
.align(Alignment.BottomEnd)
@ -162,7 +153,7 @@ fun StreamItem(
bottom = 0.5.dp
),
text = getTimeStringFromMs(
lengthInMs,
playlistItem.lengthInS * 1000L,
locale,
leadingZerosForMinutes = false
),
@ -173,24 +164,25 @@ fun StreamItem(
Column(
modifier = Modifier
.padding(1.dp)
.padding(6.dp)
.weight(1f)
.wrapContentHeight()
.fillMaxWidth()
) {
Text(
text = title, fontSize = 14.sp, fontWeight = FontWeight.Bold, maxLines = 1,
text = playlistItem.title,
fontSize = 14.sp,
fontWeight = FontWeight.Bold,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Text(
text = playlistItem.creator,
fontSize = 13.sp,
fontWeight = FontWeight.Light,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
if (creator != null) {
Text(
text = creator,
fontSize = 13.sp,
fontWeight = FontWeight.Light,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
IconButton(
modifier = if (reorderableScope != null) {
@ -233,16 +225,13 @@ fun StreamItemPreview() {
Surface(modifier = Modifier.fillMaxSize(), color = Color.DarkGray) {
Box(modifier = Modifier.fillMaxSize()) {
StreamItem(
uniqueId = 0,
title = "Video Title",
creator = "Video Creator",
thumbnail = null,
lengthInMs = 15 * 60 * 1000,
playlistItem = PlaylistItem.DUMMY,
onClicked = {},
reorderableScope = null,
haptic = null,
onDragFinished = {},
isDragging = false,
isCurrentlyPlaying = true
)
}
}

View File

@ -24,9 +24,12 @@ import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.ContextWrapper
import android.graphics.drawable.shapes.Shape
import android.view.WindowManager
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.displayCutout
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.union
import androidx.compose.foundation.layout.waterfall
@ -34,11 +37,16 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.painterResource
import androidx.core.os.ConfigurationCompat
import androidx.core.view.WindowCompat
import coil.compose.AsyncImage
import net.newpipe.newplayer.R
import net.newpipe.newplayer.model.EmbeddedUiConfig
import java.util.Locale
@ -100,7 +108,7 @@ fun getEmbeddedUiConfig(activity: Activity): EmbeddedUiConfig {
}
@Composable
fun getInsets() =
fun getInsets() =
WindowInsets.systemBars.union(WindowInsets.displayCutout).union(WindowInsets.waterfall)
private const val HOURS_PER_DAY = 24
@ -113,7 +121,11 @@ private const val MILLIS_PER_DAY =
private const val MILLIS_PER_HOUR = MINUTES_PER_HOUR * SECONDS_PER_MINUTE * MILLIS_PER_SECOND
private const val MILLIS_PER_MINUTE = SECONDS_PER_MINUTE * MILLIS_PER_SECOND
fun getTimeStringFromMs(timeSpanInMs: Long, locale: Locale, leadingZerosForMinutes:Boolean = true): String {
fun getTimeStringFromMs(
timeSpanInMs: Long,
locale: Locale,
leadingZerosForMinutes: Boolean = true
): String {
val days = timeSpanInMs / MILLIS_PER_DAY
val millisThisDay = timeSpanInMs - days * MILLIS_PER_DAY
val hours = millisThisDay / MILLIS_PER_HOUR
@ -126,7 +138,55 @@ fun getTimeStringFromMs(timeSpanInMs: Long, locale: Locale, leadingZerosForMinut
val time_string =
if (0L < days) String.format(locale, "%d:%02d:%02d:%02d", days, hours, minutes, seconds)
else if (0L < hours) String.format(locale, "%d:%02d:%02d", hours, minutes, seconds)
else String.format(locale, if(leadingZerosForMinutes) "%02d:%02d" else "%d:%02d", minutes, seconds)
else String.format(
locale,
if (leadingZerosForMinutes) "%02d:%02d" else "%d:%02d",
minutes,
seconds
)
return time_string
}
@Composable
fun Thumbnail(
modifier: Modifier = Modifier,
thumbnail: Thumbnail?,
contentDescription: String,
shape: androidx.compose.ui.graphics.Shape? = null
) {
val modifier = if (shape == null) {
modifier
} else {
modifier
.clip(shape)
}
when (thumbnail) {
is OnlineThumbnail -> AsyncImage(
modifier = modifier,
model = thumbnail.url,
contentDescription = contentDescription
)
is BitmapThumbnail -> Image(
modifier = modifier,
bitmap = thumbnail.img,
contentDescription = contentDescription
)
is VectorThumbnail -> Image(
modifier = modifier,
imageVector = thumbnail.vec,
contentDescription = contentDescription
)
null -> Image(
modifier = modifier,
painter = painterResource(R.drawable.tiny_placeholder),
contentDescription = contentDescription
)
}
}