diff --git a/app/src/main/java/com/futo/platformplayer/Settings.kt b/app/src/main/java/com/futo/platformplayer/Settings.kt index e9b247ff..3656684a 100644 --- a/app/src/main/java/com/futo/platformplayer/Settings.kt +++ b/app/src/main/java/com/futo/platformplayer/Settings.kt @@ -415,7 +415,7 @@ class Settings : FragmentedStorageFileJson() { @FormField(R.string.simplify_sources, FieldForm.TOGGLE, R.string.simplify_sources_description, 4) var simplifySources: Boolean = true; - @FormField(R.string.force_allow_full_screen_rotation, FieldForm.TOGGLE, R.string.force_allow_full_screen_rotation_description, 5) + @FormField(R.string.force_enable_auto_rotate_in_full_screen, FieldForm.TOGGLE, R.string.force_enable_auto_rotate_in_full_screen_description, 5) var forceAllowFullScreenRotation: Boolean = false @FormField(R.string.background_behavior, FieldForm.DROPDOWN, -1, 6) diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt index f7fb1daf..776c4512 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailFragment.kt @@ -1,11 +1,12 @@ package com.futo.platformplayer.fragment.mainactivity.main -import android.annotation.SuppressLint +import android.content.Context import android.content.pm.ActivityInfo import android.content.res.Configuration import android.os.Build import android.os.Bundle import android.view.LayoutInflater +import android.view.OrientationEventListener import android.view.View import android.view.ViewGroup import android.view.WindowInsets @@ -28,13 +29,18 @@ import com.futo.platformplayer.models.PlatformVideoWithTime import com.futo.platformplayer.models.UrlVideoWithTime import com.futo.platformplayer.states.StatePlayer import com.futo.platformplayer.views.containers.SingleViewTouchableMotionLayout +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import kotlin.math.min +//region Fragment @UnstableApi -class VideoDetailFragment : MainFragment { - override val isMainView : Boolean = false; +class VideoDetailFragment() : MainFragment() { + override val isMainView: Boolean = false; override val hasBottomBar: Boolean = true; - override val isOverlay : Boolean = true; + override val isOverlay: Boolean = true; override val isHistory: Boolean = false; private var _isActive: Boolean = false; @@ -76,8 +82,10 @@ class VideoDetailFragment : MainFragment { private var _loadUrlOnCreate: UrlVideoWithTime? = null; private var _leavingPiP = false; -//region Fragment - constructor() : super() + private var _landscapeOrientationListener: LandscapeOrientationListener? = null + private var _portraitOrientationListener: PortraitOrientationListener? = null + private var _lastSetOrientation: Int = Configuration.ORIENTATION_UNDEFINED + private var _ignoreNextNewOrientation = false fun nextVideo() { _viewDetail?.nextVideo(true, true, true); @@ -101,6 +109,15 @@ class VideoDetailFragment : MainFragment { val isSmallWindow = isSmallWindow() + val temp = _lastSetOrientation + + if (_ignoreNextNewOrientation) { + _ignoreNextNewOrientation = false + } else { + // the device has rotated so update our state tracking what the physical orientation of the device is + _lastSetOrientation = newConfig.orientation + } + if ( isSmallWindow && newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE @@ -113,6 +130,7 @@ class VideoDetailFragment : MainFragment { && isFullscreen && !Settings.instance.playback.fullscreenPortrait && newConfig.orientation == Configuration.ORIENTATION_PORTRAIT + && temp == Configuration.ORIENTATION_LANDSCAPE && isLandscapeVideo ) { _viewDetail?.setFullscreen(false) @@ -153,20 +171,48 @@ class VideoDetailFragment : MainFragment { val isSmallWindow = isSmallWindow() + val autoRotateEnabled = android.provider.Settings.System.getInt( + context?.contentResolver, + android.provider.Settings.System.ACCELEROMETER_ROTATION, 0 + ) == 1 + // For small windows if the device isn't landscape right now and full screen portrait isn't allowed then we should force landscape - if (isSmallWindow && isFullscreen && !isFullScreenPortraitAllowed && resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT && !rotationLock && isLandscapeVideo) { - if(Settings.instance.playback.forceAllowFullScreenRotation){ + if (isSmallWindow && isFullscreen && !isFullScreenPortraitAllowed && _lastSetOrientation != Configuration.ORIENTATION_LANDSCAPE && !rotationLock && isLandscapeVideo) { + if (Settings.instance.playback.forceAllowFullScreenRotation) { a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE - }else{ + } else { a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE } + // the next orientation change will not reflect the device because we are manually setting the orientation to landscape + _ignoreNextNewOrientation = true + if (autoRotateEnabled + ) { + // start listening for the device to rotate to landscape + // at which point we'll be able to set requestedOrientation to back to UNSPECIFIED + _landscapeOrientationListener?.enableListener() + } } // For small windows if the device isn't in a portrait orientation and we're in the maximized state then we should force portrait - else if (isSmallWindow && !isMinimizingFromFullScreen && !isFullscreen && state == State.MAXIMIZED && resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) { + else if (isSmallWindow && !isMinimizingFromFullScreen && !isFullscreen && state == State.MAXIMIZED && _lastSetOrientation == Configuration.ORIENTATION_LANDSCAPE) { a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT + // the next orientation change will not reflect the device because we are manually setting the orientation to portrait + _ignoreNextNewOrientation = true + if (autoRotateEnabled + ) { + // start listening for the device to rotate to portrait + // at which point we'll be able to set requestedOrientation to back to UNSPECIFIED + _portraitOrientationListener?.enableListener() + } else { + // the rotation state resets to portrait when changing requestedOrientation + _lastSetOrientation = Configuration.ORIENTATION_PORTRAIT + } } else if (rotationLock) { + _portraitOrientationListener?.disableListener() + _landscapeOrientationListener?.disableListener() a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED } else { + _portraitOrientationListener?.disableListener() + _landscapeOrientationListener?.disableListener() a.requestedOrientation = if (isReversePortraitAllowed) { ActivityInfo.SCREEN_ORIENTATION_FULL_USER } else { @@ -341,6 +387,26 @@ class VideoDetailFragment : MainFragment { StatePlayer.instance.onRotationLockChanged.subscribe(this) { updateOrientation() } + + _landscapeOrientationListener = LandscapeOrientationListener(requireContext()) + { + CoroutineScope(Dispatchers.Main).launch { + // delay to make sure that the system auto rotate updates + delay(300) + _lastSetOrientation = Configuration.ORIENTATION_LANDSCAPE + updateOrientation() + } + } + _portraitOrientationListener = PortraitOrientationListener(requireContext()) + { + CoroutineScope(Dispatchers.Main).launch { + // delay to make sure that the system auto rotate updates + delay(300) + _lastSetOrientation = Configuration.ORIENTATION_PORTRAIT + updateOrientation() + } + } + return _view!!; } @@ -442,6 +508,9 @@ class VideoDetailFragment : MainFragment { SettingsActivity.settingsActivityClosed.remove(this) StatePlayer.instance.onRotationLockChanged.remove(this) + _landscapeOrientationListener?.disableListener() + _portraitOrientationListener?.disableListener() + _viewDetail?.let { _viewDetail = null; it.onDestroy(); @@ -534,4 +603,66 @@ class VideoDetailFragment : MainFragment { //region View //TODO: Determine if encapsulated would be readable enough //endregion -} \ No newline at end of file +} + +class LandscapeOrientationListener( + context: Context, + private val onLandscapeDetected: () -> Unit +) : OrientationEventListener(context) { + + private var isListening = false + + override fun onOrientationChanged(orientation: Int) { + if (!isListening) return + + if (orientation in 60..120 || orientation in 240..300) { + onLandscapeDetected() + disableListener() + } + } + + fun enableListener() { + if (!isListening) { + isListening = true + enable() + } + } + + fun disableListener() { + if (isListening) { + isListening = false + disable() + } + } +} + +class PortraitOrientationListener( + context: Context, + private val onPortraitDetected: () -> Unit +) : OrientationEventListener(context) { + + private var isListening = false + + override fun onOrientationChanged(orientation: Int) { + if (!isListening) return + + if (orientation in 0..30 || orientation in 330..360 || orientation in 150..210) { + onPortraitDetected() + disableListener() + } + } + + fun enableListener() { + if (!isListening) { + isListening = true + enable() + } + } + + fun disableListener() { + if (isListening) { + isListening = false + disable() + } + } +} diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5394a597..87d9bdb5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -287,8 +287,8 @@ Schedules discovered planned content as notifications, resulting in more accurate notifications for this content. Attempt to utilize byte ranges Auto Update - Force Allow Full Screen Rotation - Allow auto-rotation between the two landscape orientations even when you disable auto-rotate at the system level. + Force Enable Auto-Rotate In Full-Screen Mode + Force enable auto-rotation between the two landscape orientations in full-screen mode, even when you disable auto-rotate at the system level. Simplify sources Deduplicate sources by resolution so that only more relevant sources are visible. Automatic Backup