package com.player.musicoo.fragment import android.annotation.SuppressLint import android.content.Context import android.os.Handler import android.os.Looper import android.text.Editable import android.text.TextWatcher import android.view.KeyEvent import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager import android.widget.TextView import android.widget.TextView.OnEditorActionListener import androidx.recyclerview.widget.LinearLayoutManager import com.google.android.flexbox.AlignItems import com.google.android.flexbox.FlexWrap import com.google.android.flexbox.FlexboxLayoutManager import com.google.android.flexbox.JustifyContent import com.gyf.immersionbar.ktx.immersionBar import com.player.musicoo.adapter.SearchHistoryAdapter import com.player.musicoo.adapter.SearchSuggestionsAdapter import com.player.musicoo.databinding.FragmentSearchBinding import com.player.musicoo.innertube.Innertube import com.player.musicoo.innertube.models.bodies.SearchSuggestionsBody import com.player.musicoo.innertube.requests.moSearchPage import com.player.musicoo.innertube.requests.searchSuggestions import com.player.musicoo.util.LogTag import com.player.musicoo.view.SearchResultOptimalView import com.player.musicoo.view.SearchResultOtherView import kotlinx.coroutines.channels.Channel import kotlinx.coroutines.isActive import kotlinx.coroutines.selects.select class SearchFragment : MoBaseFragment(), TextWatcher, View.OnFocusChangeListener, SearchSuggestionsAdapter.OnItemClickListener, OnEditorActionListener, SearchHistoryAdapter.HistoryOnItemClickListener { interface SearchFragmentCancelClickListener { fun onFragmentClick() } fun setButtonClickListener(listener: SearchFragmentCancelClickListener) { this.buttonClickListener = listener } private var buttonClickListener: SearchFragmentCancelClickListener? = null private val requests: Channel = Channel(Channel.UNLIMITED) sealed class Request { data object SearchSuggestions : Request() data class SearchData(val input: String) : Request() } private var searchSuggestionsAdapterAdapter: SearchSuggestionsAdapter? = null private var searchSuggestionsList: MutableList = mutableListOf() private var searchHistorySet: MutableSet = mutableSetOf() private var searchHistory: MutableList = mutableListOf() private var searchHistoryAdapter: SearchHistoryAdapter? = null override val bindingInflater: (LayoutInflater, ViewGroup?, Boolean) -> FragmentSearchBinding get() = FragmentSearchBinding::inflate override suspend fun onViewCreated() { initView() initSearchSuggestionsAdapter() initSearchHistoryAdapter() initImmersionBar() onReceive() } private fun initView() { binding.cancelBtn.setOnClickListener { buttonClickListener?.onFragmentClick() } binding.searchEdit.let { it.setOnEditorActionListener(this) it.addTextChangedListener(this) it.onFocusChangeListener = this it.requestFocus() } binding.deleteInputBtn.setOnClickListener { binding.searchEdit.text.clear() } binding.deleteHistoryBtn.setOnClickListener { appStore.searchHistoryStore = emptySet() updateHistoryUi() } } private fun initSearchSuggestionsAdapter() { searchSuggestionsAdapterAdapter = SearchSuggestionsAdapter(requireActivity(), searchSuggestionsList) searchSuggestionsAdapterAdapter?.setOnItemClickListener(this) binding.searchSuggestionsRv.layoutManager = LinearLayoutManager( requireActivity(), LinearLayoutManager.VERTICAL, false ) binding.searchSuggestionsRv.adapter = searchSuggestionsAdapterAdapter } private fun initSearchHistoryAdapter() { searchHistoryAdapter = SearchHistoryAdapter(requireActivity(), searchHistory) searchHistoryAdapter?.setOnItemClickListener(this) val layoutManager = FlexboxLayoutManager(requireActivity()) layoutManager.flexWrap = FlexWrap.WRAP // 设置换行方式为自动换行 layoutManager.alignItems = AlignItems.STRETCH // 设置项目在副轴上的对齐方式为拉伸 layoutManager.justifyContent = JustifyContent.FLEX_START // 设置主轴上的对齐方式为起始端对齐 binding.historyRv.layoutManager = layoutManager binding.historyRv.adapter = searchHistoryAdapter updateHistoryUi() } @SuppressLint("NotifyDataSetChanged") private fun updateHistoryUi() { searchHistory.clear() searchHistory.addAll(appStore.searchHistoryStore) searchHistoryAdapter?.notifyDataSetChanged() if (appStore.searchHistoryStore.isNotEmpty()) { binding.historyLayout.visibility = View.VISIBLE } else { binding.historyLayout.visibility = View.GONE } } private fun initImmersionBar() { immersionBar { statusBarDarkFont(false) statusBarView(binding.view) } } @SuppressLint("NotifyDataSetChanged", "SetTextI18n") private suspend fun onReceive() { while (isActive) { select { requests.onReceive { when (it) { is Request.SearchSuggestions -> { val input = binding.searchEdit.text.toString().trim() Innertube.searchSuggestions(SearchSuggestionsBody(input = input)) ?.onSuccess { suggestionsList -> LogTag.LogD(TAG, "suggestionsList->${suggestionsList?.size}") if (suggestionsList != null) { showSearchSuggestions() searchSuggestionsList.clear() searchSuggestionsList.addAll(suggestionsList) searchSuggestionsAdapterAdapter?.notifyDataSetChanged() } else { binding.searchSuggestionsLayout.visibility = View.GONE } }?.onFailure { error -> LogTag.LogD(TAG, "searchSuggestions onFailure->${error}") binding.searchSuggestionsLayout.visibility = View.GONE } } is Request.SearchData -> { binding.contentLayout.removeAllViews() binding.searchEdit.clearFocus() showLoadingLayout() val input = it.input if (input.isNotEmpty()) { searchHistorySet.clear() searchHistorySet.addAll(appStore.searchHistoryStore) if (!appStore.searchHistoryStore.contains(input)) {//把不存在的记录保存到集合中 searchHistorySet.add(input) } appStore.searchHistoryStore = searchHistorySet updateHistoryUi() Innertube.moSearchPage(input)?.onSuccess { result -> showResultContent() for (dataPage: Innertube.SearchDataPage in result) { if (dataPage.type == 1) {//type为1的是最佳结果。 binding.contentLayout.addView( SearchResultOptimalView(requireActivity(), dataPage) ) } else if (dataPage.type == 2) {//type为2的是其他搜索结果。 if (dataPage.searchResultList.isNotEmpty()) {//如何数据集合为空就不添加view binding.contentLayout.addView( SearchResultOtherView( requireActivity(), dataPage ) ) } } } }?.onFailure { showNoContentLayout() } } } } } } } } override fun onHiddenChanged(hidden: Boolean) { super.onHiddenChanged(hidden) if (!hidden) { binding.searchEdit.requestFocus() } else { binding.searchEdit.clearFocus() binding.searchEdit.text.clear() binding.contentLayout.removeAllViews() } } override fun onHistoryItemClick(position: Int) { binding.searchEdit.setText(searchHistory[position]) requests.trySend(Request.SearchData(searchHistory[position])) } override fun onItemClick(position: Int) { binding.searchEdit.setText(searchSuggestionsList[position]) requests.trySend(Request.SearchData(searchSuggestionsList[position])) } override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean { if (actionId == EditorInfo.IME_ACTION_SEARCH) { requests.trySend(Request.SearchData(binding.searchEdit.text.toString().trim())) return true } return false } override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} @SuppressLint("NotifyDataSetChanged") override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) { if (s.isNullOrEmpty()) { binding.deleteInputBtn.visibility = View.GONE showSearchHistory() updateHistoryUi() binding.searchEdit.requestFocus() } else { binding.deleteInputBtn.visibility = View.VISIBLE } } override fun afterTextChanged(s: Editable?) { requests.trySend(Request.SearchSuggestions) } override fun onFocusChange(v: View?, hasFocus: Boolean) { if (hasFocus) { Handler(Looper.getMainLooper()).postDelayed({ showImm() }, 200) } else { Handler(Looper.getMainLooper()).postDelayed({ hideImm() }, 200) } } private fun showImm() { val imm = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.showSoftInput(binding.searchEdit, InputMethodManager.SHOW_IMPLICIT) } private fun hideImm() { val imm = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager imm.hideSoftInputFromWindow(binding.searchEdit.windowToken, 0) } private fun showResultContent() { binding.contentScrollView.visibility = View.VISIBLE binding.searchSuggestionsLayout.visibility = View.GONE binding.historyLayout.visibility = View.GONE binding.loadingLayout.visibility = View.GONE binding.noContentLayout.visibility = View.GONE } private fun showSearchSuggestions() { binding.contentScrollView.visibility = View.GONE binding.searchSuggestionsLayout.visibility = View.VISIBLE binding.historyLayout.visibility = View.GONE binding.loadingLayout.visibility = View.GONE binding.noContentLayout.visibility = View.GONE } private fun showSearchHistory() { binding.contentScrollView.visibility = View.GONE binding.searchSuggestionsLayout.visibility = View.GONE binding.historyLayout.visibility = View.VISIBLE binding.loadingLayout.visibility = View.GONE binding.noContentLayout.visibility = View.GONE } private fun showLoadingLayout() { binding.loadingLayout.visibility = View.VISIBLE binding.noContentLayout.visibility = View.GONE binding.contentScrollView.visibility = View.GONE binding.searchSuggestionsLayout.visibility = View.GONE binding.historyLayout.visibility = View.GONE } private fun showNoContentLayout() { binding.loadingLayout.visibility = View.GONE binding.noContentLayout.visibility = View.VISIBLE binding.contentScrollView.visibility = View.GONE binding.searchSuggestionsLayout.visibility = View.GONE binding.historyLayout.visibility = View.GONE } }