mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-04-20 03:24:50 +00:00
Merge branch 'master' of gitlab.futo.org:videostreaming/grayjay
This commit is contained in:
commit
14b699485a
19 changed files with 587 additions and 575 deletions
|
@ -51,7 +51,6 @@
|
|||
android:name=".activities.MainActivity"
|
||||
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout"
|
||||
android:exported="true"
|
||||
android:screenOrientation="sensorPortrait"
|
||||
android:theme="@style/Theme.FutoVideo.NoActionBar"
|
||||
android:launchMode="singleTask"
|
||||
android:resizeableActivity="true"
|
||||
|
@ -146,11 +145,9 @@
|
|||
<data android:scheme="polycentric" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".activities.TestActivity"
|
||||
android:theme="@style/Theme.FutoVideo.NoActionBar" />
|
||||
|
||||
<activity
|
||||
android:name=".activities.SettingsActivity"
|
||||
android:screenOrientation="sensorPortrait"
|
||||
|
@ -173,7 +170,6 @@
|
|||
android:theme="@style/Theme.FutoVideo.NoActionBar" />
|
||||
<activity
|
||||
android:name=".activities.AddSourceActivity"
|
||||
android:screenOrientation="sensorPortrait"
|
||||
android:exported="true"
|
||||
android:theme="@style/Theme.FutoVideo.NoActionBar">
|
||||
<intent-filter>
|
||||
|
@ -217,7 +213,6 @@
|
|||
android:name=".activities.ManageTabsActivity"
|
||||
android:screenOrientation="sensorPortrait"
|
||||
android:theme="@style/Theme.FutoVideo.NoActionBar" />
|
||||
|
||||
<activity
|
||||
android:name=".activities.QRCaptureActivity"
|
||||
android:screenOrientation="sensorPortrait"
|
||||
|
|
|
@ -2,11 +2,8 @@ package com.futo.platformplayer
|
|||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Context.POWER_SERVICE
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.PowerManager
|
||||
import android.webkit.CookieManager
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
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.StateMeta
|
||||
import com.futo.platformplayer.states.StatePayment
|
||||
import com.futo.platformplayer.states.StatePlayer
|
||||
import com.futo.platformplayer.states.StatePolycentric
|
||||
import com.futo.platformplayer.states.StateUpdate
|
||||
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.FormField
|
||||
import com.futo.platformplayer.views.fields.FormFieldButton
|
||||
import com.futo.platformplayer.views.fields.FormFieldWarning
|
||||
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.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
@ -426,8 +420,6 @@ class Settings : FragmentedStorageFileJson() {
|
|||
@DropdownFieldOptionsId(R.array.system_enabled_disabled_array)
|
||||
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)
|
||||
@DropdownFieldOptionsId(R.array.player_background_behavior)
|
||||
var backgroundPlay: Int = 2;
|
||||
|
@ -483,17 +475,6 @@ class Settings : FragmentedStorageFileJson() {
|
|||
@FormField(R.string.reverse_portrait, FieldForm.TOGGLE, R.string.reverse_portrait_description, 14)
|
||||
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)
|
||||
var preferWebmVideo: Boolean = false;
|
||||
@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();
|
||||
@Serializable
|
||||
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)
|
||||
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.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import com.futo.platformplayer.BuildConfig
|
||||
import com.futo.platformplayer.R
|
||||
import com.futo.platformplayer.Settings
|
||||
|
@ -250,6 +251,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||
super.attachBaseContext(StateApp.instance.getLocaleContext(newBase))
|
||||
}
|
||||
|
||||
@UnstableApi
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
Logger.i(TAG, "MainActivity Starting");
|
||||
StateApp.instance.setGlobalContext(this, lifecycleScope);
|
||||
|
@ -513,6 +515,9 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
|
|||
|
||||
//startActivity(Intent(this, TestActivity::class.java));
|
||||
|
||||
// updates the requestedOrientation based on user settings
|
||||
_fragVideoDetail.updateOrientation()
|
||||
|
||||
val sharedPreferences =
|
||||
getSharedPreferences("GrayjayFirstBoot", Context.MODE_PRIVATE)
|
||||
val isFirstBoot = sharedPreferences.getBoolean("IsFirstBoot", true)
|
||||
|
|
|
@ -7,6 +7,7 @@ import android.annotation.SuppressLint
|
|||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
@ -34,7 +35,7 @@ import kotlin.math.roundToInt
|
|||
class MenuBottomBarFragment : MainActivityFragment() {
|
||||
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);
|
||||
_view = view;
|
||||
return view;
|
||||
|
@ -56,7 +57,13 @@ class MenuBottomBarFragment : MainActivityFragment() {
|
|||
return _view?.onBackPressed() ?: false;
|
||||
}
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
|
||||
_view?.updateAllButtonVisibility()
|
||||
}
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
class MenuBottomBarView : LinearLayout {
|
||||
private val _fragment: MenuBottomBarFragment;
|
||||
private val _inflater: LayoutInflater;
|
||||
|
@ -76,7 +83,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
|
|||
private var _buttonsVisible = 0;
|
||||
private var _subscriptionsVisible = true;
|
||||
|
||||
var currentButtonDefinitions: List<ButtonDefinition>? = null;
|
||||
private var currentButtonDefinitions: List<ButtonDefinition>? = null;
|
||||
|
||||
constructor(fragment: MenuBottomBarFragment, inflater: LayoutInflater) : super(inflater.context) {
|
||||
_fragment = fragment;
|
||||
|
@ -132,7 +139,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
|
|||
val staggerFactor = 3.0f
|
||||
|
||||
if (visible) {
|
||||
moreOverlay.visibility = LinearLayout.VISIBLE
|
||||
moreOverlay.visibility = VISIBLE
|
||||
val animations = arrayListOf<Animator>()
|
||||
animations.add(ObjectAnimator.ofFloat(moreOverlayBackground, "alpha", 0.0f, 1.0f).setDuration(duration))
|
||||
|
||||
|
@ -161,7 +168,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
|
|||
animatorSet.doOnEnd {
|
||||
_moreVisibleAnimating = false
|
||||
_moreVisible = false
|
||||
moreOverlay.visibility = LinearLayout.INVISIBLE
|
||||
moreOverlay.visibility = INVISIBLE
|
||||
}
|
||||
animatorSet.playTogether(animations)
|
||||
animatorSet.start()
|
||||
|
@ -178,7 +185,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
|
|||
_layoutBottomBarButtons.removeAllViews();
|
||||
|
||||
_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()) {
|
||||
|
@ -192,7 +199,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
|
|||
_layoutBottomBarButtons.addView(menuButton)
|
||||
if (index < buttonDefinitions.size - 1) {
|
||||
_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 {
|
||||
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);
|
||||
}
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration?) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
|
||||
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 metrics = StateApp.instance.displayMetrics ?: resources.displayMetrics;
|
||||
val metrics = resources.displayMetrics
|
||||
_buttonsVisible = floor(metrics.widthPixels.toDouble() / 65.dp(resources).toDouble()).roundToInt();
|
||||
if (_buttonsVisible >= defs.size) {
|
||||
updateBottomMenuButtons(defs.toMutableList(), false);
|
||||
|
|
|
@ -4,7 +4,7 @@ import android.content.Context
|
|||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.LinearLayout
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.futo.platformplayer.R
|
||||
import com.futo.platformplayer.Settings
|
||||
|
@ -45,9 +45,7 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
|
|||
private var _videoOptionsOverlay: SlideUpMenuOverlay? = null;
|
||||
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> {
|
||||
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> {
|
||||
val player = StatePlayer.instance.getThumbnailPlayerOrCreate(context);
|
||||
player.modifyState("ThumbnailPlayer", { state -> state.muted = true });
|
||||
player.modifyState("ThumbnailPlayer") { state -> state.muted = true };
|
||||
_exoPlayer = player;
|
||||
|
||||
val v = LinearLayout(context).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 {
|
||||
return PreviewContentListAdapter(context, feedStyle, dataset, player, _previewsEnabled, arrayListOf(), arrayListOf(), shouldShowTimeBar).apply {
|
||||
attachAdapterEvents(this);
|
||||
}
|
||||
}
|
||||
|
@ -142,7 +134,10 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
|
|||
val newQueue = listOf(content) + recyclerData.results
|
||||
.filterIsInstance<IPlatformVideo>()
|
||||
.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);
|
||||
}
|
||||
|
||||
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)
|
||||
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) };
|
||||
}
|
||||
|
||||
override fun createLayoutManager(recyclerResults: RecyclerView, context: Context): LinearLayoutManager {
|
||||
val llmResults = LinearLayoutManager(context);
|
||||
llmResults.orientation = LinearLayoutManager.VERTICAL;
|
||||
return llmResults;
|
||||
override fun createLayoutManager(
|
||||
recyclerResults: RecyclerView,
|
||||
context: Context
|
||||
): GridLayoutManager {
|
||||
val glmResults =
|
||||
GridLayoutManager(
|
||||
context,
|
||||
(resources.configuration.screenWidthDp / resources.getDimension(R.dimen.landscape_threshold)).toInt() + 1
|
||||
);
|
||||
return glmResults
|
||||
}
|
||||
|
||||
override fun onScrollStateChanged(newState: Int) {
|
||||
|
@ -217,11 +213,11 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
|
|||
}
|
||||
|
||||
private fun playPreview() {
|
||||
if(feedStyle == FeedStyle.THUMBNAIL)
|
||||
if(feedStyle == FeedStyle.THUMBNAIL || recyclerData.layoutManager.spanCount > 1)
|
||||
return;
|
||||
|
||||
val firstVisible = recyclerData.layoutManager.findFirstVisibleItemPosition();
|
||||
val lastVisible = recyclerData.layoutManager.findLastVisibleItemPosition();
|
||||
val firstVisible = recyclerData.layoutManager.findFirstVisibleItemPosition()
|
||||
val lastVisible = recyclerData.layoutManager.findLastVisibleItemPosition()
|
||||
val itemsVisible = lastVisible - firstVisible + 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)
|
||||
}
|
||||
|
||||
fun stopVideo() {
|
||||
private fun stopVideo() {
|
||||
//TODO: Is this still necessary?
|
||||
(recyclerData.adapter as PreviewContentListAdapter?)?.stopPreview();
|
||||
}
|
||||
|
@ -269,6 +265,6 @@ abstract class ContentFeedView<TFragment> : FeedView<TFragment, IPlatformContent
|
|||
}
|
||||
|
||||
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.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.ViewGroup.MarginLayoutParams
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.futo.platformplayer.*
|
||||
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.views.FeedStyle
|
||||
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 {
|
||||
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> {
|
||||
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);
|
||||
glmResults.orientation = LinearLayoutManager.VERTICAL;
|
||||
/*
|
||||
* An empty override to remove the inherited span count update functionality
|
||||
*/
|
||||
override fun updateSpanCount(){
|
||||
|
||||
}
|
||||
|
||||
override fun createLayoutManager(
|
||||
recyclerResults: RecyclerView,
|
||||
context: Context
|
||||
): GridLayoutManager {
|
||||
val glmResults = GridLayoutManager(context, 2)
|
||||
|
||||
_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 {
|
||||
private val TAG = "CreatorFeedView";
|
||||
private const val TAG = "CreatorFeedView";
|
||||
}
|
||||
}
|
|
@ -1,13 +1,14 @@
|
|||
package com.futo.platformplayer.fragment.mainactivity.main
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.Color
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.*
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.LayoutManager
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
|
@ -33,7 +34,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
|
|||
protected val _recyclerResults: RecyclerView;
|
||||
protected val _overlayContainer: FrameLayout;
|
||||
protected val _swipeRefresh: SwipeRefreshLayout;
|
||||
private val _progress_bar: ProgressBar;
|
||||
private val _progressBar: ProgressBar;
|
||||
private val _spinnerSortBy: Spinner;
|
||||
private val _containerSortBy: LinearLayout;
|
||||
private val _tagsView: TagsView;
|
||||
|
@ -44,7 +45,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
|
|||
|
||||
private var _loading: Boolean = true;
|
||||
|
||||
private val _pager_lock = Object();
|
||||
private val _pagerLock = Object();
|
||||
private var _cache: ItemCache<TResult>? = null;
|
||||
|
||||
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 _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;
|
||||
|
||||
private val _scrollListener: RecyclerView.OnScrollListener;
|
||||
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;
|
||||
inflater.inflate(R.layout.fragment_feed, this);
|
||||
|
||||
_textCentered = findViewById(R.id.text_centered);
|
||||
_emptyPagerContainer = findViewById(R.id.empty_pager_container);
|
||||
_progress_bar = findViewById(R.id.progress_bar);
|
||||
_progress_bar.inactiveColor = Color.TRANSPARENT;
|
||||
_progressBar = findViewById(R.id.progress_bar);
|
||||
_progressBar.inactiveColor = Color.TRANSPARENT;
|
||||
|
||||
_swipeRefresh = findViewById(R.id.swipe_refresh);
|
||||
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);
|
||||
|
||||
val visibleItemCount = _recyclerResults.childCount;
|
||||
val firstVisibleItem = recyclerData.layoutManager.findFirstVisibleItemPosition();
|
||||
val firstVisibleItem = recyclerData.layoutManager.findFirstVisibleItemPosition()
|
||||
//Logger.i(TAG, "onScrolled loadNextPage visibleItemCount=$visibleItemCount firstVisibleItem=$visibleItemCount")
|
||||
|
||||
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) {
|
||||
val firstVisibleView = layoutManager.findViewByPosition(firstVisibleItemPosition)
|
||||
val itemHeight = firstVisibleView?.height ?: 0
|
||||
val occupiedSpace = recyclerData.results.size * itemHeight
|
||||
val occupiedSpace = recyclerData.results.size / recyclerData.layoutManager.spanCount * itemHeight
|
||||
val recyclerViewHeight = _recyclerResults.height
|
||||
Logger.i(TAG, "ensureEnoughContentVisible loadNextPage occupiedSpace=$occupiedSpace recyclerViewHeight=$recyclerViewHeight")
|
||||
occupiedSpace >= recyclerViewHeight
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
}
|
||||
Logger.i(TAG, "ensureEnoughContentVisible loadNextPage canScroll=$canScroll _automaticNextPageCounter=$_automaticNextPageCounter")
|
||||
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() {
|
||||
updateSpanCount()
|
||||
|
||||
//Reload the pager if the plugin was killed
|
||||
val pager = recyclerData.pager;
|
||||
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>?) {
|
||||
_activeTags = activeTags;
|
||||
|
||||
if (activeTags != null && activeTags.isNotEmpty()) {
|
||||
if (!activeTags.isNullOrEmpty()) {
|
||||
_tagsView.setTags(activeTags);
|
||||
_tagsView.visibility = View.VISIBLE;
|
||||
} else {
|
||||
|
@ -262,7 +274,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
|
|||
protected open fun setSortByOptions(options: List<String>?) {
|
||||
_sortByOptions = options;
|
||||
|
||||
if (options != null && options.isNotEmpty()) {
|
||||
if (!options.isNullOrEmpty()) {
|
||||
val allOptions = arrayListOf<String>();
|
||||
allOptions.add("Default");
|
||||
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 createLayoutManager(recyclerResults: RecyclerView, context: Context): LinearLayoutManager;
|
||||
protected open fun onRestoreCachedData(cachedData: RecyclerData<InsertedViewAdapterWithLoader<TViewHolder>, LinearLayoutManager, TPager, TResult, TConverted, InsertedViewHolder<TViewHolder>>) {}
|
||||
protected abstract fun createLayoutManager(recyclerResults: RecyclerView, context: Context): GridLayoutManager;
|
||||
protected open fun onRestoreCachedData(cachedData: RecyclerData<InsertedViewAdapterWithLoader<TViewHolder>, GridLayoutManager, TPager, TResult, TConverted, InsertedViewHolder<TViewHolder>>) {}
|
||||
|
||||
protected fun setProgress(fin: Int, total: Int) {
|
||||
val progress = (fin.toFloat() / total);
|
||||
_progress_bar.progress = progress;
|
||||
_progressBar.progress = progress;
|
||||
if(progress > 0 && progress < 1)
|
||||
{
|
||||
if(_progress_bar.height == 0)
|
||||
_progress_bar.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 5);
|
||||
if(_progressBar.height == 0)
|
||||
_progressBar.layoutParams = LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 5);
|
||||
}
|
||||
else if(_progress_bar.height > 0) {
|
||||
_progress_bar.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0);
|
||||
else if(_progressBar.height > 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);
|
||||
}
|
||||
fun setPager(pager: TPager, cache: ItemCache<TResult>? = null) {
|
||||
synchronized(_pager_lock) {
|
||||
synchronized(_pagerLock) {
|
||||
detachParentPagerEvents();
|
||||
detachPagerEvents();
|
||||
|
||||
|
@ -425,7 +437,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
|
|||
val p = recyclerData.pager;
|
||||
if(p is IReplacerPager<*>) {
|
||||
p.onReplaced.subscribe(this) { _, newItem ->
|
||||
synchronized(_pager_lock) {
|
||||
synchronized(_pagerLock) {
|
||||
val filtered = filterResults(listOf(newItem as TResult));
|
||||
if(filtered.isEmpty())
|
||||
return@subscribe;
|
||||
|
@ -443,7 +455,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
|
|||
|
||||
var _lastNextPage = false;
|
||||
private fun loadNextPage() {
|
||||
synchronized(_pager_lock) {
|
||||
synchronized(_pagerLock) {
|
||||
val pager: TPager = recyclerData.pager ?: return;
|
||||
val hasMorePages = pager.hasMorePages();
|
||||
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 {
|
||||
private val TAG = "FeedView";
|
||||
private const val TAG = "FeedView";
|
||||
}
|
||||
|
||||
abstract class ItemCache<TResult>(val cachePager: IPager<TResult>) {
|
||||
|
|
|
@ -6,7 +6,7 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.futo.platformplayer.*
|
||||
import com.futo.platformplayer.activities.MainActivity
|
||||
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.ScriptImplementationException
|
||||
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.StateMeta
|
||||
import com.futo.platformplayer.states.StatePlatform
|
||||
import com.futo.platformplayer.states.StateSubscriptions
|
||||
import com.futo.platformplayer.views.FeedStyle
|
||||
import com.futo.platformplayer.views.NoResultsView
|
||||
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.announcements.AnnouncementView
|
||||
import com.futo.platformplayer.views.buttons.BigButton
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import java.time.OffsetDateTime
|
||||
import java.util.UUID
|
||||
|
||||
class HomeFragment : MainFragment() {
|
||||
override val isMainView : Boolean = true;
|
||||
|
@ -44,7 +37,7 @@ class HomeFragment : MainFragment() {
|
|||
override val hasBottomBar: Boolean get() = true;
|
||||
|
||||
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() {
|
||||
_view?.reloadFeed()
|
||||
|
@ -101,15 +94,19 @@ class HomeFragment : MainFragment() {
|
|||
class HomeView : ContentFeedView<HomeFragment> {
|
||||
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>>;
|
||||
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) {
|
||||
_announcementsView = AnnouncementView(context, null).apply {
|
||||
headerView.addView(this);
|
||||
};
|
||||
constructor(fragment: HomeFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, GridLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null) : super(fragment, inflater, cachedRecyclerData) {
|
||||
|
||||
_taskGetPager = TaskHandler<Boolean, IPager<IPlatformContent>>({ fragment.lifecycleScope }, {
|
||||
StatePlatform.instance.getHomeRefresh(fragment.lifecycleScope)
|
||||
|
@ -174,7 +171,7 @@ class HomeFragment : MainFragment() {
|
|||
loadResults();
|
||||
}
|
||||
|
||||
override fun getEmptyPagerView(): View? {
|
||||
override fun getEmptyPagerView(): View {
|
||||
val dp10 = 10.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) {
|
||||
fragment.navigate<SourcesFragment>();
|
||||
}.withMargin(dp10, dp30))
|
||||
);
|
||||
return null;
|
||||
)
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
Logger.i(TAG, "Got new home pager ${pager}");
|
||||
Logger.i(TAG, "Got new home pager $pager");
|
||||
finishRefreshLayoutLoader();
|
||||
setLoading(false);
|
||||
setPager(pager);
|
||||
|
@ -237,7 +233,7 @@ class HomeFragment : MainFragment() {
|
|||
}
|
||||
|
||||
companion object {
|
||||
val TAG = "HomeFragment";
|
||||
const val TAG = "HomeFragment";
|
||||
|
||||
fun newInstance() = HomeFragment().apply {}
|
||||
}
|
||||
|
|
|
@ -5,12 +5,10 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import com.futo.platformplayer.*
|
||||
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.IPlatformContent
|
||||
import com.futo.platformplayer.api.media.models.video.IPlatformVideo
|
||||
|
@ -47,7 +45,6 @@ import kotlinx.coroutines.withContext
|
|||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.encodeToString
|
||||
import kotlinx.serialization.json.Json
|
||||
import java.nio.channels.Channel
|
||||
import java.time.OffsetDateTime
|
||||
import kotlin.system.measureTimeMillis
|
||||
|
||||
|
@ -58,7 +55,7 @@ class SubscriptionsFeedFragment : MainFragment() {
|
|||
|
||||
private var _view: SubscriptionsFeedView? = 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) {
|
||||
super.onShownWithView(parameter, isBack);
|
||||
|
@ -111,7 +108,7 @@ class SubscriptionsFeedFragment : MainFragment() {
|
|||
|
||||
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()");
|
||||
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 isHomeEnabled = homeTab?.enabled == true;
|
||||
if (announcementsView != null && isHomeEnabled) {
|
||||
headerView.removeView(announcementsView);
|
||||
_announcementsView = null;
|
||||
recyclerData.adapter.viewsToPrepend.remove(announcementsView)
|
||||
_announcementsView = null
|
||||
}
|
||||
|
||||
if (announcementsView == null && !isHomeEnabled) {
|
||||
val c = context;
|
||||
if (c != null) {
|
||||
_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 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 }
|
||||
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())
|
||||
throw RateLimitException(rateLimitPlugins.map { it.key.id });
|
||||
}
|
||||
|
@ -277,7 +277,7 @@ class SubscriptionsFeedFragment : MainFragment() {
|
|||
|
||||
private fun initializeToolbarContent() {
|
||||
_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?.onToggleGroup?.subscribe { g ->
|
||||
|
@ -397,7 +397,7 @@ class SubscriptionsFeedFragment : MainFragment() {
|
|||
_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);
|
||||
setEmptyPager(cachedData.results.isEmpty());
|
||||
}
|
||||
|
@ -452,7 +452,7 @@ class SubscriptionsFeedFragment : MainFragment() {
|
|||
if (toShow is PluginException)
|
||||
UIDialogs.appToast(ToastView.Toast(
|
||||
toShow.message +
|
||||
(if(channel != null) "\nChannel: " + channel else ""), false, null,
|
||||
(if(channel != null) "\nChannel: $channel" else ""), false, null,
|
||||
"Plugin ${toShow.config.name} failed")
|
||||
);
|
||||
else
|
||||
|
@ -463,14 +463,14 @@ class SubscriptionsFeedFragment : MainFragment() {
|
|||
val failedChannels = exs.filterIsInstance<ChannelException>().map { it.channelNameOrUrl }.distinct().toList();
|
||||
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 }
|
||||
.filter { it != null }
|
||||
.filterNotNull()
|
||||
.distinctBy { it?.config?.name }
|
||||
.map { it!! }
|
||||
.toList();
|
||||
for(distinctPluginFail in failedPlugins)
|
||||
UIDialogs.appToast(context.getString(R.string.plugin_pluginname_failed_message).replace("{pluginName}", distinctPluginFail.config.name).replace("{message}", distinctPluginFail.message ?: ""));
|
||||
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"));
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
|
@ -482,7 +482,7 @@ class SubscriptionsFeedFragment : MainFragment() {
|
|||
}
|
||||
|
||||
companion object {
|
||||
val TAG = "SubscriptionsFeedFragment";
|
||||
const val TAG = "SubscriptionsFeedFragment";
|
||||
|
||||
fun newInstance() = SubscriptionsFeedFragment().apply {}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import android.view.LayoutInflater
|
|||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.ScrollView
|
||||
import android.widget.TextView
|
||||
import com.futo.platformplayer.*
|
||||
import com.futo.platformplayer.api.media.IPlatformClient
|
||||
|
@ -58,7 +59,15 @@ class TutorialFragment : MainFragment() {
|
|||
}
|
||||
|
||||
@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
|
||||
|
||||
constructor(fragment: TutorialFragment, inflater: LayoutInflater) : super(inflater.context) {
|
||||
|
@ -150,7 +159,7 @@ class TutorialFragment : MainFragment() {
|
|||
}
|
||||
|
||||
companion object {
|
||||
val TAG = "HomeFragment";
|
||||
const val TAG = "HomeFragment";
|
||||
|
||||
fun newInstance() = TutorialFragment().apply {}
|
||||
val initialSetupVideos = listOf(
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
package com.futo.platformplayer.fragment.mainactivity.main
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.pm.ActivityInfo
|
||||
import android.content.res.Configuration
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
@ -14,10 +13,9 @@ import android.view.WindowInsetsController
|
|||
import android.view.WindowManager
|
||||
import androidx.constraintlayout.motion.widget.MotionLayout
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import com.futo.platformplayer.R
|
||||
import com.futo.platformplayer.Settings
|
||||
import com.futo.platformplayer.SimpleOrientationListener
|
||||
import com.futo.platformplayer.UIDialogs
|
||||
import com.futo.platformplayer.activities.SettingsActivity
|
||||
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.constructs.Event0
|
||||
import com.futo.platformplayer.constructs.Event1
|
||||
import com.futo.platformplayer.listeners.AutoRotateChangeListener
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.models.PlatformVideoWithTime
|
||||
import com.futo.platformplayer.models.UrlVideoWithTime
|
||||
import com.futo.platformplayer.states.StatePlayer
|
||||
import com.futo.platformplayer.views.containers.SingleViewTouchableMotionLayout
|
||||
import kotlin.math.min
|
||||
|
||||
|
||||
@UnstableApi
|
||||
class VideoDetailFragment : MainFragment {
|
||||
override val isMainView : Boolean = false;
|
||||
override val hasBottomBar: Boolean = true;
|
||||
|
@ -43,11 +41,13 @@ class VideoDetailFragment : MainFragment {
|
|||
|
||||
private var _viewDetail : VideoDetailView? = 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;
|
||||
/**
|
||||
* 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>();
|
||||
var isTransitioning : Boolean = false
|
||||
private set;
|
||||
|
@ -77,8 +77,7 @@ class VideoDetailFragment : MainFragment {
|
|||
private var _leavingPiP = false;
|
||||
|
||||
//region Fragment
|
||||
constructor() : super() {
|
||||
}
|
||||
constructor() : super()
|
||||
|
||||
fun nextVideo() {
|
||||
_viewDetail?.nextVideo(true, true, true);
|
||||
|
@ -88,65 +87,105 @@ class VideoDetailFragment : MainFragment {
|
|||
_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()
|
||||
}
|
||||
|
||||
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 isMaximized = state == State.MAXIMIZED
|
||||
val isFullScreenPortraitAllowed = Settings.instance.playback.fullscreenPortrait;
|
||||
val bypassRotationPrevention = Settings.instance.other.bypassRotationPrevention;
|
||||
val fullAutorotateLock = Settings.instance.playback.fullAutorotateLock
|
||||
val currentRequestedOrientation = a.requestedOrientation
|
||||
var currentOrientation = if (_currentOrientation == -1) currentRequestedOrientation else _currentOrientation
|
||||
if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT && !Settings.instance.playback.reversePortrait)
|
||||
currentOrientation = currentRequestedOrientation
|
||||
val isFullScreenPortraitAllowed = Settings.instance.playback.fullscreenPortrait
|
||||
val isReversePortraitAllowed = Settings.instance.playback.reversePortrait
|
||||
val rotationLock = StatePlayer.instance.rotationLock
|
||||
|
||||
val isAutoRotate = Settings.instance.playback.isAutoRotate()
|
||||
val isFs = isFullscreen
|
||||
val isLandscapeVideo: Boolean = _viewDetail?.isLandscapeVideo() ?: false
|
||||
|
||||
if (fullAutorotateLock) {
|
||||
if (isFs && isMaximized) {
|
||||
if (isFullScreenPortraitAllowed) {
|
||||
if (isAutoRotate) {
|
||||
a.requestedOrientation = currentOrientation
|
||||
}
|
||||
} else if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE || currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_LANDSCAPE) {
|
||||
if (isAutoRotate || currentOrientation != currentRequestedOrientation && (currentRequestedOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || currentRequestedOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT)) {
|
||||
a.requestedOrientation = currentOrientation
|
||||
}
|
||||
} else {
|
||||
a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
|
||||
}
|
||||
} else if (bypassRotationPrevention) {
|
||||
a.requestedOrientation = currentOrientation
|
||||
} else if (currentOrientation == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || currentOrientation == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT) {
|
||||
a.requestedOrientation = currentOrientation
|
||||
} else {
|
||||
a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||
}
|
||||
val isSmallWindow = isSmallWindow()
|
||||
|
||||
// For small windows if the device isn't landscape right now and full screen portrait isn't allowed then we should force landscape
|
||||
if (isSmallWindow && isFullscreen && !isFullScreenPortraitAllowed && resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT && !rotationLock && isLandscapeVideo) {
|
||||
a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_USER_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
|
||||
else if (isSmallWindow && !isMinimizingFromFullScreen && !isFullscreen && state == State.MAXIMIZED && resources.configuration.orientation == Configuration.ORIENTATION_LANDSCAPE) {
|
||||
a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR_PORTRAIT
|
||||
} else if (rotationLock) {
|
||||
a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
|
||||
} 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
|
||||
when (Settings.instance.playback.autoRotate) {
|
||||
0 -> {
|
||||
a.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LOCKED
|
||||
}
|
||||
|
||||
1 -> {
|
||||
a.requestedOrientation = if (isReversePortraitAllowed) {
|
||||
ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR
|
||||
} else {
|
||||
ActivityInfo.SCREEN_ORIENTATION_SENSOR
|
||||
}
|
||||
}
|
||||
|
||||
2 -> {
|
||||
a.requestedOrientation = if (isReversePortraitAllowed) {
|
||||
ActivityInfo.SCREEN_ORIENTATION_FULL_USER
|
||||
} else {
|
||||
ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED
|
||||
}
|
||||
}
|
||||
} 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}");
|
||||
}
|
||||
|
||||
override fun onShownWithView(parameter: Any?, isBack: Boolean) {
|
||||
|
@ -188,10 +227,6 @@ class VideoDetailFragment : MainFragment {
|
|||
return true;
|
||||
}
|
||||
|
||||
override fun onHide() {
|
||||
super.onHide();
|
||||
}
|
||||
|
||||
fun preventPictureInPicture() {
|
||||
Logger.i(TAG, "preventPictureInPicture() preventPictureInPicture = true");
|
||||
_viewDetail?.preventPictureInPicture = true;
|
||||
|
@ -231,7 +266,9 @@ class VideoDetailFragment : MainFragment {
|
|||
_viewDetail = _view!!.findViewById<VideoDetailView>(R.id.fragview_videodetail).also {
|
||||
it.applyFragment(this);
|
||||
it.onFullscreenChanged.subscribe(::onFullscreenChanged);
|
||||
it.onVideoChanged.subscribe(::onVideoChanged)
|
||||
it.onMinimize.subscribe {
|
||||
isMinimizingFromFullScreen = true
|
||||
_view!!.transitionToStart();
|
||||
};
|
||||
it.onClose.subscribe {
|
||||
|
@ -268,6 +305,7 @@ class VideoDetailFragment : MainFragment {
|
|||
|
||||
if (state != State.MINIMIZED && progress < 0.1) {
|
||||
state = State.MINIMIZED;
|
||||
isMinimizingFromFullScreen = false
|
||||
onMinimize.emit();
|
||||
}
|
||||
else if (state != State.MAXIMIZED && progress > 0.9) {
|
||||
|
@ -306,13 +344,6 @@ class VideoDetailFragment : MainFragment {
|
|||
minimizeVideoDetail();
|
||||
}
|
||||
|
||||
_autoRotateChangeListener = AutoRotateChangeListener(requireContext(), Handler()) { _ ->
|
||||
if (updateAutoFullscreen()) {
|
||||
return@AutoRotateChangeListener
|
||||
}
|
||||
updateOrientation()
|
||||
}
|
||||
|
||||
_loadUrlOnCreate?.let { _viewDetail?.setVideo(it.url, it.timeSeconds, it.playWhenReady) };
|
||||
maximizeVideoDetail();
|
||||
|
||||
|
@ -321,40 +352,11 @@ class VideoDetailFragment : MainFragment {
|
|||
}
|
||||
|
||||
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()
|
||||
}
|
||||
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() {
|
||||
val viewDetail = _viewDetail;
|
||||
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) {
|
||||
_viewDetail?.onStop();
|
||||
StateCasting.instance.onStop();
|
||||
Logger.v(TAG, "called onStop() shouldStop: $shouldStop");
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroyMainView() {
|
||||
super.onDestroyMainView();
|
||||
Logger.v(TAG, "onDestroyMainView");
|
||||
_autoRotateChangeListener?.unregister()
|
||||
_orientationListener.stopListening()
|
||||
|
||||
SettingsActivity.settingsActivityClosed.remove(this)
|
||||
StatePlayer.instance.onRotationLockChanged.remove(this)
|
||||
|
@ -532,7 +531,7 @@ class VideoDetailFragment : MainFragment {
|
|||
}
|
||||
|
||||
companion object {
|
||||
private val TAG = "VideoDetailFragment";
|
||||
private const val TAG = "VideoDetailFragment";
|
||||
|
||||
fun newInstance() = VideoDetailFragment().apply {}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import android.app.PictureInPictureParams
|
|||
import android.app.RemoteAction
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.res.Configuration
|
||||
import android.content.res.Resources
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Rect
|
||||
|
@ -81,6 +82,7 @@ import com.futo.platformplayer.casting.CastConnectionState
|
|||
import com.futo.platformplayer.casting.StateCasting
|
||||
import com.futo.platformplayer.constructs.Event0
|
||||
import com.futo.platformplayer.constructs.Event1
|
||||
import com.futo.platformplayer.constructs.Event2
|
||||
import com.futo.platformplayer.constructs.TaskHandler
|
||||
import com.futo.platformplayer.downloads.VideoLocal
|
||||
import com.futo.platformplayer.dp
|
||||
|
@ -159,20 +161,20 @@ import com.futo.polycentric.core.Opinion
|
|||
import com.futo.polycentric.core.toURLInfoSystemLinkUrl
|
||||
import com.google.protobuf.ByteString
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import okhttp3.Dispatcher
|
||||
import org.w3c.dom.Text
|
||||
import userpackage.Protocol
|
||||
import java.time.OffsetDateTime
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.max
|
||||
import kotlin.math.roundToLong
|
||||
|
||||
@androidx.media3.common.util.UnstableApi
|
||||
@UnstableApi
|
||||
class VideoDetailView : ConstraintLayout {
|
||||
private val TAG = "VideoDetailView"
|
||||
|
||||
|
@ -185,7 +187,7 @@ class VideoDetailView : ConstraintLayout {
|
|||
private var _searchVideo: IPlatformVideo? = null;
|
||||
var video: IPlatformVideoDetails? = null
|
||||
private set;
|
||||
var videoLocal: VideoLocal? = null;
|
||||
private var videoLocal: VideoLocal? = null;
|
||||
private var _playbackTracker: IPlaybackTracker? = null;
|
||||
private var _historyIndex: DBHistory.Index? = null;
|
||||
|
||||
|
@ -200,7 +202,7 @@ class VideoDetailView : ConstraintLayout {
|
|||
private val _timeBar: TimeBar;
|
||||
private var _upNext: UpNextView;
|
||||
|
||||
val rootView: ConstraintLayout;
|
||||
private val rootView: ConstraintLayout;
|
||||
|
||||
private val _title: TextView;
|
||||
private val _subTitle: TextView;
|
||||
|
@ -289,7 +291,7 @@ class VideoDetailView : ConstraintLayout {
|
|||
|
||||
var isPlaying: Boolean = false
|
||||
private set;
|
||||
var lastPositionMilliseconds: Long = 0
|
||||
private var lastPositionMilliseconds: Long = 0
|
||||
private set;
|
||||
private var _historicalPosition: Long = 0;
|
||||
private var _commentsCount = 0;
|
||||
|
@ -304,6 +306,7 @@ class VideoDetailView : ConstraintLayout {
|
|||
val onFullscreenChanged = Event1<Boolean>();
|
||||
val onEnterPictureInPicture = Event0();
|
||||
val onPlayChanged = Event1<Boolean>();
|
||||
val onVideoChanged = Event2<Int, Int>()
|
||||
|
||||
var allowBackground : Boolean = false
|
||||
private set;
|
||||
|
@ -529,12 +532,14 @@ class VideoDetailView : ConstraintLayout {
|
|||
_cast.onChapterChanged.subscribe(onChapterChanged);
|
||||
|
||||
_cast.onMinimizeClick.subscribe {
|
||||
_player.setFullScreen(false);
|
||||
onMinimize.emit();
|
||||
// emit minimize before toggling fullscreen so we know that the full screen toggle is happening during a minimize operation
|
||||
onMinimize.emit()
|
||||
_player.setFullScreen(false)
|
||||
};
|
||||
_player.onMinimize.subscribe {
|
||||
_player.setFullScreen(false);
|
||||
onMinimize.emit();
|
||||
// emit minimize before toggling fullscreen so we know that the full screen toggle is happening during a minimize operation
|
||||
onMinimize.emit()
|
||||
_player.setFullScreen(false)
|
||||
};
|
||||
|
||||
_player.onTimeBarChanged.subscribe { position, _ ->
|
||||
|
@ -723,7 +728,8 @@ class VideoDetailView : ConstraintLayout {
|
|||
|
||||
if (c is PolycentricPlatformComment) {
|
||||
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) },
|
||||
{
|
||||
val newComment = parentComment.cloneWithUpdatedReplyCount((parentComment.replyCount ?: 0) + 1);
|
||||
|
@ -731,7 +737,7 @@ class VideoDetailView : ConstraintLayout {
|
|||
parentComment = newComment;
|
||||
});
|
||||
} 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);
|
||||
};
|
||||
|
@ -941,7 +947,7 @@ class VideoDetailView : ConstraintLayout {
|
|||
else {
|
||||
val selectedButtons = _buttonPinStore.getAllValues()
|
||||
.map { x-> buttons.find { it.tagRef == x } }
|
||||
.filter { it != null }
|
||||
.filterNotNull()
|
||||
.map { it!! };
|
||||
_buttonPins.setButtons(*(selectedButtons +
|
||||
buttons.filter { !selectedButtons.contains(it) } +
|
||||
|
@ -1257,7 +1263,8 @@ class VideoDetailView : ConstraintLayout {
|
|||
|
||||
switchContentView(_container_content_main);
|
||||
}
|
||||
//@OptIn(ExperimentalCoroutinesApi::class)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
fun setVideoDetails(videoDetail: IPlatformVideoDetails, newVideo: Boolean = false) {
|
||||
Logger.i(TAG, "setVideoDetails (${videoDetail.name})")
|
||||
_didTriggerDatasourceErrroCount = 0;
|
||||
|
@ -1265,7 +1272,7 @@ class VideoDetailView : ConstraintLayout {
|
|||
_autoplayVideo = null
|
||||
Logger.i(TAG, "Autoplay video cleared (setVideoDetails)")
|
||||
|
||||
if(newVideo && this.video?.url == videoDetail.url)
|
||||
if (newVideo && this.video?.url == videoDetail.url)
|
||||
return;
|
||||
|
||||
if (newVideo) {
|
||||
|
@ -1274,8 +1281,13 @@ class VideoDetailView : ConstraintLayout {
|
|||
_lastSubtitleSource = null;
|
||||
}
|
||||
|
||||
if(videoDetail.datetime != null && videoDetail.datetime!! > OffsetDateTime.now())
|
||||
UIDialogs.toast(context, context.getString(R.string.planned_in) + " ${videoDetail.datetime?.toHumanNowDiffString(true)}")
|
||||
if (videoDetail.datetime != null && videoDetail.datetime!! > OffsetDateTime.now())
|
||||
UIDialogs.toast(
|
||||
context,
|
||||
context.getString(R.string.planned_in) + " ${
|
||||
videoDetail.datetime?.toHumanNowDiffString(true)
|
||||
}"
|
||||
)
|
||||
|
||||
if (!videoDetail.isLive) {
|
||||
_player.setPlaybackRate(Settings.instance.playback.getDefaultPlaybackSpeed());
|
||||
|
@ -1284,26 +1296,25 @@ class VideoDetailView : ConstraintLayout {
|
|||
val videoLocal: VideoLocal?;
|
||||
val video: IPlatformVideoDetails?;
|
||||
|
||||
if(videoDetail is VideoLocal) {
|
||||
if (videoDetail is VideoLocal) {
|
||||
videoLocal = videoDetail;
|
||||
video = videoDetail;
|
||||
this.video = video;
|
||||
val videoTask = StatePlatform.instance.getContentDetails(videoDetail.url);
|
||||
videoTask.invokeOnCompletion { ex ->
|
||||
if(ex != null) {
|
||||
if (ex != null) {
|
||||
Logger.e(TAG, "Failed to fetch live video for offline video", ex);
|
||||
return@invokeOnCompletion;
|
||||
}
|
||||
val result = videoTask.getCompleted();
|
||||
if(this.video == videoDetail && result is IPlatformVideoDetails) {
|
||||
if (this.video == videoDetail && result is IPlatformVideoDetails) {
|
||||
this.video = result;
|
||||
fragment.lifecycleScope.launch(Dispatchers.Main) {
|
||||
updateQualitySourcesOverlay(result, videoLocal);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
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);
|
||||
video = videoDetail;
|
||||
}
|
||||
|
@ -1311,7 +1322,9 @@ class VideoDetailView : ConstraintLayout {
|
|||
this.video = video;
|
||||
cleanupPlaybackTracker();
|
||||
|
||||
if(video is JSVideoDetails) {
|
||||
onVideoChanged.emit(video.video.videoSources[0].width, video.video.videoSources[0].height)
|
||||
|
||||
if (video is JSVideoDetails) {
|
||||
val me = this;
|
||||
fragment.lifecycleScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
|
@ -1319,8 +1332,7 @@ class VideoDetailView : ConstraintLayout {
|
|||
val chapters = null ?: StatePlatform.instance.getContentChapters(video.url);
|
||||
_player.setChapters(chapters);
|
||||
_cast.setChapters(chapters);
|
||||
}
|
||||
catch(ex: Throwable) {
|
||||
} catch (ex: Throwable) {
|
||||
Logger.e(TAG, "Failed to get chapters", ex);
|
||||
_player.setChapters(null);
|
||||
_cast.setChapters(null);
|
||||
|
@ -1330,7 +1342,7 @@ class VideoDetailView : ConstraintLayout {
|
|||
}*/
|
||||
}
|
||||
try {
|
||||
if(!StateApp.instance.privateMode) {
|
||||
if (!StateApp.instance.privateMode) {
|
||||
val stopwatch = com.futo.platformplayer.debug.Stopwatch()
|
||||
var tracker = video.getPlaybackTracker()
|
||||
Logger.i(TAG, "video.getPlaybackTracker took ${stopwatch.elapsedMs}ms")
|
||||
|
@ -1346,17 +1358,20 @@ class VideoDetailView : ConstraintLayout {
|
|||
|
||||
if (me.video == video)
|
||||
me._playbackTracker = tracker;
|
||||
}
|
||||
else if(me.video == video)
|
||||
} else if (me.video == video)
|
||||
me._playbackTracker = null;
|
||||
}
|
||||
catch(ex: Throwable) {
|
||||
} catch (ex: Throwable) {
|
||||
Logger.e(TAG, "Playback tracker failed", ex);
|
||||
|
||||
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));
|
||||
};
|
||||
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
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1373,8 +1388,11 @@ class VideoDetailView : ConstraintLayout {
|
|||
if (Settings.instance.comments.recommendationsDefault && !Settings.instance.comments.hideRecommendations) {
|
||||
setTabIndex(2, true)
|
||||
} else {
|
||||
when(Settings.instance.comments.defaultCommentSection) {
|
||||
0 -> if(Settings.instance.other.polycentricEnabled) setTabIndex(0, true) else setTabIndex(1, true);
|
||||
when (Settings.instance.comments.defaultCommentSection) {
|
||||
0 -> if (Settings.instance.other.polycentricEnabled) setTabIndex(
|
||||
0,
|
||||
true
|
||||
) else setTabIndex(1, true);
|
||||
1 -> setTabIndex(1, true);
|
||||
2 -> setTabIndex(StateMeta.instance.getLastCommentSection(), true)
|
||||
}
|
||||
|
@ -1384,9 +1402,16 @@ class VideoDetailView : ConstraintLayout {
|
|||
//UI
|
||||
_title.text = video.name;
|
||||
_channelName.text = video.author.name;
|
||||
if(video.author.subscribers != null) {
|
||||
_channelMeta.text = if((video.author.subscribers ?: 0) > 0) video.author.subscribers!!.toHumanNumber() + " " + context.getString(R.string.subscribers) else "";
|
||||
(_channelName.layoutParams as MarginLayoutParams).setMargins(0, (DP_5 * -1).toInt(), 0, 0);
|
||||
if (video.author.subscribers != null) {
|
||||
_channelMeta.text = if ((video.author.subscribers
|
||||
?: 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 {
|
||||
_channelMeta.text = "";
|
||||
(_channelName.layoutParams as MarginLayoutParams).setMargins(0, (DP_2).toInt(), 0, 0);
|
||||
|
@ -1394,7 +1419,7 @@ class VideoDetailView : ConstraintLayout {
|
|||
|
||||
|
||||
video.author.let {
|
||||
if(it is PlatformAuthorMembershipLink && !it.membershipUrl.isNullOrEmpty())
|
||||
if (it is PlatformAuthorMembershipLink && !it.membershipUrl.isNullOrEmpty())
|
||||
_monetization.setPlatformMembership(video.id.pluginId, it.membershipUrl);
|
||||
else
|
||||
_monetization.setPlatformMembership(null, null);
|
||||
|
@ -1408,7 +1433,8 @@ class VideoDetailView : ConstraintLayout {
|
|||
_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) {
|
||||
setPolycentricProfile(cachedPolycentricProfile, animate = false);
|
||||
} else {
|
||||
|
@ -1417,13 +1443,19 @@ class VideoDetailView : ConstraintLayout {
|
|||
}
|
||||
|
||||
_platform.setPlatformFromClientID(video.id.pluginId);
|
||||
val subTitleSegments : ArrayList<String> = ArrayList();
|
||||
if(video.viewCount > 0)
|
||||
subTitleSegments.add("${video.viewCount.toHumanNumber()} ${if(video.isLive) context.getString(R.string.watching_now) else context.getString(R.string.views)}");
|
||||
if(video.datetime != null) {
|
||||
val subTitleSegments: ArrayList<String> = ArrayList();
|
||||
if (video.viewCount > 0)
|
||||
subTitleSegments.add(
|
||||
"${video.viewCount.toHumanNumber()} ${
|
||||
if (video.isLive) context.getString(
|
||||
R.string.watching_now
|
||||
) else context.getString(R.string.views)
|
||||
}"
|
||||
);
|
||||
if (video.datetime != null) {
|
||||
val diff = video.datetime?.getNowDiffSeconds() ?: 0;
|
||||
val ago = video.datetime?.toHumanNowDiffString(true)
|
||||
if(diff >= 0)
|
||||
if (diff >= 0)
|
||||
subTitleSegments.add("${ago} ago");
|
||||
else
|
||||
subTitleSegments.add("available in ${ago}");
|
||||
|
@ -1436,20 +1468,27 @@ class VideoDetailView : ConstraintLayout {
|
|||
|
||||
fragment.lifecycleScope.launch(Dispatchers.IO) {
|
||||
try {
|
||||
val queryReferencesResponse = ApiMethods.getQueryReferences(PolycentricCache.SERVER, ref, null,null,
|
||||
val queryReferencesResponse = ApiMethods.getQueryReferences(
|
||||
PolycentricCache.SERVER, ref, null, null,
|
||||
arrayListOf(
|
||||
Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder().setFromType(ContentType.OPINION.value).setValue(
|
||||
ByteString.copyFrom(Opinion.like.data)).build(),
|
||||
Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder().setFromType(ContentType.OPINION.value).setValue(
|
||||
ByteString.copyFrom(Opinion.dislike.data)).build()
|
||||
Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder()
|
||||
.setFromType(ContentType.OPINION.value).setValue(
|
||||
ByteString.copyFrom(Opinion.like.data)
|
||||
).build(),
|
||||
Protocol.QueryReferencesRequestCountLWWElementReferences.newBuilder()
|
||||
.setFromType(ContentType.OPINION.value).setValue(
|
||||
ByteString.copyFrom(Opinion.dislike.data)
|
||||
).build()
|
||||
),
|
||||
extraByteReferences = listOfNotNull(extraBytesRef)
|
||||
);
|
||||
|
||||
val likes = queryReferencesResponse.countsList[0];
|
||||
val dislikes = queryReferencesResponse.countsList[1];
|
||||
val hasLiked = 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*/;
|
||||
val hasLiked =
|
||||
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) {
|
||||
_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) {
|
||||
|
@ -1495,6 +1538,7 @@ class VideoDetailView : ConstraintLayout {
|
|||
_textDislikes.visibility = View.VISIBLE;
|
||||
_textDislikes.text = r.dislikes.toHumanNumber();
|
||||
}
|
||||
|
||||
is RatingLikes -> {
|
||||
val r = video.rating as RatingLikes;
|
||||
_layoutRating.visibility = View.VISIBLE;
|
||||
|
@ -1506,6 +1550,7 @@ class VideoDetailView : ConstraintLayout {
|
|||
_imageDislikeIcon.visibility = View.GONE;
|
||||
_textDislikes.visibility = View.GONE;
|
||||
}
|
||||
|
||||
else -> {
|
||||
_layoutRating.visibility = View.GONE;
|
||||
}
|
||||
|
@ -1517,6 +1562,7 @@ class VideoDetailView : ConstraintLayout {
|
|||
|
||||
setLoading(false);
|
||||
|
||||
|
||||
//Set Mediasource
|
||||
|
||||
val toResume = _videoResumePositionMilliseconds;
|
||||
|
@ -1533,9 +1579,22 @@ class VideoDetailView : ConstraintLayout {
|
|||
val historyItem = getHistoryIndex(videoDetail) ?: return@launch;
|
||||
|
||||
withContext(Dispatchers.Main) {
|
||||
_historicalPosition = StateHistory.instance.updateHistoryPosition(video, 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) {
|
||||
_historicalPosition = StateHistory.instance.updateHistoryPosition(
|
||||
video,
|
||||
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;
|
||||
_textResume.text = "Resume at ${_historicalPosition.toHumanTime(false)}";
|
||||
|
||||
|
@ -1561,10 +1620,10 @@ class VideoDetailView : ConstraintLayout {
|
|||
|
||||
_liveChat?.stop();
|
||||
_liveChat = null;
|
||||
if(video.isLive && video.live != null) {
|
||||
if (video.isLive && video.live != null) {
|
||||
loadLiveChat(video);
|
||||
}
|
||||
if(video.isLive && video.live == null && !video.video.videoSources.any())
|
||||
if (video.isLive && video.live == null && !video.video.videoSources.any())
|
||||
startLiveTry(video);
|
||||
|
||||
|
||||
|
@ -1945,7 +2004,7 @@ class VideoDetailView : ConstraintLayout {
|
|||
?.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 }))
|
||||
?.distinct()
|
||||
?.filter { it != null }
|
||||
?.filterNotNull()
|
||||
?.toList() ?: listOf() else videoSources?.toList() ?: listOf()
|
||||
val bestAudioContainer = audioSources?.let { VideoHelper.selectBestAudioSource(it, FutoVideoPlayerBase.PREFERED_AUDIO_CONTAINERS)?.container };
|
||||
val bestAudioSources = if(doDedup) audioSources
|
||||
|
@ -2246,7 +2305,7 @@ class VideoDetailView : ConstraintLayout {
|
|||
cleanupPlaybackTracker();
|
||||
|
||||
val url = _url;
|
||||
if (url != null && url.isNotBlank()) {
|
||||
if (!url.isNullOrBlank()) {
|
||||
setLoading(true);
|
||||
_taskLoadVideo.run(url);
|
||||
}
|
||||
|
@ -2258,7 +2317,7 @@ class VideoDetailView : ConstraintLayout {
|
|||
if(fullscreen) {
|
||||
_layoutPlayerContainer.setPadding(0, 0, 0, 0);
|
||||
|
||||
val lp = _container_content.layoutParams as ConstraintLayout.LayoutParams;
|
||||
val lp = _container_content.layoutParams as LayoutParams;
|
||||
lp.topMargin = 0;
|
||||
_container_content.layoutParams = lp;
|
||||
|
||||
|
@ -2271,7 +2330,7 @@ class VideoDetailView : ConstraintLayout {
|
|||
else {
|
||||
_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();
|
||||
_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) {
|
||||
Logger.i(TAG, "setFullscreen(fullscreen=$fullscreen)")
|
||||
_player.setFullScreen(fullscreen);
|
||||
_player.setFullScreen(fullscreen)
|
||||
}
|
||||
private fun setLoading(isLoading : Boolean) {
|
||||
if(isLoading){
|
||||
|
@ -2505,7 +2575,8 @@ class VideoDetailView : ConstraintLayout {
|
|||
_overlayContainer.removeAllViews();
|
||||
_overlay_quality_selector?.hide();
|
||||
|
||||
_player.fillHeight();
|
||||
_player.setFullScreen(true)
|
||||
_player.fillHeight(false)
|
||||
_layoutPlayerContainer.setPadding(0, 0, 0, 0);
|
||||
}
|
||||
fun handleLeavePictureInPicture() {
|
||||
|
@ -2641,7 +2712,7 @@ class VideoDetailView : ConstraintLayout {
|
|||
else {
|
||||
if(_player.layoutParams.height == WRAP_CONTENT) {
|
||||
_player.layoutParams = FrameLayout.LayoutParams(MATCH_PARENT, MATCH_PARENT);
|
||||
_player.fillHeight();
|
||||
_player.fillHeight(true)
|
||||
_cast.layoutParams = _cast.layoutParams.apply {
|
||||
(this as MarginLayoutParams).bottomMargin = 0;
|
||||
};
|
||||
|
@ -2714,13 +2785,24 @@ class VideoDetailView : ConstraintLayout {
|
|||
if(_minimize_controls.isClickable != clickable)
|
||||
_minimize_controls.isClickable = clickable;
|
||||
}
|
||||
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);
|
||||
|
||||
override fun onConfigurationChanged(newConfig: Configuration?) {
|
||||
super.onConfigurationChanged(newConfig)
|
||||
if (fragment.state == VideoDetailFragment.State.MINIMIZED) {
|
||||
_player.fillHeight(true)
|
||||
} else if (!fragment.isFullscreen) {
|
||||
_player.fitHeight()
|
||||
}
|
||||
}
|
||||
fun setTopPadding(value : Float) {
|
||||
_player.setPadding(0, value.toInt(), _player.paddingRight, 0);
|
||||
|
||||
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) {
|
||||
_player.setPadding(_player.paddingLeft, value.toInt(), _player.paddingRight, 0)
|
||||
}
|
||||
|
||||
//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.lifecycleScope
|
||||
import com.futo.platformplayer.R
|
||||
import com.futo.platformplayer.constructs.Event0
|
||||
import com.futo.platformplayer.dp
|
||||
import com.futo.platformplayer.logging.Logger
|
||||
import com.futo.platformplayer.states.Announcement
|
||||
|
@ -35,6 +36,8 @@ class AnnouncementView : LinearLayout {
|
|||
private val _category: String?;
|
||||
private var _currentAnnouncement: Announcement? = null;
|
||||
|
||||
val onClose = Event0();
|
||||
|
||||
private val _scope: CoroutineScope?;
|
||||
|
||||
constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) {
|
||||
|
@ -101,6 +104,10 @@ class AnnouncementView : LinearLayout {
|
|||
setAnnouncement(announcements.firstOrNull(), announcements.size);
|
||||
}
|
||||
|
||||
fun isClosed(): Boolean{
|
||||
return _currentAnnouncement == null
|
||||
}
|
||||
|
||||
private fun setAnnouncement(announcement: Announcement?, count: Int) {
|
||||
if(count == 0 && announcement == null)
|
||||
Logger.i(TAG, "setAnnouncement announcement=$announcement count=$count");
|
||||
|
@ -108,11 +115,12 @@ class AnnouncementView : LinearLayout {
|
|||
_currentAnnouncement = announcement;
|
||||
|
||||
if (announcement == null) {
|
||||
_root.visibility = View.GONE;
|
||||
visibility = View.GONE
|
||||
onClose.emit()
|
||||
return;
|
||||
}
|
||||
|
||||
_root.visibility = View.VISIBLE;
|
||||
visibility = View.VISIBLE
|
||||
|
||||
_textTitle.text = announcement.title;
|
||||
_textBody.text = announcement.msg;
|
||||
|
|
|
@ -20,7 +20,6 @@ import androidx.annotation.OptIn
|
|||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.setMargins
|
||||
import androidx.media3.common.C
|
||||
import androidx.media3.common.PlaybackParameters
|
||||
import androidx.media3.common.VideoSize
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
|
@ -111,7 +110,9 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
|||
private val _author_fullscreen: TextView;
|
||||
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 _isControlsLocked: Boolean = false;
|
||||
|
@ -632,7 +633,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
|||
|
||||
private fun fitOrFill(fullScreen: Boolean) {
|
||||
if (fullScreen) {
|
||||
fillHeight();
|
||||
fillHeight(false);
|
||||
} else {
|
||||
fitHeight();
|
||||
}
|
||||
|
@ -655,7 +656,7 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
|||
gestureControl.resetZoomPan()
|
||||
_lastSourceFit = null;
|
||||
if(isFullScreen)
|
||||
fillHeight();
|
||||
fillHeight(false);
|
||||
else if(_root.layoutParams.height != MATCH_PARENT)
|
||||
fitHeight(videoSize);
|
||||
}
|
||||
|
@ -718,58 +719,73 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
|||
|
||||
//Sizing
|
||||
@OptIn(UnstableApi::class)
|
||||
fun fitHeight(videoSize : VideoSize? = null){
|
||||
Logger.i(TAG, "Video Fit Height");
|
||||
if(_originalBottomMargin != 0) {
|
||||
val layoutParams = _videoView.layoutParams as ConstraintLayout.LayoutParams;
|
||||
layoutParams.setMargins(0, 0, 0, _originalBottomMargin);
|
||||
_videoView.layoutParams = layoutParams;
|
||||
fun fitHeight(videoSize: VideoSize? = null) {
|
||||
Logger.i(TAG, "Video Fit Height")
|
||||
if (_originalBottomMargin != 0) {
|
||||
val layoutParams = _videoView.layoutParams as ConstraintLayout.LayoutParams
|
||||
layoutParams.setMargins(0, 0, 0, _originalBottomMargin)
|
||||
_videoView.layoutParams = layoutParams
|
||||
}
|
||||
|
||||
var h = videoSize?.height ?: lastVideoSource?.height ?: exoPlayer?.player?.videoSize?.height ?: 0;
|
||||
var w = videoSize?.width ?: lastVideoSource?.width ?: exoPlayer?.player?.videoSize?.width ?: 0;
|
||||
var h = videoSize?.height ?: lastVideoSource?.height ?: exoPlayer?.player?.videoSize?.height
|
||||
?: 0
|
||||
var w =
|
||||
videoSize?.width ?: lastVideoSource?.width ?: exoPlayer?.player?.videoSize?.width ?: 0
|
||||
|
||||
if(h == 0 && w == 0) {
|
||||
Logger.i(TAG, "UNKNOWN VIDEO FIT: (videoSize: ${videoSize != null}, player.videoSize: ${exoPlayer?.player?.videoSize != null})");
|
||||
w = 1280;
|
||||
h = 720;
|
||||
if (h == 0 && w == 0) {
|
||||
Logger.i(
|
||||
TAG,
|
||||
"UNKNOWN VIDEO FIT: (videoSize: ${videoSize != null}, player.videoSize: ${exoPlayer?.player?.videoSize != null})"
|
||||
);
|
||||
w = 1280
|
||||
h = 720
|
||||
}
|
||||
|
||||
val configuration = resources.configuration
|
||||
|
||||
if(_lastSourceFit == null){
|
||||
val metrics = StateApp.instance.displayMetrics ?: resources.displayMetrics;
|
||||
val windowWidth = configuration.screenWidthDp
|
||||
val windowHeight = configuration.screenHeightDp
|
||||
|
||||
val viewWidth = Math.min(metrics.widthPixels, metrics.heightPixels); //TODO: Get parent width. was this.width
|
||||
val deviceHeight = Math.max(metrics.widthPixels, metrics.heightPixels);
|
||||
val maxHeight = deviceHeight * 0.4;
|
||||
if (_lastSourceFit == null || windowWidth != _lastWindowWidth || windowHeight != _lastWindowHeight) {
|
||||
val maxHeight = windowHeight * 0.4f
|
||||
|
||||
val determinedHeight = if(w > h)
|
||||
((h * (viewWidth.toDouble() / w)).toInt())
|
||||
val aspectRatio = h.toFloat() / w
|
||||
val determinedHeight = (aspectRatio * windowWidth)
|
||||
|
||||
_lastSourceFit = determinedHeight
|
||||
_lastSourceFit = _lastSourceFit!!.coerceAtLeast(220f)
|
||||
_lastSourceFit = _lastSourceFit!!.coerceAtMost(maxHeight)
|
||||
|
||||
_desiredResizeModePortrait = if (_lastSourceFit != determinedHeight)
|
||||
AspectRatioFrameLayout.RESIZE_MODE_FIT
|
||||
else
|
||||
((h * (viewWidth.toDouble() / w)).toInt());
|
||||
_lastSourceFit = determinedHeight;
|
||||
_lastSourceFit = Math.max(_lastSourceFit!!, 250);
|
||||
_lastSourceFit = Math.min(_lastSourceFit!!, maxHeight.toInt());
|
||||
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
|
||||
}
|
||||
AspectRatioFrameLayout.RESIZE_MODE_ZOOM
|
||||
|
||||
val marginBottom = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 7f, resources.displayMetrics).toInt();
|
||||
val rootParams = LayoutParams(LayoutParams.MATCH_PARENT, _lastSourceFit!! + marginBottom)
|
||||
rootParams.bottomMargin = marginBottom;
|
||||
_lastWindowWidth = windowWidth
|
||||
_lastWindowHeight = windowHeight
|
||||
}
|
||||
_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;
|
||||
isFitMode = true
|
||||
}
|
||||
fun fillHeight(){
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
fun fillHeight(isMiniPlayer: Boolean) {
|
||||
Logger.i(TAG, "Video Fill Height");
|
||||
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);
|
||||
_videoView.layoutParams = layoutParams;
|
||||
_videoView.invalidate();
|
||||
|
@ -777,6 +793,11 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
|
|||
val rootParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
|
||||
_root.layoutParams = rootParams;
|
||||
_root.invalidate();
|
||||
|
||||
if(isMiniPlayer){
|
||||
_videoView.resizeMode = AspectRatioFrameLayout.RESIZE_MODE_ZOOM
|
||||
}
|
||||
|
||||
isFitMode = false;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<?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"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
|
@ -12,95 +13,108 @@
|
|||
android:contentDescription="@string/cd_button_back"
|
||||
android:padding="10dp"
|
||||
android:scaleType="fitCenter"
|
||||
app:srcCompat="@drawable/ic_back_thin_white_16dp"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintLeft_toLeftOf="parent" />
|
||||
app:srcCompat="@drawable/ic_back_thin_white_16dp" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/button_help"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:contentDescription="@string/cd_button_help"
|
||||
app:srcCompat="@drawable/ic_help"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/ic_help" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image_polycentric"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
app:srcCompat="@drawable/neopass"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0px"
|
||||
app:layout_constraintTop_toBottomOf="@id/button_help"
|
||||
android:layout_marginTop="40dp"/>
|
||||
app:layout_constraintBottom_toBottomOf="parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_polycentric"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/polycentric"
|
||||
android:fontFamily="@font/inter_light"
|
||||
android:textSize="32dp"
|
||||
android:layout_marginTop="12dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/image_polycentric"
|
||||
app:layout_constraintLeft_toLeftOf="@id/image_polycentric"
|
||||
app:layout_constraintRight_toRightOf="@id/image_polycentric" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_profile_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/profile_name"
|
||||
android:fontFamily="@font/inter_light"
|
||||
android:textSize="16dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/edit_profile_name"
|
||||
app:layout_constraintLeft_toLeftOf="@id/edit_profile_name" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/edit_profile_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/this_will_be_visible_to_other_users"
|
||||
android:layout_marginStart="40dp"
|
||||
android:layout_marginEnd="40dp"
|
||||
android:layout_marginTop="60dp"
|
||||
android:background="@drawable/background_16_round_4dp"
|
||||
android:singleLine="true"
|
||||
app:layout_constraintTop_toBottomOf="@id/text_polycentric"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/button_create_profile"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/button_create_profile"
|
||||
android:layout_width="140dp"
|
||||
android:layout_height="40dp"
|
||||
android:background="@drawable/background_button_primary_round"
|
||||
android:gravity="center"
|
||||
android:layout_marginTop="40dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/edit_profile_name">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/inter_light"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16dp"
|
||||
android:text="@string/create_profile" />
|
||||
</LinearLayout>
|
||||
<com.futo.platformplayer.views.LoaderView
|
||||
android:id="@+id/loader"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintTop_toBottomOf="@id/button_create_profile"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent" />
|
||||
android:background="@color/black">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/image_polycentric"
|
||||
android:layout_width="80dp"
|
||||
android:layout_height="80dp"
|
||||
android:layout_marginTop="40dp"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:srcCompat="@drawable/neopass" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_polycentric"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="12dp"
|
||||
android:fontFamily="@font/inter_light"
|
||||
android:text="@string/polycentric"
|
||||
android:textSize="32dp"
|
||||
app:layout_constraintLeft_toLeftOf="@id/image_polycentric"
|
||||
app:layout_constraintRight_toRightOf="@id/image_polycentric"
|
||||
app:layout_constraintTop_toBottomOf="@id/image_polycentric" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text_profile_name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
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_constraintLeft_toLeftOf="@id/edit_profile_name" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/edit_profile_name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="40dp"
|
||||
android:layout_marginTop="60dp"
|
||||
android:layout_marginEnd="40dp"
|
||||
android:background="@drawable/background_16_round_4dp"
|
||||
android:hint="@string/this_will_be_visible_to_other_users"
|
||||
android:singleLine="true"
|
||||
app:layout_constraintBottom_toTopOf="@id/button_create_profile"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/text_polycentric" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/button_create_profile"
|
||||
android:layout_width="140dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_marginTop="40dp"
|
||||
android:layout_marginBottom="10dp"
|
||||
android:background="@drawable/background_button_primary_round"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/edit_profile_name">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:fontFamily="@font/inter_light"
|
||||
android:text="@string/create_profile"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16dp" />
|
||||
</LinearLayout>
|
||||
|
||||
<com.futo.platformplayer.views.LoaderView
|
||||
android:id="@+id/loader"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintLeft_toLeftOf="parent"
|
||||
app:layout_constraintRight_toRightOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/button_create_profile" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
|
@ -1,5 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<dimen name="video_view_right_padding"></dimen>
|
||||
<dimen name="minimized_player_max_width">500dp</dimen>
|
||||
<dimen name="app_bar_height">200dp</dimen>
|
||||
</resources>
|
||||
<dimen name="landscape_threshold">300dp</dimen>
|
||||
</resources>
|
||||
|
|
|
@ -259,7 +259,7 @@
|
|||
<string name="add_a_comment">Add a comment…</string>
|
||||
<string name="dismiss">Dismiss</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="signature">Signature</string>
|
||||
<string name="valid">Valid</string>
|
||||
|
@ -370,11 +370,11 @@
|
|||
<string name="brightness_slider">Brightness slider</string>
|
||||
<string name="brightness_slider_descr">Enable slide gesture to change brightness</string>
|
||||
<string name="toggle_full_screen">Toggle full screen</string>
|
||||
<string name="toggle_full_screen_descr">Enable swipe gesture to toggle fullscreen</string>
|
||||
<string name="toggle_full_screen_descr">Enable swipe gesture to toggle full screen</string>
|
||||
<string name="system_brightness">System brightness</string>
|
||||
<string name="system_brightness_descr">Gesture controls adjust system brightness</string>
|
||||
<string name="restore_system_brightness">Restore system brightness</string>
|
||||
<string name="restore_system_brightness_descr">Restore system brightness when exiting fullscreen</string>
|
||||
<string name="restore_system_brightness_descr">Restore system brightness when exiting full screen</string>
|
||||
<string name="zoom_option">Enable zoom</string>
|
||||
<string name="zoom_option_descr">Enable two finger pinch zoom gesture</string>
|
||||
<string name="pan_option">Enable pan</string>
|
||||
|
@ -382,7 +382,7 @@
|
|||
<string name="system_volume">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="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_description">Allow app to flip into reverse portrait</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_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_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_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_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_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>
|
||||
|
@ -853,7 +853,7 @@
|
|||
<string name="cd_button_loop">Loop</string>
|
||||
<string name="cd_button_previous">Previous</string>
|
||||
<string name="cd_button_next">Next</string>
|
||||
<string name="cd_button_fullscreen">Fullscreen</string>
|
||||
<string name="cd_button_fullscreen">Full screen</string>
|
||||
<string name="cd_button_autoplay">Autoplay</string>
|
||||
<string name="cd_update_spinner">Update spinner</string>
|
||||
<string name="cd_button_play">Play</string>
|
||||
|
|
Loading…
Add table
Reference in a new issue