From 550099fe0b85c4832e703662212e580f6e1e0c64 Mon Sep 17 00:00:00 2001 From: NivinCNC Date: Wed, 3 Dec 2025 22:06:51 +0530 Subject: [PATCH 1/9] Add search suggestions to search UI Implemented search suggestions using Google's autocomplete API. Added SearchSuggestionAdapter, SearchSuggestionApi, and related UI components to display and interact with suggestions in both mobile and TV search fragments. Suggestions are fetched with debounce and can be selected to search or fill the search box. --- .../cloudstream3/ui/search/SearchFragment.kt | 32 +++++++ .../ui/search/SearchSuggestionAdapter.kt | 48 ++++++++++ .../ui/search/SearchSuggestionApi.kt | 93 +++++++++++++++++++ .../cloudstream3/ui/search/SearchViewModel.kt | 35 +++++++ .../drawable/ic_baseline_north_west_24.xml | 9 ++ app/src/main/res/layout/fragment_search.xml | 10 ++ .../main/res/layout/fragment_search_tv.xml | 11 +++ .../res/layout/search_suggestion_item.xml | 43 +++++++++ 8 files changed, 281 insertions(+) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchSuggestionAdapter.kt create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchSuggestionApi.kt create mode 100644 app/src/main/res/drawable/ic_baseline_north_west_24.xml create mode 100644 app/src/main/res/layout/search_suggestion_item.xml diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt index d1efe62054b..419b26b4b6d 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt @@ -21,6 +21,7 @@ import androidx.core.view.isVisible import androidx.fragment.app.activityViewModels import androidx.preference.PreferenceManager import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager import androidx.activity.result.contract.ActivityResultContracts import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog @@ -411,6 +412,7 @@ class SearchFragment : BaseFragment( binding.mainSearch.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String): Boolean { search(query) + searchViewModel.clearSuggestions() binding.mainSearch.let { hideKeyboard(it) @@ -425,11 +427,17 @@ class SearchFragment : BaseFragment( if (showHistory) { searchViewModel.clearSearch() searchViewModel.updateHistory() + searchViewModel.clearSuggestions() + } else { + // Fetch suggestions when user is typing + searchViewModel.fetchSuggestions(newText) } binding.apply { searchHistoryHolder.isVisible = showHistory searchMasterRecycler.isVisible = !showHistory && isAdvancedSearch searchAutofitResults.isVisible = !showHistory && !isAdvancedSearch + // Hide suggestions when showing history or showing search results + searchSuggestionsRecycler.isVisible = !showHistory } return true @@ -579,11 +587,29 @@ class SearchFragment : BaseFragment( } } + val suggestionAdapter = SearchSuggestionAdapter { callback -> + when (callback.clickAction) { + SEARCH_SUGGESTION_CLICK -> { + // Search directly + binding.mainSearch.setQuery(callback.suggestion, true) + searchViewModel.clearSuggestions() + } + SEARCH_SUGGESTION_FILL -> { + // Fill the search box without searching + binding.mainSearch.setQuery(callback.suggestion, false) + } + } + } + binding.apply { searchHistoryRecycler.adapter = historyAdapter searchHistoryRecycler.setLinearListLayout(isHorizontal = false, nextRight = FOCUS_SELF) //searchHistoryRecycler.layoutManager = GridLayoutManager(context, 1) + // Setup suggestions RecyclerView + searchSuggestionsRecycler.adapter = suggestionAdapter + searchSuggestionsRecycler.layoutManager = LinearLayoutManager(context) + searchMasterRecycler.setRecycledViewPool(ParentItemAdapter.sharedPool) searchMasterRecycler.adapter = masterAdapter //searchMasterRecycler.setLinearListLayout(isHorizontal = false, nextRight = FOCUS_SELF) @@ -612,6 +638,12 @@ class SearchFragment : BaseFragment( (binding.searchHistoryRecycler.adapter as? SearchHistoryAdaptor?)?.submitList(list) } + // Observe search suggestions + observe(searchViewModel.searchSuggestions) { suggestions -> + binding.searchSuggestionsRecycler.isVisible = suggestions.isNotEmpty() + (binding.searchSuggestionsRecycler.adapter as? SearchSuggestionAdapter?)?.submitList(suggestions) + } + searchViewModel.updateHistory() } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchSuggestionAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchSuggestionAdapter.kt new file mode 100644 index 00000000000..27bea55202b --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchSuggestionAdapter.kt @@ -0,0 +1,48 @@ +package com.lagradost.cloudstream3.ui.search + +import android.view.LayoutInflater +import android.view.ViewGroup +import com.lagradost.cloudstream3.databinding.SearchSuggestionItemBinding +import com.lagradost.cloudstream3.ui.BaseDiffCallback +import com.lagradost.cloudstream3.ui.NoStateAdapter +import com.lagradost.cloudstream3.ui.ViewHolderState + +const val SEARCH_SUGGESTION_CLICK = 0 +const val SEARCH_SUGGESTION_FILL = 1 + +data class SearchSuggestionCallback( + val suggestion: String, + val clickAction: Int, +) + +class SearchSuggestionAdapter( + private val clickCallback: (SearchSuggestionCallback) -> Unit, +) : NoStateAdapter(diffCallback = BaseDiffCallback(itemSame = { a, b -> a == b })) { + + override fun onCreateContent(parent: ViewGroup): ViewHolderState { + return ViewHolderState( + SearchSuggestionItemBinding.inflate(LayoutInflater.from(parent.context), parent, false), + ) + } + + override fun onBindContent( + holder: ViewHolderState, + item: String, + position: Int + ) { + val binding = holder.view as? SearchSuggestionItemBinding ?: return + binding.apply { + suggestionText.text = item + + // Click on the whole item to search + suggestionItem.setOnClickListener { + clickCallback.invoke(SearchSuggestionCallback(item, SEARCH_SUGGESTION_CLICK)) + } + + // Click on the arrow to fill the search box without searching + suggestionFill.setOnClickListener { + clickCallback.invoke(SearchSuggestionCallback(item, SEARCH_SUGGESTION_FILL)) + } + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchSuggestionApi.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchSuggestionApi.kt new file mode 100644 index 00000000000..53d25e7036f --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchSuggestionApi.kt @@ -0,0 +1,93 @@ +package com.lagradost.cloudstream3.ui.search + +import com.lagradost.cloudstream3.app +import com.lagradost.cloudstream3.mvvm.logError + +/** + * API for fetching search suggestions from external sources. + * Uses Google's suggestion API which provides movie/show related suggestions. + */ +object SearchSuggestionApi { + private const val GOOGLE_SUGGESTION_URL = "https://suggestqueries.google.com/complete/search" + + /** + * Fetches search suggestions from Google's autocomplete API. + * + * @param query The search query to get suggestions for + * @return List of suggestion strings, empty list on failure + */ + suspend fun getSuggestions(query: String): List { + if (query.isBlank() || query.length < 2) return emptyList() + + return try { + val response = app.get( + GOOGLE_SUGGESTION_URL, + params = mapOf( + "client" to "firefox", // Returns JSON format + "q" to query, + "hl" to "en" // Language hint + ), + cacheTime = 60 // Cache for 1 minute + ) + + // Response format: ["query",["suggestion1","suggestion2",...]] + val text = response.text + parseSuggestions(text) + } catch (e: Exception) { + logError(e) + emptyList() + } + } + + /** + * Parses the Google suggestion JSON response. + * Format: ["query",["suggestion1","suggestion2",...]] + */ + private fun parseSuggestions(json: String): List { + return try { + // Simple parsing without full JSON library + // Find the array between the first [ after the query and the last ] + val startIndex = json.indexOf("[", json.indexOf(",")) + val endIndex = json.lastIndexOf("]") + + if (startIndex == -1 || endIndex == -1 || startIndex >= endIndex) { + return emptyList() + } + + val arrayContent = json.substring(startIndex + 1, endIndex) + + // Extract strings from the array + val suggestions = mutableListOf() + var inQuote = false + var currentString = StringBuilder() + var escaped = false + + for (char in arrayContent) { + when { + escaped -> { + currentString.append(char) + escaped = false + } + char == '\\' -> { + escaped = true + } + char == '"' -> { + if (inQuote) { + suggestions.add(currentString.toString()) + currentString = StringBuilder() + } + inQuote = !inQuote + } + inQuote -> { + currentString.append(char) + } + } + } + + suggestions.take(10) // Limit to 10 suggestions + } catch (e: Exception) { + logError(e) + emptyList() + } + } +} diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt index 63fb8c10ed8..f927063e42c 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt @@ -21,6 +21,7 @@ import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.DataStoreHelper.currentAccount import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -43,6 +44,11 @@ class SearchViewModel : ViewModel() { private val _currentHistory: MutableLiveData> = MutableLiveData() val currentHistory: LiveData> get() = _currentHistory + private val _searchSuggestions: MutableLiveData> = MutableLiveData() + val searchSuggestions: LiveData> get() = _searchSuggestions + + private var suggestionJob: Job? = null + private var repos = synchronized(apis) { apis.map { APIRepository(it) } } fun clearSearch() { @@ -83,6 +89,35 @@ class SearchViewModel : ViewModel() { _currentHistory.postValue(items) } + /** + * Fetches search suggestions with debouncing. + * Waits 300ms before making the API call to avoid too many requests. + * + * @param query The search query to get suggestions for + */ + fun fetchSuggestions(query: String) { + suggestionJob?.cancel() + + if (query.isBlank() || query.length < 2) { + _searchSuggestions.postValue(emptyList()) + return + } + + suggestionJob = viewModelScope.launch(Dispatchers.IO) { + delay(300) // Debounce + val suggestions = SearchSuggestionApi.getSuggestions(query) + _searchSuggestions.postValue(suggestions) + } + } + + /** + * Clears the current search suggestions. + */ + fun clearSuggestions() { + suggestionJob?.cancel() + _searchSuggestions.postValue(emptyList()) + } + private val lock: MutableSet = mutableSetOf() // ExpandableHomepageList because the home adapter is reused in the search fragment diff --git a/app/src/main/res/drawable/ic_baseline_north_west_24.xml b/app/src/main/res/drawable/ic_baseline_north_west_24.xml new file mode 100644 index 00000000000..c46eb4b0cc4 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_north_west_24.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index 88dedca5fd9..ac95c523b85 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -113,6 +113,16 @@ layout="@layout/tvtypes_chips_scroll" /> + + + + + + + + + + + + + From cdbf1cf7ab842eca187698dbcc3cdb9ff6c0397f Mon Sep 17 00:00:00 2001 From: NivinCNC Date: Wed, 3 Dec 2025 22:18:04 +0530 Subject: [PATCH 2/9] Refactor search layouts to use FrameLayout overlay Changed root layout from LinearLayout to FrameLayout in both search and search_tv fragments. Moved search suggestions RecyclerView to overlay above content for better UI layering and increased elevation for improved visibility. --- app/src/main/res/layout/fragment_search.xml | 33 ++++++++++------- .../main/res/layout/fragment_search_tv.xml | 35 +++++++++++-------- 2 files changed, 41 insertions(+), 27 deletions(-) diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index ac95c523b85..44c999275c3 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -1,5 +1,5 @@ - + + - - - \ No newline at end of file + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_search_tv.xml b/app/src/main/res/layout/fragment_search_tv.xml index 9b083235bc6..3f72317c119 100644 --- a/app/src/main/res/layout/fragment_search_tv.xml +++ b/app/src/main/res/layout/fragment_search_tv.xml @@ -1,5 +1,5 @@ - + + - - - \ No newline at end of file + + + + + + \ No newline at end of file From 1be2ee985e4dc73f85637cf0ba9267d34db40f2f Mon Sep 17 00:00:00 2001 From: NivinCNC Date: Thu, 4 Dec 2025 17:33:19 +0530 Subject: [PATCH 3/9] Add toggle for search suggestions feature Introduces a new setting to enable or disable search suggestions while typing. Updates UI logic to respect this setting, improves suggestion parsing, and adjusts search suggestion recycler margin for better layout. --- .../cloudstream3/ui/search/SearchFragment.kt | 16 ++++-- .../ui/search/SearchSuggestionApi.kt | 53 ++++--------------- .../cloudstream3/ui/search/SearchViewModel.kt | 2 +- app/src/main/res/layout/fragment_search.xml | 2 +- .../main/res/layout/fragment_search_tv.xml | 2 +- app/src/main/res/values/strings.xml | 2 + app/src/main/res/xml/settings_ui.xml | 6 +++ 7 files changed, 34 insertions(+), 49 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt index 419b26b4b6d..06a899a9319 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt @@ -401,6 +401,7 @@ class SearchFragment : BaseFragment( val settingsManager = context?.let { PreferenceManager.getDefaultSharedPreferences(it) } val isAdvancedSearch = settingsManager?.getBoolean("advanced_search", true) ?: true + val isSearchSuggestionsEnabled = settingsManager?.getBoolean("search_suggestions_enabled", true) ?: true selectedSearchTypes = DataStoreHelper.searchPreferenceTags.toMutableList() @@ -409,6 +410,13 @@ class SearchFragment : BaseFragment( binding.searchFilter.isFocusableInTouchMode = true } + // Hide suggestions when search view loses focus + binding.mainSearch.setOnQueryTextFocusChangeListener { _, hasFocus -> + if (!hasFocus) { + searchViewModel.clearSuggestions() + } + } + binding.mainSearch.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String): Boolean { search(query) @@ -429,15 +437,17 @@ class SearchFragment : BaseFragment( searchViewModel.updateHistory() searchViewModel.clearSuggestions() } else { - // Fetch suggestions when user is typing - searchViewModel.fetchSuggestions(newText) + // Fetch suggestions when user is typing (if enabled) + if (isSearchSuggestionsEnabled) { + searchViewModel.fetchSuggestions(newText) + } } binding.apply { searchHistoryHolder.isVisible = showHistory searchMasterRecycler.isVisible = !showHistory && isAdvancedSearch searchAutofitResults.isVisible = !showHistory && !isAdvancedSearch // Hide suggestions when showing history or showing search results - searchSuggestionsRecycler.isVisible = !showHistory + searchSuggestionsRecycler.isVisible = !showHistory && isSearchSuggestionsEnabled } return true diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchSuggestionApi.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchSuggestionApi.kt index 53d25e7036f..ea2dfd30ba1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchSuggestionApi.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchSuggestionApi.kt @@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.ui.search import com.lagradost.cloudstream3.app import com.lagradost.cloudstream3.mvvm.logError +import com.lagradost.nicehttp.NiceResponse /** * API for fetching search suggestions from external sources. @@ -27,12 +28,11 @@ object SearchSuggestionApi { "q" to query, "hl" to "en" // Language hint ), - cacheTime = 60 // Cache for 1 minute + cacheTime = 60 * 24 // Cache for 1 day (cacheUnit default is Minutes) ) // Response format: ["query",["suggestion1","suggestion2",...]] - val text = response.text - parseSuggestions(text) + parseSuggestions(response) } catch (e: Exception) { logError(e) emptyList() @@ -43,48 +43,15 @@ object SearchSuggestionApi { * Parses the Google suggestion JSON response. * Format: ["query",["suggestion1","suggestion2",...]] */ - private fun parseSuggestions(json: String): List { + private fun parseSuggestions(response: NiceResponse): List { return try { - // Simple parsing without full JSON library - // Find the array between the first [ after the query and the last ] - val startIndex = json.indexOf("[", json.indexOf(",")) - val endIndex = json.lastIndexOf("]") - - if (startIndex == -1 || endIndex == -1 || startIndex >= endIndex) { - return emptyList() - } - - val arrayContent = json.substring(startIndex + 1, endIndex) - - // Extract strings from the array - val suggestions = mutableListOf() - var inQuote = false - var currentString = StringBuilder() - var escaped = false - - for (char in arrayContent) { - when { - escaped -> { - currentString.append(char) - escaped = false - } - char == '\\' -> { - escaped = true - } - char == '"' -> { - if (inQuote) { - suggestions.add(currentString.toString()) - currentString = StringBuilder() - } - inQuote = !inQuote - } - inQuote -> { - currentString.append(char) - } - } + val parsed = response.parsed>() + val suggestions = parsed.getOrNull(1) + when (suggestions) { + is List<*> -> suggestions.filterIsInstance().take(10) + is Array<*> -> suggestions.filterIsInstance().take(10) + else -> emptyList() } - - suggestions.take(10) // Limit to 10 suggestions } catch (e: Exception) { logError(e) emptyList() diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt index f927063e42c..27db8d1ae5e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchViewModel.kt @@ -103,7 +103,7 @@ class SearchViewModel : ViewModel() { return } - suggestionJob = viewModelScope.launch(Dispatchers.IO) { + suggestionJob = ioSafe { delay(300) // Debounce val suggestions = SearchSuggestionApi.getSuggestions(query) _searchSuggestions.postValue(suggestions) diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index 44c999275c3..c610a48a3b4 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -190,7 +190,7 @@ android:id="@+id/search_suggestions_recycler" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="110dp" + android:layout_marginTop="100dp" android:background="?attr/primaryGrayBackground" android:elevation="8dp" android:visibility="gone" diff --git a/app/src/main/res/layout/fragment_search_tv.xml b/app/src/main/res/layout/fragment_search_tv.xml index 3f72317c119..816481c8c98 100644 --- a/app/src/main/res/layout/fragment_search_tv.xml +++ b/app/src/main/res/layout/fragment_search_tv.xml @@ -192,7 +192,7 @@ android:id="@+id/search_suggestions_recycler" android:layout_width="match_parent" android:layout_height="wrap_content" - android:layout_marginTop="110dp" + android:layout_marginTop="100dp" android:layout_marginStart="@dimen/navbar_width" android:background="?attr/primaryGrayBackground" android:elevation="8dp" diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2409c88787a..d5c399cd57a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -272,6 +272,8 @@ Info Advanced Search Gives you the search results separated by provider + Search Suggestions + Show search suggestions while typing Show filler episode for anime Show trailers Show posters from Kitsu diff --git a/app/src/main/res/xml/settings_ui.xml b/app/src/main/res/xml/settings_ui.xml index 08c0fba7296..ba438269b4c 100644 --- a/app/src/main/res/xml/settings_ui.xml +++ b/app/src/main/res/xml/settings_ui.xml @@ -58,6 +58,12 @@ android:title="@string/advanced_search" app:defaultValue="true" app:key="advanced_search" /> + Date: Thu, 4 Dec 2025 18:42:04 +0530 Subject: [PATCH 4/9] Increase search suggestion text size to match search_history_item Updated the text size of search suggestions from 16sp to 18sp for improved readability in the UI. --- app/src/main/res/layout/search_suggestion_item.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/layout/search_suggestion_item.xml b/app/src/main/res/layout/search_suggestion_item.xml index 30688ccc315..d07f7b06db2 100644 --- a/app/src/main/res/layout/search_suggestion_item.xml +++ b/app/src/main/res/layout/search_suggestion_item.xml @@ -27,7 +27,7 @@ android:ellipsize="end" android:maxLines="1" android:textColor="?attr/textColor" - android:textSize="16sp" + android:textSize="18sp" tools:text="Search suggestion" /> Date: Fri, 5 Dec 2025 16:14:39 +0530 Subject: [PATCH 5/9] Add clear suggestions button to search suggestions overlay Introduces a clear suggestions button to the search suggestions overlay, visible on TV layouts. Refactors the suggestions overlay into a FrameLayout to support the button, updates focus handling for TV, and adds a new string resource for the button label. --- .../cloudstream3/ui/search/SearchFragment.kt | 33 ++++++++++++++----- app/src/main/res/layout/fragment_search.xml | 26 ++++++++++++--- .../main/res/layout/fragment_search_tv.xml | 32 +++++++++++++++--- app/src/main/res/values/strings.xml | 1 + 4 files changed, 76 insertions(+), 16 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt index 06a899a9319..db9c7c2ec2f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt @@ -408,15 +408,16 @@ class SearchFragment : BaseFragment( if (isLayout(TV)) { binding.searchFilter.isFocusable = true binding.searchFilter.isFocusableInTouchMode = true - } - - // Hide suggestions when search view loses focus - binding.mainSearch.setOnQueryTextFocusChangeListener { _, hasFocus -> - if (!hasFocus) { - searchViewModel.clearSuggestions() + } else { + // Hide suggestions when search view loses focus + binding.mainSearch.setOnQueryTextFocusChangeListener { _, hasFocus -> + if (!hasFocus) { + searchViewModel.clearSuggestions() + } } } + binding.mainSearch.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String): Boolean { search(query) @@ -447,13 +448,18 @@ class SearchFragment : BaseFragment( searchMasterRecycler.isVisible = !showHistory && isAdvancedSearch searchAutofitResults.isVisible = !showHistory && !isAdvancedSearch // Hide suggestions when showing history or showing search results - searchSuggestionsRecycler.isVisible = !showHistory && isSearchSuggestionsEnabled + searchSuggestionsHolder.isVisible = !showHistory && isSearchSuggestionsEnabled } return true } }) + // Clear suggestions button (for TV) + binding.clearSuggestionsButton.setOnClickListener { + searchViewModel.clearSuggestions() + } + binding.searchClearCallHistory.setOnClickListener { activity?.let { ctx -> val builder: AlertDialog.Builder = AlertDialog.Builder(ctx) @@ -650,8 +656,19 @@ class SearchFragment : BaseFragment( // Observe search suggestions observe(searchViewModel.searchSuggestions) { suggestions -> - binding.searchSuggestionsRecycler.isVisible = suggestions.isNotEmpty() + val hasSuggestions = suggestions.isNotEmpty() + binding.searchSuggestionsHolder.isVisible = hasSuggestions (binding.searchSuggestionsRecycler.adapter as? SearchSuggestionAdapter?)?.submitList(suggestions) + + // On TV, redirect focus from chips to suggestions when visible + if (isLayout(TV or EMULATOR)) { + if (hasSuggestions) { + binding.tvtypesChipsScroll.tvtypesChips.root.nextFocusDownId = R.id.search_suggestions_recycler + } else { + // Reset to default focus target (history) + binding.tvtypesChipsScroll.tvtypesChips.root.nextFocusDownId = R.id.search_history_recycler + } + } } searchViewModel.updateHistory() diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index c610a48a3b4..7d91330eef6 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -186,14 +186,32 @@ - + android:visibility="gone"> + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_search_tv.xml b/app/src/main/res/layout/fragment_search_tv.xml index 816481c8c98..635bef11a11 100644 --- a/app/src/main/res/layout/fragment_search_tv.xml +++ b/app/src/main/res/layout/fragment_search_tv.xml @@ -188,15 +188,39 @@ - + android:visibility="gone"> + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d5c399cd57a..7db3db2dda2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -274,6 +274,7 @@ Gives you the search results separated by provider Search Suggestions Show search suggestions while typing + Clear Suggestions Show filler episode for anime Show trailers Show posters from Kitsu From 994d11b4b63f515c7d5d64c10b86899ec5158e7d Mon Sep 17 00:00:00 2001 From: NivinCNC Date: Fri, 5 Dec 2025 17:16:57 +0530 Subject: [PATCH 6/9] Refactor search UI focus handling for phone layout --- .../cloudstream3/ui/search/SearchFragment.kt | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt index db9c7c2ec2f..74b3c1cbc99 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt @@ -57,6 +57,7 @@ import com.lagradost.cloudstream3.ui.home.ParentItemAdapter import com.lagradost.cloudstream3.ui.result.FOCUS_SELF import com.lagradost.cloudstream3.ui.result.setLinearListLayout import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR +import com.lagradost.cloudstream3.ui.settings.Globals.PHONE import com.lagradost.cloudstream3.ui.settings.Globals.TV import com.lagradost.cloudstream3.ui.settings.Globals.isLandscape import com.lagradost.cloudstream3.ui.settings.Globals.isLayout @@ -405,11 +406,13 @@ class SearchFragment : BaseFragment( selectedSearchTypes = DataStoreHelper.searchPreferenceTags.toMutableList() - if (isLayout(TV)) { + if (!isLayout(PHONE)) { binding.searchFilter.isFocusable = true binding.searchFilter.isFocusableInTouchMode = true - } else { - // Hide suggestions when search view loses focus + } + + // Hide suggestions when search view loses focus (phone only) + if (isLayout(PHONE)) { binding.mainSearch.setOnQueryTextFocusChangeListener { _, hasFocus -> if (!hasFocus) { searchViewModel.clearSuggestions() @@ -660,8 +663,9 @@ class SearchFragment : BaseFragment( binding.searchSuggestionsHolder.isVisible = hasSuggestions (binding.searchSuggestionsRecycler.adapter as? SearchSuggestionAdapter?)?.submitList(suggestions) - // On TV, redirect focus from chips to suggestions when visible - if (isLayout(TV or EMULATOR)) { + // On non-phone layouts, show clear button and redirect focus + if (!isLayout(PHONE)) { + binding.clearSuggestionsButton.isVisible = hasSuggestions if (hasSuggestions) { binding.tvtypesChipsScroll.tvtypesChips.root.nextFocusDownId = R.id.search_suggestions_recycler } else { From 9b7ab72e9e17d2ab993701d851cc6cd83c256255 Mon Sep 17 00:00:00 2001 From: NivinCNC Date: Thu, 11 Dec 2025 23:05:24 +0530 Subject: [PATCH 7/9] Refactor search history and suggestions to use adapter footers Moved 'clear history' and 'clear suggestions' buttons into adapter footers for TV and emulator layouts, removing them from the main fragment layouts. Updated SearchHistoryAdaptor and SearchSuggestionAdapter to support footers, and adjusted SearchFragment logic to handle new clear actions. This improves modularity and focus handling, especially for non-phone devices. --- .../cloudstream3/ui/search/SearchFragment.kt | 96 ++++++++++--------- .../ui/search/SearchHistoryAdaptor.kt | 44 ++++++++- .../ui/search/SearchSuggestionAdapter.kt | 31 +++++- app/src/main/res/layout/fragment_search.xml | 69 +++---------- .../main/res/layout/fragment_search_tv.xml | 79 ++++----------- .../main/res/layout/search_history_footer.xml | 14 +++ .../res/layout/search_suggestion_footer.xml | 14 +++ 7 files changed, 179 insertions(+), 168 deletions(-) create mode 100644 app/src/main/res/layout/search_history_footer.xml create mode 100644 app/src/main/res/layout/search_suggestion_footer.xml diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt index 74b3c1cbc99..046367f6769 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchFragment.kt @@ -73,6 +73,8 @@ import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.DataStoreHelper import com.lagradost.cloudstream3.utils.DataStoreHelper.currentAccount import com.lagradost.cloudstream3.utils.SubtitleHelper +import com.lagradost.cloudstream3.utils.BackPressedCallbackHelper.attachBackPressedCallback +import com.lagradost.cloudstream3.utils.BackPressedCallbackHelper.detachBackPressedCallback import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.fixSystemBarsPadding import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount @@ -139,6 +141,7 @@ class SearchFragment : BaseFragment( override fun onDestroyView() { hideKeyboard() bottomSheetDialog?.ownHide() + activity?.detachBackPressedCallback("SearchFragment") super.onDestroyView() } @@ -447,56 +450,17 @@ class SearchFragment : BaseFragment( } } binding.apply { - searchHistoryHolder.isVisible = showHistory + searchHistoryRecycler.isVisible = showHistory searchMasterRecycler.isVisible = !showHistory && isAdvancedSearch searchAutofitResults.isVisible = !showHistory && !isAdvancedSearch // Hide suggestions when showing history or showing search results - searchSuggestionsHolder.isVisible = !showHistory && isSearchSuggestionsEnabled + searchSuggestionsRecycler.isVisible = !showHistory && isSearchSuggestionsEnabled } return true } }) - // Clear suggestions button (for TV) - binding.clearSuggestionsButton.setOnClickListener { - searchViewModel.clearSuggestions() - } - - binding.searchClearCallHistory.setOnClickListener { - activity?.let { ctx -> - val builder: AlertDialog.Builder = AlertDialog.Builder(ctx) - val dialogClickListener = - DialogInterface.OnClickListener { _, which -> - when (which) { - DialogInterface.BUTTON_POSITIVE -> { - removeKeys("$currentAccount/$SEARCH_HISTORY_KEY") - searchViewModel.updateHistory() - } - - DialogInterface.BUTTON_NEGATIVE -> { - } - } - } - - try { - builder.setTitle(R.string.clear_history).setMessage( - ctx.getString(R.string.delete_message).format( - ctx.getString(R.string.history) - ) - ) - .setPositiveButton(R.string.sort_clear, dialogClickListener) - .setNegativeButton(R.string.cancel, dialogClickListener) - .show().setDefaultFocus() - } catch (e: Exception) { - logError(e) - // ye you somehow fucked up formatting did you? - } - } - - - } - observe(searchViewModel.searchResponse) { when (it) { is Resource.Success -> { @@ -586,6 +550,7 @@ class SearchFragment : BaseFragment( val searchItem = click.item when (click.clickAction) { SEARCH_HISTORY_OPEN -> { + if (searchItem == null) return@SearchHistoryAdaptor searchViewModel.clearSearch() if (searchItem.type.isNotEmpty()) updateChips( @@ -596,9 +561,42 @@ class SearchFragment : BaseFragment( } SEARCH_HISTORY_REMOVE -> { + if (searchItem == null) return@SearchHistoryAdaptor removeKey("$currentAccount/$SEARCH_HISTORY_KEY", searchItem.key) searchViewModel.updateHistory() } + + SEARCH_HISTORY_CLEAR -> { + // Show confirmation dialog (from footer button) + activity?.let { ctx -> + val builder: AlertDialog.Builder = AlertDialog.Builder(ctx) + val dialogClickListener = + DialogInterface.OnClickListener { _, which -> + when (which) { + DialogInterface.BUTTON_POSITIVE -> { + removeKeys("$currentAccount/$SEARCH_HISTORY_KEY") + searchViewModel.updateHistory() + } + + DialogInterface.BUTTON_NEGATIVE -> { + } + } + } + + try { + builder.setTitle(R.string.clear_history).setMessage( + ctx.getString(R.string.delete_message).format( + ctx.getString(R.string.history) + ) + ) + .setPositiveButton(R.string.sort_clear, dialogClickListener) + .setNegativeButton(R.string.cancel, dialogClickListener) + .show().setDefaultFocus() + } catch (e: Exception) { + logError(e) + } + } + } else -> { // wth are you doing??? @@ -617,6 +615,10 @@ class SearchFragment : BaseFragment( // Fill the search box without searching binding.mainSearch.setQuery(callback.suggestion, false) } + SEARCH_SUGGESTION_CLEAR -> { + // Clear suggestions (from footer button) + searchViewModel.clearSuggestions() + } } } @@ -653,24 +655,28 @@ class SearchFragment : BaseFragment( } observe(searchViewModel.currentHistory) { list -> - binding.searchClearCallHistory.isVisible = list.isNotEmpty() (binding.searchHistoryRecycler.adapter as? SearchHistoryAdaptor?)?.submitList(list) } // Observe search suggestions observe(searchViewModel.searchSuggestions) { suggestions -> val hasSuggestions = suggestions.isNotEmpty() - binding.searchSuggestionsHolder.isVisible = hasSuggestions + binding.searchSuggestionsRecycler.isVisible = hasSuggestions (binding.searchSuggestionsRecycler.adapter as? SearchSuggestionAdapter?)?.submitList(suggestions) - // On non-phone layouts, show clear button and redirect focus + // On non-phone layouts, redirect focus and handle back button if (!isLayout(PHONE)) { - binding.clearSuggestionsButton.isVisible = hasSuggestions if (hasSuggestions) { binding.tvtypesChipsScroll.tvtypesChips.root.nextFocusDownId = R.id.search_suggestions_recycler + // Attach back button callback to clear suggestions + activity?.attachBackPressedCallback("SearchFragment") { + searchViewModel.clearSuggestions() + } } else { // Reset to default focus target (history) binding.tvtypesChipsScroll.tvtypesChips.root.nextFocusDownId = R.id.search_history_recycler + // Detach back button callback when no suggestions + activity?.detachBackPressedCallback("SearchFragment") } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHistoryAdaptor.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHistoryAdaptor.kt index 2a95c76b255..4a4274cf6f4 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHistoryAdaptor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHistoryAdaptor.kt @@ -4,10 +4,14 @@ import android.view.LayoutInflater import android.view.ViewGroup import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.TvType +import com.lagradost.cloudstream3.databinding.SearchHistoryFooterBinding import com.lagradost.cloudstream3.databinding.SearchHistoryItemBinding +import com.lagradost.cloudstream3.ui.BaseAdapter import com.lagradost.cloudstream3.ui.BaseDiffCallback -import com.lagradost.cloudstream3.ui.NoStateAdapter import com.lagradost.cloudstream3.ui.ViewHolderState +import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR +import com.lagradost.cloudstream3.ui.settings.Globals.TV +import com.lagradost.cloudstream3.ui.settings.Globals.isLayout data class SearchHistoryItem( @JsonProperty("searchedAt") val searchedAt: Long, @@ -17,18 +21,31 @@ data class SearchHistoryItem( ) data class SearchHistoryCallback( - val item: SearchHistoryItem, + val item: SearchHistoryItem?, val clickAction: Int, ) const val SEARCH_HISTORY_OPEN = 0 const val SEARCH_HISTORY_REMOVE = 1 +const val SEARCH_HISTORY_CLEAR = 2 class SearchHistoryAdaptor( private val clickCallback: (SearchHistoryCallback) -> Unit, -) : NoStateAdapter(diffCallback = BaseDiffCallback(itemSame = { a,b -> +) : BaseAdapter(diffCallback = BaseDiffCallback(itemSame = { a,b -> a.searchedAt == b.searchedAt && a.searchText == b.searchText })) { + + // Add footer for TV and EMULATOR layouts only + override val footers = if (isLayout(TV or EMULATOR)) 1 else 0 + + override fun submitList(list: Collection?, commitCallback: Runnable?) { + super.submitList(list, commitCallback) + // Notify footer to rebind when list changes to update visibility + if (footers > 0) { + notifyItemChanged(itemCount - 1) + } + } + override fun onCreateContent(parent: ViewGroup): ViewHolderState { return ViewHolderState( SearchHistoryItemBinding.inflate(LayoutInflater.from(parent.context), parent, false), @@ -52,4 +69,25 @@ class SearchHistoryAdaptor( } } } + + override fun onCreateFooter(parent: ViewGroup): ViewHolderState { + return ViewHolderState( + SearchHistoryFooterBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + } + + override fun onBindFooter(holder: ViewHolderState) { + val binding = holder.view as? SearchHistoryFooterBinding ?: return + // Hide footer when list is empty + binding.searchClearCallHistory.apply { + visibility = if (immutableCurrentList.isEmpty()) android.view.View.GONE else android.view.View.VISIBLE + if (isLayout(TV or EMULATOR)) { + isFocusable = true + isFocusableInTouchMode = true + } + setOnClickListener { + clickCallback.invoke(SearchHistoryCallback(null, SEARCH_HISTORY_CLEAR)) + } + } + } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchSuggestionAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchSuggestionAdapter.kt index 27bea55202b..e50062e17f1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchSuggestionAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchSuggestionAdapter.kt @@ -2,13 +2,18 @@ package com.lagradost.cloudstream3.ui.search import android.view.LayoutInflater import android.view.ViewGroup +import com.lagradost.cloudstream3.databinding.SearchSuggestionFooterBinding import com.lagradost.cloudstream3.databinding.SearchSuggestionItemBinding +import com.lagradost.cloudstream3.ui.BaseAdapter import com.lagradost.cloudstream3.ui.BaseDiffCallback -import com.lagradost.cloudstream3.ui.NoStateAdapter import com.lagradost.cloudstream3.ui.ViewHolderState +import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR +import com.lagradost.cloudstream3.ui.settings.Globals.TV +import com.lagradost.cloudstream3.ui.settings.Globals.isLayout const val SEARCH_SUGGESTION_CLICK = 0 const val SEARCH_SUGGESTION_FILL = 1 +const val SEARCH_SUGGESTION_CLEAR = 2 data class SearchSuggestionCallback( val suggestion: String, @@ -17,7 +22,10 @@ data class SearchSuggestionCallback( class SearchSuggestionAdapter( private val clickCallback: (SearchSuggestionCallback) -> Unit, -) : NoStateAdapter(diffCallback = BaseDiffCallback(itemSame = { a, b -> a == b })) { +) : BaseAdapter(diffCallback = BaseDiffCallback(itemSame = { a, b -> a == b })) { + + // Add footer for TV and EMULATOR layouts only + override val footers = if (isLayout(TV or EMULATOR)) 1 else 0 override fun onCreateContent(parent: ViewGroup): ViewHolderState { return ViewHolderState( @@ -45,4 +53,23 @@ class SearchSuggestionAdapter( } } } + + override fun onCreateFooter(parent: ViewGroup): ViewHolderState { + return ViewHolderState( + SearchSuggestionFooterBinding.inflate(LayoutInflater.from(parent.context), parent, false) + ) + } + + override fun onBindFooter(holder: ViewHolderState) { + val binding = holder.view as? SearchSuggestionFooterBinding ?: return + binding.clearSuggestionsButton.apply { + if (isLayout(TV or EMULATOR)) { + isFocusable = true + isFocusableInTouchMode = true + } + setOnClickListener { + clickCallback.invoke(SearchSuggestionCallback("", SEARCH_SUGGESTION_CLEAR)) + } + } + } } diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index 7d91330eef6..408460d41d3 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -145,73 +145,30 @@ android:nextFocusLeft="@id/nav_rail_view" android:nextFocusUp="@id/tvtypes_chips" - android:nextFocusDown="@id/search_clear_call_history" android:visibility="gone" tools:listitem="@layout/homepage_parent" /> - - - - - - - + android:layout_height="match_parent" + android:background="?attr/primaryBlackBackground" + android:descendantFocusability="afterDescendants" + android:nextFocusLeft="@id/nav_rail_view" + android:nextFocusUp="@id/tvtypes_chips" + android:visibility="visible" + tools:listitem="@layout/search_history_item" /> - - - - - - - + android:visibility="gone" + tools:listitem="@layout/search_suggestion_item" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_search_tv.xml b/app/src/main/res/layout/fragment_search_tv.xml index 635bef11a11..ed2f3b63991 100644 --- a/app/src/main/res/layout/fragment_search_tv.xml +++ b/app/src/main/res/layout/fragment_search_tv.xml @@ -146,81 +146,36 @@ android:descendantFocusability="afterDescendants" android:nextFocusLeft="@id/navigation_search" android:nextFocusUp="@id/tvtypes_chips" - android:nextFocusDown="@id/search_clear_call_history" android:visibility="gone" tools:listitem="@layout/homepage_parent" /> - - - - - - + android:layout_marginStart="@dimen/navbar_width" + android:background="?attr/primaryBlackBackground" + android:descendantFocusability="afterDescendants" + android:nextFocusLeft="@id/navigation_search" + android:nextFocusUp="@id/tvtypes_chips" + android:tag = "@string/tv_no_focus_tag" + android:visibility="visible" + tools:listitem="@layout/search_history_item" /> - - - - - - + android:descendantFocusability="afterDescendants" + android:nextFocusLeft="@id/navigation_search" + android:nextFocusUp="@id/tvtypes_chips" + android:visibility="gone" + tools:listitem="@layout/search_suggestion_item" /> \ No newline at end of file diff --git a/app/src/main/res/layout/search_history_footer.xml b/app/src/main/res/layout/search_history_footer.xml new file mode 100644 index 00000000000..d8f0d933b9e --- /dev/null +++ b/app/src/main/res/layout/search_history_footer.xml @@ -0,0 +1,14 @@ + + diff --git a/app/src/main/res/layout/search_suggestion_footer.xml b/app/src/main/res/layout/search_suggestion_footer.xml new file mode 100644 index 00000000000..929fd3b04c0 --- /dev/null +++ b/app/src/main/res/layout/search_suggestion_footer.xml @@ -0,0 +1,14 @@ + + From 2c797d5727c6b99381e6c919ccd20beb8c238288 Mon Sep 17 00:00:00 2001 From: NivinCNC Date: Fri, 12 Dec 2025 15:17:15 +0530 Subject: [PATCH 8/9] Show search footers on all layouts and fix visibility Changed SearchHistoryAdaptor and SearchSuggestionAdapter to always display the footer regardless of layout, instead of only on TV or EMULATOR. Updated footer visibility logic to use isGone when the list is empty and ensured footers rebind on list changes to update their state. --- .../ui/search/SearchHistoryAdaptor.kt | 10 +++++----- .../ui/search/SearchSuggestionAdapter.kt | 17 +++++++++++++---- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHistoryAdaptor.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHistoryAdaptor.kt index 4a4274cf6f4..d8a70a48ab6 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHistoryAdaptor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHistoryAdaptor.kt @@ -6,8 +6,8 @@ import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.databinding.SearchHistoryFooterBinding import com.lagradost.cloudstream3.databinding.SearchHistoryItemBinding -import com.lagradost.cloudstream3.ui.BaseAdapter import com.lagradost.cloudstream3.ui.BaseDiffCallback +import com.lagradost.cloudstream3.ui.NoStateAdapter import com.lagradost.cloudstream3.ui.ViewHolderState import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR import com.lagradost.cloudstream3.ui.settings.Globals.TV @@ -31,12 +31,12 @@ const val SEARCH_HISTORY_CLEAR = 2 class SearchHistoryAdaptor( private val clickCallback: (SearchHistoryCallback) -> Unit, -) : BaseAdapter(diffCallback = BaseDiffCallback(itemSame = { a,b -> +) : NoStateAdapter(diffCallback = BaseDiffCallback(itemSame = { a,b -> a.searchedAt == b.searchedAt && a.searchText == b.searchText })) { - // Add footer for TV and EMULATOR layouts only - override val footers = if (isLayout(TV or EMULATOR)) 1 else 0 + // Add footer for all layouts + override val footers = 1 override fun submitList(list: Collection?, commitCallback: Runnable?) { super.submitList(list, commitCallback) @@ -80,7 +80,7 @@ class SearchHistoryAdaptor( val binding = holder.view as? SearchHistoryFooterBinding ?: return // Hide footer when list is empty binding.searchClearCallHistory.apply { - visibility = if (immutableCurrentList.isEmpty()) android.view.View.GONE else android.view.View.VISIBLE + isGone = immutableCurrentList.isEmpty() if (isLayout(TV or EMULATOR)) { isFocusable = true isFocusableInTouchMode = true diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchSuggestionAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchSuggestionAdapter.kt index e50062e17f1..a224bc29d10 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchSuggestionAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchSuggestionAdapter.kt @@ -4,8 +4,8 @@ import android.view.LayoutInflater import android.view.ViewGroup import com.lagradost.cloudstream3.databinding.SearchSuggestionFooterBinding import com.lagradost.cloudstream3.databinding.SearchSuggestionItemBinding -import com.lagradost.cloudstream3.ui.BaseAdapter import com.lagradost.cloudstream3.ui.BaseDiffCallback +import com.lagradost.cloudstream3.ui.NoStateAdapter import com.lagradost.cloudstream3.ui.ViewHolderState import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR import com.lagradost.cloudstream3.ui.settings.Globals.TV @@ -22,10 +22,18 @@ data class SearchSuggestionCallback( class SearchSuggestionAdapter( private val clickCallback: (SearchSuggestionCallback) -> Unit, -) : BaseAdapter(diffCallback = BaseDiffCallback(itemSame = { a, b -> a == b })) { +) : NoStateAdapter(diffCallback = BaseDiffCallback(itemSame = { a, b -> a == b })) { - // Add footer for TV and EMULATOR layouts only - override val footers = if (isLayout(TV or EMULATOR)) 1 else 0 + // Add footer for all layouts + override val footers = 1 + + override fun submitList(list: Collection?, commitCallback: Runnable?) { + super.submitList(list, commitCallback) + // Notify footer to rebind when list changes to update visibility + if (footers > 0) { + notifyItemChanged(itemCount - 1) + } + } override fun onCreateContent(parent: ViewGroup): ViewHolderState { return ViewHolderState( @@ -63,6 +71,7 @@ class SearchSuggestionAdapter( override fun onBindFooter(holder: ViewHolderState) { val binding = holder.view as? SearchSuggestionFooterBinding ?: return binding.clearSuggestionsButton.apply { + isGone = immutableCurrentList.isEmpty() if (isLayout(TV or EMULATOR)) { isFocusable = true isFocusableInTouchMode = true From caea56da869861caeb97760d59a99d52f6610dc0 Mon Sep 17 00:00:00 2001 From: NivinCNC Date: Fri, 12 Dec 2025 15:23:39 +0530 Subject: [PATCH 9/9] Import isGone extension in search adapters Added import for androidx.core.view.isGone in both SearchHistoryAdaptor and SearchSuggestionAdapter. This prepares the adapters to use the isGone extension for view visibility handling. --- .../com/lagradost/cloudstream3/ui/search/SearchHistoryAdaptor.kt | 1 + .../lagradost/cloudstream3/ui/search/SearchSuggestionAdapter.kt | 1 + 2 files changed, 2 insertions(+) diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHistoryAdaptor.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHistoryAdaptor.kt index d8a70a48ab6..4868abb3d08 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHistoryAdaptor.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHistoryAdaptor.kt @@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.ui.search import android.view.LayoutInflater import android.view.ViewGroup +import androidx.core.view.isGone import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.databinding.SearchHistoryFooterBinding diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchSuggestionAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchSuggestionAdapter.kt index a224bc29d10..74d5e7b08a3 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchSuggestionAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchSuggestionAdapter.kt @@ -2,6 +2,7 @@ package com.lagradost.cloudstream3.ui.search import android.view.LayoutInflater import android.view.ViewGroup +import androidx.core.view.isGone import com.lagradost.cloudstream3.databinding.SearchSuggestionFooterBinding import com.lagradost.cloudstream3.databinding.SearchSuggestionItemBinding import com.lagradost.cloudstream3.ui.BaseDiffCallback