forked from recloudstream/cloudstream
		
	added primitive search history & added appCategory = video
fixed #874 and #878
This commit is contained in:
		
							parent
							
								
									71da2233d9
								
							
						
					
					
						commit
						279605e6ae
					
				
					 6 changed files with 246 additions and 16 deletions
				
			
		|  | @ -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"> | ||||
|         <meta-data | ||||
|                 android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME" | ||||
|                 android:value="com.lagradost.cloudstream3.utils.CastOptionsProvider"/> | ||||
|  |  | |||
|  | @ -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<MaterialButton>(R.id.cancel_btt) | ||||
|                     val applyBtt = dialog.findViewById<MaterialButton>(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<List<String>>(SEARCH_PREF_TAGS) | ||||
|             ?.mapNotNull { listName -> TvType.values().firstOrNull { it.name == listName } } | ||||
|             ?.toMutableList() | ||||
|             ?: mutableListOf(TvType.Movie, TvType.TvSeries) | ||||
| 
 | ||||
|         fun updateSelectedList(list: MutableList<TvType>) { | ||||
|             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") | ||||
|  |  | |||
|  | @ -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<TvType>, | ||||
|     @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<SearchHistoryItem>, | ||||
|     private val clickCallback: (SearchHistoryCallback) -> Unit, | ||||
| ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() { | ||||
| 
 | ||||
|     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<SearchHistoryItem>) { | ||||
|         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<SearchHistoryItem>, | ||||
|     private val newList: List<SearchHistoryItem> | ||||
| ) : | ||||
|     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] | ||||
| } | ||||
|  | @ -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<List<SearchResponse>> | ||||
| ) | ||||
| 
 | ||||
| class SearchViewModel : ViewModel() { | ||||
|     private val _searchResponse: MutableLiveData<Resource<ArrayList<SearchResponse>>> = | ||||
|         MutableLiveData() | ||||
|     val searchResponse: LiveData<Resource<ArrayList<SearchResponse>>> get() = _searchResponse | ||||
| const val SEARCH_HISTORY_KEY = "search_history" | ||||
| 
 | ||||
|     private val _currentSearch: MutableLiveData<ArrayList<OnGoingSearch>> = MutableLiveData() | ||||
|     val currentSearch: LiveData<ArrayList<OnGoingSearch>> get() = _currentSearch | ||||
| class SearchViewModel : ViewModel() { | ||||
|     private val _searchResponse: MutableLiveData<Resource<List<SearchResponse>>> = | ||||
|         MutableLiveData() | ||||
|     val searchResponse: LiveData<Resource<List<SearchResponse>>> get() = _searchResponse | ||||
| 
 | ||||
|     private val _currentSearch: MutableLiveData<List<OnGoingSearch>> = MutableLiveData() | ||||
|     val currentSearch: LiveData<List<OnGoingSearch>> get() = _currentSearch | ||||
| 
 | ||||
|     private val _currentHistory: MutableLiveData<List<SearchHistoryItem>> = MutableLiveData() | ||||
|     val currentHistory: LiveData<List<SearchHistoryItem>> 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<SearchHistoryItem>(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<OnGoingSearch>() | ||||
|  |  | |||
|  | @ -81,6 +81,7 @@ | |||
|                 app:tint="?attr/textColor" | ||||
|                 android:contentDescription="@string/change_providers_img_des" /> | ||||
|     </FrameLayout> | ||||
| 
 | ||||
|     <HorizontalScrollView | ||||
|             android:paddingStart="10dp" | ||||
|             android:paddingEnd="10dp" | ||||
|  | @ -144,6 +145,7 @@ | |||
| 
 | ||||
| 
 | ||||
|     <com.lagradost.cloudstream3.ui.AutofitRecyclerView | ||||
|             android:visibility="gone" | ||||
|             android:nextFocusLeft="@id/nav_rail_view" | ||||
|             android:descendantFocusability="afterDescendants" | ||||
| 
 | ||||
|  | @ -160,6 +162,7 @@ | |||
|             android:orientation="vertical" /> | ||||
| 
 | ||||
|     <androidx.recyclerview.widget.RecyclerView | ||||
|             android:visibility="gone" | ||||
|             android:nextFocusLeft="@id/nav_rail_view" | ||||
|             android:descendantFocusability="afterDescendants" | ||||
| 
 | ||||
|  | @ -168,4 +171,15 @@ | |||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="match_parent" | ||||
|             tools:listitem="@layout/homepage_parent" /> | ||||
| 
 | ||||
|     <androidx.recyclerview.widget.RecyclerView | ||||
|             android:visibility="visible" | ||||
|             android:nextFocusLeft="@id/nav_rail_view" | ||||
|             android:descendantFocusability="afterDescendants" | ||||
| 
 | ||||
|             android:background="?attr/primaryBlackBackground" | ||||
|             android:id="@+id/search_history_recycler" | ||||
|             android:layout_width="match_parent" | ||||
|             android:layout_height="match_parent" | ||||
|             tools:listitem="@layout/search_history_item" /> | ||||
| </LinearLayout> | ||||
							
								
								
									
										37
									
								
								app/src/main/res/layout/search_history_item.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								app/src/main/res/layout/search_history_item.xml
									
										
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,37 @@ | |||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|         xmlns:app="http://schemas.android.com/apk/res-auto" | ||||
|         xmlns:tools="http://schemas.android.com/tools" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="wrap_content" | ||||
|         android:orientation="horizontal" | ||||
|         android:id="@+id/home_history_tab" | ||||
|         android:nextFocusRight="@id/home_history_remove"> | ||||
| 
 | ||||
|     <TextView | ||||
|             android:id="@+id/home_history_title" | ||||
|             android:textColor="?attr/textColor" | ||||
|             android:textSize="18sp" | ||||
|             tools:text="Hello World" | ||||
|             android:padding="10dp" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_height="wrap_content" | ||||
|             app:layout_constraintStart_toStartOf="parent" | ||||
|             app:layout_constraintTop_toTopOf="parent" | ||||
|             app:layout_constraintBottom_toBottomOf="parent" /> | ||||
| 
 | ||||
|     <ImageView | ||||
|             android:padding="10dp" | ||||
|             android:id="@+id/home_history_remove" | ||||
|             android:nextFocusLeft="@id/home_history_tab" | ||||
| 
 | ||||
|             android:src="@drawable/ic_baseline_close_24" | ||||
|             android:layout_width="wrap_content" | ||||
|             android:layout_gravity="center_vertical|end" | ||||
|             android:layout_height="match_parent" | ||||
|             app:tint="?attr/white" | ||||
|             tools:ignore="ContentDescription" | ||||
|             app:layout_constraintBottom_toBottomOf="parent" | ||||
|             app:layout_constraintTop_toTopOf="parent" | ||||
|             app:layout_constraintEnd_toEndOf="parent" /> | ||||
| </androidx.constraintlayout.widget.ConstraintLayout> | ||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue