package com.lagradost.cloudstream3.ui.search import android.content.res.Configuration import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.WindowManager import android.widget.AbsListView import android.widget.ArrayAdapter import android.widget.ImageView import android.widget.ListView import androidx.appcompat.widget.SearchView import androidx.core.view.isVisible import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import androidx.preference.PreferenceManager import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetDialog import com.google.android.material.button.MaterialButton import com.lagradost.cloudstream3.* import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia import com.lagradost.cloudstream3.APIHolder.filterSearchResultByFilmQuality import com.lagradost.cloudstream3.APIHolder.getApiFromNameNull import com.lagradost.cloudstream3.APIHolder.getApiProviderLangSettings import com.lagradost.cloudstream3.APIHolder.getApiSettings import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey import com.lagradost.cloudstream3.AcraApplication.Companion.setKey import com.lagradost.cloudstream3.MainActivity.Companion.afterPluginsLoadedEvent import com.lagradost.cloudstream3.mvvm.Resource import com.lagradost.cloudstream3.mvvm.logError import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.ui.APIRepository import com.lagradost.cloudstream3.ui.home.HomeFragment import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.bindChips import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.currentSpan import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.loadHomepageList import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.updateChips import com.lagradost.cloudstream3.ui.home.ParentItemAdapter import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.SubtitleHelper import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import kotlinx.android.synthetic.main.fragment_search.* import kotlinx.android.synthetic.main.tvtypes_chips.* import java.util.concurrent.locks.ReentrantLock const val SEARCH_PREF_TAGS = "search_pref_tags" const val SEARCH_PREF_PROVIDERS = "search_pref_providers" class SearchFragment : Fragment() { companion object { fun List.filterSearchResponse(): List { return this.filter { response -> if (response is AnimeSearchResponse) { val status = response.dubStatus (status.isNullOrEmpty()) || (status.any { APIRepository.dubStatusActive.contains(it) }) } else { true } } } } private val searchViewModel: SearchViewModel by activityViewModels() override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?, ): View? { activity?.window?.setSoftInputMode( WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE ) return inflater.inflate(R.layout.fragment_search, container, false) } private fun fixGrid() { activity?.getSpanCount()?.let { currentSpan = it } search_autofit_results.spanCount = currentSpan currentSpan = currentSpan HomeFragment.configEvent.invoke(currentSpan) } override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) fixGrid() } override fun onDestroyView() { hideKeyboard() super.onDestroyView() } override fun onResume() { super.onResume() afterPluginsLoadedEvent += ::reloadRepos } override fun onStop() { super.onStop() afterPluginsLoadedEvent -= ::reloadRepos } var selectedSearchTypes = mutableListOf() var selectedApis = mutableSetOf() fun search(query: String?) { if (query == null) return context?.getApiSettings()?.let { settings -> searchViewModel.searchAndCancel( query = query, providersActive = selectedApis.filter { name -> settings.contains(name) && getApiFromNameNull(name)?.supportedTypes?.any { selectedSearchTypes.contains( it ) } == true }.toSet() ) } } // Null if defined as a variable // This needs to be run after view created private fun reloadRepos(success: Boolean = false) = main { searchViewModel.reloadRepos() context?.filterProviderByPreferredMedia()?.let { validAPIs -> bindChips( home_select_group, selectedSearchTypes, validAPIs.flatMap { api -> api.supportedTypes }.distinct() ) { list -> if (selectedSearchTypes.toSet() != list.toSet()) { setKey(SEARCH_PREF_TAGS, selectedSearchTypes) selectedSearchTypes.clear() selectedSearchTypes.addAll(list) search(main_search?.query?.toString()) } } } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) context?.fixPaddingStatusbar(searchRoot) fixGrid() reloadRepos() val adapter: RecyclerView.Adapter? = activity?.let { SearchAdapter( ArrayList(), search_autofit_results, ) { callback -> SearchHelper.handleSearchClickCallback(activity, callback) } } search_autofit_results.adapter = adapter search_loading_bar.alpha = 0f val searchExitIcon = main_search.findViewById(androidx.appcompat.R.id.search_close_btn) // val searchMagIcon = // main_search.findViewById(androidx.appcompat.R.id.search_mag_icon) //searchMagIcon.scaleX = 0.65f //searchMagIcon.scaleY = 0.65f context?.let { ctx -> val validAPIs = ctx.filterProviderByPreferredMedia() selectedApis = ctx.getKey( SEARCH_PREF_PROVIDERS, defVal = validAPIs.map { it.name } )!!.toMutableSet() } search_filter.setOnClickListener { searchView -> searchView?.context?.let { ctx -> val validAPIs = ctx.filterProviderByPreferredMedia(hasHomePageIsRequired = false) var currentValidApis = listOf() val currentSelectedApis = if (selectedApis.isEmpty()) validAPIs.map { it.name } .toMutableSet() else selectedApis val builder = BottomSheetDialog(ctx) builder.behavior.state = BottomSheetBehavior.STATE_EXPANDED builder.setContentView(R.layout.home_select_mainpage) builder.show() builder.let { dialog -> val isMultiLang = ctx.getApiProviderLangSettings().size > 1 val cancelBtt = dialog.findViewById(R.id.cancel_btt) val applyBtt = dialog.findViewById(R.id.apply_btt) val listView = dialog.findViewById(R.id.listview1) val arrayAdapter = ArrayAdapter(ctx, R.layout.sort_bottom_single_choice) listView?.adapter = arrayAdapter listView?.choiceMode = AbsListView.CHOICE_MODE_MULTIPLE listView?.setOnItemClickListener { _, _, i, _ -> if (currentValidApis.isNotEmpty()) { val api = currentValidApis[i].name if (currentSelectedApis.contains(api)) { listView.setItemChecked(i, false) currentSelectedApis -= api } else { listView.setItemChecked(i, true) currentSelectedApis += api } } } fun updateList(types: List) { setKey(SEARCH_PREF_TAGS, types.map {it.name}) arrayAdapter.clear() currentValidApis = validAPIs.filter { api -> api.supportedTypes.any { types.contains(it) } }.sortedBy { it.name.lowercase() } val names = currentValidApis.map { if (isMultiLang) "${ SubtitleHelper.getFlagFromIso( it.lang )?.plus(" ") ?: "" }${it.name}" else it.name } for ((index, api) in currentValidApis.map { it.name }.withIndex()) { listView?.setItemChecked(index, currentSelectedApis.contains(api)) } //arrayAdapter.notifyDataSetChanged() arrayAdapter.addAll(names) arrayAdapter.notifyDataSetChanged() } val selectedSearchTypes = getKey>(SEARCH_PREF_TAGS) ?.mapNotNull { listName -> TvType.values().firstOrNull { it.name == listName } } ?.toMutableList() ?: mutableListOf(TvType.Movie, TvType.TvSeries) bindChips( dialog.home_select_group, selectedSearchTypes, TvType.values().toList() ) { list -> updateList(list) } cancelBtt?.setOnClickListener { dialog.dismissSafe() } cancelBtt?.setOnClickListener { dialog.dismissSafe() } applyBtt?.setOnClickListener { //if (currentApiName != selectedApiName) { // currentApiName?.let(callback) //} dialog.dismissSafe() } dialog.setOnDismissListener { context?.setKey(SEARCH_PREF_PROVIDERS, currentSelectedApis.toList()) selectedApis = currentSelectedApis } updateList(selectedSearchTypes.toList()) } } } 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) if (isTrueTvSettings()) { search_filter.isFocusable = true search_filter.isFocusableInTouchMode = true } main_search.setOnQueryTextListener(object : SearchView.OnQueryTextListener { override fun onQueryTextSubmit(query: String): Boolean { search(query) main_search?.let { hideKeyboard(it) } return true } override fun onQueryTextChange(newText: String): Boolean { //searchViewModel.quickSearch(newText) val showHistory = newText.isBlank() if (showHistory) { 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 -> { it.value.let { data -> if (data.isNotEmpty()) { (search_autofit_results?.adapter as SearchAdapter?)?.updateList(data) } } searchExitIcon.alpha = 1f search_loading_bar.alpha = 0f } is Resource.Failure -> { // Toast.makeText(activity, "Server error", Toast.LENGTH_LONG).show() searchExitIcon.alpha = 1f search_loading_bar.alpha = 0f } is Resource.Loading -> { searchExitIcon.alpha = 0f search_loading_bar.alpha = 1f } } } val listLock = ReentrantLock() observe(searchViewModel.currentSearch) { list -> try { // https://stackoverflow.com/questions/6866238/concurrent-modification-exception-adding-to-an-arraylist listLock.lock() (search_master_recycler?.adapter as ParentItemAdapter?)?.apply { val newItems = list.map { ongoing -> val dataList = if (ongoing.data is Resource.Success) ongoing.data.value else ArrayList() val dataListFiltered = context?.filterSearchResultByFilmQuality(dataList) ?: dataList val ongoingList = HomePageList( ongoing.apiName, dataListFiltered ) ongoingList } updateList(newItems) //notifyDataSetChanged() } } catch (e: Exception) { logError(e) } finally { listLock.unlock() } } /*main_search.setOnQueryTextFocusChangeListener { _, b -> if (b) { // https://stackoverflow.com/questions/12022715/unable-to-show-keyboard-automatically-in-the-searchview showInputMethod(view.findFocus()) } }*/ //main_search.onActionViewExpanded()*/ val masterAdapter: RecyclerView.Adapter = ParentItemAdapter(mutableListOf(), { callback -> SearchHelper.handleSearchClickCallback(activity, callback) }, { item -> activity?.loadHomepageList(item) }) val historyAdapter = SearchHistoryAdaptor(mutableListOf()) { click -> val searchItem = click.item when (click.clickAction) { SEARCH_HISTORY_OPEN -> { searchViewModel.clearSearch() if (searchItem.type.isNotEmpty()) updateChips(home_select_group, 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) // SubtitlesFragment.push(activity) //searchViewModel.search("iron man") //(activity as AppCompatActivity).loadResult("https://shiro.is/overlord-dubbed", "overlord-dubbed", "Shiro") /* (activity as AppCompatActivity?)?.supportFragmentManager.beginTransaction() .setCustomAnimations(R.anim.enter_anim, R.anim.exit_anim, R.anim.pop_enter, R.anim.pop_exit) .add(R.id.homeRoot, PlayerFragment.newInstance(PlayerData(0, null,0))) .commit()*/ } }