|
|
@@ -11,90 +11,52 @@ import androidx.constraintlayout.widget.ConstraintLayout
|
|
|
import androidx.core.view.WindowInsetsCompat
|
|
|
import androidx.core.view.updateLayoutParams
|
|
|
import androidx.core.view.updatePadding
|
|
|
-import androidx.lifecycle.MutableLiveData
|
|
|
-import androidx.recyclerview.widget.LinearLayoutManager
|
|
|
-import androidx.recyclerview.widget.RecyclerView
|
|
|
-import com.adealink.frame.base.Rlt
|
|
|
+import androidx.fragment.app.Fragment
|
|
|
+import com.adealink.frame.aab.util.getCompatColor
|
|
|
import com.adealink.frame.base.fastLazy
|
|
|
import com.adealink.frame.mvvm.view.viewBinding
|
|
|
import com.adealink.frame.router.Router
|
|
|
import com.adealink.frame.router.annotation.RouterUri
|
|
|
-import com.adealink.frame.statistics.BaseStatEvent
|
|
|
import com.adealink.frame.util.onClick
|
|
|
import com.adealink.weparty.commonui.BaseActivity
|
|
|
import com.adealink.weparty.commonui.ext.dp
|
|
|
import com.adealink.weparty.commonui.ext.fitSystemWindows
|
|
|
import com.adealink.weparty.commonui.ext.gone
|
|
|
-import com.adealink.weparty.commonui.ext.isSuccess
|
|
|
+import com.adealink.weparty.commonui.ext.hide
|
|
|
import com.adealink.weparty.commonui.ext.onWindowInsets
|
|
|
import com.adealink.weparty.commonui.ext.show
|
|
|
+import com.adealink.weparty.commonui.recycleview.adapter.BaseActivityTabFragmentStateAdapter
|
|
|
import com.adealink.weparty.commonui.recycleview.adapter.MultiTypeListAdapter
|
|
|
-import com.adealink.weparty.commonui.recycleview.itemdecoration.VerticalSpaceItemDecoration
|
|
|
import com.adealink.weparty.commonui.recycleview.layoutmanager.FlowLayoutManager
|
|
|
-import com.adealink.weparty.commonui.toast.util.showFailedToast
|
|
|
-import com.adealink.weparty.module.playmate.event.PlaymateListEventReporter
|
|
|
-import com.adealink.weparty.module.playmate.event.PlaymateStatEvent
|
|
|
+import com.adealink.weparty.commonui.widget.EmptyFragment
|
|
|
import com.adealink.weparty.module.profile.Profile
|
|
|
-import com.adealink.weparty.network.data.NoMoreDataError
|
|
|
+import com.adealink.weparty.profile.R
|
|
|
import com.adealink.weparty.profile.databinding.ActivityUserSearchBinding
|
|
|
+import com.adealink.weparty.profile.databinding.LayoutSearchTabBinding
|
|
|
import com.adealink.weparty.profile.datasource.local.SearchLocalService
|
|
|
-import com.adealink.weparty.profile.relation.viewmodel.FollowViewModel
|
|
|
import com.adealink.weparty.profile.search.adapter.SearchHistoryViewBinder
|
|
|
-import com.adealink.weparty.profile.search.adapter.SearchItemViewBinder
|
|
|
import com.adealink.weparty.profile.search.data.SearchHistoryItemData
|
|
|
-import com.adealink.weparty.profile.search.data.SearchResultItemData
|
|
|
import com.adealink.weparty.profile.search.viewmodel.SearchViewModel
|
|
|
import com.adealink.weparty.util.applyImmersive
|
|
|
+import com.google.android.material.tabs.TabLayout
|
|
|
+import com.google.android.material.tabs.TabLayoutMediator
|
|
|
import com.qmuiteam.qmui.widget.util.QMUIKeyboardHelper
|
|
|
import com.qmuiteam.qmui.widget.util.QMUIStatusBarHelper
|
|
|
-import com.scwang.smart.refresh.layout.api.RefreshLayout
|
|
|
-import com.scwang.smart.refresh.layout.listener.OnRefreshLoadMoreListener
|
|
|
-import com.adealink.weparty.R as APP_R
|
|
|
|
|
|
@RouterUri(
|
|
|
path = [Profile.Search.PATH],
|
|
|
desc = "搜索"
|
|
|
)
|
|
|
class SearchActivity : BaseActivity(),
|
|
|
- SearchItemViewBinder.OnResultClickListener,
|
|
|
SearchHistoryViewBinder.OnHistoryClickListener {
|
|
|
|
|
|
private val binding by viewBinding(ActivityUserSearchBinding::inflate)
|
|
|
|
|
|
private val historyAdapter by fastLazy { MultiTypeListAdapter<SearchHistoryItemData>() }
|
|
|
private var historyList = mutableListOf<String>()
|
|
|
- private val resultAdapter by fastLazy { MultiTypeListAdapter<SearchResultItemData>() }
|
|
|
|
|
|
private val searchViewModel by viewModels<SearchViewModel>()
|
|
|
- private val followViewModel by viewModels<FollowViewModel>()
|
|
|
- private var isSearching = MutableLiveData<Boolean>()
|
|
|
- private var isInputEmpty = MutableLiveData<Boolean>()
|
|
|
-
|
|
|
- private var exposureReport = object : PlaymateListEventReporter() {
|
|
|
- override fun getReportEvent(
|
|
|
- viewHolder: RecyclerView.ViewHolder,
|
|
|
- position: Int
|
|
|
- ): BaseStatEvent? {
|
|
|
- val item = resultAdapter.getItem(position) ?: return null
|
|
|
- return PlaymateStatEvent(PlaymateStatEvent.Action.EXPOSURE).apply {
|
|
|
- id to item.data.uid
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- override fun reportEvent(events: List<BaseStatEvent>) {
|
|
|
- val reportIds = mutableListOf<String>()
|
|
|
- for (event in events) {
|
|
|
- val playmateEvent = (event as? PlaymateStatEvent) ?: continue
|
|
|
- val idValue = (playmateEvent.id.value as? String) ?: continue
|
|
|
- reportIds.add(idValue)
|
|
|
- }
|
|
|
- if (reportIds.isNotEmpty()) {
|
|
|
- PlaymateStatEvent(PlaymateStatEvent.Action.EXPOSURE).apply {
|
|
|
- ids to reportIds
|
|
|
- }.send()
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
+ private lateinit var pageAdapter: PageAdapter
|
|
|
|
|
|
override fun onBeforeCreate() {
|
|
|
super.onBeforeCreate()
|
|
|
@@ -106,60 +68,37 @@ class SearchActivity : BaseActivity(),
|
|
|
QMUIStatusBarHelper.setStatusBarLightMode(this)
|
|
|
setContentView(binding.root)
|
|
|
applyImmersive { top ->
|
|
|
- binding.clTop.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
|
|
+ binding.clTop.root.updateLayoutParams<ConstraintLayout.LayoutParams> {
|
|
|
topMargin = top
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- binding.ivBack.onClick {
|
|
|
+ binding.clTop.ivBack.onClick {
|
|
|
finish()
|
|
|
}
|
|
|
- binding.ivClearInput.onClick {
|
|
|
- binding.etSearchInput.setText(null)
|
|
|
+ binding.clTop.ivClearInput.onClick {
|
|
|
+ binding.clTop.etSearchInput.setText(null)
|
|
|
}
|
|
|
|
|
|
historyAdapter.register(SearchHistoryViewBinder(this))
|
|
|
- binding.rvHistory.layoutManager = FlowLayoutManager(
|
|
|
+ binding.clHistory.rvHistory.layoutManager = FlowLayoutManager(
|
|
|
FlowLayoutManager.VERTICAL,
|
|
|
8.dp(),
|
|
|
10.dp()
|
|
|
)
|
|
|
- binding.rvHistory.adapter = historyAdapter
|
|
|
-
|
|
|
- binding.vRefresh.setEnableRefresh(true)
|
|
|
- binding.vRefresh.setEnableLoadMore(false)
|
|
|
- binding.vRefresh.setEnableAutoLoadMore(true)
|
|
|
- binding.vRefresh.setOnRefreshLoadMoreListener(object : OnRefreshLoadMoreListener {
|
|
|
- override fun onRefresh(refreshLayout: RefreshLayout) {
|
|
|
- searchViewModel.reSearch()
|
|
|
- }
|
|
|
+ binding.clHistory.rvHistory.adapter = historyAdapter
|
|
|
|
|
|
-
|
|
|
- override fun onLoadMore(refreshLayout: RefreshLayout) {
|
|
|
- searchViewModel.searchMore()
|
|
|
- }
|
|
|
- })
|
|
|
-
|
|
|
- resultAdapter.register(SearchItemViewBinder(this))
|
|
|
- binding.rvResult.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
|
|
|
- binding.rvResult.adapter = resultAdapter
|
|
|
- binding.rvResult.addItemDecoration(
|
|
|
- VerticalSpaceItemDecoration(20.dp())
|
|
|
- )
|
|
|
- exposureReport.register(binding.rvResult)
|
|
|
-
|
|
|
- binding.etSearchInput.isClickable = true
|
|
|
- binding.etSearchInput.setOnEditorActionListener(object : OnEditorActionListener {
|
|
|
+ binding.clTop.etSearchInput.isClickable = true
|
|
|
+ binding.clTop.etSearchInput.setOnEditorActionListener(object : OnEditorActionListener {
|
|
|
override fun onEditorAction(v: TextView?, actionId: Int, event: KeyEvent?): Boolean {
|
|
|
if (actionId == EditorInfo.IME_ACTION_SEARCH
|
|
|
|| (actionId == EditorInfo.IME_ACTION_UNSPECIFIED && event != null && event.keyCode == KeyEvent.KEYCODE_ENTER && event.action == KeyEvent.ACTION_DOWN)
|
|
|
) {
|
|
|
- search(binding.etSearchInput.text?.toString(), false)
|
|
|
+ search(binding.clTop.etSearchInput.text?.toString(), false)
|
|
|
}
|
|
|
return true
|
|
|
}
|
|
|
})
|
|
|
- binding.etSearchInput.addTextChangedListener(object : TextWatcher {
|
|
|
+ binding.clTop.etSearchInput.addTextChangedListener(object : TextWatcher {
|
|
|
override fun beforeTextChanged(
|
|
|
s: CharSequence?,
|
|
|
start: Int,
|
|
|
@@ -178,16 +117,18 @@ class SearchActivity : BaseActivity(),
|
|
|
|
|
|
override fun afterTextChanged(s: Editable?) {
|
|
|
val input = s?.trim()?.toString()
|
|
|
- isInputEmpty.postValue(input.isNullOrEmpty())
|
|
|
+ searchViewModel.setInput(input)
|
|
|
}
|
|
|
})
|
|
|
- binding.tvSearch.onClick {
|
|
|
- search(binding.etSearchInput.text?.toString(), false)
|
|
|
+ binding.clTop.tvSearch.onClick {
|
|
|
+ search(binding.clTop.etSearchInput.text?.toString(), false)
|
|
|
}
|
|
|
|
|
|
fitWindow()
|
|
|
- QMUIKeyboardHelper.showKeyboard(binding.etSearchInput, 200)
|
|
|
- binding.etSearchInput.requestFocus()
|
|
|
+ QMUIKeyboardHelper.showKeyboard(binding.clTop.etSearchInput, 200)
|
|
|
+ binding.clTop.etSearchInput.requestFocus()
|
|
|
+
|
|
|
+ initSearchTab()
|
|
|
}
|
|
|
|
|
|
private fun fitWindow() {
|
|
|
@@ -205,14 +146,71 @@ class SearchActivity : BaseActivity(),
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ private fun initSearchTab() {
|
|
|
+ pageAdapter = PageAdapter()
|
|
|
+ binding.clResult.vpContent.offscreenPageLimit = 1
|
|
|
+ binding.clResult.vpContent.adapter = pageAdapter
|
|
|
+ binding.clResult.vpContent.isSaveEnabled = false
|
|
|
+ TabLayoutMediator(
|
|
|
+ binding.clResult.tlTab,
|
|
|
+ binding.clResult.vpContent,
|
|
|
+ true,
|
|
|
+ true
|
|
|
+ ) { tabLayout, position ->
|
|
|
+ tabLayout.setCustomView(R.layout.layout_search_tab)
|
|
|
+ tabLayout.customView?.let { tabView ->
|
|
|
+ val tabViewBinding = LayoutSearchTabBinding.bind(tabView)
|
|
|
+ onConfigureTab(tabViewBinding, position)
|
|
|
+ updateTabView(
|
|
|
+ tabLayout,
|
|
|
+ 0 == position
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }.attach()
|
|
|
+ binding.clResult.tlTab.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
|
|
|
+
|
|
|
+ override fun onTabSelected(tab: TabLayout.Tab?) {
|
|
|
+ updateTabView(tab, true)
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onTabUnselected(tab: TabLayout.Tab?) {
|
|
|
+ updateTabView(tab, false)
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun onTabReselected(tab: TabLayout.Tab?) {
|
|
|
+ }
|
|
|
+
|
|
|
+ })
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun onConfigureTab(binding: LayoutSearchTabBinding, position: Int) {
|
|
|
+ binding.tvTab.text = pageAdapter.getTabName(position)
|
|
|
+ }
|
|
|
+
|
|
|
+ private fun updateTabView(
|
|
|
+ tabView: TabLayout.Tab?,
|
|
|
+ isSelected: Boolean
|
|
|
+ ) {
|
|
|
+ tabView?.customView?.let { tabView ->
|
|
|
+ val tabViewBinding = LayoutSearchTabBinding.bind(tabView)
|
|
|
+ if (isSelected) {
|
|
|
+ tabViewBinding.vSelected.show()
|
|
|
+ tabViewBinding.tvTab.setTextColor(getCompatColor(com.adealink.weparty.R.color.color_FF1D2129))
|
|
|
+ } else {
|
|
|
+ tabViewBinding.vSelected.hide()
|
|
|
+ tabViewBinding.tvTab.setTextColor(getCompatColor(com.adealink.weparty.R.color.color_FFC9CDD4))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
override fun loadData() {
|
|
|
super.loadData()
|
|
|
updateHistoryList(SearchLocalService.getSearchHistory()) { list ->
|
|
|
if (list.isEmpty()) {
|
|
|
- binding.clHistory.gone()
|
|
|
+ binding.clHistory.root.gone()
|
|
|
return@updateHistoryList
|
|
|
}
|
|
|
- binding.clHistory.show()
|
|
|
+ binding.clHistory.root.show()
|
|
|
historyAdapter.submitList(list.map { SearchHistoryItemData(it) })
|
|
|
}
|
|
|
}
|
|
|
@@ -222,13 +220,13 @@ class SearchActivity : BaseActivity(),
|
|
|
if (keyword.isNullOrEmpty()) {
|
|
|
return
|
|
|
}
|
|
|
- binding.etSearchInput.clearFocus()
|
|
|
- QMUIKeyboardHelper.hideKeyboard(binding.etSearchInput)
|
|
|
+ binding.clTop.etSearchInput.clearFocus()
|
|
|
+ QMUIKeyboardHelper.hideKeyboard(binding.clTop.etSearchInput)
|
|
|
hideHistoryList()
|
|
|
|
|
|
//校验非法字符
|
|
|
- isSearching.postValue(true)
|
|
|
searchViewModel.search(keyword)
|
|
|
+ binding.clResult.root.show()
|
|
|
|
|
|
if (!fromHistory) {
|
|
|
addHistoryList(input) { list ->
|
|
|
@@ -260,98 +258,52 @@ class SearchActivity : BaseActivity(),
|
|
|
|
|
|
override fun observeViewModel() {
|
|
|
super.observeViewModel()
|
|
|
- isSearching.observe(this) {
|
|
|
- if (it) {
|
|
|
- binding.clHistory.gone()
|
|
|
- }
|
|
|
- }
|
|
|
- isInputEmpty.observe(this) {
|
|
|
- binding.ivClearInput.show(!it)
|
|
|
- binding.tvSearch.isEnabled = !it
|
|
|
+ searchViewModel.isInputEmpty.observe(this) {
|
|
|
+ binding.clTop.ivClearInput.show(!it)
|
|
|
+ binding.clTop.tvSearch.isEnabled = !it
|
|
|
if (it) {
|
|
|
- binding.clSearchResult.gone()
|
|
|
+ binding.clResult.root.gone()
|
|
|
if (historyList.isEmpty()) {
|
|
|
- binding.clHistory.gone()
|
|
|
+ binding.clHistory.root.gone()
|
|
|
} else {
|
|
|
- binding.clHistory.show()
|
|
|
+ binding.clHistory.root.show()
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
- searchViewModel.searchRltLD.observeWithoutCache(this) { rlt ->
|
|
|
- binding.vRefresh.finishRefresh()
|
|
|
- binding.vRefresh.finishLoadMore()
|
|
|
- binding.vRefresh.setEnableLoadMore(searchViewModel.hasMoreData())
|
|
|
-
|
|
|
- isSearching.postValue(false)
|
|
|
- when (rlt) {
|
|
|
- is Rlt.Failed -> {
|
|
|
- if (rlt.error is NoMoreDataError) {
|
|
|
- return@observeWithoutCache
|
|
|
- }
|
|
|
- binding.clSearchResult.show()
|
|
|
- binding.rvResult.gone()
|
|
|
- binding.vErrorView.show(
|
|
|
- errorEmptyResId = APP_R.drawable.common_list_empty_ic,
|
|
|
- title = APP_R.string.commonui_list_empty
|
|
|
- )
|
|
|
- }
|
|
|
-
|
|
|
- is Rlt.Success -> {
|
|
|
- binding.clSearchResult.show()
|
|
|
- binding.rvResult.show()
|
|
|
- binding.vErrorView.gone()
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- searchViewModel.searchResultLD.observeWithoutCache(this) { list ->
|
|
|
- resultAdapter.submitList(list, true)
|
|
|
- }
|
|
|
}
|
|
|
|
|
|
private fun hideHistoryList() {
|
|
|
- binding.clHistory.gone()
|
|
|
- }
|
|
|
-
|
|
|
-
|
|
|
- override fun goProfile(uid: String) {
|
|
|
- Router.build(this, Profile.UserProfile.PATH)
|
|
|
- .putExtra(Profile.Common.EXTRA_UID, uid)
|
|
|
- .start()
|
|
|
+ binding.clHistory.root.gone()
|
|
|
}
|
|
|
|
|
|
- override fun follow(uid: String) {
|
|
|
- followViewModel.follow(uid).observe(this) {
|
|
|
- if (it.isSuccess) {
|
|
|
- searchViewModel.notifyFollowChanged(uid, true)
|
|
|
- } else {
|
|
|
- showFailedToast(it)
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- override fun unFollow(uid: String) {
|
|
|
- followViewModel.unFollow(uid).observe(this) {
|
|
|
- if (it.isSuccess) {
|
|
|
- searchViewModel.notifyFollowChanged(uid, false)
|
|
|
- } else {
|
|
|
- showFailedToast(it)
|
|
|
- }
|
|
|
- }
|
|
|
+ override fun onDestroy() {
|
|
|
+ super.onDestroy()
|
|
|
+ binding.clTop.etSearchInput.clearFocus()
|
|
|
+ QMUIKeyboardHelper.hideKeyboard(binding.clTop.etSearchInput)
|
|
|
}
|
|
|
|
|
|
-
|
|
|
override fun onClick(
|
|
|
item: SearchHistoryItemData,
|
|
|
pos: Int
|
|
|
) {
|
|
|
- binding.etSearchInput.setText(item.keyword)
|
|
|
+ binding.clTop.etSearchInput.setText(item.keyword)
|
|
|
search(item.keyword, true)
|
|
|
}
|
|
|
|
|
|
- override fun onDestroy() {
|
|
|
- super.onDestroy()
|
|
|
- binding.etSearchInput.clearFocus()
|
|
|
- QMUIKeyboardHelper.hideKeyboard(binding.etSearchInput)
|
|
|
+ internal inner class PageAdapter : BaseActivityTabFragmentStateAdapter(this) {
|
|
|
+ override fun getTabName(pos: Int): String {
|
|
|
+ return SEARCH_TABS.getOrNull(pos)?.name?.invoke() ?: ""
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun getItemCount(): Int {
|
|
|
+ return SEARCH_TABS.size
|
|
|
+ }
|
|
|
+
|
|
|
+ override fun createFragment(position: Int): Fragment {
|
|
|
+ val tab = SEARCH_TABS.getOrNull(position) ?: return EmptyFragment()
|
|
|
+ return tab.fragmentBuilder.invoke()
|
|
|
+ }
|
|
|
+
|
|
|
}
|
|
|
|
|
|
}
|