Merge pull request #1257 from Bnyro/master

bottom sheet for sorting
This commit is contained in:
Bnyro 2022-09-10 16:57:24 +02:00 committed by GitHub
commit 7f13fd1689
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 304 additions and 300 deletions

View File

@ -0,0 +1,42 @@
package com.github.libretube.adapters
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.github.libretube.databinding.BottomSheetItemBinding
import com.github.libretube.obj.BottomSheetItem
class BottomSheetAdapter(
private val items: List<BottomSheetItem>,
private val listener: (index: Int) -> Unit
) : RecyclerView.Adapter<BottomSheetViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BottomSheetViewHolder {
val binding = BottomSheetItemBinding.inflate(
LayoutInflater.from(parent.context),
parent,
false
)
return BottomSheetViewHolder(binding)
}
override fun onBindViewHolder(holder: BottomSheetViewHolder, position: Int) {
val item = items[position]
holder.binding.apply {
title.text = if (item.currentValue != null) "${item.title} (${item.currentValue})" else item.title
if (item.drawable != null) drawable.setImageResource(item.drawable) else drawable.visibility = View.GONE
root.setOnClickListener {
listener.invoke(position)
}
}
}
override fun getItemCount(): Int {
return items.size
}
}
class BottomSheetViewHolder(
val binding: BottomSheetItemBinding
) : RecyclerView.ViewHolder(binding.root)

View File

