rotation fixes

This commit is contained in:
Kai 2024-09-10 09:36:59 -05:00
commit cb9cecfa5d
No known key found for this signature in database
16 changed files with 234 additions and 348 deletions

View file

@ -51,7 +51,6 @@
android:name=".activities.MainActivity" android:name=".activities.MainActivity"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout" android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
android:exported="true" android:exported="true"
android:screenOrientation="sensorPortrait"
android:theme="@style/Theme.FutoVideo.NoActionBar" android:theme="@style/Theme.FutoVideo.NoActionBar"
android:launchMode="singleTask" android:launchMode="singleTask"
android:resizeableActivity="true" android:resizeableActivity="true"
@ -153,27 +152,21 @@
<activity <activity
android:name=".activities.SettingsActivity" android:name=".activities.SettingsActivity"
android:screenOrientation="sensorPortrait"
android:theme="@style/Theme.FutoVideo.NoActionBar" /> android:theme="@style/Theme.FutoVideo.NoActionBar" />
<activity <activity
android:name=".activities.DeveloperActivity" android:name=".activities.DeveloperActivity"
android:screenOrientation="sensorPortrait"
android:theme="@style/Theme.FutoVideo.NoActionBar" /> android:theme="@style/Theme.FutoVideo.NoActionBar" />
<activity <activity
android:name=".activities.ExceptionActivity" android:name=".activities.ExceptionActivity"
android:screenOrientation="sensorPortrait"
android:theme="@style/Theme.FutoVideo.NoActionBar" /> android:theme="@style/Theme.FutoVideo.NoActionBar" />
<activity <activity
android:name=".activities.CaptchaActivity" android:name=".activities.CaptchaActivity"
android:screenOrientation="sensorPortrait"
android:theme="@style/Theme.FutoVideo.NoActionBar" /> android:theme="@style/Theme.FutoVideo.NoActionBar" />
<activity <activity
android:name=".activities.LoginActivity" android:name=".activities.LoginActivity"
android:screenOrientation="sensorPortrait"
android:theme="@style/Theme.FutoVideo.NoActionBar" /> android:theme="@style/Theme.FutoVideo.NoActionBar" />
<activity <activity
android:name=".activities.AddSourceActivity" android:name=".activities.AddSourceActivity"
android:screenOrientation="sensorPortrait"
android:exported="true" android:exported="true"
android:theme="@style/Theme.FutoVideo.NoActionBar"> android:theme="@style/Theme.FutoVideo.NoActionBar">
<intent-filter> <intent-filter>
@ -187,44 +180,34 @@
</activity> </activity>
<activity <activity
android:name=".activities.AddSourceOptionsActivity" android:name=".activities.AddSourceOptionsActivity"
android:screenOrientation="sensorPortrait"
android:theme="@style/Theme.FutoVideo.NoActionBar" /> android:theme="@style/Theme.FutoVideo.NoActionBar" />
<activity <activity
android:name=".activities.PolycentricHomeActivity" android:name=".activities.PolycentricHomeActivity"
android:screenOrientation="sensorPortrait"
android:theme="@style/Theme.FutoVideo.NoActionBar" /> android:theme="@style/Theme.FutoVideo.NoActionBar" />
<activity <activity
android:name=".activities.PolycentricBackupActivity" android:name=".activities.PolycentricBackupActivity"
android:screenOrientation="sensorPortrait"
android:theme="@style/Theme.FutoVideo.NoActionBar" /> android:theme="@style/Theme.FutoVideo.NoActionBar" />
<activity <activity
android:name=".activities.PolycentricCreateProfileActivity" android:name=".activities.PolycentricCreateProfileActivity"
android:screenOrientation="sensorPortrait"
android:theme="@style/Theme.FutoVideo.NoActionBar" /> android:theme="@style/Theme.FutoVideo.NoActionBar" />
<activity <activity
android:name=".activities.PolycentricProfileActivity" android:name=".activities.PolycentricProfileActivity"
android:screenOrientation="sensorPortrait"
android:theme="@style/Theme.FutoVideo.NoActionBar" /> android:theme="@style/Theme.FutoVideo.NoActionBar" />
<activity <activity
android:name=".activities.PolycentricWhyActivity" android:name=".activities.PolycentricWhyActivity"
android:screenOrientation="sensorPortrait"
android:theme="@style/Theme.FutoVideo.NoActionBar" /> android:theme="@style/Theme.FutoVideo.NoActionBar" />
<activity <activity
android:name=".activities.PolycentricImportProfileActivity" android:name=".activities.PolycentricImportProfileActivity"
android:screenOrientation="sensorPortrait"
android:theme="@style/Theme.FutoVideo.NoActionBar" /> android:theme="@style/Theme.FutoVideo.NoActionBar" />
<activity <activity
android:name=".activities.ManageTabsActivity" android:name=".activities.ManageTabsActivity"
android:screenOrientation="sensorPortrait"
android:theme="@style/Theme.FutoVideo.NoActionBar" /> android:theme="@style/Theme.FutoVideo.NoActionBar" />
<activity <activity
android:name=".activities.QRCaptureActivity" android:name=".activities.QRCaptureActivity"
android:screenOrientation="sensorPortrait"
android:theme="@style/Theme.FutoVideo.NoActionBar" /> android:theme="@style/Theme.FutoVideo.NoActionBar" />
<activity <activity
android:name=".activities.FCastGuideActivity" android:name=".activities.FCastGuideActivity"
android:screenOrientation="sensorPortrait"
android:theme="@style/Theme.FutoVideo.NoActionBar" /> android:theme="@style/Theme.FutoVideo.NoActionBar" />
</application> </application>
</manifest> </manifest>

View file

