From 279605e6ae98548aeba1fe004dc8ae02fc3e6098 Mon Sep 17 00:00:00 2001 From: LagradOst <11805592+LagradOst@users.noreply.github.com> Date: Tue, 29 Mar 2022 20:34:00 +0200 Subject: [PATCH] added primitive search history & added appCategory = video fixed #874 and #878 --- app/src/main/AndroidManifest.xml | 3 +- .../cloudstream3/ui/search/SearchFragment.kt | 58 ++++++++-- .../ui/search/SearchHistoryAdaptor.kt | 103 ++++++++++++++++++ .../cloudstream3/ui/search/SearchViewModel.kt | 47 ++++++-- app/src/main/res/layout/fragment_search.xml | 14 +++ .../main/res/layout/search_history_item.xml | 37 +++++++ 6 files changed, 246 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHistoryAdaptor.kt create mode 100644 app/src/main/res/layout/search_history_item.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6396f687..07937af4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -27,7 +27,8 @@ android:supportsRtl="true" android:theme="@style/AppTheme" android:fullBackupContent="@xml/backup_descriptor" - tools:targetApi="m"> + android:appCategory="video" + tools:targetApi="o"> 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 0b0eb782..5ba23e11 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 @@ -23,6 +23,7 @@ import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia import com.lagradost.cloudstream3.APIHolder.getApiFromName import com.lagradost.cloudstream3.APIHolder.getApiSettings +import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.observe @@ -164,7 +165,8 @@ class SearchFragment : Fragment() { val cancelBtt = dialog.findViewById(R.id.cancel_btt) val applyBtt = dialog.findViewById(R.id.apply_btt) - val pairList = HomeFragment.getPairList(anime, cartoons, tvs, docs, movies,asian) + val pairList = + HomeFragment.getPairList(anime, cartoons, tvs, docs, movies, asian) cancelBtt?.setOnClickListener { dialog.dismissSafe() @@ -277,10 +279,21 @@ class SearchFragment : Fragment() { search_select_asian, ) + val settingsManager = context?.let { PreferenceManager.getDefaultSharedPreferences(it) } + val isAdvancedSearch = settingsManager?.getBoolean("advanced_search", true) ?: true + selectedSearchTypes = context?.getKey>(SEARCH_PREF_TAGS) ?.mapNotNull { listName -> TvType.values().firstOrNull { it.name == listName } } ?.toMutableList() ?: mutableListOf(TvType.Movie, TvType.TvSeries) + + fun updateSelectedList(list: MutableList) { + selectedSearchTypes = list + for ((button, validTypes) in pairList) { + button?.isSelected = selectedSearchTypes.any { validTypes.contains(it) } + } + } + context?.filterProviderByPreferredMedia()?.let { validAPIs -> for ((button, validTypes) in pairList) { val isValid = @@ -339,10 +352,25 @@ class SearchFragment : Fragment() { override fun onQueryTextChange(newText: String): Boolean { //searchViewModel.quickSearch(newText) + val showHistory = newText.isBlank() + searchViewModel.clearSearch() + searchViewModel.updateHistory() + + search_history_recycler?.isVisible = showHistory + + search_master_recycler?.isVisible = !showHistory && isAdvancedSearch + search_autofit_results?.isVisible = !showHistory && !isAdvancedSearch + return true } }) + observe(searchViewModel.currentHistory) { list -> + (search_history_recycler.adapter as? SearchHistoryAdaptor?)?.updateList(list) + } + + searchViewModel.updateHistory() + observe(searchViewModel.searchResponse) { when (it) { is Resource.Success -> { @@ -406,15 +434,31 @@ class SearchFragment : Fragment() { activity?.loadHomepageList(item) }) + val historyAdapter = SearchHistoryAdaptor(mutableListOf()) { click -> + val searchItem = click.item + when (click.clickAction) { + SEARCH_HISTORY_OPEN -> { + searchViewModel.clearSearch() + if (searchItem.type.isNotEmpty()) + updateSelectedList(searchItem.type.toMutableList()) + main_search?.setQuery(searchItem.searchText, true) + } + SEARCH_HISTORY_REMOVE -> { + removeKey(SEARCH_HISTORY_KEY, searchItem.key) + searchViewModel.updateHistory() + } + else -> { + // wth are you doing??? + } + } + } + + search_history_recycler?.adapter = historyAdapter + search_history_recycler?.layoutManager = GridLayoutManager(context, 1) + search_master_recycler?.adapter = masterAdapter search_master_recycler?.layoutManager = GridLayoutManager(context, 1) - val settingsManager = context?.let { PreferenceManager.getDefaultSharedPreferences(it) } - val isAdvancedSearch = settingsManager?.getBoolean("advanced_search", true) ?: true - - search_master_recycler?.isVisible = isAdvancedSearch - search_autofit_results?.isVisible = !isAdvancedSearch - // SubtitlesFragment.push(activity) //searchViewModel.search("iron man") //(activity as AppCompatActivity).loadResult("https://shiro.is/overlord-dubbed", "overlord-dubbed", "Shiro") 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 new file mode 100644 index 00000000..8132301b --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/search/SearchHistoryAdaptor.kt @@ -0,0 +1,103 @@ +package com.lagradost.cloudstream3.ui.search + +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.RecyclerView +import com.fasterxml.jackson.annotation.JsonProperty +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.TvType +import kotlinx.android.synthetic.main.search_history_item.view.* + +data class SearchHistoryItem( + @JsonProperty("searchedAt") val searchedAt: Long, + @JsonProperty("searchText") val searchText: String, + @JsonProperty("type") val type: List, + @JsonProperty("key") val key: String, +) + +data class SearchHistoryCallback( + val item: SearchHistoryItem, + val clickAction: Int, +) + +const val SEARCH_HISTORY_OPEN = 0 +const val SEARCH_HISTORY_REMOVE = 1 + +class SearchHistoryAdaptor( + private val cardList: MutableList, + private val clickCallback: (SearchHistoryCallback) -> Unit, +) : RecyclerView.Adapter() { + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return CardViewHolder( + LayoutInflater.from(parent.context) + .inflate(R.layout.search_history_item, parent, false), + clickCallback, + ) + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is CardViewHolder -> { + holder.bind(cardList[position]) + } + } + } + + override fun getItemCount(): Int { + return cardList.size + } + + fun updateList(newList: List) { + val diffResult = DiffUtil.calculateDiff( + SearchHistoryDiffCallback(this.cardList, newList) + ) + + cardList.clear() + cardList.addAll(newList) + + diffResult.dispatchUpdatesTo(this) + } + + class CardViewHolder + constructor( + itemView: View, + private val clickCallback: (SearchHistoryCallback) -> Unit, + ) : + RecyclerView.ViewHolder(itemView) { + private val removeButton: ImageView = itemView.home_history_remove + private val openButton: View = itemView.home_history_tab + private val title: TextView = itemView.home_history_title + + fun bind(card: SearchHistoryItem) { + title.text = card.searchText + + removeButton.setOnClickListener { + clickCallback.invoke(SearchHistoryCallback(card, SEARCH_HISTORY_REMOVE)) + } + openButton.setOnClickListener { + clickCallback.invoke(SearchHistoryCallback(card, SEARCH_HISTORY_OPEN)) + } + } + } +} + +class SearchHistoryDiffCallback( + private val oldList: List, + private val newList: List +) : + DiffUtil.Callback() { + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = + oldList[oldItemPosition].searchText == newList[newItemPosition].searchText + + override fun getOldListSize() = oldList.size + + override fun getNewListSize() = newList.size + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = + oldList[oldItemPosition] == newList[newItemPosition] +} \ No newline at end of file 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 81dab9f9..551f0919 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 @@ -6,11 +6,15 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.apis +import com.lagradost.cloudstream3.AcraApplication.Companion.getKey +import com.lagradost.cloudstream3.AcraApplication.Companion.getKeys +import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.safeApiCall import com.lagradost.cloudstream3.syncproviders.OAuth2API.Companion.SyncApis import com.lagradost.cloudstream3.syncproviders.SyncAPI import com.lagradost.cloudstream3.ui.APIRepository +import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job import kotlinx.coroutines.launch @@ -21,22 +25,28 @@ data class OnGoingSearch( val data: Resource> ) -class SearchViewModel : ViewModel() { - private val _searchResponse: MutableLiveData>> = - MutableLiveData() - val searchResponse: LiveData>> get() = _searchResponse +const val SEARCH_HISTORY_KEY = "search_history" - private val _currentSearch: MutableLiveData> = MutableLiveData() - val currentSearch: LiveData> get() = _currentSearch +class SearchViewModel : ViewModel() { + private val _searchResponse: MutableLiveData>> = + MutableLiveData() + val searchResponse: LiveData>> get() = _searchResponse + + private val _currentSearch: MutableLiveData> = MutableLiveData() + val currentSearch: LiveData> get() = _currentSearch + + private val _currentHistory: MutableLiveData> = MutableLiveData() + val currentHistory: LiveData> get() = _currentHistory private val repos = apis.map { APIRepository(it) } private val syncApis = SyncApis - private fun clearSearch() { + fun clearSearch() { _searchResponse.postValue(Resource.Success(ArrayList())) + _currentSearch.postValue(emptyList()) } - var onGoingSearch: Job? = null + private var onGoingSearch: Job? = null fun searchAndCancel( query: String, isMainApis: Boolean = true, @@ -68,6 +78,15 @@ class SearchViewModel : ViewModel() { ) } + fun updateHistory() = viewModelScope.launch { + ioSafe { + val items = getKeys(SEARCH_HISTORY_KEY)?.mapNotNull { + getKey(it) + }?.sortedByDescending { it.searchedAt } ?: emptyList() + _currentHistory.postValue(items) + } + } + private fun search( query: String, isMainApis: Boolean = true, @@ -80,6 +99,18 @@ class SearchViewModel : ViewModel() { return@launch } + val key = query.hashCode().toString() + setKey( + SEARCH_HISTORY_KEY, + key, + SearchHistoryItem( + searchedAt = System.currentTimeMillis(), + searchText = query, + type = emptyList(), // TODO implement tv type + key = key, + ) + ) + _searchResponse.postValue(Resource.Loading()) val currentList = ArrayList() diff --git a/app/src/main/res/layout/fragment_search.xml b/app/src/main/res/layout/fragment_search.xml index 48b4e70a..c7d41b7f 100644 --- a/app/src/main/res/layout/fragment_search.xml +++ b/app/src/main/res/layout/fragment_search.xml @@ -81,6 +81,7 @@ app:tint="?attr/textColor" android:contentDescription="@string/change_providers_img_des" /> + + + \ No newline at end of file diff --git a/app/src/main/res/layout/search_history_item.xml b/app/src/main/res/layout/search_history_item.xml new file mode 100644 index 00000000..4daf4c8c --- /dev/null +++ b/app/src/main/res/layout/search_history_item.xml @@ -0,0 +1,37 @@ + + + + + + +