diff --git a/app/src/main/java/com/futo/platformplayer/Settings.kt b/app/src/main/java/com/futo/platformplayer/Settings.kt index ffb6e3a5..784c2c36 100644 --- a/app/src/main/java/com/futo/platformplayer/Settings.kt +++ b/app/src/main/java/com/futo/platformplayer/Settings.kt @@ -478,15 +478,22 @@ class Settings : FragmentedStorageFileJson() { @DropdownFieldOptionsId(R.array.rotation_zone) var rotationZone: Int = 2; - @FormField(R.string.prefer_webm, FieldForm.TOGGLE, R.string.prefer_webm_description, 16) + @FormField(R.string.stability_threshold_time, FieldForm.DROPDOWN, R.string.stability_threshold_time_description, 16) + @DropdownFieldOptionsId(R.array.rotation_threshold_time) + var stabilityThresholdTime: Int = 1; + + @FormField(R.string.full_autorotate_lock, FieldForm.TOGGLE, R.string.full_autorotate_lock_description, 17) + var fullAutorotateLock: Boolean = false; + + @FormField(R.string.prefer_webm, FieldForm.TOGGLE, R.string.prefer_webm_description, 18) var preferWebmVideo: Boolean = false; - @FormField(R.string.prefer_webm_audio, FieldForm.TOGGLE, R.string.prefer_webm_audio_description, 17) + @FormField(R.string.prefer_webm_audio, FieldForm.TOGGLE, R.string.prefer_webm_audio_description, 19) var preferWebmAudio: Boolean = false; - @FormField(R.string.allow_under_cutout, FieldForm.TOGGLE, R.string.allow_under_cutout_description, 18) + @FormField(R.string.allow_under_cutout, FieldForm.TOGGLE, R.string.allow_under_cutout_description, 20) var allowVideoToGoUnderCutout: Boolean = true; - @FormField(R.string.autoplay, FieldForm.TOGGLE, R.string.autoplay, 19) + @FormField(R.string.autoplay, FieldForm.TOGGLE, R.string.autoplay, 21) var autoplay: Boolean = false; } diff --git a/app/src/main/java/com/futo/platformplayer/SimpleOrientationListener.kt b/app/src/main/java/com/futo/platformplayer/SimpleOrientationListener.kt index a289f02b..9fc4367b 100644 --- a/app/src/main/java/com/futo/platformplayer/SimpleOrientationListener.kt +++ b/app/src/main/java/com/futo/platformplayer/SimpleOrientationListener.kt @@ -8,6 +8,7 @@ import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.logging.Logger import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import kotlinx.coroutines.delay import kotlinx.coroutines.launch @@ -17,13 +18,23 @@ class SimpleOrientationListener( ) { private var lastOrientation: Int = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED private var lastStableOrientation: Int = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED - private val stabilityThresholdTime = 500L + private var _currentJob: Job? = null val onOrientationChanged = Event1() private val orientationListener = object : OrientationEventListener(activity, SensorManager.SENSOR_DELAY_UI) { override fun onOrientationChanged(orientation: Int) { //val rotationZone = 45 + val stabilityThresholdTime = when (Settings.instance.playback.stabilityThresholdTime) { + 0 -> 100L + 1 -> 500L + 2 -> 750L + 3 -> 1000L + 4 -> 1500L + 5 -> 2000L + else -> 500L + } + val rotationZone = when (Settings.instance.playback.rotationZone) { 0 -> 15 1 -> 30 @@ -42,7 +53,8 @@ class SimpleOrientationListener( if (newOrientation != lastStableOrientation) { lastStableOrientation = newOrientation - lifecycleScope.launch(Dispatchers.Main) { + _currentJob?.cancel() + _currentJob = lifecycleScope.launch(Dispatchers.Main) { try { delay(stabilityThresholdTime) if (newOrientation == lastStableOrientation) { @@ -63,6 +75,8 @@ class SimpleOrientationListener( } fun stopListening() { + _currentJob?.cancel() + _currentJob = null orientationListener.disable() } diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt index d3003991..fa99c302 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/bottombar/MenuBottomBarFragment.kt @@ -310,19 +310,6 @@ class MenuBottomBarFragment : MainActivityFragment() { if (!StatePayment.instance.hasPaid) { newCurrentButtonDefinitions.add(ButtonDefinition(98, R.drawable.ic_paid, R.drawable.ic_paid_filled, R.string.buy, canToggle = false, { it.currentMain is BuyFragment }, { it.navigate() })) } - newCurrentButtonDefinitions.add(ButtonDefinition(97, R.drawable.ic_quiz, R.drawable.ic_quiz_fill, R.string.faq, canToggle = false, { false }, { - it.navigate(Settings.URL_FAQ); - })) - newCurrentButtonDefinitions.add(ButtonDefinition(96, R.drawable.ic_disabled_visible, R.drawable.ic_disabled_visible, R.string.privacy_mode, canToggle = false, { false }, { - UIDialogs.showDialog(context, R.drawable.ic_disabled_visible_purple, "Privacy Mode", - "All requests will be processed anonymously (unauthenticated), playback and history tracking will be disabled.\n\nTap the icon to disable.", null, 0, - UIDialogs.Action("Cancel", { - StateApp.instance.setPrivacyMode(false); - }, UIDialogs.ActionStyle.NONE), - UIDialogs.Action("Enable", { - StateApp.instance.setPrivacyMode(true); - }, UIDialogs.ActionStyle.PRIMARY)); - })) //Add conditional buttons here, when you add a conditional button, be sure to add the register and unregister events for when the button needs to be updated @@ -379,13 +366,26 @@ class MenuBottomBarFragment : MainActivityFragment() { }), ButtonDefinition(1, R.drawable.ic_subscriptions, R.drawable.ic_subscriptions_filled, R.string.subscriptions, canToggle = true, { it.currentMain is SubscriptionsFeedFragment }, { it.navigate() }), ButtonDefinition(2, R.drawable.ic_creators, R.drawable.ic_creators_filled, R.string.creators, canToggle = false, { it.currentMain is CreatorsFragment }, { it.navigate() }), - ButtonDefinition(3, R.drawable.ic_sources, R.drawable.ic_sources_filled, R.string.sources, canToggle = false, { it.currentMain is SourcesFragment }, { it.navigate() }), + ButtonDefinition(3, R.drawable.ic_sources, R.drawable.ic_sources_filled, R.string.sources, canToggle = true, { it.currentMain is SourcesFragment }, { it.navigate() }), ButtonDefinition(4, R.drawable.ic_playlist, R.drawable.ic_playlist_filled, R.string.playlists, canToggle = false, { it.currentMain is PlaylistsFragment }, { it.navigate() }), ButtonDefinition(5, R.drawable.ic_history, R.drawable.ic_history, R.string.history, canToggle = false, { it.currentMain is HistoryFragment }, { it.navigate() }), ButtonDefinition(6, R.drawable.ic_download, R.drawable.ic_download, R.string.downloads, canToggle = false, { it.currentMain is DownloadsFragment }, { it.navigate() }), ButtonDefinition(8, R.drawable.ic_chat, R.drawable.ic_chat_filled, R.string.comments, canToggle = true, { it.currentMain is CommentsFragment }, { it.navigate() }), ButtonDefinition(9, R.drawable.ic_subscriptions, R.drawable.ic_subscriptions_filled, R.string.subscription_group_menu, canToggle = true, { it.currentMain is SubscriptionGroupListFragment }, { it.navigate() }), ButtonDefinition(10, R.drawable.ic_help_square, R.drawable.ic_help_square_fill, R.string.tutorials, canToggle = true, { it.currentMain is TutorialFragment }, { it.navigate() }), + ButtonDefinition(11, R.drawable.ic_quiz, R.drawable.ic_quiz_fill, R.string.faq, canToggle = true, { false }, { + it.navigate(Settings.URL_FAQ) + }), + ButtonDefinition(12, R.drawable.ic_disabled_visible, R.drawable.ic_disabled_visible, R.string.privacy_mode, canToggle = true, { false }, { + UIDialogs.showDialog(it.requireContext(), R.drawable.ic_disabled_visible_purple, "Privacy Mode", + "All requests will be processed anonymously (unauthenticated), playback and history tracking will be disabled.\n\nTap the icon to disable.", null, 0, + UIDialogs.Action("Cancel", { + StateApp.instance.setPrivacyMode(false); + }, UIDialogs.ActionStyle.NONE), + UIDialogs.Action("Enable", { + StateApp.instance.setPrivacyMode(true); + }, UIDialogs.ActionStyle.PRIMARY)); + }), ButtonDefinition(7, R.drawable.ic_settings, R.drawable.ic_settings_filled, R.string.settings, canToggle = false, { false }, { val c = it.context ?: return@ButtonDefinition; Logger.i(TAG, "settings preventPictureInPicture()"); @@ -396,7 +396,6 @@ class MenuBottomBarFragment : MainActivityFragment() { c.overridePendingTransition(R.anim.slide_in_up, R.anim.slide_darken); } }) - //96 is reserved for privacy button //98 is reserved for buy button //99 is reserved for more button ); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt index 7f4ea091..c5f3d430 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SourceDetailFragment.kt @@ -397,23 +397,43 @@ class SourceDetailFragment : MainFragment() { UIDialogs.Action("Cancel", {}, UIDialogs.ActionStyle.NONE), UIDialogs.Action("Login", { LoginActivity.showLogin(StateApp.instance.context, config) { - StatePlugins.instance.setPluginAuth(config.id, it); - reloadSource(config.id); + try { + StatePlugins.instance.setPluginAuth(config.id, it); + reloadSource(config.id); + } catch (e: Throwable) { + StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { + context?.let { c -> UIDialogs.showGeneralErrorDialog(c, "Failed to set plugin authentication (loginSource, loginWarning)", e) } + } + Logger.e(TAG, "Failed to set plugin authentication (loginSource, loginWarning)", e) + } }; }, UIDialogs.ActionStyle.PRIMARY)) } else LoginActivity.showLogin(StateApp.instance.context, config) { - StatePlugins.instance.setPluginAuth(config.id, it); - reloadSource(config.id); + try { + StatePlugins.instance.setPluginAuth(config.id, it); + reloadSource(config.id); + } catch (e: Throwable) { + StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { + context?.let { c -> UIDialogs.showGeneralErrorDialog(c, "Failed to set plugin authentication (loginSource)", e) } + } + Logger.e(TAG, "Failed to set plugin authentication (loginSource)", e) + } }; } private fun logoutSource(clear: Boolean = true) { val config = _config ?: return; - StatePlugins.instance.setPluginAuth(config.id, null); - reloadSource(config.id); - + try { + StatePlugins.instance.setPluginAuth(config.id, null); + reloadSource(config.id); + } catch (e: Throwable) { + StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { + context?.let { c -> UIDialogs.showGeneralErrorDialog(c, "Failed to clear plugin authentication", e) } + } + Logger.e(TAG, "Failed to clear plugin authentication", e) + } //TODO: Maybe add a dialog option.. if(Settings.instance.plugins.clearCookiesOnLogout && clear) { 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 b1a498f5..1530537b 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 @@ -97,6 +97,7 @@ class VideoDetailFragment : MainFragment { val isMaximized = state == State.MAXIMIZED val isFullScreenPortraitAllowed = Settings.instance.playback.fullscreenPortrait; val bypassRotationPrevention = Settings.instance.other.bypassRotationPrevention; + val fullAutorotateLock = Settings.instance.playback.fullAutorotateLock val currentRequestedOrientation = a.requestedOrientation var currentOrientation = if (_currentOrientation == -1) currentRequestedOrientation else _currentOrientation if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT && !Settings.instance.playback.reversePortrait) @@ -105,27 +106,47 @@ class VideoDetailFragment : MainFragment { val isAutoRotate = Settings.instance.playback.isAutoRotate() val isFs = isFullscreen - if (isFs && isMaximized) { - if (isFullScreenPortraitAllowed) { - if (isAutoRotate) { - a.requestedOrientation = currentOrientation - } - } else if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) { - if (isAutoRotate || currentOrientation != currentRequestedOrientation && (currentRequestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || currentRequestedOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT)) { - a.requestedOrientation = currentOrientation + if (fullAutorotateLock) { + if (isFs && isMaximized) { + if (isFullScreenPortraitAllowed) { + if (isAutoRotate) { + a.requestedOrientation = currentOrientation + } + } else if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) { + if (isAutoRotate || currentOrientation != currentRequestedOrientation && (currentRequestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || currentRequestedOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT)) { + a.requestedOrientation = currentOrientation + } + } else { + a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE } + } else if (bypassRotationPrevention) { + a.requestedOrientation = currentOrientation + } else if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT) { + a.requestedOrientation = currentOrientation } else { - a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT } - } else if (bypassRotationPrevention) { - a.requestedOrientation = currentOrientation - } else if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT) { - a.requestedOrientation = currentOrientation } else { - a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + if (isFs && isMaximized) { + if (isFullScreenPortraitAllowed) { + a.requestedOrientation = currentOrientation + } else if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) { + a.requestedOrientation = currentOrientation + } else if (currentRequestedOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || currentRequestedOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) { + //Don't change anything + } else { + a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE + } + } else if (bypassRotationPrevention) { + a.requestedOrientation = currentOrientation + } else if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT) { + a.requestedOrientation = currentOrientation + } else { + a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + } } - Log.i(TAG, "updateOrientation (isFs = ${isFs}, currentOrientation = ${currentOrientation}, currentRequestedOrientation = ${currentRequestedOrientation}, isMaximized = ${isMaximized}, isAutoRotate = ${isAutoRotate}, isFullScreenPortraitAllowed = ${isFullScreenPortraitAllowed}) resulted in requested orientation ${activity?.requestedOrientation}"); + Log.i(TAG, "updateOrientation (isFs = ${isFs}, currentOrientation = ${currentOrientation}, fullAutorotateLock = ${fullAutorotateLock}, currentRequestedOrientation = ${currentRequestedOrientation}, isMaximized = ${isMaximized}, isAutoRotate = ${isAutoRotate}, isFullScreenPortraitAllowed = ${isFullScreenPortraitAllowed}) resulted in requested orientation ${activity?.requestedOrientation}"); } override fun onShownWithView(parameter: Any?, isBack: Boolean) { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt index 6d44996f..ee835a3e 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt @@ -2360,20 +2360,11 @@ class VideoDetailView : ConstraintLayout { _layoutRecommended.visibility = View.VISIBLE _commentsList.clear() - val url = _url - if (url != null) { - _layoutRecommended.addView(LoaderView(context).apply { - layoutParams = LinearLayout.LayoutParams(60.dp(resources), 60.dp(resources)) - start() - }) - _taskLoadRecommendations.run(url) - } else { - _layoutRecommended.addView(TextView(context).apply { - layoutParams = LinearLayout.LayoutParams(60.dp(resources), 60.dp(resources)) - textSize = 12.0f - text = "No recommendations found" - }) - } + _layoutRecommended.addView(LoaderView(context).apply { + layoutParams = LinearLayout.LayoutParams(60.dp(resources), 60.dp(resources)) + start() + }) + _taskLoadRecommendations.run(null) } } @@ -2385,7 +2376,7 @@ class VideoDetailView : ConstraintLayout { if (_tabIndex == 2) { _layoutRecommended.removeAllViews() - if (results == null) { + if (results == null || results.isEmpty()) { _layoutRecommended.addView(TextView(context).apply { layoutParams = LinearLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT).apply { setMargins(20.dp(resources), 20.dp(resources), 20.dp(resources), 20.dp(resources)) @@ -2767,7 +2758,15 @@ class VideoDetailView : ConstraintLayout { } } else TaskHandler(IPlatformVideoDetails::class.java, {fragment.lifecycleScope}); - private val _taskLoadRecommendations = TaskHandler?>(StateApp.instance.scopeGetter, { video?.getContentRecommendations(StatePlatform.instance.getContentClient(it)) }) + private val _taskLoadRecommendations = TaskHandler?>(StateApp.instance.scopeGetter, { + video?.let { v -> + if (v is VideoLocal) { + StatePlatform.instance.getContentRecommendations(v.url) + } else { + video?.getContentRecommendations(StatePlatform.instance.getContentClient(v.url)) + } + } + }) .success { setRecommendations(it?.getResults()?.filter { it is IPlatformVideo }?.map { it as IPlatformVideo }, "No recommendations found") } .exception { setRecommendations(null, it.message) diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt index 420a3721..71aa6d6f 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt @@ -10,6 +10,8 @@ import com.futo.platformplayer.api.media.platforms.js.SourceAuth import com.futo.platformplayer.api.media.platforms.js.SourceCaptchaData import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig import com.futo.platformplayer.api.media.platforms.js.SourcePluginDescriptor +import com.futo.platformplayer.fragment.mainactivity.main.SourceDetailFragment +import com.futo.platformplayer.fragment.mainactivity.main.SourceDetailFragment.Companion import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.ImageVariable import com.futo.platformplayer.stores.FragmentedStorage @@ -128,7 +130,15 @@ class StatePlugins { return false; LoginActivity.showLogin(context, config) { - StatePlugins.instance.setPluginAuth(config.id, it); + try { + StatePlugins.instance.setPluginAuth(config.id, it); + } catch (e: Throwable) { + StateApp.instance.scopeOrNull?.launch(Dispatchers.Main) { + UIDialogs.showGeneralErrorDialog(context, "Failed to set plugin authentication (loginPlugin)", e) + } + Logger.e(SourceDetailFragment.TAG, "Failed to set plugin authentication (loginPlugin)", e) + return@showLogin + } StateApp.instance.scope.launch(Dispatchers.IO) { StatePlatform.instance.reloadClient(context, id); diff --git a/app/src/main/res/layout/video_player_ui.xml b/app/src/main/res/layout/video_player_ui.xml index 32a9f5cf..e849b942 100644 --- a/app/src/main/res/layout/video_player_ui.xml +++ b/app/src/main/res/layout/video_player_ui.xml @@ -29,6 +29,14 @@ android:layout_height="wrap_content" app:layout_constraintTop_toTopOf="parent" app:layout_constraintRight_toRightOf="parent"> + - - + - - Allow app to flip into reverse portrait Rotation zone Specify the sensitivity of rotation zones (decrease to make less sensitive) + Stability threshold time + Specify the duration the orientation needs to be the same to trigger a rotation Prefer Webm Video Codecs If player should prefer Webm codecs (vp9/opus) over mp4 codecs (h264/AAC), may result in worse compatibility. + Full auto rotate lock + Prevent any rotation while rotation lock is engaged (even flipping between landscape and landscape reverse). Prefer Webm Audio Codecs If player should prefer Webm codecs (opus) over mp4 codecs (AAC), may result in worse compatibility. Allow video under cutout @@ -966,4 +970,12 @@ 30 45 + + 100 + 500 + 750 + 1000 + 1500 + 2000 + \ No newline at end of file