mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-05-01 15:14:29 +02:00
Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay
This commit is contained in:
commit
14b699485a
@ -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"
|
||||||
|
@ -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;
|
||||||
|
|
||||||
|
@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
@ -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)
|
||||||
|
@ -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);
|
||||||
|
@ -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";
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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";
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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>) {
|
||||||
|
@ -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 {}
|
||||||
}
|
}
|
||||||
|
@ -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 {}
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
@ -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 {}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
@ -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;
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>
|
@ -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>
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user