Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay

This commit is contained in:
Kelvin 2024-11-25 17:10:00 +01:00
commit 14b699485a
19 changed files with 587 additions and 575 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"
@ -146,11 +145,9 @@
<data android:scheme="polycentric" /> <data android:scheme="polycentric" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity <activity
android:name=".activities.TestActivity" android:name=".activities.TestActivity"
android:theme="@style/Theme.FutoVideo.NoActionBar" /> android:theme="@style/Theme.FutoVideo.NoActionBar" />
<activity <activity
android:name=".activities.SettingsActivity" android:name=".activities.SettingsActivity"
android:screenOrientation="sensorPortrait" android:screenOrientation="sensorPortrait"
@ -173,7 +170,6 @@
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>
@ -217,7 +213,6 @@
android:name=".activities.ManageTabsActivity" android:name=".activities.ManageTabsActivity"
android:screenOrientation="sensorPortrait" 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:screenOrientation="sensorPortrait"

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
@ -27,7 +24,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
@ -37,9 +33,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
@ -426,8 +420,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;
@ -483,17 +475,6 @@ class Settings : FragmentedStorageFileJson() {
@FormField(R.string.reverse_portrait, FieldForm.TOGGLE, R.string.reverse_portrait_description, 14) @FormField(R.string.reverse_portrait, FieldForm.TOGGLE, R.string.reverse_portrait_description, 14)
var reversePortrait: Boolean = false; var reversePortrait: Boolean = false;
@FormField(R.string.rotation_zone, FieldForm.DROPDOWN, R.string.rotation_zone_description, 15)
@DropdownFieldOptionsId(R.array.rotation_zone)
var rotationZone: Int = 2;
@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) @FormField(R.string.prefer_webm, FieldForm.TOGGLE, R.string.prefer_webm_description, 18)
var preferWebmVideo: Boolean = false; var preferWebmVideo: Boolean = false;
@FormField(R.string.prefer_webm_audio, FieldForm.TOGGLE, R.string.prefer_webm_audio_description, 19) @FormField(R.string.prefer_webm_audio, FieldForm.TOGGLE, R.string.prefer_webm_audio_description, 19)
@ -880,10 +861,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.playlist_delete_confirmation, FieldForm.TOGGLE, R.string.playlist_delete_confirmation_description, 2) @FormField(R.string.playlist_delete_confirmation, FieldForm.TOGGLE, R.string.playlist_delete_confirmation_description, 2)
var playlistDeleteConfirmation: Boolean = true; var playlistDeleteConfirmation: Boolean = true;

View File

@ -1,86 +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.Job
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 var _currentJob: Job? = null
val onOrientationChanged = Event1<Int>()
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
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
_currentJob?.cancel()
_currentJob = 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() {
_currentJob?.cancel()
_currentJob = null
orientationListener.disable()
}
companion object {
private val TAG = "SimpleOrientationListener"
}
}

View File

