From cea6224153935309950cd0e3e7d84dba39d469ff Mon Sep 17 00:00:00 2001 From: Maxr1998 Date: Wed, 28 Dec 2022 13:51:14 +0100 Subject: [PATCH] Rework fullscreen/insets handling and fix deprecations --- .../mobile/events/ActivityEventHandler.kt | 8 +- .../mobile/player/ui/PlayerFragment.kt | 105 ++++++++---------- .../player/ui/PlayerFullscreenHelper.kt | 31 ++++++ .../mobile/utils/extensions/Activity.kt | 29 +---- .../mobile/utils/extensions/Window.kt | 8 ++ 5 files changed, 91 insertions(+), 90 deletions(-) create mode 100644 app/src/main/java/org/jellyfin/mobile/player/ui/PlayerFullscreenHelper.kt create mode 100644 app/src/main/java/org/jellyfin/mobile/utils/extensions/Window.kt diff --git a/app/src/main/java/org/jellyfin/mobile/events/ActivityEventHandler.kt b/app/src/main/java/org/jellyfin/mobile/events/ActivityEventHandler.kt index 06911ca9f..adcc45e6a 100644 --- a/app/src/main/java/org/jellyfin/mobile/events/ActivityEventHandler.kt +++ b/app/src/main/java/org/jellyfin/mobile/events/ActivityEventHandler.kt @@ -15,11 +15,10 @@ import org.jellyfin.mobile.MainActivity import org.jellyfin.mobile.R import org.jellyfin.mobile.bridge.JavascriptCallback import org.jellyfin.mobile.player.ui.PlayerFragment +import org.jellyfin.mobile.player.ui.PlayerFullscreenHelper import org.jellyfin.mobile.settings.SettingsFragment import org.jellyfin.mobile.utils.Constants import org.jellyfin.mobile.utils.extensions.addFragment -import org.jellyfin.mobile.utils.extensions.disableFullscreen -import org.jellyfin.mobile.utils.extensions.enableFullscreen import org.jellyfin.mobile.utils.requestDownload import org.jellyfin.mobile.webapp.WebappFunctionChannel import timber.log.Timber @@ -45,14 +44,15 @@ class ActivityEventHandler( private suspend fun MainActivity.handleEvent(event: ActivityEvent) { when (event) { is ActivityEvent.ChangeFullscreen -> { + val fullscreenHelper = PlayerFullscreenHelper(window) if (event.isFullscreen) { requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE - enableFullscreen() + fullscreenHelper.enableFullscreen() window.setBackgroundDrawable(null) } else { // Reset screen orientation requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED - disableFullscreen(true) + fullscreenHelper.disableFullscreen() // Reset window background color window.setBackgroundDrawableResource(R.color.theme_background) } diff --git a/app/src/main/java/org/jellyfin/mobile/player/ui/PlayerFragment.kt b/app/src/main/java/org/jellyfin/mobile/player/ui/PlayerFragment.kt index 57a8fc7ba..4e9cf1f03 100644 --- a/app/src/main/java/org/jellyfin/mobile/player/ui/PlayerFragment.kt +++ b/app/src/main/java/org/jellyfin/mobile/player/ui/PlayerFragment.kt @@ -14,11 +14,11 @@ import android.view.OrientationEventListener import android.view.View import android.view.ViewGroup import android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE -import android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON import android.widget.ImageButton import androidx.annotation.RequiresApi import androidx.appcompat.widget.Toolbar import androidx.core.view.ViewCompat +import androidx.core.view.WindowInsetsCompat import androidx.core.view.isVisible import androidx.core.view.updatePadding import androidx.fragment.app.Fragment @@ -42,11 +42,9 @@ import org.jellyfin.mobile.utils.Constants.PIP_MIN_RATIONAL import org.jellyfin.mobile.utils.SmartOrientationListener import org.jellyfin.mobile.utils.brightness import org.jellyfin.mobile.utils.extensions.aspectRational -import org.jellyfin.mobile.utils.extensions.disableFullscreen -import org.jellyfin.mobile.utils.extensions.enableFullscreen import org.jellyfin.mobile.utils.extensions.getParcelableCompat -import org.jellyfin.mobile.utils.extensions.isFullscreen import org.jellyfin.mobile.utils.extensions.isLandscape +import org.jellyfin.mobile.utils.extensions.keepScreenOn import org.jellyfin.mobile.utils.toast import org.jellyfin.sdk.model.api.MediaStream import org.koin.android.ext.android.inject @@ -66,6 +64,7 @@ class PlayerFragment : Fragment() { private val fullscreenSwitcher: ImageButton get() = playerControlsBinding.fullscreenSwitcher private var playerMenus: PlayerMenus? = null + private lateinit var playerFullscreenHelper: PlayerFullscreenHelper lateinit var playerLockScreenHelper: PlayerLockScreenHelper lateinit var playerGestureHelper: PlayerGestureHelper @@ -92,12 +91,7 @@ class PlayerFragment : Fragment() { } viewModel.playerState.observe(this) { playerState -> val isPlaying = viewModel.playerOrNull?.isPlaying == true - val window = requireActivity().window - if (isPlaying) { - window.addFlags(FLAG_KEEP_SCREEN_ON) - } else { - window.clearFlags(FLAG_KEEP_SCREEN_ON) - } + requireActivity().window.keepScreenOn = isPlaying loadingIndicator.isVisible = playerState == Player.STATE_BUFFERING } viewModel.decoderType.observe(this) { type -> @@ -110,15 +104,12 @@ class PlayerFragment : Fragment() { viewModel.mediaQueueManager.mediaQueue.observe(this) { queueItem -> val jellyfinMediaSource = queueItem.jellyfinMediaSource - with(requireActivity()) { - if (jellyfinMediaSource.selectedVideoStream?.isLandscape == false) { - // For portrait videos, immediately enable fullscreen - enableFullscreen() - updateFullscreenSwitcher(isFullscreen()) - } else if (appPreferences.exoPlayerStartLandscapeVideoInLandscape) { - // Auto-switch to landscape for landscape videos if enabled - requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE - } + if (jellyfinMediaSource.selectedVideoStream?.isLandscape == false) { + // For portrait videos, immediately enable fullscreen + playerFullscreenHelper.enableFullscreen() + } else if (appPreferences.exoPlayerStartLandscapeVideoInLandscape) { + // Auto-switch to landscape for landscape videos if enabled + requireActivity().requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE } // Update title and player menus @@ -149,26 +140,38 @@ class PlayerFragment : Fragment() { return playerBinding.root } - @Suppress("DEPRECATION") override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + val window = requireActivity().window - // Handle system window insets + // Insets handling ViewCompat.setOnApplyWindowInsetsListener(playerBinding.root) { _, insets -> + val systemInsets = insets.getInsetsIgnoringVisibility(WindowInsetsCompat.Type.systemBars()) + playerFullscreenHelper.onWindowInsetsChanged(insets) + playerControlsView.updatePadding( - top = insets.systemWindowInsetTop, - left = insets.systemWindowInsetLeft, - right = insets.systemWindowInsetRight, - bottom = insets.systemWindowInsetBottom, + top = systemInsets.top, + left = systemInsets.left, + right = systemInsets.right, + bottom = systemInsets.bottom, ) playerOverlay.updatePadding( - top = insets.systemWindowInsetTop, - left = insets.systemWindowInsetLeft, - right = insets.systemWindowInsetRight, - bottom = insets.systemWindowInsetBottom, + top = systemInsets.top, + left = systemInsets.left, + right = systemInsets.right, + bottom = systemInsets.bottom, ) + + // Update fullscreen switcher icon + val fullscreenDrawable = when { + playerFullscreenHelper.isFullscreen -> R.drawable.ic_fullscreen_exit_white_32dp + else -> R.drawable.ic_fullscreen_enter_white_32dp + } + fullscreenSwitcher.setImageResource(fullscreenDrawable) + insets } + ViewCompat.requestApplyInsets(view) // Handle toolbar back button toolbar.setNavigationOnClickListener { parentFragmentManager.popBackStack() } @@ -179,9 +182,8 @@ class PlayerFragment : Fragment() { // Set controller timeout suppressControllerAutoHide(false) + playerFullscreenHelper = PlayerFullscreenHelper(window) playerLockScreenHelper = PlayerLockScreenHelper(this, playerBinding, orientationListener) - - // Setup gesture handling playerGestureHelper = PlayerGestureHelper(this, playerBinding, playerLockScreenHelper) // Handle fullscreen switcher @@ -196,10 +198,7 @@ class PlayerFragment : Fragment() { } } else { // Portrait video, only handle fullscreen state - with(requireActivity()) { - if (isFullscreen()) disableFullscreen() else enableFullscreen() - updateFullscreenSwitcher(isFullscreen()) - } + playerFullscreenHelper.toggleFullscreen() } } } @@ -213,8 +212,8 @@ class PlayerFragment : Fragment() { super.onResume() // When returning from another app, fullscreen mode for landscape orientation has to be set again - with(requireActivity()) { - if (isLandscape()) enableFullscreen() + if (isLandscape()) { + playerFullscreenHelper.enableFullscreen() } } @@ -222,31 +221,21 @@ class PlayerFragment : Fragment() { * Handle current orientation and update fullscreen state and switcher icon */ private fun updateFullscreenState(configuration: Configuration) { - with(requireActivity()) { - // Do not handle any orientation changes while being in Picture-in-Picture mode - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && isInPictureInPictureMode) { - return - } + // Do not handle any orientation changes while being in Picture-in-Picture mode + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && requireActivity().isInPictureInPictureMode) { + return + } - if (isLandscape(configuration)) { + when { + isLandscape(configuration) -> { // Landscape orientation is always fullscreen - enableFullscreen() - } else { + playerFullscreenHelper.enableFullscreen() + } + currentVideoStream?.isLandscape != false -> { // Disable fullscreen for landscape video in portrait orientation - if (currentVideoStream?.isLandscape != false) { - disableFullscreen() - } + playerFullscreenHelper.disableFullscreen() } - updateFullscreenSwitcher(isFullscreen()) - } - } - - private fun updateFullscreenSwitcher(fullscreen: Boolean) { - val fullscreenDrawable = when { - fullscreen -> R.drawable.ic_fullscreen_exit_white_32dp - else -> R.drawable.ic_fullscreen_enter_white_32dp } - fullscreenSwitcher.setImageResource(fullscreenDrawable) } /** @@ -383,7 +372,7 @@ class PlayerFragment : Fragment() { with(requireActivity()) { // Reset screen orientation requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED - disableFullscreen() + playerFullscreenHelper.disableFullscreen() // Reset screen brightness window.brightness = BRIGHTNESS_OVERRIDE_NONE } diff --git a/app/src/main/java/org/jellyfin/mobile/player/ui/PlayerFullscreenHelper.kt b/app/src/main/java/org/jellyfin/mobile/player/ui/PlayerFullscreenHelper.kt new file mode 100644 index 000000000..a4293a925 --- /dev/null +++ b/app/src/main/java/org/jellyfin/mobile/player/ui/PlayerFullscreenHelper.kt @@ -0,0 +1,31 @@ +package org.jellyfin.mobile.player.ui + +import android.view.Window +import androidx.core.view.WindowCompat +import androidx.core.view.WindowInsetsCompat +import androidx.core.view.WindowInsetsControllerCompat + +class PlayerFullscreenHelper(private val window: Window) { + private val windowInsetsController = WindowCompat.getInsetsController(window, window.decorView) + var isFullscreen: Boolean = false + private set + + fun onWindowInsetsChanged(insets: WindowInsetsCompat) { + isFullscreen = !insets.isVisible(WindowInsetsCompat.Type.statusBars()) // systemBars() doesn't work here + } + + fun enableFullscreen() { + WindowCompat.setDecorFitsSystemWindows(window, false) + windowInsetsController.hide(WindowInsetsCompat.Type.systemBars()) + windowInsetsController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE + } + + fun disableFullscreen() { + WindowCompat.setDecorFitsSystemWindows(window, true) + windowInsetsController.show(WindowInsetsCompat.Type.systemBars()) + } + + fun toggleFullscreen() { + if (isFullscreen) disableFullscreen() else enableFullscreen() + } +} diff --git a/app/src/main/java/org/jellyfin/mobile/utils/extensions/Activity.kt b/app/src/main/java/org/jellyfin/mobile/utils/extensions/Activity.kt index e88c752ae..f7833fa01 100644 --- a/app/src/main/java/org/jellyfin/mobile/utils/extensions/Activity.kt +++ b/app/src/main/java/org/jellyfin/mobile/utils/extensions/Activity.kt @@ -1,38 +1,11 @@ -@file:Suppress("DEPRECATION") - package org.jellyfin.mobile.utils.extensions import android.app.Activity import android.content.pm.ActivityInfo import android.graphics.Point import android.view.Surface -import android.view.View -import android.view.WindowManager - -const val STABLE_LAYOUT_FLAGS = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or - View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - -const val FULLSCREEN_FLAGS = View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or - View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or - View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - -fun Activity.isFullscreen() = window.decorView.systemUiVisibility.hasFlag(FULLSCREEN_FLAGS) - -fun Activity.enableFullscreen() { - window.apply { - decorView.systemUiVisibility = FULLSCREEN_FLAGS - addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) - clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN) - } -} - -fun Activity.disableFullscreen(keepStableLayout: Boolean = false) { - window.apply { - decorView.systemUiVisibility = if (keepStableLayout) STABLE_LAYOUT_FLAGS else 0 - clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN) - } -} +@Suppress("DEPRECATION") fun Activity.lockOrientation() { val display = windowManager.defaultDisplay val size = Point().also(display::getSize) diff --git a/app/src/main/java/org/jellyfin/mobile/utils/extensions/Window.kt b/app/src/main/java/org/jellyfin/mobile/utils/extensions/Window.kt new file mode 100644 index 000000000..a1568d3a9 --- /dev/null +++ b/app/src/main/java/org/jellyfin/mobile/utils/extensions/Window.kt @@ -0,0 +1,8 @@ +package org.jellyfin.mobile.utils.extensions + +import android.view.Window +import android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON + +inline var Window.keepScreenOn: Boolean + get() = attributes.flags.hasFlag(FLAG_KEEP_SCREEN_ON) + set(value) = if (value) addFlags(FLAG_KEEP_SCREEN_ON) else clearFlags(FLAG_KEEP_SCREEN_ON)