Musicoo/app/src/main/java/com/player/musicoo/fragment/SearchFragment.kt
2024-05-13 18:34:51 +08:00

319 lines
13 KiB
Kotlin

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<FragmentSearchBinding>(), 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<Request> = 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<String> = mutableListOf()
private var searchHistorySet: MutableSet<String> = mutableSetOf()
private var searchHistory: MutableList<String> = 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<Unit> {
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
}
}