diff --git a/app/src/main/java/com/github/libretube/adapters/BottomSheetAdapter.kt b/app/src/main/java/com/github/libretube/adapters/BottomSheetAdapter.kt new file mode 100644 index 000000000..fad261583 --- /dev/null +++ b/app/src/main/java/com/github/libretube/adapters/BottomSheetAdapter.kt @@ -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, + private val listener: (index: Int) -> Unit +) : RecyclerView.Adapter() { + 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) diff --git a/app/src/main/java/com/github/libretube/fragments/PlayerFragment.kt b/app/src/main/java/com/github/libretube/fragments/PlayerFragment.kt index 3259874d5..670c4bba4 100644 --- a/app/src/main/java/com/github/libretube/fragments/PlayerFragment.kt +++ b/app/src/main/java/com/github/libretube/fragments/PlayerFragment.kt @@ -60,7 +60,7 @@ import com.github.libretube.extensions.await import com.github.libretube.extensions.formatShort import com.github.libretube.extensions.hideKeyboard 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.obj.ChapterSegment 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() { if (!this@PlayerFragment::streams.isInitialized || streams.subtitles == null || diff --git a/app/src/main/java/com/github/libretube/fragments/SubscriptionsFragment.kt b/app/src/main/java/com/github/libretube/fragments/SubscriptionsFragment.kt index 2d47e8ad1..44d2da11a 100644 --- a/app/src/main/java/com/github/libretube/fragments/SubscriptionsFragment.kt +++ b/app/src/main/java/com/github/libretube/fragments/SubscriptionsFragment.kt @@ -16,8 +16,9 @@ import com.github.libretube.constants.PreferenceKeys import com.github.libretube.databinding.FragmentSubscriptionsBinding import com.github.libretube.extensions.BaseFragment import com.github.libretube.models.SubscriptionsViewModel +import com.github.libretube.obj.BottomSheetItem import com.github.libretube.util.PreferenceHelper -import com.google.android.material.dialog.MaterialAlertDialogBuilder +import com.github.libretube.views.BottomSheet class SubscriptionsFragment : BaseFragment() { private lateinit var binding: FragmentSubscriptionsBinding @@ -107,15 +108,20 @@ class SubscriptionsFragment : BaseFragment() { private fun showSortDialog() { val sortOptions = resources.getStringArray(R.array.sortOptions) val sortOptionValues = resources.getStringArray(R.array.sortOptionsValues) - MaterialAlertDialogBuilder(requireContext()) - .setTitle(R.string.sort) - .setItems(sortOptions) { _, index -> + val items = mutableListOf() + sortOptions.forEach { + items += BottomSheetItem(it) + } + + val bottomSheet = BottomSheet().apply { + setItems(items) { index -> binding.sortTV.text = sortOptions[index] sortOrder = sortOptionValues[index] showFeed() } - .setNegativeButton(R.string.cancel, null) - .show() + } + + bottomSheet.show(childFragmentManager, null) } private fun showFeed() { diff --git a/app/src/main/java/com/github/libretube/interfaces/OnlinePlayerOptionsInterface.kt b/app/src/main/java/com/github/libretube/interfaces/OnlinePlayerOptionsInterface.kt deleted file mode 100644 index 17c7e65f4..000000000 --- a/app/src/main/java/com/github/libretube/interfaces/OnlinePlayerOptionsInterface.kt +++ /dev/null @@ -1,7 +0,0 @@ -package com.github.libretube.interfaces - -interface OnlinePlayerOptionsInterface { - fun onCaptionClicked() - - fun onQualityClicked() -} diff --git a/app/src/main/java/com/github/libretube/interfaces/PlayerOptionsInterface.kt b/app/src/main/java/com/github/libretube/interfaces/PlayerOptionsInterface.kt index 4553aadc4..9cd6373a3 100644 --- a/app/src/main/java/com/github/libretube/interfaces/PlayerOptionsInterface.kt +++ b/app/src/main/java/com/github/libretube/interfaces/PlayerOptionsInterface.kt @@ -1,11 +1,7 @@ package com.github.libretube.interfaces interface PlayerOptionsInterface { - fun onAutoplayClicked() + fun onCaptionClicked() - fun onPlaybackSpeedClicked() - - fun onResizeModeClicked() - - fun onRepeatModeClicked() + fun onQualityClicked() } diff --git a/app/src/main/java/com/github/libretube/obj/BottomSheetItem.kt b/app/src/main/java/com/github/libretube/obj/BottomSheetItem.kt new file mode 100644 index 000000000..d9614e5f8 --- /dev/null +++ b/app/src/main/java/com/github/libretube/obj/BottomSheetItem.kt @@ -0,0 +1,7 @@ +package com.github.libretube.obj + +data class BottomSheetItem( + val title: String, + val drawable: Int? = null, + val currentValue: String? = null +) diff --git a/app/src/main/java/com/github/libretube/views/BottomSheet.kt b/app/src/main/java/com/github/libretube/views/BottomSheet.kt new file mode 100644 index 000000000..3a5cac4dd --- /dev/null +++ b/app/src/main/java/com/github/libretube/views/BottomSheet.kt @@ -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 + 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(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, listener: (index: Int) -> Unit) { + this.items = items + this.listener = { index -> + listener.invoke(index) + dialog?.dismiss() + } + } +} diff --git a/app/src/main/java/com/github/libretube/views/CustomExoPlayerView.kt b/app/src/main/java/com/github/libretube/views/CustomExoPlayerView.kt index 657e9052e..ece7af0af 100644 --- a/app/src/main/java/com/github/libretube/views/CustomExoPlayerView.kt +++ b/app/src/main/java/com/github/libretube/views/CustomExoPlayerView.kt @@ -18,8 +18,8 @@ import com.github.libretube.databinding.DoubleTapOverlayBinding import com.github.libretube.databinding.ExoStyledPlayerControlViewBinding import com.github.libretube.extensions.setSliderRangeAndValue import com.github.libretube.interfaces.DoubleTapInterface -import com.github.libretube.interfaces.OnlinePlayerOptionsInterface import com.github.libretube.interfaces.PlayerOptionsInterface +import com.github.libretube.obj.BottomSheetItem import com.github.libretube.util.DoubleTapListener import com.github.libretube.util.PreferenceHelper import com.google.android.exoplayer2.trackselection.TrackSelector @@ -40,7 +40,7 @@ internal class CustomExoPlayerView( * Objects from the parent fragment */ private var doubleTapListener: DoubleTapInterface? = null - private var onlinePlayerOptionsInterface: OnlinePlayerOptionsInterface? = null + private var playerOptionsInterface: PlayerOptionsInterface? = null private lateinit var childFragmentManager: FragmentManager private var trackSelector: TrackSelector? = null @@ -85,12 +85,12 @@ internal class CustomExoPlayerView( fun initialize( childFragmentManager: FragmentManager, - playerViewInterface: OnlinePlayerOptionsInterface?, + playerViewInterface: PlayerOptionsInterface?, doubleTapOverlayBinding: DoubleTapOverlayBinding, trackSelector: TrackSelector? ) { this.childFragmentManager = childFragmentManager - this.onlinePlayerOptionsInterface = playerViewInterface + this.playerOptionsInterface = playerViewInterface this.doubleTapOverlayBinding = doubleTapOverlayBinding this.trackSelector = trackSelector @@ -99,7 +99,7 @@ internal class CustomExoPlayerView( enableDoubleTapToSeek() - initializeAdvancedOptions() + initializeAdvancedOptions(context) // locking the player binding.lockPlayer.setOnClickListener { @@ -139,48 +139,77 @@ internal class CustomExoPlayerView( return false } - private fun initializeAdvancedOptions() { + private fun initializeAdvancedOptions(context: Context) { binding.toggleOptions.setOnClickListener { - val bottomSheetFragment = PlayerOptionsBottomSheet().apply { - setOnClickListeners( - playerOptionsInterface, - onlinePlayerOptionsInterface + val bottomSheetFragment = BottomSheet().apply { + val items = mutableListOf( + BottomSheetItem( + context.getString(R.string.player_autoplay), + R.drawable.ic_play, + if (autoplayEnabled) { + context.getString(R.string.enabled) + } else { + context.getString(R.string.disabled) + } + ), + 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" + ) ) - // set the auto play mode - currentAutoplayMode = if (autoplayEnabled) { - context?.getString(R.string.enabled) - } else { - context?.getString(R.string.disabled) + + 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()) { + trackSelector!!.parameters.preferredTextLanguages[0] + } else { + context.getString(R.string.none) + } + ) + ) } - // set the current caption language - currentCaptions = - if (trackSelector != null && trackSelector!!.parameters.preferredTextLanguages.isNotEmpty()) { - trackSelector!!.parameters.preferredTextLanguages[0] - } else { - context?.getString(R.string.none) + + setItems(items) { index -> + when (index) { + 0 -> onAutoplayClicked() + 1 -> onRepeatModeClicked() + 2 -> onResizeModeClicked() + 3 -> onPlaybackSpeedClicked() + 4 -> playerOptionsInterface?.onQualityClicked() + 5 -> playerOptionsInterface?.onCaptionClicked() } - // 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 = - if (player?.repeatMode == RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE) { - context?.getString(R.string.repeat_mode_none) - } else { - context?.getString(R.string.repeat_mode_current) - } - // 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) @@ -282,81 +311,80 @@ internal class CustomExoPlayerView( } } - private val playerOptionsInterface = object : PlayerOptionsInterface { - override fun onAutoplayClicked() { - // autoplay options dialog - MaterialAlertDialogBuilder(context) - .setTitle(R.string.player_autoplay) - .setItems( - arrayOf( - context.getString(R.string.enabled), - context.getString(R.string.disabled) - ) - ) { _, index -> - when (index) { - 0 -> autoplayEnabled = true - 1 -> autoplayEnabled = false - } + fun onAutoplayClicked() { + // autoplay options dialog + MaterialAlertDialogBuilder(context) + .setTitle(R.string.player_autoplay) + .setItems( + arrayOf( + context.getString(R.string.enabled), + context.getString(R.string.disabled) + ) + ) { _, index -> + when (index) { + 0 -> autoplayEnabled = true + 1 -> autoplayEnabled = false } - .show() - } + } + .show() + } - override fun onPlaybackSpeedClicked() { - val playbackSpeedBinding = DialogSliderBinding.inflate( - LayoutInflater.from(context) - ) - playbackSpeedBinding.slider.setSliderRangeAndValue( - PreferenceRanges.playbackSpeed - ) - playbackSpeedBinding.slider.value = player?.playbackParameters?.speed ?: 1f - // change playback speed dialog - MaterialAlertDialogBuilder(context) - .setTitle(R.string.change_playback_speed) - .setView(playbackSpeedBinding.root) - .setNegativeButton(R.string.cancel, null) - .setPositiveButton(R.string.okay) { _, _ -> - player?.setPlaybackSpeed( - playbackSpeedBinding.slider.value - ) - } - .show() - } + fun onPlaybackSpeedClicked() { + val playbackSpeedBinding = DialogSliderBinding.inflate( + LayoutInflater.from(context) + ) + playbackSpeedBinding.slider.setSliderRangeAndValue( + PreferenceRanges.playbackSpeed + ) + playbackSpeedBinding.slider.value = player?.playbackParameters?.speed ?: 1f + // change playback speed dialog + MaterialAlertDialogBuilder(context) + .setTitle(R.string.change_playback_speed) + .setView(playbackSpeedBinding.root) + .setNegativeButton(R.string.cancel, null) + .setPositiveButton(R.string.okay) { _, _ -> + player?.setPlaybackSpeed( + playbackSpeedBinding.slider.value + ) + } + .show() + } - override fun onResizeModeClicked() { - // switching between original aspect ratio (black bars) and zoomed to fill device screen - val aspectRatioModeNames = context.resources?.getStringArray(R.array.resizeMode) + fun onResizeModeClicked() { + // switching between original aspect ratio (black bars) and zoomed to fill device screen + val aspectRatioModeNames = context.resources?.getStringArray(R.array.resizeMode) - val aspectRatioModes = arrayOf( - AspectRatioFrameLayout.RESIZE_MODE_FIT, - AspectRatioFrameLayout.RESIZE_MODE_ZOOM, - AspectRatioFrameLayout.RESIZE_MODE_FILL - ) + val aspectRatioModes = arrayOf( + AspectRatioFrameLayout.RESIZE_MODE_FIT, + AspectRatioFrameLayout.RESIZE_MODE_ZOOM, + AspectRatioFrameLayout.RESIZE_MODE_FILL + ) - MaterialAlertDialogBuilder(context) - .setTitle(R.string.aspect_ratio) - .setItems(aspectRatioModeNames) { _, index -> - resizeMode = aspectRatioModes[index] - } - .show() - } + MaterialAlertDialogBuilder(context) + .setTitle(R.string.aspect_ratio) + .setItems(aspectRatioModeNames) { _, index -> + resizeMode = aspectRatioModes[index] + } + .show() + } - override fun onRepeatModeClicked() { - val repeatModeNames = arrayOf( - context.getString(R.string.repeat_mode_none), - context.getString(R.string.repeat_mode_current) - ) + fun onRepeatModeClicked() { + val repeatModeNames = arrayOf( + context.getString(R.string.repeat_mode_none), + context.getString(R.string.repeat_mode_current) + ) - val repeatModes = arrayOf( - RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL, - RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE - ) - // repeat mode options dialog - MaterialAlertDialogBuilder(context) - .setTitle(R.string.repeat_mode) - .setItems(repeatModeNames) { _, index -> - player?.repeatMode = repeatModes[index] - } - .show() - } + val repeatModes = arrayOf( + RepeatModeUtil.REPEAT_TOGGLE_MODE_NONE, + RepeatModeUtil.REPEAT_TOGGLE_MODE_ALL + + ) + // repeat mode options dialog + MaterialAlertDialogBuilder(context) + .setTitle(R.string.repeat_mode) + .setItems(repeatModeNames) { _, index -> + player?.repeatMode = repeatModes[index] + } + .show() } } diff --git a/app/src/main/java/com/github/libretube/views/PlayerOptionsBottomSheet.kt b/app/src/main/java/com/github/libretube/views/PlayerOptionsBottomSheet.kt deleted file mode 100644 index 901f46894..000000000 --- a/app/src/main/java/com/github/libretube/views/PlayerOptionsBottomSheet.kt +++ /dev/null @@ -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(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)" - } -} diff --git a/app/src/main/res/layout/bottom_sheet.xml b/app/src/main/res/layout/bottom_sheet.xml index fddbf9545..42fa494ee 100644 --- a/app/src/main/res/layout/bottom_sheet.xml +++ b/app/src/main/res/layout/bottom_sheet.xml @@ -1,46 +1,34 @@ - + xmlns:app="http://schemas.android.com/apk/res-auto"> - + - + - + + - + - + - + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/bottom_sheet_item.xml b/app/src/main/res/layout/bottom_sheet_item.xml new file mode 100644 index 000000000..251ec72b9 --- /dev/null +++ b/app/src/main/res/layout/bottom_sheet_item.xml @@ -0,0 +1,24 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/style.xml b/app/src/main/res/values/style.xml index f2a1ced7f..a5bd04bd7 100644 --- a/app/src/main/res/values/style.xml +++ b/app/src/main/res/values/style.xml @@ -151,19 +151,6 @@ - -