mirror of
https://github.com/libre-tube/LibreTube.git
synced 2025-04-29 16:30:31 +05:30
feat: Video chapters redesign
This commit is contained in:
parent
05538f9155
commit
818b9c72fe
@ -14,6 +14,7 @@ import android.util.Base64
|
||||
import android.view.accessibility.CaptioningManager
|
||||
import android.widget.Toast
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.PendingIntentCompat
|
||||
import androidx.core.app.RemoteActionCompat
|
||||
import androidx.core.content.getSystemService
|
||||
@ -38,6 +39,9 @@ import com.github.libretube.db.DatabaseHolder
|
||||
import com.github.libretube.enums.PlayerEvent
|
||||
import com.github.libretube.enums.SbSkipOptions
|
||||
import com.github.libretube.extensions.updateParameters
|
||||
import com.github.libretube.ui.sheets.BaseBottomSheet
|
||||
import com.github.libretube.ui.sheets.ChaptersBottomSheet
|
||||
import com.github.libretube.ui.sheets.ExpandedBottomSheet
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import java.util.Locale
|
||||
import kotlin.math.absoluteValue
|
||||
@ -530,46 +534,6 @@ object PlayerHelper {
|
||||
return chapters.indexOfLast { currentPosition >= it.start }.takeIf { it >= 0 }
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a dialog with the chapters provided, even if the list is empty
|
||||
*/
|
||||
fun showChaptersDialog(context: Context, chapters: List<ChapterSegment>, player: ExoPlayer) {
|
||||
val titles = chapters.map { chapter ->
|
||||
"(${DateUtils.formatElapsedTime(chapter.start)}) ${chapter.title}"
|
||||
}
|
||||
val dialog = MaterialAlertDialogBuilder(context)
|
||||
.setTitle(R.string.chapters)
|
||||
.setItems(titles.toTypedArray()) { _, index ->
|
||||
val chapter = chapters.getOrNull(index) ?: return@setItems
|
||||
player.seekTo(chapter.start * 1000)
|
||||
}
|
||||
.create()
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
val highlightColor =
|
||||
ThemeHelper.getThemeColor(context, android.R.attr.colorControlHighlight)
|
||||
|
||||
val updatePosition = Runnable {
|
||||
// scroll to the current playing index in the chapter
|
||||
val currentPosition =
|
||||
getCurrentChapterIndex(player, chapters) ?: return@Runnable
|
||||
dialog.listView.smoothScrollToPosition(currentPosition)
|
||||
|
||||
val children = dialog.listView.children.toList()
|
||||
// reset the background colors of all chapters
|
||||
children.forEach { it.setBackgroundColor(Color.TRANSPARENT) }
|
||||
// highlight the current chapter
|
||||
children.getOrNull(currentPosition)?.setBackgroundColor(highlightColor)
|
||||
}
|
||||
|
||||
dialog.setOnShowListener {
|
||||
updatePosition.run()
|
||||
// update the position after a short delay
|
||||
if (dialog.isShowing) handler.postDelayed(updatePosition, 200)
|
||||
}
|
||||
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
fun getPosition(videoId: String, duration: Long?): Long? {
|
||||
if (duration == null) return null
|
||||
|
||||
|
@ -7,8 +7,9 @@ import android.view.ViewGroup
|
||||
import androidx.core.graphics.ColorUtils
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.github.libretube.R
|
||||
import com.github.libretube.api.obj.ChapterSegment
|
||||
import com.github.libretube.databinding.ChapterColumnBinding
|
||||
import com.github.libretube.databinding.ChaptersRowBinding
|
||||
import com.github.libretube.helpers.ImageHelper
|
||||
import com.github.libretube.helpers.ThemeHelper
|
||||
import com.github.libretube.ui.viewholders.ChaptersViewHolder
|
||||
@ -21,7 +22,7 @@ class ChaptersAdapter(
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ChaptersViewHolder {
|
||||
val layoutInflater = LayoutInflater.from(parent.context)
|
||||
val binding = ChapterColumnBinding.inflate(layoutInflater, parent, false)
|
||||
val binding = ChaptersRowBinding.inflate(layoutInflater, parent, false)
|
||||
return ChaptersViewHolder(binding)
|
||||
}
|
||||
|
||||
@ -36,6 +37,13 @@ class ChaptersAdapter(
|
||||
chapterTitle.text = chapter.title
|
||||
timeStamp.text = DateUtils.formatElapsedTime(chapter.start)
|
||||
|
||||
val chapterEnd = chapters.getOrNull(position + 1)?.start ?: (exoPlayer.duration / 1000)
|
||||
val durationSpan = chapterEnd - chapter.start
|
||||
duration.text = root.context.getString(
|
||||
R.string.duration_span,
|
||||
DateUtils.formatElapsedTime(durationSpan)
|
||||
)
|
||||
|
||||
val color = when {
|
||||
selectedPosition == position -> {
|
||||
ThemeHelper.getThemeColor(root.context, android.R.attr.colorControlHighlight)
|
||||
@ -51,7 +59,7 @@ class ChaptersAdapter(
|
||||
|
||||
else -> Color.TRANSPARENT
|
||||
}
|
||||
chapterLL.setBackgroundColor(color)
|
||||
root.setBackgroundColor(color)
|
||||
|
||||
root.setOnClickListener {
|
||||
updateSelectedPosition(position)
|
||||
|
@ -7,14 +7,14 @@ import androidx.recyclerview.widget.ItemTouchHelper
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.github.libretube.R
|
||||
import com.github.libretube.databinding.DialogNavbarOptionsBinding
|
||||
import com.github.libretube.databinding.SimpleOptionsRecyclerBinding
|
||||
import com.github.libretube.helpers.NavBarHelper
|
||||
import com.github.libretube.ui.adapters.NavBarOptionsAdapter
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
||||
class NavBarOptionsDialog : DialogFragment() {
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val binding = DialogNavbarOptionsBinding.inflate(layoutInflater)
|
||||
val binding = SimpleOptionsRecyclerBinding.inflate(layoutInflater)
|
||||
val options = NavBarHelper.getNavBarItems(requireContext())
|
||||
val adapter = NavBarOptionsAdapter(
|
||||
options.toMutableList(),
|
||||
|
@ -13,6 +13,7 @@ import android.text.format.DateUtils
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.constraintlayout.motion.widget.MotionLayout
|
||||
import androidx.constraintlayout.motion.widget.TransitionAdapter
|
||||
import androidx.core.view.isGone
|
||||
@ -40,6 +41,7 @@ import com.github.libretube.ui.dialogs.ShareDialog
|
||||
import com.github.libretube.ui.interfaces.AudioPlayerOptions
|
||||
import com.github.libretube.ui.listeners.AudioPlayerThumbnailListener
|
||||
import com.github.libretube.ui.models.PlayerViewModel
|
||||
import com.github.libretube.ui.sheets.ChaptersBottomSheet
|
||||
import com.github.libretube.ui.sheets.PlaybackOptionsSheet
|
||||
import com.github.libretube.ui.sheets.PlayingQueueSheet
|
||||
import com.github.libretube.ui.sheets.VideoOptionsBottomSheet
|
||||
@ -177,7 +179,8 @@ class AudioPlayerFragment : Fragment(), AudioPlayerOptions {
|
||||
val streams = playerService.streams ?: return@setOnClickListener
|
||||
val player = playerService.player ?: return@setOnClickListener
|
||||
|
||||
PlayerHelper.showChaptersDialog(requireContext(), streams.chapters, player)
|
||||
ChaptersBottomSheet(streams.chapters, player)
|
||||
.show(requireActivity().supportFragmentManager)
|
||||
}
|
||||
|
||||
binding.miniPlayerClose.setOnClickListener {
|
||||
|
@ -95,7 +95,6 @@ import com.github.libretube.parcelable.PlayerData
|
||||
import com.github.libretube.services.DownloadService
|
||||
import com.github.libretube.ui.activities.MainActivity
|
||||
import com.github.libretube.ui.activities.VideoTagsAdapter
|
||||
import com.github.libretube.ui.adapters.ChaptersAdapter
|
||||
import com.github.libretube.ui.adapters.VideosAdapter
|
||||
import com.github.libretube.ui.dialogs.AddToPlaylistDialog
|
||||
import com.github.libretube.ui.dialogs.DownloadDialog
|
||||
@ -107,6 +106,7 @@ import com.github.libretube.ui.listeners.SeekbarPreviewListener
|
||||
import com.github.libretube.ui.models.CommentsViewModel
|
||||
import com.github.libretube.ui.models.PlayerViewModel
|
||||
import com.github.libretube.ui.sheets.BaseBottomSheet
|
||||
import com.github.libretube.ui.sheets.ChaptersBottomSheet
|
||||
import com.github.libretube.ui.sheets.CommentsSheet
|
||||
import com.github.libretube.ui.sheets.PlayingQueueSheet
|
||||
import com.github.libretube.util.HtmlParser
|
||||
@ -774,7 +774,13 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
||||
// first
|
||||
fetchSponsorBlockSegments()
|
||||
|
||||
initializeChapters()
|
||||
// enable the chapters dialog in the player
|
||||
playerBinding.chapterLL.setOnClickListener {
|
||||
ChaptersBottomSheet(chapters, exoPlayer)
|
||||
.show(requireActivity().supportFragmentManager)
|
||||
}
|
||||
|
||||
setCurrentChapterName()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1155,34 +1161,6 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
||||
}
|
||||
}
|
||||
|
||||
private fun initializeChapters() {
|
||||
if (chapters.isEmpty()) {
|
||||
binding.chaptersRecView.isGone = true
|
||||
playerBinding.chapterLL.isInvisible = true
|
||||
return
|
||||
}
|
||||
// show the chapter layouts
|
||||
binding.chaptersRecView.isVisible = true
|
||||
playerBinding.chapterLL.isVisible = true
|
||||
|
||||
// enable chapters in the video description
|
||||
binding.chaptersRecView.layoutManager =
|
||||
LinearLayoutManager(
|
||||
context,
|
||||
LinearLayoutManager.HORIZONTAL,
|
||||
false
|
||||
)
|
||||
|
||||
binding.chaptersRecView.adapter = ChaptersAdapter(chapters, exoPlayer)
|
||||
|
||||
// enable the chapters dialog in the player
|
||||
playerBinding.chapterLL.setOnClickListener {
|
||||
PlayerHelper.showChaptersDialog(requireContext(), chapters, exoPlayer)
|
||||
}
|
||||
|
||||
setCurrentChapterName()
|
||||
}
|
||||
|
||||
private suspend fun initializeHighlight(highlight: Segment) {
|
||||
val frameReceiver = OnlineTimeFrameReceiver(requireContext(), streams.previewFrames)
|
||||
val frame = withContext(Dispatchers.IO) {
|
||||
@ -1197,12 +1175,14 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
||||
chapters.sortBy { it.start }
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
initializeChapters()
|
||||
setCurrentChapterName()
|
||||
}
|
||||
}
|
||||
|
||||
// set the name of the video chapter in the exoPlayerView
|
||||
private fun setCurrentChapterName(forceUpdate: Boolean = false, enqueueNew: Boolean = true) {
|
||||
playerBinding.chapterLL.isVisible = chapters.isNotEmpty()
|
||||
|
||||
// return if chapters are empty to avoid crashes
|
||||
if (chapters.isEmpty() || _binding == null) return
|
||||
|
||||
@ -1218,9 +1198,6 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
|
||||
// change the chapter name textView text to the chapterName
|
||||
if (chapterName != playerBinding.chapterName.text) {
|
||||
playerBinding.chapterName.text = chapterName
|
||||
// update the selected item
|
||||
val chaptersAdapter = binding.chaptersRecView.adapter as ChaptersAdapter
|
||||
chaptersAdapter.updateSelectedPosition(chapterIndex)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,63 @@
|
||||
package com.github.libretube.ui.sheets
|
||||
|
||||
import android.graphics.Color
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.children
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import com.github.libretube.R
|
||||
import com.github.libretube.api.obj.ChapterSegment
|
||||
import com.github.libretube.databinding.BottomSheetBinding
|
||||
import com.github.libretube.helpers.PlayerHelper
|
||||
import com.github.libretube.helpers.ThemeHelper
|
||||
import com.github.libretube.ui.adapters.ChaptersAdapter
|
||||
|
||||
class ChaptersBottomSheet(
|
||||
private val chapters: List<ChapterSegment>,
|
||||
private val exoPlayer: ExoPlayer
|
||||
): ExpandedBottomSheet() {
|
||||
private lateinit var binding: BottomSheetBinding
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
binding = BottomSheetBinding.inflate(layoutInflater)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
binding.optionsRecycler.layoutManager = LinearLayoutManager(context)
|
||||
binding.optionsRecycler.adapter = ChaptersAdapter(chapters, exoPlayer)
|
||||
|
||||
binding.bottomSheetTitle.text = context?.getString(R.string.chapters)
|
||||
binding.bottomSheetTitleLayout.isVisible = true
|
||||
|
||||
val handler = Handler(Looper.getMainLooper())
|
||||
val highlightColor =
|
||||
ThemeHelper.getThemeColor(requireContext(), android.R.attr.colorControlHighlight)
|
||||
|
||||
val updatePosition = Runnable {
|
||||
// scroll to the current playing index in the chapter
|
||||
val currentPosition =
|
||||
PlayerHelper.getCurrentChapterIndex(exoPlayer, chapters) ?: return@Runnable
|
||||
binding.optionsRecycler.smoothScrollToPosition(currentPosition)
|
||||
|
||||
val children = binding.optionsRecycler.children.toList()
|
||||
// reset the background colors of all chapters
|
||||
children.forEach { it.setBackgroundColor(Color.TRANSPARENT) }
|
||||
// highlight the current chapter
|
||||
children.getOrNull(currentPosition)?.setBackgroundColor(highlightColor)
|
||||
}
|
||||
|
||||
updatePosition.run()
|
||||
handler.postDelayed(updatePosition, 200)
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ import com.github.libretube.databinding.CommentsSheetBinding
|
||||
import com.github.libretube.ui.fragments.CommentsMainFragment
|
||||
import com.github.libretube.ui.models.CommentsViewModel
|
||||
|
||||
class CommentsSheet : ExpandedBottomSheet() {
|
||||
class CommentsSheet : UndimmedBottomSheet() {
|
||||
lateinit var binding: CommentsSheetBinding
|
||||
private val commentsViewModel: CommentsViewModel by activityViewModels()
|
||||
|
||||
@ -82,41 +82,4 @@ class CommentsSheet : ExpandedBottomSheet() {
|
||||
super.onDismiss(dialog)
|
||||
commentsViewModel.commentsSheetDismiss = null
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val dialog = super.onCreateDialog(savedInstanceState)
|
||||
|
||||
// BottomSheetDialogFragment passthrough user outside touch event
|
||||
dialog.setOnShowListener {
|
||||
dialog.findViewById<View>(com.google.android.material.R.id.touch_outside)?.apply {
|
||||
setOnTouchListener { v, event ->
|
||||
event.setLocation(event.rawX - v.x, event.rawY - v.y)
|
||||
activity?.dispatchTouchEvent(event)
|
||||
v.performClick()
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dialog.apply {
|
||||
setOnKeyListener { _, keyCode, _ ->
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
if (childFragmentManager.backStackEntryCount > 0) {
|
||||
childFragmentManager.popBackStack()
|
||||
return@setOnKeyListener true
|
||||
}
|
||||
}
|
||||
return@setOnKeyListener false
|
||||
}
|
||||
|
||||
window?.let {
|
||||
it.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
|
||||
it.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||
}
|
||||
|
||||
setCanceledOnTouchOutside(false)
|
||||
}
|
||||
|
||||
return dialog
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,49 @@
|
||||
package com.github.libretube.ui.sheets
|
||||
|
||||
import android.app.Dialog
|
||||
import android.os.Bundle
|
||||
import android.view.KeyEvent
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
|
||||
/**
|
||||
* A bottom sheet that allows touches on its top/background
|
||||
*/
|
||||
open class UndimmedBottomSheet: ExpandedBottomSheet() {
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
val dialog = super.onCreateDialog(savedInstanceState)
|
||||
|
||||
// BottomSheetDialogFragment passthrough user outside touch event
|
||||
dialog.setOnShowListener {
|
||||
dialog.findViewById<View>(com.google.android.material.R.id.touch_outside)?.apply {
|
||||
setOnTouchListener { v, event ->
|
||||
event.setLocation(event.rawX - v.x, event.rawY - v.y)
|
||||
activity?.dispatchTouchEvent(event)
|
||||
v.performClick()
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dialog.apply {
|
||||
setOnKeyListener { _, keyCode, _ ->
|
||||
if (keyCode == KeyEvent.KEYCODE_BACK) {
|
||||
if (childFragmentManager.backStackEntryCount > 0) {
|
||||
childFragmentManager.popBackStack()
|
||||
return@setOnKeyListener true
|
||||
}
|
||||
}
|
||||
return@setOnKeyListener false
|
||||
}
|
||||
|
||||
window?.let {
|
||||
it.addFlags(WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL)
|
||||
it.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
|
||||
}
|
||||
|
||||
setCanceledOnTouchOutside(false)
|
||||
}
|
||||
|
||||
return dialog
|
||||
}
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
package com.github.libretube.ui.viewholders
|
||||
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.github.libretube.databinding.ChapterColumnBinding
|
||||
import com.github.libretube.databinding.ChaptersRowBinding
|
||||
|
||||
class ChaptersViewHolder(val binding: ChapterColumnBinding) : RecyclerView.ViewHolder(binding.root)
|
||||
class ChaptersViewHolder(val binding: ChaptersRowBinding) : RecyclerView.ViewHolder(binding.root)
|
||||
|
@ -23,6 +23,27 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/bottom_sheet_title_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="16dp"
|
||||
android:visibility="gone">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/bottom_sheet_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="10dp"
|
||||
android:textSize="27sp" />
|
||||
|
||||
<com.google.android.material.divider.MaterialDivider
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/options_recycler"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -1,67 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginHorizontal="1dp"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:backgroundTint="@android:color/transparent"
|
||||
app:strokeWidth="0dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/chapterLL"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="5dp">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="55dp">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/chapter_image"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:shapeAppearanceOverlay="@style/RoundedImageView"
|
||||
tools:src="@tools:sample/backgrounds/scenic" />
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
app:cardBackgroundColor="?colorPrimary"
|
||||
app:cardCornerRadius="4dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/timeStamp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="3dp"
|
||||
android:paddingVertical="1dp"
|
||||
android:textColor="?colorOnPrimary"
|
||||
android:textSize="10sp"
|
||||
tools:ignore="SmallSp"
|
||||
tools:text="05:36" />
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/chapter_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="3dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="3"
|
||||
android:textSize="13sp"
|
||||
tools:text="Title" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.google.android.material.card.MaterialCardView>
|
78
app/src/main/res/layout/chapters_row.xml
Normal file
78
app/src/main/res/layout/chapters_row.xml
Normal file
@ -0,0 +1,78 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:backgroundTint="@android:color/transparent"
|
||||
android:orientation="horizontal"
|
||||
android:paddingHorizontal="10dp"
|
||||
android:paddingVertical="5dp">
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="55dp"
|
||||
android:layout_gravity="center">
|
||||
|
||||
<com.google.android.material.imageview.ShapeableImageView
|
||||
android:id="@+id/chapter_image"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
app:shapeAppearanceOverlay="@style/RoundedImageView"
|
||||
tools:src="@tools:sample/backgrounds/scenic" />
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom|end"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
app:cardBackgroundColor="?colorPrimary"
|
||||
app:cardCornerRadius="4dp"
|
||||
app:cardElevation="0dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/timeStamp"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingHorizontal="3dp"
|
||||
android:paddingVertical="1dp"
|
||||
android:textColor="?colorOnPrimary"
|
||||
android:textSize="10sp"
|
||||
tools:ignore="SmallSp"
|
||||
tools:text="05:36" />
|
||||
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginStart="10dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/chapter_title"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="3dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="3"
|
||||
tools:text="Chapter title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/duration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="3dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="3"
|
||||
android:textSize="12sp"
|
||||
tools:text="Duration: 5s" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
@ -107,15 +107,6 @@
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/chapters_recView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="3dp"
|
||||
android:layout_marginTop="20dp"
|
||||
android:nestedScrollingEnabled="false"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/player_description"
|
||||
android:layout_width="match_parent"
|
||||
|
@ -438,6 +438,7 @@
|
||||
<string name="visibility_unlisted">Unlisted</string>
|
||||
<string name="sort_by">Sort by</string>
|
||||
<string name="uploader_name">Uploader name</string>
|
||||
<string name="duration_span">Duration: %1$s</string>
|
||||
|
||||
<!-- Backup & Restore Settings -->
|
||||
<string name="import_subscriptions_from">Import subscriptions from</string>
|
||||
|
Loading…
x
Reference in New Issue
Block a user