highlight active chapter
This commit is contained in:
parent
48f5e159d9
commit
79f8719ac3
|
@ -21,14 +21,11 @@
|
|||
package net.newpipe.newplayer.model
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.media3.common.Player
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import net.newpipe.newplayer.Chapter
|
||||
import net.newpipe.newplayer.NewPlayer
|
||||
import net.newpipe.newplayer.RepeatMode
|
||||
import net.newpipe.newplayer.ui.ContentScale
|
||||
import net.newpipe.newplayer.utils.Thumbnail
|
||||
|
||||
|
||||
interface VideoPlayerViewModel {
|
||||
|
@ -61,7 +58,7 @@ interface VideoPlayerViewModel {
|
|||
fun closeStreamSelection()
|
||||
fun chapterSelected(chapter: Chapter)
|
||||
fun streamSelected(streamId: Int)
|
||||
fun cycleRepeatmode()
|
||||
fun cycleRepeatMode()
|
||||
fun toggleShuffle()
|
||||
fun onStorePlaylist()
|
||||
fun movePlaylistItem(from: Int, to: Int)
|
||||
|
|
|
@ -419,7 +419,6 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
|||
}
|
||||
|
||||
override fun openStreamSelection(selectChapter: Boolean, embeddedUiConfig: EmbeddedUiConfig) {
|
||||
resetPlaylistProgressUpdaterJob()
|
||||
uiVisibilityJob?.cancel()
|
||||
if (!uiState.value.uiMode.fullscreen) {
|
||||
this.embeddedUiConfig = embeddedUiConfig
|
||||
|
@ -428,10 +427,16 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
|||
if (selectChapter) uiState.value.uiMode.getChapterSelectUiState()
|
||||
else uiState.value.uiMode.getStreamSelectUiState()
|
||||
)
|
||||
if(selectChapter) {
|
||||
resetProgressUpdatePeriodicallyJob()
|
||||
} else {
|
||||
resetPlaylistProgressUpdaterJob()
|
||||
}
|
||||
}
|
||||
|
||||
override fun closeStreamSelection() {
|
||||
playlistProgressUpdatrJob?.cancel()
|
||||
progressUpdaterJob?.cancel()
|
||||
updateUiMode(uiState.value.uiMode.getUiHiddenState())
|
||||
}
|
||||
|
||||
|
@ -466,7 +471,7 @@ class VideoPlayerViewModelImpl @Inject constructor(
|
|||
println("stream selected: $streamId")
|
||||
}
|
||||
|
||||
override fun cycleRepeatmode() {
|
||||
override fun cycleRepeatMode() {
|
||||
newPlayer?.let {
|
||||
it.repeatMode = when (it.repeatMode) {
|
||||
RepeatMode.DONT_REPEAT -> RepeatMode.REPEAT_ALL
|
||||
|
|
|
@ -92,7 +92,7 @@ open class VideoPlayerViewModelDummy : VideoPlayerViewModel {
|
|||
println("dummy impl stream selected: $streamId")
|
||||
}
|
||||
|
||||
override fun cycleRepeatmode() {
|
||||
override fun cycleRepeatMode() {
|
||||
println("dummy impl")
|
||||
}
|
||||
|
||||
|
|
|
@ -48,6 +48,7 @@ import net.newpipe.newplayer.ui.videoplayer.streamselect.ChapterItem
|
|||
import net.newpipe.newplayer.ui.videoplayer.streamselect.ChapterSelectTopBar
|
||||
import net.newpipe.newplayer.ui.videoplayer.streamselect.StreamItem
|
||||
import net.newpipe.newplayer.ui.videoplayer.streamselect.StreamSelectTopBar
|
||||
import net.newpipe.newplayer.ui.videoplayer.streamselect.isActiveChapter
|
||||
import net.newpipe.newplayer.utils.ReorderHapticFeedbackType
|
||||
import net.newpipe.newplayer.utils.getInsets
|
||||
import net.newpipe.newplayer.utils.rememberReorderHapticFeedback
|
||||
|
@ -100,7 +101,12 @@ fun StreamSelectUI(
|
|||
thumbnail = chapter.thumbnail,
|
||||
onClicked = {
|
||||
viewModel.chapterSelected(chapter)
|
||||
}
|
||||
},
|
||||
isCurrentChapter = isActiveChapter(
|
||||
chapterIndex,
|
||||
uiState.chapters,
|
||||
uiState.playbackPositionInMs
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -139,7 +145,7 @@ fun ReorderableStreamItemsList(
|
|||
verticalArrangement = Arrangement.spacedBy(5.dp),
|
||||
state = lazyListState
|
||||
) {
|
||||
itemsIndexed(uiState.playList, key = {_, item -> item.uniqueId}) { index, playlistItem ->
|
||||
itemsIndexed(uiState.playList, key = { _, item -> item.uniqueId }) { index, playlistItem ->
|
||||
ReorderableItem(
|
||||
state = reorderableLazyListState,
|
||||
key = playlistItem.uniqueId
|
||||
|
|
|
@ -21,14 +21,20 @@
|
|||
|
||||
package net.newpipe.newplayer.ui.videoplayer.streamselect
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.Image
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Surface
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
|
@ -44,6 +50,8 @@ import androidx.compose.ui.tooling.preview.Preview
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import coil.compose.AsyncImage
|
||||
import net.newpipe.newplayer.Chapter
|
||||
import net.newpipe.newplayer.NewPlayerException
|
||||
import net.newpipe.newplayer.R
|
||||
import net.newpipe.newplayer.ui.theme.VideoPlayerTheme
|
||||
import net.newpipe.newplayer.ui.videoplayer.ITEM_CORNER_SHAPE
|
||||
|
@ -54,6 +62,16 @@ import net.newpipe.newplayer.utils.VectorThumbnail
|
|||
import net.newpipe.newplayer.utils.getLocale
|
||||
import net.newpipe.newplayer.utils.getTimeStringFromMs
|
||||
|
||||
fun isActiveChapter(chapterId: Int, chapters: List<Chapter>, playbackPosition: Long) : Boolean {
|
||||
assert(0 <= chapterId && chapterId < chapters.size) {
|
||||
throw NewPlayerException("Chapter Id out of bounds: id: $chapterId, chapters.size: ${chapters.size}")
|
||||
}
|
||||
val chapterStart = chapters[chapterId].chapterStartInMs
|
||||
val chapterEnd =
|
||||
if (chapterId + 1 < chapters.size) chapters[chapterId + 1].chapterStartInMs
|
||||
else Long.MAX_VALUE
|
||||
return playbackPosition in chapterStart..<chapterEnd
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ChapterItem(
|
||||
|
@ -62,41 +80,56 @@ fun ChapterItem(
|
|||
thumbnail: Thumbnail?,
|
||||
chapterTitle: String,
|
||||
chapterStartInMs: Long,
|
||||
onClicked: (Int) -> Unit
|
||||
onClicked: (Int) -> Unit,
|
||||
isCurrentChapter: Boolean
|
||||
) {
|
||||
val locale = getLocale()!!
|
||||
Row(
|
||||
Box(
|
||||
modifier = modifier
|
||||
.height(80.dp)
|
||||
.clip(ITEM_CORNER_SHAPE)
|
||||
.clickable { onClicked(id) }
|
||||
) {
|
||||
val contentDescription = stringResource(R.string.chapter_thumbnail)
|
||||
Thumbnail(
|
||||
thumbnail = thumbnail,
|
||||
contentDescription = contentDescription,
|
||||
shape = ITEM_CORNER_SHAPE
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp, top = 5.dp, bottom = 5.dp)
|
||||
.weight(1f),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
AnimatedVisibility(
|
||||
isCurrentChapter,
|
||||
enter = fadeIn(animationSpec = tween(200)),
|
||||
exit = fadeOut(animationSpec = tween(400))
|
||||
) {
|
||||
Text(
|
||||
text = chapterTitle,
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
getTimeStringFromMs(chapterStartInMs, locale),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Surface(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
color = Color.White.copy(alpha = 0.2f),
|
||||
) {}
|
||||
}
|
||||
|
||||
|
||||
|
||||
Row {
|
||||
val contentDescription = stringResource(R.string.chapter_thumbnail)
|
||||
Thumbnail(
|
||||
thumbnail = thumbnail,
|
||||
contentDescription = contentDescription,
|
||||
shape = ITEM_CORNER_SHAPE
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier
|
||||
.padding(start = 8.dp, top = 5.dp, bottom = 5.dp)
|
||||
.weight(1f),
|
||||
horizontalAlignment = Alignment.Start,
|
||||
) {
|
||||
Text(
|
||||
text = chapterTitle,
|
||||
fontSize = 18.sp,
|
||||
fontWeight = FontWeight.Bold,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
Text(
|
||||
getTimeStringFromMs(chapterStartInMs, locale),
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -111,7 +144,8 @@ fun ChapterItemPreview() {
|
|||
modifier = Modifier.fillMaxSize(),
|
||||
chapterTitle = "Chapter Title",
|
||||
chapterStartInMs = (4 * 60 + 32) * 1000,
|
||||
onClicked = {}
|
||||
onClicked = {},
|
||||
isCurrentChapter = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,7 +24,6 @@ import androidx.compose.foundation.layout.fillMaxSize
|
|||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.PlaylistAdd
|
||||
import androidx.compose.material.icons.filled.Close
|
||||
import androidx.compose.material.icons.filled.PlaylistAdd
|
||||
import androidx.compose.material.icons.filled.Repeat
|
||||
import androidx.compose.material.icons.filled.RepeatOn
|
||||
import androidx.compose.material.icons.filled.RepeatOneOn
|
||||
|
@ -43,8 +42,6 @@ import androidx.compose.ui.graphics.Color
|
|||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.media3.common.Player
|
||||
import net.newpipe.newplayer.NewPlayerException
|
||||
import net.newpipe.newplayer.R
|
||||
import net.newpipe.newplayer.RepeatMode
|
||||
import net.newpipe.newplayer.model.VideoPlayerUIState
|
||||
|
@ -79,7 +76,7 @@ fun StreamSelectTopBar(
|
|||
)
|
||||
}, actions = {
|
||||
IconButton(
|
||||
onClick = viewModel::cycleRepeatmode
|
||||
onClick = viewModel::cycleRepeatMode
|
||||
) {
|
||||
when (uiState.repeatMode) {
|
||||
RepeatMode.DONT_REPEAT -> Icon(
|
||||
|
|
Loading…
Reference in New Issue