319 lines
13 KiB
Kotlin
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
|
|
}
|
|
|
|
} |