Merge branch 'master' into small-hls-fixes

This commit is contained in:
Kai 2025-05-28 16:57:57 -05:00
commit 418f4a6075
No known key found for this signature in database
21 changed files with 143 additions and 74 deletions

6
.gitmodules vendored
View file

@ -100,3 +100,9 @@
[submodule "app/src/unstable/assets/sources/curiositystream"] [submodule "app/src/unstable/assets/sources/curiositystream"]
path = app/src/unstable/assets/sources/curiositystream path = app/src/unstable/assets/sources/curiositystream
url = ../plugins/curiositystream.git url = ../plugins/curiositystream.git
[submodule "app/src/unstable/assets/sources/crunchyroll"]
path = app/src/unstable/assets/sources/crunchyroll
url = ../plugins/crunchyroll.git
[submodule "app/src/stable/assets/sources/crunchyroll"]
path = app/src/stable/assets/sources/crunchyroll
url = ../plugins/crunchyroll.git

View file

@ -198,6 +198,7 @@ dependencies {
implementation 'com.google.android.flexbox:flexbox:3.0.0' implementation 'com.google.android.flexbox:flexbox:3.0.0'
implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation fileTree(dir: 'aar', include: ['*.aar']) implementation fileTree(dir: 'aar', include: ['*.aar'])
implementation 'com.arthenica:smart-exception-java:0.2.1'
implementation 'org.jetbrains.kotlin:kotlin-reflect:1.9.0' implementation 'org.jetbrains.kotlin:kotlin-reflect:1.9.0'
implementation 'com.github.dhaval2404:imagepicker:2.1' implementation 'com.github.dhaval2404:imagepicker:2.1'
implementation 'com.google.zxing:core:3.4.1' implementation 'com.google.zxing:core:3.4.1'

View file

@ -1123,8 +1123,8 @@ class UISlideOverlays {
return SlideUpMenuOverlay(container.context, container, container.context.getString(R.string.add_to), null, true, items).apply { show() }; return SlideUpMenuOverlay(container.context, container, container.context.getString(R.string.add_to), null, true, items).apply { show() };
} }
fun showFiltersOverlay(lifecycleScope: CoroutineScope, container: ViewGroup, enabledClientsIds: List<String>, filterValues: HashMap<String, List<String>>, isChannelSearch: Boolean = false): SlideUpMenuFilters { fun showFiltersOverlay(lifecycleScope: CoroutineScope, container: ViewGroup, enabledClientsIds: List<String>, filterValues: HashMap<String, List<String>>): SlideUpMenuFilters {
val overlay = SlideUpMenuFilters(lifecycleScope, container, enabledClientsIds, filterValues, isChannelSearch); val overlay = SlideUpMenuFilters(lifecycleScope, container, enabledClientsIds, filterValues);
overlay.show(); overlay.show();
return overlay; return overlay;
} }

View file

