From 4295669a4d5ef964f8ecfa170024b7a035da2115 Mon Sep 17 00:00:00 2001 From: Abdallah <54363735+abdallahmehiz@users.noreply.github.com> Date: Tue, 30 Jan 2024 11:45:20 +0100 Subject: [PATCH] feat(player): Show the current chapter in player controls ui (#1363) Co-authored-by: jmir1 --- .../tachiyomi/ui/player/PlayerActivity.kt | 2 +- .../settings/sheets/VideoChaptersSheet.kt | 15 +- .../ui/player/viewer/PlayerControlsView.kt | 15 +- .../viewer/components/CurrentChapter.kt | 132 ++++++++++++++++++ app/src/main/res/layout/player_controls.xml | 30 ++-- 5 files changed, 163 insertions(+), 31 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/ui/player/viewer/components/CurrentChapter.kt diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt index 3bd4423272..95a8a8fe1e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/player/PlayerActivity.kt @@ -297,6 +297,7 @@ class PlayerActivity : BaseActivity() { field = value runOnUiThread { playerControls.seekbar.updateSeekbar(chapters = value) + playerControls.chapterText.updateCurrentChapterText(chapters = value) } } @@ -1766,7 +1767,6 @@ class PlayerActivity : BaseActivity() { emptyList() } val combinedChapters = (startChapter + playerChapters + filteredAniskipChapters).sortedBy { it.time } - runOnUiThread { binding.playerControls.binding.chaptersBtn.isVisible = combinedChapters.isNotEmpty() } videoChapters = combinedChapters } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/sheets/VideoChaptersSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/sheets/VideoChaptersSheet.kt index 9a07feff39..cb0ef10bee 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/sheets/VideoChaptersSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/player/settings/sheets/VideoChaptersSheet.kt @@ -52,7 +52,9 @@ fun VideoChaptersSheet( fontSize = 20.sp, ) - videoChapters.forEachIndexed { index, videoChapter -> + val currentChapter = videoChapters.last { it.time <= currentTimePosition } + + videoChapters.forEach { videoChapter -> val videoChapterTime = videoChapter.time.roundToInt() val videoChapterName = if (videoChapter.title.isNullOrBlank()) { Utils.prettyTime(videoChapterTime) @@ -60,16 +62,7 @@ fun VideoChaptersSheet( "${videoChapter.title} (${Utils.prettyTime(videoChapterTime)})" } - val nextChapterTime = videoChapters.getOrNull(index + 1)?.time?.toInt() - - val selected = (index == videoChapters.lastIndex && currentTimePosition >= videoChapterTime) || - ( - currentTimePosition >= videoChapterTime && - ( - nextChapterTime == null || - currentTimePosition < nextChapterTime - ) - ) + val selected = videoChapter == currentChapter val onClick = { currentTimePosition = videoChapter.time.roundToInt() diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/player/viewer/PlayerControlsView.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/player/viewer/PlayerControlsView.kt index 7f169ca05e..ca8b527574 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/player/viewer/PlayerControlsView.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/player/viewer/PlayerControlsView.kt @@ -16,6 +16,7 @@ import androidx.core.view.isVisible import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.databinding.PlayerControlsBinding import eu.kanade.tachiyomi.ui.player.PlayerActivity +import eu.kanade.tachiyomi.ui.player.viewer.components.CurrentChapter import eu.kanade.tachiyomi.ui.player.viewer.components.Seekbar import `is`.xyz.mpv.MPVLib import `is`.xyz.mpv.Utils @@ -44,6 +45,11 @@ class PlayerControlsView @JvmOverloads constructor(context: Context, attrs: Attr onValueChangeFinished = ::onValueChangeFinished, ) + val chapterText: CurrentChapter = CurrentChapter( + view = binding.currentChapter, + onClick = { activity.viewModel.showVideoChapters() }, + ) + private fun onValueChange(value: Float, wasSeeking: Boolean) { if (!wasSeeking) { SeekState.mode = SeekState.SEEKBAR @@ -146,8 +152,6 @@ class PlayerControlsView @JvmOverloads constructor(context: Context, attrs: Attr binding.streamsBtn.setOnClickListener { activity.viewModel.showStreamsCatalog() } - binding.chaptersBtn.setOnClickListener { activity.viewModel.showVideoChapters() } - binding.titleMainTxt.setOnClickListener { activity.viewModel.showEpisodeList() } binding.titleSecondaryTxt.setOnClickListener { activity.viewModel.showEpisodeList() } @@ -319,6 +323,7 @@ class PlayerControlsView @JvmOverloads constructor(context: Context, attrs: Attr activity.viewModel.onSecondReached(position, duration) } seekbar.updateSeekbar(value = position.toFloat()) + chapterText.updateCurrentChapterText(value = position.toFloat()) } @SuppressLint("SetTextI18n") @@ -371,6 +376,9 @@ class PlayerControlsView @JvmOverloads constructor(context: Context, attrs: Attr binding.bottomLeftControlsGroup.startAnimation( AnimationUtils.loadAnimation(context, R.anim.player_exit_left), ) + binding.currentChapter.startAnimation( + AnimationUtils.loadAnimation(context, R.anim.player_exit_left), + ) binding.middleControlsGroup.startAnimation( AnimationUtils.loadAnimation(context, R.anim.player_fade_out), ) @@ -399,6 +407,9 @@ class PlayerControlsView @JvmOverloads constructor(context: Context, attrs: Attr binding.bottomLeftControlsGroup.startAnimation( AnimationUtils.loadAnimation(context, R.anim.player_enter_left), ) + binding.currentChapter.startAnimation( + AnimationUtils.loadAnimation(context, R.anim.player_enter_left), + ) binding.middleControlsGroup.startAnimation( AnimationUtils.loadAnimation(context, R.anim.player_fade_in), ) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/player/viewer/components/CurrentChapter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/player/viewer/components/CurrentChapter.kt new file mode 100644 index 0000000000..679131a4e5 --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/player/viewer/components/CurrentChapter.kt @@ -0,0 +1,132 @@ +package eu.kanade.tachiyomi.ui.player.viewer.components + +import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.SizeTransform +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.animation.togetherWith +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.MaterialTheme +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.vector.ImageVector +import androidx.compose.ui.platform.ComposeView +import androidx.compose.ui.res.vectorResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import eu.kanade.tachiyomi.R +import eu.kanade.tachiyomi.util.view.setComposeContent +import `is`.xyz.mpv.MPVView.Chapter +import `is`.xyz.mpv.Utils +import tachiyomi.presentation.core.components.material.padding + +class CurrentChapter( + private val view: ComposeView, + private val onClick: () -> Unit, +) { + private var value: Float = 0F + private var chapters: List = listOf() + + fun updateCurrentChapterText( + value: Float? = null, + chapters: List? = null, + ) { + if (value != null) { + this.value = value + } + if (chapters != null) { + this.chapters = chapters + } + if (this.chapters.isEmpty()) { + return + } + val chapter = this.chapters.last { it.time <= (value ?: 0F) } + view.setComposeContent { + CurrentChapterComposable( + chapter = chapter, + modifier = Modifier + .clickable { onClick() } + .padding(end = MaterialTheme.padding.large) + .wrapContentSize(Alignment.CenterStart), + ) + } + } + + @Composable + private fun CurrentChapterComposable( + chapter: Chapter, + modifier: Modifier = Modifier, + ) { + Box( + modifier = modifier + .clip(RoundedCornerShape(25)) + .background(MaterialTheme.colorScheme.background.copy(alpha = 0.6F)) + .padding(horizontal = MaterialTheme.padding.medium, vertical = MaterialTheme.padding.small), + ) { + AnimatedContent( + targetState = chapter, + transitionSpec = { + if (targetState.time > initialState.time) { + (slideInVertically { height -> height } + fadeIn()) + .togetherWith(slideOutVertically { height -> -height } + fadeOut()) + } else { + (slideInVertically { height -> -height } + fadeIn()) + .togetherWith(slideOutVertically { height -> height } + fadeOut()) + }.using( + SizeTransform(clip = false), + ) + }, + label = "Chapter", + ) { currentChapter -> + Row { + Icon( + imageVector = ImageVector.vectorResource(R.drawable.ic_video_chapter_20dp), + contentDescription = null, + modifier = Modifier + .padding(end = MaterialTheme.padding.small) + .size(16.dp), + ) + Text( + text = Utils.prettyTime(currentChapter.time.toInt()), + textAlign = TextAlign.Center, + fontWeight = FontWeight.ExtraBold, + maxLines = 1, + overflow = TextOverflow.Clip, + color = MaterialTheme.colorScheme.tertiary, + ) + currentChapter.title?.let { + Text( + text = " • ", + textAlign = TextAlign.Center, + maxLines = 1, + overflow = TextOverflow.Clip, + ) + Text( + text = it, + textAlign = TextAlign.Center, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onBackground, + ) + } + } + } + } + } +} diff --git a/app/src/main/res/layout/player_controls.xml b/app/src/main/res/layout/player_controls.xml index 03db78775d..6e9c3a225a 100644 --- a/app/src/main/res/layout/player_controls.xml +++ b/app/src/main/res/layout/player_controls.xml @@ -129,32 +129,19 @@ android:background="?attr/selectableItemBackground" android:contentDescription="Tracks" android:src="@drawable/ic_video_settings_20dp" - app:layout_constraintLeft_toRightOf="@id/chaptersBtn" + app:layout_constraintLeft_toRightOf="@id/toggleAutoplay" app:layout_constraintRight_toLeftOf="@id/settingsBtn" app:layout_constraintTop_toTopOf="@id/settingsBtn" app:tint="?attr/colorOnPrimarySurface" /> - - + app:layout_constraintRight_toLeftOf="@id/streamsBtn" + app:layout_constraintTop_toTopOf="@id/streamsBtn" /> @@ -263,6 +250,16 @@ android:layoutDirection="ltr" android:visibility="visible"> + + -