@ -2,11 +2,8 @@ package com.futo.platformplayer
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.content.Context.POWER_SERVICE
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build
import android.os.PowerManager
import android.webkit.CookieManager import android.webkit.CookieManager
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.futo.platformplayer.activities.MainActivity import com.futo.platformplayer.activities.MainActivity
@ -26,7 +23,6 @@ import com.futo.platformplayer.states.StateBackup
import com.futo.platformplayer.states.StateCache import com.futo.platformplayer.states.StateCache
import com.futo.platformplayer.states.StateMeta import com.futo.platformplayer.states.StateMeta
import com.futo.platformplayer.states.StatePayment import com.futo.platformplayer.states.StatePayment
import com.futo.platformplayer.states.StatePlayer
import com.futo.platformplayer.states.StatePolycentric import com.futo.platformplayer.states.StatePolycentric
import com.futo.platformplayer.states.StateUpdate import com.futo.platformplayer.states.StateUpdate
import com.futo.platformplayer.stores.FragmentedStorage import com.futo.platformplayer.stores.FragmentedStorage
@ -36,9 +32,7 @@ import com.futo.platformplayer.views.fields.DropdownFieldOptionsId
import com.futo.platformplayer.views.fields.FieldForm import com.futo.platformplayer.views.fields.FieldForm
import com.futo.platformplayer.views.fields.FormField import com.futo.platformplayer.views.fields.FormField
import com.futo.platformplayer.views.fields.FormFieldButton import com.futo.platformplayer.views.fields.FormFieldButton
import com.futo.platformplayer.views.fields.FormFieldWarning
import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuItem import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuItem
import com.stripe.android.customersheet.injection.CustomerSheetViewModelModule_Companion_ContextFactory.context
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -417,8 +411,6 @@ class Settings : FragmentedStorageFileJson() {
@DropdownFieldOptionsId(R.array.system_enabled_disabled_array) @DropdownFieldOptionsId(R.array.system_enabled_disabled_array)
var autoRotate: Int = 2; var autoRotate: Int = 2;
fun isAutoRotate() = (autoRotate == 1 && !StatePlayer.instance.rotationLock) || (autoRotate == 2 && StateApp.instance.getCurrentSystemAutoRotate() && !StatePlayer.instance.rotationLock);
@FormField(R.string.background_behavior, FieldForm.DROPDOWN, -1, 7) @FormField(R.string.background_behavior, FieldForm.DROPDOWN, -1, 7)
@DropdownFieldOptionsId(R.array.player_background_behavior) @DropdownFieldOptionsId(R.array.player_background_behavior)
var backgroundPlay: Int = 2; var backgroundPlay: Int = 2;
@ -857,10 +849,6 @@ class Settings : FragmentedStorageFileJson() {
var other = Other(); var other = Other();
@Serializable @Serializable
class Other { class Other {
@FormField(R.string.bypass_rotation_prevention, FieldForm.TOGGLE, R.string.bypass_rotation_prevention_description, 1)
@FormFieldWarning(R.string.bypass_rotation_prevention_warning)
var bypassRotationPrevention: Boolean = false;
@FormField(R.string.enable_polycentric, FieldForm.TOGGLE, R.string.can_be_disabled_when_you_are_experiencing_issues, 1) @FormField(R.string.enable_polycentric, FieldForm.TOGGLE, R.string.can_be_disabled_when_you_are_experiencing_issues, 1)
var polycentricEnabled: Boolean = true; var polycentricEnabled: Boolean = true;
} }

View file

@ -1,72 +0,0 @@
package com.futo.platformplayer
import android.app.Activity
import android.content.pm.ActivityInfo
import android.hardware.SensorManager
import android.view.OrientationEventListener
import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.logging.Logger
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class SimpleOrientationListener(
private val activity: Activity,
private val lifecycleScope: CoroutineScope
) {
private var lastOrientation: Int = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
private var lastStableOrientation: Int = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
private val stabilityThresholdTime = 500L
val onOrientationChanged = Event1<Int>()
private val orientationListener = object : OrientationEventListener(activity, SensorManager.SENSOR_DELAY_UI) {
override fun onOrientationChanged(orientation: Int) {
//val rotationZone = 45
val rotationZone = when (Settings.instance.playback.rotationZone) {
0 -> 15
1 -> 30
2 -> 45
else -> 45
}
val newOrientation = when {
orientation in (90 - rotationZone)..(90 + rotationZone - 1) -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE
orientation in (180 - rotationZone)..(180 + rotationZone - 1) -> ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT
orientation in (270 - rotationZone)..(270 + rotationZone - 1) -> ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
orientation in (360 - rotationZone)..(360 + rotationZone - 1) || orientation in 0..(rotationZone - 1) -> ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
else -> lastOrientation
}
if (newOrientation != lastStableOrientation) {
lastStableOrientation = newOrientation
lifecycleScope.launch(Dispatchers.Main) {
try {
delay(stabilityThresholdTime)
if (newOrientation == lastStableOrientation) {
lastOrientation = newOrientation
onOrientationChanged.emit(newOrientation)
}
} catch (e: Throwable) {
Logger.i(TAG, "Failed to trigger onOrientationChanged", e)
}
}
}
}
}
init {
orientationListener.enable()
lastOrientation = activity.resources.configuration.orientation
}
fun stopListening() {
orientationListener.disable()
}
companion object {
private val TAG = "SimpleOrientationListener"
}
}

View file

@ -7,7 +7,6 @@ import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
import android.content.pm.PackageManager import android.content.pm.PackageManager
import android.content.res.Configuration import android.content.res.Configuration
import android.net.Uri import android.net.Uri
import android.net.wifi.WifiManager
import android.os.Bundle import android.os.Bundle
import android.os.StrictMode import android.os.StrictMode
import android.os.StrictMode.VmPolicy import android.os.StrictMode.VmPolicy
@ -515,6 +514,9 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
sharedPreferences.edit().putBoolean("IsFirstBoot", false).apply() sharedPreferences.edit().putBoolean("IsFirstBoot", false).apply()
} }
_fragVideoDetail.detectWindowSize()
_fragVideoDetail.updateOrientation()
} }
/* /*

View file

@ -7,6 +7,7 @@ import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -34,7 +35,7 @@ import kotlin.math.roundToInt
class MenuBottomBarFragment : MainActivityFragment() { class MenuBottomBarFragment : MainActivityFragment() {
private var _view: MenuBottomBarView? = null; private var _view: MenuBottomBarView? = null;
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val view = MenuBottomBarView(this, inflater); val view = MenuBottomBarView(this, inflater);
_view = view; _view = view;
return view; return view;
@ -56,7 +57,13 @@ class MenuBottomBarFragment : MainActivityFragment() {
return _view?.onBackPressed() ?: false; return _view?.onBackPressed() ?: false;
} }
@SuppressLint("ViewConstructor") override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
_view?.updateAllButtonVisibility()
}
@SuppressLint("ViewConstructor")
class MenuBottomBarView : LinearLayout { class MenuBottomBarView : LinearLayout {
private val _fragment: MenuBottomBarFragment; private val _fragment: MenuBottomBarFragment;
private val _inflater: LayoutInflater; private val _inflater: LayoutInflater;
@ -76,7 +83,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
private var _buttonsVisible = 0; private var _buttonsVisible = 0;
private var _subscriptionsVisible = true; private var _subscriptionsVisible = true;
var currentButtonDefinitions: List<ButtonDefinition>? = null; private var currentButtonDefinitions: List<ButtonDefinition>? = null;
constructor(fragment: MenuBottomBarFragment, inflater: LayoutInflater) : super(inflater.context) { constructor(fragment: MenuBottomBarFragment, inflater: LayoutInflater) : super(inflater.context) {
_fragment = fragment; _fragment = fragment;
@ -132,7 +139,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
val staggerFactor = 3.0f val staggerFactor = 3.0f
if (visible) { if (visible) {
moreOverlay.visibility = LinearLayout.VISIBLE moreOverlay.visibility = VISIBLE
val animations = arrayListOf<Animator>() val animations = arrayListOf<Animator>()
animations.add(ObjectAnimator.ofFloat(moreOverlayBackground, "alpha", 0.0f, 1.0f).setDuration(duration)) animations.add(ObjectAnimator.ofFloat(moreOverlayBackground, "alpha", 0.0f, 1.0f).setDuration(duration))
@ -161,7 +168,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
animatorSet.doOnEnd { animatorSet.doOnEnd {
_moreVisibleAnimating = false _moreVisibleAnimating = false
_moreVisible = false _moreVisible = false
moreOverlay.visibility = LinearLayout.INVISIBLE moreOverlay.visibility = INVISIBLE
} }
animatorSet.playTogether(animations) animatorSet.playTogether(animations)
animatorSet.start() animatorSet.start()
@ -178,7 +185,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
_layoutBottomBarButtons.removeAllViews(); _layoutBottomBarButtons.removeAllViews();
_layoutBottomBarButtons.addView(Space(context).apply { _layoutBottomBarButtons.addView(Space(context).apply {
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f) layoutParams = LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f)
}) })
for ((index, button) in buttons.withIndex()) { for ((index, button) in buttons.withIndex()) {
@ -192,7 +199,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
_layoutBottomBarButtons.addView(menuButton) _layoutBottomBarButtons.addView(menuButton)
if (index < buttonDefinitions.size - 1) { if (index < buttonDefinitions.size - 1) {
_layoutBottomBarButtons.addView(Space(context).apply { _layoutBottomBarButtons.addView(Space(context).apply {
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f) layoutParams = LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f)
}) })
} }
@ -200,7 +207,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
} }
_layoutBottomBarButtons.addView(Space(context).apply { _layoutBottomBarButtons.addView(Space(context).apply {
layoutParams = LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT, 1f) layoutParams = LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f)
}) })
} }
@ -251,9 +258,19 @@ class MenuBottomBarFragment : MainActivityFragment() {
button.updateActive(_fragment); button.updateActive(_fragment);
} }
override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig)
updateAllButtonVisibility()
}
fun updateAllButtonVisibility() { fun updateAllButtonVisibility() {
if(_moreVisible) {
setMoreVisible(false)
}
val defs = currentButtonDefinitions?.toMutableList() ?: return val defs = currentButtonDefinitions?.toMutableList() ?: return
val metrics = StateApp.instance.displayMetrics ?: resources.displayMetrics; val metrics = resources.displayMetrics
_buttonsVisible = floor(metrics.widthPixels.toDouble() / 65.dp(resources).toDouble()).roundToInt(); _buttonsVisible = floor(metrics.widthPixels.toDouble() / 65.dp(resources).toDouble()).roundToInt();
if (_buttonsVisible >= defs.size) { if (_buttonsVisible >= defs.size) {
updateBottomMenuButtons(defs.toMutableList(), false); updateBottomMenuButtons(defs.toMutableList(), false);

View file

@ -4,8 +4,8 @@ import android.content.Context
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.futo.platformplayer.R import com.futo.platformplayer.R
import com.futo.platformplayer.Settings import com.futo.platformplayer.Settings
import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.UIDialogs
@ -45,9 +45,7 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
private var _videoOptionsOverlay: SlideUpMenuOverlay? = null; private var _videoOptionsOverlay: SlideUpMenuOverlay? = null;
protected open val shouldShowTimeBar: Boolean get() = true protected open val shouldShowTimeBar: Boolean get() = true
constructor(fragment: TFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, LinearLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null) : super(fragment, inflater, cachedRecyclerData) { constructor(fragment: TFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, StaggeredGridLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null) : super(fragment, inflater, cachedRecyclerData)
}
override fun filterResults(results: List<IPlatformContent>): List<IPlatformContent> { override fun filterResults(results: List<IPlatformContent>): List<IPlatformContent> {
return results; return results;
@ -55,12 +53,12 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
override fun createAdapter(recyclerResults: RecyclerView, context: Context, dataset: ArrayList<IPlatformContent>): InsertedViewAdapterWithLoader<ContentPreviewViewHolder> { override fun createAdapter(recyclerResults: RecyclerView, context: Context, dataset: ArrayList<IPlatformContent>): InsertedViewAdapterWithLoader<ContentPreviewViewHolder> {
val player = StatePlayer.instance.getThumbnailPlayerOrCreate(context); val player = StatePlayer.instance.getThumbnailPlayerOrCreate(context);
player.modifyState("ThumbnailPlayer", { state -> state.muted = true }); player.modifyState("ThumbnailPlayer") { state -> state.muted = true };
_exoPlayer = player; _exoPlayer = player;
val v = LinearLayout(context).apply { val v = LinearLayout(context).apply {
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
orientation = LinearLayout.VERTICAL; orientation = VERTICAL;
}; };
headerView = v; headerView = v;
@ -142,7 +140,10 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
val newQueue = listOf(content) + recyclerData.results val newQueue = listOf(content) + recyclerData.results
.filterIsInstance<IPlatformVideo>() .filterIsInstance<IPlatformVideo>()
.filter { it != content }; .filter { it != content };
StatePlayer.instance.setQueue(newQueue, StatePlayer.TYPE_QUEUE, "Feed Queue", true, false); StatePlayer.instance.setQueue(newQueue, StatePlayer.TYPE_QUEUE, "Feed Queue",
focus = true,
shuffle = false
);
}) })
); );
} }
@ -160,21 +161,27 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
adapter.onLongPress.remove(this); adapter.onLongPress.remove(this);
} }
override fun onRestoreCachedData(cachedData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, LinearLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>) { override fun onRestoreCachedData(cachedData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, StaggeredGridLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>) {
super.onRestoreCachedData(cachedData) super.onRestoreCachedData(cachedData)
val v = LinearLayout(context).apply { val v = LinearLayout(context).apply {
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
orientation = LinearLayout.VERTICAL; orientation = VERTICAL;
}; };
headerView = v; headerView = v;
cachedData.adapter.viewsToPrepend.add(v); cachedData.adapter.viewsToPrepend.add(v);
(cachedData.adapter as PreviewContentListAdapter?)?.let { attachAdapterEvents(it) }; (cachedData.adapter as PreviewContentListAdapter?)?.let { attachAdapterEvents(it) };
} }
override fun createLayoutManager(recyclerResults: RecyclerView, context: Context): LinearLayoutManager { override fun createLayoutManager(
val llmResults = LinearLayoutManager(context); recyclerResults: RecyclerView,
llmResults.orientation = LinearLayoutManager.VERTICAL; context: Context
return llmResults; ): StaggeredGridLayoutManager {
val glmResults =
StaggeredGridLayoutManager(
if (resources.configuration.screenWidthDp >= resources.getDimension(R.dimen.landscape_threshold)) 2 else 1,
StaggeredGridLayoutManager.VERTICAL
);
return glmResults
} }
override fun onScrollStateChanged(newState: Int) { override fun onScrollStateChanged(newState: Int) {
@ -220,8 +227,8 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
if(feedStyle == FeedStyle.THUMBNAIL) if(feedStyle == FeedStyle.THUMBNAIL)
return; return;
val firstVisible = recyclerData.layoutManager.findFirstVisibleItemPosition(); val firstVisible = recyclerData.layoutManager.findFirstVisibleItemPositions(IntArray(recyclerData.layoutManager.spanCount))[0]
val lastVisible = recyclerData.layoutManager.findLastVisibleItemPosition(); val lastVisible = recyclerData.layoutManager.findLastVisibleItemPositions(IntArray(recyclerData.layoutManager.spanCount))[0]
val itemsVisible = lastVisible - firstVisible + 1; val itemsVisible = lastVisible - firstVisible + 1;
val autoPlayIndex = (firstVisible + floor(itemsVisible / 2.0 + 0.49).toInt()).coerceAtLeast(0).coerceAtMost((recyclerData.results.size - 1)); val autoPlayIndex = (firstVisible + floor(itemsVisible / 2.0 + 0.49).toInt()).coerceAtLeast(0).coerceAtMost((recyclerData.results.size - 1));
@ -241,7 +248,7 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
(recyclerData.adapter as PreviewContentListAdapter?)?.preview(viewHolder.childViewHolder) (recyclerData.adapter as PreviewContentListAdapter?)?.preview(viewHolder.childViewHolder)
} }
fun stopVideo() { private fun stopVideo() {
//TODO: Is this still necessary? //TODO: Is this still necessary?
(recyclerData.adapter as PreviewContentListAdapter?)?.stopPreview(); (recyclerData.adapter as PreviewContentListAdapter?)?.stopPreview();
} }
@ -269,6 +276,6 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
} }
companion object { companion object {
private val TAG = "ContentFeedView"; private const val TAG = "ContentFeedView";
} }
} }

View file

@ -3,13 +3,9 @@ package com.futo.platformplayer.fragment.mainactivity.main
import android.content.Context import android.content.Context
import android.util.TypedValue import android.util.TypedValue
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup.MarginLayoutParams
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.futo.platformplayer.* import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.futo.platformplayer.api.media.models.PlatformAuthorLink import com.futo.platformplayer.api.media.models.PlatformAuthorLink
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
import com.futo.platformplayer.api.media.structures.* import com.futo.platformplayer.api.media.structures.*
import com.futo.platformplayer.views.FeedStyle import com.futo.platformplayer.views.FeedStyle
import com.futo.platformplayer.views.adapters.* import com.futo.platformplayer.views.adapters.*
@ -18,9 +14,7 @@ import com.futo.platformplayer.views.adapters.viewholders.CreatorViewHolder
abstract class CreatorFeedView<TFragment> : FeedView<TFragment, PlatformAuthorLink, PlatformAuthorLink, IPager<PlatformAuthorLink>, CreatorViewHolder> where TFragment : MainFragment { abstract class CreatorFeedView<TFragment> : FeedView<TFragment, PlatformAuthorLink, PlatformAuthorLink, IPager<PlatformAuthorLink>, CreatorViewHolder> where TFragment : MainFragment {
override val feedStyle: FeedStyle = FeedStyle.THUMBNAIL; //R.layout.list_creator; override val feedStyle: FeedStyle = FeedStyle.THUMBNAIL; //R.layout.list_creator;
constructor(fragment: TFragment, inflater: LayoutInflater) : super(fragment, inflater) { constructor(fragment: TFragment, inflater: LayoutInflater) : super(fragment, inflater)
}
override fun createAdapter(recyclerResults: RecyclerView, context: Context, dataset: ArrayList<PlatformAuthorLink>): InsertedViewAdapterWithLoader<CreatorViewHolder> { override fun createAdapter(recyclerResults: RecyclerView, context: Context, dataset: ArrayList<PlatformAuthorLink>): InsertedViewAdapterWithLoader<CreatorViewHolder> {
return InsertedViewAdapterWithLoader(context, arrayListOf(), arrayListOf(), return InsertedViewAdapterWithLoader(context, arrayListOf(), arrayListOf(),
@ -34,18 +28,24 @@ abstract class CreatorFeedView<TFragment> : FeedView<TFragment, PlatformAuthorLi
); );
} }
override fun createLayoutManager(recyclerResults: RecyclerView, context: Context): LinearLayoutManager { override fun createLayoutManager(
val glmResults = GridLayoutManager(context, 2); recyclerResults: RecyclerView,
glmResults.orientation = LinearLayoutManager.VERTICAL; context: Context
): StaggeredGridLayoutManager {
val glmResults = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
_swipeRefresh.layoutParams = (_swipeRefresh.layoutParams as MarginLayoutParams?)?.apply { _swipeRefresh.layoutParams = (_swipeRefresh.layoutParams as MarginLayoutParams?)?.apply {
rightMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 8.0f, context.resources.displayMetrics).toInt(); rightMargin = TypedValue.applyDimension(
}; TypedValue.COMPLEX_UNIT_DIP,
8.0f,
context.resources.displayMetrics
).toInt()
}
return glmResults; return glmResults
} }
companion object { companion object {
private val TAG = "CreatorFeedView"; private const val TAG = "CreatorFeedView";
} }
} }

View file

@ -1,15 +1,16 @@
package com.futo.platformplayer.fragment.mainactivity.main package com.futo.platformplayer.fragment.mainactivity.main
import android.content.Context import android.content.Context
import android.content.res.Configuration
import android.graphics.Color import android.graphics.Color
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.widget.* import android.widget.*
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.LayoutManager import androidx.recyclerview.widget.RecyclerView.LayoutManager
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import com.futo.platformplayer.* import com.futo.platformplayer.*
import com.futo.platformplayer.api.media.IPlatformClient import com.futo.platformplayer.api.media.IPlatformClient
@ -33,7 +34,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
protected val _recyclerResults: RecyclerView; protected val _recyclerResults: RecyclerView;
protected val _overlayContainer: FrameLayout; protected val _overlayContainer: FrameLayout;
protected val _swipeRefresh: SwipeRefreshLayout; protected val _swipeRefresh: SwipeRefreshLayout;
private val _progress_bar: ProgressBar; private val _progressBar: ProgressBar;
private val _spinnerSortBy: Spinner; private val _spinnerSortBy: Spinner;
private val _containerSortBy: LinearLayout; private val _containerSortBy: LinearLayout;
private val _tagsView: TagsView; private val _tagsView: TagsView;
@ -44,7 +45,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
private var _loading: Boolean = true; private var _loading: Boolean = true;
private val _pager_lock = Object(); private val _pagerLock = Object();
private var _cache: ItemCache<TResult>? = null; private var _cache: ItemCache<TResult>? = null;
open val visibleThreshold = 15; open val visibleThreshold = 15;
@ -58,21 +59,21 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
private var _activeTags: List<String>? = null; private var _activeTags: List<String>? = null;
private var _nextPageHandler: TaskHandler<TPager, List<TResult>>; private var _nextPageHandler: TaskHandler<TPager, List<TResult>>;
val recyclerData: RecyclerData<InsertedViewAdapterWithLoader<TViewHolder>, LinearLayoutManager, TPager, TResult, TConverted, InsertedViewHolder<TViewHolder>>; val recyclerData: RecyclerData<InsertedViewAdapterWithLoader<TViewHolder>, StaggeredGridLayoutManager, TPager, TResult, TConverted, InsertedViewHolder<TViewHolder>>;
val fragment: TFragment; val fragment: TFragment;
private val _scrollListener: RecyclerView.OnScrollListener; private val _scrollListener: RecyclerView.OnScrollListener;
private var _automaticNextPageCounter = 0; private var _automaticNextPageCounter = 0;
constructor(fragment: TFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<TViewHolder>, LinearLayoutManager, TPager, TResult, TConverted, InsertedViewHolder<TViewHolder>>? = null) : super(inflater.context) { constructor(fragment: TFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<TViewHolder>, StaggeredGridLayoutManager, TPager, TResult, TConverted, InsertedViewHolder<TViewHolder>>? = null) : super(inflater.context) {
this.fragment = fragment; this.fragment = fragment;
inflater.inflate(R.layout.fragment_feed, this); inflater.inflate(R.layout.fragment_feed, this);
_textCentered = findViewById(R.id.text_centered); _textCentered = findViewById(R.id.text_centered);
_emptyPagerContainer = findViewById(R.id.empty_pager_container); _emptyPagerContainer = findViewById(R.id.empty_pager_container);
_progress_bar = findViewById(R.id.progress_bar); _progressBar = findViewById(R.id.progress_bar);
_progress_bar.inactiveColor = Color.TRANSPARENT; _progressBar.inactiveColor = Color.TRANSPARENT;
_swipeRefresh = findViewById(R.id.swipe_refresh); _swipeRefresh = findViewById(R.id.swipe_refresh);
val recyclerResults: RecyclerView = findViewById(R.id.list_results); val recyclerResults: RecyclerView = findViewById(R.id.list_results);
@ -158,7 +159,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
super.onScrolled(recyclerView, dx, dy); super.onScrolled(recyclerView, dx, dy);
val visibleItemCount = _recyclerResults.childCount; val visibleItemCount = _recyclerResults.childCount;
val firstVisibleItem = recyclerData.layoutManager.findFirstVisibleItemPosition(); val firstVisibleItem = recyclerData.layoutManager.findFirstVisibleItemPositions(IntArray(recyclerData.layoutManager.spanCount))[0]
//Logger.i(TAG, "onScrolled loadNextPage visibleItemCount=$visibleItemCount firstVisibleItem=$visibleItemCount") //Logger.i(TAG, "onScrolled loadNextPage visibleItemCount=$visibleItemCount firstVisibleItem=$visibleItemCount")
if (!_loading && firstVisibleItem + visibleItemCount + visibleThreshold >= recyclerData.results.size && firstVisibleItem > 0) { if (!_loading && firstVisibleItem + visibleItemCount + visibleThreshold >= recyclerData.results.size && firstVisibleItem > 0) {
@ -174,7 +175,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
private fun ensureEnoughContentVisible(filteredResults: List<TConverted>) { private fun ensureEnoughContentVisible(filteredResults: List<TConverted>) {
val canScroll = if (recyclerData.results.isEmpty()) false else { val canScroll = if (recyclerData.results.isEmpty()) false else {
val layoutManager = recyclerData.layoutManager val layoutManager = recyclerData.layoutManager
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition() val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPositions(IntArray(recyclerData.layoutManager.spanCount))[0]
if (firstVisibleItemPosition != RecyclerView.NO_POSITION) { if (firstVisibleItemPosition != RecyclerView.NO_POSITION) {
val firstVisibleView = layoutManager.findViewByPosition(firstVisibleItemPosition) val firstVisibleView = layoutManager.findViewByPosition(firstVisibleItemPosition)
@ -226,7 +227,23 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
} }
} }
private fun updateSpanCount() {
if (resources.configuration.screenWidthDp >= resources.getDimension(R.dimen.landscape_threshold) && recyclerData.layoutManager.spanCount != 2) {
recyclerData.layoutManager.spanCount = 2
} else if (resources.configuration.screenWidthDp < resources.getDimension(R.dimen.landscape_threshold) && recyclerData.layoutManager.spanCount != 1) {
recyclerData.layoutManager.spanCount = 1
}
}
override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig)
updateSpanCount()
}
fun onResume() { fun onResume() {
updateSpanCount()
//Reload the pager if the plugin was killed //Reload the pager if the plugin was killed
val pager = recyclerData.pager; val pager = recyclerData.pager;
if((pager is MultiPager<*> && pager.findPager { it is JSPager<*> && !it.isAvailable } != null) || if((pager is MultiPager<*> && pager.findPager { it is JSPager<*> && !it.isAvailable } != null) ||
@ -252,7 +269,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
protected open fun setActiveTags(activeTags: List<String>?) { protected open fun setActiveTags(activeTags: List<String>?) {
_activeTags = activeTags; _activeTags = activeTags;
if (activeTags != null && activeTags.isNotEmpty()) { if (!activeTags.isNullOrEmpty()) {
_tagsView.setTags(activeTags); _tagsView.setTags(activeTags);
_tagsView.visibility = View.VISIBLE; _tagsView.visibility = View.VISIBLE;
} else { } else {
@ -262,7 +279,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
protected open fun setSortByOptions(options: List<String>?) { protected open fun setSortByOptions(options: List<String>?) {
_sortByOptions = options; _sortByOptions = options;
if (options != null && options.isNotEmpty()) { if (!options.isNullOrEmpty()) {
val allOptions = arrayListOf<String>(); val allOptions = arrayListOf<String>();
allOptions.add("Default"); allOptions.add("Default");
allOptions.addAll(options); allOptions.addAll(options);
@ -277,19 +294,19 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
} }
} }
protected abstract fun createAdapter(recyclerResults: RecyclerView, context: Context, dataset: ArrayList<TConverted>): InsertedViewAdapterWithLoader<TViewHolder>; protected abstract fun createAdapter(recyclerResults: RecyclerView, context: Context, dataset: ArrayList<TConverted>): InsertedViewAdapterWithLoader<TViewHolder>;
protected abstract fun createLayoutManager(recyclerResults: RecyclerView, context: Context): LinearLayoutManager; protected abstract fun createLayoutManager(recyclerResults: RecyclerView, context: Context): StaggeredGridLayoutManager;
protected open fun onRestoreCachedData(cachedData: RecyclerData<InsertedViewAdapterWithLoader<TViewHolder>, LinearLayoutManager, TPager, TResult, TConverted, InsertedViewHolder<TViewHolder>>) {} protected open fun onRestoreCachedData(cachedData: RecyclerData<InsertedViewAdapterWithLoader<TViewHolder>, StaggeredGridLayoutManager, TPager, TResult, TConverted, InsertedViewHolder<TViewHolder>>) {}
protected fun setProgress(fin: Int, total: Int) { protected fun setProgress(fin: Int, total: Int) {
val progress = (fin.toFloat() / total); val progress = (fin.toFloat() / total);
_progress_bar.progress = progress; _progressBar.progress = progress;
if(progress > 0 && progress < 1) if(progress > 0 && progress < 1)
{ {
if(_progress_bar.height == 0) if(_progressBar.height == 0)
_progress_bar.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 5); _progressBar.layoutParams = LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 5);
} }
else if(_progress_bar.height > 0) { else if(_progressBar.height > 0) {
_progress_bar.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0); _progressBar.layoutParams = LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0);
} }
} }
@ -345,7 +362,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
//insertPagerResults(_cache!!.cachePager.getResults(), false); //insertPagerResults(_cache!!.cachePager.getResults(), false);
} }
fun setPager(pager: TPager, cache: ItemCache<TResult>? = null) { fun setPager(pager: TPager, cache: ItemCache<TResult>? = null) {
synchronized(_pager_lock) { synchronized(_pagerLock) {
detachParentPagerEvents(); detachParentPagerEvents();
detachPagerEvents(); detachPagerEvents();
@ -425,7 +442,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
val p = recyclerData.pager; val p = recyclerData.pager;
if(p is IReplacerPager<*>) { if(p is IReplacerPager<*>) {
p.onReplaced.subscribe(this) { _, newItem -> p.onReplaced.subscribe(this) { _, newItem ->
synchronized(_pager_lock) { synchronized(_pagerLock) {
val filtered = filterResults(listOf(newItem as TResult)); val filtered = filterResults(listOf(newItem as TResult));
if(filtered.isEmpty()) if(filtered.isEmpty())
return@subscribe; return@subscribe;
@ -443,7 +460,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
var _lastNextPage = false; var _lastNextPage = false;
private fun loadNextPage() { private fun loadNextPage() {
synchronized(_pager_lock) { synchronized(_pagerLock) {
val pager: TPager = recyclerData.pager ?: return; val pager: TPager = recyclerData.pager ?: return;
val hasMorePages = pager.hasMorePages(); val hasMorePages = pager.hasMorePages();
Logger.i(TAG, "loadNextPage() hasMorePages=$hasMorePages, page size=${pager.getResults().size}"); Logger.i(TAG, "loadNextPage() hasMorePages=$hasMorePages, page size=${pager.getResults().size}");
@ -468,7 +485,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
} }
companion object { companion object {
private val TAG = "FeedView"; private const val TAG = "FeedView";
} }
abstract class ItemCache<TResult>(val cachePager: IPager<TResult>) { abstract class ItemCache<TResult>(val cachePager: IPager<TResult>) {

View file

@ -6,7 +6,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.futo.platformplayer.* import com.futo.platformplayer.*
import com.futo.platformplayer.activities.MainActivity import com.futo.platformplayer.activities.MainActivity
import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.api.media.models.contents.IPlatformContent
@ -18,13 +18,9 @@ import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException
import com.futo.platformplayer.engine.exceptions.ScriptExecutionException import com.futo.platformplayer.engine.exceptions.ScriptExecutionException
import com.futo.platformplayer.engine.exceptions.ScriptImplementationException import com.futo.platformplayer.engine.exceptions.ScriptImplementationException
import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.SearchType
import com.futo.platformplayer.states.AnnouncementType
import com.futo.platformplayer.states.StateAnnouncement
import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.states.StateMeta import com.futo.platformplayer.states.StateMeta
import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.states.StateSubscriptions
import com.futo.platformplayer.views.FeedStyle import com.futo.platformplayer.views.FeedStyle
import com.futo.platformplayer.views.NoResultsView import com.futo.platformplayer.views.NoResultsView
import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder
@ -32,11 +28,8 @@ import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader
import com.futo.platformplayer.views.adapters.InsertedViewHolder import com.futo.platformplayer.views.adapters.InsertedViewHolder
import com.futo.platformplayer.views.announcements.AnnouncementView import com.futo.platformplayer.views.announcements.AnnouncementView
import com.futo.platformplayer.views.buttons.BigButton import com.futo.platformplayer.views.buttons.BigButton
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import java.time.OffsetDateTime import java.time.OffsetDateTime
import java.util.UUID
class HomeFragment : MainFragment() { class HomeFragment : MainFragment() {
override val isMainView : Boolean = true; override val isMainView : Boolean = true;
@ -44,7 +37,7 @@ class HomeFragment : MainFragment() {
override val hasBottomBar: Boolean get() = true; override val hasBottomBar: Boolean get() = true;
private var _view: HomeView? = null; private var _view: HomeView? = null;
private var _cachedRecyclerData: FeedView.RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, LinearLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null; private var _cachedRecyclerData: FeedView.RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, StaggeredGridLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null;
fun reloadFeed() { fun reloadFeed() {
_view?.reloadFeed() _view?.reloadFeed()
@ -101,15 +94,14 @@ class HomeFragment : MainFragment() {
class HomeView : ContentFeedView<HomeFragment> { class HomeView : ContentFeedView<HomeFragment> {
override val feedStyle: FeedStyle get() = Settings.instance.home.getHomeFeedStyle(); override val feedStyle: FeedStyle get() = Settings.instance.home.getHomeFeedStyle();
private var _announcementsView: AnnouncementView; private var _announcementsView: AnnouncementView = AnnouncementView(context, null).apply {
headerView.addView(this);
};
private val _taskGetPager: TaskHandler<Boolean, IPager<IPlatformContent>>; private val _taskGetPager: TaskHandler<Boolean, IPager<IPlatformContent>>;
override val shouldShowTimeBar: Boolean get() = Settings.instance.home.progressBar override val shouldShowTimeBar: Boolean get() = Settings.instance.home.progressBar
constructor(fragment: HomeFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, LinearLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null) : super(fragment, inflater, cachedRecyclerData) { constructor(fragment: HomeFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, StaggeredGridLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null) : super(fragment, inflater, cachedRecyclerData) {;
_announcementsView = AnnouncementView(context, null).apply {
headerView.addView(this);
};
_taskGetPager = TaskHandler<Boolean, IPager<IPlatformContent>>({ fragment.lifecycleScope }, { _taskGetPager = TaskHandler<Boolean, IPager<IPlatformContent>>({ fragment.lifecycleScope }, {
StatePlatform.instance.getHomeRefresh(fragment.lifecycleScope) StatePlatform.instance.getHomeRefresh(fragment.lifecycleScope)
@ -174,7 +166,7 @@ class HomeFragment : MainFragment() {
loadResults(); loadResults();
} }
override fun getEmptyPagerView(): View? { override fun getEmptyPagerView(): View {
val dp10 = 10.dp(resources); val dp10 = 10.dp(resources);
val dp30 = 30.dp(resources); val dp30 = 30.dp(resources);
@ -206,8 +198,7 @@ class HomeFragment : MainFragment() {
listOf(BigButton(context, "Sources", "Go to the sources tab", R.drawable.ic_creators) { listOf(BigButton(context, "Sources", "Go to the sources tab", R.drawable.ic_creators) {
fragment.navigate<SourcesFragment>(); fragment.navigate<SourcesFragment>();
}.withMargin(dp10, dp30)) }.withMargin(dp10, dp30))
); )
return null;
} }
override fun reload() { override fun reload() {
@ -227,7 +218,7 @@ class HomeFragment : MainFragment() {
//StateAnnouncement.instance.registerAnnouncement(UUID.randomUUID().toString(), context.getString(R.string.no_home_available), context.getString(R.string.no_home_page_is_available_please_check_if_you_are_connected_to_the_internet_and_refresh), AnnouncementType.SESSION); //StateAnnouncement.instance.registerAnnouncement(UUID.randomUUID().toString(), context.getString(R.string.no_home_available), context.getString(R.string.no_home_page_is_available_please_check_if_you_are_connected_to_the_internet_and_refresh), AnnouncementType.SESSION);
} }
Logger.i(TAG, "Got new home pager ${pager}"); Logger.i(TAG, "Got new home pager $pager");
finishRefreshLayoutLoader(); finishRefreshLayoutLoader();
setLoading(false); setLoading(false);
setPager(pager); setPager(pager);
@ -237,7 +228,7 @@ class HomeFragment : MainFragment() {
} }
companion object { companion object {
val TAG = "HomeFragment"; const val TAG = "HomeFragment";
fun newInstance() = HomeFragment().apply {} fun newInstance() = HomeFragment().apply {}
} }

View file

@ -7,10 +7,9 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.futo.platformplayer.* import com.futo.platformplayer.*
import com.futo.platformplayer.activities.MainActivity import com.futo.platformplayer.activities.MainActivity
import com.futo.platformplayer.api.media.IPlatformClient
import com.futo.platformplayer.api.media.models.contents.ContentType import com.futo.platformplayer.api.media.models.contents.ContentType
import com.futo.platformplayer.api.media.models.contents.IPlatformContent import com.futo.platformplayer.api.media.models.contents.IPlatformContent
import com.futo.platformplayer.api.media.models.video.IPlatformVideo import com.futo.platformplayer.api.media.models.video.IPlatformVideo
@ -46,7 +45,6 @@ import kotlinx.coroutines.withContext
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import java.nio.channels.Channel
import java.time.OffsetDateTime import java.time.OffsetDateTime
import kotlin.system.measureTimeMillis import kotlin.system.measureTimeMillis
@ -57,7 +55,7 @@ class SubscriptionsFeedFragment : MainFragment() {
private var _view: SubscriptionsFeedView? = null; private var _view: SubscriptionsFeedView? = null;
private var _group: SubscriptionGroup? = null; private var _group: SubscriptionGroup? = null;
private var _cachedRecyclerData: FeedView.RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, LinearLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null; private var _cachedRecyclerData: FeedView.RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, StaggeredGridLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null;
override fun onShownWithView(parameter: Any?, isBack: Boolean) { override fun onShownWithView(parameter: Any?, isBack: Boolean) {
super.onShownWithView(parameter, isBack); super.onShownWithView(parameter, isBack);
@ -110,7 +108,7 @@ class SubscriptionsFeedFragment : MainFragment() {
var subGroup: SubscriptionGroup? = null; var subGroup: SubscriptionGroup? = null;
constructor(fragment: SubscriptionsFeedFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, LinearLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null) : super(fragment, inflater, cachedRecyclerData) { constructor(fragment: SubscriptionsFeedFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, StaggeredGridLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null) : super(fragment, inflater, cachedRecyclerData) {
Logger.i(TAG, "SubscriptionsFeedFragment constructor()"); Logger.i(TAG, "SubscriptionsFeedFragment constructor()");
StateSubscriptions.instance.global.onUpdateProgress.subscribe(this) { progress, total -> StateSubscriptions.instance.global.onUpdateProgress.subscribe(this) { progress, total ->
}; };
@ -214,7 +212,7 @@ class SubscriptionsFeedFragment : MainFragment() {
val subRequestCounts = StateSubscriptions.instance.getSubscriptionRequestCount(group); val subRequestCounts = StateSubscriptions.instance.getSubscriptionRequestCount(group);
val reqCountStr = subRequestCounts.map { " ${it.key.config.name}: ${it.value}/${it.key.getSubscriptionRateLimit()}" }.joinToString("\n"); val reqCountStr = subRequestCounts.map { " ${it.key.config.name}: ${it.value}/${it.key.getSubscriptionRateLimit()}" }.joinToString("\n");
val rateLimitPlugins = subRequestCounts.filter { clientCount -> clientCount.key.getSubscriptionRateLimit()?.let { rateLimit -> clientCount.value > rateLimit } == true } val rateLimitPlugins = subRequestCounts.filter { clientCount -> clientCount.key.getSubscriptionRateLimit()?.let { rateLimit -> clientCount.value > rateLimit } == true }
Logger.w(TAG, "Trying to refreshing subscriptions with requests:\n" + reqCountStr); Logger.w(TAG, "Trying to refreshing subscriptions with requests:\n$reqCountStr");
if(rateLimitPlugins.any()) if(rateLimitPlugins.any())
throw RateLimitException(rateLimitPlugins.map { it.key.id }); throw RateLimitException(rateLimitPlugins.map { it.key.id });
} }
@ -276,7 +274,7 @@ class SubscriptionsFeedFragment : MainFragment() {
private fun initializeToolbarContent() { private fun initializeToolbarContent() {
_subscriptionBar = SubscriptionBar(context).apply { _subscriptionBar = SubscriptionBar(context).apply {
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
}; };
_subscriptionBar?.onClickChannel?.subscribe { c -> fragment.navigate<ChannelFragment>(c); }; _subscriptionBar?.onClickChannel?.subscribe { c -> fragment.navigate<ChannelFragment>(c); };
_subscriptionBar?.onToggleGroup?.subscribe { g -> _subscriptionBar?.onToggleGroup?.subscribe { g ->
@ -395,7 +393,7 @@ class SubscriptionsFeedFragment : MainFragment() {
_taskGetPager.run(withRefetch); _taskGetPager.run(withRefetch);
} }
override fun onRestoreCachedData(cachedData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, LinearLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>) { override fun onRestoreCachedData(cachedData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, StaggeredGridLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>) {
super.onRestoreCachedData(cachedData); super.onRestoreCachedData(cachedData);
setEmptyPager(cachedData.results.isEmpty()); setEmptyPager(cachedData.results.isEmpty());
} }
@ -450,7 +448,7 @@ class SubscriptionsFeedFragment : MainFragment() {
if (toShow is PluginException) if (toShow is PluginException)
UIDialogs.appToast(ToastView.Toast( UIDialogs.appToast(ToastView.Toast(
toShow.message + toShow.message +
(if(channel != null) "\nChannel: " + channel else ""), false, null, (if(channel != null) "\nChannel: $channel" else ""), false, null,
"Plugin ${toShow.config.name} failed") "Plugin ${toShow.config.name} failed")
); );
else else
@ -461,14 +459,14 @@ class SubscriptionsFeedFragment : MainFragment() {
val failedChannels = exs.filterIsInstance<ChannelException>().map { it.channelNameOrUrl }.distinct().toList(); val failedChannels = exs.filterIsInstance<ChannelException>().map { it.channelNameOrUrl }.distinct().toList();
val failedPlugins = exs.filter { it is PluginException || (it is ChannelException && it.cause is PluginException) } val failedPlugins = exs.filter { it is PluginException || (it is ChannelException && it.cause is PluginException) }
.map { if(it is ChannelException) (it.cause as PluginException) else if(it is PluginException) it else null } .map { if(it is ChannelException) (it.cause as PluginException) else if(it is PluginException) it else null }
.filter { it != null } .filterNotNull()
.distinctBy { it?.config?.name } .distinctBy { it?.config?.name }
.map { it!! } .map { it!! }
.toList(); .toList();
for(distinctPluginFail in failedPlugins) for(distinctPluginFail in failedPlugins)
UIDialogs.appToast(context.getString(R.string.plugin_pluginname_failed_message).replace("{pluginName}", distinctPluginFail.config.name).replace("{message}", distinctPluginFail.message ?: "")); UIDialogs.appToast(context.getString(R.string.plugin_pluginname_failed_message).replace("{pluginName}", distinctPluginFail.config.name).replace("{message}", distinctPluginFail.message ?: ""));
if(failedChannels.isNotEmpty()) if(failedChannels.isNotEmpty())
UIDialogs.appToast(ToastView.Toast(failedChannels.take(3).map { "- ${it}" }.joinToString("\n") + UIDialogs.appToast(ToastView.Toast(failedChannels.take(3).map { "- $it" }.joinToString("\n") +
(if(failedChannels.size >= 3) "\nAnd ${failedChannels.size - 3} more" else ""), false, null, "Failed Channels")); (if(failedChannels.size >= 3) "\nAnd ${failedChannels.size - 3} more" else ""), false, null, "Failed Channels"));
} }
} catch (e: Throwable) { } catch (e: Throwable) {
@ -480,7 +478,7 @@ class SubscriptionsFeedFragment : MainFragment() {
} }
companion object { companion object {
val TAG = "SubscriptionsFeedFragment"; const val TAG = "SubscriptionsFeedFragment";
fun newInstance() = SubscriptionsFeedFragment().apply {} fun newInstance() = SubscriptionsFeedFragment().apply {}
} }

View file

@ -6,6 +6,7 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.ScrollView
import android.widget.TextView import android.widget.TextView
import com.futo.platformplayer.* import com.futo.platformplayer.*
import com.futo.platformplayer.api.media.IPlatformClient import com.futo.platformplayer.api.media.IPlatformClient
@ -58,7 +59,15 @@ class TutorialFragment : MainFragment() {
} }
@SuppressLint("ViewConstructor") @SuppressLint("ViewConstructor")
class TutorialView : LinearLayout { class TutorialView(fragment: TutorialFragment, inflater: LayoutInflater) :
ScrollView(inflater.context) {
init {
addView(TutorialContainer(fragment, inflater))
}
}
@SuppressLint("ViewConstructor")
class TutorialContainer : LinearLayout {
val fragment: TutorialFragment val fragment: TutorialFragment
constructor(fragment: TutorialFragment, inflater: LayoutInflater) : super(inflater.context) { constructor(fragment: TutorialFragment, inflater: LayoutInflater) : super(inflater.context) {
@ -150,7 +159,7 @@ class TutorialFragment : MainFragment() {
} }
companion object { companion object {
val TAG = "HomeFragment"; const val TAG = "HomeFragment";
fun newInstance() = TutorialFragment().apply {} fun newInstance() = TutorialFragment().apply {}
val initialSetupVideos = listOf( val initialSetupVideos = listOf(

View file

@ -1,11 +1,10 @@
package com.futo.platformplayer.fragment.mainactivity.main package com.futo.platformplayer.fragment.mainactivity.main
import android.annotation.SuppressLint
import android.content.pm.ActivityInfo import android.content.pm.ActivityInfo
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -14,10 +13,8 @@ import android.view.WindowInsetsController
import android.view.WindowManager import android.view.WindowManager
import androidx.constraintlayout.motion.widget.MotionLayout import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import androidx.lifecycle.lifecycleScope
import com.futo.platformplayer.R import com.futo.platformplayer.R
import com.futo.platformplayer.Settings import com.futo.platformplayer.Settings
import com.futo.platformplayer.SimpleOrientationListener
import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.activities.SettingsActivity import com.futo.platformplayer.activities.SettingsActivity
import com.futo.platformplayer.api.media.models.video.IPlatformVideo import com.futo.platformplayer.api.media.models.video.IPlatformVideo
@ -25,12 +22,12 @@ import com.futo.platformplayer.api.media.models.video.IPlatformVideoDetails
import com.futo.platformplayer.casting.StateCasting import com.futo.platformplayer.casting.StateCasting
import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.constructs.Event0
import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.listeners.AutoRotateChangeListener
import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.PlatformVideoWithTime import com.futo.platformplayer.models.PlatformVideoWithTime
import com.futo.platformplayer.models.UrlVideoWithTime import com.futo.platformplayer.models.UrlVideoWithTime
import com.futo.platformplayer.states.StatePlayer import com.futo.platformplayer.states.StatePlayer
import com.futo.platformplayer.views.containers.SingleViewTouchableMotionLayout import com.futo.platformplayer.views.containers.SingleViewTouchableMotionLayout
import kotlin.math.min
class VideoDetailFragment : MainFragment { class VideoDetailFragment : MainFragment {
@ -43,11 +40,10 @@ class VideoDetailFragment : MainFragment {
private var _viewDetail : VideoDetailView? = null; private var _viewDetail : VideoDetailView? = null;
private var _view : SingleViewTouchableMotionLayout? = null; private var _view : SingleViewTouchableMotionLayout? = null;
private lateinit var _autoRotateChangeListener: AutoRotateChangeListener
private lateinit var _orientationListener: SimpleOrientationListener
private var _currentOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
var isFullscreen : Boolean = false; private var isFullscreen : Boolean = false;
var isMinimizing : Boolean = false;
private var isSmallWindow : Boolean = true;
val onFullscreenChanged = Event1<Boolean>(); val onFullscreenChanged = Event1<Boolean>();
var isTransitioning : Boolean = false var isTransitioning : Boolean = false
private set; private set;
@ -77,8 +73,7 @@ class VideoDetailFragment : MainFragment {
private var _leavingPiP = false; private var _leavingPiP = false;
//region Fragment //region Fragment
constructor() : super() { constructor() : super()
}
fun nextVideo() { fun nextVideo() {
_viewDetail?.nextVideo(true, true, true); _viewDetail?.nextVideo(true, true, true);
@ -88,44 +83,74 @@ class VideoDetailFragment : MainFragment {
_viewDetail?.prevVideo(true); _viewDetail?.prevVideo(true);
} }
private fun onStateChanged(state: VideoDetailFragment.State) { fun detectWindowSize() {
isSmallWindow = min(
resources.configuration.screenWidthDp,
resources.configuration.screenHeightDp
) < resources.getDimension(R.dimen.landscape_threshold)
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
detectWindowSize()
if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE && !isFullscreen && state == State.MAXIMIZED) {
_viewDetail?.setFullscreen(true)
}
if (isFullscreen && !Settings.instance.playback.fullscreenPortrait && newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
_viewDetail?.setFullscreen(false)
}
}
private fun onStateChanged(state: State) {
if (isSmallWindow && state == State.MAXIMIZED && !isFullscreen && resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
_viewDetail?.setFullscreen(true)
}
updateOrientation() updateOrientation()
} }
private fun updateOrientation() { @SuppressLint("SourceLockedOrientationActivity")
fun updateOrientation() {
val a = activity ?: return val a = activity ?: return
val isMaximized = state == State.MAXIMIZED
val isFullScreenPortraitAllowed = Settings.instance.playback.fullscreenPortrait; val isFullScreenPortraitAllowed = Settings.instance.playback.fullscreenPortrait;
val bypassRotationPrevention = Settings.instance.other.bypassRotationPrevention; val isReversePortraitAllowed = Settings.instance.playback.reversePortrait;
val currentRequestedOrientation = a.requestedOrientation val rotationLock = StatePlayer.instance.rotationLock
var currentOrientation = if (_currentOrientation == -1) currentRequestedOrientation else _currentOrientation
if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT && !Settings.instance.playback.reversePortrait)
currentOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
val isAutoRotate = Settings.instance.playback.isAutoRotate() // For small windows if the device isn't landscape right now and full screen portrait isn't allowed then we should force landscape
val isFs = isFullscreen if (isSmallWindow && isFullscreen && !isFullScreenPortraitAllowed && resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT && !rotationLock) {
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
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_PORTRAIT
} }
// 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 && !isMinimizing && !isFullscreen && state == State.MAXIMIZED && resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
activity?.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
} else if (rotationLock) {
a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
} else {
when (Settings.instance.playback.autoRotate) {
0 -> {
a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
}
Log.i(TAG, "updateOrientation (isFs = ${isFs}, currentOrientation = ${currentOrientation}, currentRequestedOrientation = ${currentRequestedOrientation}, isMaximized = ${isMaximized}, isAutoRotate = ${isAutoRotate}, isFullScreenPortraitAllowed = ${isFullScreenPortraitAllowed}) resulted in requested orientation ${activity?.requestedOrientation}"); 1 -> {
a.requestedOrientation = if (isReversePortraitAllowed) {
ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
} else {
ActivityInfo.SCREEN_ORIENTATION_SENSOR
}
}
2 -> {
a.requestedOrientation = if (isReversePortraitAllowed) {
ActivityInfo.SCREEN_ORIENTATION_FULL_USER
} else {
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
}
}
}
}
} }
override fun onShownWithView(parameter: Any?, isBack: Boolean) { override fun onShownWithView(parameter: Any?, isBack: Boolean) {
@ -167,10 +192,6 @@ class VideoDetailFragment : MainFragment {
return true; return true;
} }
override fun onHide() {
super.onHide();
}
fun preventPictureInPicture() { fun preventPictureInPicture() {
Logger.i(TAG, "preventPictureInPicture() preventPictureInPicture = true"); Logger.i(TAG, "preventPictureInPicture() preventPictureInPicture = true");
_viewDetail?.preventPictureInPicture = true; _viewDetail?.preventPictureInPicture = true;
@ -211,6 +232,7 @@ class VideoDetailFragment : MainFragment {
it.applyFragment(this); it.applyFragment(this);
it.onFullscreenChanged.subscribe(::onFullscreenChanged); it.onFullscreenChanged.subscribe(::onFullscreenChanged);
it.onMinimize.subscribe { it.onMinimize.subscribe {
isMinimizing = true
_view!!.transitionToStart(); _view!!.transitionToStart();
}; };
it.onClose.subscribe { it.onClose.subscribe {
@ -247,6 +269,7 @@ class VideoDetailFragment : MainFragment {
if (state != State.MINIMIZED && progress < 0.1) { if (state != State.MINIMIZED && progress < 0.1) {
state = State.MINIMIZED; state = State.MINIMIZED;
isMinimizing = false
onMinimize.emit(); onMinimize.emit();
} }
else if (state != State.MAXIMIZED && progress > 0.9) { else if (state != State.MAXIMIZED && progress > 0.9) {
@ -285,13 +308,6 @@ class VideoDetailFragment : MainFragment {
minimizeVideoDetail(); minimizeVideoDetail();
} }
_autoRotateChangeListener = AutoRotateChangeListener(requireContext(), Handler()) { _ ->
if (updateAutoFullscreen()) {
return@AutoRotateChangeListener
}
updateOrientation()
}
_loadUrlOnCreate?.let { _viewDetail?.setVideo(it.url, it.timeSeconds, it.playWhenReady) }; _loadUrlOnCreate?.let { _viewDetail?.setVideo(it.url, it.timeSeconds, it.playWhenReady) };
maximizeVideoDetail(); maximizeVideoDetail();
@ -300,40 +316,11 @@ class VideoDetailFragment : MainFragment {
} }
StatePlayer.instance.onRotationLockChanged.subscribe(this) { StatePlayer.instance.onRotationLockChanged.subscribe(this) {
if (updateAutoFullscreen()) {
return@subscribe
}
updateOrientation()
}
_orientationListener = SimpleOrientationListener(requireActivity(), lifecycleScope)
_orientationListener.onOrientationChanged.subscribe {
_currentOrientation = it
Logger.i(TAG, "Current orientation changed (_currentOrientation = ${_currentOrientation})")
if (updateAutoFullscreen()) {
return@subscribe
}
updateOrientation() updateOrientation()
} }
return _view!!; return _view!!;
} }
private fun updateAutoFullscreen(): Boolean {
if (Settings.instance.playback.isAutoRotate()) {
if (state == State.MAXIMIZED && !isFullscreen && (_currentOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || _currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE)) {
_viewDetail?.setFullscreen(true)
return true
}
if (state == State.MAXIMIZED && isFullscreen && !Settings.instance.playback.fullscreenPortrait && (_currentOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || _currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT)) {
_viewDetail?.setFullscreen(false)
return true
}
}
return false
}
fun onUserLeaveHint() { fun onUserLeaveHint() {
val viewDetail = _viewDetail; val viewDetail = _viewDetail;
Logger.i(TAG, "onUserLeaveHint preventPictureInPicture=${viewDetail?.preventPictureInPicture} isCasting=${StateCasting.instance.isCasting} isBackgroundPictureInPicture=${Settings.instance.playback.isBackgroundPictureInPicture()} allowBackground=${viewDetail?.allowBackground}"); Logger.i(TAG, "onUserLeaveHint preventPictureInPicture=${viewDetail?.preventPictureInPicture} isCasting=${StateCasting.instance.isCasting} isBackgroundPictureInPicture=${Settings.instance.playback.isBackgroundPictureInPicture()} allowBackground=${viewDetail?.allowBackground}");
@ -422,15 +409,12 @@ class VideoDetailFragment : MainFragment {
if(shouldStop) { if(shouldStop) {
_viewDetail?.onStop(); _viewDetail?.onStop();
StateCasting.instance.onStop(); StateCasting.instance.onStop();
Logger.v(TAG, "called onStop() shouldStop: $shouldStop");
} }
} }
override fun onDestroyMainView() { override fun onDestroyMainView() {
super.onDestroyMainView(); super.onDestroyMainView();
Logger.v(TAG, "onDestroyMainView"); Logger.v(TAG, "onDestroyMainView");
_autoRotateChangeListener?.unregister()
_orientationListener.stopListening()
SettingsActivity.settingsActivityClosed.remove(this) SettingsActivity.settingsActivityClosed.remove(this)
StatePlayer.instance.onRotationLockChanged.remove(this) StatePlayer.instance.onRotationLockChanged.remove(this)
@ -511,7 +495,7 @@ class VideoDetailFragment : MainFragment {
} }
companion object { companion object {
private val TAG = "VideoDetailFragment"; private const val TAG = "VideoDetailFragment";
fun newInstance() = VideoDetailFragment().apply {} fun newInstance() = VideoDetailFragment().apply {}
} }

View file

@ -154,20 +154,19 @@ import com.futo.polycentric.core.Opinion
import com.futo.polycentric.core.toURLInfoSystemLinkUrl import com.futo.polycentric.core.toURLInfoSystemLinkUrl
import com.google.protobuf.ByteString import com.google.protobuf.ByteString
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.Dispatcher
import org.w3c.dom.Text
import userpackage.Protocol import userpackage.Protocol
import java.time.OffsetDateTime import java.time.OffsetDateTime
import kotlin.math.abs import kotlin.math.abs
import kotlin.math.roundToLong import kotlin.math.roundToLong
@androidx.media3.common.util.UnstableApi @UnstableApi
class VideoDetailView : ConstraintLayout { class VideoDetailView : ConstraintLayout {
private val TAG = "VideoDetailView" private val TAG = "VideoDetailView"
@ -180,7 +179,7 @@ class VideoDetailView : ConstraintLayout {
private var _searchVideo: IPlatformVideo? = null; private var _searchVideo: IPlatformVideo? = null;
var video: IPlatformVideoDetails? = null var video: IPlatformVideoDetails? = null
private set; private set;
var videoLocal: VideoLocal? = null; private var videoLocal: VideoLocal? = null;
private var _playbackTracker: IPlaybackTracker? = null; private var _playbackTracker: IPlaybackTracker? = null;
private var _historyIndex: DBHistory.Index? = null; private var _historyIndex: DBHistory.Index? = null;
@ -195,7 +194,7 @@ class VideoDetailView : ConstraintLayout {
private val _timeBar: TimeBar; private val _timeBar: TimeBar;
private var _upNext: UpNextView; private var _upNext: UpNextView;
val rootView: ConstraintLayout; private val rootView: ConstraintLayout;
private val _title: TextView; private val _title: TextView;
private val _subTitle: TextView; private val _subTitle: TextView;
@ -284,7 +283,7 @@ class VideoDetailView : ConstraintLayout {
var isPlaying: Boolean = false var isPlaying: Boolean = false
private set; private set;
var lastPositionMilliseconds: Long = 0 private var lastPositionMilliseconds: Long = 0
private set; private set;
private var _historicalPosition: Long = 0; private var _historicalPosition: Long = 0;
private var _commentsCount = 0; private var _commentsCount = 0;
@ -524,12 +523,14 @@ class VideoDetailView : ConstraintLayout {
_cast.onChapterChanged.subscribe(onChapterChanged); _cast.onChapterChanged.subscribe(onChapterChanged);
_cast.onMinimizeClick.subscribe { _cast.onMinimizeClick.subscribe {
_player.setFullScreen(false); // emit minimize before toggling fullscreen so we know that the full screen toggle is happening during a minimize operation
onMinimize.emit(); onMinimize.emit()
_player.setFullScreen(false)
}; };
_player.onMinimize.subscribe { _player.onMinimize.subscribe {
_player.setFullScreen(false); // emit minimize before toggling fullscreen so we know that the full screen toggle is happening during a minimize operation
onMinimize.emit(); onMinimize.emit()
_player.setFullScreen(false)
}; };
_player.onTimeBarChanged.subscribe { position, _ -> _player.onTimeBarChanged.subscribe { position, _ ->
@ -697,7 +698,8 @@ class VideoDetailView : ConstraintLayout {
if (c is PolycentricPlatformComment) { if (c is PolycentricPlatformComment) {
var parentComment: PolycentricPlatformComment = c; var parentComment: PolycentricPlatformComment = c;
_container_content_replies.load(if (_tabIndex!! == 0) false else true, metadata, c.contextUrl, c.reference, c, _container_content_replies.load(
_tabIndex!! != 0, metadata, c.contextUrl, c.reference, c,
{ StatePolycentric.instance.getCommentPager(c.contextUrl, c.reference) }, { StatePolycentric.instance.getCommentPager(c.contextUrl, c.reference) },
{ {
val newComment = parentComment.cloneWithUpdatedReplyCount((parentComment.replyCount ?: 0) + 1); val newComment = parentComment.cloneWithUpdatedReplyCount((parentComment.replyCount ?: 0) + 1);
@ -705,7 +707,7 @@ class VideoDetailView : ConstraintLayout {
parentComment = newComment; parentComment = newComment;
}); });
} else { } else {
_container_content_replies.load(if (_tabIndex!! == 0) false else true, metadata, null, null, c, { StatePlatform.instance.getSubComments(c) }); _container_content_replies.load(_tabIndex!! != 0, metadata, null, null, c, { StatePlatform.instance.getSubComments(c) });
} }
switchContentView(_container_content_replies); switchContentView(_container_content_replies);
}; };
@ -887,7 +889,7 @@ class VideoDetailView : ConstraintLayout {
else { else {
val selectedButtons = _buttonPinStore.getAllValues() val selectedButtons = _buttonPinStore.getAllValues()
.map { x-> buttons.find { it.tagRef == x } } .map { x-> buttons.find { it.tagRef == x } }
.filter { it != null } .filterNotNull()
.map { it!! }; .map { it!! };
_buttonPins.setButtons(*(selectedButtons + _buttonPins.setButtons(*(selectedButtons +
buttons.filter { !selectedButtons.contains(it) } + buttons.filter { !selectedButtons.contains(it) } +
@ -1201,7 +1203,7 @@ class VideoDetailView : ConstraintLayout {
switchContentView(_container_content_main); switchContentView(_container_content_main);
} }
//@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
fun setVideoDetails(videoDetail: IPlatformVideoDetails, newVideo: Boolean = false) { fun setVideoDetails(videoDetail: IPlatformVideoDetails, newVideo: Boolean = false) {
Logger.i(TAG, "setVideoDetails (${videoDetail.name})") Logger.i(TAG, "setVideoDetails (${videoDetail.name})")
_didTriggerDatasourceErrroCount = 0; _didTriggerDatasourceErrroCount = 0;
@ -1870,7 +1872,7 @@ class VideoDetailView : ConstraintLayout {
?.map { x -> VideoHelper.selectBestVideoSource(videoSources.filter { x == it.height * it.width }, -1, FutoVideoPlayerBase.PREFERED_VIDEO_CONTAINERS) } ?.map { x -> VideoHelper.selectBestVideoSource(videoSources.filter { x == it.height * it.width }, -1, FutoVideoPlayerBase.PREFERED_VIDEO_CONTAINERS) }
?.plus(videoSources.filter { it is IHLSManifestSource || it is IDashManifestSource })) ?.plus(videoSources.filter { it is IHLSManifestSource || it is IDashManifestSource }))
?.distinct() ?.distinct()
?.filter { it != null } ?.filterNotNull()
?.toList() ?: listOf() else videoSources?.toList() ?: listOf() ?.toList() ?: listOf() else videoSources?.toList() ?: listOf()
val bestAudioContainer = audioSources?.let { VideoHelper.selectBestAudioSource(it, FutoVideoPlayerBase.PREFERED_AUDIO_CONTAINERS)?.container }; val bestAudioContainer = audioSources?.let { VideoHelper.selectBestAudioSource(it, FutoVideoPlayerBase.PREFERED_AUDIO_CONTAINERS)?.container };
val bestAudioSources = if(doDedup) audioSources val bestAudioSources = if(doDedup) audioSources
@ -2171,7 +2173,7 @@ class VideoDetailView : ConstraintLayout {
cleanupPlaybackTracker(); cleanupPlaybackTracker();
val url = _url; val url = _url;
if (url != null && url.isNotBlank()) { if (!url.isNullOrBlank()) {
setLoading(true); setLoading(true);
_taskLoadVideo.run(url); _taskLoadVideo.run(url);
} }
@ -2183,7 +2185,7 @@ class VideoDetailView : ConstraintLayout {
if(fullscreen) { if(fullscreen) {
_layoutPlayerContainer.setPadding(0, 0, 0, 0); _layoutPlayerContainer.setPadding(0, 0, 0, 0);
val lp = _container_content.layoutParams as ConstraintLayout.LayoutParams; val lp = _container_content.layoutParams as LayoutParams;
lp.topMargin = 0; lp.topMargin = 0;
_container_content.layoutParams = lp; _container_content.layoutParams = lp;
@ -2196,7 +2198,7 @@ class VideoDetailView : ConstraintLayout {
else { else {
_layoutPlayerContainer.setPadding(0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6.0f, Resources.getSystem().displayMetrics).toInt()); _layoutPlayerContainer.setPadding(0, 0, 0, TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 6.0f, Resources.getSystem().displayMetrics).toInt());
val lp = _container_content.layoutParams as ConstraintLayout.LayoutParams; val lp = _container_content.layoutParams as LayoutParams;
lp.topMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, -18.0f, Resources.getSystem().displayMetrics).toInt(); lp.topMargin = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, -18.0f, Resources.getSystem().displayMetrics).toInt();
_container_content.layoutParams = lp; _container_content.layoutParams = lp;
@ -2239,7 +2241,7 @@ class VideoDetailView : ConstraintLayout {
fun setFullscreen(fullscreen : Boolean) { fun setFullscreen(fullscreen : Boolean) {
Logger.i(TAG, "setFullscreen(fullscreen=$fullscreen)") Logger.i(TAG, "setFullscreen(fullscreen=$fullscreen)")
_player.setFullScreen(fullscreen); _player.setFullScreen(fullscreen)
} }
private fun setLoading(isLoading : Boolean) { private fun setLoading(isLoading : Boolean) {
if(isLoading){ if(isLoading){

View file

@ -1,42 +0,0 @@
package com.futo.platformplayer.listeners
import android.content.Context
import android.database.ContentObserver
import android.os.Handler
import android.provider.Settings
class AutoRotateObserver(handler: Handler, private val onChangeCallback: () -> Unit) : ContentObserver(handler) {
override fun onChange(selfChange: Boolean) {
super.onChange(selfChange)
onChangeCallback()
}
}
class AutoRotateChangeListener(context: Context, handler: Handler, private val onAutoRotateChanged: (Boolean) -> Unit) {
private val contentResolver = context.contentResolver
private val autoRotateObserver = AutoRotateObserver(handler) {
val isAutoRotateEnabled = isAutoRotateEnabled()
onAutoRotateChanged(isAutoRotateEnabled)
}
init {
contentResolver.registerContentObserver(
Settings.System.getUriFor(Settings.System.ACCELEROMETER_ROTATION),
false,
autoRotateObserver
)
}
fun unregister() {
contentResolver.unregisterContentObserver(autoRotateObserver)
}
private fun isAutoRotateEnabled(): Boolean {
return Settings.System.getInt(
contentResolver,
Settings.System.ACCELEROMETER_ROTATION,
0
) == 1
}
}

View file

@ -2,4 +2,5 @@
<resources> <resources>
<dimen name="video_view_right_padding"></dimen> <dimen name="video_view_right_padding"></dimen>
<dimen name="app_bar_height">200dp</dimen> <dimen name="app_bar_height">200dp</dimen>
</resources> <dimen name="landscape_threshold">300dp</dimen>
</resources>

View file

@ -7,7 +7,8 @@
<application> <application>
<receiver android:name=".receivers.InstallReceiver" /> <receiver android:name=".receivers.InstallReceiver" />
<activity android:name=".activities.MainActivity"> <activity android:name=".activities.MainActivity"
android:exported="true">
<intent-filter android:autoVerify="true"> <intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" /> <action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH" />