mirror of
https://gitlab.futo.org/videostreaming/grayjay.git
synced 2025-04-29 22:24:29 +02:00
Feed filter loading improved, home filters support, various peripheral stuff
This commit is contained in:
parent
0a59e04f19
commit
7d64003d1c
@ -205,7 +205,7 @@ class Settings : FragmentedStorageFileJson() {
|
|||||||
var home = HomeSettings();
|
var home = HomeSettings();
|
||||||
@Serializable
|
@Serializable
|
||||||
class HomeSettings {
|
class HomeSettings {
|
||||||
@FormField(R.string.feed_style, FieldForm.DROPDOWN, R.string.may_require_restart, 5)
|
@FormField(R.string.feed_style, FieldForm.DROPDOWN, R.string.may_require_restart, 3)
|
||||||
@DropdownFieldOptionsId(R.array.feed_style)
|
@DropdownFieldOptionsId(R.array.feed_style)
|
||||||
var homeFeedStyle: Int = 1;
|
var homeFeedStyle: Int = 1;
|
||||||
|
|
||||||
@ -216,6 +216,9 @@ class Settings : FragmentedStorageFileJson() {
|
|||||||
return FeedStyle.THUMBNAIL;
|
return FeedStyle.THUMBNAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@FormField(R.string.show_home_filters, FieldForm.TOGGLE, R.string.show_home_filters_description, 4)
|
||||||
|
var showHomeFilters: Boolean = true;
|
||||||
|
|
||||||
@FormField(R.string.preview_feed_items, FieldForm.TOGGLE, R.string.preview_feed_items_description, 6)
|
@FormField(R.string.preview_feed_items, FieldForm.TOGGLE, R.string.preview_feed_items_description, 6)
|
||||||
var previewFeedItems: Boolean = true;
|
var previewFeedItems: Boolean = true;
|
||||||
|
|
||||||
|
@ -1148,7 +1148,7 @@ class UISlideOverlays {
|
|||||||
container.context.getString(R.string.decide_which_buttons_should_be_pinned),
|
container.context.getString(R.string.decide_which_buttons_should_be_pinned),
|
||||||
tag = "",
|
tag = "",
|
||||||
call = {
|
call = {
|
||||||
showOrderOverlay(container, container.context.getString(R.string.select_your_pins_in_order), (visible + hidden).map { Pair(it.text.text.toString(), it.tagRef!!) }) {
|
showOrderOverlay(container, container.context.getString(R.string.select_your_pins_in_order), (visible + hidden).map { Pair(it.text.text.toString(), it.tagRef!!) }, {
|
||||||
val selected = it
|
val selected = it
|
||||||
.map { x -> visible.find { it.tagRef == x } ?: hidden.find { it.tagRef == x } }
|
.map { x -> visible.find { it.tagRef == x } ?: hidden.find { it.tagRef == x } }
|
||||||
.filter { it != null }
|
.filter { it != null }
|
||||||
@ -1156,7 +1156,7 @@ class UISlideOverlays {
|
|||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
onPinnedbuttons?.invoke(selected + (visible + hidden).filter { !selected.contains(it) });
|
onPinnedbuttons?.invoke(selected + (visible + hidden).filter { !selected.contains(it) });
|
||||||
}
|
});
|
||||||
},
|
},
|
||||||
invokeParent = false
|
invokeParent = false
|
||||||
))
|
))
|
||||||
@ -1164,29 +1164,40 @@ class UISlideOverlays {
|
|||||||
|
|
||||||
return SlideUpMenuOverlay(container.context, container, container.context.getString(R.string.more_options), null, true, *views).apply { show() };
|
return SlideUpMenuOverlay(container.context, container, container.context.getString(R.string.more_options), null, true, *views).apply { show() };
|
||||||
}
|
}
|
||||||
|
fun showOrderOverlay(container: ViewGroup, title: String, options: List<Pair<String, Any>>, onOrdered: (List<Any>)->Unit, description: String? = null) {
|
||||||
fun showOrderOverlay(container: ViewGroup, title: String, options: List<Pair<String, Any>>, onOrdered: (List<Any>)->Unit) {
|
|
||||||
val selection: MutableList<Any> = mutableListOf();
|
val selection: MutableList<Any> = mutableListOf();
|
||||||
|
|
||||||
var overlay: SlideUpMenuOverlay? = null;
|
var overlay: SlideUpMenuOverlay? = null;
|
||||||
|
|
||||||
overlay = SlideUpMenuOverlay(container.context, container, title, container.context.getString(R.string.save), true,
|
overlay = SlideUpMenuOverlay(container.context, container, title, container.context.getString(R.string.save), true,
|
||||||
options.map { SlideUpMenuItem(
|
listOf(
|
||||||
|
if(!description.isNullOrEmpty()) SlideUpMenuGroup(container.context, "", description, "", listOf()) else null,
|
||||||
|
).filterNotNull() +
|
||||||
|
(options.map { SlideUpMenuItem(
|
||||||
container.context,
|
container.context,
|
||||||
R.drawable.ic_move_up,
|
R.drawable.ic_move_up,
|
||||||
it.first,
|
it.first,
|
||||||
"",
|
"",
|
||||||
tag = it.second,
|
tag = it.second,
|
||||||
call = {
|
call = {
|
||||||
|
val overlayItem = overlay?.getSlideUpItemByTag(it.second);
|
||||||
if(overlay!!.selectOption(null, it.second, true, true)) {
|
if(overlay!!.selectOption(null, it.second, true, true)) {
|
||||||
if(!selection.contains(it.second))
|
if(!selection.contains(it.second)) {
|
||||||
selection.add(it.second);
|
selection.add(it.second);
|
||||||
} else
|
if(overlayItem != null) {
|
||||||
|
overlayItem.setSubText(selection.indexOf(it.second).toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
selection.remove(it.second);
|
selection.remove(it.second);
|
||||||
|
if(overlayItem != null) {
|
||||||
|
overlayItem.setSubText("");
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
invokeParent = false
|
invokeParent = false
|
||||||
)
|
)
|
||||||
});
|
}));
|
||||||
overlay.onOK.subscribe {
|
overlay.onOK.subscribe {
|
||||||
onOrdered.invoke(selection);
|
onOrdered.invoke(selection);
|
||||||
overlay.hide();
|
overlay.hide();
|
||||||
|
@ -3,6 +3,7 @@ package com.futo.platformplayer.api.media.models.contents
|
|||||||
import com.futo.platformplayer.api.media.PlatformID
|
import com.futo.platformplayer.api.media.PlatformID
|
||||||
import com.futo.platformplayer.api.media.models.PlatformAuthorLink
|
import com.futo.platformplayer.api.media.models.PlatformAuthorLink
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.json.JsonNames
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
|
|
||||||
interface IPlatformContent {
|
interface IPlatformContent {
|
||||||
|
@ -10,6 +10,7 @@ import com.futo.polycentric.core.combineHashCodes
|
|||||||
import kotlinx.serialization.decodeFromString
|
import kotlinx.serialization.decodeFromString
|
||||||
import kotlinx.serialization.encodeToString
|
import kotlinx.serialization.encodeToString
|
||||||
import kotlinx.serialization.json.Json
|
import kotlinx.serialization.json.Json
|
||||||
|
import kotlinx.serialization.json.JsonNames
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
|
|
||||||
@kotlinx.serialization.Serializable
|
@kotlinx.serialization.Serializable
|
||||||
@ -20,6 +21,7 @@ open class SerializedPlatformVideo(
|
|||||||
override val thumbnails: Thumbnails,
|
override val thumbnails: Thumbnails,
|
||||||
override val author: PlatformAuthorLink,
|
override val author: PlatformAuthorLink,
|
||||||
@kotlinx.serialization.Serializable(with = OffsetDateTimeNullableSerializer::class)
|
@kotlinx.serialization.Serializable(with = OffsetDateTimeNullableSerializer::class)
|
||||||
|
@JsonNames("datetime", "dateTime")
|
||||||
override val datetime: OffsetDateTime? = null,
|
override val datetime: OffsetDateTime? = null,
|
||||||
override val url: String,
|
override val url: String,
|
||||||
override val shareUrl: String = "",
|
override val shareUrl: String = "",
|
||||||
|
@ -6,7 +6,7 @@ import com.futo.platformplayer.constructs.Event1
|
|||||||
* A RefreshPager represents a pager that can be modified overtime (eg. By getting more results later, by recreating the pager)
|
* A RefreshPager represents a pager that can be modified overtime (eg. By getting more results later, by recreating the pager)
|
||||||
* When the onPagerChanged event is emitted, a new pager instance is passed, or requested via getCurrentPager
|
* When the onPagerChanged event is emitted, a new pager instance is passed, or requested via getCurrentPager
|
||||||
*/
|
*/
|
||||||
interface IRefreshPager<T> {
|
interface IRefreshPager<T>: IPager<T> {
|
||||||
val onPagerChanged: Event1<IPager<T>>;
|
val onPagerChanged: Event1<IPager<T>>;
|
||||||
val onPagerError: Event1<Throwable>;
|
val onPagerError: Event1<Throwable>;
|
||||||
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
package com.futo.platformplayer.api.media.structures
|
package com.futo.platformplayer.api.media.structures
|
||||||
|
|
||||||
|
import com.futo.platformplayer.api.media.structures.ReusablePager.Window
|
||||||
|
import com.futo.platformplayer.constructs.Event1
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -9,8 +11,8 @@ import com.futo.platformplayer.logging.Logger
|
|||||||
* A "Window" is effectively a pager that just reads previous results from the shared results, but when the end is reached, it will call nextPage on the parent if possible for new results.
|
* A "Window" is effectively a pager that just reads previous results from the shared results, but when the end is reached, it will call nextPage on the parent if possible for new results.
|
||||||
* This allows multiple Windows to exist of the same pager, without messing with position, or duplicate requests
|
* This allows multiple Windows to exist of the same pager, without messing with position, or duplicate requests
|
||||||
*/
|
*/
|
||||||
class ReusablePager<T>: INestedPager<T>, IPager<T> {
|
open class ReusablePager<T>: INestedPager<T>, IReusablePager<T> {
|
||||||
private val _pager: IPager<T>;
|
protected var _pager: IPager<T>;
|
||||||
val previousResults = arrayListOf<T>();
|
val previousResults = arrayListOf<T>();
|
||||||
|
|
||||||
constructor(subPager: IPager<T>) {
|
constructor(subPager: IPager<T>) {
|
||||||
@ -44,7 +46,7 @@ class ReusablePager<T>: INestedPager<T>, IPager<T> {
|
|||||||
return previousResults;
|
return previousResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getWindow(): Window<T> {
|
override fun getWindow(): Window<T> {
|
||||||
return Window(this);
|
return Window(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,4 +97,118 @@ class ReusablePager<T>: INestedPager<T>, IPager<T> {
|
|||||||
return ReusablePager(this);
|
return ReusablePager(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public class ReusableRefreshPager<T>: INestedPager<T>, IReusablePager<T> {
|
||||||
|
protected var _pager: IRefreshPager<T>;
|
||||||
|
val previousResults = arrayListOf<T>();
|
||||||
|
|
||||||
|
private var _currentPage: IPager<T>;
|
||||||
|
|
||||||
|
|
||||||
|
val onPagerChanged = Event1<IPager<T>>()
|
||||||
|
val onPagerError = Event1<Throwable>()
|
||||||
|
|
||||||
|
constructor(subPager: IRefreshPager<T>) {
|
||||||
|
this._pager = subPager;
|
||||||
|
_currentPage = this;
|
||||||
|
synchronized(previousResults) {
|
||||||
|
previousResults.addAll(subPager.getResults());
|
||||||
|
}
|
||||||
|
_pager.onPagerError.subscribe(onPagerError::emit);
|
||||||
|
_pager.onPagerChanged.subscribe {
|
||||||
|
_currentPage = it;
|
||||||
|
synchronized(previousResults) {
|
||||||
|
previousResults.clear();
|
||||||
|
previousResults.addAll(it.getResults());
|
||||||
|
}
|
||||||
|
|
||||||
|
onPagerChanged.emit(_currentPage);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findPager(query: (IPager<T>) -> Boolean): IPager<T>? {
|
||||||
|
if(query(_pager))
|
||||||
|
return _pager;
|
||||||
|
else if(_pager is INestedPager<*>)
|
||||||
|
return (_pager as INestedPager<T>).findPager(query);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hasMorePages(): Boolean {
|
||||||
|
return _pager.hasMorePages();
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun nextPage() {
|
||||||
|
_pager.nextPage();
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getResults(): List<T> {
|
||||||
|
val results = _pager.getResults();
|
||||||
|
synchronized(previousResults) {
|
||||||
|
previousResults.addAll(results);
|
||||||
|
}
|
||||||
|
return previousResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getWindow(): RefreshWindow<T> {
|
||||||
|
return RefreshWindow(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class RefreshWindow<T>: IPager<T>, INestedPager<T>, IRefreshPager<T> {
|
||||||
|
private val _parent: ReusableRefreshPager<T>;
|
||||||
|
private var _position: Int = 0;
|
||||||
|
private var _read: Int = 0;
|
||||||
|
|
||||||
|
private var _currentResults: List<T>;
|
||||||
|
|
||||||
|
override val onPagerChanged = Event1<IPager<T>>();
|
||||||
|
override val onPagerError = Event1<Throwable>();
|
||||||
|
|
||||||
|
|
||||||
|
override fun getCurrentPager(): IPager<T> {
|
||||||
|
return _parent.getWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(parent: ReusableRefreshPager<T>) {
|
||||||
|
_parent = parent;
|
||||||
|
|
||||||
|
synchronized(_parent.previousResults) {
|
||||||
|
_currentResults = _parent.previousResults.toList();
|
||||||
|
_read += _currentResults.size;
|
||||||
|
}
|
||||||
|
parent.onPagerChanged.subscribe(onPagerChanged::emit);
|
||||||
|
parent.onPagerError.subscribe(onPagerError::emit);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun hasMorePages(): Boolean {
|
||||||
|
return _parent.previousResults.size > _read || _parent.hasMorePages();
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun nextPage() {
|
||||||
|
synchronized(_parent.previousResults) {
|
||||||
|
if (_parent.previousResults.size <= _read) {
|
||||||
|
_parent.nextPage();
|
||||||
|
_parent.getResults();
|
||||||
|
}
|
||||||
|
_currentResults = _parent.previousResults.drop(_read).toList();
|
||||||
|
_read += _currentResults.size;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getResults(): List<T> {
|
||||||
|
return _currentResults;
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun findPager(query: (IPager<T>) -> Boolean): IPager<T>? {
|
||||||
|
return _parent.findPager(query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IReusablePager<T>: IPager<T> {
|
||||||
|
fun getWindow(): IPager<T>;
|
||||||
}
|
}
|
@ -3,12 +3,15 @@ package com.futo.platformplayer.fragment.mainactivity.main
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
|
import android.util.DisplayMetrics
|
||||||
|
import android.view.Display
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.*
|
import android.widget.*
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
import androidx.recyclerview.widget.RecyclerView.LayoutManager
|
import androidx.recyclerview.widget.RecyclerView.LayoutManager
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||||
@ -20,6 +23,7 @@ import com.futo.platformplayer.constructs.Event1
|
|||||||
import com.futo.platformplayer.constructs.TaskHandler
|
import com.futo.platformplayer.constructs.TaskHandler
|
||||||
import com.futo.platformplayer.engine.exceptions.PluginException
|
import com.futo.platformplayer.engine.exceptions.PluginException
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
|
import com.futo.platformplayer.states.StateApp
|
||||||
import com.futo.platformplayer.views.FeedStyle
|
import com.futo.platformplayer.views.FeedStyle
|
||||||
import com.futo.platformplayer.views.others.ProgressBar
|
import com.futo.platformplayer.views.others.ProgressBar
|
||||||
import com.futo.platformplayer.views.others.TagsView
|
import com.futo.platformplayer.views.others.TagsView
|
||||||
@ -28,7 +32,9 @@ import com.futo.platformplayer.views.adapters.InsertedViewHolder
|
|||||||
import com.futo.platformplayer.views.announcements.AnnouncementView
|
import com.futo.platformplayer.views.announcements.AnnouncementView
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.delay
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
import kotlin.math.max
|
import kotlin.math.max
|
||||||
|
|
||||||
@ -68,6 +74,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
|
|||||||
|
|
||||||
private val _scrollListener: RecyclerView.OnScrollListener;
|
private val _scrollListener: RecyclerView.OnScrollListener;
|
||||||
private var _automaticNextPageCounter = 0;
|
private var _automaticNextPageCounter = 0;
|
||||||
|
private val _automaticBackoff = arrayOf(0, 500, 1000, 1000, 2000, 5000, 5000, 5000);
|
||||||
|
|
||||||
constructor(fragment: TFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<TViewHolder>, GridLayoutManager, TPager, TResult, TConverted, InsertedViewHolder<TViewHolder>>? = null) : super(inflater.context) {
|
constructor(fragment: TFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<TViewHolder>, GridLayoutManager, TPager, TResult, TConverted, InsertedViewHolder<TViewHolder>>? = null) : super(inflater.context) {
|
||||||
this.fragment = fragment;
|
this.fragment = fragment;
|
||||||
@ -129,6 +136,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
|
|||||||
_toolbarContentView = findViewById(R.id.container_toolbar_content);
|
_toolbarContentView = findViewById(R.id.container_toolbar_content);
|
||||||
|
|
||||||
_nextPageHandler = TaskHandler<TPager, List<TResult>>({fragment.lifecycleScope}, {
|
_nextPageHandler = TaskHandler<TPager, List<TResult>>({fragment.lifecycleScope}, {
|
||||||
|
|
||||||
if (it is IAsyncPager<*>)
|
if (it is IAsyncPager<*>)
|
||||||
it.nextPageAsync();
|
it.nextPageAsync();
|
||||||
else
|
else
|
||||||
@ -182,26 +190,53 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L
|
|||||||
|
|
||||||
private fun ensureEnoughContentVisible(filteredResults: List<TConverted>) {
|
private fun ensureEnoughContentVisible(filteredResults: List<TConverted>) {
|
||||||
val canScroll = if (recyclerData.results.isEmpty()) false else {
|
val canScroll = if (recyclerData.results.isEmpty()) false else {
|
||||||
|
val height = resources.displayMetrics.heightPixels;
|
||||||
|
|
||||||
val layoutManager = recyclerData.layoutManager
|
val layoutManager = recyclerData.layoutManager
|
||||||
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
|
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
|
||||||
|
val firstVisibleItemView = if(firstVisibleItemPosition != RecyclerView.NO_POSITION) layoutManager.findViewByPosition(firstVisibleItemPosition) else null;
|
||||||
if (firstVisibleItemPosition != RecyclerView.NO_POSITION) {
|
val lastVisibleItemPosition = layoutManager.findLastCompletelyVisibleItemPosition();
|
||||||
val firstVisibleView = layoutManager.findViewByPosition(firstVisibleItemPosition)
|
val lastVisibleItemView = if(lastVisibleItemPosition != RecyclerView.NO_POSITION) layoutManager.findViewByPosition(lastVisibleItemPosition) else null;
|
||||||
val itemHeight = firstVisibleView?.height ?: 0
|
if(lastVisibleItemView != null && lastVisibleItemPosition == (recyclerData.results.size - 1)) {
|
||||||
val occupiedSpace = recyclerData.results.size / recyclerData.layoutManager.spanCount * itemHeight
|
false;
|
||||||
val recyclerViewHeight = _recyclerResults.height
|
}
|
||||||
Logger.i(TAG, "ensureEnoughContentVisible loadNextPage occupiedSpace=$occupiedSpace recyclerViewHeight=$recyclerViewHeight")
|
else if (firstVisibleItemView != null && height != null && firstVisibleItemView.height * recyclerData.results.size < height) {
|
||||||
occupiedSpace >= recyclerViewHeight
|
false;
|
||||||
} else {
|
} else {
|
||||||
false
|
true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.i(TAG, "ensureEnoughContentVisible loadNextPage canScroll=$canScroll _automaticNextPageCounter=$_automaticNextPageCounter")
|
Logger.i(TAG, "ensureEnoughContentVisible loadNextPage canScroll=$canScroll _automaticNextPageCounter=$_automaticNextPageCounter")
|
||||||
if (!canScroll || filteredResults.isEmpty()) {
|
if (!canScroll || filteredResults.isEmpty()) {
|
||||||
_automaticNextPageCounter++
|
_automaticNextPageCounter++
|
||||||
if(_automaticNextPageCounter <= 4)
|
if(_automaticNextPageCounter < _automaticBackoff.size) {
|
||||||
loadNextPage()
|
if(_automaticNextPageCounter > 0) {
|
||||||
|
val automaticNextPageCounterSaved = _automaticNextPageCounter;
|
||||||
|
fragment.lifecycleScope.launch(Dispatchers.Default) {
|
||||||
|
val backoff = _automaticBackoff[Math.min(_automaticBackoff.size - 1, _automaticNextPageCounter)];
|
||||||
|
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
setLoading(true);
|
||||||
|
}
|
||||||
|
delay(backoff.toLong());
|
||||||
|
if(automaticNextPageCounterSaved == _automaticNextPageCounter) {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
loadNextPage();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
withContext(Dispatchers.Main) {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
loadNextPage();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
|
Logger.i(TAG, "ensureEnoughContentVisible automaticNextPageCounter reset");
|
||||||
_automaticNextPageCounter = 0;
|
_automaticNextPageCounter = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,22 +5,32 @@ import android.os.Bundle
|
|||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import androidx.core.view.allViews
|
||||||
|
import androidx.core.view.contains
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.recyclerview.widget.GridLayoutManager
|
import androidx.recyclerview.widget.GridLayoutManager
|
||||||
import com.futo.platformplayer.*
|
import com.futo.platformplayer.*
|
||||||
|
import com.futo.platformplayer.UISlideOverlays.Companion.showOrderOverlay
|
||||||
import com.futo.platformplayer.activities.MainActivity
|
import com.futo.platformplayer.activities.MainActivity
|
||||||
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
||||||
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
import com.futo.platformplayer.api.media.platforms.js.JSClient
|
||||||
import com.futo.platformplayer.api.media.structures.EmptyPager
|
import com.futo.platformplayer.api.media.structures.EmptyPager
|
||||||
import com.futo.platformplayer.api.media.structures.IPager
|
import com.futo.platformplayer.api.media.structures.IPager
|
||||||
|
import com.futo.platformplayer.api.media.structures.IRefreshPager
|
||||||
|
import com.futo.platformplayer.api.media.structures.IReusablePager
|
||||||
|
import com.futo.platformplayer.api.media.structures.ReusablePager
|
||||||
|
import com.futo.platformplayer.api.media.structures.ReusableRefreshPager
|
||||||
import com.futo.platformplayer.constructs.TaskHandler
|
import com.futo.platformplayer.constructs.TaskHandler
|
||||||
import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException
|
import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException
|
||||||
import com.futo.platformplayer.engine.exceptions.ScriptExecutionException
|
import com.futo.platformplayer.engine.exceptions.ScriptExecutionException
|
||||||
import com.futo.platformplayer.engine.exceptions.ScriptImplementationException
|
import com.futo.platformplayer.engine.exceptions.ScriptImplementationException
|
||||||
import com.futo.platformplayer.logging.Logger
|
import com.futo.platformplayer.logging.Logger
|
||||||
import com.futo.platformplayer.states.StateApp
|
import com.futo.platformplayer.states.StateApp
|
||||||
|
import com.futo.platformplayer.states.StateHistory
|
||||||
import com.futo.platformplayer.states.StateMeta
|
import com.futo.platformplayer.states.StateMeta
|
||||||
import com.futo.platformplayer.states.StatePlatform
|
import com.futo.platformplayer.states.StatePlatform
|
||||||
|
import com.futo.platformplayer.stores.FragmentedStorage
|
||||||
|
import com.futo.platformplayer.stores.StringArrayStorage
|
||||||
import com.futo.platformplayer.views.FeedStyle
|
import com.futo.platformplayer.views.FeedStyle
|
||||||
import com.futo.platformplayer.views.NoResultsView
|
import com.futo.platformplayer.views.NoResultsView
|
||||||
import com.futo.platformplayer.views.ToggleBar
|
import com.futo.platformplayer.views.ToggleBar
|
||||||
@ -91,6 +101,7 @@ class HomeFragment : MainFragment() {
|
|||||||
_view?.setPreviewsEnabled(previewsEnabled && Settings.instance.home.previewFeedItems);
|
_view?.setPreviewsEnabled(previewsEnabled && Settings.instance.home.previewFeedItems);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@SuppressLint("ViewConstructor")
|
@SuppressLint("ViewConstructor")
|
||||||
class HomeView : ContentFeedView<HomeFragment> {
|
class HomeView : ContentFeedView<HomeFragment> {
|
||||||
override val feedStyle: FeedStyle get() = Settings.instance.home.getHomeFeedStyle();
|
override val feedStyle: FeedStyle get() = Settings.instance.home.getHomeFeedStyle();
|
||||||
@ -100,11 +111,20 @@ class HomeFragment : MainFragment() {
|
|||||||
private val _taskGetPager: TaskHandler<Boolean, IPager<IPlatformContent>>;
|
private val _taskGetPager: TaskHandler<Boolean, IPager<IPlatformContent>>;
|
||||||
override val shouldShowTimeBar: Boolean get() = Settings.instance.home.progressBar
|
override val shouldShowTimeBar: Boolean get() = Settings.instance.home.progressBar
|
||||||
|
|
||||||
|
private var _lastPager: IReusablePager<IPlatformContent>? = null;
|
||||||
|
|
||||||
constructor(fragment: HomeFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, GridLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null) : super(fragment, inflater, cachedRecyclerData) {
|
constructor(fragment: HomeFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, GridLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null) : super(fragment, inflater, cachedRecyclerData) {
|
||||||
_taskGetPager = TaskHandler<Boolean, IPager<IPlatformContent>>({ fragment.lifecycleScope }, {
|
_taskGetPager = TaskHandler<Boolean, IPager<IPlatformContent>>({ fragment.lifecycleScope }, {
|
||||||
StatePlatform.instance.getHomeRefresh(fragment.lifecycleScope)
|
StatePlatform.instance.getHomeRefresh(fragment.lifecycleScope)
|
||||||
})
|
})
|
||||||
.success { loadedResult(it); }
|
.success {
|
||||||
|
val wrappedPager = if(it is IRefreshPager)
|
||||||
|
ReusableRefreshPager(it);
|
||||||
|
else
|
||||||
|
ReusablePager(it);
|
||||||
|
_lastPager = wrappedPager;
|
||||||
|
loadedResult(wrappedPager.getWindow());
|
||||||
|
}
|
||||||
.exception<ScriptCaptchaRequiredException> { }
|
.exception<ScriptCaptchaRequiredException> { }
|
||||||
.exception<ScriptExecutionException> {
|
.exception<ScriptExecutionException> {
|
||||||
Logger.w(ChannelFragment.TAG, "Plugin failure.", it);
|
Logger.w(ChannelFragment.TAG, "Plugin failure.", it);
|
||||||
@ -208,21 +228,81 @@ class HomeFragment : MainFragment() {
|
|||||||
|
|
||||||
private val _filterLock = Object();
|
private val _filterLock = Object();
|
||||||
private var _toggleRecent = false;
|
private var _toggleRecent = false;
|
||||||
|
private var _toggleWatched = false;
|
||||||
|
private var _togglePluginsDisabled = mutableListOf<String>();
|
||||||
|
private var _togglesConfig = FragmentedStorage.get<StringArrayStorage>("home_toggles");
|
||||||
fun initializeToolbarContent() {
|
fun initializeToolbarContent() {
|
||||||
//Not stable enough with current viewport paging, doesn't work with less results, and reloads content instead of just re-filtering existing
|
if(_toolbarContentView.allViews.any { it is ToggleBar })
|
||||||
/*
|
_toolbarContentView.removeView(_toolbarContentView.allViews.find { it is ToggleBar });
|
||||||
_toggleBar = ToggleBar(context).apply {
|
|
||||||
layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
|
|
||||||
}
|
|
||||||
synchronized(_filterLock) {
|
|
||||||
_toggleBar?.setToggles(
|
|
||||||
//TODO: loadResults needs to be replaced with an internal reload of the current content
|
|
||||||
ToggleBar.Toggle("Recent", _toggleRecent) { _toggleRecent = it; loadResults(false) }
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
_toolbarContentView.addView(_toggleBar, 0);
|
if(Settings.instance.home.showHomeFilters) {
|
||||||
*/
|
|
||||||
|
if (!_togglesConfig.any()) {
|
||||||
|
_togglesConfig.set("today", "watched", "plugins");
|
||||||
|
_togglesConfig.save();
|
||||||
|
}
|
||||||
|
_toggleBar = ToggleBar(context).apply {
|
||||||
|
layoutParams =
|
||||||
|
LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
|
||||||
|
}
|
||||||
|
_togglePluginsDisabled.clear();
|
||||||
|
synchronized(_filterLock) {
|
||||||
|
val buttonsPlugins = (if (_togglesConfig.contains("plugins"))
|
||||||
|
(StatePlatform.instance.getEnabledClients()
|
||||||
|
.map { plugin ->
|
||||||
|
ToggleBar.Toggle(plugin.name, plugin.icon, true, {
|
||||||
|
if (it) {
|
||||||
|
if (_togglePluginsDisabled.contains(plugin.id))
|
||||||
|
_togglePluginsDisabled.remove(plugin.id);
|
||||||
|
} else {
|
||||||
|
if (!_togglePluginsDisabled.contains(plugin.id))
|
||||||
|
_togglePluginsDisabled.add(plugin.id);
|
||||||
|
}
|
||||||
|
reloadForFilters();
|
||||||
|
}).withTag("plugins")
|
||||||
|
})
|
||||||
|
else listOf())
|
||||||
|
val buttons = (listOf<ToggleBar.Toggle?>(
|
||||||
|
(if (_togglesConfig.contains("today"))
|
||||||
|
ToggleBar.Toggle("Today", _toggleRecent) {
|
||||||
|
_toggleRecent = it; reloadForFilters()
|
||||||
|
}
|
||||||
|
.withTag("today") else null),
|
||||||
|
(if (_togglesConfig.contains("watched"))
|
||||||
|
ToggleBar.Toggle("Unwatched", _toggleWatched) {
|
||||||
|
_toggleWatched = it; reloadForFilters()
|
||||||
|
}
|
||||||
|
.withTag("watched") else null),
|
||||||
|
).filterNotNull() + buttonsPlugins)
|
||||||
|
.sortedBy { _togglesConfig.indexOf(it.tag ?: "") } ?: listOf()
|
||||||
|
|
||||||
|
val buttonSettings = ToggleBar.Toggle("", R.drawable.ic_settings, true, {
|
||||||
|
showOrderOverlay(_overlayContainer,
|
||||||
|
"Visible home filters",
|
||||||
|
listOf(
|
||||||
|
Pair("Plugins", "plugins"),
|
||||||
|
Pair("Today", "today"),
|
||||||
|
Pair("Watched", "watched")
|
||||||
|
),
|
||||||
|
{
|
||||||
|
val newArray = it.map { it.toString() }.toTypedArray();
|
||||||
|
_togglesConfig.set(*(if (newArray.any()) newArray else arrayOf("none")));
|
||||||
|
_togglesConfig.save();
|
||||||
|
initializeToolbarContent();
|
||||||
|
},
|
||||||
|
"Select which toggles you want to see in order. You can also choose to hide filters in the Grayjay Settings"
|
||||||
|
);
|
||||||
|
}).asButton();
|
||||||
|
|
||||||
|
val buttonsOrder = (buttons + listOf(buttonSettings)).toTypedArray();
|
||||||
|
_toggleBar?.setToggles(*buttonsOrder);
|
||||||
|
}
|
||||||
|
|
||||||
|
_toolbarContentView.addView(_toggleBar, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun reloadForFilters() {
|
||||||
|
_lastPager?.let { loadedResult(it.getWindow()) };
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun filterResults(results: List<IPlatformContent>): List<IPlatformContent> {
|
override fun filterResults(results: List<IPlatformContent>): List<IPlatformContent> {
|
||||||
@ -232,7 +312,11 @@ class HomeFragment : MainFragment() {
|
|||||||
if(StateMeta.instance.isCreatorHidden(it.author.url))
|
if(StateMeta.instance.isCreatorHidden(it.author.url))
|
||||||
return@filter false;
|
return@filter false;
|
||||||
|
|
||||||
if(_toggleRecent && (it.datetime?.getNowDiffHours() ?: 0) > 23) {
|
if(_toggleRecent && (it.datetime?.getNowDiffHours() ?: 0) > 25)
|
||||||
|
return@filter false;
|
||||||
|
if(_toggleWatched && StateHistory.instance.isHistoryWatched(it.url, 0))
|
||||||
|
return@filter false;
|
||||||
|
if(_togglePluginsDisabled.any() && it.id.pluginId != null && _togglePluginsDisabled.contains(it.id.pluginId)) {
|
||||||
return@filter false;
|
return@filter false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -19,10 +19,10 @@ import kotlinx.serialization.json.jsonPrimitive
|
|||||||
class PlatformContentSerializer : JsonContentPolymorphicSerializer<SerializedPlatformContent>(SerializedPlatformContent::class) {
|
class PlatformContentSerializer : JsonContentPolymorphicSerializer<SerializedPlatformContent>(SerializedPlatformContent::class) {
|
||||||
|
|
||||||
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<SerializedPlatformContent> {
|
override fun selectDeserializer(element: JsonElement): DeserializationStrategy<SerializedPlatformContent> {
|
||||||
val obj = element.jsonObject["contentType"];
|
val obj = element.jsonObject["contentType"] ?: element.jsonObject["ContentType"];
|
||||||
|
|
||||||
//TODO: Remove this temporary fallback..at some point
|
//TODO: Remove this temporary fallback..at some point
|
||||||
if(obj == null && element.jsonObject["isLive"]?.jsonPrimitive?.booleanOrNull != null)
|
if(obj == null && (element.jsonObject["isLive"]?.jsonPrimitive?.booleanOrNull ?: element.jsonObject["IsLive"]?.jsonPrimitive?.booleanOrNull) != null)
|
||||||
return SerializedPlatformVideo.serializer();
|
return SerializedPlatformVideo.serializer();
|
||||||
|
|
||||||
if(obj?.jsonPrimitive?.isString != false) {
|
if(obj?.jsonPrimitive?.isString != false) {
|
||||||
|
@ -69,7 +69,7 @@ class StateSubscriptions {
|
|||||||
|
|
||||||
val onSubscriptionsChanged = Event2<List<Subscription>, Boolean>();
|
val onSubscriptionsChanged = Event2<List<Subscription>, Boolean>();
|
||||||
|
|
||||||
private val _subsExchangeServer = "https://exchange.grayjay.app/";
|
private val _subsExchangeServer = "http://10.10.15.159"//"https://exchange.grayjay.app/";
|
||||||
private val _subscriptionKey = FragmentedStorage.get<StringStorage>("sub_exchange_key");
|
private val _subscriptionKey = FragmentedStorage.get<StringStorage>("sub_exchange_key");
|
||||||
|
|
||||||
init {
|
init {
|
||||||
|
@ -41,4 +41,19 @@ class StringArrayStorage : FragmentedStorageFileJson() {
|
|||||||
return values.toList();
|
return values.toList();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fun any(): Boolean {
|
||||||
|
synchronized(values) {
|
||||||
|
return values.any();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun contains(v: String): Boolean {
|
||||||
|
synchronized(values) {
|
||||||
|
return values.contains(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun indexOf(v: String): Int {
|
||||||
|
synchronized(values){
|
||||||
|
return values.indexOf(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
@ -153,6 +153,7 @@ abstract class SubscriptionsTaskFetchAlgorithm(
|
|||||||
*resolves
|
*resolves
|
||||||
);
|
);
|
||||||
if (resolve != null) {
|
if (resolve != null) {
|
||||||
|
val invalids = resolve.filter { it.content.any { it.datetime == null } };
|
||||||
UIDialogs.appToast("SubsExchange (Res: ${resolves.size}, Prov: ${resolve.size})")
|
UIDialogs.appToast("SubsExchange (Res: ${resolves.size}, Prov: ${resolve.size})")
|
||||||
for(result in resolve){
|
for(result in resolve){
|
||||||
val task = providedTasks?.find { it.url == result.channelUrl };
|
val task = providedTasks?.find { it.url == result.channelUrl };
|
||||||
|
@ -5,6 +5,7 @@ import com.futo.platformplayer.api.media.models.contents.IPlatformContent
|
|||||||
import com.futo.platformplayer.api.media.models.video.SerializedPlatformContent
|
import com.futo.platformplayer.api.media.models.video.SerializedPlatformContent
|
||||||
import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer
|
import com.futo.platformplayer.serializers.OffsetDateTimeNullableSerializer
|
||||||
import com.futo.platformplayer.serializers.OffsetDateTimeSerializer
|
import com.futo.platformplayer.serializers.OffsetDateTimeSerializer
|
||||||
|
import com.futo.platformplayer.serializers.OffsetDateTimeStringSerializer
|
||||||
import kotlinx.serialization.SerialName
|
import kotlinx.serialization.SerialName
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import java.time.OffsetDateTime
|
import java.time.OffsetDateTime
|
||||||
@ -12,12 +13,12 @@ import java.time.OffsetDateTime
|
|||||||
@Serializable
|
@Serializable
|
||||||
class ChannelResult(
|
class ChannelResult(
|
||||||
@kotlinx.serialization.Serializable(with = OffsetDateTimeSerializer::class)
|
@kotlinx.serialization.Serializable(with = OffsetDateTimeSerializer::class)
|
||||||
@SerialName("DateTime")
|
@SerialName("dateTime")
|
||||||
var dateTime: OffsetDateTime,
|
var dateTime: OffsetDateTime,
|
||||||
@SerialName("ChannelUrl")
|
@SerialName("channelUrl")
|
||||||
var channelUrl: String,
|
var channelUrl: String,
|
||||||
@SerialName("Content")
|
@SerialName("content")
|
||||||
var content: List<SerializedPlatformContent>,
|
var content: List<SerializedPlatformContent>,
|
||||||
@SerialName("Channel")
|
@SerialName("channel")
|
||||||
var channel: IPlatformChannel? = null
|
var channel: IPlatformChannel? = null
|
||||||
)
|
)
|
@ -52,6 +52,7 @@ class SubsExchangeClient(private val server: String, private val privateKey: Str
|
|||||||
fun resolveContract(contract: ExchangeContract, vararg resolves: ChannelResolve): Array<ChannelResult> {
|
fun resolveContract(contract: ExchangeContract, vararg resolves: ChannelResolve): Array<ChannelResult> {
|
||||||
val contractResolve = convertResolves(*resolves)
|
val contractResolve = convertResolves(*resolves)
|
||||||
val result = post("/api/Channel/Resolve?contractId=${contract.id}", Serializer.json.encodeToString(contractResolve), "application/json")
|
val result = post("/api/Channel/Resolve?contractId=${contract.id}", Serializer.json.encodeToString(contractResolve), "application/json")
|
||||||
|
Logger.v("SubsExchangeClient", "Resolve:" + result);
|
||||||
return Serializer.json.decodeFromString(result)
|
return Serializer.json.decodeFromString(result)
|
||||||
}
|
}
|
||||||
suspend fun resolveContractAsync(contract: ExchangeContract, vararg resolves: ChannelResolve): Array<ChannelResult> {
|
suspend fun resolveContractAsync(contract: ExchangeContract, vararg resolves: ChannelResolve): Array<ChannelResult> {
|
||||||
|
@ -12,6 +12,7 @@ import com.futo.platformplayer.Settings
|
|||||||
import com.futo.platformplayer.UIDialogs
|
import com.futo.platformplayer.UIDialogs
|
||||||
import com.futo.platformplayer.api.media.models.channels.SerializedChannel
|
import com.futo.platformplayer.api.media.models.channels.SerializedChannel
|
||||||
import com.futo.platformplayer.constructs.Event1
|
import com.futo.platformplayer.constructs.Event1
|
||||||
|
import com.futo.platformplayer.models.ImageVariable
|
||||||
import com.futo.platformplayer.models.Subscription
|
import com.futo.platformplayer.models.Subscription
|
||||||
import com.futo.platformplayer.models.SubscriptionGroup
|
import com.futo.platformplayer.models.SubscriptionGroup
|
||||||
import com.futo.platformplayer.states.StateSubscriptionGroups
|
import com.futo.platformplayer.states.StateSubscriptionGroups
|
||||||
@ -46,7 +47,12 @@ class ToggleBar : LinearLayout {
|
|||||||
_tagsContainer.removeAllViews();
|
_tagsContainer.removeAllViews();
|
||||||
for(button in buttons) {
|
for(button in buttons) {
|
||||||
_tagsContainer.addView(ToggleTagView(context).apply {
|
_tagsContainer.addView(ToggleTagView(context).apply {
|
||||||
this.setInfo(button.name, button.isActive);
|
if(button.icon > 0)
|
||||||
|
this.setInfo(button.icon, button.name, button.isActive, button.isButton);
|
||||||
|
else if(button.iconVariable != null)
|
||||||
|
this.setInfo(button.iconVariable, button.name, button.isActive, button.isButton);
|
||||||
|
else
|
||||||
|
this.setInfo(button.name, button.isActive, button.isButton);
|
||||||
this.onClick.subscribe { button.action(it); };
|
this.onClick.subscribe { button.action(it); };
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -55,20 +61,42 @@ class ToggleBar : LinearLayout {
|
|||||||
class Toggle {
|
class Toggle {
|
||||||
val name: String;
|
val name: String;
|
||||||
val icon: Int;
|
val icon: Int;
|
||||||
|
val iconVariable: ImageVariable?;
|
||||||
val action: (Boolean)->Unit;
|
val action: (Boolean)->Unit;
|
||||||
val isActive: Boolean;
|
val isActive: Boolean;
|
||||||
|
var isButton: Boolean = false
|
||||||
|
private set;
|
||||||
|
var tag: String? = null;
|
||||||
|
|
||||||
|
constructor(name: String, icon: ImageVariable?, isActive: Boolean = false, action: (Boolean)->Unit) {
|
||||||
|
this.name = name;
|
||||||
|
this.icon = 0;
|
||||||
|
this.iconVariable = icon;
|
||||||
|
this.action = action;
|
||||||
|
this.isActive = isActive;
|
||||||
|
}
|
||||||
constructor(name: String, icon: Int, isActive: Boolean = false, action: (Boolean)->Unit) {
|
constructor(name: String, icon: Int, isActive: Boolean = false, action: (Boolean)->Unit) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.icon = icon;
|
this.icon = icon;
|
||||||
|
this.iconVariable = null;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
this.isActive = isActive;
|
this.isActive = isActive;
|
||||||
}
|
}
|
||||||
constructor(name: String, isActive: Boolean = false, action: (Boolean)->Unit) {
|
constructor(name: String, isActive: Boolean = false, action: (Boolean)->Unit) {
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.icon = 0;
|
this.icon = 0;
|
||||||
|
this.iconVariable = null;
|
||||||
this.action = action;
|
this.action = action;
|
||||||
this.isActive = isActive;
|
this.isActive = isActive;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun asButton(): Toggle{
|
||||||
|
isButton = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
fun withTag(str: String): Toggle {
|
||||||
|
tag = str;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,19 +4,27 @@ import android.content.Context
|
|||||||
import android.graphics.Color
|
import android.graphics.Color
|
||||||
import android.util.AttributeSet
|
import android.util.AttributeSet
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
|
import android.widget.ImageView
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
|
import com.bumptech.glide.Glide
|
||||||
import com.futo.platformplayer.R
|
import com.futo.platformplayer.R
|
||||||
import com.futo.platformplayer.constructs.Event1
|
import com.futo.platformplayer.constructs.Event1
|
||||||
|
import com.futo.platformplayer.images.GlideHelper
|
||||||
|
import com.futo.platformplayer.models.ImageVariable
|
||||||
|
|
||||||
class ToggleTagView : LinearLayout {
|
class ToggleTagView : LinearLayout {
|
||||||
private val _root: FrameLayout;
|
private val _root: FrameLayout;
|
||||||
private val _textTag: TextView;
|
private val _textTag: TextView;
|
||||||
private var _text: String = "";
|
private var _text: String = "";
|
||||||
|
private var _image: ImageView;
|
||||||
|
|
||||||
var isActive: Boolean = false
|
var isActive: Boolean = false
|
||||||
private set;
|
private set;
|
||||||
|
var isButton: Boolean = false
|
||||||
|
private set;
|
||||||
|
|
||||||
var onClick = Event1<Boolean>();
|
var onClick = Event1<Boolean>();
|
||||||
|
|
||||||
@ -24,7 +32,12 @@ class ToggleTagView : LinearLayout {
|
|||||||
LayoutInflater.from(context).inflate(R.layout.view_toggle_tag, this, true);
|
LayoutInflater.from(context).inflate(R.layout.view_toggle_tag, this, true);
|
||||||
_root = findViewById(R.id.root);
|
_root = findViewById(R.id.root);
|
||||||
_textTag = findViewById(R.id.text_tag);
|
_textTag = findViewById(R.id.text_tag);
|
||||||
_root.setOnClickListener { setToggle(!isActive); onClick.emit(isActive); }
|
_image = findViewById(R.id.image_tag);
|
||||||
|
_root.setOnClickListener {
|
||||||
|
if(!isButton)
|
||||||
|
setToggle(!isActive);
|
||||||
|
onClick.emit(isActive);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setToggle(isActive: Boolean) {
|
fun setToggle(isActive: Boolean) {
|
||||||
@ -39,9 +52,27 @@ class ToggleTagView : LinearLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setInfo(text: String, isActive: Boolean) {
|
fun setInfo(imageResource: Int, text: String, isActive: Boolean, isButton: Boolean = false) {
|
||||||
_text = text;
|
_text = text;
|
||||||
_textTag.text = text;
|
_textTag.text = text;
|
||||||
setToggle(isActive);
|
setToggle(isActive);
|
||||||
|
_image.setImageResource(imageResource);
|
||||||
|
_image.visibility = View.VISIBLE;
|
||||||
|
this.isButton = isButton;
|
||||||
|
}
|
||||||
|
fun setInfo(image: ImageVariable, text: String, isActive: Boolean, isButton: Boolean = false) {
|
||||||
|
_text = text;
|
||||||
|
_textTag.text = text;
|
||||||
|
setToggle(isActive);
|
||||||
|
image.setImageView(_image, R.drawable.ic_error_pred);
|
||||||
|
_image.visibility = View.VISIBLE;
|
||||||
|
this.isButton = isButton;
|
||||||
|
}
|
||||||
|
fun setInfo(text: String, isActive: Boolean, isButton: Boolean = false) {
|
||||||
|
_image.visibility = View.GONE;
|
||||||
|
_text = text;
|
||||||
|
_textTag.text = text;
|
||||||
|
setToggle(isActive);
|
||||||
|
this.isButton = isButton;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -113,6 +113,13 @@ class SlideUpMenuOverlay : RelativeLayout {
|
|||||||
_textOK.visibility = View.VISIBLE;
|
_textOK.visibility = View.VISIBLE;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
fun getSlideUpItemByTag(itemTag: Any?): SlideUpMenuItem? {
|
||||||
|
for(view in groupItems){
|
||||||
|
if(view is SlideUpMenuItem && view.itemTag == itemTag)
|
||||||
|
return view;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
fun selectOption(groupTag: Any?, itemTag: Any?, multiSelect: Boolean = false, toggle: Boolean = false): Boolean {
|
fun selectOption(groupTag: Any?, itemTag: Any?, multiSelect: Boolean = false, toggle: Boolean = false): Boolean {
|
||||||
var didSelect = false;
|
var didSelect = false;
|
||||||
|
@ -3,14 +3,14 @@
|
|||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<ScrollView
|
<HorizontalScrollView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:scrollbars="horizontal">
|
android:scrollbars="horizontal">
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:id="@+id/container_tags"
|
android:id="@+id/container_tags"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal" />
|
android:orientation="horizontal" />
|
||||||
</ScrollView>
|
</HorizontalScrollView>
|
||||||
</LinearLayout>
|
</LinearLayout>
|
@ -10,16 +10,27 @@
|
|||||||
android:layout_marginTop="17dp"
|
android:layout_marginTop="17dp"
|
||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
android:id="@+id/root">
|
android:id="@+id/root">
|
||||||
|
<LinearLayout
|
||||||
<TextView
|
|
||||||
android:id="@+id/text_tag"
|
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="match_parent"
|
||||||
android:textColor="@color/white"
|
android:orientation="horizontal">
|
||||||
android:layout_gravity="center"
|
<ImageView
|
||||||
android:gravity="center"
|
android:id="@+id/image_tag"
|
||||||
android:textSize="11dp"
|
android:visibility="gone"
|
||||||
android:fontFamily="@font/inter_light"
|
android:layout_width="24dp"
|
||||||
tools:text="Tag text" />
|
android:layout_height="24dp"
|
||||||
|
android:layout_marginRight="5dp"
|
||||||
|
android:layout_marginTop="4dp" />
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/text_tag"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:gravity="center"
|
||||||
|
android:textSize="11dp"
|
||||||
|
android:fontFamily="@font/inter_light"
|
||||||
|
tools:text="Tag text" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
</FrameLayout>
|
</FrameLayout>
|
@ -417,6 +417,8 @@
|
|||||||
<string name="show_subscription_group_description">If subscription groups should be shown above your subscriptions to filter</string>
|
<string name="show_subscription_group_description">If subscription groups should be shown above your subscriptions to filter</string>
|
||||||
<string name="preview_feed_items">Preview Feed Items</string>
|
<string name="preview_feed_items">Preview Feed Items</string>
|
||||||
<string name="preview_feed_items_description">When the preview feedstyle is used, if items should auto-preview when scrolling over them</string>
|
<string name="preview_feed_items_description">When the preview feedstyle is used, if items should auto-preview when scrolling over them</string>
|
||||||
|
<string name="show_home_filters">Show Home Filters</string>
|
||||||
|
<string name="show_home_filters_description">If the home filters should be shown above home</string>
|
||||||
<string name="log_level">Log Level</string>
|
<string name="log_level">Log Level</string>
|
||||||
<string name="logging">Logging</string>
|
<string name="logging">Logging</string>
|
||||||
<string name="sync_grayjay">Sync Grayjay</string>
|
<string name="sync_grayjay">Sync Grayjay</string>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user