Skip to content

Commit

Permalink
Rework fullscreen/insets handling and fix deprecations
Browse files Browse the repository at this point in the history
  • Loading branch information
Maxr1998 authored and nielsvanvelzen committed Dec 28, 2022
1 parent a0ea262 commit cea6224
Show file tree
Hide file tree
Showing 5 changed files with 91 additions and 90 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
}
Expand Down
105 changes: 47 additions & 58 deletions app/src/main/java/org/jellyfin/mobile/player/ui/PlayerFragment.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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

Expand All @@ -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 ->
Expand All @@ -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
Expand Down Expand Up @@ -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() }
Expand All @@ -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
Expand All @@ -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()
}
}
}
Expand All @@ -213,40 +212,30 @@ 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()
}
}

/**
* 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)
}

/**
Expand Down Expand Up @@ -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
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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()
}
}
Original file line number Diff line number Diff line change
@@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -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)

0 comments on commit cea6224

Please sign in to comment.