@ -28,6 +28,7 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentContainerView import androidx.fragment.app.FragmentContainerView
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.media3.common.util.UnstableApi
import com.futo.platformplayer.BuildConfig import com.futo.platformplayer.BuildConfig
import com.futo.platformplayer.R import com.futo.platformplayer.R
import com.futo.platformplayer.Settings import com.futo.platformplayer.Settings
@ -250,6 +251,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
super.attachBaseContext(StateApp.instance.getLocaleContext(newBase)) super.attachBaseContext(StateApp.instance.getLocaleContext(newBase))
} }
@UnstableApi
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
Logger.i(TAG, "MainActivity Starting"); Logger.i(TAG, "MainActivity Starting");
StateApp.instance.setGlobalContext(this, lifecycleScope); StateApp.instance.setGlobalContext(this, lifecycleScope);
@ -513,6 +515,9 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
//startActivity(Intent(this, TestActivity::class.java)); //startActivity(Intent(this, TestActivity::class.java));
// updates the requestedOrientation based on user settings
_fragVideoDetail.updateOrientation()
val sharedPreferences = val sharedPreferences =
getSharedPreferences("GrayjayFirstBoot", Context.MODE_PRIVATE) getSharedPreferences("GrayjayFirstBoot", Context.MODE_PRIVATE)
val isFirstBoot = sharedPreferences.getBoolean("IsFirstBoot", true) val isFirstBoot = sharedPreferences.getBoolean("IsFirstBoot", true)

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,6 +57,12 @@ class MenuBottomBarFragment : MainActivityFragment() {
return _view?.onBackPressed() ?: false; return _view?.onBackPressed() ?: false;
} }
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
_view?.updateAllButtonVisibility()
}
@SuppressLint("ViewConstructor") @SuppressLint("ViewConstructor")
class MenuBottomBarView : LinearLayout { class MenuBottomBarView : LinearLayout {
private val _fragment: MenuBottomBarFragment; private val _fragment: MenuBottomBarFragment;
@ -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)
}) })
} }
@ -255,9 +262,20 @@ class MenuBottomBarFragment : MainActivityFragment() {
button.updateActive(_fragment); button.updateActive(_fragment);
} }
override fun onConfigurationChanged(newConfig: Configuration?) {
super.onConfigurationChanged(newConfig)
updateAllButtonVisibility()
}
fun updateAllButtonVisibility() { fun updateAllButtonVisibility() {
// if the more fly-out menu is open the we should close it
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,7 +4,7 @@ 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.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.futo.platformplayer.R import com.futo.platformplayer.R
import com.futo.platformplayer.Settings import com.futo.platformplayer.Settings
@ -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>, GridLayoutManager, 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,16 +53,10 @@ 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 { return PreviewContentListAdapter(context, feedStyle, dataset, player, _previewsEnabled, arrayListOf(), arrayListOf(), shouldShowTimeBar).apply {
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
orientation = LinearLayout.VERTICAL;
};
headerView = v;
return PreviewContentListAdapter(context, feedStyle, dataset, player, _previewsEnabled, arrayListOf(v), arrayListOf(), shouldShowTimeBar).apply {
attachAdapterEvents(this); attachAdapterEvents(this);
} }
} }
@ -142,7 +134,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 +155,22 @@ 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>, GridLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>) {
super.onRestoreCachedData(cachedData) super.onRestoreCachedData(cachedData)
val v = LinearLayout(context).apply {
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
orientation = LinearLayout.VERTICAL;
};
headerView = 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; ): GridLayoutManager {
val glmResults =
GridLayoutManager(
context,
(resources.configuration.screenWidthDp / resources.getDimension(R.dimen.landscape_threshold)).toInt() + 1
);
return glmResults
} }
override fun onScrollStateChanged(newState: Int) { override fun onScrollStateChanged(newState: Int) {
@ -217,11 +213,11 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
} }
private fun playPreview() { private fun playPreview() {
if(feedStyle == FeedStyle.THUMBNAIL) if(feedStyle == FeedStyle.THUMBNAIL || recyclerData.layoutManager.spanCount > 1)
return; return;
val firstVisible = recyclerData.layoutManager.findFirstVisibleItemPosition(); val firstVisible = recyclerData.layoutManager.findFirstVisibleItemPosition()
val lastVisible = recyclerData.layoutManager.findLastVisibleItemPosition(); val lastVisible = recyclerData.layoutManager.findLastVisibleItemPosition()
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 +237,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 +265,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.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.futo.platformplayer.*
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,31 @@ abstract class CreatorFeedView<TFragment> : FeedView<TFragment, PlatformAuthorLi
); );
} }
override fun createLayoutManager(recyclerResults: RecyclerView, context: Context): LinearLayoutManager { /*
val glmResults = GridLayoutManager(context, 2); * An empty override to remove the inherited span count update functionality
glmResults.orientation = LinearLayoutManager.VERTICAL; */
override fun updateSpanCount(){
}
override fun createLayoutManager(
recyclerResults: RecyclerView,
context: Context
): GridLayoutManager {
val glmResults = GridLayoutManager(context, 2)
_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,13 +1,14 @@
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.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.LayoutManager import androidx.recyclerview.widget.RecyclerView.LayoutManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
@ -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>, GridLayoutManager, 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>, GridLayoutManager, 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.findFirstVisibleItemPosition()
//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) {
@ -179,14 +180,13 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
if (firstVisibleItemPosition != RecyclerView.NO_POSITION) { if (firstVisibleItemPosition != RecyclerView.NO_POSITION) {
val firstVisibleView = layoutManager.findViewByPosition(firstVisibleItemPosition) val firstVisibleView = layoutManager.findViewByPosition(firstVisibleItemPosition)
val itemHeight = firstVisibleView?.height ?: 0 val itemHeight = firstVisibleView?.height ?: 0
val occupiedSpace = recyclerData.results.size * itemHeight val occupiedSpace = recyclerData.results.size / recyclerData.layoutManager.spanCount * itemHeight
val recyclerViewHeight = _recyclerResults.height val recyclerViewHeight = _recyclerResults.height
Logger.i(TAG, "ensureEnoughContentVisible loadNextPage occupiedSpace=$occupiedSpace recyclerViewHeight=$recyclerViewHeight") Logger.i(TAG, "ensureEnoughContentVisible loadNextPage occupiedSpace=$occupiedSpace recyclerViewHeight=$recyclerViewHeight")
occupiedSpace >= recyclerViewHeight occupiedSpace >= recyclerViewHeight
} else { } else {
false false
} }
} }
Logger.i(TAG, "ensureEnoughContentVisible loadNextPage canScroll=$canScroll _automaticNextPageCounter=$_automaticNextPageCounter") Logger.i(TAG, "ensureEnoughContentVisible loadNextPage canScroll=$canScroll _automaticNextPageCounter=$_automaticNextPageCounter")
if (!canScroll || filteredResults.isEmpty()) { if (!canScroll || filteredResults.isEmpty()) {
@ -226,7 +226,19 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
} }
} }
open fun updateSpanCount() {
recyclerData.layoutManager.spanCount = (resources.configuration.screenWidthDp / resources.getDimension(R.dimen.landscape_threshold)).toInt() + 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 +264,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 +274,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 +289,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): GridLayoutManager;
protected open fun onRestoreCachedData(cachedData: RecyclerData<InsertedViewAdapterWithLoader<TViewHolder>, LinearLayoutManager, TPager, TResult, TConverted, InsertedViewHolder<TViewHolder>>) {} protected open fun onRestoreCachedData(cachedData: RecyclerData<InsertedViewAdapterWithLoader<TViewHolder>, GridLayoutManager, 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 +357,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 +437,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 +455,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 +480,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.GridLayoutManager
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>, GridLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null;
fun reloadFeed() { fun reloadFeed() {
_view?.reloadFeed() _view?.reloadFeed()
@ -101,15 +94,19 @@ 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 {
if(!this.isClosed()) {
recyclerData.adapter.viewsToPrepend.add(this)
this.onClose.subscribe {
recyclerData.adapter.viewsToPrepend.remove(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>, GridLayoutManager, 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 +171,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 +203,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 +223,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 +233,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

@ -5,12 +5,10 @@ import android.os.Bundle
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.LinearLayout
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.GridLayoutManager
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
@ -47,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
@ -58,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>, GridLayoutManager, 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);
@ -111,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>, GridLayoutManager, 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 ->
}; };
@ -152,16 +149,19 @@ class SubscriptionsFeedFragment : MainFragment() {
val homeTab = Settings.instance.tabs.find { it.id == 0 }; val homeTab = Settings.instance.tabs.find { it.id == 0 };
val isHomeEnabled = homeTab?.enabled == true; val isHomeEnabled = homeTab?.enabled == true;
if (announcementsView != null && isHomeEnabled) { if (announcementsView != null && isHomeEnabled) {
headerView.removeView(announcementsView); recyclerData.adapter.viewsToPrepend.remove(announcementsView)
_announcementsView = null; _announcementsView = null
} }
if (announcementsView == null && !isHomeEnabled) { if (announcementsView == null && !isHomeEnabled) {
val c = context; val c = context;
if (c != null) { if (c != null) {
_announcementsView = AnnouncementView(c, null).apply { _announcementsView = AnnouncementView(c, null).apply {
headerView.addView(this) recyclerData.adapter.viewsToPrepend.add(this)
}; this.onClose.subscribe {
recyclerData.adapter.viewsToPrepend.remove(this)
}
}
} }
} }
@ -215,7 +215,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 });
} }
@ -277,7 +277,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 ->
@ -397,7 +397,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>, GridLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>) {
super.onRestoreCachedData(cachedData); super.onRestoreCachedData(cachedData);
setEmptyPager(cachedData.results.isEmpty()); setEmptyPager(cachedData.results.isEmpty());
} }
@ -452,7 +452,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
@ -463,14 +463,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) {
@ -482,7 +482,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,9 @@ 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 androidx.media3.common.util.UnstableApi
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,14 +23,14 @@ 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
@UnstableApi
class VideoDetailFragment : MainFragment { class VideoDetailFragment : MainFragment {
override val isMainView : Boolean = false; override val isMainView : Boolean = false;
override val hasBottomBar: Boolean = true; override val hasBottomBar: Boolean = true;
@ -43,11 +41,13 @@ 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; var isFullscreen : Boolean = false;
/**
* whether the view is in the process of switching from full-screen maximized to minimized
* this is used to detect that the app is skipping the non full-screen maximized state
*/
var isMinimizingFromFullScreen : Boolean = false;
val onFullscreenChanged = Event1<Boolean>(); val onFullscreenChanged = Event1<Boolean>();
var isTransitioning : Boolean = false var isTransitioning : Boolean = false
private set; private set;
@ -77,8 +77,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,65 +87,105 @@ class VideoDetailFragment : MainFragment {
_viewDetail?.prevVideo(true); _viewDetail?.prevVideo(true);
} }
private fun onStateChanged(state: VideoDetailFragment.State) { private fun isSmallWindow(): Boolean {
return min(
resources.configuration.screenWidthDp,
resources.configuration.screenHeightDp
) < resources.getDimension(R.dimen.landscape_threshold)
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
val isLandscapeVideo: Boolean = _viewDetail?.isLandscapeVideo() ?: false
val isSmallWindow = isSmallWindow()
if (
isSmallWindow
&& newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE
&& !isFullscreen
&& state == State.MAXIMIZED
) {
_viewDetail?.setFullscreen(true)
} else if (
isSmallWindow
&& isFullscreen
&& !Settings.instance.playback.fullscreenPortrait
&& newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
&& isLandscapeVideo
) {
_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() { private fun onVideoChanged(videoWidth : Int, videoHeight: Int) {
if (
isSmallWindow()
&& state == State.MAXIMIZED
&& !isFullscreen
&& videoHeight > videoWidth
) {
_viewDetail?.setFullscreen(true)
}
}
@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 isReversePortraitAllowed = Settings.instance.playback.reversePortrait
val bypassRotationPrevention = Settings.instance.other.bypassRotationPrevention; val rotationLock = StatePlayer.instance.rotationLock
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)
currentOrientation = currentRequestedOrientation
val isAutoRotate = Settings.instance.playback.isAutoRotate() val isLandscapeVideo: Boolean = _viewDetail?.isLandscapeVideo() ?: false
val isFs = isFullscreen
if (fullAutorotateLock) { val isSmallWindow = isSmallWindow()
if (isFs && isMaximized) {
if (isFullScreenPortraitAllowed) { // For small windows if the device isn't landscape right now and full screen portrait isn't allowed then we should force landscape
if (isAutoRotate) { if (isSmallWindow && isFullscreen && !isFullScreenPortraitAllowed && resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT && !rotationLock && isLandscapeVideo) {
a.requestedOrientation = currentOrientation a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_LANDSCAPE
} }
} else if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) { // For small windows if the device isn't in a portrait orientation and we're in the maximized state then we should force portrait
if (isAutoRotate || currentOrientation != currentRequestedOrientation && (currentRequestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || currentRequestedOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT)) { else if (isSmallWindow && !isMinimizingFromFullScreen && !isFullscreen && state == State.MAXIMIZED && resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
a.requestedOrientation = currentOrientation a.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
} }
1 -> {
a.requestedOrientation = if (isReversePortraitAllowed) {
ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
} else { } else {
a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE ActivityInfo.SCREEN_ORIENTATION_SENSOR
}
} 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
}
} else {
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}, fullAutorotateLock = ${fullAutorotateLock}, currentRequestedOrientation = ${currentRequestedOrientation}, isMaximized = ${isMaximized}, isAutoRotate = ${isAutoRotate}, isFullScreenPortraitAllowed = ${isFullScreenPortraitAllowed}) resulted in requested orientation ${activity?.requestedOrientation}"); 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) {
@ -188,10 +227,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;
@ -231,7 +266,9 @@ class VideoDetailFragment : MainFragment {
_viewDetail = _view!!.findViewById<VideoDetailView>(R.id.fragview_videodetail).also { _viewDetail = _view!!.findViewById<VideoDetailView>(R.id.fragview_videodetail).also {
it.applyFragment(this); it.applyFragment(this);
it.onFullscreenChanged.subscribe(::onFullscreenChanged); it.onFullscreenChanged.subscribe(::onFullscreenChanged);
it.onVideoChanged.subscribe(::onVideoChanged)
it.onMinimize.subscribe { it.onMinimize.subscribe {
isMinimizingFromFullScreen = true
_view!!.transitionToStart(); _view!!.transitionToStart();
}; };
it.onClose.subscribe { it.onClose.subscribe {
@ -268,6 +305,7 @@ class VideoDetailFragment : MainFragment {
if (state != State.MINIMIZED && progress < 0.1) { if (state != State.MINIMIZED && progress < 0.1) {
state = State.MINIMIZED; state = State.MINIMIZED;
isMinimizingFromFullScreen = false
onMinimize.emit(); onMinimize.emit();
} }
else if (state != State.MAXIMIZED && progress > 0.9) { else if (state != State.MAXIMIZED && progress > 0.9) {
@ -306,13 +344,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();
@ -321,40 +352,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 || (Settings.instance.playback.reversePortrait && _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}");
@ -443,15 +445,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)
@ -532,7 +531,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

@ -4,6 +4,7 @@ import android.app.PictureInPictureParams
import android.app.RemoteAction import android.app.RemoteAction
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.Configuration
import android.content.res.Resources import android.content.res.Resources
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.Rect import android.graphics.Rect
@ -81,6 +82,7 @@ import com.futo.platformplayer.casting.CastConnectionState
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.constructs.Event2
import com.futo.platformplayer.constructs.TaskHandler import com.futo.platformplayer.constructs.TaskHandler
import com.futo.platformplayer.downloads.VideoLocal import com.futo.platformplayer.downloads.VideoLocal
import com.futo.platformplayer.dp import com.futo.platformplayer.dp
@ -159,20 +161,20 @@ 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.max
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"
@ -185,7 +187,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;
@ -200,7 +202,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;
@ -289,7 +291,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;
@ -304,6 +306,7 @@ class VideoDetailView : ConstraintLayout {
val onFullscreenChanged = Event1<Boolean>(); val onFullscreenChanged = Event1<Boolean>();
val onEnterPictureInPicture = Event0(); val onEnterPictureInPicture = Event0();
val onPlayChanged = Event1<Boolean>(); val onPlayChanged = Event1<Boolean>();
val onVideoChanged = Event2<Int, Int>()
var allowBackground : Boolean = false var allowBackground : Boolean = false
private set; private set;
@ -529,12 +532,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, _ ->
@ -723,7 +728,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);
@ -731,7 +737,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);
}; };
@ -941,7 +947,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) } +
@ -1257,7 +1263,8 @@ 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;
@ -1275,7 +1282,12 @@ class VideoDetailView : ConstraintLayout {
} }
if (videoDetail.datetime != null && videoDetail.datetime!! > OffsetDateTime.now()) if (videoDetail.datetime != null && videoDetail.datetime!! > OffsetDateTime.now())
UIDialogs.toast(context, context.getString(R.string.planned_in) + " ${videoDetail.datetime?.toHumanNowDiffString(true)}") UIDialogs.toast(
context,
context.getString(R.string.planned_in) + " ${
videoDetail.datetime?.toHumanNowDiffString(true)
}"
)
if (!videoDetail.isLive) { if (!videoDetail.isLive) {
_player.setPlaybackRate(Settings.instance.playback.getDefaultPlaybackSpeed()); _player.setPlaybackRate(Settings.instance.playback.getDefaultPlaybackSpeed());
@ -1302,8 +1314,7 @@ class VideoDetailView : ConstraintLayout {
} }
} }
}; };
} } else { //TODO: Update cached video if it exists with video
else { //TODO: Update cached video if it exists with video
videoLocal = StateDownloads.instance.getCachedVideo(videoDetail.id); videoLocal = StateDownloads.instance.getCachedVideo(videoDetail.id);
video = videoDetail; video = videoDetail;
} }
@ -1311,6 +1322,8 @@ class VideoDetailView : ConstraintLayout {
this.video = video; this.video = video;
cleanupPlaybackTracker(); cleanupPlaybackTracker();
onVideoChanged.emit(video.video.videoSources[0].width, video.video.videoSources[0].height)
if (video is JSVideoDetails) { if (video is JSVideoDetails) {
val me = this; val me = this;
fragment.lifecycleScope.launch(Dispatchers.IO) { fragment.lifecycleScope.launch(Dispatchers.IO) {
@ -1319,8 +1332,7 @@ class VideoDetailView : ConstraintLayout {
val chapters = null ?: StatePlatform.instance.getContentChapters(video.url); val chapters = null ?: StatePlatform.instance.getContentChapters(video.url);
_player.setChapters(chapters); _player.setChapters(chapters);
_cast.setChapters(chapters); _cast.setChapters(chapters);
} } catch (ex: Throwable) {
catch(ex: Throwable) {
Logger.e(TAG, "Failed to get chapters", ex); Logger.e(TAG, "Failed to get chapters", ex);
_player.setChapters(null); _player.setChapters(null);
_cast.setChapters(null); _cast.setChapters(null);
@ -1346,17 +1358,20 @@ class VideoDetailView : ConstraintLayout {
if (me.video == video) if (me.video == video)
me._playbackTracker = tracker; me._playbackTracker = tracker;
} } else if (me.video == video)
else if(me.video == video)
me._playbackTracker = null; me._playbackTracker = null;
} } catch (ex: Throwable) {
catch(ex: Throwable) {
Logger.e(TAG, "Playback tracker failed", ex); Logger.e(TAG, "Playback tracker failed", ex);
if(me.video?.isLive == true || ex.message?.contains("Unable to resolve host") == true) withContext(Dispatchers.Main) { if(me.video?.isLive == true || ex.message?.contains("Unable to resolve host") == true) withContext(Dispatchers.Main) {
UIDialogs.toast(context, context.getString(R.string.failed_to_get_playback_tracker)); UIDialogs.toast(context, context.getString(R.string.failed_to_get_playback_tracker));
}; };
else withContext(Dispatchers.Main) { else withContext(Dispatchers.Main) {
UIDialogs.showGeneralErrorDialog(context, context.getString(R.string.failed_to_get_playback_tracker), ex); UIDialogs.showGeneralErrorDialog(
context,
context.getString(R.string.failed_to_get_playback_tracker),
ex
);
} }
} }
}; };
@ -1374,7 +1389,10 @@ class VideoDetailView : ConstraintLayout {
setTabIndex(2, true) setTabIndex(2, true)
} else { } else {
when (Settings.instance.comments.defaultCommentSection) { when (Settings.instance.comments.defaultCommentSection) {
0 -> if(Settings.instance.other.polycentricEnabled) setTabIndex(0, true) else setTabIndex(1, true); 0 -> if (Settings.instance.other.polycentricEnabled) setTabIndex(
0,
true
) else setTabIndex(1, true);
1 -> setTabIndex(1, true); 1 -> setTabIndex(1, true);
2 -> setTabIndex(StateMeta.instance.getLastCommentSection(), true) 2 -> setTabIndex(StateMeta.instance.getLastCommentSection(), true)
} }
@ -1385,8 +1403,15 @@ class VideoDetailView : ConstraintLayout {
_title.text = video.name; _title.text = video.name;
_channelName.text = video.author.name; _channelName.text = video.author.name;
if (video.author.subscribers != null) { if (video.author.subscribers != null) {
_channelMeta.text = if((video.author.subscribers ?: 0) > 0) video.author.subscribers!!.toHumanNumber() + " " + context.getString(R.string.subscribers) else ""; _channelMeta.text = if ((video.author.subscribers
(_channelName.layoutParams as MarginLayoutParams).setMargins(0, (DP_5 * -1).toInt(), 0, 0); ?: 0) > 0
) video.author.subscribers!!.toHumanNumber() + " " + context.getString(R.string.subscribers) else "";
(_channelName.layoutParams as MarginLayoutParams).setMargins(
0,
(DP_5 * -1).toInt(),
0,
0
);
} else { } else {
_channelMeta.text = ""; _channelMeta.text = "";
(_channelName.layoutParams as MarginLayoutParams).setMargins(0, (DP_2).toInt(), 0, 0); (_channelName.layoutParams as MarginLayoutParams).setMargins(0, (DP_2).toInt(), 0, 0);
@ -1408,7 +1433,8 @@ class VideoDetailView : ConstraintLayout {
_creatorThumbnail.setThumbnail(video.author.thumbnail, false); _creatorThumbnail.setThumbnail(video.author.thumbnail, false);
val cachedPolycentricProfile = PolycentricCache.instance.getCachedProfile(video.author.url, true); val cachedPolycentricProfile =
PolycentricCache.instance.getCachedProfile(video.author.url, true);
if (cachedPolycentricProfile != null) { if (cachedPolycentricProfile != null) {
setPolycentricProfile(cachedPolycentricProfile, animate = false); setPolycentricProfile(cachedPolycentricProfile, animate = false);
} else { } else {
@ -1419,7 +1445,13 @@ class VideoDetailView : ConstraintLayout {
_platform.setPlatformFromClientID(video.id.pluginId); _platform.setPlatformFromClientID(video.id.pluginId);
val subTitleSegments: ArrayList<String> = ArrayList(); val subTitleSegments: ArrayList<String> = ArrayList();
if (video.viewCount > 0) if (video.viewCount > 0)
subTitleSegments.add("${video.viewCount.toHumanNumber()} ${if(video.isLive) context.getString(R.string.watching_now) else context.getString(R.string.views)}"); subTitleSegments.add(
"${video.viewCount.toHumanNumber()} ${
if (video.isLive) context.getString(
R.string.watching_now
) else context.getString(R.string.views)
}"
);
if (video.datetime != null) { if (video.datetime != null) {
val diff = video.datetime?.getNowDiffSeconds() ?: 0; val diff = video.datetime?.getNowDiffSeconds() ?: 0;
val ago = video.datetime?.toHumanNowDiffString(true) val ago = video.datetime?.toHumanNowDiffString(true)
@ -1436,20 +1468,27 @@ class VideoDetailView : ConstraintLayout {
fragment.lifecycleScope.launch(Dispatchers.IO) { fragment.lifecycleScope.launch(Dispatchers.IO) {
try { try {
val queryReferencesResponse = ApiMethods.getQueryReferences(PolycentricCache.SERVER, ref, null,null, val queryReferencesResponse = ApiMethods.getQueryReferences(
PolycentricCache.SERVER, ref, null, null,
arrayListOf( arrayListOf(
Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder().setFromType(ContentType.OPINION.value).setValue( Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder()
ByteString.copyFrom(Opinion.like.data)).build(), .setFromType(ContentType.OPINION.value).setValue(
Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder().setFromType(ContentType.OPINION.value).setValue( ByteString.copyFrom(Opinion.like.data)
ByteString.copyFrom(Opinion.dislike.data)).build() ).build(),
Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder()
.setFromType(ContentType.OPINION.value).setValue(
ByteString.copyFrom(Opinion.dislike.data)
).build()
), ),
extraByteReferences = listOfNotNull(extraBytesRef) extraByteReferences = listOfNotNull(extraBytesRef)
); );
val likes = queryReferencesResponse.countsList[0]; val likes = queryReferencesResponse.countsList[0];
val dislikes = queryReferencesResponse.countsList[1]; val dislikes = queryReferencesResponse.countsList[1];
val hasLiked = StatePolycentric.instance.hasLiked(ref.toByteArray())/* || extraBytesRef?.let { StatePolycentric.instance.hasLiked(it) } ?: false*/; val hasLiked =
val hasDisliked = StatePolycentric.instance.hasDisliked(ref.toByteArray())/* || extraBytesRef?.let { StatePolycentric.instance.hasDisliked(it) } ?: false*/; StatePolycentric.instance.hasLiked(ref.toByteArray())/* || extraBytesRef?.let { StatePolycentric.instance.hasLiked(it) } ?: false*/;
val hasDisliked =
StatePolycentric.instance.hasDisliked(ref.toByteArray())/* || extraBytesRef?.let { StatePolycentric.instance.hasDisliked(it) } ?: false*/;
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
_rating.visibility = View.VISIBLE; _rating.visibility = View.VISIBLE;
@ -1473,7 +1512,11 @@ class VideoDetailView : ConstraintLayout {
} }
} }
StatePolycentric.instance.updateLikeMap(ref, args.hasLiked, args.hasDisliked) StatePolycentric.instance.updateLikeMap(
ref,
args.hasLiked,
args.hasDisliked
)
}; };
} }
} catch (e: Throwable) { } catch (e: Throwable) {
@ -1495,6 +1538,7 @@ class VideoDetailView : ConstraintLayout {
_textDislikes.visibility = View.VISIBLE; _textDislikes.visibility = View.VISIBLE;
_textDislikes.text = r.dislikes.toHumanNumber(); _textDislikes.text = r.dislikes.toHumanNumber();
} }
is RatingLikes -> { is RatingLikes -> {
val r = video.rating as RatingLikes; val r = video.rating as RatingLikes;
_layoutRating.visibility = View.VISIBLE; _layoutRating.visibility = View.VISIBLE;
@ -1506,6 +1550,7 @@ class VideoDetailView : ConstraintLayout {
_imageDislikeIcon.visibility = View.GONE; _imageDislikeIcon.visibility = View.GONE;
_textDislikes.visibility = View.GONE; _textDislikes.visibility = View.GONE;
} }
else -> { else -> {
_layoutRating.visibility = View.GONE; _layoutRating.visibility = View.GONE;
} }
@ -1517,6 +1562,7 @@ class VideoDetailView : ConstraintLayout {
setLoading(false); setLoading(false);
//Set Mediasource //Set Mediasource
val toResume = _videoResumePositionMilliseconds; val toResume = _videoResumePositionMilliseconds;
@ -1533,9 +1579,22 @@ class VideoDetailView : ConstraintLayout {
val historyItem = getHistoryIndex(videoDetail) ?: return@launch; val historyItem = getHistoryIndex(videoDetail) ?: return@launch;
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
_historicalPosition = StateHistory.instance.updateHistoryPosition(video, historyItem,false, (toResume.toFloat() / 1000.0f).toLong(), null, true); _historicalPosition = StateHistory.instance.updateHistoryPosition(
Logger.i(TAG, "Historical position: $_historicalPosition, last position: $lastPositionMilliseconds"); video,
if (_historicalPosition > 60 && video.duration - _historicalPosition > 5 && Math.abs(_historicalPosition - lastPositionMilliseconds / 1000) > 5.0) { historyItem,
false,
(toResume.toFloat() / 1000.0f).toLong(),
null,
true
);
Logger.i(
TAG,
"Historical position: $_historicalPosition, last position: $lastPositionMilliseconds"
);
if (_historicalPosition > 60 && video.duration - _historicalPosition > 5 && Math.abs(
_historicalPosition - lastPositionMilliseconds / 1000
) > 5.0
) {
_layoutResume.visibility = View.VISIBLE; _layoutResume.visibility = View.VISIBLE;
_textResume.text = "Resume at ${_historicalPosition.toHumanTime(false)}"; _textResume.text = "Resume at ${_historicalPosition.toHumanTime(false)}";
@ -1945,7 +2004,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
@ -2246,7 +2305,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);
} }
@ -2258,7 +2317,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;
@ -2271,7 +2330,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;
@ -2312,9 +2371,20 @@ class VideoDetailView : ConstraintLayout {
} }
} }
fun isLandscapeVideo(): Boolean? {
var videoSourceWidth = _player.exoPlayer?.player?.videoSize?.width
var videoSourceHeight = _player.exoPlayer?.player?.videoSize?.height
return if (videoSourceWidth == null || videoSourceHeight == null || videoSourceWidth == 0 || videoSourceHeight == 0){
null
} else{
videoSourceWidth >= videoSourceHeight
}
}
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){
@ -2505,7 +2575,8 @@ class VideoDetailView : ConstraintLayout {
_overlayContainer.removeAllViews(); _overlayContainer.removeAllViews();
_overlay_quality_selector?.hide(); _overlay_quality_selector?.hide();
_player.fillHeight(); _player.setFullScreen(true)
_player.fillHeight(false)
_layoutPlayerContainer.setPadding(0, 0, 0, 0); _layoutPlayerContainer.setPadding(0, 0, 0, 0);
} }
fun handleLeavePictureInPicture() { fun handleLeavePictureInPicture() {
@ -2641,7 +2712,7 @@ class VideoDetailView : ConstraintLayout {
else { else {
if(_player.layoutParams.height == WRAP_CONTENT) { if(_player.layoutParams.height == WRAP_CONTENT) {
_player.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT); _player.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
_player.fillHeight(); _player.fillHeight(true)
_cast.layoutParams = _cast.layoutParams.apply { _cast.layoutParams = _cast.layoutParams.apply {
(this as MarginLayoutParams).bottomMargin = 0; (this as MarginLayoutParams).bottomMargin = 0;
}; };
@ -2714,13 +2785,24 @@ class VideoDetailView : ConstraintLayout {
if(_minimize_controls.isClickable != clickable) if(_minimize_controls.isClickable != clickable)
_minimize_controls.isClickable = clickable; _minimize_controls.isClickable = clickable;
} }
fun setVideoMinimize(value : Float) {
val padRight = (resources.displayMetrics.widthPixels * 0.70 * value).toInt(); override fun onConfigurationChanged(newConfig: Configuration?) {
_player.setPadding(0, _player.paddingTop, padRight, 0); super.onConfigurationChanged(newConfig)
_cast.setPadding(0, _cast.paddingTop, padRight, 0); if (fragment.state == VideoDetailFragment.State.MINIMIZED) {
_player.fillHeight(true)
} else if (!fragment.isFullscreen) {
_player.fitHeight()
} }
}
fun setVideoMinimize(value : Float) {
val padRight = (resources.displayMetrics.widthPixels * 0.70 * value).toInt()
_player.setPadding(0, _player.paddingTop, padRight, 0)
_cast.setPadding(0, _cast.paddingTop, padRight, 0)
}
fun setTopPadding(value: Float) { fun setTopPadding(value: Float) {
_player.setPadding(0, value.toInt(), _player.paddingRight, 0); _player.setPadding(_player.paddingLeft, value.toInt(), _player.paddingRight, 0)
} }
//Tasks //Tasks

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

@ -10,6 +10,7 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.lifecycle.findViewTreeLifecycleOwner import androidx.lifecycle.findViewTreeLifecycleOwner
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.futo.platformplayer.R import com.futo.platformplayer.R
import com.futo.platformplayer.constructs.Event0
import com.futo.platformplayer.dp import com.futo.platformplayer.dp
import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.Announcement import com.futo.platformplayer.states.Announcement
@ -35,6 +36,8 @@ class AnnouncementView : LinearLayout {
private val _category: String?; private val _category: String?;
private var _currentAnnouncement: Announcement? = null; private var _currentAnnouncement: Announcement? = null;
val onClose = Event0();
private val _scope: CoroutineScope?; private val _scope: CoroutineScope?;
constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) {
@ -101,6 +104,10 @@ class AnnouncementView : LinearLayout {
setAnnouncement(announcements.firstOrNull(), announcements.size); setAnnouncement(announcements.firstOrNull(), announcements.size);
} }
fun isClosed(): Boolean{
return _currentAnnouncement == null
}
private fun setAnnouncement(announcement: Announcement?, count: Int) { private fun setAnnouncement(announcement: Announcement?, count: Int) {
if(count == 0 && announcement == null) if(count == 0 && announcement == null)
Logger.i(TAG, "setAnnouncement announcement=$announcement count=$count"); Logger.i(TAG, "setAnnouncement announcement=$announcement count=$count");
@ -108,11 +115,12 @@ class AnnouncementView : LinearLayout {
_currentAnnouncement = announcement; _currentAnnouncement = announcement;
if (announcement == null) { if (announcement == null) {
_root.visibility = View.GONE; visibility = View.GONE
onClose.emit()
return; return;
} }
_root.visibility = View.VISIBLE; visibility = View.VISIBLE
_textTitle.text = announcement.title; _textTitle.text = announcement.title;
_textBody.text = announcement.msg; _textBody.text = announcement.msg;

View File

@ -20,7 +20,6 @@ import androidx.annotation.OptIn
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.view.setMargins import androidx.core.view.setMargins
import androidx.media3.common.C
import androidx.media3.common.PlaybackParameters import androidx.media3.common.PlaybackParameters
import androidx.media3.common.VideoSize import androidx.media3.common.VideoSize
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
@ -111,7 +110,9 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
private val _author_fullscreen: TextView; private val _author_fullscreen: TextView;
private var _shouldRestartHideJobOnPlaybackStateChange: Boolean = false; private var _shouldRestartHideJobOnPlaybackStateChange: Boolean = false;
private var _lastSourceFit: Int? = null; private var _lastSourceFit: Float? = null;
private var _lastWindowWidth: Int = resources.configuration.screenWidthDp
private var _lastWindowHeight: Int = resources.configuration.screenHeightDp
private var _originalBottomMargin: Int = 0; private var _originalBottomMargin: Int = 0;
private var _isControlsLocked: Boolean = false; private var _isControlsLocked: Boolean = false;
@ -632,7 +633,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
private fun fitOrFill(fullScreen: Boolean) { private fun fitOrFill(fullScreen: Boolean) {
if (fullScreen) { if (fullScreen) {
fillHeight(); fillHeight(false);
} else { } else {
fitHeight(); fitHeight();
} }
@ -655,7 +656,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
gestureControl.resetZoomPan() gestureControl.resetZoomPan()
_lastSourceFit = null; _lastSourceFit = null;
if(isFullScreen) if(isFullScreen)
fillHeight(); fillHeight(false);
else if(_root.layoutParams.height != MATCH_PARENT) else if(_root.layoutParams.height != MATCH_PARENT)
fitHeight(videoSize); fitHeight(videoSize);
} }
@ -719,57 +720,72 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
//Sizing //Sizing
@OptIn(UnstableApi::class) @OptIn(UnstableApi::class)
fun fitHeight(videoSize: VideoSize? = null) { fun fitHeight(videoSize: VideoSize? = null) {
Logger.i(TAG, "Video Fit Height"); Logger.i(TAG, "Video Fit Height")
if (_originalBottomMargin != 0) { if (_originalBottomMargin != 0) {
val layoutParams = _videoView.layoutParams as ConstraintLayout.LayoutParams; val layoutParams = _videoView.layoutParams as ConstraintLayout.LayoutParams
layoutParams.setMargins(0, 0, 0, _originalBottomMargin); layoutParams.setMargins(0, 0, 0, _originalBottomMargin)
_videoView.layoutParams = layoutParams; _videoView.layoutParams = layoutParams
} }
var h = videoSize?.height ?: lastVideoSource?.height ?: exoPlayer?.player?.videoSize?.height ?: 0; var h = videoSize?.height ?: lastVideoSource?.height ?: exoPlayer?.player?.videoSize?.height
var w = videoSize?.width ?: lastVideoSource?.width ?: exoPlayer?.player?.videoSize?.width ?: 0; ?: 0
var w =
videoSize?.width ?: lastVideoSource?.width ?: exoPlayer?.player?.videoSize?.width ?: 0
if (h == 0 && w == 0) { if (h == 0 && w == 0) {
Logger.i(TAG, "UNKNOWN VIDEO FIT: (videoSize: ${videoSize != null}, player.videoSize: ${exoPlayer?.player?.videoSize != null})"); Logger.i(
w = 1280; TAG,
h = 720; "UNKNOWN VIDEO FIT: (videoSize: ${videoSize != null}, player.videoSize: ${exoPlayer?.player?.videoSize != null})"
);
w = 1280
h = 720
} }
val configuration = resources.configuration
if(_lastSourceFit == null){ val windowWidth = configuration.screenWidthDp
val metrics = StateApp.instance.displayMetrics ?: resources.displayMetrics; val windowHeight = configuration.screenHeightDp
val viewWidth = Math.min(metrics.widthPixels, metrics.heightPixels); //TODO: Get parent width. was this.width if (_lastSourceFit == null || windowWidth != _lastWindowWidth || windowHeight != _lastWindowHeight) {
val deviceHeight = Math.max(metrics.widthPixels, metrics.heightPixels); val maxHeight = windowHeight * 0.4f
val maxHeight = deviceHeight * 0.4;
val determinedHeight = if(w > h) val aspectRatio = h.toFloat() / w
((h * (viewWidth.toDouble() / w)).toInt()) val determinedHeight = (aspectRatio * windowWidth)
_lastSourceFit = determinedHeight
_lastSourceFit = _lastSourceFit!!.coerceAtLeast(220f)
_lastSourceFit = _lastSourceFit!!.coerceAtMost(maxHeight)
_desiredResizeModePortrait = if (_lastSourceFit != determinedHeight)
AspectRatioFrameLayout.RESIZE_MODE_FIT
else else
((h * (viewWidth.toDouble() / w)).toInt()); AspectRatioFrameLayout.RESIZE_MODE_ZOOM
_lastSourceFit = determinedHeight;
_lastSourceFit = Math.max(_lastSourceFit!!, 250); _lastWindowWidth = windowWidth
_lastSourceFit = Math.min(_lastSourceFit!!, maxHeight.toInt()); _lastWindowHeight = windowHeight
if((_lastSourceFit ?: 0) < 300 || (_lastSourceFit ?: 0) > viewWidth) {
Log.d(TAG, "WEIRD HEIGHT DETECTED: ${_lastSourceFit}, Width: ${w}, Height: ${h}, VWidth: ${viewWidth}");
} }
if(_lastSourceFit != determinedHeight)
_desiredResizeModePortrait = AspectRatioFrameLayout.RESIZE_MODE_FIT;
else
_desiredResizeModePortrait = AspectRatioFrameLayout.RESIZE_MODE_ZOOM;
_videoView.resizeMode = _desiredResizeModePortrait _videoView.resizeMode = _desiredResizeModePortrait
val marginBottom =
TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 7f, resources.displayMetrics)
.toInt()
val height = TypedValue.applyDimension(
TypedValue.COMPLEX_UNIT_DIP,
_lastSourceFit!!,
resources.displayMetrics
)
val rootParams = LayoutParams(LayoutParams.MATCH_PARENT, (height + marginBottom).toInt())
rootParams.bottomMargin = marginBottom
_root.layoutParams = rootParams
isFitMode = true
} }
val marginBottom = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 7f, resources.displayMetrics).toInt(); @OptIn(UnstableApi::class)
val rootParams = LayoutParams(LayoutParams.MATCH_PARENT, _lastSourceFit!! + marginBottom) fun fillHeight(isMiniPlayer: Boolean) {
rootParams.bottomMargin = marginBottom;
_root.layoutParams = rootParams
isFitMode = true;
}
fun fillHeight(){
Logger.i(TAG, "Video Fill Height"); Logger.i(TAG, "Video Fill Height");
val layoutParams = _videoView.layoutParams as ConstraintLayout.LayoutParams; val layoutParams = _videoView.layoutParams as ConstraintLayout.LayoutParams;
_originalBottomMargin = if(layoutParams.bottomMargin > 0) layoutParams.bottomMargin else _originalBottomMargin; _originalBottomMargin =
if (layoutParams.bottomMargin > 0) layoutParams.bottomMargin else _originalBottomMargin;
layoutParams.setMargins(0); layoutParams.setMargins(0);
_videoView.layoutParams = layoutParams; _videoView.layoutParams = layoutParams;
_videoView.invalidate(); _videoView.invalidate();
@ -777,6 +793,11 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
val rootParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); val rootParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
_root.layoutParams = rootParams; _root.layoutParams = rootParams;
_root.invalidate(); _root.invalidate();
if(isMiniPlayer){
_videoView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM
}
isFitMode = false; isFitMode = false;
} }

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
@ -12,49 +13,59 @@
android:contentDescription="@string/cd_button_back" android:contentDescription="@string/cd_button_back"
android:padding="10dp" android:padding="10dp"
android:scaleType="fitCenter" android:scaleType="fitCenter"
app:srcCompat="@drawable/ic_back_thin_white_16dp" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintLeft_toLeftOf="parent" /> app:srcCompat="@drawable/ic_back_thin_white_16dp" />
<ImageButton <ImageButton
android:id="@+id/button_help" android:id="@+id/button_help"
android:layout_width="50dp" android:layout_width="50dp"
android:layout_height="50dp" android:layout_height="50dp"
android:contentDescription="@string/cd_button_help"
app:srcCompat="@drawable/ic_help"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_help" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="0px"
app:layout_constraintTop_toBottomOf="@id/button_help"
app:layout_constraintBottom_toBottomOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/black">
<ImageView <ImageView
android:id="@+id/image_polycentric" android:id="@+id/image_polycentric"
android:layout_width="80dp" android:layout_width="80dp"
android:layout_height="80dp" android:layout_height="80dp"
app:srcCompat="@drawable/neopass" android:layout_marginTop="40dp"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/button_help" app:layout_constraintTop_toTopOf="parent"
android:layout_marginTop="40dp"/> app:srcCompat="@drawable/neopass" />
<TextView <TextView
android:id="@+id/text_polycentric" android:id="@+id/text_polycentric"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/polycentric"
android:fontFamily="@font/inter_light"
android:textSize="32dp"
android:layout_marginTop="12dp" android:layout_marginTop="12dp"
app:layout_constraintTop_toBottomOf="@id/image_polycentric" android:fontFamily="@font/inter_light"
android:text="@string/polycentric"
android:textSize="32dp"
app:layout_constraintLeft_toLeftOf="@id/image_polycentric" app:layout_constraintLeft_toLeftOf="@id/image_polycentric"
app:layout_constraintRight_toRightOf="@id/image_polycentric" /> app:layout_constraintRight_toRightOf="@id/image_polycentric"
app:layout_constraintTop_toBottomOf="@id/image_polycentric" />
<TextView <TextView
android:id="@+id/text_profile_name" android:id="@+id/text_profile_name"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/profile_name"
android:fontFamily="@font/inter_light"
android:textSize="16dp"
android:layout_marginBottom="8dp" android:layout_marginBottom="8dp"
android:fontFamily="@font/inter_light"
android:text="@string/profile_name"
android:textSize="16dp"
app:layout_constraintBottom_toTopOf="@id/edit_profile_name" app:layout_constraintBottom_toTopOf="@id/edit_profile_name"
app:layout_constraintLeft_toLeftOf="@id/edit_profile_name" /> app:layout_constraintLeft_toLeftOf="@id/edit_profile_name" />
@ -62,25 +73,25 @@
android:id="@+id/edit_profile_name" android:id="@+id/edit_profile_name"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:hint="@string/this_will_be_visible_to_other_users"
android:layout_marginStart="40dp" android:layout_marginStart="40dp"
android:layout_marginEnd="40dp"
android:layout_marginTop="60dp" android:layout_marginTop="60dp"
android:layout_marginEnd="40dp"
android:background="@drawable/background_16_round_4dp" android:background="@drawable/background_16_round_4dp"
android:hint="@string/this_will_be_visible_to_other_users"
android:singleLine="true" android:singleLine="true"
app:layout_constraintTop_toBottomOf="@id/text_polycentric" app:layout_constraintBottom_toTopOf="@id/button_create_profile"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toTopOf="@id/button_create_profile"/> app:layout_constraintTop_toBottomOf="@id/text_polycentric" />
<LinearLayout <LinearLayout
android:id="@+id/button_create_profile" android:id="@+id/button_create_profile"
android:layout_width="140dp" android:layout_width="140dp"
android:layout_height="40dp" android:layout_height="40dp"
android:background="@drawable/background_button_primary_round"
android:gravity="center"
android:layout_marginTop="40dp" android:layout_marginTop="40dp"
android:layout_marginBottom="10dp" android:layout_marginBottom="10dp"
android:background="@drawable/background_button_primary_round"
android:gravity="center"
android:orientation="horizontal" android:orientation="horizontal"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
@ -90,17 +101,20 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:fontFamily="@font/inter_light" android:fontFamily="@font/inter_light"
android:text="@string/create_profile"
android:textColor="@color/white" android:textColor="@color/white"
android:textSize="16dp" android:textSize="16dp" />
android:text="@string/create_profile" />
</LinearLayout> </LinearLayout>
<com.futo.platformplayer.views.LoaderView <com.futo.platformplayer.views.LoaderView
android:id="@+id/loader" android:id="@+id/loader"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="50dp" android:layout_height="50dp"
android:visibility="gone" android:visibility="gone"
app:layout_constraintTop_toBottomOf="@id/button_create_profile"
app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent" /> app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toBottomOf="@id/button_create_profile" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<dimen name="video_view_right_padding"></dimen> <dimen name="minimized_player_max_width">500dp</dimen>
<dimen name="app_bar_height">200dp</dimen> <dimen name="app_bar_height">200dp</dimen>
<dimen name="landscape_threshold">300dp</dimen>
</resources> </resources>

View File

@ -259,7 +259,7 @@
<string name="add_a_comment">Add a comment…</string> <string name="add_a_comment">Add a comment…</string>
<string name="dismiss">Dismiss</string> <string name="dismiss">Dismiss</string>
<string name="scan_a_qr_code_to_install">Scan a QR code to install</string> <string name="scan_a_qr_code_to_install">Scan a QR code to install</string>
<string name="toggle_fullscreen">Toggle fullscreen</string> <string name="toggle_fullscreen">Toggle full-screen</string>
<string name="by">By</string> <string name="by">By</string>
<string name="signature">Signature</string> <string name="signature">Signature</string>
<string name="valid">Valid</string> <string name="valid">Valid</string>
@ -382,7 +382,7 @@
<string name="system_volume">System volume</string> <string name="system_volume">System volume</string>
<string name="system_volume_descr">Gesture controls adjust system volume</string> <string name="system_volume_descr">Gesture controls adjust system volume</string>
<string name="live_chat_webview">Live Chat Webview</string> <string name="live_chat_webview">Live Chat Webview</string>
<string name="full_screen_portrait">Fullscreen portrait</string> <string name="full_screen_portrait">Full-screen portrait</string>
<string name="reverse_portrait">Allow reverse portrait</string> <string name="reverse_portrait">Allow reverse portrait</string>
<string name="reverse_portrait_description">Allow app to flip into reverse portrait</string> <string name="reverse_portrait_description">Allow app to flip into reverse portrait</string>
<string name="rotation_zone">Rotation zone</string> <string name="rotation_zone">Rotation zone</string>
@ -396,12 +396,12 @@
<string name="prefer_webm_audio">Prefer Webm Audio Codecs</string> <string name="prefer_webm_audio">Prefer Webm Audio Codecs</string>
<string name="prefer_webm_audio_description">If player should prefer Webm codecs (opus) over mp4 codecs (AAC), may result in worse compatibility.</string> <string name="prefer_webm_audio_description">If player should prefer Webm codecs (opus) over mp4 codecs (AAC), may result in worse compatibility.</string>
<string name="allow_under_cutout">Allow video under cutout</string> <string name="allow_under_cutout">Allow video under cutout</string>
<string name="allow_under_cutout_description">Allow video to go underneath the screen cutout in full-screen.\nMay require restart</string> <string name="allow_under_cutout_description">Allow video to go underneath the screen cutout in full screen.\nMay require restart</string>
<string name="autoplay">Enable autoplay by default</string> <string name="autoplay">Enable autoplay by default</string>
<string name="autoplay_description">Autoplay will be enabled by default whenever you watch a video</string> <string name="autoplay_description">Autoplay will be enabled by default whenever you watch a video</string>
<string name="allow_full_screen_portrait">Allow full-screen portrait</string>
<string name="delete_watchlist_on_finish">Delete from WatchLater when watched</string> <string name="delete_watchlist_on_finish">Delete from WatchLater when watched</string>
<string name="delete_watchlist_on_finish_description">After you leave a video that you mostly watched, it will be removed from watch later.</string> <string name="delete_watchlist_on_finish_description">After you leave a video that you mostly watched, it will be removed from watch later.</string>
<string name="allow_full_screen_portrait">Allow fullscreen portrait</string>
<string name="background_switch_audio">Switch to Audio in Background</string> <string name="background_switch_audio">Switch to Audio in Background</string>
<string name="background_switch_audio_description">Optimize bandwidth usage by switching to audio-only stream in background if available, may cause stutter</string> <string name="background_switch_audio_description">Optimize bandwidth usage by switching to audio-only stream in background if available, may cause stutter</string>
<string name="subscription_group_menu">Groups</string> <string name="subscription_group_menu">Groups</string>