From cc0683c3c78ba67b318526d6096faccfc3e35ac7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E6=99=A8?= Date: Mon, 24 Mar 2025 15:20:27 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=88=86=E7=B1=BB=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E6=90=9C=E7=B4=A2=E5=8A=9F=E8=83=BD=E2=80=94=E5=AE=A2=E6=88=B7?= =?UTF-8?q?=E7=AB=AF=20https://jira.shanqu.cc/browse/GHZSCY-7512?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/build.gradle | 2 + .../java/com/gh/common/util/DirectUtils.kt | 4 +- .../gh/common/util/ViewPagerFragmentHelper.kt | 4 +- .../com/gh/common/view/CategoryFilterView.kt | 337 ++++++----- .../category2/CategoryDirectoryAdapter.kt | 119 ++-- .../category2/CategoryV2Activity.kt | 4 +- .../gamecenter/category2/CategoryV2Adapter.kt | 29 +- .../category2/CategoryV2Fragment.kt | 562 +++++++----------- .../category2/CategoryV2ListAdapter.kt | 46 +- .../category2/CategoryV2ListFragment.kt | 217 ++----- .../category2/CategoryV2ListViewModel.kt | 44 +- .../category2/CategoryV2ViewModel.kt | 260 ++++---- .../gamecenter/category2/SearchCategoryPop.kt | 292 +++++++++ .../category2/SearchCategoryResultsAdapter.kt | 57 ++ .../category2/SelectedTagsAdapter.kt | 48 ++ .../category2/SubCategoryAdapter.kt | 116 ++-- .../gh/gamecenter/entity/CategoryEntity.kt | 16 +- .../drawable-xxhdpi/ic_catalog_selected.webp | Bin 860 -> 0 bytes .../ic_category_recommend.webp | Bin 1054 -> 886 bytes .../drawable-xxhdpi/ic_category_selected.webp | Bin 1200 -> 0 bytes .../drawable-xxxhdpi/ic_catalog_selected.webp | Bin 990 -> 0 bytes .../drawable-xxxhdpi/pic_category_guide.webp | Bin 21384 -> 47440 bytes .../res/drawable/bg_category_selected.xml | 2 +- .../res/drawable/bg_category_selected_tag.xml | 7 + .../main/res/drawable/bg_category_sidebar.xml | 7 + .../res/drawable/bg_more_category_default.xml | 7 + .../drawable/bg_more_category_filtered.xml | 10 + .../drawable/bg_search_category_number.xml | 5 + app/src/main/res/drawable/ic_arrow_down.xml | 13 + app/src/main/res/drawable/ic_arrow_up.xml | 13 + .../ic_basic_checkmark_circle_fill.xml | 21 + .../res/drawable/ic_basic_classification.xml | 14 + app/src/main/res/drawable/ic_clear.xml | 10 + app/src/main/res/drawable/ic_x_size_8.xml | 14 + ...shape_category_filter_size_item_normal.xml | 5 + ...ape_category_filter_size_item_selected.xml | 5 + .../main/res/drawable/shape_filtered_left.xml | 6 + .../res/drawable/shape_filtered_right.xml | 6 + .../res/layout/category_directory_item.xml | 9 +- app/src/main/res/layout/category_v2_item.xml | 31 +- app/src/main/res/layout/fragment_category.xml | 178 +++--- .../res/layout/fragment_category_list.xml | 46 +- .../res/layout/layout_category_filter.xml | 72 ++- .../layout/layout_category_filter_size.xml | 9 + .../res/layout/pop_category_filter_size.xml | 19 + .../res/layout/pop_category_filter_type.xml | 48 ++ .../main/res/layout/pop_search_category.xml | 134 +++++ .../layout/recycler_category_selected_tag.xml | 9 + .../recycler_search_category_result.xml | 12 + app/src/main/res/layout/sub_category_item.xml | 30 +- app/src/main/res/values-night/styles.xml | 19 - app/src/main/res/values-zh-rTW/strings.xml | 10 + app/src/main/res/values/strings.xml | 10 + app/src/main/res/values/styles.xml | 19 - dependencies.gradle | 1 + .../gamecenter/common/constant/Constants.java | 3 - .../common/constant/EntranceConsts.java | 8 + .../gamecenter/common/utils/SensorsBridge.kt | 48 ++ .../com/gh/gamecenter/common/view/GhSearch.kt | 111 ++++ .../bg_shape_ui_surface_bottom_radius_12.xml | 8 + .../bg_shape_white_alpha_20_radius_999.xml | 5 + .../src/main/res/drawable/ic_clear.xml | 10 + .../src/main/res/drawable/ic_search.xml | 10 + .../src/main/res/layout/layout_gh_search.xml | 64 ++ .../src/main/res/values-night/colors.xml | 3 + module_common/src/main/res/values/attrs.xml | 12 + module_common/src/main/res/values/colors.xml | 3 + 67 files changed, 2142 insertions(+), 1101 deletions(-) create mode 100644 app/src/main/java/com/gh/gamecenter/category2/SearchCategoryPop.kt create mode 100644 app/src/main/java/com/gh/gamecenter/category2/SearchCategoryResultsAdapter.kt create mode 100644 app/src/main/java/com/gh/gamecenter/category2/SelectedTagsAdapter.kt delete mode 100644 app/src/main/res/drawable-xxhdpi/ic_catalog_selected.webp delete mode 100644 app/src/main/res/drawable-xxhdpi/ic_category_selected.webp delete mode 100644 app/src/main/res/drawable-xxxhdpi/ic_catalog_selected.webp create mode 100644 app/src/main/res/drawable/bg_category_selected_tag.xml create mode 100644 app/src/main/res/drawable/bg_category_sidebar.xml create mode 100644 app/src/main/res/drawable/bg_more_category_default.xml create mode 100644 app/src/main/res/drawable/bg_more_category_filtered.xml create mode 100644 app/src/main/res/drawable/bg_search_category_number.xml create mode 100644 app/src/main/res/drawable/ic_arrow_down.xml create mode 100644 app/src/main/res/drawable/ic_arrow_up.xml create mode 100644 app/src/main/res/drawable/ic_basic_checkmark_circle_fill.xml create mode 100644 app/src/main/res/drawable/ic_basic_classification.xml create mode 100644 app/src/main/res/drawable/ic_clear.xml create mode 100644 app/src/main/res/drawable/ic_x_size_8.xml create mode 100644 app/src/main/res/drawable/shape_category_filter_size_item_normal.xml create mode 100644 app/src/main/res/drawable/shape_category_filter_size_item_selected.xml create mode 100644 app/src/main/res/drawable/shape_filtered_left.xml create mode 100644 app/src/main/res/drawable/shape_filtered_right.xml create mode 100644 app/src/main/res/layout/layout_category_filter_size.xml create mode 100644 app/src/main/res/layout/pop_category_filter_size.xml create mode 100644 app/src/main/res/layout/pop_category_filter_type.xml create mode 100644 app/src/main/res/layout/pop_search_category.xml create mode 100644 app/src/main/res/layout/recycler_category_selected_tag.xml create mode 100644 app/src/main/res/layout/recycler_search_category_result.xml create mode 100644 module_common/src/main/java/com/gh/gamecenter/common/view/GhSearch.kt create mode 100644 module_common/src/main/res/drawable/bg_shape_ui_surface_bottom_radius_12.xml create mode 100644 module_common/src/main/res/drawable/bg_shape_white_alpha_20_radius_999.xml create mode 100644 module_common/src/main/res/drawable/ic_clear.xml create mode 100644 module_common/src/main/res/drawable/ic_search.xml create mode 100644 module_common/src/main/res/layout/layout_gh_search.xml diff --git a/app/build.gradle b/app/build.gradle index c166ea605e..3dd73aa451 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -539,6 +539,8 @@ dependencies { if(!gradle.ext.excludeOptionalModules || gradle.ext.enableWechatPay){ implementation(project(":feature:wechat_pay")) } + + implementation "me.xdrop:fuzzywuzzy:${fuzzyVersion}" } File propFile = file('sign.properties') diff --git a/app/src/main/java/com/gh/common/util/DirectUtils.kt b/app/src/main/java/com/gh/common/util/DirectUtils.kt index ed8927b5a7..7ac7efa3a3 100644 --- a/app/src/main/java/com/gh/common/util/DirectUtils.kt +++ b/app/src/main/java/com/gh/common/util/DirectUtils.kt @@ -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, diff --git a/app/src/main/java/com/gh/common/util/ViewPagerFragmentHelper.kt b/app/src/main/java/com/gh/common/util/ViewPagerFragmentHelper.kt index 5b6fe53fb9..c8ca215ce3 100644 --- a/app/src/main/java/com/gh/common/util/ViewPagerFragmentHelper.kt +++ b/app/src/main/java/com/gh/common/util/ViewPagerFragmentHelper.kt @@ -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) } // 通用内容合集详情页 diff --git a/app/src/main/java/com/gh/common/view/CategoryFilterView.kt b/app/src/main/java/com/gh/common/view/CategoryFilterView.kt index c16c4c31ce..dfff61fdb6 100644 --- a/app/src/main/java/com/gh/common/view/CategoryFilterView.kt +++ b/app/src/main/java/com/gh/common/view/CategoryFilterView.kt @@ -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? = 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(R.id.flexbox) - val backgroundView = layout.findViewById(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(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(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(R.id.flexbox) - val backgroundView = layout.findViewById(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(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 { - return arrayListOf().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() { + + private val dataList = arrayListOf() + private var selectedPosition = 0 + + @SuppressLint("NotifyDataSetChanged") + fun setData(data: List, 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 } } \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/category2/CategoryDirectoryAdapter.kt b/app/src/main/java/com/gh/gamecenter/category2/CategoryDirectoryAdapter.kt index 6a48be497c..1a9a85b141 100644 --- a/app/src/main/java/com/gh/gamecenter/category2/CategoryDirectoryAdapter.kt +++ b/app/src/main/java/com/gh/gamecenter/category2/CategoryDirectoryAdapter.kt @@ -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 -) : BaseRecyclerAdapter(context) { + private val listener: SearchCategoryPop.OnSearchCategoryListener +) : RecyclerView.Adapter() { - val width = mContext.resources.displayMetrics.widthPixels * 260 / 360 + private val data = arrayListOf() - fun setListData(list: List) { - mList = list + @SuppressLint("NotifyDataSetChanged") + fun setListData(newData: List) { + 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) { + 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(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, "") + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/category2/CategoryV2Activity.kt b/app/src/main/java/com/gh/gamecenter/category2/CategoryV2Activity.kt index bd1bd83cb2..d23fef9b7d 100644 --- a/app/src/main/java/com/gh/gamecenter/category2/CategoryV2Activity.kt +++ b/app/src/main/java/com/gh/gamecenter/category2/CategoryV2Activity.kt @@ -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) } diff --git a/app/src/main/java/com/gh/gamecenter/category2/CategoryV2Adapter.kt b/app/src/main/java/com/gh/gamecenter/category2/CategoryV2Adapter.kt index 7d85935313..4f4ca2486b 100644 --- a/app/src/main/java/com/gh/gamecenter/category2/CategoryV2Adapter.kt +++ b/app/src/main/java/com/gh/gamecenter/category2/CategoryV2Adapter.kt @@ -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 ) : BaseRecyclerAdapter(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(binding.root) } \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/category2/CategoryV2Fragment.kt b/app/src/main/java/com/gh/gamecenter/category2/CategoryV2Fragment.kt index 81ccaad4eb..10304c9cc7 100644 --- a/app/src/main/java/com/gh/gamecenter/category2/CategoryV2Fragment.kt +++ b/app/src/main/java/com/gh/gamecenter/category2/CategoryV2Fragment.kt @@ -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() + private var mHomeViewModel: SearchToolbarTabWrapperViewModel? = null private var mEntity: SidebarsEntity? = null - private var mSpecialCatalogFragment: SpecialCatalogFragment? = null - private var mCategoryV2ListFragment: CategoryV2ListFragment? = null private var mLastPageDataMap: HashMap? = 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) { - 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( + 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( + 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( - 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( - 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( - 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( - 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( - 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( - 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 + } } \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/category2/CategoryV2ListAdapter.kt b/app/src/main/java/com/gh/gamecenter/category2/CategoryV2ListAdapter.kt index 42d704d402..5efd6a914a 100644 --- a/app/src/main/java/com/gh/gamecenter/category2/CategoryV2ListAdapter.kt +++ b/app/src/main/java/com/gh/gamecenter/category2/CategoryV2ListAdapter.kt @@ -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() 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 } diff --git a/app/src/main/java/com/gh/gamecenter/category2/CategoryV2ListFragment.kt b/app/src/main/java/com/gh/gamecenter/category2/CategoryV2ListFragment.kt index 22f56f0a20..27dd593a52 100644 --- a/app/src/main/java/com/gh/gamecenter/category2/CategoryV2ListFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/category2/CategoryV2ListFragment.kt @@ -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() { - 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( + ownerProducer = { parentFragment ?: this } + ) + + override fun isAutomaticLoad(): Boolean { + return false + } + private var mAdapter: CategoryV2ListAdapter? = null - private var mSelectedViewList = ArrayList() private var mBinding: FragmentCategoryListBinding? = null - private var mCategoryViewModel: CategoryV2ViewModel? = null private var mLastPageDataMap: HashMap? = null private val mDataWatcher = object : DataWatcher() { override fun onDataChanged(downloadEntity: DownloadEntity) { @@ -52,6 +59,12 @@ class CategoryV2ListFragment : ListFragment } } + 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 override fun provideListViewModel() = viewModelProvider( CategoryV2ListViewModel.Factory( - mCategoryId, - mSubCategoryId, + pageId, arguments?.getParcelableArrayList(EntranceConsts.KEY_EXPOSURE_SOURCE_LIST) ) ) @@ -70,12 +82,7 @@ class CategoryV2ListFragment : ListFragment ?: CategoryV2ListAdapter( requireContext(), mListViewModel ?: provideListViewModel(), - mCategoryViewModel ?: viewModelProviderFromParent( - CategoryV2ViewModel.Factory( - mCategoryId, - mCategoryTitle - ), mCategoryId - ), + parentViewModel, mEntrance, mLastPageDataMap ).apply { mAdapter = this } @@ -83,12 +90,10 @@ class CategoryV2ListFragment : ListFragment 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 - 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 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 .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) { + 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 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 } } - 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 } } - fun openDirectoryLayout() { - (parentFragment as? CategoryV2Fragment)?.openDirectoryLayout() - } - override fun onDarkModeChanged() { super.onDarkModeChanged() mBinding?.filterContainer?.run { diff --git a/app/src/main/java/com/gh/gamecenter/category2/CategoryV2ListViewModel.kt b/app/src/main/java/com/gh/gamecenter/category2/CategoryV2ListViewModel.kt index a1fbcb2c61..4a397d8cc5 100644 --- a/app/src/main/java/com/gh/gamecenter/category2/CategoryV2ListViewModel.kt +++ b/app/src/main/java/com/gh/gamecenter/category2/CategoryV2ListViewModel.kt @@ -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? ) : ListViewModel(application) { val refresh = MutableLiveData() - var sortType = CategoryFilterView.SortType.RECOMMENDED - var sortSize = SubjectSettingEntity.Size() + var gameFiltered = CategoryV2ViewModel.GameFiltered() + private set override fun provideDataObservable(page: Int): Observable>? = 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?) : + class Factory(val categoryId: String, val exposureSourceList: List?) : ViewModelProvider.NewInstanceFactory() { override fun create(modelClass: Class): T { return CategoryV2ListViewModel( HaloApp.getInstance().application, categoryId, - categoryIds, exposureSourceList ) as T } diff --git a/app/src/main/java/com/gh/gamecenter/category2/CategoryV2ViewModel.kt b/app/src/main/java/com/gh/gamecenter/category2/CategoryV2ViewModel.kt index ba6f329bd9..721a4d9723 100644 --- a/app/src/main/java/com/gh/gamecenter/category2/CategoryV2ViewModel.kt +++ b/app/src/main/java/com/gh/gamecenter/category2/CategoryV2ViewModel.kt @@ -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() - var directories = ArrayList() + var sidebarsLiveData = MutableLiveData() var directoriesLiveData = MutableLiveData>() - var selectedCount = 0 - var selectedCountLiveData = MutableLiveData() - var categoryPositionLiveData = MutableLiveData>() - var selectedCategoryName: String = "" - var selectedCategoryPosition: Int = 0 - var selectedCategoryList = ArrayList() 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() { - 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>>() { + override fun onSuccess(data: Pair>) { + 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>() { - override fun onSuccess(data: List) { - 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 create(modelClass: Class): T { - return CategoryV2ViewModel( - HaloApp.getInstance().application, - categoryId, - categoryTitle - ) as T + private val _notifySubCategorySelected = MutableLiveData>() + val notifySubCategorySelected: LiveData> = _notifySubCategorySelected + + private val _selectedSubCategories = MutableLiveData>() + val selectedSubCategories: LiveData> = _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() + val selectedSidebarsPosition: LiveData = _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() + val gameFiltered: LiveData = _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 = "全部" + ) } \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/category2/SearchCategoryPop.kt b/app/src/main/java/com/gh/gamecenter/category2/SearchCategoryPop.kt new file mode 100644 index 0000000000..7383f26d51 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/category2/SearchCategoryPop.kt @@ -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? = 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) { + 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?) { + 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>() { + override fun onSuccess(data: List) { + 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? = 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() + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/category2/SearchCategoryResultsAdapter.kt b/app/src/main/java/com/gh/gamecenter/category2/SearchCategoryResultsAdapter.kt new file mode 100644 index 0000000000..e9253b7afb --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/category2/SearchCategoryResultsAdapter.kt @@ -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() { + private val dataList = arrayListOf() + private var key = "" + + @SuppressLint("NotifyDataSetChanged") + fun setData(data: List, 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) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/category2/SelectedTagsAdapter.kt b/app/src/main/java/com/gh/gamecenter/category2/SelectedTagsAdapter.kt new file mode 100644 index 0000000000..28abaad805 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/category2/SelectedTagsAdapter.kt @@ -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( + 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() { + 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) +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/category2/SubCategoryAdapter.kt b/app/src/main/java/com/gh/gamecenter/category2/SubCategoryAdapter.kt index 848f7bb6c7..2cafb599ad 100644 --- a/app/src/main/java/com/gh/gamecenter/category2/SubCategoryAdapter.kt +++ b/app/src/main/java/com/gh/gamecenter/category2/SubCategoryAdapter.kt @@ -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, - private val mPrimaryIndex: Int -) : BaseRecyclerAdapter(context) { + private val listener: SearchCategoryPop.OnSearchCategoryListener, +) : RecyclerView.Adapter() { - override fun getItemCount() = mList.size + private lateinit var itemData: CategoryEntity + private val data: List + 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) { + 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(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) } \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/entity/CategoryEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/CategoryEntity.kt index 0d0079753d..dcb371cb92 100644 --- a/app/src/main/java/com/gh/gamecenter/entity/CategoryEntity.kt +++ b/app/src/main/java/com/gh/gamecenter/entity/CategoryEntity.kt @@ -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? = null, - var selected: Boolean = false, - var primaryIndex: Int = -1 -) : Parcelable \ No newline at end of file + var data: List? = null +) : Parcelable { + + val id: String + get() = _id ?: "" + + @IgnoredOnParcel + var selected: Boolean = false +} \ No newline at end of file diff --git a/app/src/main/res/drawable-xxhdpi/ic_catalog_selected.webp b/app/src/main/res/drawable-xxhdpi/ic_catalog_selected.webp deleted file mode 100644 index b2cab258b8bc2ed446168860d18deecd9ca1142c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 860 zcmV-i1Ec&>Nk&Fg0{{S5MM6+kP&il$0000G0000f000pH06|PpNcaE%00H0Ppp7I) zI(N@p_{1OZgNTSc&=6X5Q`v@ix2u!WXu0a1ylkB_;rCobcj+K9x%~rTB-^H~{cPK| zZ5!YIJO41H^Am&>k z`I=-njz~X5MZ$ZeFD$S{ONhw-vux1E4Yc%j+NHWVmk}i1@hZ zx0=1#9l1{~nzqI#x2mOqVWMhk0=9XeW@H6kWBXIySH}c`g_EpH3B)~}R~c_zFGvjy zZeM)g55q7>cUEV+hj-%ralfd$K&>s9Nau2yM9>ER7*3CFC4Xt7lT{D8qZ|E385;03HTp!mNCdL2&{{Nxv{a|lg#sS&Mu^J+r5dUG(X*IYG z(D?MY^48a?v4^qh!&&}3cxm>xGC|{%Ifj9T1nXb!VhlSQP}e2@4dtUfBA+Mml9+h& zbVTGxB1IFSzxiLjb4LauFhS9c?A{Bnk6-ORe|zB#@2u-<`UC8%@c;IM*th-{E}S8s z^;tK5v;QNT3ZX1dmT*&$uLqo`iumrI^4KmLXjjXE{-mN5E>ChbEA*fS_a={3jQ9k^ zZI%db?e-M-fb=winy*pQpUl<0|1eO%JnntjCis>5dz6ztyL7f7nP+tIVd7?_k4#+` zHv4hP+!akT`t|u-Py7;f@0c32@BSVBoLc{4ZGPGrqybWpI8=-_zy8!*3D*DV&H9WH zjCK>UpyW;X{{7j1{h=S5mOeNQ9*y089Sd@!82^iY{P*_cvvlM5?$EA51=q8BEBJfU z3Nh6n!CM%&>qg}1xrsZJ{;&Tif6is#6}+4FOl^f6D}Vo6fBJNqw-gM)0Zc*}(iPP= mZ{RqDX!Jv6vpQxRqi;J=?acSNs3QL)flFsmC`E}*= z?$Tnz0!Of>13;{>Hg#HnyW;c&z6E+oz^jlm<78T~HTZlXw9rEYVnZW`utNct#bCBj zdSb27jvbO%k@Nym#pyqAx?y$$OY)L40yt`%i32$uo4Mu0I4Uw2cN)vfON2eZ;sn{b}{Gy(u@9E{h~$Hz&hK}juNe6 zv1&w-ZMt9c#eb{8-#(Q|O{h}>n)ZG)8rqYEsPy*V zM^mjvvU`n3gay|yz=8)rSY=zpGADhk{+156X@4gG=w_s;{)NhEjkWS}ztFoN`p$G$ zIcX0tfA~60lVnW@xN!+Mc3uTn^X9KR_&lem-)M`!J9LTSP1MiM=g>nOZs>S7LIwUS zM~SWjFaPC`zqnJT=ih}CcVX}s3&V~W(f?CC=GC)FZ|pX*aL7P$LGJ#Z}*Rso8N9r_SAhYS(m1xhCv!_w?Tf&M+@%yG6?-hCU~fu;`plg-3pr M^&gLOpa1{>0O)?2$N&HU literal 1054 zcmV+(1mXKqNk&E%1ONb6MM6+kP&il$0000G0000l001KZ06|PpNW}mE00CFQxQ!&m z!L3?`gC9pkgeVK}nI<~hpXwLCIHL}J4*W!R{ z^}#g-;A$Ljl_r>uK32ihCOR&^HRqv)o6mi=zDiwN!EFFmP&gne0{{SU69An7Dlh;f z06sAiheDzu3yU%k0fJiqF3i23#p|DnzlDDwlyB~PrYBRgPE~RMd4Ty+ACp$GK%uFbVuf zW23~kv92o^h*HtP+4*a(`8BCk%U4N$a z&<6!p-<$L_@y=skPHsP9qZF7W=3ZOLLW}IFiJP`dSfH-?;oVlgQW_{%0I=;Z9SUHx zfp1#c$DePDq0nDEpqbX)kKVy{qA7@C1>0-KcleSU8PjaAJ&L56%kQ{a)+I|sbM!zj zODR*!tsR@yWI0|@R(f(H5Fx?!XX=HA{C2Y8`zLLdQnf0C&%}oPLQv=KV2tF7wXd zJ9K<$4{2TOdtu=FqwOo5l;Sa^Ek3hHj~73IuLDmZ8ImUs9^dZaz@xS2@v4FT5k`Yd zp^q2WJ5iLphDt|4Bq%q8OtEvdt+`hGOv#miV7gf!VYg(4QIJ1E`kxMMW9FT0#Qx|9 zOTeQcy^EYv>AanlaTuw=$AN|X8m2j7SKouuA?gqkvze`8 z&_j%Q6sL~iB=itJFQQkskLX`8VOB5#My-pEPN0Fse0}DW)KnTZwkVvy6`S)d zf7@r~cE9#N&9474Pb>@7tpAKP&)R?WqJ3YO;q-aV1GK-FV-{6(_UX-;>vPb-2jKz7 YRB+=N=Q{wnckKT$E<61sVrxzS0Im%N+W-In diff --git a/app/src/main/res/drawable-xxhdpi/ic_category_selected.webp b/app/src/main/res/drawable-xxhdpi/ic_category_selected.webp deleted file mode 100644 index 7146e44db5611abb7e29df0f0289d7ebb7d20968..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1200 zcmV;h1W)@?Nk&Gf1ONb6MM6+kP&il$0000G0000x001ul06|PpNMiv200DR7plu{c zT37Y{$Di=*afpcAtlR&Y2~r+InH$%B*~_&eztA< zz3>0NZQHhI{|opL=bo7jMD%|GdY{Db>*;iDEEBLmz5c-9|C_0*R|)YQE<|)V;!=To z?uQNio-}O=u=GFJ5dBSi6oB$MAEJY#3Zi?A=q9fM-^wo^dd#W7+Wq81H%S$samR=D z-AYL9@u3-;5}b>FY<`+*unxU)p)>{I8xbJunY^Y9UQP z@kkOPTaw^d{uTsl>sb&`yp@FHjwCoHegwfX{3Zw}UrIt~QxaUW--2N3y$J#e$C41< zkOceEw;)*B&w_x$l_Vs#CBZ)NBM6qk7ePSjLK1=-l3-r|09H^qARYq%0FV>_odGI5 z05AYPF&2qKA|W9Z3a&UH0|d4JX5OH`0Q>;hWau1Xo0s1_JI~AXfbjtS$@aOz1NH0m zcZ37`fBD|94?qtx|Lxzce*u5CAGO~ApReEl|9|g2S5cJP<(eHI&>m|1{Btq#$NaBw z58{8zzux~`>`VHq>=*hjYN0uBYRe;7A|cO8@4P3^$JKw^Oa=iOY-f@g1ip+JBbWX~ zUV~kq(x7{DcAxQrsE)sr3~S+j08e3&{{{xOGr>@^NhgXL%9W;ER-{3d_t zAXFea^TK%mjA4iY;a87dgA9bQ^wyI45paOM4UF?|dKvIqIuw&(Cx_J&Tb7%ILw^cV zJyMh_Oa3QHYcJ3;{)x7igP$Du4+t%NXsXZiE!B;m4b**zDxQ5h1)`hruf{Xq%Vl?R z4}tvVM5EwBCZ3;0Pi$luukQ9?&%ThQxrvw8)|#KPyY)Md8Xg{RG0xuehSAe)FD`V7 z>5^=M$hm~xFQAtxacF`@YWrvhn;XygvKNAj?ajT;#|Dv*b3C)d&X-ry{ukO2kMg{f z%G?5uE5|C=rV#ni*@g(UE@B>9@7PLD|7-H!kA-CEj+T^mKiq0v1+(}6xL9mU&X~?` zzbg$anXqLeiCglK6I?Jy>$yZE4oPY|bK6qJtHJLJO^x}L;UDMJD{ah4#ei2C>P@9#uI~Uq_eg~YEA;-x zIVT*po<~930E6>%MVA6V|Ebu>AjqR!zvMT-W50DA+i?Z#Nxf)^g#Y&}aI-G1KamhBd3@$A6!Z)$ OYi7to9^?Vw00005x=iu_ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_catalog_selected.webp b/app/src/main/res/drawable-xxxhdpi/ic_catalog_selected.webp deleted file mode 100644 index 88de08ec0c92fefd69cb113ee0e860da8c69f4fd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 990 zcmV<410noUNk&H20{{S5MM6+kP&il$0000G0000t000*N06|PpNEZPB00Ae%plu^b zmt=qbjE_V_kb9v2Hnz$nV7o+Fckqxeg$j+VACi$cXwgK z<#0Isa5#5&&Hw)dEa~ZUME@rM*0t^|Q>(x1%qMh}{(0Ww1WWkuy;hw1VupX;Y$Qtw z6@N{ljGw6Zp`tt@F>4cJNh9%uQl((tE5egQYLehV`smWY(Vq{HJk~4O7>MN~C&hkP zMCL@T1U(Mo^gFg4uv+}`@z(^|B_PIm^Sz~*n~PL8Q6So1oPh*%X}BmO8Msn+F?ura zeZ@tn_(7yCF~4^Q2;OVWr*i<}E!TZbj|mY2LYE?%Lf%o6~*W*TG_{!?A~C zp)nXVim9j5hz2%709H^qAeaIG0B{iiodGI102ly1DG-K2p`i}`N&pN2LR$bJ@Kzs} z{v7bE_k$-teSdA<#(01~fPY~BGyVhL|D*$?1D=ESXW(b`SJCp==DvnxoN@opG-5S? zKa>BK`$_48@pt-v=6?WR!heu|eEyO9y~Y9h8Z|A4OG9!yC0O1ju<|t4O~XX5r9N^O z+=Y+ec3VQR`?ge20092~{uvJ&|Bip-hO(fnqG;i~1Uv$U|N7rikHLTVrT_jZa2;Mg z|D2bnPJRCMCmPmuR~OVEy4(MH*Az@!DONLl*vhr6Z6@N{Y09-F(QotwIZ>DZfB*Aw z23SAm|IN;zUGVWzH81V$#q))CKmKOL5%TlZGJo4+xro?%2Y$&l3d&|X>7x`MxL*mK zkcrlf&Yp=A8*wMS%6dzf`0L-~Sga27Ub}{5u!$ zZNK3+q}4{D2galEX6LzLj;q%R?GC_d9p|p;@5VGq9{bhvhWLGEYtecowbqc49rGw> zdJP}ue2|VaV+Ztm#{cn3KN(qxyWXZZYF}mk>U5;VKHp~U2NUv`b?Z-mIukh(^hd2< zPbXq_&5_oay|x|ZI$Q3NG~hB%lnL{-u@ymY+B0wkWLs{P`sZQy?@XK@D*0w!l!!^y zLir;l4OW2;sEp|c z*I4{N{=)G9=UrkpbK-uScXD$h&rRAw_FE}HcM|>Y>CWO`x5diCPq9V&JVNV&rLX_s z{z!i%kbkZ^(hbOPdO{l|_NC|VpZ{R^A-2PV-kp4sK#+a}viq47i}-Ao^FhU*{htma MtS>FZU1$IR0B=w51^@s6 diff --git a/app/src/main/res/drawable-xxxhdpi/pic_category_guide.webp b/app/src/main/res/drawable-xxxhdpi/pic_category_guide.webp index b229d64c55b29ab601b7ed8aeaf46f3956f2ed4a..1b6e315650eb8460da696ed17d4f376302b16696 100644 GIT binary patch literal 47440 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