@ -60,7 +60,7 @@ import com.github.libretube.extensions.await
import com.github.libretube.extensions.formatShort import com.github.libretube.extensions.formatShort
import com.github.libretube.extensions.hideKeyboard import com.github.libretube.extensions.hideKeyboard
import com.github.libretube.extensions.toID import com.github.libretube.extensions.toID
import com.github.libretube.interfaces.OnlinePlayerOptionsInterface import com.github.libretube.interfaces.PlayerOptionsInterface
import com.github.libretube.models.PlayerViewModel import com.github.libretube.models.PlayerViewModel
import com.github.libretube.obj.ChapterSegment import com.github.libretube.obj.ChapterSegment
import com.github.libretube.obj.Segment import com.github.libretube.obj.Segment
@ -399,7 +399,7 @@ class PlayerFragment : BaseFragment() {
} }
} }
private val onlinePlayerOptionsInterface = object : OnlinePlayerOptionsInterface { private val onlinePlayerOptionsInterface = object : PlayerOptionsInterface {
override fun onCaptionClicked() { override fun onCaptionClicked() {
if (!this@PlayerFragment::streams.isInitialized || if (!this@PlayerFragment::streams.isInitialized ||
streams.subtitles == null || streams.subtitles == null ||

View File

@ -16,8 +16,9 @@ import com.github.libretube.constants.PreferenceKeys
import com.github.libretube.databinding.FragmentSubscriptionsBinding import com.github.libretube.databinding.FragmentSubscriptionsBinding
import com.github.libretube.extensions.BaseFragment import com.github.libretube.extensions.BaseFragment
import com.github.libretube.models.SubscriptionsViewModel import com.github.libretube.models.SubscriptionsViewModel
import com.github.libretube.obj.BottomSheetItem
import com.github.libretube.util.PreferenceHelper import com.github.libretube.util.PreferenceHelper
import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.github.libretube.views.BottomSheet
class SubscriptionsFragment : BaseFragment() { class SubscriptionsFragment : BaseFragment() {
private lateinit var binding: FragmentSubscriptionsBinding private lateinit var binding: FragmentSubscriptionsBinding
@ -107,15 +108,20 @@ class SubscriptionsFragment : BaseFragment() {
private fun showSortDialog() { private fun showSortDialog() {
val sortOptions = resources.getStringArray(R.array.sortOptions) val sortOptions = resources.getStringArray(R.array.sortOptions)
val sortOptionValues = resources.getStringArray(R.array.sortOptionsValues) val sortOptionValues = resources.getStringArray(R.array.sortOptionsValues)
MaterialAlertDialogBuilder(requireContext()) val items = mutableListOf<BottomSheetItem>()
.setTitle(R.string.sort) sortOptions.forEach {
.setItems(sortOptions) { _, index -> items += BottomSheetItem(it)
}
val bottomSheet = BottomSheet().apply {
setItems(items) { index ->
binding.sortTV.text = sortOptions[index] binding.sortTV.text = sortOptions[index]
sortOrder = sortOptionValues[index] sortOrder = sortOptionValues[index]
showFeed() showFeed()
} }
.setNegativeButton(R.string.cancel, null) }
.show()
bottomSheet.show(childFragmentManager, null)
} }
private fun showFeed() { private fun showFeed() {

View File

@ -1,7 +0,0 @@
package com.github.libretube.interfaces
interface OnlinePlayerOptionsInterface {
fun onCaptionClicked()
fun onQualityClicked()
}

View File

@ -1,11 +1,7 @@
package com.github.libretube.interfaces package com.github.libretube.interfaces
interface PlayerOptionsInterface { interface PlayerOptionsInterface {
fun onAutoplayClicked() fun onCaptionClicked()
fun onPlaybackSpeedClicked() fun onQualityClicked()
fun onResizeModeClicked()
fun onRepeatModeClicked()
} }

View File

@ -0,0 +1,7 @@
package com.github.libretube.obj
data class BottomSheetItem(
val title: String,
val drawable: Int? = null,
val currentValue: String? = null
)

View File

@ -0,0 +1,51 @@
package com.github.libretube.views
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import com.github.libretube.adapters.BottomSheetAdapter
import com.github.libretube.databinding.BottomSheetBinding
import com.github.libretube.obj.BottomSheetItem
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
class BottomSheet : BottomSheetDialogFragment() {
private lateinit var items: List<BottomSheetItem>
private lateinit var listener: (index: Int) -> Unit
private lateinit var binding: BottomSheetBinding
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
dialog!!.setOnShowListener { dialog ->
val d = dialog as BottomSheetDialog
val bottomSheetInternal =
d.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet)!!
BottomSheetBehavior.from(bottomSheetInternal).state =
BottomSheetBehavior.STATE_EXPANDED
}
binding = BottomSheetBinding.inflate(layoutInflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.optionsRecycler.layoutManager = LinearLayoutManager(requireContext())
binding.optionsRecycler.adapter = BottomSheetAdapter(items, listener)
}
fun setItems(items: List<BottomSheetItem>, listener: (index: Int) -> Unit) {
this.items = items
this.listener = { index ->
listener.invoke(index)
dialog?.dismiss()
}
}
}

View File

@ -18,8 +18,8 @@ import com.github.libretube.databinding.DoubleTapOverlayBinding
import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding
import com.github.libretube.extensions.setSliderRangeAndValue import com.github.libretube.extensions.setSliderRangeAndValue
import com.github.libretube.interfaces.DoubleTapInterface import com.github.libretube.interfaces.DoubleTapInterface
import com.github.libretube.interfaces.OnlinePlayerOptionsInterface
import com.github.libretube.interfaces.PlayerOptionsInterface import com.github.libretube.interfaces.PlayerOptionsInterface
import com.github.libretube.obj.BottomSheetItem
import com.github.libretube.util.DoubleTapListener import com.github.libretube.util.DoubleTapListener
import com.github.libretube.util.PreferenceHelper import com.github.libretube.util.PreferenceHelper
import com.google.android.exoplayer2.trackselection.TrackSelector import com.google.android.exoplayer2.trackselection.TrackSelector
@ -40,7 +40,7 @@ internal class CustomExoPlayerView(
* Objects from the parent fragment * Objects from the parent fragment
*/ */
private var doubleTapListener: DoubleTapInterface? = null private var doubleTapListener: DoubleTapInterface? = null
private var onlinePlayerOptionsInterface: OnlinePlayerOptionsInterface? = null private var playerOptionsInterface: PlayerOptionsInterface? = null
private lateinit var childFragmentManager: FragmentManager private lateinit var childFragmentManager: FragmentManager
private var trackSelector: TrackSelector? = null private var trackSelector: TrackSelector? = null
@ -85,12 +85,12 @@ internal class CustomExoPlayerView(
fun initialize( fun initialize(
childFragmentManager: FragmentManager, childFragmentManager: FragmentManager,
playerViewInterface: OnlinePlayerOptionsInterface?, playerViewInterface: PlayerOptionsInterface?,
doubleTapOverlayBinding: DoubleTapOverlayBinding, doubleTapOverlayBinding: DoubleTapOverlayBinding,
trackSelector: TrackSelector? trackSelector: TrackSelector?
) { ) {
this.childFragmentManager = childFragmentManager this.childFragmentManager = childFragmentManager
this.onlinePlayerOptionsInterface = playerViewInterface this.playerOptionsInterface = playerViewInterface
this.doubleTapOverlayBinding = doubleTapOverlayBinding this.doubleTapOverlayBinding = doubleTapOverlayBinding
this.trackSelector = trackSelector this.trackSelector = trackSelector
@ -99,7 +99,7 @@ internal class CustomExoPlayerView(
enableDoubleTapToSeek() enableDoubleTapToSeek()
initializeAdvancedOptions() initializeAdvancedOptions(context)
// locking the player // locking the player
binding.lockPlayer.setOnClickListener { binding.lockPlayer.setOnClickListener {
@ -139,48 +139,77 @@ internal class CustomExoPlayerView(
return false return false
} }
private fun initializeAdvancedOptions() { private fun initializeAdvancedOptions(context: Context) {
binding.toggleOptions.setOnClickListener { binding.toggleOptions.setOnClickListener {
val bottomSheetFragment = PlayerOptionsBottomSheet().apply { val bottomSheetFragment = BottomSheet().apply {
setOnClickListeners( val items = mutableListOf(
playerOptionsInterface, BottomSheetItem(
onlinePlayerOptionsInterface context.getString(R.string.player_autoplay),
) R.drawable.ic_play,
// set the auto play mode if (autoplayEnabled) {
currentAutoplayMode = if (autoplayEnabled) { context.getString(R.string.enabled)
context?.getString(R.string.enabled)
} else { } else {
context?.getString(R.string.disabled) context.getString(R.string.disabled)
} }
// set the current caption language ),
currentCaptions = BottomSheetItem(
context.getString(R.string.repeat_mode),
R.drawable.ic_repeat,
if (player?.repeatMode == RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE) {
context.getString(R.string.repeat_mode_none)
} else {
context.getString(R.string.repeat_mode_current)
}
),
BottomSheetItem(
context.getString(R.string.player_resize_mode),
R.drawable.ic_aspect_ratio,
when (resizeMode) {
AspectRatioFrameLayout.RESIZE_MODE_FIT -> context.getString(R.string.resize_mode_fit)
AspectRatioFrameLayout.RESIZE_MODE_FILL -> context.getString(R.string.resize_mode_fill)
else -> context.getString(R.string.resize_mode_zoom)
}
),
BottomSheetItem(
context.getString(R.string.playback_speed),
R.drawable.ic_speed,
"${player?.playbackParameters?.speed
.toString()
.replace(".0", "")
}x"
)
)
if (playerOptionsInterface != null) {
items.add(
BottomSheetItem(
context.getString(R.string.quality),
R.drawable.ic_hd,
"${player?.videoSize?.height}p"
)
)
items.add(
BottomSheetItem(
context.getString(R.string.captions),
R.drawable.ic_caption,
if (trackSelector != null && trackSelector!!.parameters.preferredTextLanguages.isNotEmpty()) { if (trackSelector != null && trackSelector!!.parameters.preferredTextLanguages.isNotEmpty()) {
trackSelector!!.parameters.preferredTextLanguages[0] trackSelector!!.parameters.preferredTextLanguages[0]
} else { } else {
context?.getString(R.string.none) context.getString(R.string.none)
} }
// set the playback speed )
currentPlaybackSpeed = "${ )
player?.playbackParameters?.speed.toString()
.replace(".0", "")
}x"
// set the quality text
val quality = player?.videoSize?.height
if (quality != 0) {
currentQuality = "${quality}p"
} }
// set the repeat mode
currentRepeatMode = setItems(items) { index ->
if (player?.repeatMode == RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE) { when (index) {
context?.getString(R.string.repeat_mode_none) 0 -> onAutoplayClicked()
} else { 1 -> onRepeatModeClicked()
context?.getString(R.string.repeat_mode_current) 2 -> onResizeModeClicked()
3 -> onPlaybackSpeedClicked()
4 -> playerOptionsInterface?.onQualityClicked()
5 -> playerOptionsInterface?.onCaptionClicked()
} }
// set the aspect ratio mode
currentResizeMode = when (resizeMode) {
AspectRatioFrameLayout.RESIZE_MODE_FIT -> context?.getString(R.string.resize_mode_fit)
AspectRatioFrameLayout.RESIZE_MODE_FILL -> context?.getString(R.string.resize_mode_fill)
else -> context?.getString(R.string.resize_mode_zoom)
} }
} }
bottomSheetFragment.show(childFragmentManager, null) bottomSheetFragment.show(childFragmentManager, null)
@ -282,8 +311,7 @@ internal class CustomExoPlayerView(
} }
} }
private val playerOptionsInterface = object : PlayerOptionsInterface { fun onAutoplayClicked() {
override fun onAutoplayClicked() {
// autoplay options dialog // autoplay options dialog
MaterialAlertDialogBuilder(context) MaterialAlertDialogBuilder(context)
.setTitle(R.string.player_autoplay) .setTitle(R.string.player_autoplay)
@ -301,7 +329,7 @@ internal class CustomExoPlayerView(
.show() .show()
} }
override fun onPlaybackSpeedClicked() { fun onPlaybackSpeedClicked() {
val playbackSpeedBinding = DialogSliderBinding.inflate( val playbackSpeedBinding = DialogSliderBinding.inflate(
LayoutInflater.from(context) LayoutInflater.from(context)
) )
@ -322,7 +350,7 @@ internal class CustomExoPlayerView(
.show() .show()
} }
override fun onResizeModeClicked() { fun onResizeModeClicked() {
// switching between original aspect ratio (black bars) and zoomed to fill device screen // switching between original aspect ratio (black bars) and zoomed to fill device screen
val aspectRatioModeNames = context.resources?.getStringArray(R.array.resizeMode) val aspectRatioModeNames = context.resources?.getStringArray(R.array.resizeMode)
@ -340,15 +368,16 @@ internal class CustomExoPlayerView(
.show() .show()
} }
override fun onRepeatModeClicked() { fun onRepeatModeClicked() {
val repeatModeNames = arrayOf( val repeatModeNames = arrayOf(
context.getString(R.string.repeat_mode_none), context.getString(R.string.repeat_mode_none),
context.getString(R.string.repeat_mode_current) context.getString(R.string.repeat_mode_current)
) )
val repeatModes = arrayOf( val repeatModes = arrayOf(
RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL, RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE,
RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL
) )
// repeat mode options dialog // repeat mode options dialog
MaterialAlertDialogBuilder(context) MaterialAlertDialogBuilder(context)
@ -359,4 +388,3 @@ internal class CustomExoPlayerView(
.show() .show()
} }
} }
}

View File

@ -1,118 +0,0 @@
package com.github.libretube.views
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import com.github.libretube.databinding.BottomSheetBinding
import com.github.libretube.interfaces.OnlinePlayerOptionsInterface
import com.github.libretube.interfaces.PlayerOptionsInterface
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
/**
* Bottom Sheet including all the player options
*/
class PlayerOptionsBottomSheet : BottomSheetDialogFragment() {
lateinit var binding: BottomSheetBinding
private lateinit var playerOptionsInterface: PlayerOptionsInterface
private var onlinePlayerOptionsInterface: OnlinePlayerOptionsInterface? = null
/**
* current values
*/
var currentPlaybackSpeed: String? = null
var currentAutoplayMode: String? = null
var currentRepeatMode: String? = null
var currentQuality: String? = null
var currentResizeMode: String? = null
var currentCaptions: String? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
// expand the bottom sheet on creation
dialog!!.setOnShowListener { dialog ->
val d = dialog as BottomSheetDialog
val bottomSheetInternal =
d.findViewById<View>(com.google.android.material.R.id.design_bottom_sheet)!!
BottomSheetBehavior.from(bottomSheetInternal).state =
BottomSheetBehavior.STATE_EXPANDED
}
binding = BottomSheetBinding.inflate(layoutInflater, container, false)
return binding.root
}
fun setOnClickListeners(
playerOptionsInterface: PlayerOptionsInterface,
onlinePlayerOptionsInterface: OnlinePlayerOptionsInterface?
) {
this.playerOptionsInterface = playerOptionsInterface
this.onlinePlayerOptionsInterface = onlinePlayerOptionsInterface
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
if (onlinePlayerOptionsInterface == null) {
binding.captions.visibility = View.GONE
binding.quality.visibility = View.GONE
}
/**
* update the text if a value is selected
*/
binding.autoplay.updateText(currentAutoplayMode)
binding.captions.updateText(currentCaptions)
binding.playbackSpeed.updateText(currentPlaybackSpeed)
binding.quality.updateText(currentQuality)
binding.repeatMode.updateText(currentRepeatMode)
binding.resizeMode.updateText(currentResizeMode)
binding.resizeMode.setOnClickListener {
playerOptionsInterface.onResizeModeClicked()
this.dismiss()
}
binding.quality.setOnClickListener {
onlinePlayerOptionsInterface?.onQualityClicked()
this.dismiss()
}
binding.playbackSpeed.setOnClickListener {
playerOptionsInterface.onPlaybackSpeedClicked()
this.dismiss()
}
binding.captions.setOnClickListener {
onlinePlayerOptionsInterface?.onCaptionClicked()
this.dismiss()
}
binding.autoplay.setOnClickListener {
playerOptionsInterface.onAutoplayClicked()
this.dismiss()
}
binding.repeatMode.setOnClickListener {
playerOptionsInterface.onRepeatModeClicked()
this.dismiss()
}
}
private fun TextView.updateText(currentValue: String?) {
if (currentValue == null) return
this.text = "${this.text} ($currentValue)"
}
}

View File

@ -1,46 +1,34 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<FrameLayout
android:id="@+id/standard_bottom_sheet"
style="@style/Widget.Material3.BottomSheet"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
<LinearLayout
android:orientation="vertical" android:orientation="vertical"
android:paddingHorizontal="7dp" android:layout_width="match_parent"
android:paddingVertical="10dp"> android:layout_height="wrap_content" >
<TextView <!-- Drag handle for accessibility -->
android:id="@+id/quality" <com.google.android.material.bottomsheet.BottomSheetDragHandleView
style="@style/BottomSheetItem" android:id="@+id/drag_handle"
android:text="@string/quality" android:layout_width="match_parent"
app:drawableStartCompat="@drawable/ic_hd" /> android:layout_height="wrap_content"/>
<TextView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/playbackSpeed" android:id="@+id/options_recycler"
style="@style/BottomSheetItem" android:layout_width="match_parent"
android:text="@string/playback_speed" android:layout_height="wrap_content" />
app:drawableStartCompat="@drawable/ic_speed" />
<TextView
android:id="@+id/captions"
style="@style/BottomSheetItem"
android:text="@string/captions"
app:drawableStartCompat="@drawable/ic_caption" />
<TextView
android:id="@+id/autoplay"
style="@style/BottomSheetItem"
android:text="@string/player_autoplay"
app:drawableStartCompat="@drawable/ic_play" />
<TextView
android:id="@+id/repeatMode"
style="@style/BottomSheetItem"
android:text="@string/repeat_mode"
app:drawableStartCompat="@drawable/ic_repeat" />
<TextView
android:id="@+id/resizeMode"
style="@style/BottomSheetItem"
android:text="@string/player_resize_mode"
app:drawableStartCompat="@drawable/ic_aspect_ratio" />
</LinearLayout> </LinearLayout>
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/selectableItemBackground"
android:orientation="horizontal"
android:padding="10dp">
<ImageView
android:id="@+id/drawable"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="10dp"
tools:src="@drawable/ic_download" />
<TextView
android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16sp"
tools:text="Option" />
</LinearLayout>

View File

@ -151,19 +151,6 @@
</style> </style>
<style name="BottomSheetItem">
<item name="android:textSize">16sp</item>
<item name="android:drawablePadding">20dp</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:padding">10dp</item>
<item name="android:layout_marginTop">2dp</item>
<item name="android:layout_marginBottom">2dp</item>
<item name="background">?attr/selectableItemBackground</item>
</style>
<style name="ItemRow"> <style name="ItemRow">
<item name="android:layout_width">match_parent</item> <item name="android:layout_width">match_parent</item>