feat:分类新增搜索功能—客户端 https://jira.shanqu.cc/browse/GHZSCY-7512

This commit is contained in:
张晨
2025-03-24 15:20:27 +08:00
parent 7d9f36d587
commit cc0683c3c7
67 changed files with 2142 additions and 1101 deletions

View File

@ -1534,8 +1534,8 @@ object DirectUtils {
if (categoryId.isEmpty()) return
val bundle = Bundle()
bundle.putString(KEY_TO, CategoryV2Activity::class.java.name)
bundle.putString(KEY_CATEGORY_ID, categoryId)
bundle.putString(KEY_CATEGORY_TITLE, categoryTitle)
bundle.putString(KEY_PAGE_ID, categoryId)
bundle.putString(KEY_PAGE_NAME, categoryTitle)
bundle.putString(KEY_ENTRANCE, BaseActivity.mergeEntranceAndPath(entrance, path))
if (exposureEvent != null) bundle.putParcelableArrayList(
KEY_EXPOSURE_SOURCE_LIST,

View File

@ -199,8 +199,8 @@ object ViewPagerFragmentHelper {
// 分类2.0
TYPE_CATEGORY_V2 -> {
className = CategoryV2Fragment::class.java.name
bundle.putString(EntranceConsts.KEY_CATEGORY_ID, entity.link)
bundle.putString(EntranceConsts.KEY_CATEGORY_TITLE, entity.text)
bundle.putString(EntranceConsts.KEY_PAGE_ID, entity.link)
bundle.putString(EntranceConsts.KEY_PAGE_NAME, entity.text)
bundle.putBoolean(EntranceConsts.KEY_SHOW_DOWNLOAD_MENU, !isTabWrapper)
}
// 通用内容合集详情页

View File

@ -1,38 +1,48 @@
package com.gh.common.view
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Color
import android.content.res.ColorStateList
import android.graphics.Typeface
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.CompoundButton
import android.widget.PopupWindow
import android.widget.TextView
import android.widget.RadioButton
import androidx.annotation.ColorInt
import androidx.core.content.ContextCompat
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.forEach
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.setDrawableEnd
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.LayoutCategoryFilterBinding
import com.gh.gamecenter.databinding.LayoutCategoryFilterSizeBinding
import com.gh.gamecenter.databinding.PopCategoryFilterSizeBinding
import com.gh.gamecenter.databinding.PopCategoryFilterTypeBinding
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.google.android.flexbox.FlexboxLayout
class CategoryFilterView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
) : ConstraintLayout(context, attrs, defStyleAttr) {
private var mTypeTv: TextView
private var mCatalogTv: TextView
private var mSizeTv: TextView
private var mTypeContainer: View
private var mCatalogContainer: View
private var mSizeContainer: View
private val binding: LayoutCategoryFilterBinding
private var mTypeFilterArray = arrayOf(SortType.RECOMMENDED, SortType.NEWEST, SortType.RATING)
private var sizeFilterArray: ArrayList<SubjectSettingEntity.Size>? = null
private val mTypeFilterArray = arrayOf(SortType.RECOMMENDED, SortType.NEWEST, SortType.RATING)
private val sizeFilterArray by lazy {
listOf(
SubjectSettingEntity.Size(min = -1, max = -1, text = "全部大小"),
SubjectSettingEntity.Size(min = -1, max = 100, text = "100M以下"),
SubjectSettingEntity.Size(min = 100, max = 300, text = "100-300M"),
SubjectSettingEntity.Size(min = 300, max = 500, text = "300-500M"),
SubjectSettingEntity.Size(min = 500, max = 1000, text = "500M-1G"),
SubjectSettingEntity.Size(min = 1000, max = -1, text = "1G以上")
)
}
private var mOnCategoryFilterSetupListener: OnCategoryFilterSetupListener? = null
private var mOnFilterClickListener: OnFilterClickListener? = null
@ -41,29 +51,28 @@ class CategoryFilterView @JvmOverloads constructor(
private var mSizePopupWindow: PopupWindow? = null
init {
View.inflate(context, R.layout.layout_category_filter, this)
mTypeTv = findViewById(R.id.type_tv)
mCatalogTv = findViewById(R.id.catalog_tv)
mSizeTv = findViewById(R.id.size_tv)
mTypeContainer = findViewById(R.id.container_type)
mCatalogContainer = findViewById(R.id.container_category)
mSizeContainer = findViewById(R.id.container_size)
mTypeTv.text = mTypeFilterArray[0].value
val inflater = LayoutInflater.from(context)
binding = LayoutCategoryFilterBinding.inflate(inflater, this, true)
mTypeContainer.setOnClickListener {
binding.ivTypeArrow.setImageResource(R.drawable.ic_arrow_down)
binding.ivTypeArrow.imageTintList =
ColorStateList.valueOf(com.gh.gamecenter.common.R.color.text_neutral.toColor(context))
binding.ivSizeArrow.setImageResource(R.drawable.ic_arrow_down)
binding.ivSizeArrow.imageTintList =
ColorStateList.valueOf(com.gh.gamecenter.common.R.color.text_neutral.toColor(context))
binding.tvType.text = mTypeFilterArray[0].value
binding.llTypeContainer.setOnClickListener {
mOnFilterClickListener?.onTypeClick()
showSelectTypePopupWindow(this, mTypeTv, mTypeTv.text.toString())
showSelectTypePopupWindow()
}
mCatalogContainer.setOnClickListener {
mOnFilterClickListener?.onCategoryClick()
mOnCategoryFilterSetupListener?.onSetupSortCategory()
}
mSizeContainer.setOnClickListener {
binding.llSizeContainer.setOnClickListener {
mOnFilterClickListener?.onSizeClick()
showSelectSizePopupWindow(this, mSizeTv, mSizeTv.text.toString())
showSelectSizePopupWindow()
}
}
@ -76,154 +85,133 @@ class CategoryFilterView @JvmOverloads constructor(
}
fun resetSortSize() {
mSizeTv.text = "全部大小"
binding.tvType.text = R.string.size.toResString()
}
private fun toggleHighlightedTextView(targetTextView: TextView, highlightIt: Boolean) {
if (highlightIt) {
targetTextView.background = ContextCompat.getDrawable(targetTextView.context, R.drawable.bg_tag_text)
targetTextView.setTextColor(Color.WHITE)
} else {
targetTextView.background = null
targetTextView.setTextColor(ContextCompat.getColor(targetTextView.context, com.gh.gamecenter.common.R.color.text_757575))
}
}
private fun showSelectTypePopupWindow() {
binding.tvType.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
private fun showSelectTypePopupWindow(containerView: View, typeTv: TextView, typeText: String) {
typeTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
typeTv.setDrawableEnd(R.drawable.ic_filter_arrow_up)
binding.ivTypeArrow.setImageResource(R.drawable.ic_arrow_up)
binding.ivTypeArrow.imageTintList =
ColorStateList.valueOf(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
val inflater = LayoutInflater.from(typeTv.context)
val layout = inflater.inflate(R.layout.layout_filter_size, null)
val windowWidth = typeTv.context.resources.displayMetrics.widthPixels - 80F.dip2px()
val inflater = LayoutInflater.from(context)
val typeBinding = PopCategoryFilterTypeBinding.inflate(inflater, null, false)
val windowWidth = resources.displayMetrics.widthPixels - 80F.dip2px()
val windowHeight = mOnCategoryFilterSetupListener?.getPopHeight() ?: 0
val popupWindow = PopupWindow(
layout,
typeBinding.root,
windowWidth,
LayoutParams.WRAP_CONTENT
if (windowHeight == 0) ViewGroup.LayoutParams.WRAP_CONTENT else (windowHeight - height)
).apply { mTypePopupWindow = this }
val flexboxLayout = layout.findViewById<FlexboxLayout>(R.id.flexbox)
val backgroundView = layout.findViewById<View>(R.id.background)
backgroundView.setOnClickListener {
typeBinding.root.setOnClickListener {
popupWindow.dismiss()
}
for (type in mTypeFilterArray) {
val item = inflater.inflate(R.layout.item_filter_size, flexboxLayout, false)
val checkedId = when (binding.tvType.text.toString()) {
"最新" -> R.id.rb_newest
"评分" -> R.id.rb_score
else -> R.id.rb_popular
}
// 单列 3 个,强行设置宽度为屏幕的 1/3
val width = windowWidth / 3
val height = item.layoutParams.height
val checkButton = typeBinding.root.findViewById<RadioButton>(checkedId)
checkButton.setTypeface(checkButton.typeface, Typeface.BOLD)
checkButton.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
checkButton.setDrawableEnd(R.drawable.ic_basic_checkmark_circle_fill)
item.layoutParams = ViewGroup.LayoutParams(width, height)
flexboxLayout.addView(item)
val tv = item.findViewById<TextView>(R.id.size_tv)
tv.text = type.value
toggleHighlightedTextView(tv, typeText == type.value)
tv.tag = type.value
item.setOnClickListener {
toggleHighlightedTextView(tv, true)
popupWindow.dismiss()
typeTv.text = type.value
val onCheckedChangeListener = CompoundButton.OnCheckedChangeListener { button, isChecked ->
if (isChecked) {
button.setTypeface(button.typeface, Typeface.BOLD)
button.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
button.setDrawableEnd(R.drawable.ic_basic_checkmark_circle_fill)
val type = when (button.id) {
R.id.rb_newest -> SortType.NEWEST
R.id.rb_score -> SortType.RATING
else -> SortType.RECOMMENDED
}
binding.tvType.text = type.value
mOnCategoryFilterSetupListener?.onSetupSortType(type)
popupWindow.dismiss()
} else {
button.setTypeface(button.typeface, Typeface.NORMAL)
button.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
button.setDrawableEnd(null)
}
}
typeBinding.rgFilter.forEach {
if (it is RadioButton) {
it.setOnCheckedChangeListener(onCheckedChangeListener)
}
}
popupWindow.setOnDismissListener {
typeTv.setTextColor(com.gh.gamecenter.common.R.color.text_757575.toColor(context))
typeTv.setDrawableEnd(R.drawable.ic_filter_arrow_down)
binding.tvType.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
binding.ivTypeArrow.setImageResource(R.drawable.ic_arrow_down)
binding.ivTypeArrow.imageTintList =
ColorStateList.valueOf(com.gh.gamecenter.common.R.color.text_neutral.toColor(context))
mTypePopupWindow = null
}
popupWindow.isTouchable = true
popupWindow.isFocusable = true
popupWindow.animationStyle = 0
popupWindow.showAsDropDown(containerView, 0, 0)
popupWindow.showAsDropDown(this, 0, 0)
}
private fun showSelectSizePopupWindow(containerView: View, sizeTv: TextView, sizeText: String) {
sizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
sizeTv.setDrawableEnd(R.drawable.ic_filter_arrow_up)
private fun showSelectSizePopupWindow() {
binding.tvSize.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
binding.ivSizeArrow.setImageResource(R.drawable.ic_arrow_up)
binding.ivSizeArrow.imageTintList =
ColorStateList.valueOf(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
val inflater = LayoutInflater.from(sizeTv.context)
val layout = inflater.inflate(R.layout.layout_filter_size, null)
val windowWidth = sizeTv.context.resources.displayMetrics.widthPixels - 80F.dip2px()
val inflater = LayoutInflater.from(context)
val sizeBinding = PopCategoryFilterSizeBinding.inflate(inflater, null, false)
val windowWidth = context.resources.displayMetrics.widthPixels - 80F.dip2px()
val windowHeight = mOnCategoryFilterSetupListener?.getPopHeight() ?: 0
val popupWindow = PopupWindow(
layout,
sizeBinding.root,
windowWidth,
LayoutParams.WRAP_CONTENT
if (windowHeight == 0) ViewGroup.LayoutParams.WRAP_CONTENT else (windowHeight - height)
).apply { mSizePopupWindow = this }
val flexboxLayout = layout.findViewById<FlexboxLayout>(R.id.flexbox)
val backgroundView = layout.findViewById<View>(R.id.background)
sizeFilterArray = if (sizeFilterArray == null) {
getDefaultSizeFilterArray()
} else {
sizeFilterArray?.apply {
if (firstOrNull()?.text != "全部大小") {
add(0, SubjectSettingEntity.Size(min = -1, max = -1, text = "全部大小"))
}
}
}
backgroundView.setOnClickListener {
sizeBinding.root.setOnClickListener {
popupWindow.dismiss()
}
for (size in sizeFilterArray!!) {
val item = inflater.inflate(R.layout.item_filter_size, flexboxLayout, false)
// 单列 3 个,强行设置宽度为屏幕的 1/3
val width = windowWidth / 3
val height = item.layoutParams.height
item.layoutParams = ViewGroup.LayoutParams(width, height)
flexboxLayout.addView(item)
val tv = item.findViewById<TextView>(R.id.size_tv)
tv.text = size.text
toggleHighlightedTextView(tv, sizeText == size.text)
tv.tag = size.text
item.setOnClickListener {
toggleHighlightedTextView(tv, true)
popupWindow.dismiss()
sizeTv.text = size.text
mOnCategoryFilterSetupListener?.onSetupSortSize(size)
}
sizeBinding.rvSize.setOnClickListener {
// do nothing
}
val sizeAdapter = CategoryFilterSizeAdapter {
if (it.min == INVALID_SIZE && it.max == INVALID_SIZE) {
binding.tvSize.text = R.string.size.toResString()
} else {
binding.tvSize.text = it.text
}
popupWindow.dismiss()
mOnCategoryFilterSetupListener?.onSetupSortSize(it)
}
sizeBinding.rvSize.layoutManager = GridLayoutManager(context, 3, RecyclerView.VERTICAL, false)
sizeBinding.rvSize.adapter = sizeAdapter
val selectedPosition =
sizeFilterArray.indexOfFirst { it.text?.contains(binding.tvSize.text.toString()) == true }
sizeAdapter.setData(sizeFilterArray, selectedPosition)
popupWindow.setOnDismissListener {
sizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_757575.toColor(context))
sizeTv.setDrawableEnd(R.drawable.ic_filter_arrow_down)
binding.tvSize.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
binding.ivSizeArrow.setImageResource(R.drawable.ic_arrow_down)
binding.ivSizeArrow.imageTintList =
ColorStateList.valueOf(com.gh.gamecenter.common.R.color.text_neutral.toColor(context))
mSizePopupWindow = null
}
popupWindow.isTouchable = true
popupWindow.isFocusable = true
popupWindow.animationStyle = 0
popupWindow.showAsDropDown(containerView, 0, 0)
}
private fun getDefaultSizeFilterArray(): ArrayList<SubjectSettingEntity.Size> {
return arrayListOf<SubjectSettingEntity.Size>().apply {
add(SubjectSettingEntity.Size(min = -1, max = -1, text = "全部大小"))
add(SubjectSettingEntity.Size(min = -1, max = 100, text = "100M以下"))
add(SubjectSettingEntity.Size(min = 100, max = 300, text = "100-300M"))
add(SubjectSettingEntity.Size(min = 300, max = 500, text = "300-500M"))
add(SubjectSettingEntity.Size(min = 500, max = 1000, text = "500M-1G"))
add(SubjectSettingEntity.Size(min = 1000, max = -1, text = "1G以上"))
}
popupWindow.showAsDropDown(this, 0, 0)
}
fun setRootBackgroundColor(@ColorInt color: Int) {
@ -231,21 +219,23 @@ class CategoryFilterView @JvmOverloads constructor(
}
fun setItemTextColor(@ColorInt color: Int) {
mTypeTv.setTextColor(color)
mCatalogTv.setTextColor(color)
mSizeTv.setTextColor(color)
val colorInt = com.gh.gamecenter.common.R.color.text_neutral.toColor(context)
binding.tvType.setTextColor(color)
binding.tvSize.setTextColor(color)
binding.ivTypeArrow.imageTintList = ColorStateList.valueOf(colorInt)
binding.ivSizeArrow.imageTintList = ColorStateList.valueOf(colorInt)
}
fun updatePopupWindow() {
when {
mTypePopupWindow != null && mTypePopupWindow!!.isShowing -> {
mTypePopupWindow?.dismiss()
showSelectTypePopupWindow(this, mTypeTv, mTypeTv.text.toString())
showSelectTypePopupWindow()
}
mSizePopupWindow != null && mSizePopupWindow!!.isShowing -> {
mSizePopupWindow?.dismiss()
showSelectSizePopupWindow(this, mSizeTv, mSizeTv.text.toString())
showSelectSizePopupWindow()
}
}
}
@ -253,18 +243,69 @@ class CategoryFilterView @JvmOverloads constructor(
interface OnCategoryFilterSetupListener {
fun onSetupSortSize(sortSize: SubjectSettingEntity.Size)
fun onSetupSortType(sortType: SortType)
fun onSetupSortCategory()
fun getPopHeight(): Int
}
interface OnFilterClickListener {
fun onCategoryClick()
fun onTypeClick()
fun onSizeClick()
}
enum class SortType(val value: String) {
RECOMMENDED("热门推荐"),
NEWEST("最新上线"),
RATING("最高评分")
RECOMMENDED("热门"),
NEWEST("最新"),
RATING("评分")
}
class CategoryFilterSizeAdapter(private val itemClick: (SubjectSettingEntity.Size) -> Unit) :
RecyclerView.Adapter<CategoryFilterSizeAdapter.SizeViewHolder>() {
private val dataList = arrayListOf<SubjectSettingEntity.Size>()
private var selectedPosition = 0
@SuppressLint("NotifyDataSetChanged")
fun setData(data: List<SubjectSettingEntity.Size>, position: Int) {
dataList.clear()
dataList.addAll(data)
selectedPosition = position
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SizeViewHolder {
return SizeViewHolder(parent.toBinding())
}
override fun getItemCount(): Int {
return dataList.size
}
override fun onBindViewHolder(holder: SizeViewHolder, position: Int) {
val item = dataList[position]
val context = holder.binding.root.context
if (selectedPosition == position) {
holder.binding.root.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
holder.binding.root.setBackgroundResource(R.drawable.shape_category_filter_size_item_selected)
} else {
holder.binding.root.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
holder.binding.root.setBackgroundResource(R.drawable.shape_category_filter_size_item_normal)
}
holder.binding.root.text = item.text
holder.binding.root.setOnClickListener {
if (selectedPosition == holder.bindingAdapterPosition) {
return@setOnClickListener
}
val lastPosition = selectedPosition
selectedPosition = holder.bindingAdapterPosition
notifyItemChanged(lastPosition)
notifyItemChanged(selectedPosition)
itemClick(item)
}
}
class SizeViewHolder(val binding: LayoutCategoryFilterSizeBinding) : RecyclerView.ViewHolder(binding.root)
}
companion object {
private const val INVALID_SIZE = -1
}
}

View File

@ -1,82 +1,89 @@
package com.gh.gamecenter.category2
import android.content.Context
import android.annotation.SuppressLint
import android.view.ViewGroup
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.common.utils.dip2px
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.view.GridSpacingItemColorDecoration
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.databinding.CategoryDirectoryItemBinding
import com.gh.gamecenter.entity.CategoryEntity
import com.lightgame.adapter.BaseRecyclerAdapter
class CategoryDirectoryAdapter(
context: Context,
private val mViewModel: CategoryV2ViewModel,
private var mList: List<CategoryEntity>
) : BaseRecyclerAdapter<CategoryDirectoryAdapter.CategoryDirectoryItemViewHolder>(context) {
private val listener: SearchCategoryPop.OnSearchCategoryListener
) : RecyclerView.Adapter<CategoryDirectoryAdapter.CategoryDirectoryItemViewHolder>() {
val width = mContext.resources.displayMetrics.widthPixels * 260 / 360
private val data = arrayListOf<CategoryEntity>()
fun setListData(list: List<CategoryEntity>) {
mList = list
@SuppressLint("NotifyDataSetChanged")
fun setListData(newData: List<CategoryEntity>) {
data.clear()
data.addAll(newData)
notifyDataSetChanged()
}
override fun getItemCount() = mList.size
override fun getItemCount() = data.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
CategoryDirectoryItemViewHolder(CategoryDirectoryItemBinding.inflate(mLayoutInflater))
CategoryDirectoryItemViewHolder(listener, parent.toBinding())
override fun onBindViewHolder(holder: CategoryDirectoryItemViewHolder, position: Int) {
holder.binding.run {
root.layoutParams = root.layoutParams?.apply {
width = mContext.resources.displayMetrics.widthPixels * 260 / 360
} ?: RecyclerView.LayoutParams(width, RecyclerView.LayoutParams.WRAP_CONTENT)
holder.onBind(position, data[position])
}
val padTop = if (position == 0) 16F.dip2px() else 24F.dip2px()
root.setPadding(16F.dip2px(), padTop, 16F.dip2px(), 0)
override fun onBindViewHolder(holder: CategoryDirectoryItemViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.isEmpty()) {
super.onBindViewHolder(holder, position, payloads)
} else {
holder.notifyItemSelectedChanged()
}
val entity = mList[position]
title.text = entity.name
title.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(mContext))
}
subCategoryRv.run {
if (adapter is SubCategoryAdapter) {
layoutManager = GridLayoutManager(mContext, 3)
adapter = entity.data?.let {
SubCategoryAdapter(
mContext,
mViewModel,
it,
position
)
}
} else {
layoutManager = GridLayoutManager(mContext, 3)
adapter = entity.data?.let {
SubCategoryAdapter(
mContext,
mViewModel,
it,
position
)
}
addItemDecoration(
GridSpacingItemColorDecoration(
mContext,
6,
6,
com.gh.gamecenter.common.R.color.transparent
)
)
}
}
fun notifyItemSelectedChanged(parentId: String) {
val position = data.indexOfFirst { it.id == parentId }
if (position != -1) {
notifyItemChanged(position, "")
}
}
class CategoryDirectoryItemViewHolder(val binding: CategoryDirectoryItemBinding) :
BaseRecyclerViewHolder<Any>(binding.root)
class CategoryDirectoryItemViewHolder(
private val listener: SearchCategoryPop.OnSearchCategoryListener,
val binding: CategoryDirectoryItemBinding
) :
ViewHolder(binding.root) {
private val childAdapter by lazy {
SubCategoryAdapter(listener)
}
fun onBind(position: Int, item: CategoryEntity) {
val context = binding.root.context
binding.title.text = item.name
if (binding.subCategoryRv.adapter == null) {
binding.subCategoryRv.layoutManager = object : GridLayoutManager(context, 4) {
override fun canScrollVertically(): Boolean {
return false
}
}
binding.subCategoryRv.adapter = childAdapter
binding.subCategoryRv.addItemDecoration(
GridSpacingItemColorDecoration(
context,
8,
8,
com.gh.gamecenter.common.R.color.transparent
)
)
}
childAdapter.setData(position, item)
}
fun notifyItemSelectedChanged() {
childAdapter.notifyItemRangeChanged(0, childAdapter.itemCount, "")
}
}
}

View File

@ -37,8 +37,8 @@ class CategoryV2Activity : DownloadToolbarActivity() {
companion object {
fun getIntent(context: Context, catalogId: String, catalogTitle: String, entrance: String): Intent {
val bundle = Bundle()
bundle.putString(EntranceConsts.KEY_CATEGORY_ID, catalogId)
bundle.putString(EntranceConsts.KEY_CATEGORY_TITLE, catalogTitle)
bundle.putString(EntranceConsts.KEY_PAGE_ID, catalogId)
bundle.putString(EntranceConsts.KEY_PAGE_NAME, catalogTitle)
bundle.putString(EntranceConsts.KEY_ENTRANCE, entrance)
return getTargetIntent(context, CategoryV2Activity::class.java, CategoryV2Fragment::class.java, bundle)
}

View File

@ -1,6 +1,7 @@
package com.gh.gamecenter.category2
import android.content.Context
import android.graphics.Typeface
import android.view.View
import android.view.ViewGroup
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
@ -13,11 +14,13 @@ import com.lightgame.adapter.BaseRecyclerAdapter
class CategoryV2Adapter(
context: Context,
private val mFragment: CategoryV2Fragment,
private val mViewModel: CategoryV2ViewModel,
private val mList: List<SidebarsEntity.SidebarEntity>
) : BaseRecyclerAdapter<CategoryV2Adapter.CategoryV2ItemViewHolder>(context) {
var selectedPosition = 0
private set
override fun getItemCount() = mList.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
@ -28,25 +31,35 @@ class CategoryV2Adapter(
val catalogEntity = mList[position]
catalogName.text = catalogEntity.name
recommendTag.goneIf(!catalogEntity.recommended)
if (catalogEntity.name == mViewModel.selectedCategoryName) {
if (position == selectedPosition) {
selectedTag.visibility = View.VISIBLE
catalogName.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(mContext))
catalogName.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(mContext))
catalogName.setTypeface(null, Typeface.BOLD)
root.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_surface.toColor(mContext))
} else {
selectedTag.visibility = View.GONE
catalogName.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(mContext))
catalogName.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(mContext))
catalogName.setTypeface(null, Typeface.NORMAL)
root.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_background.toColor(mContext))
}
root.setOnClickListener {
if (catalogEntity.name != mViewModel.selectedCategoryName) {
mViewModel.selectedCategoryName = catalogEntity.name
mFragment.changeCategory(position)
if (position != selectedPosition) {
mViewModel.logClickSide()
notifyDataSetChanged()
mViewModel.selectSidebarsPosition(position)
}
}
}
}
fun selectPosition(newPosition: Int) {
if (selectedPosition == newPosition) {
return
}
val oldSelection = selectedPosition
selectedPosition = newPosition
notifyItemChanged(oldSelection)
notifyItemChanged(newPosition)
}
class CategoryV2ItemViewHolder(val binding: CategoryV2ItemBinding) : BaseRecyclerViewHolder<Any>(binding.root)
}

View File

@ -1,14 +1,14 @@
package com.gh.gamecenter.category2
import android.content.res.ColorStateList
import android.graphics.Typeface
import android.os.Bundle
import android.view.Gravity
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.core.view.GravityCompat
import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.core.widget.TextViewCompat
import androidx.fragment.app.viewModels
import com.gh.common.util.LogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.SearchActivity
@ -22,35 +22,49 @@ import com.gh.gamecenter.common.view.FixLinearLayoutManager
import com.gh.gamecenter.core.utils.PageSwitchDataHelper
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.databinding.FragmentCategoryBinding
import com.gh.gamecenter.entity.CategoryEntity
import com.gh.gamecenter.entity.SidebarsEntity
import com.gh.gamecenter.livedata.EventObserver
import com.gh.gamecenter.wrapper.SearchToolbarTabWrapperViewModel
class CategoryV2Fragment : LazyFragment() {
private var mBinding: FragmentCategoryBinding? = null
private var mViewModel: CategoryV2ViewModel? = null
private val viewModel by viewModels<CategoryV2ViewModel>()
private var mHomeViewModel: SearchToolbarTabWrapperViewModel? = null
private var mEntity: SidebarsEntity? = null
private var mSpecialCatalogFragment: SpecialCatalogFragment? = null
private var mCategoryV2ListFragment: CategoryV2ListFragment? = null
private var mLastPageDataMap: HashMap<String, String>? = null
private var mCategoryId: String = ""
private var mCategoryTitle: String = ""
private var pageId: String = ""
private var pageName: String = ""
private var mLastSelectedPosition = -1
private var searchCategoryPop: SearchCategoryPop? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
pageId = arguments?.getString(EntranceConsts.KEY_PAGE_ID) ?: ""
pageName = arguments?.getString(EntranceConsts.KEY_PAGE_NAME) ?: ""
mLastPageDataMap = PageSwitchDataHelper.popLastPageData()
savedInstanceState?.run {
mLastSelectedPosition = getInt(EntranceConsts.KEY_LAST_SELECTED_POSITION)
}
// 除了这里以外,下面还有一个判断是否为首页 tab 栏的赋值
var entrance = if (mEntrance.contains("首页")) "首页" else "板块"
val multiTabNavId = arguments?.getString(EntranceConsts.KEY_MULTI_TAB_NAV_ID, "") ?: ""
if (arguments?.getBoolean(EntranceConsts.KEY_IS_HOME) == true && multiTabNavId.isNotEmpty()) {
mHomeViewModel =
viewModelProviderFromParent(SearchToolbarTabWrapperViewModel.Factory(multiTabNavId, ""), multiTabNavId)
entrance = "首页Tab栏"
}
viewModel.init(entrance)
}
override fun onSaveInstanceState(outState: Bundle) {
mViewModel?.run {
outState.putInt(EntranceConsts.KEY_LAST_SELECTED_POSITION, selectedCategoryPosition)
}
outState.putInt(EntranceConsts.KEY_LAST_SELECTED_POSITION, viewModel.selectedSidebarsPosition.value ?: 0)
super.onSaveInstanceState(outState)
}
@ -61,100 +75,40 @@ class CategoryV2Fragment : LazyFragment() {
}
override fun onFragmentFirstVisible() {
mCategoryId = arguments?.getString(EntranceConsts.KEY_CATEGORY_ID) ?: ""
mCategoryTitle = arguments?.getString(EntranceConsts.KEY_CATEGORY_TITLE) ?: ""
mLastPageDataMap = PageSwitchDataHelper.popLastPageData()
mViewModel = viewModelProviderFromParent(CategoryV2ViewModel.Factory(mCategoryId, mCategoryTitle), mCategoryId)
// 除了这里以外,下面还有一个判断是否为首页 tab 栏的赋值
mViewModel?.entrance = if (mEntrance.contains("首页")) "首页" else "板块"
val multiTabNavId = arguments?.getString(EntranceConsts.KEY_MULTI_TAB_NAV_ID, "") ?: ""
if (arguments?.getBoolean(EntranceConsts.KEY_IS_HOME) == true && multiTabNavId.isNotEmpty()) {
mHomeViewModel = viewModelProviderFromParent(SearchToolbarTabWrapperViewModel.Factory(multiTabNavId, ""), multiTabNavId)
mViewModel?.entrance = "首页Tab栏"
}
mViewModel?.logAppearance()
viewModel.logAppearance()
super.onFragmentFirstVisible()
viewModel.loadData(pageId, pageName)
}
override fun initRealView() {
super.initRealView()
initMenu(R.menu.menu_search)
setNavigationTitle(mCategoryTitle)
setNavigationTitle(pageName)
mBinding?.run {
val width = resources.displayMetrics.widthPixels * 260 / 360
drawerLayout.setScrimColor(com.gh.gamecenter.common.R.color.black_alpha_30.toColor())
// 关闭手势滑动
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
drawerLayout.addDrawerListener(object : DrawerLayout.DrawerListener {
override fun onDrawerStateChanged(newState: Int) {
}
override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
}
override fun onDrawerClosed(drawerView: View) {
showGuide()
}
override fun onDrawerOpened(drawerView: View) {
}
})
directoryContainer.layoutParams.width = width
directoryRv.layoutParams.width = width
// 嵌入在首页时特殊处理
if (arguments?.getBoolean(EntranceConsts.KEY_IS_HOME) == true) {
root.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_surface.toColor(requireContext()))
root.setPadding(0, 8F.dip2px(), 0, 0)
directoryRv.isNestedScrollingEnabled = false
categoryRv.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_background.toColor(requireContext()))
}
tvMoreCategory.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()))
tvTagNumber.typeface =
Typeface.createFromAsset(requireContext().assets, Constants.DIN_FONT_PATH)
}
mViewModel?.directoriesLiveData?.observeNonNull(viewLifecycleOwner) {
initDirectoryView(it)
}
mViewModel?.selectedCountLiveData?.observeNonNull(viewLifecycleOwner) {
mBinding?.run {
if (it == 0) {
confirmTv.text = "确定"
} else {
mViewModel?.run {
if (selectedCategoryName != "全部") {
selectedCategoryName = "全部"
selectedCategoryPosition = if (mEntity?.hasSpecial == true) 1 else 0
categoryRv.adapter?.notifyDataSetChanged()
mCategoryV2ListFragment?.updateSubCategoryId("all")
}
}
confirmTv.text = "确定(已选${it})"
}
}
}
mViewModel?.sidebarsLiveData?.observe(viewLifecycleOwner, Observer {
viewModel.sidebarsLiveData.observe(viewLifecycleOwner) { sidebars ->
mBinding?.run {
reuseLoading.root.visibility = View.GONE
if (it != null) {
if (sidebars != null) {
reuseNoConnection.root.visibility = View.GONE
categoryContainer.visibility = View.VISIBLE
reuseNoneData.root.visibility = View.GONE
mEntity = it
(mEntity!!.sidebars as ArrayList).run {
mEntity = sidebars
(sidebars.sidebars as ArrayList).run {
val allEntity = SidebarsEntity.SidebarEntity(name = "全部", categoryId = "all")
if (mEntity!!.hasSpecial) {
add(0, allEntity)
if (sidebars.hasSpecial) {
val specialEntity = SidebarsEntity.SidebarEntity(name = "精选")
add(0, specialEntity)
add(1, allEntity)
} else {
add(0, allEntity)
add(1, specialEntity)
}
}
initView()
@ -165,12 +119,70 @@ class CategoryV2Fragment : LazyFragment() {
reuseNoConnection.root.setOnClickListener {
reuseNoConnection.root.visibility = View.GONE
reuseLoading.root.visibility = View.VISIBLE
mViewModel?.getSidebars()
mViewModel?.getCategoryDirectories()
viewModel.loadData(pageId, pageName)
}
}
}
})
}
viewModel.directoriesLiveData.observe(viewLifecycleOwner) {
searchCategoryPop?.setData(it)
}
viewModel.selectedSubCategories.observe(viewLifecycleOwner) { selectedList ->
searchCategoryPop?.updateCategorySelected(selectedList)
updateMoreCategory(selectedList.size)
}
viewModel.selectedSidebarsPosition.observe(viewLifecycleOwner) {
mLastSelectedPosition = it
onSelectedPositionChanged(it)
val adapter = mBinding?.categoryRv?.adapter
if (adapter is CategoryV2Adapter) {
adapter.selectPosition(it)
}
}
viewModel.notifySubCategorySelected.observe(
viewLifecycleOwner,
EventObserver {
searchCategoryPop?.notifyItemSelectedChanged(it)
})
}
private fun createSearchPop(isAutoRequestFocus: Boolean): SearchCategoryPop {
val height = mBinding!!.root.height
return SearchCategoryPop.newInstance(requireContext(), height, isAutoRequestFocus, pageId, pageName).apply {
val data = viewModel.directoriesLiveData.value ?: listOf()
setData(data)
val selectedList = viewModel.selectedSubCategories.value
updateCategorySelected(selectedList)
setOnSearchCategoryListener(object : SearchCategoryPop.OnSearchCategoryListener {
override fun isEnableSelected(): Boolean {
val size = viewModel.selectedSubCategories.value?.size ?: 0
return size < 5
}
override fun onItemSelected(selected: CategoryV2ViewModel.SelectedTags) {
viewModel.addSubCategorySelected(selected)
}
override fun onItemRemoved(parentId: String, subCategoryId: String) {
viewModel.removeSubCategorySelected(parentId, subCategoryId, "全部游戏")
}
override fun onResetSelected() {
viewModel.clearSelectedTag()
viewModel.updateGameFiltered()
}
override fun onSubmit() {
viewModel.logClickDetermine()
}
})
}
}
fun removeGuide() {
@ -181,15 +193,7 @@ class CategoryV2Fragment : LazyFragment() {
}
private fun showGuide() {
if (!isAdded) return
mBinding?.run {
val isShow = SPUtils.getBoolean(Constants.SP_SHOW_CATEGORY_GUIDE)
if (isShow) return
guideContainer.layoutParams = (guideContainer.layoutParams as ViewGroup.MarginLayoutParams).apply {
val screenWidth = resources.displayMetrics.widthPixels
leftMargin = screenWidth * 66F.dip2px() / 360F.dip2px()
}
guideContainer.visibility = View.VISIBLE
postDelayedRunnable({
@ -204,7 +208,7 @@ class CategoryV2Fragment : LazyFragment() {
override fun onMenuItemClick(menuItem: MenuItem?) {
menuItem?.run {
if (itemId == R.id.menu_search) {
LogUtils.uploadSearchGame("access_to_search", mCategoryTitle, "", "")
LogUtils.uploadSearchGame("access_to_search", pageName, "", "")
val intent = SearchActivity.getIntent(
requireContext(),
false,
@ -217,63 +221,64 @@ class CategoryV2Fragment : LazyFragment() {
}
}
private fun initDirectoryView(list: List<CategoryEntity>) {
mBinding?.run {
mViewModel?.run {
if (directoryRv.adapter != null) {
(directoryRv.adapter as? CategoryDirectoryAdapter)?.setListData(list)
} else {
directoryRv.layoutManager = FixLinearLayoutManager(requireContext())
directoryRv.adapter = CategoryDirectoryAdapter(
requireContext(),
this,
list
)
}
}
resetTv.setOnClickListener {
mViewModel?.logClickReset("全部类别")
confirmTv.text = "确定"
mViewModel?.resetDirectoryList()
mCategoryV2ListFragment?.changeCategoryTab()
}
confirmTv.setOnClickListener {
mViewModel?.logClickDetermine()
drawerLayout.closeDrawer(GravityCompat.START)
}
}
}
private fun initView() {
if (mEntity?.sidebars?.isNullOrEmpty() == true || mViewModel == null) return
if (mEntity?.sidebars.isNullOrEmpty()) return
initSelectedCategory()
initCategoryRv()
initContentFragment()
mBinding?.run {
vSearchCategory.setOnClickListener {
SensorsBridge.logClassificationSearch(pageId, pageName)
removeGuide()
showSearchPop(true)
}
vMoreCategory.setOnClickListener {
removeGuide()
showSearchPop(false)
}
}
val isShow = SPUtils.getBoolean(Constants.SP_SHOW_CATEGORY_GUIDE)
if (!isShow) {
postDelayedRunnable({
showSearchPop(false)
searchCategoryPop?.setOnDismissListener {
showGuide()
}
}, 200)
}
}
private fun showSearchPop(isAutoRequestFocus: Boolean) {
mBinding?.run {
val location = IntArray(2)
vSearchCategory.getLocationOnScreen(location)
val popTop = location[1] - 8F.dip2px()
searchCategoryPop = createSearchPop(isAutoRequestFocus)
searchCategoryPop?.showAtLocation(vSearchCategory, Gravity.TOP, 0, popTop)
}
}
private fun initSelectedCategory() {
mEntity?.run {
mViewModel?.run {
if (mLastSelectedPosition != -1) {
selectedCategoryPosition = mLastSelectedPosition
selectedCategoryName = sidebars[mLastSelectedPosition].name
} else {
selectedCategoryPosition = 0
selectedCategoryName = sidebars[0].name
}
}
if (mLastSelectedPosition != -1) {
viewModel.selectSidebarsPosition(mLastSelectedPosition)
} else {
// 默认选中第 0 个 位置
viewModel.selectSidebarsPosition(0)
}
}
private fun initCategoryRv() {
mEntity?.run {
mViewModel?.run {
viewModel.run {
mBinding?.categoryRv?.layoutManager = FixLinearLayoutManager(requireContext())
mBinding?.categoryRv?.adapter = CategoryV2Adapter(
requireContext(),
this@CategoryV2Fragment,
this,
sidebars
)
@ -281,226 +286,93 @@ class CategoryV2Fragment : LazyFragment() {
}
}
private fun initContentFragment() {
mEntity?.apply {
mViewModel?.apply {
if (hasSpecial && selectedCategoryPosition == 0) {
initSpecialCatalogFragment()
} else {
initCategoryV2ListFragment()
private fun onSelectedPositionChanged(position: Int) {
mEntity?.run {
viewModel.run {
clearSelectedTag()
childFragmentManager.fragments.find { it.isAdded }
val targetFragment =
if (hasSpecial && position == 1) {
val fragment = childFragmentManager.findFragmentByTag(SpecialCatalogFragment::class.java.name)
?: SpecialCatalogFragment()
fragment.arguments = bundleOf(
EntranceConsts.KEY_IS_CATEGORY_V2 to true,
EntranceConsts.KEY_CATALOG_ID to pageId,
EntranceConsts.KEY_CATALOG_TITLE to pageName,
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST to arguments?.getParcelableArrayList<ExposureSource>(
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST
),
EntranceConsts.KEY_LAST_PAGE_DATA to mLastPageDataMap
)
fragment
// 第一次点"全部"tab展开全部类别选择框
// 加延迟是为了防止卡顿
if (SPUtils.getBoolean(Constants.SP_FIRST_ENTER_CATEGORY_V2, true)) {
SPUtils.setBoolean(Constants.SP_FIRST_ENTER_CATEGORY_V2, false)
mBinding?.drawerLayout?.postDelayed({
tryCatchInRelease { openDrawer() }
}, 500L)
} else {
val fragment = (childFragmentManager.findFragmentByTag(CategoryV2ListFragment::class.java.name)
?: CategoryV2ListFragment())
fragment.arguments = bundleOf(
EntranceConsts.KEY_PAGE_ID to id,
EntranceConsts.KEY_PAGE_NAME to pageName,
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST to arguments?.getParcelableArrayList<ExposureSource>(
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST
),
EntranceConsts.KEY_LAST_PAGE_DATA to mLastPageDataMap
)
fragment
}
}
}
}
}
private fun initSpecialCatalogFragment() {
mEntity?.run {
mSpecialCatalogFragment = childFragmentManager
.findFragmentByTag(SpecialCatalogFragment::class.java.name)
as? SpecialCatalogFragment ?: SpecialCatalogFragment()
mSpecialCatalogFragment?.arguments = bundleOf(
EntranceConsts.KEY_IS_CATEGORY_V2 to true,
EntranceConsts.KEY_CATALOG_ID to id,
EntranceConsts.KEY_CATALOG_TITLE to mCategoryTitle,
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST to arguments?.getParcelableArrayList<ExposureSource>(
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST
),
EntranceConsts.KEY_LAST_PAGE_DATA to mLastPageDataMap
)
childFragmentManager
.beginTransaction()
.replace(
R.id.gamesContainer,
mSpecialCatalogFragment!!,
SpecialCatalogFragment::class.java.name
)
.commitAllowingStateLoss()
}
}
private fun initCategoryV2ListFragment() {
mEntity?.run {
mViewModel?.run {
mCategoryV2ListFragment = childFragmentManager
.findFragmentByTag(CategoryV2ListFragment::class.java.name)
as? CategoryV2ListFragment ?: CategoryV2ListFragment()
mCategoryV2ListFragment?.arguments = bundleOf(
EntranceConsts.KEY_CATEGORY_ID to id,
EntranceConsts.KEY_SUB_CATEGORY_ID to sidebars[selectedCategoryPosition].categoryId,
EntranceConsts.KEY_CATEGORY_TITLE to mCategoryTitle,
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST to arguments?.getParcelableArrayList<ExposureSource>(
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST
),
EntranceConsts.KEY_LAST_PAGE_DATA to mLastPageDataMap
)
childFragmentManager
.beginTransaction()
.replace(
R.id.gamesContainer,
mCategoryV2ListFragment!!,
CategoryV2ListFragment::class.java.name
)
.replace(R.id.gamesContainer, targetFragment, targetFragment::class.java.name)
.commitAllowingStateLoss()
}
}
}
fun changeCategory(position: Int) {
mEntity?.run {
mViewModel?.run {
resetDirectoryList()
if (hasSpecial) {
if (selectedCategoryPosition == 0) {
mCategoryV2ListFragment = childFragmentManager
.findFragmentByTag(CategoryV2ListFragment::class.java.name)
as? CategoryV2ListFragment ?: CategoryV2ListFragment()
mCategoryV2ListFragment?.arguments = bundleOf(
EntranceConsts.KEY_CATEGORY_ID to id,
EntranceConsts.KEY_SUB_CATEGORY_ID to sidebars[position].categoryId,
EntranceConsts.KEY_CATALOG_TITLE to mCategoryTitle,
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST to arguments?.getParcelableArrayList<ExposureSource>(
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST
),
EntranceConsts.KEY_LAST_PAGE_DATA to mLastPageDataMap
)
childFragmentManager
.beginTransaction()
.replace(
R.id.gamesContainer,
mCategoryV2ListFragment!!,
CategoryV2ListFragment::class.java.name
)
.commitAllowingStateLoss()
} else {
if (position == 0) {
removeGuide()
mSpecialCatalogFragment = childFragmentManager
.findFragmentByTag(SpecialCatalogFragment::class.java.name)
as? SpecialCatalogFragment ?: SpecialCatalogFragment()
mSpecialCatalogFragment?.arguments = bundleOf(
EntranceConsts.KEY_IS_CATEGORY_V2 to true,
EntranceConsts.KEY_CATALOG_ID to id,
EntranceConsts.KEY_CATALOG_TITLE to mCategoryTitle,
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST to arguments?.getParcelableArrayList<ExposureSource>(
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST
),
EntranceConsts.KEY_LAST_PAGE_DATA to mLastPageDataMap
)
childFragmentManager
.beginTransaction()
.replace(
R.id.gamesContainer,
mSpecialCatalogFragment!!,
SpecialCatalogFragment::class.java.name
)
.commitAllowingStateLoss()
} else {
if (mCategoryV2ListFragment?.isStateSaved == false) {
mCategoryV2ListFragment?.arguments = bundleOf(
EntranceConsts.KEY_CATEGORY_ID to id,
EntranceConsts.KEY_SUB_CATEGORY_ID to sidebars[position].categoryId,
EntranceConsts.KEY_CATALOG_TITLE to mCategoryTitle,
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST to arguments?.getParcelableArrayList<ExposureSource>(
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST
),
EntranceConsts.KEY_LAST_PAGE_DATA to mLastPageDataMap
)
}
mCategoryV2ListFragment?.changeCategoryTab(sidebars[position].categoryId)
}
}
// 第一次点"全部"tab展开全部类别选择框
// 加延迟是为了防止卡顿
if (position == 1 && SPUtils.getBoolean(Constants.SP_FIRST_ENTER_CATEGORY_V2, true)) {
SPUtils.setBoolean(Constants.SP_FIRST_ENTER_CATEGORY_V2, false)
mBinding?.drawerLayout?.postDelayed({
tryCatchInRelease { openDrawer() }
}, 200L)
}
} else {
if (mCategoryV2ListFragment?.isStateSaved == false) {
mCategoryV2ListFragment?.arguments = bundleOf(
EntranceConsts.KEY_CATEGORY_ID to id,
EntranceConsts.KEY_SUB_CATEGORY_ID to sidebars[position].categoryId,
EntranceConsts.KEY_CATALOG_TITLE to mCategoryTitle,
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST to arguments?.getParcelableArrayList<ExposureSource>(
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST
),
EntranceConsts.KEY_LAST_PAGE_DATA to mLastPageDataMap
)
}
mCategoryV2ListFragment?.changeCategoryTab(sidebars[position].categoryId)
}
selectedCategoryPosition = position
}
}
}
fun openDirectoryLayout() {
private fun updateMoreCategory(size: Int) {
mBinding?.run {
var i = 0
mViewModel?.run {
mEntity?.run {
val sidebar = sidebars[selectedCategoryPosition]
if (sidebar.name != "全部") {
directories.forEachIndexed { index, entity ->
if (sidebar.type == "level_one") {
if (sidebar.categoryId == entity.id) {
i = index
return@run
}
} else {
if (sidebar.parentId == entity.id) {
i = index
return@run
}
}
}
} else if (sidebar.name == "全部" && selectedCategoryList.isNotEmpty()) {
i = selectedCategoryList[0].primaryIndex
}
if (size > 0) {
vMoreCategory.setBackgroundResource(R.drawable.bg_more_category_filtered)
TextViewCompat.setCompoundDrawableTintList(
tvMoreCategory,
ColorStateList.valueOf(com.gh.gamecenter.common.R.color.text_theme.toColor(requireContext()))
)
tvMoreCategory.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(requireContext()))
tvTagNumber.goneIf(false) {
tvTagNumber.text = "$size"
}
} else {
vMoreCategory.setBackgroundResource(R.drawable.bg_more_category_default)
TextViewCompat.setCompoundDrawableTintList(
tvMoreCategory,
ColorStateList.valueOf(com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()))
)
tvMoreCategory.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()))
tvTagNumber.goneIf(true)
}
openDrawer()
(directoryRv.layoutManager as? LinearLayoutManager)?.scrollToPositionWithOffset(i, 0)
}
}
private fun openDrawer() {
mBinding?.drawerLayout?.openDrawer(GravityCompat.START)
mHomeViewModel?.let {
mBinding?.directoryContainer?.setPadding(
0,
0,
0,
requireContext().resources.getDimension(com.gh.gamecenter.common.R.dimen.main_bottom_tab_height).toInt() - it.appBarOffset
)
}
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
getItemMenu(R.id.menu_search)?.setIcon(R.drawable.ic_column_search)
mBinding?.categoryRv?.adapter?.run {
mBinding?.categoryRv?.recycledViewPool?.clear()
notifyItemRangeChanged(0, itemCount)
}
mBinding?.directoryRv?.adapter?.run {
mBinding?.directoryRv?.recycledViewPool?.clear()
notifyItemRangeChanged(0, itemCount)
mBinding?.run {
categoryRv.adapter?.run {
categoryRv.recycledViewPool.clear()
notifyItemRangeChanged(0, itemCount, "")
}
val selectedTagsSize = viewModel.selectedSubCategories.value?.size ?: 0
updateMoreCategory(selectedTagsSize)
context?.let {
ivSearchCategory.imageTintList =
ColorStateList.valueOf(com.gh.gamecenter.common.R.color.text_instance.toColor(it))
}
}
}
companion object {
private const val SPECIAL_CATEGORY_POSITION = 1
}
}

View File

@ -76,8 +76,15 @@ class CategoryV2ListAdapter(
ItemViewType.GAME_NORMAL -> {
CategoryGameItemViewHolder(parent.toBinding())
}
else -> {
FooterViewHolder(mLayoutInflater.inflate(com.gh.gamecenter.common.R.layout.refresh_footerview, parent, false))
FooterViewHolder(
mLayoutInflater.inflate(
com.gh.gamecenter.common.R.layout.refresh_footerview,
parent,
false
)
)
}
}
}
@ -94,20 +101,12 @@ class CategoryV2ListAdapter(
holder.bindGameItem(gameEntity)
holder.initServerType(gameEntity)
val categoryTitle = mCategoryViewModel.categoryTitle
val selectedCategoryName = mCategoryViewModel.selectedCategoryName
val builder = StringBuilder()
mCategoryViewModel.selectedCategoryList.run {
forEachIndexed { index, entity ->
builder.append(entity.name)
if (index != size - 1) {
builder.append("_")
}
}
}
val selectedSubCatalogName = builder.toString()
val sortType = mViewModel.sortType.value
val sortSize = mViewModel.sortSize.text
val categoryTitle = mCategoryViewModel.pageName
val selectedCategoryName = mCategoryViewModel.selectedSidebarsName
val selectedSubCatalogName =
mCategoryViewModel.selectedSubCategories.value?.joinToString("-") { it.category.name ?: "" }
val sortType = mViewModel.gameFiltered.sortType.value
val sortSize = mViewModel.gameFiltered.size.text
val exposureSources = ArrayList<ExposureSource>()
if (!mViewModel.exposureSourceList.isNullOrEmpty()) {
@ -172,8 +171,13 @@ class CategoryV2ListAdapter(
) {
val trackEvent = JSONObject()
try {
trackEvent.put("navigation_bar_name", mCategoryViewModel.selectedCategoryName)
trackEvent.put("game_tag", mCategoryViewModel.selectedCategoryList.map { it.name })
trackEvent.put("navigation_bar_name", mCategoryViewModel.selectedSidebarsName)
trackEvent.put(
"game_tag",
(mCategoryViewModel.selectedSubCategories.value ?: listOf())
.map {
it.category.name
})
trackEvent.put("game_status", gameEntity.category)
trackEvent.put(
"inclusion_size",
@ -266,18 +270,24 @@ class CategoryV2ListAdapter(
binding.gameKaifuType.visibility = View.GONE
binding.gameKaifuType.text = ""
}
serverLabel != null -> {
binding.gameKaifuType.visibility = View.VISIBLE
binding.gameKaifuType.text = serverLabel.value
if (gameEntity.isUseDefaultServerStyle()) {
binding.gameKaifuType.background =
com.gh.gamecenter.feature.R.drawable.server_label_default_bg.toDrawable(binding.root.context)
binding.gameKaifuType.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(binding.root.context))
binding.gameKaifuType.setTextColor(
com.gh.gamecenter.common.R.color.text_secondary.toColor(
binding.root.context
)
)
} else {
binding.gameKaifuType.background = DrawableView.getServerDrawable(serverLabel.color)
binding.gameKaifuType.setTextColor(com.gh.gamecenter.common.R.color.white.toColor(binding.root.context))
}
}
else -> binding.gameKaifuType.visibility = View.GONE
}

View File

@ -2,9 +2,10 @@ package com.gh.gamecenter.category2
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.ethanhua.skeleton.Skeleton
import com.gh.gamecenter.common.constant.Constants
import com.gh.common.exposure.ExposureListener
import com.gh.common.util.DialogUtils
import com.gh.common.view.CategoryFilterView
@ -13,16 +14,17 @@ import com.gh.common.xapk.XapkUnzipStatus
import com.gh.download.DownloadManager
import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.ListFragment
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.observeNonNull
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.viewModelProvider
import com.gh.gamecenter.databinding.FragmentCategoryListBinding
import com.gh.gamecenter.databinding.LayoutSelectedCategoryBinding
import com.gh.gamecenter.entity.CategoryEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.eventbus.EBPackage
import com.google.android.flexbox.FlexboxLayout
import com.gh.gamecenter.feature.entity.GameEntity
import com.lightgame.download.DataWatcher
import com.lightgame.download.DownloadEntity
import org.greenrobot.eventbus.Subscribe
@ -30,13 +32,18 @@ import org.greenrobot.eventbus.ThreadMode
class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>() {
private var mCategoryId: String = ""
private var mSubCategoryId: String = ""
private var mCategoryTitle: String = ""
private var pageId: String = ""
private var pageName: String = ""
private val parentViewModel by viewModels<CategoryV2ViewModel>(
ownerProducer = { parentFragment ?: this }
)
override fun isAutomaticLoad(): Boolean {
return false
}
private var mAdapter: CategoryV2ListAdapter? = null
private var mSelectedViewList = ArrayList<View>()
private var mBinding: FragmentCategoryListBinding? = null
private var mCategoryViewModel: CategoryV2ViewModel? = null
private var mLastPageDataMap: HashMap<String, String>? = null
private val mDataWatcher = object : DataWatcher() {
override fun onDataChanged(downloadEntity: DownloadEntity) {
@ -52,6 +59,12 @@ class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>
}
}
private val selectedTagsAdapter by lazy {
SelectedTagsAdapter {
parentViewModel.removeSubCategorySelected(it.parentId, it.category.id, "游戏列表")
}
}
override fun getLayoutId() = 0
override fun getInflatedLayout() =
@ -60,8 +73,7 @@ class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>
override fun provideListViewModel() =
viewModelProvider<CategoryV2ListViewModel>(
CategoryV2ListViewModel.Factory(
mCategoryId,
mSubCategoryId,
pageId,
arguments?.getParcelableArrayList(EntranceConsts.KEY_EXPOSURE_SOURCE_LIST)
)
)
@ -70,12 +82,7 @@ class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>
?: CategoryV2ListAdapter(
requireContext(),
mListViewModel ?: provideListViewModel(),
mCategoryViewModel ?: viewModelProviderFromParent(
CategoryV2ViewModel.Factory(
mCategoryId,
mCategoryTitle
), mCategoryId
),
parentViewModel,
mEntrance,
mLastPageDataMap
).apply { mAdapter = this }
@ -83,12 +90,10 @@ class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>
override fun getItemDecoration() = null
override fun onCreate(savedInstanceState: Bundle?) {
mCategoryId = arguments?.getString(EntranceConsts.KEY_CATEGORY_ID) ?: ""
mSubCategoryId = arguments?.getString(EntranceConsts.KEY_SUB_CATEGORY_ID) ?: ""
mCategoryTitle = arguments?.getString(EntranceConsts.KEY_CATEGORY_TITLE) ?: ""
pageId = arguments?.getString(EntranceConsts.KEY_PAGE_ID) ?: ""
pageName = arguments?.getString(EntranceConsts.KEY_PAGE_NAME) ?: ""
mLastPageDataMap = arguments?.getSerializable(EntranceConsts.KEY_LAST_PAGE_DATA) as? HashMap<String, String>
mCategoryViewModel =
viewModelProviderFromParent(CategoryV2ViewModel.Factory(mCategoryId, mCategoryTitle), mCategoryId)
mEntrance = arguments?.getString(EntranceConsts.KEY_ENTRANCE) ?: Constants.ENTRANCE_UNKNOWN
super.onCreate(savedInstanceState)
@ -104,19 +109,8 @@ class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>
initFilterView()
mListViewModel?.refresh?.observeNonNull(viewLifecycleOwner) { onRefresh() }
mCategoryViewModel?.run {
categoryPositionLiveData.observeNonNull(viewLifecycleOwner) {
directories[it.first].data?.get(it.second)?.run {
mBinding?.selectedCategoryContainer?.visibility = View.VISIBLE
if (selected) {
addCategory(this)
} else {
removeCategory(this)
}
}
}
mListViewModel?.refresh?.observeNonNull(viewLifecycleOwner) {
onRefresh()
}
mListRv.addOnScrollListener(ExposureListener(this, provideListAdapter()))
@ -132,19 +126,40 @@ class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>
.show()
mBinding?.reuseNoneData?.reuseResetLoadTv?.setOnClickListener {
mCategoryViewModel?.run {
// 重试时,
// 清空所有筛选条件
with(parentViewModel) {
logClickReset("游戏列表")
resetDirectoryList()
clearSelectedTag()
// 移除大小限制
mBinding?.filterContainer?.resetSortSize()
val size = SubjectSettingEntity.Size()
updateGameFiltered(size)
}
}
with(parentViewModel) {
gameFiltered.observe(viewLifecycleOwner) {
mListViewModel.updateSortConfig(it)
}
selectedSubCategories.observe(viewLifecycleOwner) {
updateSelectedTags(it)
}
resetSortSize()
changeCategoryTab()
// openDirectoryLayout()
}
}
private fun resetSortSize() {
mBinding?.filterContainer?.resetSortSize()
mListViewModel?.sortSize = SubjectSettingEntity.Size(min = -1, max = -1, text = "全部大小")
private fun updateSelectedTags(selectedTags: List<CategoryV2ViewModel.SelectedTags>) {
mBinding?.run {
flTagsContainer.goneIf(selectedTags.isEmpty()) {
if (rvTags.adapter == null) {
rvTags.layoutManager = LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL, false)
rvTags.adapter = selectedTagsAdapter
}
selectedTagsAdapter.submitList(selectedTags)
}
}
}
@ -154,22 +169,19 @@ class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>
setOnConfigSetupListener(object : CategoryFilterView.OnCategoryFilterSetupListener {
override fun onSetupSortSize(sortSize: SubjectSettingEntity.Size) {
mListViewModel?.updateSortConfig(sortSize = sortSize)
parentViewModel.updateGameFiltered(size = sortSize)
}
override fun onSetupSortType(sortType: CategoryFilterView.SortType) {
mListViewModel?.updateSortConfig(sortType = sortType)
parentViewModel.updateGameFiltered(sortType = sortType)
}
override fun onSetupSortCategory() {
openDirectoryLayout()
override fun getPopHeight(): Int {
return (mBinding?.root?.height ?: 0) - (mBinding?.flTagsContainer?.height ?: 0)
}
})
setOnFilterClickListener(object : CategoryFilterView.OnFilterClickListener {
override fun onCategoryClick() {
removeGuide()
}
override fun onTypeClick() {
removeGuide()
@ -183,101 +195,6 @@ class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>
}
}
fun updateSubCategoryId(id: String) {
mSubCategoryId = id
}
fun changeCategoryTab(categoryId: String? = null) {
mSelectedViewList.clear()
mBinding?.selectedCategoryContainer?.run {
removeAllViews()
visibility = View.GONE
}
if (categoryId != null) mSubCategoryId = categoryId
mListViewModel?.updateSortConfig(categoryIds = mSubCategoryId)
}
private fun addCategory(entity: CategoryEntity) {
addCategoryView(entity)
mCategoryViewModel?.selectedCategoryList?.add(entity)
updateCategoryGame()
}
private fun removeCategory(entity: CategoryEntity) {
mCategoryViewModel?.selectedCategoryList?.run {
if (isEmpty()) return
removeCategoryView(entity.name ?: "")
remove(entity)
if (size == 0) {
mBinding?.selectedCategoryContainer?.visibility = View.GONE
mListViewModel?.updateSortConfig(categoryIds = mSubCategoryId)
} else {
updateCategoryGame()
}
}
}
private fun addCategoryView(entity: CategoryEntity) {
val binding = LayoutSelectedCategoryBinding.inflate(layoutInflater).apply {
val params =
FlexboxLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
params.setMargins(0, 8F.dip2px(), 8F.dip2px(), 0)
params.height = 24F.dip2px()
root.layoutParams = params
name.text = entity.name
root.setOnClickListener {
removeCategoryAndNotify(entity)
}
}
mSelectedViewList.add(binding.root)
mBinding?.selectedCategoryContainer?.addView(binding.root)
}
private fun removeCategoryView(categoryName: String) {
mCategoryViewModel?.selectedCategoryList?.run {
var i = 0
forEachIndexed { index, categoryEntity ->
if (categoryName == categoryEntity.name) {
i = index
}
}
if (i < mSelectedViewList.size) {
mBinding?.selectedCategoryContainer?.removeView(mSelectedViewList[i])
mSelectedViewList.removeAt(i)
}
}
}
private fun removeCategoryAndNotify(entity: CategoryEntity) {
removeCategory(entity)
entity.selected = false
mCategoryViewModel?.run {
if (selectedCount > 0) {
selectedCount--
postSelectedCount()
postCategoryDirectoryList()
logClickClassificationDelete(entity.primaryIndex, entity.name ?: "", "游戏列表")
}
}
}
private fun updateCategoryGame() {
mCategoryViewModel?.selectedCategoryList?.run {
val categoryIds = StringBuilder()
forEachIndexed { index, s ->
categoryIds.append(s.id)
if (index != size - 1) {
categoryIds.append("-")
}
}
mListViewModel?.updateSortConfig(categoryIds = categoryIds.toString())
}
}
private fun removeGuide() {
(parentFragment as? CategoryV2Fragment)?.removeGuide()
}
@ -328,10 +245,6 @@ class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>
}
}
fun openDirectoryLayout() {
(parentFragment as? CategoryV2Fragment)?.openDirectoryLayout()
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
mBinding?.filterContainer?.run {

View File

@ -4,14 +4,13 @@ import android.app.Application
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.gh.gamecenter.common.entity.ExposureEntity
import com.gh.common.exposure.ExposureUtils
import com.gh.gamecenter.core.utils.UrlFilterUtils
import com.gh.common.view.CategoryFilterView
import com.gh.gamecenter.common.baselist.ListViewModel
import com.gh.gamecenter.common.entity.ExposureEntity
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.core.utils.UrlFilterUtils
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.gh.gamecenter.retrofit.RetrofitManager
import com.halo.assistant.HaloApp
import io.reactivex.Observable
@ -20,14 +19,13 @@ import io.reactivex.Single
class CategoryV2ListViewModel(
application: Application,
val categoryId: String,
var categoryIds: String,
var exposureSourceList: List<ExposureSource>?
) : ListViewModel<GameEntity, GameEntity>(application) {
val refresh = MutableLiveData<Boolean>()
var sortType = CategoryFilterView.SortType.RECOMMENDED
var sortSize = SubjectSettingEntity.Size()
var gameFiltered = CategoryV2ViewModel.GameFiltered()
private set
override fun provideDataObservable(page: Int): Observable<List<GameEntity>>? = null
@ -51,52 +49,36 @@ class CategoryV2ListViewModel(
}
fun updateSortConfig(
sortSize: SubjectSettingEntity.Size? = null,
sortType: CategoryFilterView.SortType? = null,
categoryIds: String? = null
gameFiltered: CategoryV2ViewModel.GameFiltered
) {
when {
sortSize != null && sortSize != this.sortSize -> {
this.sortSize = sortSize
refresh.postValue(true)
}
sortType != null && sortType != this.sortType -> {
this.sortType = sortType
refresh.postValue(true)
}
categoryIds != null && categoryIds != this.categoryIds -> {
this.categoryIds = categoryIds
refresh.postValue(true)
}
}
this.gameFiltered = gameFiltered
refresh.postValue(true)
}
private fun getFilter(): String? {
return UrlFilterUtils.getFilterQuery(
"category_ids", categoryIds,
"min_size", sortSize.min.toString(),
"max_size", sortSize.max.toString()
"category_ids", gameFiltered.categoryIds.ifBlank { gameFiltered.sidebarCategoryId },
"min_size", gameFiltered.size.min.toString(),
"max_size", gameFiltered.size.max.toString()
)
}
private fun getSortType(): String? {
return when (sortType) {
return when (gameFiltered.sortType) {
CategoryFilterView.SortType.RECOMMENDED -> "download:-1"
CategoryFilterView.SortType.NEWEST -> "publish:-1"
CategoryFilterView.SortType.RATING -> "star:-1"
}
}
class Factory(val categoryId: String, val categoryIds: String, val exposureSourceList: List<ExposureSource>?) :
class Factory(val categoryId: String, val exposureSourceList: List<ExposureSource>?) :
ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return CategoryV2ListViewModel(
HaloApp.getInstance().application,
categoryId,
categoryIds,
exposureSourceList
) as T
}

View File

@ -1,160 +1,214 @@
package com.gh.gamecenter.category2
import android.annotation.SuppressLint
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.gh.common.util.LogUtils
import com.gh.common.view.CategoryFilterView
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.entity.CategoryEntity
import com.gh.gamecenter.entity.SidebarsEntity
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.gh.gamecenter.livedata.Event
import com.gh.gamecenter.retrofit.RetrofitManager
import com.halo.assistant.HaloApp
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import io.reactivex.disposables.CompositeDisposable
class CategoryV2ViewModel(
application: Application,
private val mCategoryId: String,
val categoryTitle: String
) : AndroidViewModel(application) {
class CategoryV2ViewModel : ViewModel() {
private val compositeDisposable = CompositeDisposable()
private val api = RetrofitManager.getInstance().api
var sidebarsLiveData = MutableLiveData<SidebarsEntity>()
var directories = ArrayList<CategoryEntity>()
var sidebarsLiveData = MutableLiveData<SidebarsEntity?>()
var directoriesLiveData = MutableLiveData<List<CategoryEntity>>()
var selectedCount = 0
var selectedCountLiveData = MutableLiveData<Int>()
var categoryPositionLiveData = MutableLiveData<Pair<Int, Int>>()
var selectedCategoryName: String = ""
var selectedCategoryPosition: Int = 0
var selectedCategoryList = ArrayList<CategoryEntity>()
var entrance: String = ""
private var pageId = ""
var pageName = ""
private set
init {
getSidebars()
getCategoryDirectories()
fun init(entrance: String) {
this.entrance = entrance
}
@SuppressLint("CheckResult")
fun getSidebars() {
api.getSidebars(mCategoryId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BiResponse<SidebarsEntity>() {
override fun onSuccess(data: SidebarsEntity) {
sidebarsLiveData.postValue(data)
fun loadData(pageId: String, pageName: String) {
this.pageId = pageId
this.pageName = pageName
val sidebarsObservable = api.getSidebars(pageId)
val directoriesObservable = api.getCategoryDirectories(pageId)
.onErrorReturnItem(listOf())
sidebarsObservable.zipWith(directoriesObservable) { t1, t2 ->
t1 to t2
}.compose(singleToMain())
.subscribe(object : BiResponse<Pair<SidebarsEntity, List<CategoryEntity>>>() {
override fun onSuccess(data: Pair<SidebarsEntity, List<CategoryEntity>>) {
val (sidebarsData, directories) = data
directoriesLiveData.value = directories
sidebarsLiveData.value = sidebarsData
}
override fun onFailure(exception: Exception) {
super.onFailure(exception)
sidebarsLiveData.postValue(null)
}
})
}).let(compositeDisposable::add)
}
@SuppressLint("CheckResult")
fun getCategoryDirectories() {
api.getCategoryDirectories(mCategoryId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BiResponse<List<CategoryEntity>>() {
override fun onSuccess(data: List<CategoryEntity>) {
directories = ArrayList(data)
postCategoryDirectoryList()
fun clearSelectedTag() {
logClickReset("全部类别")
val filteredList = _selectedSubCategories.value
if (!filteredList.isNullOrEmpty()) {
_selectedSubCategories.value = mutableListOf()
val directories = directoriesLiveData.value ?: return
directories.forEach {
it.data?.forEach { child ->
child.selected = false
}
})
}
fun postSelectedCount() {
selectedCountLiveData.postValue(selectedCount)
}
fun postCategoryPosition(primaryIndex: Int, subIndex: Int) {
categoryPositionLiveData.postValue(Pair(primaryIndex, subIndex))
}
fun postCategoryDirectoryList() {
directoriesLiveData.postValue(directories)
}
fun resetDirectoryList() {
selectedCount = 0
selectedCategoryList.clear()
directories.forEach {
it.data?.forEach { entity ->
entity.selected = false
}
directoriesLiveData.value = directories
}
postSelectedCount()
postCategoryDirectoryList()
}
fun logAppearance() {
LogUtils.logCategoryV2AppearanceEvent(entrance, categoryTitle)
LogUtils.logCategoryV2AppearanceEvent(entrance, pageName)
}
fun logClickSide() {
LogUtils.logCategoryV2ClickSideEvent(entrance, categoryTitle, selectedCategoryName, selectedCategoryPosition)
val sidebars = sidebarsLiveData.value?.sidebars
val selectedPosition = selectedSidebarsPosition.value ?: 0
val selectedCategoryName = sidebars?.getOrNull(selectedPosition)?.name ?: ""
LogUtils.logCategoryV2ClickSideEvent(entrance, pageName, selectedCategoryName, selectedPosition)
}
fun logClickClassification(primaryIndex: Int, categoryName: String, position: Int) {
private fun logClickClassification(selected: SelectedTags) {
LogUtils.logCategoryV2ClickClassificationEvent(
entrance,
categoryTitle,
selectedCategoryName,
directories[primaryIndex].name,
categoryName,
primaryIndex,
position
pageName,
selectedSidebarsName,
selected.parentName,
selected.category.name ?: "",
selected.parentPosition,
selected.position
)
}
fun logClickClassificationDelete(primaryIndex: Int, categoryName: String, location: String) {
private fun logClickClassificationDelete(directoryName: String, categoryName: String, location: String) {
LogUtils.logCategoryV2ClickClassificationDeleteEvent(
entrance,
categoryTitle,
directories[primaryIndex].name,
pageName,
directoryName,
categoryName,
location
)
}
fun logClickDetermine() {
val categoryName = StringBuilder()
selectedCategoryList.forEachIndexed { index, s ->
categoryName.append(s.name)
if (index != selectedCategoryList.size - 1) {
categoryName.append("+")
}
}
LogUtils.logCategoryV2ClickDetermineEvent(entrance, categoryTitle, categoryName.toString())
val categoryNames = selectedSubCategories.value?.joinToString("+") { it.category.name ?: "" }
LogUtils.logCategoryV2ClickDetermineEvent(entrance, pageName, categoryNames ?: "")
}
fun logClickReset(location: String) {
val categoryName = StringBuilder()
selectedCategoryList.forEachIndexed { index, s ->
categoryName.append(s.name)
if (index != selectedCategoryList.size - 1) {
categoryName.append("+")
}
}
LogUtils.logCategoryV2ClickResetEvent(entrance, categoryTitle, categoryName.toString(), location)
val categoryName = selectedSubCategories.value?.joinToString("+") { it.category.name ?: "" }
LogUtils.logCategoryV2ClickResetEvent(entrance, pageName, categoryName ?: "", location)
}
class Factory(
private val categoryId: String,
private val categoryTitle: String
) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return CategoryV2ViewModel(
HaloApp.getInstance().application,
categoryId,
categoryTitle
) as T
private val _notifySubCategorySelected = MutableLiveData<Event<String>>()
val notifySubCategorySelected: LiveData<Event<String>> = _notifySubCategorySelected
private val _selectedSubCategories = MutableLiveData<List<SelectedTags>>()
val selectedSubCategories: LiveData<List<SelectedTags>> = _selectedSubCategories
fun addSubCategorySelected(selected: SelectedTags) {
val list = _selectedSubCategories.value
val newData = if (list == null) {
mutableListOf(selected)
} else {
list + selected
}
selected.category.selected = true
_selectedSubCategories.value = newData
// 当搜索条件发生变化时,侧边栏默认选中 “全部”
selectSidebarsPosition(0, false)
updateGameFiltered()
_notifySubCategorySelected.value = Event(selected.parentId)
logClickClassification(selected)
}
fun removeSubCategorySelected(parentId: String, categoryId: String, location: String) {
val list = _selectedSubCategories.value ?: return
val position = list.indexOfFirst {
it.parentId == parentId && categoryId == it.category.id
}
if (position != -1) {
val item = list[position]
item.category.selected = false
_selectedSubCategories.value = list - item
updateGameFiltered()
_notifySubCategorySelected.value = Event(parentId)
logClickClassificationDelete(item.parentName, item.category.name ?: "", location)
}
}
private val _selectedSidebarsPosition = MutableLiveData<Int>()
val selectedSidebarsPosition: LiveData<Int> = _selectedSidebarsPosition
val selectedSidebarsName: String
get() = sidebarsLiveData.value?.sidebars?.getOrNull(selectedSidebarsPosition.value ?: 0)?.name ?: ""
fun selectSidebarsPosition(position: Int, triggerSearch: Boolean = true) {
val oldPosition = _selectedSidebarsPosition.value ?: INVALID_POSITION
if (position != oldPosition) {
_selectedSidebarsPosition.value = position
// 如果是点击搜索而被动切换到 “全部” tab则这里不需要更新筛选条件
if (triggerSearch && position != 1) {
updateGameFiltered()
}
}
}
private val _gameFiltered = MutableLiveData<GameFiltered>()
val gameFiltered: LiveData<GameFiltered> = _gameFiltered
fun updateGameFiltered(
size: SubjectSettingEntity.Size? = null,
sortType: CategoryFilterView.SortType? = null
) {
val oldFiltered = _gameFiltered.value
val newSize = size ?: oldFiltered?.size ?: SubjectSettingEntity.Size()
val newSortType = sortType ?: oldFiltered?.sortType ?: CategoryFilterView.SortType.RECOMMENDED
val selectedSidebarPosition = selectedSidebarsPosition.value ?: 0
val categoryIds = selectedSubCategories.value?.joinToString("-") { it.category.id } ?: ""
val sidebarCategoryId =
sidebarsLiveData.value?.sidebars?.getOrNull(selectedSidebarPosition)?.categoryId ?: "all"
_gameFiltered.value = GameFiltered(newSize, newSortType, categoryIds, sidebarCategoryId)
}
override fun onCleared() {
super.onCleared()
compositeDisposable.clear()
}
companion object {
private const val INVALID_POSITION = -1
}
data class SelectedTags(
val parentId: String,
val parentName: String,
val parentPosition: Int,
val category: CategoryEntity,
val position: Int
)
data class GameFiltered(
val size: SubjectSettingEntity.Size = SubjectSettingEntity.Size(),
val sortType: CategoryFilterView.SortType = CategoryFilterView.SortType.RECOMMENDED,
val categoryIds: String = "",
val sidebarCategoryId: String = "全部"
)
}

View File

@ -0,0 +1,292 @@
package com.gh.gamecenter.category2
import android.content.Context
import android.graphics.Color
import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.blankj.utilcode.util.KeyboardUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.common.utils.toResString
import com.gh.gamecenter.common.view.BugFixedPopupWindow
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.databinding.PopSearchCategoryBinding
import com.gh.gamecenter.entity.CategoryEntity
import io.reactivex.Single
import io.reactivex.disposables.CompositeDisposable
import me.xdrop.fuzzywuzzy.FuzzySearch
class SearchCategoryPop(
height: Int,
private val isAutoRequestFocus: Boolean,
private val pageId: String,
private val pageName: String,
private val binding: PopSearchCategoryBinding
) : BugFixedPopupWindow(binding.root, ViewGroup.LayoutParams.MATCH_PARENT, height) {
private var listener: OnSearchCategoryListener? = null
private val handler = Handler(Looper.getMainLooper())
private var searchDataList: List<CategoryV2ViewModel.SelectedTags>? = null
private val compositeDisposable = CompositeDisposable()
private var isSearching = false
private val adapter by lazy {
CategoryDirectoryAdapter(object : OnSearchCategoryListener {
override fun isEnableSelected(): Boolean {
return listener?.isEnableSelected() ?: false
}
override fun onItemSelected(selected: CategoryV2ViewModel.SelectedTags) {
listener?.onItemSelected(selected)
}
override fun onItemRemoved(parentId: String, subCategoryId: String) {
listener?.onItemRemoved(parentId, subCategoryId)
}
override fun onResetSelected() {
listener?.onResetSelected()
}
override fun onSubmit() {
listener?.onSubmit()
}
})
}
private val resultAdapter by lazy {
SearchCategoryResultsAdapter {
SensorsBridge.logClassificationSearchReturnClick(
pageId,
pageName,
binding.searchView.searchKey,
it.category.name ?: ""
)
if (listener?.isEnableSelected() == true) {
clearSearchKey()
listener?.onItemSelected(it)
} else {
ToastUtils.toast(R.string.selected_category_tags_max_toast.toResString())
}
}
}
private val selectedTagAdapter by lazy {
SelectedTagsAdapter {
listener?.onItemRemoved(it.parentId, it.category.id)
}
}
init {
isOutsideTouchable = true
setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
isFocusable = true
inputMethodMode = INPUT_METHOD_NEEDED
softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING
initView()
}
private fun initView() {
binding.rvCategory.layoutManager = LinearLayoutManager(binding.root.context)
binding.rvCategory.adapter = adapter
binding.tvSelectedNumber.typeface =
Typeface.createFromAsset(binding.root.context.assets, Constants.DIN_FONT_PATH)
binding.root.setOnClickListener {
dismiss()
}
binding.clContent.setOnClickListener {
// 不需要具体实现,只是为了拦截 root 的点击事件
}
binding.tvReset.setOnClickListener {
listener?.onResetSelected()
}
binding.vSubmit.setOnClickListener {
dismiss()
listener?.onSubmit()
}
binding.searchView.addTextChangedListener {
handler.removeCallbacksAndMessages(null)
val key = it?.toString() ?: ""
if (key.isEmpty()) {
changeToSearching(false)
} else {
handler.postDelayed({
search(it?.toString() ?: "")
}, SEARCH_DELAY_DURATION)
}
}
if (!isAutoRequestFocus) {
binding.searchView.setEditTextOnFocusChangeListener { _, hasFocus ->
if (hasFocus) {
SensorsBridge.logClassificationSearch(pageId, pageName)
}
}
}
}
fun setData(data: List<CategoryEntity>) {
searchDataList = data
.asSequence()
.mapIndexed { index, parent ->
parent.data?.mapIndexed { childIndex, child ->
CategoryV2ViewModel.SelectedTags(parent.id, parent.name ?: "", index, child, childIndex)
} ?: listOf()
}
.flatten()
.toList()
adapter.setListData(data)
}
fun updateCategorySelected(selectedList: List<CategoryV2ViewModel.SelectedTags>?) {
val size = selectedList?.size ?: 0
binding.tvSelectedNumber.goneIf(size == 0) {
binding.tvSelectedNumber.text = "$size"
}
if (binding.rvSelected.adapter == null) {
binding.rvSelected.layoutManager =
LinearLayoutManager(binding.root.context, RecyclerView.HORIZONTAL, false)
binding.rvSelected.adapter = selectedTagAdapter
}
selectedTagAdapter.submitList(selectedList)
binding.rvSelected.goneIf(selectedList.isNullOrEmpty())
if (isSearching) {
search(binding.searchView.searchKey)
}
}
private fun search(key: String) {
Single.create {
val data = searchDataList?.filterNot { item -> item.category.selected } ?: emptyList()
val resultList = FuzzySearch.extractSorted(key, data, { item -> item.category.name ?: "" }, 1)
.map { item -> item.referent }
it.onSuccess(resultList)
}.compose(singleToMain())
.subscribe(object : BiResponse<List<CategoryV2ViewModel.SelectedTags>>() {
override fun onSuccess(data: List<CategoryV2ViewModel.SelectedTags>) {
val hasResult = data.isNotEmpty()
SensorsBridge.logClassificationSearchReturn(pageId, pageName, key, hasResult)
changeToSearching(true, key, data)
}
override fun onFailure(exception: Exception) {
SensorsBridge.logClassificationSearchReturn(pageId, pageName, key, false)
changeToSearching(true, key)
}
}).let(compositeDisposable::add)
}
private fun clearSearchKey() {
binding.searchView.clear()
}
private fun changeToSearching(
isSearching: Boolean,
key: String = "",
results: List<CategoryV2ViewModel.SelectedTags>? = null
) {
this.isSearching = isSearching
binding.rvCategory.goneIf(isSearching)
binding.rvResults.goneIf(results.isNullOrEmpty()) {
if (binding.rvResults.adapter == null) {
binding.rvResults.layoutManager = LinearLayoutManager(binding.rvResults.context)
binding.rvResults.adapter = resultAdapter
}
}
resultAdapter.setData(results ?: listOf(), key)
binding.reuseNoConnection.root.goneIf(!isSearching || !results.isNullOrEmpty()) {
binding.reuseNoConnection.reuseNoneDataTv.text = R.string.no_relevant_content_found.toResString()
binding.reuseNoConnection.reuseNoneDataDescTv.goneIf(false)
binding.reuseNoConnection.reuseNoneDataDescTv.text = R.string.try_a_different_search_term.toResString()
}
}
fun setOnSearchCategoryListener(listener: OnSearchCategoryListener) {
this.listener = listener
}
fun notifyItemSelectedChanged(parentId: String) {
adapter.notifyItemSelectedChanged(parentId)
}
override fun showAtLocation(parent: View?, gravity: Int, x: Int, y: Int) {
super.showAtLocation(parent, gravity, x, y)
if (isAutoRequestFocus) {
binding.searchView.requestFocus()
handler.removeCallbacksAndMessages(null)
// 在某些机型上延时一下才能弹起软键盘
handler.postDelayed({
KeyboardUtils.showSoftInput()
}, SHOW_SOFT_INPUT_DELAY)
}
}
override fun dismiss() {
super.dismiss()
clearSearchKey()
handler.removeCallbacksAndMessages(null)
compositeDisposable.clear()
}
companion object {
private const val SEARCH_DELAY_DURATION = 300L
private const val SHOW_SOFT_INPUT_DELAY = 200L
fun newInstance(
context: Context,
height: Int,
isAutoRequestFocus: Boolean,
pageId: String,
pageName: String
): SearchCategoryPop {
val inflater = LayoutInflater.from(context)
val binding = PopSearchCategoryBinding.inflate(inflater)
return SearchCategoryPop(height, isAutoRequestFocus, pageId, pageName, binding)
}
}
interface OnSearchCategoryListener {
fun isEnableSelected(): Boolean
fun onItemSelected(selected: CategoryV2ViewModel.SelectedTags)
fun onItemRemoved(parentId: String, subCategoryId: String)
fun onResetSelected()
fun onSubmit()
}
}

View File

@ -0,0 +1,57 @@
package com.gh.gamecenter.category2
import android.annotation.SuppressLint
import android.text.Spannable
import android.text.SpannableString
import android.text.style.ForegroundColorSpan
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.databinding.RecyclerSearchCategoryResultBinding
class SearchCategoryResultsAdapter(val clickListener: (CategoryV2ViewModel.SelectedTags) -> Unit) :
RecyclerView.Adapter<SearchCategoryResultsAdapter.ResultViewHolder>() {
private val dataList = arrayListOf<CategoryV2ViewModel.SelectedTags>()
private var key = ""
@SuppressLint("NotifyDataSetChanged")
fun setData(data: List<CategoryV2ViewModel.SelectedTags>, newKey: String) {
key = newKey
dataList.clear()
dataList.addAll(data)
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ResultViewHolder {
return ResultViewHolder(parent.toBinding())
}
override fun getItemCount() = dataList.size
override fun onBindViewHolder(holder: ResultViewHolder, position: Int) {
val item = dataList[position]
val text = item.category.name ?: ""
val spannableString = SpannableString(text)
val highlightColor = com.gh.gamecenter.common.R.color.text_theme.toColor(holder.itemView.context)
text.forEachIndexed { index, char ->
if (key.contains(char)) {
// 需要高亮
spannableString.setSpan(
ForegroundColorSpan(highlightColor),
index,
index + 1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}
holder.binding.root.text = spannableString
holder.itemView.setOnClickListener {
clickListener(item)
}
}
class ResultViewHolder(val binding: RecyclerSearchCategoryResultBinding) : RecyclerView.ViewHolder(binding.root) {
}
}

View File

@ -0,0 +1,48 @@
package com.gh.gamecenter.category2
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.databinding.RecyclerCategorySelectedTagBinding
class SelectedTagsAdapter(val click: (CategoryV2ViewModel.SelectedTags) -> Unit) :
ListAdapter<CategoryV2ViewModel.SelectedTags, SelectedTagsAdapter.TagsViewHolder>(
createDiffUtil()
) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TagsViewHolder {
return TagsViewHolder(parent.toBinding())
}
override fun onBindViewHolder(holder: TagsViewHolder, position: Int) {
val item = getItem(position)
holder.binding.root.setText(item.category.name ?: "")
holder.itemView.setOnClickListener {
click(item)
}
}
companion object {
private fun createDiffUtil() = object : DiffUtil.ItemCallback<CategoryV2ViewModel.SelectedTags>() {
override fun areItemsTheSame(
oldItem: CategoryV2ViewModel.SelectedTags,
newItem: CategoryV2ViewModel.SelectedTags
): Boolean {
return oldItem.category.id == newItem.category.id
}
override fun areContentsTheSame(
oldItem: CategoryV2ViewModel.SelectedTags,
newItem: CategoryV2ViewModel.SelectedTags
): Boolean {
return oldItem == newItem
}
}
}
class TagsViewHolder(val binding: RecyclerCategorySelectedTagBinding) : RecyclerView.ViewHolder(binding.root)
}

View File

@ -1,84 +1,94 @@
package com.gh.gamecenter.category2
import android.content.Context
import android.view.View
import android.annotation.SuppressLint
import android.view.ViewGroup
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toColor
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.toDrawable
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.databinding.SubCategoryItemBinding
import com.gh.gamecenter.entity.CategoryEntity
import com.lightgame.adapter.BaseRecyclerAdapter
class SubCategoryAdapter(
context: Context,
private val mViewModel: CategoryV2ViewModel,
private val mList: List<CategoryEntity>,
private val mPrimaryIndex: Int
) : BaseRecyclerAdapter<SubCategoryAdapter.SubCategoryItemViewHolder>(context) {
private val listener: SearchCategoryPop.OnSearchCategoryListener,
) : RecyclerView.Adapter<SubCategoryAdapter.SubCategoryItemViewHolder>() {
override fun getItemCount() = mList.size
private lateinit var itemData: CategoryEntity
private val data: List<CategoryEntity>
get() = itemData.data ?: emptyList()
private var directoryPosition = 0
@SuppressLint("NotifyDataSetChanged")
fun setData(directoryPosition: Int, newItem: CategoryEntity) {
this.directoryPosition = directoryPosition
itemData = newItem
notifyDataSetChanged()
}
override fun getItemCount() = data.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
SubCategoryItemViewHolder(SubCategoryItemBinding.inflate(mLayoutInflater))
SubCategoryItemViewHolder(parent.toBinding())
override fun onBindViewHolder(holder: SubCategoryItemViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.isEmpty()) {
super.onBindViewHolder(holder, position, payloads)
} else {
val item = data[position]
updateSelectedState(item.selected, holder.binding)
}
}
override fun onBindViewHolder(holder: SubCategoryItemViewHolder, position: Int) {
holder.binding.run {
val categoryEntity = mList[position]
name.text = categoryEntity.name
val categoryEntity = data[position]
tvName.text = categoryEntity.name
recommendIv.goneIf(categoryEntity.recommend == false)
if (categoryEntity.selected) {
selectedIv.visibility = View.VISIBLE
container.background = R.drawable.bg_category_selected.toDrawable(mContext)
name.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(mContext))
} else {
selectedIv.visibility = View.GONE
container.background = com.gh.gamecenter.common.R.drawable.bg_shape_space_radius_8.toDrawable(mContext)
name.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(mContext))
}
updateSelectedState(categoryEntity.selected, this)
root.setOnClickListener {
when {
mViewModel.selectedCount >= 5 && !categoryEntity.selected -> ToastUtils.toast("最多只能选择5个类别")
!categoryEntity.selected && !listener.isEnableSelected() -> {
ToastUtils.toast(R.string.selected_category_tags_max_toast.toResString())
}
categoryEntity.selected -> {
categoryEntity.selected = false
selectedIv.visibility = View.GONE
container.background = com.gh.gamecenter.common.R.drawable.bg_shape_space_radius_8.toDrawable(mContext)
name.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(mContext))
mViewModel.run {
if (selectedCount > 0) {
selectedCount--
postSelectedCount()
postCategoryPosition(mPrimaryIndex, position)
logClickClassificationDelete(mPrimaryIndex, categoryEntity.name ?: "", "全部类别")
}
}
listener.onItemRemoved(itemData.id, categoryEntity.id)
}
!categoryEntity.selected -> {
categoryEntity.selected = true
categoryEntity.primaryIndex = mPrimaryIndex
selectedIv.visibility = View.VISIBLE
container.background = R.drawable.bg_category_selected.toDrawable(mContext)
name.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(mContext))
mViewModel.run {
if (selectedCount < 5) {
selectedCount++
logClickClassification(mPrimaryIndex, categoryEntity.name ?: "", position)
postSelectedCount()
postCategoryPosition(mPrimaryIndex, position)
}
}
listener.onItemSelected(
CategoryV2ViewModel.SelectedTags(
itemData.id,
itemData.name ?: "",
directoryPosition,
categoryEntity,
position
)
)
}
}
}
}
}
class SubCategoryItemViewHolder(val binding: SubCategoryItemBinding) : BaseRecyclerViewHolder<Any>(binding.root)
private fun updateSelectedState(isSelected: Boolean, binding: SubCategoryItemBinding) {
with(binding) {
val context = root.context
if (isSelected) {
container.background = R.drawable.bg_category_selected.toDrawable(context)
tvName.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
} else {
container.background = com.gh.gamecenter.common.R.drawable.bg_shape_space_radius_8.toDrawable(context)
tvName.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
}
}
}
class SubCategoryItemViewHolder(val binding: SubCategoryItemBinding) : ViewHolder(binding.root)
}

View File

@ -2,16 +2,22 @@ package com.gh.gamecenter.entity
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
@Parcelize
data class CategoryEntity(
@SerializedName("_id")
var id: String? = "",
private var _id: String? = null,
var icon: String? = "",
var name: String? = "",
var recommend: Boolean? = false,
var data: List<CategoryEntity>? = null,
var selected: Boolean = false,
var primaryIndex: Int = -1
) : Parcelable
var data: List<CategoryEntity>? = null
) : Parcelable {
val id: String
get() = _id ?: ""
@IgnoredOnParcel
var selected: Boolean = false
}