{R6_V}5y4L* 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 z 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 z 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 - + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_category_selected_tag.xml b/app/src/main/res/drawable/bg_category_selected_tag.xml new file mode 100644 index 0000000000..c2dccd3609 --- /dev/null +++ b/app/src/main/res/drawable/bg_category_selected_tag.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_category_sidebar.xml b/app/src/main/res/drawable/bg_category_sidebar.xml new file mode 100644 index 0000000000..af207118d5 --- /dev/null +++ b/app/src/main/res/drawable/bg_category_sidebar.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_more_category_default.xml b/app/src/main/res/drawable/bg_more_category_default.xml new file mode 100644 index 0000000000..cb851b8882 --- /dev/null +++ b/app/src/main/res/drawable/bg_more_category_default.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_more_category_filtered.xml b/app/src/main/res/drawable/bg_more_category_filtered.xml new file mode 100644 index 0000000000..973114a8c5 --- /dev/null +++ b/app/src/main/res/drawable/bg_more_category_filtered.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_search_category_number.xml b/app/src/main/res/drawable/bg_search_category_number.xml new file mode 100644 index 0000000000..739cb3424c --- /dev/null +++ b/app/src/main/res/drawable/bg_search_category_number.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_arrow_down.xml b/app/src/main/res/drawable/ic_arrow_down.xml new file mode 100644 index 0000000000..0597014721 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_down.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/ic_arrow_up.xml b/app/src/main/res/drawable/ic_arrow_up.xml new file mode 100644 index 0000000000..a6cb520feb --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_up.xml @@ -0,0 +1,13 @@ + + + diff --git a/app/src/main/res/drawable/ic_basic_checkmark_circle_fill.xml b/app/src/main/res/drawable/ic_basic_checkmark_circle_fill.xml new file mode 100644 index 0000000000..fc2e0bf1c0 --- /dev/null +++ b/app/src/main/res/drawable/ic_basic_checkmark_circle_fill.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_basic_classification.xml b/app/src/main/res/drawable/ic_basic_classification.xml new file mode 100644 index 0000000000..09da3cf23c --- /dev/null +++ b/app/src/main/res/drawable/ic_basic_classification.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_clear.xml b/app/src/main/res/drawable/ic_clear.xml new file mode 100644 index 0000000000..2428f4db5a --- /dev/null +++ b/app/src/main/res/drawable/ic_clear.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_x_size_8.xml b/app/src/main/res/drawable/ic_x_size_8.xml new file mode 100644 index 0000000000..504e77ca0f --- /dev/null +++ b/app/src/main/res/drawable/ic_x_size_8.xml @@ -0,0 +1,14 @@ + + + + + + diff --git a/app/src/main/res/drawable/shape_category_filter_size_item_normal.xml b/app/src/main/res/drawable/shape_category_filter_size_item_normal.xml new file mode 100644 index 0000000000..c55b59e865 --- /dev/null +++ b/app/src/main/res/drawable/shape_category_filter_size_item_normal.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_category_filter_size_item_selected.xml b/app/src/main/res/drawable/shape_category_filter_size_item_selected.xml new file mode 100644 index 0000000000..ac7cff2052 --- /dev/null +++ b/app/src/main/res/drawable/shape_category_filter_size_item_selected.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_filtered_left.xml b/app/src/main/res/drawable/shape_filtered_left.xml new file mode 100644 index 0000000000..1cc6c51d4b --- /dev/null +++ b/app/src/main/res/drawable/shape_filtered_left.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_filtered_right.xml b/app/src/main/res/drawable/shape_filtered_right.xml new file mode 100644 index 0000000000..cfdde3ec28 --- /dev/null +++ b/app/src/main/res/drawable/shape_filtered_right.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/category_directory_item.xml b/app/src/main/res/layout/category_directory_item.xml index fdd6a6be2f..6b17af8fa2 100644 --- a/app/src/main/res/layout/category_directory_item.xml +++ b/app/src/main/res/layout/category_directory_item.xml @@ -2,20 +2,19 @@ + android:paddingBottom="24dp"> - - - + diff --git a/app/src/main/res/layout/fragment_category.xml b/app/src/main/res/layout/fragment_category.xml index 00d69e3712..8606f7cce5 100644 --- a/app/src/main/res/layout/fragment_category.xml +++ b/app/src/main/res/layout/fragment_category.xml @@ -1,18 +1,93 @@ - - + - + + + + + + + + + + + - + android:layout_marginTop="8dp" + app:topRightRadius="8dp"> + + + + + + - - - - - - - - - - - - - - \ No newline at end of file + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_category_list.xml b/app/src/main/res/layout/fragment_category_list.xml index acf576c614..339650cf84 100644 --- a/app/src/main/res/layout/fragment_category_list.xml +++ b/app/src/main/res/layout/fragment_category_list.xml @@ -1,37 +1,53 @@ + + + + + + + + + - - + android:layout_height="36dp" + android:layout_below="@id/fl_tags_container" /> + android:layout_below="@+id/filterContainer" /> + android:layout_below="@+id/filterContainer"> - + android:layout_width="match_parent" + android:layout_height="36dp"> + android:id="@+id/ll_type_container" + android:layout_width="wrap_content" + android:layout_height="0dp" + android:gravity="center" + android:orientation="horizontal" + android:paddingHorizontal="12dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> + android:id="@+id/tv_type" + style="@style/TextCaption1B" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginStart="4dp" + android:gravity="center" + android:text="@string/popular" + android:textColor="@color/text_tertiary" /> + + + + android:id="@+id/ll_size_container" + android:layout_width="wrap_content" + android:layout_height="match_parent" + android:layout_marginEnd="4dp" + android:gravity="center" + android:orientation="horizontal" + android:paddingHorizontal="12dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="parent"> + android:id="@+id/tv_size" + style="@style/TextCaption1B" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/size" + android:textColor="@color/text_tertiary" /> + + - - - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_category_filter_size.xml b/app/src/main/res/layout/layout_category_filter_size.xml new file mode 100644 index 0000000000..b9df09289a --- /dev/null +++ b/app/src/main/res/layout/layout_category_filter_size.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/pop_category_filter_size.xml b/app/src/main/res/layout/pop_category_filter_size.xml new file mode 100644 index 0000000000..b9196e3814 --- /dev/null +++ b/app/src/main/res/layout/pop_category_filter_size.xml @@ -0,0 +1,19 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/pop_category_filter_type.xml b/app/src/main/res/layout/pop_category_filter_type.xml new file mode 100644 index 0000000000..597af45f55 --- /dev/null +++ b/app/src/main/res/layout/pop_category_filter_type.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/pop_search_category.xml b/app/src/main/res/layout/pop_search_category.xml new file mode 100644 index 0000000000..dbb3b538f5 --- /dev/null +++ b/app/src/main/res/layout/pop_search_category.xml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/recycler_category_selected_tag.xml b/app/src/main/res/layout/recycler_category_selected_tag.xml new file mode 100644 index 0000000000..e63a3536cf --- /dev/null +++ b/app/src/main/res/layout/recycler_category_selected_tag.xml @@ -0,0 +1,9 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/recycler_search_category_result.xml b/app/src/main/res/layout/recycler_search_category_result.xml new file mode 100644 index 0000000000..1c91ccf651 --- /dev/null +++ b/app/src/main/res/layout/recycler_search_category_result.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/sub_category_item.xml b/app/src/main/res/layout/sub_category_item.xml index 12edf950c8..a397ec9394 100644 --- a/app/src/main/res/layout/sub_category_item.xml +++ b/app/src/main/res/layout/sub_category_item.xml @@ -1,5 +1,6 @@ - + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:text="最多五个字" /> - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/values-night/styles.xml b/app/src/main/res/values-night/styles.xml index 039dc45dee..ee6176a280 100644 --- a/app/src/main/res/values-night/styles.xml +++ b/app/src/main/res/values-night/styles.xml @@ -180,25 +180,6 @@ horizontal - - - - - - - -