From e0811cfd93d914ad9b6019a9d03717e0c687be7f Mon Sep 17 00:00:00 2001 From: Koen J Date: Wed, 28 May 2025 20:08:22 +0200 Subject: [PATCH] Implemented new channel search. --- .../futo/platformplayer/UISlideOverlays.kt | 4 +- .../channel/tab/ChannelContentsFragment.kt | 64 +++++++++++++++---- .../mainactivity/main/ChannelFragment.kt | 16 ++--- .../main/ContentSearchResultsFragment.kt | 36 +++-------- .../mainactivity/main/SuggestionsFragment.kt | 9 +-- .../mainactivity/main/VideoDetailView.kt | 5 +- .../topbar/SearchTopBarFragment.kt | 3 +- .../futo/platformplayer/views/SearchView.kt | 23 ++++++- .../overlays/slideup/SlideUpMenuFilters.kt | 10 +-- 9 files changed, 100 insertions(+), 70 deletions(-) diff --git a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt index 61bdf97e..92683a7c 100644 --- a/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt +++ b/app/src/main/java/com/futo/platformplayer/UISlideOverlays.kt @@ -1121,8 +1121,8 @@ class UISlideOverlays { 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, filterValues: HashMap>, isChannelSearch: Boolean = false): SlideUpMenuFilters { - val overlay = SlideUpMenuFilters(lifecycleScope, container, enabledClientsIds, filterValues, isChannelSearch); + fun showFiltersOverlay(lifecycleScope: CoroutineScope, container: ViewGroup, enabledClientsIds: List, filterValues: HashMap>): SlideUpMenuFilters { + val overlay = SlideUpMenuFilters(lifecycleScope, container, enabledClientsIds, filterValues); overlay.show(); return overlay; } diff --git a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelContentsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelContentsFragment.kt index 788fd4c1..e38b4c1d 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelContentsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelContentsFragment.kt @@ -5,6 +5,7 @@ import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.FrameLayout import androidx.fragment.app.Fragment import androidx.lifecycle.lifecycleScope 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.Event2 import com.futo.platformplayer.constructs.TaskHandler +import com.futo.platformplayer.dp import com.futo.platformplayer.engine.exceptions.PluginException import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException 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.states.StateCache import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.states.StatePlugins import com.futo.platformplayer.states.StatePolycentric import com.futo.platformplayer.states.StateSubscriptions 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.InsertedViewAdapterWithLoader import com.futo.platformplayer.views.adapters.feedtypes.PreviewContentListAdapter @@ -54,6 +58,8 @@ class ChannelContentsFragment(private val subType: String? = null) : Fragment(), private var _results: ArrayList = arrayListOf(); private var _adapterResults: InsertedViewAdapterWithLoader? = null; private var _lastPolycentricProfile: PolycentricProfile? = null; + private var _query: String? = null + private var _searchView: SearchView? = null val onContentClicked = Event2(); val onContentUrlClicked = Event2(); @@ -68,17 +74,32 @@ class ChannelContentsFragment(private val subType: String? = null) : Fragment(), private fun getContentPager(channel: IPlatformChannel): IPager { Logger.i(TAG, "getContentPager"); - val lastPolycentricProfile = _lastPolycentricProfile; - var pager: IPager? = null; - if (lastPolycentricProfile != null && StatePolycentric.instance.enabled) - pager = - StatePolycentric.instance.getChannelContent(lifecycleScope, lastPolycentricProfile, type = subType); + var pager: IPager? = null + val query = _query + if (!query.isNullOrBlank()) { + if(subType != null) { + 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(subType != null) - pager = StatePlatform.instance.getChannelContent(channel.url, subType); - else - pager = StatePlatform.instance.getChannelContent(channel.url); + if(pager == null) { + if(subType != null) { + pager = StatePlatform.instance.getChannelContent(channel.url, subType); + Logger.i(TAG, "StatePlatform.instance.getChannelContent(channel.url = ${channel.url}, subType = ${subType})") + } else { + pager = StatePlatform.instance.getChannelContent(channel.url); + Logger.i(TAG, "StatePlatform.instance.getChannelContent(channel.url = ${channel.url})") + } + } } return pager; } @@ -145,6 +166,9 @@ class ChannelContentsFragment(private val subType: String? = null) : Fragment(), _taskLoadVideos.cancel(); + val pid = channel.id.pluginId + _searchView?.visibility = if (pid != null && StatePlatform.instance.getClientOrNull(pid)?.capabilities?.hasSearchChannelContents == true) View.VISIBLE else View.GONE + _query = null _channel = channel; _results.clear(); _adapterResults?.notifyDataSetChanged(); @@ -152,12 +176,26 @@ class ChannelContentsFragment(private val subType: String? = null) : Fragment(), loadInitial(); } + fun setQuery(query: String) { + _query = query + loadInitial() + } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = inflater.inflate(R.layout.fragment_channel_videos, container, false); + _query = null _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) + } + visibility = View.GONE + } + _searchView = searchView + + _adapterResults = PreviewContentListAdapter(view.context, FeedStyle.THUMBNAIL, _results, null, Settings.instance.channel.progressBar, viewsToPrepend = arrayListOf(searchView)).apply { this.onContentUrlClicked.subscribe(this@ChannelContentsFragment.onContentUrlClicked::emit); this.onUrlClicked.subscribe(this@ChannelContentsFragment.onUrlClicked::emit); this.onContentClicked.subscribe(this@ChannelContentsFragment.onContentClicked::emit); @@ -174,6 +212,7 @@ class ChannelContentsFragment(private val subType: String? = null) : Fragment(), _recyclerResults?.layoutManager = _glmVideo; _recyclerResults?.addOnScrollListener(_scrollListener); + return view; } @@ -182,6 +221,8 @@ class ChannelContentsFragment(private val subType: String? = null) : Fragment(), _recyclerResults?.removeOnScrollListener(_scrollListener); _recyclerResults = null; _pager = null; + _query = null + _searchView = null _taskLoadVideos.cancel(); _nextPageHandler.cancel(); @@ -304,6 +345,7 @@ class ChannelContentsFragment(private val subType: String? = null) : Fragment(), } private fun loadInitial() { + Logger.i(TAG, "loadInitial") val channel: IPlatformChannel = _channel ?: return; setLoading(true); _taskLoadVideos.run(channel); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt index 3ac71315..63b60c1f 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt @@ -425,17 +425,15 @@ class ChannelFragment : MainFragment() { _fragment.lifecycleScope.launch(Dispatchers.IO) { val plugin = StatePlatform.instance.getChannelClientOrNull(channel.url) withContext(Dispatchers.Main) { - if (plugin != null && plugin.capabilities.hasSearchChannelContents) { - buttons.add(Pair(R.drawable.ic_search) { - _fragment.navigate( - SuggestionsFragmentData( - "", SearchType.VIDEO, channel.url - ) + buttons.add(Pair(R.drawable.ic_search) { + _fragment.navigate( + SuggestionsFragmentData( + "", SearchType.VIDEO ) - }) + ) + }) + _fragment.topBar?.assume()?.setMenuItems(buttons) - _fragment.topBar?.assume()?.setMenuItems(buttons) - } if(plugin != null && plugin.capabilities.hasGetChannelCapabilities) { if(plugin.getChannelCapabilities()?.types?.contains(ResultCapabilities.TYPE_SHORTS) ?: false && !(_viewPager.adapter as ChannelViewPagerAdapter).containsItem(ChannelTab.SHORTS.ordinal.toLong())) { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt index 4d517b99..72094903 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt @@ -89,7 +89,6 @@ class ContentSearchResultsFragment : MainFragment() { private var _sortBy: String? = null; private var _filterValues: HashMap> = hashMapOf(); private var _enabledClientIds: List? = null; - private var _channelUrl: String? = null; private var _searchType: SearchType? = null; private val _taskSearch: TaskHandler>; @@ -98,17 +97,12 @@ class ContentSearchResultsFragment : MainFragment() { constructor(fragment: ContentSearchResultsFragment, inflater: LayoutInflater) : super(fragment, inflater) { _taskSearch = TaskHandler>({fragment.lifecycleScope}, { query -> Logger.i(TAG, "Searching for: $query") - val channelUrl = _channelUrl; - if (channelUrl != null) { - StatePlatform.instance.searchChannel(channelUrl, query, null, _sortBy, _filterValues, _enabledClientIds) - } else { - when (_searchType) - { - 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") - } + when (_searchType) + { + 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 { } @@ -147,7 +141,6 @@ class ContentSearchResultsFragment : MainFragment() { fun onShown(parameter: Any?) { if(parameter is SuggestionsFragmentData) { setQuery(parameter.query, false); - setChannelUrl(parameter.channelUrl, false); setSearchType(parameter.searchType, false) fragment.topBar?.apply { @@ -164,7 +157,7 @@ class ContentSearchResultsFragment : MainFragment() { onFilterClick.subscribe(this) { _overlayContainer.let { 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 -> if (changed) { setFilterValues(filtersOverlay.commonCapabilities, filterValuesCopy); @@ -211,11 +204,7 @@ class ContentSearchResultsFragment : MainFragment() { fragment.lifecycleScope.launch(Dispatchers.IO) { try { - val commonCapabilities = - if(_channelUrl == null) - StatePlatform.instance.getCommonSearchCapabilities(StatePlatform.instance.getEnabledClients().map { it.id }); - else - StatePlatform.instance.getCommonSearchChannelContentsCapabilities(StatePlatform.instance.getEnabledClients().map { it.id }); + val commonCapabilities = StatePlatform.instance.getCommonSearchCapabilities(StatePlatform.instance.getEnabledClients().map { it.id }); val sorts = commonCapabilities?.sorts ?: listOf(); if (sorts.size > 1) { 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) { _searchType = searchType diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SuggestionsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SuggestionsFragment.kt index 0dfb867d..e0a68cc5 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SuggestionsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SuggestionsFragment.kt @@ -21,7 +21,7 @@ import com.futo.platformplayer.views.adapters.SearchSuggestionAdapter import com.futo.platformplayer.views.others.RadioGroupView 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 { override val isMainView : Boolean = true; @@ -34,7 +34,6 @@ class SuggestionsFragment : MainFragment { private val _suggestions: ArrayList = ArrayList(); private var _query: String? = null; private var _searchType: SearchType = SearchType.VIDEO; - private var _channelUrl: String? = null; private val _adapterSuggestions = SearchSuggestionAdapter(_suggestions); @@ -52,7 +51,7 @@ class SuggestionsFragment : MainFragment { _adapterSuggestions.onClicked.subscribe { suggestion -> val storage = FragmentedStorage.get(); storage.add(suggestion); - navigate(SuggestionsFragmentData(suggestion, _searchType, _channelUrl)); + navigate(SuggestionsFragmentData(suggestion, _searchType)); } _adapterSuggestions.onRemove.subscribe { suggestion -> val index = _suggestions.indexOf(suggestion); @@ -109,10 +108,8 @@ class SuggestionsFragment : MainFragment { if (parameter is SuggestionsFragmentData) { _searchType = parameter.searchType; - _channelUrl = parameter.channelUrl; } else if (parameter is SearchType) { _searchType = parameter; - _channelUrl = null; } _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 - navigate(SuggestionsFragmentData(it, _searchType, _channelUrl)); + navigate(SuggestionsFragmentData(it, _searchType)); }; onTextChange.subscribe(this) { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt index d1a3e41e..fd2cb19b 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt @@ -2680,9 +2680,10 @@ class VideoDetailView : ConstraintLayout { } onChannelClicked.subscribe { - if(it.url.isNotBlank()) + if(it.url.isNotBlank()) { + fragment.minimizeVideoDetail() fragment.navigate(it) - else + } else UIDialogs.appToast("No author url present"); } diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/SearchTopBarFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/SearchTopBarFragment.kt index 44d8a9ad..15952d0a 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/SearchTopBarFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/SearchTopBarFragment.kt @@ -88,7 +88,6 @@ class SearchTopBarFragment : TopFragment() { } else if (parameter is SuggestionsFragmentData) { this.setText(parameter.query); _searchType = parameter.searchType; - _channelUrl = parameter.channelUrl; } if(currentMain is SuggestionsFragment) @@ -114,7 +113,7 @@ class SearchTopBarFragment : TopFragment() { fun clear() { _editSearch?.text?.clear(); if (currentMain !is SuggestionsFragment) { - navigate(SuggestionsFragmentData("", _searchType, _channelUrl), false); + navigate(SuggestionsFragmentData("", _searchType), false); } else { onSearch.emit(""); } diff --git a/app/src/main/java/com/futo/platformplayer/views/SearchView.kt b/app/src/main/java/com/futo/platformplayer/views/SearchView.kt index c36a04a5..c7d68127 100644 --- a/app/src/main/java/com/futo/platformplayer/views/SearchView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/SearchView.kt @@ -3,6 +3,8 @@ package com.futo.platformplayer.views import android.content.Context import android.text.TextWatcher import android.util.AttributeSet +import android.view.View +import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager import android.widget.FrameLayout import android.widget.ImageButton @@ -30,9 +32,26 @@ class SearchView : FrameLayout { textSearch = findViewById(R.id.edit_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 { - onSearchChanged.emit(it.toString()); + buttonClear.visibility = if ((it?.length ?: 0) > 0) View.VISIBLE else View.GONE + onSearchChanged.emit(it.toString()) }; } } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuFilters.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuFilters.kt index 75b50a26..9180a3f1 100644 --- a/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuFilters.kt +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/slideup/SlideUpMenuFilters.kt @@ -28,17 +28,14 @@ class SlideUpMenuFilters { private var _changed: Boolean = false; private val _lifecycleScope: CoroutineScope; - private var _isChannelSearch = false; - var commonCapabilities: ResultCapabilities? = null; - constructor(lifecycleScope: CoroutineScope, container: ViewGroup, enabledClientsIds: List, filterValues: HashMap>, isChannelSearch: Boolean = false) { + constructor(lifecycleScope: CoroutineScope, container: ViewGroup, enabledClientsIds: List, filterValues: HashMap>) { _lifecycleScope = lifecycleScope; _container = container; _enabledClientsIds = enabledClientsIds; _filterValues = filterValues; - _isChannelSearch = isChannelSearch; _slideUpMenuOverlay = SlideUpMenuOverlay(_container.context, _container, container.context.getString(R.string.filters), container.context.getString(R.string.done), true, listOf()); _slideUpMenuOverlay.onOK.subscribe { onOK.emit(_enabledClientsIds, _changed); @@ -51,10 +48,7 @@ class SlideUpMenuFilters { private fun updateCommonCapabilities() { _lifecycleScope.launch(Dispatchers.IO) { try { - val caps = if(!_isChannelSearch) - StatePlatform.instance.getCommonSearchCapabilities(_enabledClientsIds); - else - StatePlatform.instance.getCommonSearchChannelContentsCapabilities(_enabledClientsIds); + val caps = StatePlatform.instance.getCommonSearchCapabilities(_enabledClientsIds); synchronized(_filterValues) { if (caps != null) { val keysToRemove = arrayListOf();