@ -5,6 +5,7 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
@ -25,6 +26,7 @@ import com.futo.platformplayer.api.media.structures.MultiPager
import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.constructs.Event2
import com.futo.platformplayer.constructs.TaskHandler import com.futo.platformplayer.constructs.TaskHandler
import com.futo.platformplayer.dp
import com.futo.platformplayer.engine.exceptions.PluginException import com.futo.platformplayer.engine.exceptions.PluginException
import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException
import com.futo.platformplayer.exceptions.ChannelException import com.futo.platformplayer.exceptions.ChannelException
@ -32,9 +34,11 @@ import com.futo.platformplayer.fragment.mainactivity.main.FeedView
import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.states.StateCache import com.futo.platformplayer.states.StateCache
import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.states.StatePlatform
import com.futo.platformplayer.states.StatePlugins
import com.futo.platformplayer.states.StatePolycentric import com.futo.platformplayer.states.StatePolycentric
import com.futo.platformplayer.states.StateSubscriptions import com.futo.platformplayer.states.StateSubscriptions
import com.futo.platformplayer.views.FeedStyle import com.futo.platformplayer.views.FeedStyle
import com.futo.platformplayer.views.SearchView
import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder import com.futo.platformplayer.views.adapters.ContentPreviewViewHolder
import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader
import com.futo.platformplayer.views.adapters.feedtypes.PreviewContentListAdapter import com.futo.platformplayer.views.adapters.feedtypes.PreviewContentListAdapter
@ -54,6 +58,8 @@ class ChannelContentsFragment(private val subType: String? = null) : Fragment(),
private var _results: ArrayList<IPlatformContent> = arrayListOf(); private var _results: ArrayList<IPlatformContent> = arrayListOf();
private var _adapterResults: InsertedViewAdapterWithLoader<ContentPreviewViewHolder>? = null; private var _adapterResults: InsertedViewAdapterWithLoader<ContentPreviewViewHolder>? = null;
private var _lastPolycentricProfile: PolycentricProfile? = null; private var _lastPolycentricProfile: PolycentricProfile? = null;
private var _query: String? = null
private var _searchView: SearchView? = null
val onContentClicked = Event2<IPlatformContent, Long>(); val onContentClicked = Event2<IPlatformContent, Long>();
val onContentUrlClicked = Event2<String, ContentType>(); val onContentUrlClicked = Event2<String, ContentType>();
@ -68,17 +74,32 @@ class ChannelContentsFragment(private val subType: String? = null) : Fragment(),
private fun getContentPager(channel: IPlatformChannel): IPager<IPlatformContent> { private fun getContentPager(channel: IPlatformChannel): IPager<IPlatformContent> {
Logger.i(TAG, "getContentPager"); Logger.i(TAG, "getContentPager");
val lastPolycentricProfile = _lastPolycentricProfile; var pager: IPager<IPlatformContent>? = null
var pager: IPager<IPlatformContent>? = null; val query = _query
if (lastPolycentricProfile != null && StatePolycentric.instance.enabled) if (!query.isNullOrBlank()) {
pager = if(subType != null) {
StatePolycentric.instance.getChannelContent(lifecycleScope, lastPolycentricProfile, type = subType); Logger.i(TAG, "StatePlatform.instance.searchChannel(channel.url = ${channel.url}, query = ${query}, subType = ${subType})")
pager = StatePlatform.instance.searchChannel(channel.url, query, subType);
} else {
Logger.i(TAG, "StatePlatform.instance.searchChannel(channel.url = ${channel.url}, query = ${query})")
pager = StatePlatform.instance.searchChannel(channel.url, query);
}
} else {
val lastPolycentricProfile = _lastPolycentricProfile;
if (lastPolycentricProfile != null && StatePolycentric.instance.enabled) {
pager = StatePolycentric.instance.getChannelContent(lifecycleScope, lastPolycentricProfile, type = subType);
Logger.i(TAG, "StatePolycentric.instance.getChannelContent(lifecycleScope, lastPolycentricProfile, type = ${subType})")
}
if(pager == null) { if(pager == null) {
if(subType != null) if(subType != null) {
pager = StatePlatform.instance.getChannelContent(channel.url, subType); pager = StatePlatform.instance.getChannelContent(channel.url, subType);
else Logger.i(TAG, "StatePlatform.instance.getChannelContent(channel.url = ${channel.url}, subType = ${subType})")
pager = StatePlatform.instance.getChannelContent(channel.url); } else {
pager = StatePlatform.instance.getChannelContent(channel.url);
Logger.i(TAG, "StatePlatform.instance.getChannelContent(channel.url = ${channel.url})")
}
}
} }
return pager; return pager;
} }
@ -145,19 +166,44 @@ class ChannelContentsFragment(private val subType: String? = null) : Fragment(),
_taskLoadVideos.cancel(); _taskLoadVideos.cancel();
_query = null
_channel = channel; _channel = channel;
updateSearchViewVisibility()
_results.clear(); _results.clear();
_adapterResults?.notifyDataSetChanged(); _adapterResults?.notifyDataSetChanged();
loadInitial(); loadInitial();
} }
private fun updateSearchViewVisibility() {
val client = _channel?.id?.pluginId?.let { StatePlatform.instance.getClientOrNull(it) }
Logger.i(TAG, "_searchView.visible = ${client?.capabilities?.hasSearchChannelContents == true}")
_searchView?.visibility = if (client?.capabilities?.hasSearchChannelContents == true) View.VISIBLE else View.GONE
}
fun setQuery(query: String) {
_query = query
_taskLoadVideos.cancel()
_results.clear()
_adapterResults?.notifyDataSetChanged()
loadInitial()
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val view = inflater.inflate(R.layout.fragment_channel_videos, container, false); val view = inflater.inflate(R.layout.fragment_channel_videos, container, false);
_query = null
_recyclerResults = view.findViewById(R.id.recycler_videos); _recyclerResults = view.findViewById(R.id.recycler_videos);
_adapterResults = PreviewContentListAdapter(view.context, FeedStyle.THUMBNAIL, _results, null, Settings.instance.channel.progressBar).apply { val searchView = SearchView(requireContext()).apply { layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT) }.apply {
onEnter.subscribe {
setQuery(it)
}
}
_searchView = searchView
updateSearchViewVisibility()
_adapterResults = PreviewContentListAdapter(view.context, FeedStyle.THUMBNAIL, _results, null, Settings.instance.channel.progressBar, viewsToPrepend = arrayListOf(searchView)).apply {
this.onContentUrlClicked.subscribe(this@ChannelContentsFragment.onContentUrlClicked::emit); this.onContentUrlClicked.subscribe(this@ChannelContentsFragment.onContentUrlClicked::emit);
this.onUrlClicked.subscribe(this@ChannelContentsFragment.onUrlClicked::emit); this.onUrlClicked.subscribe(this@ChannelContentsFragment.onUrlClicked::emit);
this.onContentClicked.subscribe(this@ChannelContentsFragment.onContentClicked::emit); this.onContentClicked.subscribe(this@ChannelContentsFragment.onContentClicked::emit);
@ -174,6 +220,7 @@ class ChannelContentsFragment(private val subType: String? = null) : Fragment(),
_recyclerResults?.layoutManager = _glmVideo; _recyclerResults?.layoutManager = _glmVideo;
_recyclerResults?.addOnScrollListener(_scrollListener); _recyclerResults?.addOnScrollListener(_scrollListener);
return view; return view;
} }
@ -182,6 +229,8 @@ class ChannelContentsFragment(private val subType: String? = null) : Fragment(),
_recyclerResults?.removeOnScrollListener(_scrollListener); _recyclerResults?.removeOnScrollListener(_scrollListener);
_recyclerResults = null; _recyclerResults = null;
_pager = null; _pager = null;
_query = null
_searchView = null
_taskLoadVideos.cancel(); _taskLoadVideos.cancel();
_nextPageHandler.cancel(); _nextPageHandler.cancel();
@ -304,6 +353,7 @@ class ChannelContentsFragment(private val subType: String? = null) : Fragment(),
} }
private fun loadInitial() { private fun loadInitial() {
Logger.i(TAG, "loadInitial")
val channel: IPlatformChannel = _channel ?: return; val channel: IPlatformChannel = _channel ?: return;
setLoading(true); setLoading(true);
_taskLoadVideos.run(channel); _taskLoadVideos.run(channel);

View file

@ -425,17 +425,15 @@ class ChannelFragment : MainFragment() {
_fragment.lifecycleScope.launch(Dispatchers.IO) { _fragment.lifecycleScope.launch(Dispatchers.IO) {
val plugin = StatePlatform.instance.getChannelClientOrNull(channel.url) val plugin = StatePlatform.instance.getChannelClientOrNull(channel.url)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
if (plugin != null && plugin.capabilities.hasSearchChannelContents) { buttons.add(Pair(R.drawable.ic_search) {
buttons.add(Pair(R.drawable.ic_search) { _fragment.navigate<SuggestionsFragment>(
_fragment.navigate<SuggestionsFragment>( SuggestionsFragmentData(
SuggestionsFragmentData( "", SearchType.VIDEO
"", SearchType.VIDEO, channel.url
)
) )
}) )
})
_fragment.topBar?.assume<NavigationTopBarFragment>()?.setMenuItems(buttons)
_fragment.topBar?.assume<NavigationTopBarFragment>()?.setMenuItems(buttons)
}
if(plugin != null && plugin.capabilities.hasGetChannelCapabilities) { if(plugin != null && plugin.capabilities.hasGetChannelCapabilities) {
if(plugin.getChannelCapabilities()?.types?.contains(ResultCapabilities.TYPE_SHORTS) ?: false && if(plugin.getChannelCapabilities()?.types?.contains(ResultCapabilities.TYPE_SHORTS) ?: false &&
!(_viewPager.adapter as ChannelViewPagerAdapter).containsItem(ChannelTab.SHORTS.ordinal.toLong())) { !(_viewPager.adapter as ChannelViewPagerAdapter).containsItem(ChannelTab.SHORTS.ordinal.toLong())) {

View file

@ -89,7 +89,6 @@ class ContentSearchResultsFragment : MainFragment() {
private var _sortBy: String? = null; private var _sortBy: String? = null;
private var _filterValues: HashMap<String, List<String>> = hashMapOf(); private var _filterValues: HashMap<String, List<String>> = hashMapOf();
private var _enabledClientIds: List<String>? = null; private var _enabledClientIds: List<String>? = null;
private var _channelUrl: String? = null;
private var _searchType: SearchType? = null; private var _searchType: SearchType? = null;
private val _taskSearch: TaskHandler<String, IPager<IPlatformContent>>; private val _taskSearch: TaskHandler<String, IPager<IPlatformContent>>;
@ -98,17 +97,12 @@ class ContentSearchResultsFragment : MainFragment() {
constructor(fragment: ContentSearchResultsFragment, inflater: LayoutInflater) : super(fragment, inflater) { constructor(fragment: ContentSearchResultsFragment, inflater: LayoutInflater) : super(fragment, inflater) {
_taskSearch = TaskHandler<String, IPager<IPlatformContent>>({fragment.lifecycleScope}, { query -> _taskSearch = TaskHandler<String, IPager<IPlatformContent>>({fragment.lifecycleScope}, { query ->
Logger.i(TAG, "Searching for: $query") Logger.i(TAG, "Searching for: $query")
val channelUrl = _channelUrl; when (_searchType)
if (channelUrl != null) { {
StatePlatform.instance.searchChannel(channelUrl, query, null, _sortBy, _filterValues, _enabledClientIds) SearchType.VIDEO -> StatePlatform.instance.searchRefresh(fragment.lifecycleScope, query, null, _sortBy, _filterValues, _enabledClientIds)
} else { SearchType.CREATOR -> StatePlatform.instance.searchChannelsAsContent(query)
when (_searchType) SearchType.PLAYLIST -> StatePlatform.instance.searchPlaylist(query)
{ else -> throw Exception("Search type must be specified")
SearchType.VIDEO -> StatePlatform.instance.searchRefresh(fragment.lifecycleScope, query, null, _sortBy, _filterValues, _enabledClientIds)
SearchType.CREATOR -> StatePlatform.instance.searchChannelsAsContent(query)
SearchType.PLAYLIST -> StatePlatform.instance.searchPlaylist(query)
else -> throw Exception("Search type must be specified")
}
} }
}) })
.success { loadedResult(it); }.exception<ScriptCaptchaRequiredException> { } .success { loadedResult(it); }.exception<ScriptCaptchaRequiredException> { }
@ -147,7 +141,6 @@ class ContentSearchResultsFragment : MainFragment() {
fun onShown(parameter: Any?) { fun onShown(parameter: Any?) {
if(parameter is SuggestionsFragmentData) { if(parameter is SuggestionsFragmentData) {
setQuery(parameter.query, false); setQuery(parameter.query, false);
setChannelUrl(parameter.channelUrl, false);
setSearchType(parameter.searchType, false) setSearchType(parameter.searchType, false)
fragment.topBar?.apply { fragment.topBar?.apply {
@ -164,7 +157,7 @@ class ContentSearchResultsFragment : MainFragment() {
onFilterClick.subscribe(this) { onFilterClick.subscribe(this) {
_overlayContainer.let { _overlayContainer.let {
val filterValuesCopy = HashMap(_filterValues); val filterValuesCopy = HashMap(_filterValues);
val filtersOverlay = UISlideOverlays.showFiltersOverlay(lifecycleScope, it, _enabledClientIds!!, filterValuesCopy, _channelUrl != null); val filtersOverlay = UISlideOverlays.showFiltersOverlay(lifecycleScope, it, _enabledClientIds!!, filterValuesCopy);
filtersOverlay.onOK.subscribe { enabledClientIds, changed -> filtersOverlay.onOK.subscribe { enabledClientIds, changed ->
if (changed) { if (changed) {
setFilterValues(filtersOverlay.commonCapabilities, filterValuesCopy); setFilterValues(filtersOverlay.commonCapabilities, filterValuesCopy);
@ -211,11 +204,7 @@ class ContentSearchResultsFragment : MainFragment() {
fragment.lifecycleScope.launch(Dispatchers.IO) { fragment.lifecycleScope.launch(Dispatchers.IO) {
try { try {
val commonCapabilities = val commonCapabilities = StatePlatform.instance.getCommonSearchCapabilities(StatePlatform.instance.getEnabledClients().map { it.id });
if(_channelUrl == null)
StatePlatform.instance.getCommonSearchCapabilities(StatePlatform.instance.getEnabledClients().map { it.id });
else
StatePlatform.instance.getCommonSearchChannelContentsCapabilities(StatePlatform.instance.getEnabledClients().map { it.id });
val sorts = commonCapabilities?.sorts ?: listOf(); val sorts = commonCapabilities?.sorts ?: listOf();
if (sorts.size > 1) { if (sorts.size > 1) {
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
@ -282,15 +271,6 @@ class ContentSearchResultsFragment : MainFragment() {
} }
} }
private fun setChannelUrl(channelUrl: String?, updateResults: Boolean = true) {
_channelUrl = channelUrl;
if (updateResults) {
clearResults();
loadResults();
}
}
private fun setSearchType(searchType: SearchType, updateResults: Boolean = true) { private fun setSearchType(searchType: SearchType, updateResults: Boolean = true) {
_searchType = searchType _searchType = searchType

View file

@ -217,7 +217,7 @@ class PlaylistsFragment : MainFragment() {
var playlistsToReturn = pls; var playlistsToReturn = pls;
if(!_listPlaylistsSearch.text.isNullOrEmpty()) if(!_listPlaylistsSearch.text.isNullOrEmpty())
playlistsToReturn = playlistsToReturn.filter { it.name.contains(_listPlaylistsSearch.text, true) }; playlistsToReturn = playlistsToReturn.filter { it.name.contains(_listPlaylistsSearch.text, true) };
if(!_ordering.value.isNullOrEmpty()){ if(!_ordering.value.isNullOrEmpty()) {
playlistsToReturn = when(_ordering.value){ playlistsToReturn = when(_ordering.value){
"nameAsc" -> playlistsToReturn.sortedBy { it.name.lowercase() } "nameAsc" -> playlistsToReturn.sortedBy { it.name.lowercase() }
"nameDesc" -> playlistsToReturn.sortedByDescending { it.name.lowercase() }; "nameDesc" -> playlistsToReturn.sortedByDescending { it.name.lowercase() };

View file

@ -21,7 +21,7 @@ import com.futo.platformplayer.views.adapters.SearchSuggestionAdapter
import com.futo.platformplayer.views.others.RadioGroupView import com.futo.platformplayer.views.others.RadioGroupView
import com.futo.platformplayer.views.others.TagsView import com.futo.platformplayer.views.others.TagsView
data class SuggestionsFragmentData(val query: String, val searchType: SearchType, val channelUrl: String? = null); data class SuggestionsFragmentData(val query: String, val searchType: SearchType);
class SuggestionsFragment : MainFragment { class SuggestionsFragment : MainFragment {
override val isMainView : Boolean = true; override val isMainView : Boolean = true;
@ -34,7 +34,6 @@ class SuggestionsFragment : MainFragment {
private val _suggestions: ArrayList<String> = ArrayList(); private val _suggestions: ArrayList<String> = ArrayList();
private var _query: String? = null; private var _query: String? = null;
private var _searchType: SearchType = SearchType.VIDEO; private var _searchType: SearchType = SearchType.VIDEO;
private var _channelUrl: String? = null;
private val _adapterSuggestions = SearchSuggestionAdapter(_suggestions); private val _adapterSuggestions = SearchSuggestionAdapter(_suggestions);
@ -52,7 +51,7 @@ class SuggestionsFragment : MainFragment {
_adapterSuggestions.onClicked.subscribe { suggestion -> _adapterSuggestions.onClicked.subscribe { suggestion ->
val storage = FragmentedStorage.get<SearchHistoryStorage>(); val storage = FragmentedStorage.get<SearchHistoryStorage>();
storage.add(suggestion); storage.add(suggestion);
navigate<ContentSearchResultsFragment>(SuggestionsFragmentData(suggestion, _searchType, _channelUrl)); navigate<ContentSearchResultsFragment>(SuggestionsFragmentData(suggestion, _searchType));
} }
_adapterSuggestions.onRemove.subscribe { suggestion -> _adapterSuggestions.onRemove.subscribe { suggestion ->
val index = _suggestions.indexOf(suggestion); val index = _suggestions.indexOf(suggestion);
@ -109,10 +108,8 @@ class SuggestionsFragment : MainFragment {
if (parameter is SuggestionsFragmentData) { if (parameter is SuggestionsFragmentData) {
_searchType = parameter.searchType; _searchType = parameter.searchType;
_channelUrl = parameter.channelUrl;
} else if (parameter is SearchType) { } else if (parameter is SearchType) {
_searchType = parameter; _searchType = parameter;
_channelUrl = null;
} }
_radioGroupView?.setOptions(listOf(Pair("Media", SearchType.VIDEO), Pair("Creators", SearchType.CREATOR), Pair("Playlists", SearchType.PLAYLIST)), listOf(_searchType), false, true) _radioGroupView?.setOptions(listOf(Pair("Media", SearchType.VIDEO), Pair("Creators", SearchType.CREATOR), Pair("Playlists", SearchType.PLAYLIST)), listOf(_searchType), false, true)
@ -135,7 +132,7 @@ class SuggestionsFragment : MainFragment {
} }
} }
else else
navigate<ContentSearchResultsFragment>(SuggestionsFragmentData(it, _searchType, _channelUrl)); navigate<ContentSearchResultsFragment>(SuggestionsFragmentData(it, _searchType));
}; };
onTextChange.subscribe(this) { onTextChange.subscribe(this) {

View file

@ -2680,9 +2680,10 @@ class VideoDetailView : ConstraintLayout {
} }
onChannelClicked.subscribe { onChannelClicked.subscribe {
if(it.url.isNotBlank()) if(it.url.isNotBlank()) {
fragment.minimizeVideoDetail()
fragment.navigate<ChannelFragment>(it) fragment.navigate<ChannelFragment>(it)
else } else
UIDialogs.appToast("No author url present"); UIDialogs.appToast("No author url present");
} }

View file

@ -88,7 +88,6 @@ class SearchTopBarFragment : TopFragment() {
} else if (parameter is SuggestionsFragmentData) { } else if (parameter is SuggestionsFragmentData) {
this.setText(parameter.query); this.setText(parameter.query);
_searchType = parameter.searchType; _searchType = parameter.searchType;
_channelUrl = parameter.channelUrl;
} }
if(currentMain is SuggestionsFragment) if(currentMain is SuggestionsFragment)
@ -114,7 +113,7 @@ class SearchTopBarFragment : TopFragment() {
fun clear() { fun clear() {
_editSearch?.text?.clear(); _editSearch?.text?.clear();
if (currentMain !is SuggestionsFragment) { if (currentMain !is SuggestionsFragment) {
navigate<SuggestionsFragment>(SuggestionsFragmentData("", _searchType, _channelUrl), false); navigate<SuggestionsFragment>(SuggestionsFragmentData("", _searchType), false);
} else { } else {
onSearch.emit(""); onSearch.emit("");
} }

View file

@ -3,6 +3,8 @@ package com.futo.platformplayer.views
import android.content.Context import android.content.Context
import android.text.TextWatcher import android.text.TextWatcher
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.ImageButton import android.widget.ImageButton
@ -30,9 +32,26 @@ class SearchView : FrameLayout {
textSearch = findViewById(R.id.edit_search) textSearch = findViewById(R.id.edit_search)
buttonClear = findViewById(R.id.button_clear_search) buttonClear = findViewById(R.id.button_clear_search)
buttonClear.setOnClickListener { textSearch.text = "" }; buttonClear.setOnClickListener {
textSearch.text = ""
textSearch?.clearFocus()
(context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).hideSoftInputFromWindow(textSearch.windowToken, 0)
onSearchChanged.emit("")
onEnter.emit("")
}
textSearch.setOnEditorActionListener { _, i, _ ->
if (i == EditorInfo.IME_ACTION_DONE) {
textSearch?.clearFocus()
(context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager).hideSoftInputFromWindow(textSearch.windowToken, 0)
onEnter.emit(textSearch.text.toString())
return@setOnEditorActionListener true
}
return@setOnEditorActionListener false
}
textSearch.addTextChangedListener { textSearch.addTextChangedListener {
onSearchChanged.emit(it.toString()); buttonClear.visibility = if ((it?.length ?: 0) > 0) View.VISIBLE else View.GONE
onSearchChanged.emit(it.toString())
}; };
} }
} }

View file

@ -28,17 +28,14 @@ class SlideUpMenuFilters {
private var _changed: Boolean = false; private var _changed: Boolean = false;
private val _lifecycleScope: CoroutineScope; private val _lifecycleScope: CoroutineScope;
private var _isChannelSearch = false;
var commonCapabilities: ResultCapabilities? = null; var commonCapabilities: ResultCapabilities? = null;
constructor(lifecycleScope: CoroutineScope, container: ViewGroup, enabledClientsIds: List<String>, filterValues: HashMap<String, List<String>>, isChannelSearch: Boolean = false) { constructor(lifecycleScope: CoroutineScope, container: ViewGroup, enabledClientsIds: List<String>, filterValues: HashMap<String, List<String>>) {
_lifecycleScope = lifecycleScope; _lifecycleScope = lifecycleScope;
_container = container; _container = container;
_enabledClientsIds = enabledClientsIds; _enabledClientsIds = enabledClientsIds;
_filterValues = filterValues; _filterValues = filterValues;
_isChannelSearch = isChannelSearch;
_slideUpMenuOverlay = SlideUpMenuOverlay(_container.context, _container, container.context.getString(R.string.filters), container.context.getString(R.string.done), true, listOf()); _slideUpMenuOverlay = SlideUpMenuOverlay(_container.context, _container, container.context.getString(R.string.filters), container.context.getString(R.string.done), true, listOf());
_slideUpMenuOverlay.onOK.subscribe { _slideUpMenuOverlay.onOK.subscribe {
onOK.emit(_enabledClientsIds, _changed); onOK.emit(_enabledClientsIds, _changed);
@ -51,10 +48,7 @@ class SlideUpMenuFilters {
private fun updateCommonCapabilities() { private fun updateCommonCapabilities() {
_lifecycleScope.launch(Dispatchers.IO) { _lifecycleScope.launch(Dispatchers.IO) {
try { try {
val caps = if(!_isChannelSearch) val caps = StatePlatform.instance.getCommonSearchCapabilities(_enabledClientsIds);
StatePlatform.instance.getCommonSearchCapabilities(_enabledClientsIds);
else
StatePlatform.instance.getCommonSearchChannelContentsCapabilities(_enabledClientsIds);
synchronized(_filterValues) { synchronized(_filterValues) {
if (caps != null) { if (caps != null) {
val keysToRemove = arrayListOf<String>(); val keysToRemove = arrayListOf<String>();

View file

@ -531,6 +531,8 @@ class FutoVideoPlayer : FutoVideoPlayerBase {
fun setLoopVisible(visible: Boolean) { fun setLoopVisible(visible: Boolean) {
_control_loop.visibility = if (visible) View.VISIBLE else View.GONE; _control_loop.visibility = if (visible) View.VISIBLE else View.GONE;
_control_loop_fullscreen.visibility = if (visible) View.VISIBLE else View.GONE; _control_loop_fullscreen.visibility = if (visible) View.VISIBLE else View.GONE;
if (StatePlayer.instance.loopVideo && !visible)
StatePlayer.instance.loopVideo = false
} }
fun stopAllGestures() { fun stopAllGestures() {

View file

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<path
android:pathData="m54,75 l25,-38h-50z"
android:strokeWidth=".83"
android:fillColor="#00000000"
android:strokeColor="#000"
android:strokeLineCap="round"/>
<path
android:fillColor="#FF000000"
android:pathData="m34,64c1,-1.1 3.6,-3.7 5.7,-5 2,-1.3 4.4,-5 5.4,-6.6 2.9,-4.3 9.4,-13 12,-15 0.5,-0.39 1.2,-1 1.5,-1.3 1,-2.3 4.6,-6.3 10,-3.5 0.5,-0.17 1.7,-0.21 2.3,-0.21 -0.44,0.3 -1.4,1.2 -1.5,2.3 -0.45,3.1 -2.1,4.9 -2.9,5.4 -0.56,3.6 -1.3,6.7 -3,7.8l1.5,2.7c2.3,2.4 7.1,7.7 7.8,9.3 -2.2,-0.73 -3.7,-1.4 -4.1,-1.7l4.1,5.7c-2.4,-0.19 -7.9,-1.8 -10,-6.6 0.95,2.6 1.9,5.8 2.2,7.1 -1.3,-1.1 -4.1,-4.2 -4.9,-8 0.22,3.7 0.19,6.4 0.14,7.3 -0.63,-0.58 -2.1,-2.4 -2.9,-5v4.3c-0.94,-1.3 -2.8,-4.5 -3,-6.9 0.16,2.7 0.06,4 -0.01,4.3l-3.6,-3.4c-0.95,0.51 -3.5,1.7 -6.1,2.2 -1.8,1.5 -4,5.3 -4.8,7v-2.2l-2.4,2.4 0.84,-2.5 -1.5,1.3c-0.35,0.21 -1.2,0.63 -1.6,0.63 0.17,-0.39 0.49,-0.82 0.63,-0.98l-2,0.77c0.23,-0.68 0.96,-2.2 2,-2.7 -1.5,0.56 -2,0.7 -2.2,0.7z"
android:strokeWidth=".2"/>
</vector>

View file

@ -233,7 +233,7 @@
android:isScrollContainer="true" android:isScrollContainer="true"
android:scrollbars="vertical" android:scrollbars="vertical"
android:maxHeight="200dp" android:maxHeight="200dp"
android:text="An error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurredAn error has occurred" /> android:text="An error has occurred" />
</LinearLayout> </LinearLayout>
</LinearLayout> </LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/> <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
</adaptive-icon> </adaptive-icon>

View file

@ -2,4 +2,5 @@
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_launcher_background"/> <background android:drawable="@color/ic_launcher_background"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/> <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<monochrome android:drawable="@drawable/ic_launcher_monochrome"/>
</adaptive-icon> </adaptive-icon>

@ -0,0 +1 @@
Subproject commit 1aa91f216c0a87604aed1669b63b7830e4288630

View file

@ -15,7 +15,8 @@
"e8b1ad5f-0c6d-497d-a5fa-0a785a16d902": "sources/bitchute/BitchuteConfig.json", "e8b1ad5f-0c6d-497d-a5fa-0a785a16d902": "sources/bitchute/BitchuteConfig.json",
"89ae4889-0420-4d16-ad6c-19c776b28f99": "sources/apple-podcasts/ApplePodcastsConfig.json", "89ae4889-0420-4d16-ad6c-19c776b28f99": "sources/apple-podcasts/ApplePodcastsConfig.json",
"8d029a7f-5507-4e36-8bd8-c19a3b77d383": "sources/tedtalks/TedTalksConfig.json", "8d029a7f-5507-4e36-8bd8-c19a3b77d383": "sources/tedtalks/TedTalksConfig.json",
"273b6523-5438-44e2-9f5d-78e0325a8fd9": "sources/curiositystream/CuriosityStreamConfig.json" "273b6523-5438-44e2-9f5d-78e0325a8fd9": "sources/curiositystream/CuriosityStreamConfig.json",
"9bb33039-8580-48d4-9849-21319ae845a4": "sources/crunchyroll/CrunchyrollConfig.json"
}, },
"SOURCES_EMBEDDED_DEFAULT": [ "SOURCES_EMBEDDED_DEFAULT": [
"35ae969a-a7db-11ed-afa1-0242ac120002" "35ae969a-a7db-11ed-afa1-0242ac120002"

@ -0,0 +1 @@
Subproject commit 1aa91f216c0a87604aed1669b63b7830e4288630

View file

@ -15,7 +15,8 @@
"e8b1ad5f-0c6d-497d-a5fa-0a785a16d902": "sources/bitchute/BitchuteConfig.json", "e8b1ad5f-0c6d-497d-a5fa-0a785a16d902": "sources/bitchute/BitchuteConfig.json",
"89ae4889-0420-4d16-ad6c-19c776b28f99": "sources/apple-podcasts/ApplePodcastsConfig.json", "89ae4889-0420-4d16-ad6c-19c776b28f99": "sources/apple-podcasts/ApplePodcastsConfig.json",
"8d029a7f-5507-4e36-8bd8-c19a3b77d383": "sources/tedtalks/TedTalksConfig.json", "8d029a7f-5507-4e36-8bd8-c19a3b77d383": "sources/tedtalks/TedTalksConfig.json",
"273b6523-5438-44e2-9f5d-78e0325a8fd9": "sources/curiositystream/CuriosityStreamConfig.json" "273b6523-5438-44e2-9f5d-78e0325a8fd9": "sources/curiositystream/CuriosityStreamConfig.json",
"9bb33039-8580-48d4-9849-21319ae845a4": "sources/crunchyroll/CrunchyrollConfig.json"
}, },
"SOURCES_EMBEDDED_DEFAULT": [ "SOURCES_EMBEDDED_DEFAULT": [
"35ae969a-a7db-11ed-afa1-0242ac120002" "35ae969a-a7db-11ed-afa1-0242ac120002"