Merge pull request #4981 from Bnyro/landscape-player-page

feat: landscape layout for player page
This commit is contained in:
Bnyro 2023-10-23 17:52:45 +02:00 committed by GitHub
commit fc9e3e6501
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 451 additions and 102 deletions

View File

@ -1,15 +1,14 @@
package com.github.libretube.helpers package com.github.libretube.helpers
import android.app.Activity
import android.os.Build import android.os.Build
import android.view.Window
import android.view.WindowManager import android.view.WindowManager
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat import androidx.core.view.WindowInsetsCompat
import com.github.libretube.ui.extensions.toggleSystemBars import com.github.libretube.ui.extensions.toggleSystemBars
object WindowHelper { object WindowHelper {
fun toggleFullscreen(activity: Activity, isFullscreen: Boolean) { fun toggleFullscreen(window: Window, isFullscreen: Boolean) {
val window = activity.window
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
window.attributes.layoutInDisplayCutoutMode = if (isFullscreen) { window.attributes.layoutInDisplayCutoutMode = if (isFullscreen) {
WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES
@ -30,7 +29,7 @@ object WindowHelper {
// Show the system bars when it is not fullscreen and hide them when it is fullscreen // Show the system bars when it is not fullscreen and hide them when it is fullscreen
// System bars means status bar and the navigation bar // System bars means status bar and the navigation bar
// See: https://developer.android.com/training/system-ui/immersive#kotlin // See: https://developer.android.com/training/system-ui/immersive#kotlin
activity.toggleSystemBars( window.toggleSystemBars(
types = WindowInsetsCompat.Type.systemBars(), types = WindowInsetsCompat.Type.systemBars(),
showBars = !isFullscreen showBars = !isFullscreen
) )

View File

@ -486,8 +486,8 @@ class MainActivity : BaseActivity() {
super.onConfigurationChanged(newConfig) super.onConfigurationChanged(newConfig)
when (newConfig.orientation) { when (newConfig.orientation) {
Configuration.ORIENTATION_PORTRAIT -> WindowHelper.toggleFullscreen(this, false) Configuration.ORIENTATION_PORTRAIT -> WindowHelper.toggleFullscreen(window, false)
Configuration.ORIENTATION_LANDSCAPE -> WindowHelper.toggleFullscreen(this, true) Configuration.ORIENTATION_LANDSCAPE -> WindowHelper.toggleFullscreen(window, true)
} }
} }

View File

@ -56,7 +56,7 @@ class OfflinePlayerActivity : BaseActivity() {
private val playerViewModel: PlayerViewModel by viewModels() private val playerViewModel: PlayerViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
WindowHelper.toggleFullscreen(this, true) WindowHelper.toggleFullscreen(window, true)
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE

View File

@ -13,7 +13,7 @@ import com.github.libretube.helpers.ThemeHelper
* Activity that applies the LibreTube theme and the in-app language * Activity that applies the LibreTube theme and the in-app language
*/ */
open class BaseActivity : AppCompatActivity() { open class BaseActivity : AppCompatActivity() {
private val screenOrientationPref by lazy { val screenOrientationPref by lazy {
val orientationPref = PreferenceHelper.getString( val orientationPref = PreferenceHelper.getString(
PreferenceKeys.ORIENTATION, PreferenceKeys.ORIENTATION,
resources.getString(R.string.config_default_orientation_pref) resources.getString(R.string.config_default_orientation_pref)

View File

@ -1,12 +1,12 @@
package com.github.libretube.ui.extensions package com.github.libretube.ui.extensions
import android.app.Activity import android.view.Window
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.core.view.WindowInsetsCompat.Type.InsetsType import androidx.core.view.WindowInsetsCompat.Type.InsetsType
import androidx.core.view.WindowInsetsControllerCompat import androidx.core.view.WindowInsetsControllerCompat
fun Activity.toggleSystemBars(@InsetsType types: Int, showBars: Boolean) { fun Window.toggleSystemBars(@InsetsType types: Int, showBars: Boolean) {
WindowCompat.getInsetsController(window, window.decorView).apply { WindowCompat.getInsetsController(this, decorView).apply {
systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
if (showBars) { if (showBars) {
show(types) show(types)

View File

@ -1,6 +1,7 @@
package com.github.libretube.ui.fragments package com.github.libretube.ui.fragments
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.app.Dialog
import android.content.BroadcastReceiver import android.content.BroadcastReceiver
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@ -17,7 +18,9 @@ import android.text.format.DateUtils
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams
import android.widget.Toast import android.widget.Toast
import android.window.OnBackInvokedDispatcher
import androidx.constraintlayout.motion.widget.MotionLayout import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.constraintlayout.motion.widget.TransitionAdapter import androidx.constraintlayout.motion.widget.TransitionAdapter
import androidx.core.content.getSystemService import androidx.core.content.getSystemService
@ -85,12 +88,14 @@ import com.github.libretube.helpers.PlayerHelper.getVideoStats
import com.github.libretube.helpers.PlayerHelper.isInSegment import com.github.libretube.helpers.PlayerHelper.isInSegment
import com.github.libretube.helpers.PreferenceHelper import com.github.libretube.helpers.PreferenceHelper
import com.github.libretube.helpers.ProxyHelper import com.github.libretube.helpers.ProxyHelper
import com.github.libretube.helpers.WindowHelper
import com.github.libretube.obj.PlayerNotificationData import com.github.libretube.obj.PlayerNotificationData
import com.github.libretube.obj.ShareData import com.github.libretube.obj.ShareData
import com.github.libretube.obj.VideoResolution import com.github.libretube.obj.VideoResolution
import com.github.libretube.parcelable.PlayerData import com.github.libretube.parcelable.PlayerData
import com.github.libretube.ui.activities.MainActivity import com.github.libretube.ui.activities.MainActivity
import com.github.libretube.ui.adapters.VideosAdapter import com.github.libretube.ui.adapters.VideosAdapter
import com.github.libretube.ui.base.BaseActivity
import com.github.libretube.ui.dialogs.AddToPlaylistDialog import com.github.libretube.ui.dialogs.AddToPlaylistDialog
import com.github.libretube.ui.dialogs.DownloadDialog import com.github.libretube.ui.dialogs.DownloadDialog
import com.github.libretube.ui.dialogs.ShareDialog import com.github.libretube.ui.dialogs.ShareDialog
@ -188,6 +193,21 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
private var scrubbingTimeBar = false private var scrubbingTimeBar = false
private var chaptersBottomSheet: ChaptersBottomSheet? = null private var chaptersBottomSheet: ChaptersBottomSheet? = null
/**
* The orientation of the `fragment_player.xml` that's currently used
* This is needed in order to figure out if the current layout is the landscape one or not.
*/
private var playerLayoutOrientation = Int.MIN_VALUE
private val fullscreenDialog by lazy {
object: Dialog(requireContext(), android.R.style.Theme_Black_NoTitleBar_Fullscreen) {
override fun onBackPressed() {
super.onBackPressed()
unsetFullscreen()
}
}
}
/** /**
* Receiver for all actions in the PiP mode * Receiver for all actions in the PiP mode
*/ */
@ -236,6 +256,8 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
keepQueue = playerData.keepQueue keepQueue = playerData.keepQueue
timeStamp = playerData.timestamp timeStamp = playerData.timestamp
playerLayoutOrientation = resources.configuration.orientation
// broadcast receiver for PiP actions // broadcast receiver for PiP actions
context?.registerReceiver( context?.registerReceiver(
broadcastReceiver, broadcastReceiver,
@ -365,17 +387,19 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
.isPictureInPictureAvailable(activity) .isPictureInPictureAvailable(activity)
} }
private fun onManualPlayerClose() {
PlayingQueue.clear()
BackgroundHelper.stopBackgroundPlay(requireContext())
killPlayerFragment()
}
// actions that don't depend on video information // actions that don't depend on video information
private fun initializeOnClickActions() { private fun initializeOnClickActions() {
binding.closeImageView.setOnClickListener { binding.closeImageView.setOnClickListener {
PlayingQueue.clear() onManualPlayerClose()
BackgroundHelper.stopBackgroundPlay(requireContext())
killPlayerFragment()
} }
playerBinding.closeImageButton.setOnClickListener { playerBinding.closeImageButton.setOnClickListener {
PlayingQueue.clear() onManualPlayerClose()
BackgroundHelper.stopBackgroundPlay(requireContext())
killPlayerFragment()
} }
playerBinding.autoPlay.isVisible = true playerBinding.autoPlay.isVisible = true
@ -499,50 +523,30 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
NavigationHelper.startAudioPlayer(requireContext()) NavigationHelper.startAudioPlayer(requireContext())
} }
/**
* If enabled, determine the orientation o use based on the video's aspect ratio
* Expected behavior: Portrait for shorts, Landscape for normal videos
*/
private fun updateFullscreenOrientation() {
if (!PlayerHelper.autoFullscreenEnabled) {
val height = streams.videoStreams.firstOrNull()?.height ?: exoPlayer.videoSize.height
val width = streams.videoStreams.firstOrNull()?.width ?: exoPlayer.videoSize.width
// different orientations of the video are only available when autorotation is disabled
val orientation = PlayerHelper.getOrientation(width, height)
mainActivity.requestedOrientation = orientation
}
}
private fun setFullscreen() { private fun setFullscreen() {
with(binding.playerMotionLayout) {
getConstraintSet(R.id.start).constrainHeight(R.id.player, -1)
enableTransition(R.id.yt_transition, false)
}
// set status bar icon color to white // set status bar icon color to white
windowInsetsControllerCompat.isAppearanceLightStatusBars = false windowInsetsControllerCompat.isAppearanceLightStatusBars = false
binding.mainContainer.isClickable = true viewModel.isFullscreen.value = true
binding.linLayout.isGone = true
if (mainActivity.screenOrientationPref == ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT) {
val height = streams.videoStreams.firstOrNull()?.height ?: exoPlayer.videoSize.height
val width = streams.videoStreams.firstOrNull()?.width ?: exoPlayer.videoSize.width
mainActivity.requestedOrientation = PlayerHelper.getOrientation(width, height)
}
commentsViewModel.setCommentSheetExpand(null) commentsViewModel.setCommentSheetExpand(null)
playerBinding.fullscreen.setImageResource(R.drawable.ic_fullscreen_exit) playerBinding.fullscreen.setImageResource(R.drawable.ic_fullscreen_exit)
playerBinding.exoTitle.isVisible = true playerBinding.exoTitle.isVisible = true
updateFullscreenOrientation()
viewModel.isFullscreen.value = true
updateResolutionOnFullscreenChange(true) updateResolutionOnFullscreenChange(true)
openOrCloseFullscreenDialog(true)
} }
@SuppressLint("SourceLockedOrientationActivity") @SuppressLint("SourceLockedOrientationActivity")
fun unsetFullscreen() { fun unsetFullscreen() {
// leave fullscreen mode
with(binding.playerMotionLayout) {
getConstraintSet(R.id.start).constrainHeight(R.id.player, 0)
enableTransition(R.id.yt_transition, true)
}
// set status bar icon color back to theme color // set status bar icon color back to theme color
windowInsetsControllerCompat.isAppearanceLightStatusBars = windowInsetsControllerCompat.isAppearanceLightStatusBars =
when (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) { when (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
@ -551,19 +555,40 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
else -> true else -> true
} }
binding.mainContainer.isClickable = false viewModel.isFullscreen.value = false
binding.linLayout.isVisible = true
playerBinding.fullscreen.setImageResource(R.drawable.ic_fullscreen)
playerBinding.exoTitle.isInvisible = true
if (!PlayerHelper.autoFullscreenEnabled) { if (mainActivity.screenOrientationPref == ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT) {
// switch back to portrait mode if autorotation disabled
mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT
} }
viewModel.isFullscreen.value = false playerBinding.fullscreen.setImageResource(R.drawable.ic_fullscreen)
playerBinding.exoTitle.isInvisible = true
updateResolutionOnFullscreenChange(false) updateResolutionOnFullscreenChange(false)
openOrCloseFullscreenDialog(false)
checkForNecessaryOrientationRestart()
}
private fun openOrCloseFullscreenDialog(open: Boolean) {
val playerView = binding.player
(playerView.parent as ViewGroup).removeView(playerView)
if (open) {
fullscreenDialog.addContentView(
binding.player,
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
)
fullscreenDialog.show()
playerView.currentWindow = fullscreenDialog.window
} else {
binding.playerMotionLayout.addView(playerView)
playerView.currentWindow = null
fullscreenDialog.dismiss()
}
WindowHelper.toggleFullscreen(fullscreenDialog.window!!, open)
} }
override fun onPause() { override fun onPause() {
@ -735,8 +760,6 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
initializePlayerView() initializePlayerView()
setupSeekbarPreview() setupSeekbarPreview()
if (viewModel.isFullscreen.value == true) updateFullscreenOrientation()
exoPlayer.playWhenReady = PlayerHelper.playAutomatically exoPlayer.playWhenReady = PlayerHelper.playAutomatically
exoPlayer.prepare() exoPlayer.prepare()
@ -1341,7 +1364,7 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
onConfigurationChanged(resources.configuration) onConfigurationChanged(resources.configuration)
} else { } else {
// go to portrait mode // go to portrait mode
mainActivity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT mainActivity.requestedOrientation = (requireActivity() as BaseActivity).screenOrientationPref
} }
} }
@ -1470,13 +1493,9 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
viewModel.isMiniPlayerVisible.value = false viewModel.isMiniPlayerVisible.value = false
} }
with(binding.playerMotionLayout) {
getConstraintSet(R.id.start).constrainHeight(R.id.player, -1)
enableTransition(R.id.yt_transition, false)
}
binding.linLayout.isGone = true
updateCurrentSubtitle(null) updateCurrentSubtitle(null)
openOrCloseFullscreenDialog(true)
} else { } else {
// close button got clicked in PiP mode // close button got clicked in PiP mode
// pause the video and keep the app alive // pause the video and keep the app alive
@ -1485,20 +1504,13 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
// enable exoPlayer controls again // enable exoPlayer controls again
binding.player.useController = true binding.player.useController = true
// set back to portrait mode
if (viewModel.isFullscreen.value != true) {
with(binding.playerMotionLayout) {
getConstraintSet(R.id.start).constrainHeight(R.id.player, 0)
enableTransition(R.id.yt_transition, true)
}
binding.linLayout.isVisible = true
}
updateCurrentSubtitle(currentSubtitle) updateCurrentSubtitle(currentSubtitle)
binding.optionsLL.post { binding.optionsLL.post {
binding.optionsLL.requestLayout() binding.optionsLL.requestLayout()
} }
openOrCloseFullscreenDialog(false)
} }
} }
@ -1564,6 +1576,12 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
private fun killPlayerFragment() { private fun killPlayerFragment() {
viewModel.isFullscreen.value = false viewModel.isFullscreen.value = false
viewModel.isMiniPlayerVisible.value = false viewModel.isMiniPlayerVisible.value = false
// dismiss the fullscreen dialog if it's currently visible
// otherwise it would stay alive while being detached from this fragment
fullscreenDialog.dismiss()
binding.player.currentWindow = null
binding.playerMotionLayout.transitionToEnd() binding.playerMotionLayout.transitionToEnd()
mainActivity.supportFragmentManager.commit { mainActivity.supportFragmentManager.commit {
remove(this@PlayerFragment) remove(this@PlayerFragment)
@ -1572,21 +1590,43 @@ class PlayerFragment : Fragment(), OnlinePlayerOptions {
onDestroy() onDestroy()
} }
/**
* Check if the activity needs to be recreated due to an orientation change
* If true, the activity will be automatically restarted
*/
private fun checkForNecessaryOrientationRestart() {
val lockedOrientations = listOf(ActivityInfo.SCREEN_ORIENTATION_USER_PORTRAIT, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE)
if (mainActivity.screenOrientationPref in lockedOrientations) return
val orientation = resources.configuration.orientation
if (viewModel.isFullscreen.value != true && orientation != playerLayoutOrientation) {
if (this::exoPlayer.isInitialized) {
arguments?.putLong(IntentData.timeStamp, exoPlayer.currentPosition / 1000)
}
playerLayoutOrientation = orientation
activity?.recreate()
}
}
override fun onConfigurationChanged(newConfig: Configuration) { override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig) super.onConfigurationChanged(newConfig)
if (!PlayerHelper.autoFullscreenEnabled || _binding == null || if (_binding == null ||
// If in PiP mode, orientation is given as landscape. // If in PiP mode, orientation is given as landscape.
PictureInPictureCompat.isInPictureInPictureMode(requireActivity()) PictureInPictureCompat.isInPictureInPictureMode(requireActivity())
) { ) {
return return
} }
when (newConfig.orientation) { if (PlayerHelper.autoFullscreenEnabled) {
// go to fullscreen mode when (newConfig.orientation) {
Configuration.ORIENTATION_LANDSCAPE -> setFullscreen() // go to fullscreen mode
// exit fullscreen if not landscape Configuration.ORIENTATION_LANDSCAPE -> setFullscreen()
else -> unsetFullscreen() // exit fullscreen if not landscape
else -> unsetFullscreen()
}
} else {
checkForNecessaryOrientationRestart()
} }
} }
} }

View File

@ -39,19 +39,6 @@ class CommentsSheet : UndimmedBottomSheet() {
val binding = binding val binding = binding
binding.dragHandle.viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
binding.dragHandle.viewTreeObserver.removeOnGlobalLayoutListener(this)
// limit the recyclerview height to not cover the video
binding.standardBottomSheet.layoutParams =
binding.commentFragContainer.layoutParams.apply {
height = playerViewModel.maxSheetHeightPx
}
}
})
binding.btnBack.setOnClickListener { binding.btnBack.setOnClickListener {
if (childFragmentManager.backStackEntryCount > 0) { if (childFragmentManager.backStackEntryCount > 0) {
childFragmentManager.popBackStack() childFragmentManager.popBackStack()

View File

@ -1,6 +1,7 @@
package com.github.libretube.ui.sheets package com.github.libretube.ui.sheets
import android.app.Dialog import android.app.Dialog
import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.view.KeyEvent import android.view.KeyEvent
import android.view.View import android.view.View
@ -21,9 +22,11 @@ abstract class UndimmedBottomSheet : ExpandedBottomSheet() {
override fun onGlobalLayout() { override fun onGlobalLayout() {
getDragHandle().viewTreeObserver.removeOnGlobalLayoutListener(this) getDragHandle().viewTreeObserver.removeOnGlobalLayoutListener(this)
// limit the recyclerview height to not cover the video if (resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT) {
getBottomSheet().updateLayoutParams { // limit the recyclerview height to not cover the video
height = getSheetMaxHeightPx() getBottomSheet().updateLayoutParams {
height = getSheetMaxHeightPx()
}
} }
} }
}) })

View File

@ -11,6 +11,7 @@ import android.text.format.DateUtils
import android.util.AttributeSet import android.util.AttributeSet
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.Window
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.ImageView import android.widget.ImageView
import android.widget.TextView import android.widget.TextView
@ -87,6 +88,12 @@ open class CustomExoPlayerView(
updateCurrentPosition() updateCurrentPosition()
} }
/**
* The window that needs to be addressed for showing and hiding the system bars
* If null, the activity's default/main window will be used
*/
var currentWindow: Window? = null
/** /**
* Preferences * Preferences
*/ */
@ -143,7 +150,7 @@ open class CustomExoPlayerView(
// change locked status // change locked status
isPlayerLocked = !isPlayerLocked isPlayerLocked = !isPlayerLocked
activity.toggleSystemBars( (currentWindow ?: activity.window).toggleSystemBars(
types = WindowInsetsCompat.Type.statusBars(), types = WindowInsetsCompat.Type.statusBars(),
showBars = !isPlayerLocked showBars = !isPlayerLocked
) )

View File

@ -13,13 +13,13 @@ class OfflinePlayerView(
override fun hideController() { override fun hideController() {
super.hideController() super.hideController()
// hide the status bars when continuing to watch video // hide the status bars when continuing to watch video
activity.toggleSystemBars(WindowInsetsCompat.Type.systemBars(), false) activity.window.toggleSystemBars(WindowInsetsCompat.Type.systemBars(), false)
} }
override fun showController() { override fun showController() {
super.showController() super.showController()
// show status bar when showing player options // show status bar when showing player options
activity.toggleSystemBars(WindowInsetsCompat.Type.statusBars(), true) activity.window.toggleSystemBars(WindowInsetsCompat.Type.statusBars(), true)
} }
override fun getTopBarMarginDp(): Int { override fun getTopBarMarginDp(): Int {

View File

@ -145,7 +145,7 @@ class OnlinePlayerView(
this.playerOptions = playerOptions this.playerOptions = playerOptions
playerViewModel.isFullscreen.observe(viewLifecycleOwner) { isFullscreen -> playerViewModel.isFullscreen.observe(viewLifecycleOwner) { isFullscreen ->
WindowHelper.toggleFullscreen(activity, isFullscreen) WindowHelper.toggleFullscreen(activity.window, isFullscreen)
updateTopBarMargin() updateTopBarMargin()
} }
@ -154,7 +154,7 @@ class OnlinePlayerView(
playerViewModel.isFullscreen.value?.let { isFullscreen -> playerViewModel.isFullscreen.value?.let { isFullscreen ->
if (!isFullscreen) return@let if (!isFullscreen) return@let
// Show status bar only not navigation bar if the player controls are visible and hide it otherwise // Show status bar only not navigation bar if the player controls are visible and hide it otherwise
activity.toggleSystemBars( activity.window.toggleSystemBars(
types = WindowInsetsCompat.Type.statusBars(), types = WindowInsetsCompat.Type.statusBars(),
showBars = visibility == View.VISIBLE && !isPlayerLocked showBars = visibility == View.VISIBLE && !isPlayerLocked
) )
@ -189,7 +189,7 @@ class OnlinePlayerView(
super.hideController() super.hideController()
if (playerViewModel?.isFullscreen?.value == true) { if (playerViewModel?.isFullscreen?.value == true) {
WindowHelper.toggleFullscreen(activity, true) WindowHelper.toggleFullscreen(activity.window, true)
} }
updateTopBarMargin() updateTopBarMargin()
} }

View File

@ -0,0 +1,312 @@
<?xml version="1.0" encoding="utf-8"?>
<com.github.libretube.ui.views.SingleViewTouchableMotionLayout 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:id="@+id/playerMotionLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutDescription="@xml/player_scene">
<ScrollView
android:id="@+id/player_scrollView"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?android:attr/colorBackground"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/related_container"
app:layout_constraintHorizontal_bias="0.5"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/main_container">
<LinearLayout
android:id="@+id/linLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<com.github.libretube.ui.views.DescriptionLayout
android:id="@+id/descriptionLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:animateLayoutChanges="true" />
<com.google.android.material.card.MaterialCardView
style="@style/Widget.Material3.CardView.Elevated"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="10dp"
android:layout_marginVertical="17dp"
app:cardCornerRadius="27dp">
<LinearLayout
android:id="@+id/optionsLL"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginHorizontal="8dp"
android:layout_marginVertical="18dp"
android:gravity="center"
android:orientation="horizontal">
<com.github.libretube.ui.views.DrawableTextView
android:id="@+id/relPlayer_share"
style="@style/PlayerActionsText"
android:text="@string/share"
app:drawableTopCompat="@drawable/ic_share" />
<com.github.libretube.ui.views.DrawableTextView
android:id="@+id/relPlayer_download"
style="@style/PlayerActionsText"
android:text="@string/download"
app:drawableTopCompat="@drawable/ic_download" />
<com.github.libretube.ui.views.DrawableTextView
android:id="@+id/relPlayer_pip"
style="@style/PlayerActionsText"
android:text="@string/pop_up"
app:drawableTopCompat="@drawable/ic_open" />
<com.github.libretube.ui.views.DrawableTextView
android:id="@+id/relPlayer_background"
style="@style/PlayerActionsText"
android:text="@string/audio"
app:drawableTopCompat="@drawable/ic_headphones" />
<com.github.libretube.ui.views.DrawableTextView
android:id="@+id/relPlayer_save"
style="@style/PlayerActionsText"
android:text="@string/save"
app:drawableTopCompat="@drawable/ic_save" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<LinearLayout
android:id="@+id/player_channel"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="8dp"
android:layout_marginVertical="10dp"
android:background="@drawable/rounded_ripple"
android:orientation="horizontal"
android:padding="5dp">
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/player_channelImage"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_gravity="center"
android:layout_marginEnd="4dp"
app:shapeAppearance="@style/CircleImageView" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
android:layout_marginStart="5dp"
android:layout_marginEnd="8dp"
android:layout_weight="1"
android:layoutDirection="locale"
android:orientation="vertical">
<TextView
android:id="@+id/player_channelName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textSize="15sp" />
<TextView
android:id="@+id/player_channelSubCount"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textSize="11sp"
tools:text="2.5M subscribers" />
</LinearLayout>
<com.google.android.material.button.MaterialButton
android:id="@+id/player_subscribe"
style="@style/Widget.Material3.Button.ElevatedButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:text="@string/subscribe"
android:textColor="?colorControlNormal"
android:textSize="12sp"
app:cornerRadius="16dp" />
</LinearLayout>
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/alternativeTrendingRec"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"/>
<com.google.android.material.card.MaterialCardView
android:id="@+id/comments_toggle"
style="@style/Widget.Material3.CardView.Elevated"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginHorizontal="12dp"
android:layout_marginVertical="16dp"
app:cardCornerRadius="18dp">
<androidx.appcompat.widget.AppCompatTextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="8dp"
android:paddingHorizontal="8dp"
android:text="@string/comments"
android:textAlignment="viewStart"
app:drawableEndCompat="@drawable/ic_arrow_up_down" />
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</ScrollView>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/main_container"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="?attr/colorSurface"
app:layout_constraintDimensionRatio="16:9"
app:layout_constraintWidth_percent=".55"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@id/related_container"
app:layout_constraintWidth_default="percent" />
<RelativeLayout
android:id="@+id/related_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:animateLayoutChanges="true"
android:descendantFocusability="blocksDescendants"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@id/main_container"
android:background="?android:windowBackground">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/related_rec_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="10dp"
android:layout_marginTop="10dp"
android:layout_marginEnd="10dp"
android:layout_marginBottom="10dp"
android:nestedScrollingEnabled="false" />
</RelativeLayout>
<com.github.libretube.ui.views.OnlinePlayerView
android:id="@+id/player"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@android:color/black"
app:controller_layout_id="@layout/exo_styled_player_control_view"
app:layout_constraintBottom_toBottomOf="@id/main_container"
app:layout_constraintStart_toStartOf="@id/main_container"
app:layout_constraintTop_toTopOf="@id/main_container"
app:layout_constraintEnd_toEndOf="@id/main_container"
app:show_buffering="when_playing">
<com.github.libretube.ui.views.DoubleTapOverlay
android:id="@+id/doubleTapOverlay"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:gravity="center" />
<com.github.libretube.ui.views.PlayerGestureControlsView
android:id="@+id/playerGestureControlsView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:gravity="center" />
<com.google.android.material.card.MaterialCardView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|end"
android:layout_marginTop="15dp"
android:layout_marginEnd="-10dp"
android:paddingEnd="20dp"
app:cardBackgroundColor="#88000000"
app:strokeWidth="1dp"
tools:ignore="RtlSymmetry">
<com.github.libretube.ui.views.DrawableTextView
android:id="@+id/sb_skip_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawablePadding="20dp"
android:padding="10dp"
android:text="@string/skip_segment"
android:textColor="@android:color/white"
android:textSize="18sp"
android:visibility="gone"
app:drawableEndCompat="@drawable/ic_next"
app:drawableEndDimen="20dp"
app:drawableTint="@android:color/white" />
</com.google.android.material.card.MaterialCardView>
<com.github.libretube.ui.views.AutoplayCountdownView
android:id="@+id/autoplay_countdown"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
</com.github.libretube.ui.views.OnlinePlayerView>
<ImageView
android:id="@+id/close_imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="8dp"
android:src="@drawable/ic_close"
android:tooltipText="@string/tooltip_close"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@id/main_container"
app:layout_constraintEnd_toEndOf="@id/main_container"
app:layout_constraintTop_toTopOf="@id/main_container" />
<ImageView
android:id="@+id/play_imageView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:alpha="0"
android:background="?attr/selectableItemBackgroundBorderless"
android:padding="8dp"
android:src="@drawable/ic_play"
android:tooltipText="@string/tooltip_play"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/close_imageView"
app:layout_constraintEnd_toStartOf="@+id/close_imageView"
app:layout_constraintTop_toTopOf="@+id/close_imageView" />
<TextView
android:id="@+id/title_textView"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:alpha="0"
android:ellipsize="end"
android:maxLines="1"
android:paddingHorizontal="8dp"
android:paddingVertical="15dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="@+id/play_imageView"
app:layout_constraintEnd_toStartOf="@+id/play_imageView"
app:layout_constraintStart_toEndOf="@+id/player"
app:layout_constraintTop_toTopOf="@+id/play_imageView" />
</com.github.libretube.ui.views.SingleViewTouchableMotionLayout>

View File

@ -33,9 +33,10 @@
<ConstraintSet android:id="@+id/start"> <ConstraintSet android:id="@+id/start">
<Constraint <Constraint
android:id="@+id/player" android:id="@+id/player"
android:layout_width="match_parent" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
motion:layout_constraintBottom_toBottomOf="@id/main_container" motion:layout_constraintBottom_toBottomOf="@id/main_container"
motion:layout_constraintEnd_toEndOf="@id/main_container"
motion:layout_constraintStart_toStartOf="@id/main_container" motion:layout_constraintStart_toStartOf="@id/main_container"
motion:layout_constraintTop_toTopOf="@id/main_container" /> motion:layout_constraintTop_toTopOf="@id/main_container" />
<Constraint android:id="@+id/doubleTapOverlay" /> <Constraint android:id="@+id/doubleTapOverlay" />