Compare commits

...

29 Commits

Author SHA1 Message Date
1d74bfc7a8 feat: 从SD卡兼容所有文件管理权限 2025-05-15 14:52:27 +08:00
3d118544bb Merge branch 'feat/GHZSCY-7890' into 'dev'
feat: 游戏详情-内容卡片 红点显示优化—客户端 https://jira.shanqu.cc/browse/GHZSCY-7890

See merge request halo/android/assistant-android!2190
2025-05-15 14:47:37 +08:00
730611362f feat: 游戏详情-内容卡片 红点显示优化—客户端 https://jira.shanqu.cc/browse/GHZSCY-7890 2025-05-15 14:47:37 +08:00
9825bb7d3f Merge branch 'feat/GHZSCY-7903' into 'dev'
feat: 关于插件说明的优化—客户端 https://jira.shanqu.cc/browse/GHZSCY-7903

See merge request halo/android/assistant-android!2189
2025-05-15 14:47:06 +08:00
7dfb69fd34 feat: 关于插件说明的优化—客户端 https://jira.shanqu.cc/browse/GHZSCY-7903 2025-05-15 14:47:06 +08:00
380939b8c4 Merge branch 'fix/GHZSCY-7971-pr' into 'dev'
fix:【光环助手】组件显示问题 https://jira.shanqu.cc/browse/GHZSCY-7971

See merge request halo/android/assistant-android!2185
2025-05-14 13:49:24 +08:00
83d43f32bf fix:【光环助手】组件显示问题 https://jira.shanqu.cc/browse/GHZSCY-7971 2025-05-14 13:49:23 +08:00
184e0731fb Merge branch 'fix/GHZSCY-7976' into 'dev'
fix: 游戏弹窗显示优化-客户端 https://jira.shanqu.cc/browse/GHZSCY-7976

See merge request halo/android/assistant-android!2186
2025-05-14 13:48:57 +08:00
085356d85e fix: 游戏弹窗显示优化-客户端 https://jira.shanqu.cc/browse/GHZSCY-7976 2025-05-14 13:48:57 +08:00
44138225fc Merge remote-tracking branch 'origin/release' into dev
# Conflicts:
#	dependencies.gradle
2025-05-14 09:25:17 +08:00
9c86504c2f Merge branch 'hotfix/v5.41.0-1070/search_culprit' into 'release'
Hotfix/v5.41.0 1070/search culprit

See merge request halo/android/assistant-android!2184
2025-05-14 09:24:25 +08:00
e4cf36d1b8 fix: 修复部分设备弹起 DSP 游戏详情弹窗的时候的问题 2025-05-13 16:35:34 +08:00
676e7a4d94 fix: 修复搜索页配置的搜索专题无法正常显示的问题 2025-05-13 16:35:08 +08:00
b3ee742faf chore: 版本更新至 5.41.1 2025-05-09 18:38:25 +08:00
ead61c1916 Merge branch 'hotfix/v5.41.0-1070/crash' into 'release'
hotfix: 修复搜索页下载闪退问题

See merge request halo/android/assistant-android!2183
2025-05-09 18:37:42 +08:00
9bb20ca41f hotfix: 修复搜索页下载闪退问题 2025-05-09 18:36:59 +08:00
74942be890 Merge branch 'fix/issues-445867' into 'release'
fix:https://sentry.shanqu.cc/organizations/lightgame/issues/445867/?project=22...

See merge request halo/android/assistant-android!2182
2025-05-09 11:16:32 +08:00
391eb64df1 fix:https://sentry.shanqu.cc/organizations/lightgame/issues/445867/?project=22... 2025-05-09 11:16:32 +08:00
03ba6f5614 Merge branch 'fix/GHZSCY-7961-pr' into 'release'
fix:【光环助手】V5.41.0版本出包-05/08测试-客户端 https://jira.shanqu.cc/browse/GHZSCY-7961

See merge request halo/android/assistant-android!2181
2025-05-09 10:37:10 +08:00
1552a3e95d fix:【光环助手】V5.41.0版本出包-05/08测试-客户端 https://jira.shanqu.cc/browse/GHZSCY-7961 2025-05-09 10:34:57 +08:00
9b7526773c Merge branch 'fix/game_detail_video' into 'release'
fix: 修复游戏详情页视频主动播放时退出页面没有停止播放的问题

See merge request halo/android/assistant-android!2179
2025-05-08 18:20:51 +08:00
764402b701 fix: 修复游戏详情页视频主动播放时退出页面没有停止播放的问题 2025-05-08 18:16:30 +08:00
dc7ebbc308 Merge branch 'fix/GHZSCY-7951-mr' into 'release'
fix: 评论区版本号显示优化-客户端 https://jira.shanqu.cc/browse/GHZSCY-7951

See merge request halo/android/assistant-android!2178
2025-05-08 16:49:09 +08:00
065ebe32ed fix: 评论区版本号显示优化-客户端 https://jira.shanqu.cc/browse/GHZSCY-7951 2025-05-08 16:49:09 +08:00
9158d951f4 Merge branch 'revert-d3c9cb77' into 'dev'
Revert "Merge branch 'revert-26e272ed' into 'dev'"

See merge request halo/android/assistant-android!2176
2025-05-08 11:11:25 +08:00
1c7f5c2d26 Revert "Merge branch 'revert-26e272ed' into 'dev'" 2025-05-08 11:11:25 +08:00
bc630f63d4 Merge branch 'revert-21a4ff55' into 'dev'
Revert "Merge branch 'revert-GHZSCY-7496' into 'dev'"

See merge request halo/android/assistant-android!2175
2025-05-08 11:04:32 +08:00
1a2cfb79c2 Revert "Merge branch 'revert-GHZSCY-7496' into 'dev'"
This reverts merge request !2174
2025-05-08 10:57:31 +08:00
7b708ebd8d chore: 版本更新至 5.42.0 2025-05-08 10:50:58 +08:00
147 changed files with 4409 additions and 1642 deletions

View File

@ -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')

View File

@ -3,9 +3,14 @@ package com.gh.vspace.installexternalgames
import android.Manifest
import android.app.Dialog
import android.content.ComponentName
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.provider.Settings
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import com.gh.common.util.DialogUtils
@ -37,6 +42,9 @@ class InstallExternalGameFragment : ToolbarFragment(), OnItemClickListener {
private var uninstallDisposable: Disposable? = null
private var shouldScan = false
override fun getLayoutId() = 0
override fun getInflatedLayout() =
@ -74,7 +82,24 @@ class InstallExternalGameFragment : ToolbarFragment(), OnItemClickListener {
adapter.notifyDataSetChanged()
}
requestStoragePermission()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (!Environment.isExternalStorageManager()) {
shouldScan = true
AlertDialog.Builder(requireContext()).setMessage("请在设置页面允许光环助手")
.setNegativeButton("") { dialog, which ->
val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
}.show()
} else {
mViewModel.scanPaths()
}
} else {
requestStoragePermission()
}
}
private fun requestStoragePermission() {
@ -96,6 +121,12 @@ class InstallExternalGameFragment : ToolbarFragment(), OnItemClickListener {
}
}
override fun onStart() {
super.onStart()
if (shouldScan) {
mViewModel.scanPaths()
}
}
private fun initView() {
dialog = DialogUtils.showWaitDialog(requireContext(), "")
@ -152,7 +183,7 @@ class InstallExternalGameFragment : ToolbarFragment(), OnItemClickListener {
}, true)
VHelper.newCwValidateVspaceBeforeAction(
requireContext(),null,
requireContext(), null,
) {
dialog.show()
}

View File

@ -55,6 +55,8 @@ public class DetailDownloadUtils {
}
viewHolder.setSpeedViewsVisible(false);
// 默认为显示状态
viewHolder.getDownloadPb().setVisibility(View.VISIBLE);
// 根据预置的配置更新 ViewHolder 的状态 (譬如青少年模式、下载内容为空等)
if (updateViewHolderWithPredefinedConfig(viewHolder, gameEntity)) {

View File

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

View File

@ -2,10 +2,9 @@ package com.gh.common.util
import android.content.Context
import android.os.Build
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.DialogHelper
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.toResString
import com.gh.gamecenter.common.utils.replaceLineBreakWithBr
import com.gh.gamecenter.core.utils.EmptyCallback
import com.gh.gamecenter.feature.entity.ApkEntity
import com.gh.gamecenter.feature.entity.GameEntity
@ -32,7 +31,7 @@ object DownloadDialogHelper {
DialogHelper.showDialogWithHtmlContent(
context,
dialog.title,
dialog.content,
dialog.content.replaceLineBreakWithBr(),
"继续下载",
"取消",
confirmClickCallback = {
@ -59,7 +58,8 @@ object DownloadDialogHelper {
gameName = gameEntity.name ?: "",
gameType = gameEntity.categoryChinese
)
}
},
extraConfig = DialogHelper.Config(centerTitle = true)
)
} else {
callback.onCallback()

View File

@ -147,6 +147,15 @@ object ViewPagerFragmentHelper {
bundle.putString(EntranceConsts.KEY_QUESTIONS_ID, linkEntity.link)
NewQuestionDetailFragment().with(bundle)
}
// 专题合集详情页
TYPE_COLUMN_COLLECTION -> {
bundle.putString(EntranceConsts.KEY_COLLECTION_ID, linkEntity.link)
bundle.putInt(EntranceConsts.KEY_POSITION, 0)
bundle.putString(EntranceConsts.KEY_COLUMNNAME, linkEntity.text)
bundle.putBoolean(EntranceConsts.KEY_IS_COLUMN_COLLECTION, true)
bundle.putString(EntranceConsts.KEY_SUBJECT_TYPE, "tab")
ColumnCollectionDetailFragment().with(bundle)
}
// 其他原来带Toolbar的Fragment
else -> createToolbarWrapperFragment(bundle, linkEntity, isTabWrapper)
}
@ -176,15 +185,6 @@ object ViewPagerFragmentHelper {
bundle.putString(EntranceConsts.KEY_SUBJECT_TYPE, "detail")
bundle.putBoolean(EntranceConsts.KEY_SHOW_DOWNLOAD_MENU, !isTabWrapper)
}
// 专题合集详情页
TYPE_COLUMN_COLLECTION -> {
className = ColumnCollectionDetailFragment::class.java.name
bundle.putString(EntranceConsts.KEY_COLLECTION_ID, entity.link)
bundle.putInt(EntranceConsts.KEY_POSITION, 0)
bundle.putString(EntranceConsts.KEY_COLUMNNAME, entity.text)
bundle.putBoolean(EntranceConsts.KEY_IS_COLUMN_COLLECTION, true)
bundle.putString(EntranceConsts.KEY_SUBJECT_TYPE, "tab")
}
// 开服表
TYPE_SERVER -> {
className = GameServersPublishFragment::class.java.name
@ -199,8 +199,8 @@ object ViewPagerFragmentHelper {
// 分类2.0
TYPE_CATEGORY_V2 -> {
className = CategoryV2Fragment::class.java.name
bundle.putString(EntranceConsts.KEY_CATEGORY_ID, entity.link)
bundle.putString(EntranceConsts.KEY_CATEGORY_TITLE, entity.text)
bundle.putString(EntranceConsts.KEY_PAGE_ID, entity.link)
bundle.putString(EntranceConsts.KEY_PAGE_NAME, entity.text)
bundle.putBoolean(EntranceConsts.KEY_SHOW_DOWNLOAD_MENU, !isTabWrapper)
}
// 通用内容合集详情页

View File

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

View File

@ -1,18 +1,23 @@
package com.gh.common.view
import android.content.Context
import android.graphics.Typeface
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CheckedTextView
import android.widget.LinearLayout
import android.widget.PopupWindow
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.forEach
import androidx.core.view.setPadding
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.setDrawableEnd
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.toDrawable
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.google.android.flexbox.FlexboxLayout
@ -30,11 +35,15 @@ class ConfigFilterView @JvmOverloads constructor(
var recommendedTv: TextView
var updateTv: TextView //更新
var container: View
private var dot1: View
private var dot2: View
private var dot3: View
var sizeFilterArray: ArrayList<SubjectSettingEntity.Size>? = null
private var sizeFilterArray: ArrayList<SubjectSettingEntity.Size>? = null
private var mOnConfigFilterSetupListener: OnConfigFilterSetupListener? = null
private var mSelectionTvList: ArrayList<TextView>
private var highlightedSortedType: SortType? = null
init {
View.inflate(context, R.layout.layout_config_filter, this)
@ -45,6 +54,9 @@ class ConfigFilterView @JvmOverloads constructor(
updateTv = findViewById(R.id.updateTv)
recommendedTv = findViewById(R.id.recommended_tv)
container = findViewById(R.id.config_controller)
dot1 = findViewById(R.id.dot1)
dot2 = findViewById(R.id.dot2)
dot3 = findViewById(R.id.dot3)
mSelectionTvList = arrayListOf(newestTv, ratingTv, updateTv, recommendedTv)
@ -54,24 +66,30 @@ class ConfigFilterView @JvmOverloads constructor(
}
ratingTv.setOnClickListener {
highlightedSortedType = SortType.RATING
mOnConfigFilterSetupListener?.onSetupSortType(SortType.RATING)
updateHighlightedTextView(ratingTv)
}
updateTv.setOnClickListener {
highlightedSortedType = SortType.UPDATE
mOnConfigFilterSetupListener?.onSetupSortType(SortType.UPDATE)
updateHighlightedTextView(updateTv)
}
newestTv.setOnClickListener {
highlightedSortedType = SortType.NEWEST
mOnConfigFilterSetupListener?.onSetupSortType(SortType.NEWEST)
updateHighlightedTextView(newestTv)
}
recommendedTv.setOnClickListener {
highlightedSortedType = SortType.RECOMMENDED
mOnConfigFilterSetupListener?.onSetupSortType(SortType.RECOMMENDED)
updateHighlightedTextView(recommendedTv)
}
updateAllTextView(SortType.UPDATE)
}
private fun updateHighlightedTextView(highlightedTv: TextView) {
@ -80,14 +98,17 @@ class ConfigFilterView @JvmOverloads constructor(
}
}
fun updateAllTextView(sortType: SortType) {
when (sortType) {
SortType.RECOMMENDED -> updateHighlightedTextView(recommendedTv)
SortType.NEWEST -> updateHighlightedTextView(newestTv)
SortType.RATING -> updateHighlightedTextView(ratingTv)
SortType.UPDATE -> updateHighlightedTextView(updateTv)
fun updateAllTextView(sortType: SortType? = highlightedSortedType) {
highlightedSortedType = sortType
sortType?.let {
when (it) {
SortType.RECOMMENDED -> updateHighlightedTextView(recommendedTv)
SortType.NEWEST -> updateHighlightedTextView(newestTv)
SortType.RATING -> updateHighlightedTextView(ratingTv)
SortType.UPDATE -> updateHighlightedTextView(updateTv)
}
mSizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
}
mSizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_757575.toColor(context))
}
fun setOnConfigSetupListener(onConfigFilterSetupListener: OnConfigFilterSetupListener) {
@ -96,11 +117,11 @@ class ConfigFilterView @JvmOverloads constructor(
private fun toggleHighlightedTextView(targetTextView: TextView, highlightIt: Boolean) {
if (highlightIt) {
targetTextView.background = R.drawable.bg_tag_text.toDrawable()
targetTextView.setTextColor(com.gh.gamecenter.common.R.color.text_white.toColor(context))
targetTextView.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
targetTextView.setTypeface(Typeface.DEFAULT, Typeface.BOLD)
} else {
targetTextView.background = null
targetTextView.setTextColor(com.gh.gamecenter.common.R.color.text_757575.toColor(context))
targetTextView.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
targetTextView.setTypeface(Typeface.DEFAULT, Typeface.NORMAL)
}
}
@ -112,8 +133,8 @@ class ConfigFilterView @JvmOverloads constructor(
}
private fun showSelectionPopupWindow(containerView: View, sizeTv: TextView, sizeText: String) {
sizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(sizeTv.context))
sizeTv.setDrawableEnd(R.drawable.ic_filter_arrow_up)
sizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(sizeTv.context))
sizeTv.setDrawableEnd(R.drawable.ic_auxiliary_arrow_up_primary_8)
val inflater = LayoutInflater.from(sizeTv.context)
val layout = inflater.inflate(R.layout.layout_filter_size, null)
@ -136,43 +157,39 @@ class ConfigFilterView @JvmOverloads constructor(
}
}
flexboxLayout.setOnClickListener { }
backgroundView.setOnClickListener {
popupWindow.dismiss()
}
for (size in sizeFilterArray!!) {
val item = inflater.inflate(R.layout.item_filter_size, flexboxLayout, false)
val item = inflater.inflate(R.layout.item_config_filter_size, flexboxLayout, false)
// 单列 4 个,强行设置宽度为屏幕的 1/4
val width = sizeTv.context.resources.displayMetrics.widthPixels / 4
val width = (sizeTv.context.resources.displayMetrics.widthPixels - 56F.dip2px()) / 4
val height = item.layoutParams.height
item.layoutParams = ViewGroup.LayoutParams(width, height)
flexboxLayout.addView(item)
val tv = item.findViewById<TextView>(R.id.size_tv)
val tv = item.findViewById<CheckedTextView>(R.id.size_tv)
tv.text = size.text
if (sizeText == size.text) {
toggleHighlightedTextView(tv, true)
} else {
toggleHighlightedTextView(tv, false)
}
tv.isChecked = sizeText == size.text
tv.tag = size.text
item.setOnClickListener {
toggleHighlightedTextView(tv, true)
tv.setOnClickListener {
flexboxLayout.forEach { checkedTv ->
(checkedTv as CheckedTextView).isChecked = checkedTv == tv
}
popupWindow.dismiss()
sizeTv.text = size.text
mOnConfigFilterSetupListener?.onSetupSortSize(size)
}
}
popupWindow.setOnDismissListener {
sizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_757575.toColor(sizeTv.context))
sizeTv.setDrawableEnd(R.drawable.ic_filter_arrow_down)
sizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(sizeTv.context))
sizeTv.setDrawableEnd(R.drawable.ic_auxiliary_arrow_down_8)
mPopupWindow = null
}
@ -193,6 +210,49 @@ class ConfigFilterView @JvmOverloads constructor(
}
}
fun initSubjectFilterView(subjectSetting: SubjectSettingEntity) {
ratingTv.visibility = View.VISIBLE
if (subjectSetting.filterOptions.size > 1) {
// 重排序
subjectSetting.filterOptions.forEachIndexed { index, s ->
when (index) {
0 -> updateTv.text = s
1 -> recommendedTv.text = s
2 -> newestTv.text = s
3 -> ratingTv.text = s
}
}
} else {
updateTv.setPadding(0)
updateTv.setTypeface(Typeface.DEFAULT, Typeface.NORMAL)
updateTv.isClickable = false
updateTv.text = when (subjectSetting.filterOptions.first()) {
"推荐" -> "根据光环推荐排序"
"最新" -> "根据游戏上新排序"
"评分" -> "根据游戏评分排序"
"更新" -> "根据更新时间排序"
else -> subjectSetting.filterOptions.first()
}
}
// 隐藏相关选项
updateTv.goneIf(subjectSetting.filterOptions.isEmpty())
recommendedTv.goneIf(subjectSetting.filterOptions.size <= 1)
dot1.goneIf(subjectSetting.filterOptions.size <= 1)
newestTv.goneIf(subjectSetting.filterOptions.size <= 2)
dot2.goneIf(subjectSetting.filterOptions.size <= 2)
ratingTv.goneIf(subjectSetting.filterOptions.size <= 3)
dot3.goneIf(subjectSetting.filterOptions.size <= 3)
sizeFilterArray = subjectSetting.filterSizes
if (subjectSetting.filterOptions.size == 1) {
updateTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
updateTv.setTypeface(Typeface.DEFAULT, Typeface.NORMAL)
highlightedSortedType = null
}
}
interface OnConfigFilterSetupListener {
fun onShowSortSize()
fun onSetupSortSize(sortSize: SubjectSettingEntity.Size)

View File

@ -217,7 +217,7 @@ class DetailViewHolder(
}
private fun restoreDialogFragment() {
val gamePermissionDialogFragment = (context as AppCompatActivity).supportFragmentManager.findFragmentByTag(
val gamePermissionDialogFragment = (context.getActivity() as? AppCompatActivity)?.supportFragmentManager?.findFragmentByTag(
GamePermissionDialogFragment::class.java.name
) as DialogFragment?
gamePermissionDialogFragment?.dismissAllowingStateLoss()
@ -233,7 +233,6 @@ class DetailViewHolder(
getDownloadBtnText(context, gameEntity, false, true, PluginLocation.only_game)
when {
localText.contains(com.gh.gamecenter.feature.R.string.update.toResString()) -> { // 本地游戏需要更新
localDownloadButton?.goneIf(true)
localDownloadContainer?.goneIf(true)
downloadPb.goneIf(true)
overlayTv?.goneIf(true)
@ -250,7 +249,6 @@ class DetailViewHolder(
localText.contains(com.gh.gamecenter.feature.R.string.launch.toResString()) && downloadText == "更新" -> { // 畅玩游戏需要更新:显示 加速/更新
localDownloadContainer?.goneIf(true)
localDownloadButton?.goneIf(true)
downloadPb.goneIf(true)
overlayTv?.goneIf(true)
@ -263,7 +261,6 @@ class DetailViewHolder(
}
localText.contains(com.gh.gamecenter.feature.R.string.launch.toResString()) -> { // 本地游戏为启动状态:显示 加速/畅玩
localDownloadButton?.goneIf(true)
localDownloadContainer?.goneIf(true)
downloadPb.goneIf(true)
overlayTv?.goneIf(true)
@ -288,20 +285,20 @@ class DetailViewHolder(
val downloadText = getDownloadBtnText(context, gameEntity, false, false, PluginLocation.only_game)
when {
downloadText.contains(com.gh.gamecenter.feature.R.string.launch.toResString()) -> {
localDownloadButton?.goneIf(true)
localDownloadContainer?.goneIf(true)
downloadPb.goneIf(true)
true
}
downloadText == com.gh.gamecenter.feature.R.string.launch.toResString() -> true
downloadText.contains(R.string.update.toResString()) -> true
else -> false
}
}
else -> false
}
it.checkIfShowSpeedUi(showSpeedUi)
it.checkIfShowSpeedUi(showSpeedUi, showDualDownloadButton)
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -7,5 +7,8 @@ class GameColumnCollection(
val id: String = "",
val name: String = "",
// 取值为 "1-1" 或 "1-2" 或 "top" 相应地代表 1行1个 或 1行2个 或 排行榜
val style: String = ""
val style: String = "",
val custom: Boolean = false, // 自定义设置
@SerializedName("custom_size")
val customSize: Int = 0 // 默认显示前X个专题
)

View File

@ -7,7 +7,7 @@ import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
@Parcelize
class SubjectData(
data class SubjectData(
// 入口必填
var subjectId: String?,
var subjectName: String?,
@ -21,13 +21,28 @@ class SubjectData(
var subjectStyle: String = "",
var showDetailSubtitle: Boolean = false,
var showDetailIconSubscript: Boolean = false,
var customLimit: String = "", // unlimited无限制、forbidden禁止移出
var requireUpdateSetting: Boolean = false, // 多专题页面需要专题页面自行获取专题配置
var isAdData: Boolean = false,
var adId: String = "", // 广告ID(本地字段),不为空时为广告专题
var codeId: String = "" // 广告CODE_ID(本地字段),不为空时为广告专题
var codeId: String = "", // 广告CODE_ID(本地字段),不为空时为广告专题
var tag: String = "" // 分类标签,埋点用
) : Parcelable, Cloneable {
@IgnoredOnParcel
val isForbidden
get() = customLimit == "forbidden"
@IgnoredOnParcel
val sortChinese
get() = when {
sort.contains("publish") -> "最新"
sort.contains("star") -> "评分"
sort.contains("update") -> "更新"
else -> "推荐"
}
@IgnoredOnParcel
val subjectStyleChinese: String
get() = CustomPageItem.subjectTypeToComponentStyle[subjectStyle] ?: ""

View File

@ -13,7 +13,7 @@ class SubjectSettingEntity(
@SerializedName("type")
var typeEntity: TypeEntity = TypeEntity(),
var tag: String = "",
var filter: String = "", // rows: off/on
var filter: String = "", // off/on
var order: Boolean = false, // 是否显示序号
@SerializedName("brief_style")
@ -34,6 +34,9 @@ class SubjectSettingEntity(
private val _showDetailIconSubscript: Boolean? = null
) : Parcelable {
val isFilterEnabled: Boolean
get() = filter == "on"
val showDetailSubtitle: Boolean
get() = _showDetailSubtitle ?: false

View File

@ -2,7 +2,7 @@ package com.gh.gamecenter.game.columncollection.detail
import android.os.Bundle
import android.view.View
import androidx.lifecycle.ViewModelProviders
import androidx.core.view.isVisible
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.baselist.LazyListFragment
@ -14,6 +14,8 @@ import com.gh.gamecenter.common.json.json
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.observeNonNull
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.viewModelProviderFromParent
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.databinding.FragmentColumnCollectionDetailBinding
import com.gh.gamecenter.entity.GameColumnCollection
import com.gh.gamecenter.entity.SubjectData
@ -23,6 +25,7 @@ class ColumnCollectionDetailFragment : LazyListFragment<LinkEntity, ColumnCollec
private var mAdapter: ColumnCollectionDetailAdapter? = null
private var mBinding: FragmentColumnCollectionDetailBinding? = null
private var mFragment: SubjectTabFragment? = null
private var mTabIndex = -1
private var mBasicExposureSourceList: ArrayList<ExposureSource>? = null
@ -56,6 +59,11 @@ class ColumnCollectionDetailFragment : LazyListFragment<LinkEntity, ColumnCollec
override fun onFragmentFirstVisible() {
super.onFragmentFirstVisible()
if (mIsFromMainWrapper) {
DisplayUtils.setLightStatusBar(requireActivity(), !mIsDarkModeOn)
mBinding?.statusBar?.isVisible = true
}
mListViewModel.getGameColumnCollection()
mListViewModel.columnCollection.observeNonNull(this) {
setNavigationTitle(it.name)
@ -91,7 +99,7 @@ class ColumnCollectionDetailFragment : LazyListFragment<LinkEntity, ColumnCollec
}
override fun onChanged(list: MutableList<LinkEntity>?) {
if (list != null && list.isNotEmpty()) {
if (!list.isNullOrEmpty()) {
showSubjectTab(list)
}
}
@ -115,25 +123,28 @@ class ColumnCollectionDetailFragment : LazyListFragment<LinkEntity, ColumnCollec
isOrder = false,
requireUpdateSetting = true,
filter = "type:全部", // 默认显示大图
subjectType = subjectType
subjectType = subjectType,
customLimit = link.customLimit
)
)
}
val fragment = childFragmentManager.findFragmentByTag(SubjectTabFragment::class.java.name)
mFragment = childFragmentManager.findFragmentByTag(SubjectTabFragment::class.java.name) as? SubjectTabFragment
?: SubjectTabFragment()
val bundle = arguments
mListViewModel.columnCollection.value?.let {
bundle?.putString(EntranceConsts.KEY_COLUMN_COLLECTION_ID, it.id)
bundle?.putString(EntranceConsts.KEY_COLUMN_COLLECTION_NAME, it.name)
bundle?.putString(EntranceConsts.KEY_COLUMN_COLLECTION_STYLE, it.style)
bundle?.putBoolean(EntranceConsts.KEY_COLUMN_COLLECTION_CUSTOM, it.custom)
bundle?.putInt(EntranceConsts.KEY_COLUMN_COLLECTION_CUSTOM_SIZE, it.customSize)
}
bundle?.putParcelableArrayList(EntranceConsts.KEY_DATA, subjectDataList)
bundle?.putBoolean(EntranceConsts.KEY_IS_COLUMN_COLLECTION, true)
fragment.arguments = bundle
mFragment!!.arguments = bundle
mBinding?.placeholder?.visibility = View.VISIBLE
childFragmentManager.beginTransaction().replace(R.id.placeholder, fragment, SubjectTabFragment::class.java.name)
childFragmentManager.beginTransaction().replace(R.id.placeholder, mFragment!!, SubjectTabFragment::class.java.name)
.commitAllowingStateLoss()
}
@ -167,9 +178,18 @@ class ColumnCollectionDetailFragment : LazyListFragment<LinkEntity, ColumnCollec
arguments?.getString(EntranceConsts.KEY_COLLECTION_ID)
?: ""
)
return ViewModelProviders.of(this, factory).get(ColumnCollectionDetailViewModel::class.java)
return viewModelProviderFromParent(factory, arguments?.getString(EntranceConsts.KEY_COLLECTION_ID) ?: "")
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
if (mIsFromMainWrapper) {
DisplayUtils.setLightStatusBar(requireActivity(), !mIsDarkModeOn)
}
}
override fun onBackPressed(): Boolean = mFragment?.onBackPressed() ?: false
companion object {
const val TYPE_QQ_MINI_GAME_COLUMN = "qq_mini_game_column"
const val TYPE_WECHAT_MINI_GAME_COLUMN = "wechat_mini_game_column"

View File

@ -37,7 +37,6 @@ import com.gh.gamecenter.gamedetail.detail.viewholder.GameDetailContentRecommend
import com.gh.gamecenter.gamedetail.entity.*
import com.gh.gamecenter.livedata.Event
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.login.user.UserRepository
import com.gh.gamecenter.manager.PackagesManager
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.vspace.VHelper
@ -105,7 +104,7 @@ class GameDetailViewModel(
private var relatedGameList = arrayListOf<GameEntity>()
// 内容卡片相关
private var contentCardList: List<ContentCardEntity>?= null
private var contentCardList: List<ContentCardEntity>? = null
private val contentCardSp by lazy {
HaloApp.getInstance().getSharedPreferences(SP_CONTENT_CARD, Context.MODE_PRIVATE)
}
@ -134,9 +133,9 @@ class GameDetailViewModel(
private var isGameUpdatable = false
private val compositeDisposable = CompositeDisposable()
private var userRelatedInfoReceivedCallback: (() -> Unit)? = null
init {
loadData()
}
@ -1117,6 +1116,14 @@ class GameDetailViewModel(
compositeDisposable.clear()
}
fun markContentCardRedDot(contentCardEntity: ContentCardEntity) {
SPUtils.setString(contentCardSp, RED_DOT_PREFIX + (gameId ?: "") + contentCardEntity.link.type, contentCardEntity.redDot.toString())
}
fun shouldShowContentCardRedDot(contentCardEntity: ContentCardEntity): Boolean {
return SPUtils.getString(contentCardSp, RED_DOT_PREFIX + (gameId ?: "") + contentCardEntity.link.type) != contentCardEntity.redDot.toString()
}
class Factory(
private val application: Application,
private val gameId: String?,
@ -1133,6 +1140,7 @@ class GameDetailViewModel(
companion object {
const val SP_CONTENT_CARD = "content_card"
const val TAG = "GameDetailViewModel"
const val RED_DOT_PREFIX = "red_dot_"
//[已领取/已淘号/再领/再淘]的礼包置底显示
fun sortLibaoList(libaoList: ArrayList<LibaoEntity>?) {

View File

@ -635,7 +635,7 @@ class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
DialogHelper.showDialogWithHtmlContent(
requireContext(),
dialog.title,
dialog.content,
dialog.content.replaceLineBreakWithBr(),
dialog.confirmButton.text.toString(),
dialog.closeButtonText,
{
@ -669,7 +669,8 @@ class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
"game_type", gameEntity?.categoryChinese ?: "",
"button_name", "关闭弹窗"
)
}
},
extraConfig = DialogHelper.Config(centerTitle = true)
)
}

View File

@ -55,6 +55,8 @@ class GameDetailAcceleratorUiHelper(private val binding: DetailDownloadItemBindi
private var hasAnyAcctRecord = false
private var showDualDownloadButton: Boolean = false
private val accelerationListener = object : OnAccelerateListener {
override fun onStateChanged(state: AccelerateState) {
when (state) {
@ -131,13 +133,17 @@ class GameDetailAcceleratorUiHelper(private val binding: DetailDownloadItemBindi
val isCurrentGameAccelerating = AcceleratorDataHolder.instance.isCurrentGameAccelerating(game.id)
when {
isCurrentGameAccelerating -> {// 如果当前游戏正处于加速状态,则需要隐藏当前下载按钮
binding.detailProgressbar.goneIf(true)
}
if (!showDualDownloadButton) {
when {
isCurrentGameAccelerating -> {// 如果当前游戏正处于加速状态,则需要隐藏当前下载按钮
binding.detailProgressbar.goneIf(true)
}
binding.detailProgressbar.text == "更新" -> { // 游戏没有处于加速状态,如果 下载按钮为 “更新” 状态,则需要显示出来
binding.detailProgressbar.goneIf(false)
binding.detailProgressbar.text == "更新" -> { // 游戏没有处于加速状态,如果 下载按钮为 “更新” 状态,则需要显示出来
binding.detailProgressbar.setBackgroundResource(com.gh.gamecenter.common.R.drawable.bg_common_button_light_fill_blue)
binding.detailProgressbar.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
binding.detailProgressbar.goneIf(false)
}
}
}
@ -152,7 +158,8 @@ class GameDetailAcceleratorUiHelper(private val binding: DetailDownloadItemBindi
} ?: R.string.network_acceleration.toResString()
}
fun checkIfShowSpeedUi(show: Boolean) {
fun checkIfShowSpeedUi(show: Boolean, showDualDownloadButton: Boolean) {
this.showDualDownloadButton = showDualDownloadButton
if (!isInit) {
return
}

View File

@ -684,7 +684,7 @@ class GameDetailFragment : LazyFragment(), IScrollable {
null,
gameStatus = gameStatus
)
GameFunctionDialogFragment.show(requireContext(), gameDetailInfoTag.infoTags)
GameFunctionDialogFragment.show(requireContext(), gameEntity, gameDetailInfoTag.infoTags, gameDetailInfoTag.link)
}
}
}

View File

@ -127,6 +127,7 @@ class GameDetailCoverAdapter(
holder.binding.player.viewModel = viewModel
holder.binding.player.showOrHideCoverFilter = showOrHideCoverFilter
holder.binding.player.scrollCalculatorHelper = scrollCalculatorHelper
holder.binding.player.video = topVideo
holder.binding.player.updateThumb(topVideo.poster)

View File

@ -1,6 +1,7 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.content.Context
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.lifecycle.LifecycleOwner
import com.gh.common.constant.Config
@ -131,11 +132,9 @@ class GameDetailContentCardSingleItemViewHolder(
contentBannerView.startBannerLoop()
}
redDotTv.goneIf(!((contentCardEntity.link.type == TYPE_SERVER && contentCardEntity.server?.total != 0) || contentCardEntity.link.type == TYPE_GIFT)) {
if ((contentCardEntity.link.type == TYPE_SERVER) && (contentCardEntity.server?.calendar?.isNotEmpty() == true))
redDotTv.text = contentCardEntity.server?.total.toString()
if ((contentCardEntity.link.type == TYPE_GIFT) && (contentCardEntity.libao != null))
redDotTv.text = contentCardEntity.libao?.total.toString()
val showRedDot = contentCardEntity.redDot != 0 && viewModel.shouldShowContentCardRedDot(contentCardEntity)
redDotTv.goneIf(!showRedDot) {
redDotTv.text = contentCardEntity.redDot.toString()
}
val showNewTag = contentCardEntity.link.type == TYPE_ARCHIVE && contentCardEntity.archive != null && contentCardEntity.showNewTag
@ -173,11 +172,11 @@ class GameDetailContentCardSingleItemViewHolder(
confirmText = context.getString(com.gh.gamecenter.common.R.string.confirm),
cancelText = context.getString(com.gh.gamecenter.common.R.string.cancel),
confirmClickCallback = {
jumpToContentCardLink(context, contentCardEntity, viewModel)
jumpToContentCardLink(context, contentCardEntity, viewModel, redDotTv)
}
)
} else {
jumpToContentCardLink(context, contentCardEntity, viewModel)
jumpToContentCardLink(context, contentCardEntity, viewModel, redDotTv)
}
}
@ -189,11 +188,16 @@ class GameDetailContentCardSingleItemViewHolder(
}
}
fun jumpToContentCardLink(context: Context, contentCardEntity: ContentCardEntity, viewModel: GameDetailViewModel) {
fun jumpToContentCardLink(context: Context, contentCardEntity: ContentCardEntity, viewModel: GameDetailViewModel, redDotTv: TextView) {
val path = "游戏详情->内容卡片"
when (contentCardEntity.link.type) {
TYPE_GIFT,
TYPE_ARCHIVE -> {
if (contentCardEntity.link.type == TYPE_GIFT) {
viewModel.markContentCardRedDot(contentCardEntity)
redDotTv.isVisible = false
}
val type = if (contentCardEntity.link.type == TYPE_GIFT) GameDetailTabEntity.TYPE_GIFT else GameDetailTabEntity.TYPE_ARCHIVE
val tabList = viewModel.gameDetailTabListLiveData.value?.data
if (tabList?.find { it.type == type } != null) {
@ -208,6 +212,8 @@ class GameDetailContentCardSingleItemViewHolder(
TYPE_SERVER -> {
if (viewModel.game != null && contentCardEntity.server != null) {
viewModel.markContentCardRedDot(contentCardEntity)
redDotTv.isVisible = false
context.startActivity(
ServersCalendarActivity.getIntent(
context,

View File

@ -10,8 +10,6 @@ import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.detail.viewholder.GameDetailContentCardSingleItemViewHolder.Companion.jumpToContentCardLink
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity.Companion.TYPE_ARCHIVE
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity.Companion.TYPE_GIFT
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity.Companion.TYPE_SERVER
import com.gh.gamecenter.gamedetail.entity.GameDetailData
class GameDetailContentCardTripleItemViewHolder(
@ -74,11 +72,9 @@ class GameDetailContentCardTripleItemViewHolder(
titleTv.text = contentCardEntity.title
ImageUtils.display(iconIv, contentCardEntity.icon)
redDotTv.goneIf(!((contentCardEntity.link.type == TYPE_SERVER && contentCardEntity.server?.total != 0) || contentCardEntity.link.type == TYPE_GIFT)) {
if ((contentCardEntity.link.type == TYPE_SERVER) && (contentCardEntity.server?.calendar?.isNotEmpty() == true))
redDotTv.text = contentCardEntity.server?.total.toString()
if ((contentCardEntity.link.type == TYPE_GIFT) && (contentCardEntity.libao != null))
redDotTv.text = contentCardEntity.libao?.total.toString()
val showRedDot = contentCardEntity.redDot != 0 && viewModel.shouldShowContentCardRedDot(contentCardEntity)
redDotTv.goneIf(!showRedDot) {
redDotTv.text = contentCardEntity.redDot.toString()
}
val showNewTag = contentCardEntity.link.type == TYPE_ARCHIVE && contentCardEntity.archive != null && contentCardEntity.showNewTag
@ -116,11 +112,11 @@ class GameDetailContentCardTripleItemViewHolder(
confirmText = context.getString(com.gh.gamecenter.common.R.string.confirm),
cancelText = context.getString(com.gh.gamecenter.common.R.string.cancel),
confirmClickCallback = {
jumpToContentCardLink(context, contentCardEntity, viewModel)
jumpToContentCardLink(context, contentCardEntity, viewModel, redDotTv)
}
)
} else {
jumpToContentCardLink(context, contentCardEntity, viewModel)
jumpToContentCardLink(context, contentCardEntity, viewModel, redDotTv)
}
}

View File

@ -9,19 +9,29 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.common.base.fragment.BaseBottomDialogFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.databinding.DialogGameDetailRecyclerViewBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.gamedetail.entity.GameDetailInfoTag
import com.lightgame.utils.AppManager
class GameFunctionDialogFragment: BaseBottomDialogFragment<DialogGameDetailRecyclerViewBinding>() {
private var infoTags: List<GameDetailInfoTag.InfoTag> = arrayListOf()
private var linkEntity: LinkEntity? = null
private var gameEntity: GameEntity? = null
private val adapter by lazy { GameFunctionAdapter(requireContext(), infoTags) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
gameEntity = arguments?.getParcelable(EntranceConsts.KEY_GAME_ENTITY)
infoTags = arguments?.getParcelableArrayList(KEY_INFO_TAG) ?: arrayListOf()
linkEntity = arguments?.getParcelable(EntranceConsts.KEY_LINK)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -30,6 +40,28 @@ class GameFunctionDialogFragment: BaseBottomDialogFragment<DialogGameDetailRecyc
mBinding.closeIv.setOnClickListener {
dismissAllowingStateLoss()
}
mBinding.subtitleTv.goneIf(linkEntity == null) {
mBinding.subtitleTv.text = linkEntity?.text
mBinding.subtitleTv.setOnClickListener { _ ->
linkEntity?.let {
DirectUtils.directToLinkPage(requireContext(), it, "游戏详情-功能说明", "")
SensorsBridge.trackGameDetailModuleClick(
gameEntity?.id,
gameEntity?.name,
gameEntity?.categoryChinese,
"组件内容",
"功能标签",
"功能标签",
null,
subModuleName = "跳转入口",
linkType = it.type,
linkId = it.link,
linkText = it.text,
gameStatus = null
)
}
}
}
mBinding.recyclerView.run {
layoutManager = LinearLayoutManager(requireContext())
adapter = this@GameFunctionDialogFragment.adapter
@ -46,7 +78,7 @@ class GameFunctionDialogFragment: BaseBottomDialogFragment<DialogGameDetailRecyc
const val KEY_INFO_TAG = "info_tag"
@JvmStatic
fun show(context: Context?, infoTags: List<GameDetailInfoTag.InfoTag>) {
fun show(context: Context?, gameEntity: GameEntity?, infoTags: List<GameDetailInfoTag.InfoTag>, linkEntity: LinkEntity?) {
val fragmentActivity: FragmentActivity = if (context is FragmentActivity) {
context
} else if (BuildConfig.DEBUG) {
@ -65,7 +97,9 @@ class GameFunctionDialogFragment: BaseBottomDialogFragment<DialogGameDetailRecyc
val dialogFragment = GameFunctionDialogFragment()
dialogFragment.arguments = bundleOf(
KEY_INFO_TAG to infoTags
EntranceConsts.KEY_GAME_ENTITY to gameEntity,
KEY_INFO_TAG to infoTags,
EntranceConsts.KEY_LINK to linkEntity
)
dialogFragment.show(fragmentActivity.supportFragmentManager, GameFunctionDialogFragment::class.java.name)
}

View File

@ -31,6 +31,13 @@ class ContentCardEntity(
var showNewTag: Boolean = false,
) {
val redDot
get() = when (link.type) {
TYPE_SERVER -> server?.total ?: 0
TYPE_GIFT -> libao?.total ?: 0
else -> 0
}
@Keep
class Dialog(
@SerializedName("_id")

View File

@ -219,7 +219,9 @@ data class GameDetailInfoTag(
@SerializedName("info_tags")
val infoTags: List<InfoTag> = listOf(), // 功能标签
@SerializedName("request_speed_status")
val requestSpeedStatus: String = "" // 求加速状态, on/off
val requestSpeedStatus: String = "", // 求加速状态, on/off
@SerializedName("plugin_tutorial_link")
val link: LinkEntity? = null, // 插件教程链接
) {
@Parcelize
data class InfoTag(

View File

@ -30,7 +30,9 @@ import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.CustomLinkMovementMethod
import com.gh.gamecenter.common.view.DrawableView
import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.core.utils.CenterImageSpan
import com.gh.gamecenter.core.utils.NumberUtils
import com.gh.gamecenter.core.utils.SpanBuilder
import com.gh.gamecenter.databinding.RatingCommentItemBinding
import com.gh.gamecenter.entity.RatingComment
import com.gh.gamecenter.feature.entity.GameEntity
@ -249,7 +251,7 @@ class RatingCommentItemViewHolder(val binding: RatingCommentItemBinding, val pat
if (game.getApk().size > 0 && game.getApk()[0].version == commentData.gameVersion) {
version.text = "当前版本"
} else {
version.text = ("版本:" + commentData.gameVersion)
version.text = commentData.gameVersion
}
}
}

View File

@ -7,6 +7,7 @@ import android.text.Spanned
import android.text.TextPaint
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
@ -15,6 +16,7 @@ import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.content.ContextCompat
import androidx.core.view.updateLayoutParams
import com.gh.common.util.*
import com.gh.common.util.NewFlatLogUtils
import com.gh.common.util.NewLogUtils
@ -27,7 +29,8 @@ 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.view.DrawableView
import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.core.utils.NumberUtils
import com.gh.gamecenter.core.utils.SpanBuilder
import com.gh.gamecenter.databinding.ItemArticleDetailCommentBinding
import com.gh.gamecenter.entity.RatingComment
import com.gh.gamecenter.gamedetail.rating.edit.RatingEditActivity
@ -165,7 +168,36 @@ class RatingDetailCommentItemViewHolder(val binding: ItemArticleDetailCommentBin
if (game != null && game.getApk().size > 0 && game.getApk()[0].version == commentData.gameVersion) {
version.text = "当前版本"
} else {
version.text = ("版本:" + commentData.gameVersion)
version.text = commentData.gameVersion
}
version.buttonDrawable = R.drawable.ic_version.toDrawable(context)
version.post {
ConstraintSet().apply {
clone(bottomContainer)
if ((version.layout?.lineCount ?: 1) > 1) {
version.gravity = Gravity.TOP
connect(version.id, ConstraintSet.START, device.id, ConstraintSet.START)
connect(version.id, ConstraintSet.TOP, device.id, ConstraintSet.BOTTOM, 6F.dip2px())
connect(version.id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM, 16F.dip2px())
connect(device.id, ConstraintSet.BOTTOM, version.id, ConstraintSet.TOP)
connect(device.id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP, 16F.dip2px())
clear(device.id, ConstraintSet.END)
clear(likeCountTv.id, ConstraintSet.TOP)
connect(likeCountTv.id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM, 4F.dip2px())
bottomContainer.updateLayoutParams<ConstraintLayout.LayoutParams> { height = ConstraintLayout.LayoutParams.WRAP_CONTENT }
} else {
version.gravity = Gravity.CENTER_VERTICAL
connect(version.id, ConstraintSet.START, device.id, ConstraintSet.END)
connect(version.id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP, 0)
connect(version.id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM, 0)
connect(device.id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM)
connect(device.id, ConstraintSet.END, version.id, ConstraintSet.START)
connect(device.id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP, 0)
connect(likeCountTv.id, ConstraintSet.TOP, timeTv.id, ConstraintSet.TOP, 0)
connect(likeCountTv.id, ConstraintSet.BOTTOM, timeTv.id, ConstraintSet.BOTTOM, 0)
bottomContainer.updateLayoutParams<ConstraintLayout.LayoutParams> { height = 48F.dip2px() }
}
}.applyTo(bottomContainer)
}
if (commentData.me.isCommented) {

View File

@ -50,6 +50,7 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS
private var mLastGetContentLengthTime = 0L
var showOrHideCoverFilter: ((Boolean) -> Unit)? = null
var scrollCalculatorHelper: GameDetailScrollCalculatorHelper? = null
init {
post {
@ -256,6 +257,7 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS
// 不需要弹弹窗,直接播放
override fun showWifiDialog() {
scrollCalculatorHelper?.currentPlayer = this
startPlayLogic(false)
//val trafficVideo = PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SettingsFragment.TRAFFIC_VIDEO_SP_KEY, false)
//if (trafficVideo) {

View File

@ -91,7 +91,8 @@ class CustomGamePluginAdapter(
binding.gameRating.textSize =
(if (gameEntity.commentCount > 3) 12 else 10).toFloat()
BindingAdapters.setGameName(binding.gameName, gameEntity, true)
BindingAdapters.setGameTags(binding.labelList, gameEntity, "")
binding.gamePlayCount.visibility = View.GONE
BindingAdapters.setGameTagsWithSellingPoint(binding.labelList, binding.layoutSellingPoints, gameEntity, "")
binding.gameRating.setDrawableStart(
if (gameEntity.commentCount > 3) com.gh.gamecenter.feature.R.drawable.game_horizontal_rating.toDrawable() else null,
null,

View File

@ -133,7 +133,8 @@ class CustomGameRefreshVerticalAdapter(
with(ui) {
gameNameTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
serverTypeTv.setTextColor(com.gh.gamecenter.common.R.color.primary_theme.toColor(context))
downloadTv.background = com.gh.gamecenter.common.R.drawable.download_button_normal_style.toDrawable(context)
downloadTv.background =
com.gh.gamecenter.common.R.drawable.download_button_normal_style.toDrawable(context)
gameDesTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
BindingAdapters.setGameName(
@ -142,7 +143,7 @@ class CustomGameRefreshVerticalAdapter(
false
)
BindingAdapters.setGame(iconIv, gameEntity)
BindingAdapters.setGameTags(gameTagContainer, gameEntity, "")
BindingAdapters.setGameTagsWithSellingPoint(gameTagContainer, sellingPointsBinding, gameEntity, "")
GameItemViewHolder.initServerType(gameNameTv, serverTypeTv, gameEntity)
gameDesTv.text = gameEntity.decoratedDes
GameItemViewHolder.initGameSubtitleAndAdLabel(

View File

@ -47,8 +47,16 @@ class WGameSubjectCPMRemoteDataSource(
miniGameExtData = info.extData,
miniGameRecommendId = info.recommendID,
mTagStyle = arrayListOf(
TagStyleEntity(name = info.categoryName),
TagStyleEntity(name = info.subcategoryName)
TagStyleEntity(
name = info.categoryName,
color = TAG_COLOR,
background = TAG_BACKGROUND
),
TagStyleEntity(
name = info.subcategoryName,
color = TAG_COLOR,
background = TAG_BACKGROUND
)
)
)
}.toMutableList()
@ -101,4 +109,10 @@ class WGameSubjectCPMRemoteDataSource(
}
}
}
companion object {
private const val TAG_COLOR = "999999"
private const val TAG_BACKGROUND = "F5F5F5"
}
}

View File

@ -527,7 +527,7 @@ class SearchGameResultAdapter(
for (key in positionAndPackageMap.keys) {
if (key.contains(download.packageName) && key.contains(download.gameId)) {
val position = positionAndPackageMap[key]
if (position != null && getItemViewType(position) == ItemViewType.GAME_SUBJECT) {
if (position != null && position < itemCount && getItemViewType(position) == ItemViewType.GAME_SUBJECT) {
val view = _recyclerView?.layoutManager?.findViewByPosition(position)
val adapter = view?.findViewById<RecyclerView>(R.id.subjectRv)?.adapter
if (adapter != null && adapter is SearchSubjectAdapter) {

View File

@ -97,9 +97,11 @@ class SearchGameResultViewModel(
// 数据源来自于第三方的专题列表,包括 CPM 专题和 DSP 专题
val thirdPartySearchSubjectList = mutableListOf<SearchSubjectEntity>()
// 避免重复添加同一个 location 位置的专题
for (item in mutableList) {
if (!mSearchSubjects.any { it.location == item.location }) {
// 避免同一个位置重复的专题
if (!mSearchSubjects.any {
it.location == item.location && it.columnId == item.columnId
}) {
mSearchSubjects.add(item)
}
}

View File

@ -42,6 +42,7 @@ class SubjectAdapter(
private val mSubjectViewModel: SubjectViewModel?,
private val mEntrance: String?,
private val mIsColumnCollection: Boolean,
private val mColumnCollectionCustomPosition: Int = -1,
private val mCollectionId: String,
private val mCollectionName: String,
private val mCollectionStyle: String
@ -217,9 +218,10 @@ class SubjectAdapter(
mSubjectViewModel?.subjectSettingLD?.value?.let { subjectSetting ->
subjectStyle = subjectSetting.typeEntity.layout
isFilterOn = subjectSetting.filter == "on"
isFilterOn = subjectSetting.isFilterEnabled
}
gameEntity.sequence = position
val exposureEvent = generateExposureEvent(gameEntity, subjectStyle, isFilterOn)
val humanReadablePosition = position + 1
mExposureEventSparseArray.put(position, exposureEvent)
@ -266,7 +268,10 @@ class SubjectAdapter(
gameColumnName = subjectData.subjectName ?: "",
gameId = gameEntity.id,
gameName = gameEntity.name ?: "",
text = "游戏"
text = "游戏",
gameTag = mViewModel.selectedLabelList.ifEmpty { listOf(mViewModel.subjectData.tag) },
sort = mViewModel.subjectData.sortChinese,
inclusionSize = mViewModel.selectedFilterSize.text ?: ""
)
}
}
@ -326,7 +331,10 @@ class SubjectAdapter(
gameColumnName = subjectData.subjectName ?: "",
gameId = gameEntity.id,
gameName = gameEntity.name ?: "",
text = "按钮"
text = "按钮",
gameTag = mViewModel.selectedLabelList.ifEmpty { listOf(mViewModel.subjectData.tag) },
sort = mViewModel.subjectData.sortChinese,
inclusionSize = mViewModel.selectedFilterSize.text ?: ""
)
}
@ -446,7 +454,10 @@ class SubjectAdapter(
linkId = linkEntity.link ?: "",
linkText = linkEntity.text ?: "",
linkType = linkEntity.type ?: "",
text = "头图"
text = "头图",
gameTag = mViewModel.selectedLabelList.ifEmpty { listOf(mViewModel.subjectData.tag) },
sort = mViewModel.subjectData.sortChinese,
inclusionSize = mViewModel.selectedFilterSize.text ?: ""
)
}
}
@ -471,13 +482,12 @@ class SubjectAdapter(
"$mCollectionName+${CustomPageItem.collectionTypeToComponentName[mCollectionStyle] ?: mCollectionStyle}+$mCollectionId"
)
)
exposureSourceList.add(ExposureSource("合集详情"))
exposureSourceList.add(
ExposureSource(
"专题",
"${subjectData.subjectName}+${subjectData.subjectStyleChinese}+${subjectData.subjectId}"
)
)
exposureSourceList.add(ExposureSource(if (mColumnCollectionCustomPosition == -1) "合集详情" else "自定义合集详情"))
var value = "${subjectData.subjectName}+${subjectData.subjectStyleChinese}+${subjectData.subjectId}"
if (mColumnCollectionCustomPosition != -1) {
value += "+${mColumnCollectionCustomPosition}"
}
exposureSourceList.add(ExposureSource("专题", value))
} else {
var sourceString = ""
exposureSourceList.add(
@ -493,12 +503,7 @@ class SubjectAdapter(
sourceString = filter
if (isFilterOn) {
val sort = when {
subjectData.sort.contains("publish") -> "最新"
subjectData.sort.contains("star") -> "评分"
else -> "最热"
}
val sort = subjectData.sortChinese
val filterSize = mViewModel.selectedFilterSize.text
sourceString =
@ -516,12 +521,7 @@ class SubjectAdapter(
}
if (isFilterOn) {
val sort = when {
subjectData.sort.contains("publish") -> "最新"
subjectData.sort.contains("star") -> "评分"
else -> "推荐"
}
val sort = subjectData.sortChinese
val filterSize = mViewModel.selectedFilterSize.text
sourceString =
@ -532,12 +532,7 @@ class SubjectAdapter(
}
else -> {
val sort = when {
subjectData.sort.contains("publish") -> "最新"
subjectData.sort.contains("star") -> "评分"
else -> "推荐"
}
val sort = subjectData.sortChinese
val filterSize = mViewModel.selectedFilterSize.text
if (isFilterOn) {

View File

@ -125,7 +125,7 @@ open class SubjectFragment : LazyFragment() {
}
val bundle = arguments
bundle?.putParcelable(EntranceConsts.KEY_SUBJECT_DATA, mViewModel?.subjectData)
bundle?.putParcelable(SubjectSettingEntity::class.java.simpleName, entity)
bundle?.putParcelable(EntranceConsts.KEY_SUBJECT_SETTING_DATA, entity)
fragment.arguments = bundle
transaction.replace(R.id.subject_content, fragment, tag)
transaction.commitAllowingStateLoss()

View File

@ -6,6 +6,8 @@ import androidx.recyclerview.widget.RecyclerView
import com.ethanhua.skeleton.Skeleton
import com.gh.common.exposure.ExposureListener
import com.gh.common.util.DialogUtils
import com.gh.common.view.ConfigFilterView
import com.gh.common.view.ConfigFilterView.SortType
import com.gh.common.xapk.XapkInstaller
import com.gh.common.xapk.XapkUnzipStatus
import com.gh.download.DownloadManager
@ -14,10 +16,12 @@ import com.gh.gamecenter.common.baselist.LazyListFragment
import com.gh.gamecenter.common.baselist.LoadType
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.viewModelProviderFromParent
import com.gh.gamecenter.common.view.SpacingItemDecoration
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.core.utils.UrlFilterUtils
import com.gh.gamecenter.entity.SubjectData
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.gh.gamecenter.eventbus.EBDownloadStatus
@ -43,6 +47,8 @@ open class SubjectListFragment : LazyListFragment<GameEntity, SubjectListViewMod
private var mCollectionName = ""
private var mCollectionStyle = ""
private var mScrollTop = false
private var mMaxSize = ""
private var mMinSize = ""
var selectedLabelList = arrayListOf<String>()
@ -78,6 +84,7 @@ open class SubjectListFragment : LazyListFragment<GameEntity, SubjectListViewMod
mSubjectViewModel,
arguments?.getString(EntranceConsts.KEY_ENTRANCE),
mIsColumnCollection,
arguments?.getInt(EntranceConsts.KEY_COLUMN_COLLECTION_CUSTOM_POSITION, -1) ?: -1,
mCollectionId,
mCollectionName,
mCollectionStyle
@ -119,10 +126,14 @@ open class SubjectListFragment : LazyListFragment<GameEntity, SubjectListViewMod
mCollectionName = arguments?.getString(EntranceConsts.KEY_COLUMN_COLLECTION_NAME, "") ?: ""
mCollectionStyle = arguments?.getString(EntranceConsts.KEY_COLUMN_COLLECTION_STYLE, "") ?: ""
super.onFragmentFirstVisible()
(mListViewModel as SubjectListViewModel).lastPageDataMap =
arguments?.get(SubjectFragment.LAST_PAGE_DATA) as? HashMap<String, String>
(mListViewModel as SubjectListViewModel).run {
lastPageDataMap =
arguments?.get(SubjectFragment.LAST_PAGE_DATA) as? HashMap<String, String>
arguments?.getString(EntranceConsts.KEY_SUBJECT_SORT_SIZE)?.let { selectedFilterSize = SubjectSettingEntity.Size(text = it) }
}
mCachedView?.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_surface.toColor(requireContext()))
initFilterView()
val skeletonLayoutId = when (arguments?.getString(EntranceConsts.KEY_SUBJECT_TYPE) ?: "") {
"detail" -> R.layout.fragment_subject_detail_skeleton
"tab" -> R.layout.fragment_subject_tab_skeleton
@ -140,7 +151,62 @@ open class SubjectListFragment : LazyListFragment<GameEntity, SubjectListViewMod
mListViewModel.load(LoadType.REFRESH)
}
override fun getRealLayoutId() = R.layout.fragment_list_base_skeleton
private fun initFilterView() {
val subjectSetting= arguments?.getParcelable<SubjectSettingEntity>(EntranceConsts.KEY_SUBJECT_SETTING_DATA) ?: return
val originalFilter = mListViewModel.subjectData.filter
mCachedView?.findViewById<ConfigFilterView>(R.id.filterView)?.run {
goneIf(!subjectSetting.isFilterEnabled) {
initSubjectFilterView(subjectSetting)
when (updateTv.text) {
"更新" -> mListViewModel.subjectData.sort = UrlFilterUtils.getFilterQuery("update_time", "-1")
"最新" -> mListViewModel.subjectData.sort = UrlFilterUtils.getFilterQuery("publish", "-1")
"评分" -> mListViewModel.subjectData.sort = UrlFilterUtils.getFilterQuery("star", "-1")
else -> mListViewModel.subjectData.sort = UrlFilterUtils.getFilterQuery("position", "1")
}
setOnConfigSetupListener(object :
ConfigFilterView.OnConfigFilterSetupListener {
override fun onShowSortSize() {}
override fun onSetupSortSize(sortSize: SubjectSettingEntity.Size) {
mMinSize = sortSize.min.toString()
mMaxSize = sortSize.max.toString()
var filter = originalFilter.ifEmpty { "type:全部" }
if (mMinSize.isNotEmpty() && mMinSize != "-1") {
filter += ",${UrlFilterUtils.getFilterQuery("min_size", mMinSize)}"
}
if (mMaxSize.isNotEmpty() && mMaxSize != "-1") {
filter += ",${UrlFilterUtils.getFilterQuery("max_size", mMaxSize)}"
}
refreshPage(filter = filter, size = sortSize)
}
override fun onSetupSortType(sortType: SortType) {
val clickTv = when (sortType) {
SortType.RATING -> ratingTv
SortType.NEWEST -> newestTv
SortType.UPDATE -> updateTv
else -> recommendedTv
}
val sort = when (clickTv.text) {
"更新" -> UrlFilterUtils.getFilterQuery("update_time", "-1")
"最新" -> UrlFilterUtils.getFilterQuery("publish", "-1")
"评分" -> UrlFilterUtils.getFilterQuery("star", "-1")
else -> UrlFilterUtils.getFilterQuery("position", "1")
}
if (mListViewModel.subjectData.sort != sort) {
refreshPage(sort = sort)
}
}
})
}
}
}
override fun getRealLayoutId() = R.layout.fragment_subject_list
override fun initRealView() {
super.initRealView()
@ -160,7 +226,11 @@ open class SubjectListFragment : LazyListFragment<GameEntity, SubjectListViewMod
mListRv?.addOnScrollListener(mExposureListener)
}
fun refreshPage(filter: String, sort: String, size: SubjectSettingEntity.Size) {
fun refreshPage(
filter: String = mListViewModel.subjectData.filter,
sort: String = mListViewModel.subjectData.sort,
size: SubjectSettingEntity.Size = mListViewModel.selectedFilterSize
) {
mListViewModel.selectedFilterSize = size
mListViewModel.selectedLabelList = selectedLabelList
mListViewModel.subjectData.filter = filter
@ -181,10 +251,6 @@ open class SubjectListFragment : LazyListFragment<GameEntity, SubjectListViewMod
}
}
override fun onResume() {
super.onResume()
}
override fun onFragmentResume() {
super.onFragmentResume()
DownloadManager.getInstance().addObserver(dataWatcher)
@ -229,5 +295,10 @@ open class SubjectListFragment : LazyListFragment<GameEntity, SubjectListViewMod
override fun onDarkModeChanged() {
super.onDarkModeChanged()
mAdapter?.let { it.notifyItemRangeChanged(0, it.itemCount) }
mCachedView?.findViewById<ConfigFilterView>(R.id.filterView)?.run {
if (isVisible) {
updateAllTextView()
}
}
}
}

View File

@ -28,7 +28,7 @@ open class SubjectListViewModel(
var exposureSourceList: List<ExposureSource>?
) : ListViewModel<GameEntity, GameEntity>(application) {
// 供专题类型为 rows 时统计用
// 供统计用
var selectedLabelList = arrayListOf<String>()
var selectedFilterSize = SubjectSettingEntity.Size(text = "全部大小")

View File

@ -1,17 +1,22 @@
package com.gh.gamecenter.subject.rows
import android.annotation.SuppressLint
import android.graphics.Typeface
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.widget.CheckedTextView
import android.widget.LinearLayout
import androidx.core.view.children
import androidx.core.view.updateLayoutParams
import com.gh.common.view.ConfigFilterView
import com.gh.common.view.ConfigFilterView.SortType
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.BaseFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.UrlFilterUtils
import com.gh.gamecenter.entity.SubjectData
@ -32,13 +37,15 @@ class SubjectRowsFragment : BaseFragment<Any>() {
// 统计用的大小筛选选中文案
private var mSelectedFilterSize = SubjectSettingEntity.Size(text = "全部大小")
private lateinit var filterView: ConfigFilterView
override fun getLayoutId(): Int {
return R.layout.fragment_subject_rows
}
override fun initView(view: View) {
super.initView(view)
mSettingEntity = arguments?.getParcelable(SubjectSettingEntity::class.java.simpleName)
mSettingEntity = arguments?.getParcelable(EntranceConsts.KEY_SUBJECT_SETTING_DATA)
?: return
mSubjectData = arguments?.getParcelable(EntranceConsts.KEY_SUBJECT_DATA) ?: return
@ -55,7 +62,7 @@ class SubjectRowsFragment : BaseFragment<Any>() {
mListFragment =
childFragmentManager.findFragmentByTag(SubjectListFragment::class.java.name) as? SubjectListFragment
?: SubjectListFragment()
mListFragment.arguments = arguments
mListFragment.arguments = (arguments?.clone() as? Bundle)?.apply { remove(EntranceConsts.KEY_SUBJECT_SETTING_DATA) }
childFragmentManager.beginTransaction()
.replace(R.id.rows_list_container, mListFragment, SubjectListFragment::class.java.name)
.commitAllowingStateLoss()
@ -64,18 +71,29 @@ class SubjectRowsFragment : BaseFragment<Any>() {
@SuppressLint("InflateParams")
private fun createLabelsLayout() {
val inflater = LayoutInflater.from(context)
for (labels in mSettingEntity.typeEntity.labels) {
mSettingEntity.typeEntity.labels.forEachIndexed { index, labels ->
val labelsItem = inflater.inflate(R.layout.subject_rows_label, mLabelsContainer, false)
val labelContainer = labelsItem.findViewById<LinearLayout>(R.id.label_container)
for (index in 0 until labels.label.size) {
val labelName = labels.label[index]
labelsItem.updateLayoutParams<MarginLayoutParams> {
topMargin = if (index == 0) 0 else 12F.dip2px()
}
for (labelIndex in 0 until labels.label.size) {
val labelName = labels.label[labelIndex]
val labelItem =
inflater.inflate(R.layout.subject_rows_label_item, labelContainer, false)
val label = labelItem.findViewById<CheckedTextView>(R.id.label)
if (index == 0) label.isChecked = true
label.updateLayoutParams<MarginLayoutParams> {
leftMargin = if (labelIndex == 0) 0 else 8F.dip2px()
}
if (labelIndex == 0) {
label.isChecked = true
label.setTypeface(Typeface.DEFAULT, Typeface.BOLD)
} else {
label.setTypeface(Typeface.DEFAULT, Typeface.NORMAL)
}
label.text = labelName
label.setOnClickListener {
mFilterMap[labels.name] = if (index == 0) {
mFilterMap[labels.name] = if (labelIndex == 0) {
""
} else {
labelName
@ -92,26 +110,18 @@ class SubjectRowsFragment : BaseFragment<Any>() {
private fun selectLabel(labelContainer: LinearLayout, label: CheckedTextView) {
for (i in 0 until labelContainer.childCount) {
val view = labelContainer.getChildAt(i)
if (view is CheckedTextView && view.isChecked) view.isChecked = false
if (view is CheckedTextView && view.isChecked) {
view.isChecked = false
view.setTypeface(Typeface.DEFAULT, Typeface.NORMAL)
}
}
label.isChecked = true
label.setTypeface(Typeface.DEFAULT, Typeface.BOLD)
}
private fun createFilterLayout() {
if (mSettingEntity.filter == "on") {
val filterView = ConfigFilterView(requireContext())
filterView.ratingTv.visibility = View.VISIBLE
// 重排序
mSettingEntity.filterOptions.forEachIndexed { index, s ->
when (index) {
0 -> filterView.updateTv.text = s
1 -> filterView.recommendedTv.text = s
2 -> filterView.newestTv.text = s
3 -> filterView.ratingTv.text = s
}
}
if (mSettingEntity.isFilterEnabled) {
filterView = ConfigFilterView(requireContext()).apply { initSubjectFilterView(mSettingEntity) }
// 设置默认页面链接
when (filterView.updateTv.text) {
"更新" -> mSubjectData.sort = UrlFilterUtils.getFilterQuery("update_time", "-1")
@ -119,15 +129,14 @@ class SubjectRowsFragment : BaseFragment<Any>() {
"评分" -> mSubjectData.sort = UrlFilterUtils.getFilterQuery("star", "-1")
else -> mSubjectData.sort = UrlFilterUtils.getFilterQuery("position", "1")
}
// 隐藏相关选项
filterView.updateTv.goneIf(mSettingEntity.filterOptions.isEmpty())
filterView.recommendedTv.goneIf(mSettingEntity.filterOptions.size <= 1)
filterView.newestTv.goneIf(mSettingEntity.filterOptions.size <= 2)
filterView.ratingTv.goneIf(mSettingEntity.filterOptions.size <= 3)
filterView.sizeFilterArray = mSettingEntity.filterSizes
mLabelsContainer.addView(filterView)
mLabelsContainer.addView(
filterView,
LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).apply {
topMargin = 8F.dip2px()
})
filterView.setOnConfigSetupListener(object :
ConfigFilterView.OnConfigFilterSetupListener {
@ -150,11 +159,11 @@ class SubjectRowsFragment : BaseFragment<Any>() {
)
}
override fun onSetupSortType(sortType: ConfigFilterView.SortType) {
override fun onSetupSortType(sortType: SortType) {
val clickTv = when (sortType) {
ConfigFilterView.SortType.RATING -> filterView.ratingTv
ConfigFilterView.SortType.NEWEST -> filterView.newestTv
ConfigFilterView.SortType.UPDATE -> filterView.updateTv
SortType.RATING -> filterView.ratingTv
SortType.NEWEST -> filterView.newestTv
SortType.UPDATE -> filterView.updateTv
else -> filterView.recommendedTv
}
@ -171,6 +180,8 @@ class SubjectRowsFragment : BaseFragment<Any>() {
}
}
})
} else {
mLabelsContainer.setPadding(0, 8F.dip2px(), 0, 8F.dip2px())
}
}
@ -242,4 +253,11 @@ class SubjectRowsFragment : BaseFragment<Any>() {
mListFragment.selectedLabelList = findAllSelectedLabel(mLabelsContainer)
mListFragment.refreshPage(mSubjectData.filter, mSubjectData.sort, mSelectedFilterSize)
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
if (::filterView.isInitialized) {
filterView.updateAllTextView()
}
}
}

View File

@ -0,0 +1,97 @@
package com.gh.gamecenter.subject.tab
import android.app.Application
import android.content.Context
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.toJson
import com.gh.gamecenter.core.utils.GsonUtils
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.entity.SubjectData
import com.gh.gamecenter.livedata.Event
import com.halo.assistant.HaloApp
class ColumnCollectionTabViewModel(
application: Application,
private val columnCollectionId: String,
private val customSize: Int,
private val subjectDataList: List<SubjectData>
) : AndroidViewModel(application) {
private val columnCollectionSp by lazy {
HaloApp.getInstance().getSharedPreferences(Constants.SP_COLUMN_COLLECTION_CUSTOM_TAB, Context.MODE_PRIVATE)
}
val tabListLiveData = MutableLiveData<List<SubjectData>>()
val followListLiveData = MutableLiveData<List<SubjectData>>()
val moreListLiveData = MutableLiveData<List<SubjectData>>()
val addFollowLiveData = MutableLiveData<Event<SubjectData>>()
val addMoreLiveData = MutableLiveData<Event<SubjectData>>()
init {
initData()
}
private fun initData() {
val customTabIdList = GsonUtils.fromJsonList<String>(SPUtils.getString(columnCollectionSp, columnCollectionId))
if (customTabIdList.isEmpty()) {
val followTabList = subjectDataList.take(customSize)
tabListLiveData.postValue(followTabList)
followListLiveData.postValue(followTabList)
moreListLiveData.postValue(subjectDataList - followTabList.toSet())
} else {
val newAddedForbiddenList = subjectDataList.filter { it.isForbidden && it.subjectId !in customTabIdList }
val tabList = arrayListOf<SubjectData>().apply { addAll(newAddedForbiddenList) }
customTabIdList.forEach { subjectId->
subjectDataList.find { subjectId == it.subjectId }?.let {
tabList.add(it)
}
}
val followTabList = tabList.take(CUSTOM_MAX_TAB).ifEmpty { subjectDataList.take(customSize) }
tabListLiveData.postValue(followTabList)
followListLiveData.postValue(followTabList)
moreListLiveData.postValue(subjectDataList - followTabList.toSet())
}
}
fun addFollow(subjectData: SubjectData) {
addFollowLiveData.postValue(Event(subjectData))
}
fun addMore(subjectData: SubjectData) {
addMoreLiveData.postValue(Event(subjectData))
}
fun saveCustomTab(followTabList: List<SubjectData>) {
tabListLiveData.postValue(followTabList)
followListLiveData.postValue(followTabList)
moreListLiveData.postValue(subjectDataList - followTabList.toSet())
val followSubjectIdList = followTabList.map { it.subjectId ?: "" }.distinct()
SPUtils.setString(columnCollectionSp, columnCollectionId, followSubjectIdList.toJson())
}
companion object {
const val CUSTOM_MAX_TAB = 15
}
class Factory(
private val columnCollectionId: String,
private val customSize: Int,
private val subjectDataList: List<SubjectData>
) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ColumnCollectionTabViewModel(
HaloApp.getInstance().application,
columnCollectionId,
customSize,
subjectDataList
) as T
}
}
}

View File

@ -0,0 +1,56 @@
package com.gh.gamecenter.subject.tab
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.view.Chips
import com.gh.gamecenter.databinding.ItemColumnCollectionCustomTabBinding
import com.gh.gamecenter.entity.SubjectData
class CustomTabFollowAdapter(private val viewModel: ColumnCollectionTabViewModel?) :
ListAdapter<SubjectData, CustomTabFollowAdapter.CustomTabFollowItemViewHolder>(createDiffItemCallBack()) {
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): CustomTabFollowItemViewHolder {
return CustomTabFollowItemViewHolder(parent.toBinding())
}
override fun onBindViewHolder(holder: CustomTabFollowItemViewHolder, position: Int) {
val subjectData = currentList.getOrNull(position) ?: return
val isForbidden = subjectData.isForbidden
holder.binding.chips.run {
setState(if (isForbidden) Chips.STATE_FILL else Chips.STATE_DEFAULT)
setText(subjectData.subjectName ?: "")
endIcon.isVisible = !isForbidden
endIcon.setImageResource(R.drawable.ic_basic_x_8_secondary)
endIcon.imageTintList = null
setOnClickListener {
if (!isForbidden) {
viewModel?.addMore(subjectData)
}
}
}
}
class CustomTabFollowItemViewHolder(val binding: ItemColumnCollectionCustomTabBinding) :
ViewHolder(binding.root)
companion object {
fun createDiffItemCallBack() = object : ItemCallback<SubjectData>() {
override fun areItemsTheSame(oldItem: SubjectData, newItem: SubjectData): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: SubjectData, newItem: SubjectData): Boolean {
return oldItem == newItem
}
}
}
}

View File

@ -0,0 +1,49 @@
package com.gh.gamecenter.subject.tab
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.view.Chips
import com.gh.gamecenter.databinding.ItemColumnCollectionCustomTabBinding
import com.gh.gamecenter.entity.SubjectData
class CustomTabMoreAdapter(private val viewModel: ColumnCollectionTabViewModel?) :
ListAdapter<SubjectData, CustomTabMoreAdapter.CustomTabMoreItemViewHolder>(createDiffItemCallBack()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomTabMoreItemViewHolder {
return CustomTabMoreItemViewHolder(parent.toBinding())
}
override fun onBindViewHolder(holder: CustomTabMoreItemViewHolder, position: Int) {
val subjectData = currentList.getOrNull(position) ?: return
holder.binding.chips.run {
setState(Chips.STATE_DEFAULT)
setText(subjectData.subjectName ?: "")
startIcon.isVisible = true
startIcon.setImageResource(R.drawable.ic_auxiliary_plus_secondary_8)
startIcon.imageTintList = null
setOnClickListener {
viewModel?.addFollow(subjectData)
}
}
}
class CustomTabMoreItemViewHolder(val binding: ItemColumnCollectionCustomTabBinding) : ViewHolder(binding.root)
companion object {
fun createDiffItemCallBack() = object : ItemCallback<SubjectData>() {
override fun areItemsTheSame(oldItem: SubjectData, newItem: SubjectData): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: SubjectData, newItem: SubjectData): Boolean {
return oldItem == newItem
}
}
}
}

View File

@ -1,56 +1,154 @@
package com.gh.gamecenter.subject.tab
import android.animation.AnimatorSet
import android.animation.ValueAnimator
import android.graphics.Typeface
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.view.animation.AccelerateDecelerateInterpolator
import android.widget.CheckedTextView
import android.widget.TextView
import android.widget.FrameLayout
import android.widget.LinearLayout
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.content.ContextCompat
import androidx.core.graphics.ColorUtils
import androidx.core.view.isInvisible
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentStatePagerAdapter
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager.widget.ViewPager
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.BaseFragment
import com.gh.gamecenter.common.base.fragment.ToolbarFragment
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.toDrawable
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.UrlFilterUtils
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.utils.DialogHelper.Config
import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.databinding.FragmentSubjectTabBinding
import com.gh.gamecenter.databinding.LayoutColumnCollectionSettingBinding
import com.gh.gamecenter.databinding.TabItemMainBinding
import com.gh.gamecenter.entity.SubjectData
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.gh.gamecenter.home.custom.model.CustomPageItem
import com.gh.gamecenter.livedata.EventObserver
import com.gh.gamecenter.subject.SubjectFragment
import com.gh.gamecenter.subject.SubjectListFragment
import com.google.android.flexbox.FlexboxLayoutManager
import com.google.android.flexbox.JustifyContent
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
import org.json.JSONException
import org.json.JSONObject
import java.util.*
import kotlin.math.abs
import kotlin.math.roundToInt
class SubjectTabFragment : BaseFragment<Any>() {
class SubjectTabFragment : ToolbarFragment() {
private val mBinding by lazy { FragmentSubjectTabBinding.inflate(layoutInflater) }
private val binding by lazy { FragmentSubjectTabBinding.inflate(layoutInflater) }
private var settingBinding: LayoutColumnCollectionSettingBinding? = null
private var isSubject = false
private var isCustomEnabled = false
private var tabStyle = TabStyle.SUBJECT_NORMAL
private var tabBindingList = arrayListOf<TabItemMainBinding>()
private var isSettingAnimating = false
private var columnCollectionTabViewModel: ColumnCollectionTabViewModel? = null
private val fragmentTag by lazy { "android:switcher:${binding.subjectViewpager.id}:" }
private var collectionId = ""
private var collectionName = ""
private var collectionStyle = ""
private val followAdapter by lazy { CustomTabFollowAdapter(columnCollectionTabViewModel) }
private val moreAdapter by lazy { CustomTabMoreAdapter(columnCollectionTabViewModel) }
private val showOrHideMore: (Boolean) -> Unit = { isShow ->
settingBinding?.run {
if (isShow == moreContainer.isVisible) return@run
val startHeight = if (isShow) 0 else moreContainer.height
val endHeight = if (isShow) {
moreContainer.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
moreContainer.measuredHeight
} else 0
ValueAnimator.ofInt(startHeight, endHeight).apply {
duration = ANIMATOR_DURATION
interpolator = AccelerateDecelerateInterpolator()
doOnStart {
if (isShow) {
moreContainer.updateLayoutParams<MarginLayoutParams> { height = 0 }
moreContainer.isVisible = true
} else {
moreContainer.isInvisible = true
}
}
doOnEnd {
if (isShow) {
moreContainer.updateLayoutParams<MarginLayoutParams> { height = MarginLayoutParams.WRAP_CONTENT }
} else {
moreContainer.isVisible = false
}
}
addUpdateListener { animation ->
moreContainer.updateLayoutParams<MarginLayoutParams> { height = animation.animatedValue as Int }
}
}.start()
}
}
private val itemTouchHelper by lazy {
ItemTouchHelper(object : ItemTouchHelper.Callback() {
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
return makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN or ItemTouchHelper.START or ItemTouchHelper.END, 0)
}
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
val from = viewHolder.bindingAdapterPosition
val to = target.bindingAdapterPosition
val newList = followAdapter.currentList.toMutableList()
Collections.swap(newList, from, to)
followAdapter.submitList(newList)
return true
}
override fun isLongPressDragEnabled(): Boolean = true
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit
})
}
override fun getLayoutId() = 0
override fun getInflatedLayout() = mBinding.root
override fun getInflatedLayout() = binding.root
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val subjectList = arguments?.getParcelableArrayList<SubjectData>(EntranceConsts.KEY_DATA)
val subjectList = arguments?.getParcelableArrayList<SubjectData>(EntranceConsts.KEY_DATA) ?: arrayListOf()
isSubject = subjectList.isEmpty()
tabStyle = when {
mIsFromMainWrapper && !isSubject -> TabStyle.COLUMN_COLLECTION_MAIN_WRAPPER
isSubject -> TabStyle.SUBJECT_NORMAL
else -> TabStyle.COLUMN_COLLECTION_NORMAL
}
val fragments = ArrayList<Fragment>()
var tagList = arrayListOf<String>()
val fragmentTag = "android:switcher:${mBinding.subjectViewpager.id}:"
if (subjectList.isNullOrEmpty()) {
val tagList: ArrayList<String>
if (isSubject) {
// 专题详情
val subjectData = arguments?.getParcelable<SubjectData>(EntranceConsts.KEY_SUBJECT_DATA)
?: return
val settingsEntity =
arguments?.getParcelable<SubjectSettingEntity>(SubjectSettingEntity::class.java.simpleName)
arguments?.getParcelable<SubjectSettingEntity>(EntranceConsts.KEY_SUBJECT_SETTING_DATA)
?: return
tagList = settingsEntity.typeEntity.content as ArrayList<String>
if (tagList.size > 1) {
mBinding.subjectTabContainer.visibility = View.VISIBLE
binding.subjectTabContainer.visibility = View.VISIBLE
}
tagList.forEachIndexed { index, tag ->
val element = childFragmentManager.findFragmentByTag("${fragmentTag}$index")
@ -60,69 +158,192 @@ class SubjectTabFragment : BaseFragment<Any>() {
copyData.filter = if (tag == "全部") {
UrlFilterUtils.getFilterQuery("tags", tag, "type", "全部")
} else {
copyData.tag = tag
UrlFilterUtils.getFilterQuery("tags", tag)
}
bundle?.putParcelable(EntranceConsts.KEY_SUBJECT_DATA, copyData)
bundle?.putParcelable(EntranceConsts.KEY_SUBJECT_SETTING_DATA, settingsEntity)
element.arguments = bundle
fragments.add(element)
}
if (binding.subjectTabContainer.isVisible) {
binding.subjectTabIndicator.setupWithTabLayout(binding.subjectTab)
binding.subjectTabIndicator.setupWithViewPager(binding.subjectViewpager)
}
val adapter = object : FragmentStatePagerAdapter(childFragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
override fun getItem(position: Int): Fragment {
return fragments[position]
}
override fun getCount(): Int {
return fragments.size
}
override fun getPageTitle(position: Int): CharSequence {
return tagList[position]
}
override fun destroyItem(container: ViewGroup, position: Int, `object`: Any) {
// super.destroyItem(container, position, `object`)
}
}
binding.subjectViewpager.adapter = adapter
binding.subjectTab.setupWithViewPager(binding.subjectViewpager)
initTabLayout()
} else {
// 专题合集详情
mBinding.subjectTabContainer.visibility = View.VISIBLE
isCustomEnabled = arguments?.getBoolean(EntranceConsts.KEY_COLUMN_COLLECTION_CUSTOM) ?: false
val customSize = (arguments?.getInt(EntranceConsts.KEY_COLUMN_COLLECTION_CUSTOM_SIZE)
?: ColumnCollectionTabViewModel.CUSTOM_MAX_TAB).coerceAtMost(ColumnCollectionTabViewModel.CUSTOM_MAX_TAB)
collectionId = arguments?.getString(EntranceConsts.KEY_COLUMN_COLLECTION_ID, "") ?: ""
collectionName = arguments?.getString(EntranceConsts.KEY_COLUMN_COLLECTION_NAME, "") ?: ""
collectionStyle = arguments?.getString(EntranceConsts.KEY_COLUMN_COLLECTION_STYLE, "") ?: ""
binding.optionIv.isVisible = isCustomEnabled
binding.maskView.isVisible = isCustomEnabled
binding.optionIv.setOnClickListener {
showSettingView()
SensorsBridge.trackColumnCollectionClick(
location = "合集详情",
columnCollectionName = collectionName,
columnCollectionId = collectionId,
text = "自定义设置",
columnCollectionPattern = CustomPageItem.collectionTypeToComponentName[collectionStyle]
?: collectionStyle
)
}
binding.subjectTabContainer.visibility = View.VISIBLE
if (isCustomEnabled) {
val columnCollectionId = arguments?.getString(EntranceConsts.KEY_COLUMN_COLLECTION_ID) ?: ""
columnCollectionTabViewModel = viewModelProvider(ColumnCollectionTabViewModel.Factory(columnCollectionId, customSize, subjectList))
observerData()
if (SPUtils.getBoolean(Constants.SP_SHOW_COLUMN_COLLECTION_CUSTOM_TAB_SETTING, true)) {
mBaseHandler.post {
showSettingView()
}
SPUtils.setBoolean(Constants.SP_SHOW_COLUMN_COLLECTION_CUSTOM_TAB_SETTING, false)
}
} else {
initColumnCollectionViewPager(subjectList)
}
}
}
subjectList.filterIndexed { index, subject ->
val element = childFragmentManager.findFragmentByTag("${fragmentTag}$index")
?: SubjectFragment()
val bundle = arguments?.clone() as Bundle?
bundle?.putParcelable(EntranceConsts.KEY_SUBJECT_DATA, subject)
bundle?.putParcelableArrayList(EntranceConsts.KEY_DATA, null)
element.arguments = bundle
fragments.add(element)
tagList.add(subject.subjectName ?: "")
private fun observerData() {
columnCollectionTabViewModel?.tabListLiveData?.observe(viewLifecycleOwner) {
binding.subjectTab.clearOnTabSelectedListeners()
initColumnCollectionViewPager(it)
}
columnCollectionTabViewModel?.followListLiveData?.observe(viewLifecycleOwner) {
followAdapter.submitList(it)
}
columnCollectionTabViewModel?.moreListLiveData?.observe(viewLifecycleOwner) {
moreAdapter.submitList(it) {
showOrHideMore.invoke(it.isNotEmpty())
}
}
columnCollectionTabViewModel?.addFollowLiveData?.observe(viewLifecycleOwner, EventObserver {
val newFollowList = followAdapter.currentList.toMutableList()
if (newFollowList.size < ColumnCollectionTabViewModel.CUSTOM_MAX_TAB) {
val newMoreList = moreAdapter.currentList.toMutableList().apply { remove(it) }
followAdapter.submitList(newFollowList.apply { add(it) })
moreAdapter.submitList(newMoreList) {
showOrHideMore.invoke(newMoreList.isNotEmpty())
}
} else {
toast("最多可以添加15个关注内容哦~")
}
})
columnCollectionTabViewModel?.addMoreLiveData?.observe(viewLifecycleOwner, EventObserver {
if (followAdapter.currentList.size == 1) {
toast("至少添加1个内容哦~")
return@EventObserver
}
mBinding.subjectViewpager.post {
mBinding.subjectViewpager.offscreenPageLimit = fragments.size
var position = arguments?.getInt(EntranceConsts.KEY_POSITION, 0) ?: 0
val columnName = arguments?.getString(EntranceConsts.KEY_COLUMNNAME) ?: ""
if (columnName.isNotEmpty()) {
position = subjectList.indexOfFirst { it.subjectName == columnName }
}
if (position < subjectList.size) {
mBinding.subjectViewpager.setCurrentItem(position, false)
}
followAdapter.submitList(followAdapter.currentList.toMutableList().apply { remove(it) })
val newMoreList = moreAdapter.currentList.toMutableList().apply { add(it) }
moreAdapter.submitList(newMoreList) {
showOrHideMore.invoke(newMoreList.isNotEmpty())
}
val categoryId = requireArguments().getString(EntranceConsts.KEY_COLUMN_COLLECTION_ID, "")
val categoryName = requireArguments().getString(EntranceConsts.KEY_COLUMN_COLLECTION_NAME, "")
})
}
mBinding.subjectViewpager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() {
override fun onPageSelected(position: Int) {
subjectList[position]?.let {
MtaHelper.onEvent("游戏专题合集", "详情Tab", it.subjectName)
NewLogUtils.logColumnCategoryDetailContentClick(
it.subjectName ?: "", it.subjectId ?: "", categoryName, categoryId
)
private fun initColumnCollectionViewPager(subjectList: List<SubjectData>) {
val fragments = arrayListOf<Fragment>()
val tagList = arrayListOf<String>()
subjectList.filterIndexed { index, subject ->
val element = childFragmentManager.findFragmentByTag("${fragmentTag}$index")
?: SubjectFragment()
val bundle = arguments?.clone() as Bundle?
bundle?.putParcelable(EntranceConsts.KEY_SUBJECT_DATA, subject)
bundle?.putParcelableArrayList(EntranceConsts.KEY_DATA, null)
bundle?.putBoolean(EntranceConsts.KEY_IS_FROM_MAIN_WRAPPER, false)
bundle?.putBoolean(EntranceConsts.KEY_IS_FROM_TAB_WRAPPER, false)
bundle?.putBoolean(EntranceConsts.KEY_COLUMN_COLLECTION_CUSTOM, false)
if (isCustomEnabled) {
bundle?.putInt(EntranceConsts.KEY_COLUMN_COLLECTION_CUSTOM_POSITION, index)
}
element.arguments = bundle
fragments.add(element)
if (categoryName?.contains("排行榜") == true) {
val trackEvent = JSONObject()
try {
trackEvent.put("list_name", it.subjectName)
trackEvent.put("position", position)
} catch (e: JSONException) {
e.printStackTrace()
}
SensorsBridge.trackEvent("GameListPageSelected", trackEvent)
tagList.add(subject.subjectName ?: "")
}
binding.subjectViewpager.post {
binding.subjectViewpager.offscreenPageLimit = fragments.size
var position = arguments?.getInt(EntranceConsts.KEY_POSITION, 0) ?: 0
val columnName = arguments?.getString(EntranceConsts.KEY_COLUMNNAME) ?: ""
if (columnName.isNotEmpty()) {
position = subjectList.indexOfFirst { it.subjectName == columnName }
}
if (position < subjectList.size) {
binding.subjectViewpager.setCurrentItem(position, false)
}
}
val categoryId = requireArguments().getString(EntranceConsts.KEY_COLUMN_COLLECTION_ID, "")
val categoryName = requireArguments().getString(EntranceConsts.KEY_COLUMN_COLLECTION_NAME, "")
binding.subjectViewpager.addOnPageChangeListener(object : ViewPager.SimpleOnPageChangeListener() {
override fun onPageSelected(position: Int) {
subjectList.getOrNull(position)?.let {
MtaHelper.onEvent("游戏专题合集", "详情Tab", it.subjectName)
NewLogUtils.logColumnCategoryDetailContentClick(
it.subjectName ?: "", it.subjectId ?: "", categoryName, categoryId
)
if (categoryName?.contains("排行榜") == true) {
val trackEvent = JSONObject()
try {
trackEvent.put("list_name", it.subjectName)
trackEvent.put("position", position)
} catch (e: JSONException) {
e.printStackTrace()
}
SensorsBridge.trackEvent("GameListPageSelected", trackEvent)
}
}
})
}
}
if (mBinding.subjectTabContainer.visibility == View.VISIBLE) {
mBinding.subjectTabIndicator.setupWithTabLayout(mBinding.subjectTab)
mBinding.subjectTabIndicator.setupWithViewPager(mBinding.subjectViewpager)
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels)
if (mIsFromMainWrapper) {
// 这里的 selectedPosition 指的是应该被高亮显示的 position
val selectedPosition = try {
(position + positionOffset).roundToInt()
} catch (e: IllegalArgumentException) {
// roundToInt() 方法有时候会报 Cannot round NaN value. 错误 positionOffset 的值为 NAN
// https://sentry.shanqu.cc/organizations/lightgame/issues/301377/?project=22
position
}
val positionOffsetOnRealSelectedPosition = if (positionOffset >= 0.5) {
positionOffset - 1
} else {
positionOffset
}
updateTabStyle(selectedPosition, positionOffsetOnRealSelectedPosition)
}
}
})
val adapter = object : FragmentStatePagerAdapter(childFragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {
override fun getItem(position: Int): Fragment {
@ -133,7 +354,7 @@ class SubjectTabFragment : BaseFragment<Any>() {
return fragments.size
}
override fun getPageTitle(position: Int): CharSequence? {
override fun getPageTitle(position: Int): CharSequence {
return tagList[position]
}
@ -141,76 +362,478 @@ class SubjectTabFragment : BaseFragment<Any>() {
// super.destroyItem(container, position, `object`)
}
}
mBinding.subjectViewpager.adapter = adapter
mBinding.subjectTab.setupWithViewPager(mBinding.subjectViewpager)
// 首页样式的 tabLayout
if (arguments?.getBoolean(EntranceConsts.KEY_IS_HOME) == true) {
mBinding.subjectTabIndicator.visibility = View.GONE
for (i in 0 until mBinding.subjectTab.tabCount) {
val tab = mBinding.subjectTab.getTabAt(i) ?: continue
binding.subjectViewpager.adapter = adapter
binding.subjectTab.setupWithViewPager(binding.subjectViewpager)
binding.subjectTabIndicator.setupWithTabLayout(binding.subjectTab)
binding.subjectTabIndicator.setupWithViewPager(binding.subjectViewpager)
initTabLayout()
}
private fun initTabLayout() {
if (tabStyle == TabStyle.COLUMN_COLLECTION_NORMAL) {
if (isCustomEnabled && SPUtils.getBoolean(Constants.SP_SHOW_COLUMN_COLLECTION_CUSTOM_TAB_GUIDE, true)) {
binding.subjectTab.addOnTabSelectedListener(object : OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
showSettingGuideIfNeeded(this)
}
override fun onTabUnselected(tab: TabLayout.Tab?) {}
override fun onTabReselected(tab: TabLayout.Tab?) {
showSettingGuideIfNeeded(this)
}
})
}
binding.subjectTabIndicator.run {
setIndicatorWidth(12)
updateLayoutParams<ConstraintLayout.LayoutParams> {
bottomMargin = 10F.dip2px()
}
}
for (i in 0 until binding.subjectTab.tabCount) {
binding.subjectTab.getTabAt(i)?.view?.run {
background = null
if (i == 0) {
updateLayoutParams<MarginLayoutParams> { leftMargin = 4F.dip2px() }
}
}
}
} else {
tabBindingList.clear()
val tabCount = binding.subjectTab.tabCount
for (i in 0 until tabCount) {
val tab = binding.subjectTab.getTabAt(i) ?: continue
val tabTitle = if (tab.text != null) tab.text.toString() else ""
tab.view.background = null
tab.customView = provideTabView(tabTitle)
if (i == 0) {
tab.customView?.background = R.drawable.border_round_theme_14.toDrawable(requireContext())
tab.customView?.findViewById<TextView>(R.id.tab_title)
?.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(requireContext()))
if (tabStyle == TabStyle.COLUMN_COLLECTION_MAIN_WRAPPER) {
tab.view.setPadding(0, 0, 0, 0)
if (i == 0) {
tab.view.updateLayoutParams {
this as LinearLayout.LayoutParams
setMargins(10F.dip2px(), 0, 0, 0)
}
} else if (i == tabCount - 1) {
tab.view.updateLayoutParams {
this as LinearLayout.LayoutParams
setMargins(0, 0, 10F.dip2px(), 0)
}
}
} else {
tab.customView?.background = R.drawable.border_round_eee_14.toDrawable(requireContext())
tab.customView?.findViewById<TextView>(R.id.tab_title)
?.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()))
}
when (i) {
0 -> tab.view.setPadding(12F.dip2px(), 0, 0, 0)
mBinding.subjectTab.tabCount - 1 -> tab.view.setPadding(8F.dip2px(), 0, 12F.dip2px(), 0)
else -> tab.view.setPadding(8F.dip2px(), 0, 0, 0)
when (i) {
0 -> tab.view.setPadding(16F.dip2px(), 0, 0, 0)
binding.subjectTab.tabCount - 1 -> tab.view.setPadding(8F.dip2px(), 0, 16F.dip2px(), 0)
else -> tab.view.setPadding(8F.dip2px(), 0, 0, 0)
}
}
}
mBinding.subjectTab.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
override fun onTabReselected(tab: TabLayout.Tab?) {}
override fun onTabUnselected(tab: TabLayout.Tab?) {
tab?.customView?.background = R.drawable.border_round_eee_14.toDrawable(requireContext())
tab?.customView?.findViewById<TextView>(R.id.tab_title)
?.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()))
binding.subjectTabIndicator.isVisible = mIsFromMainWrapper
if (tabStyle == TabStyle.COLUMN_COLLECTION_MAIN_WRAPPER) {
binding.subjectTabIndicator.run {
setIndicatorWidth(18)
updateIndicatorDrawable(R.drawable.ic_commodity_selected.toDrawable(requireContext()))
updateLayoutParams<ConstraintLayout.LayoutParams> {
height = 8F.dip2px()
bottomMargin = 8F.dip2px()
}
}
override fun onTabSelected(tab: TabLayout.Tab?) {
tab?.customView?.background = R.drawable.border_round_theme_14.toDrawable(requireContext())
tab?.customView?.findViewById<TextView>(R.id.tab_title)
?.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(requireContext()))
binding.settingGuideIv.updateLayoutParams<ConstraintLayout.LayoutParams> { topMargin = 40F.dip2px() }
binding.subjectTabContainer.updateLayoutParams<ConstraintLayout.LayoutParams> { height = 56F.dip2px() }
binding.subjectTab.updateLayoutParams<ConstraintLayout.LayoutParams> {
setMargins(0, 8F.dip2px(), 8F.dip2px(), 8F.dip2px())
}
})
} else {
binding.subjectTab.addOnTabSelectedListener(object : OnTabSelectedListener {
override fun onTabReselected(tab: TabLayout.Tab?) {}
override fun onTabUnselected(tab: TabLayout.Tab?) {
tab?.customView?.findViewById<CheckedTextView>(R.id.tab_title)?.isChecked = false
}
override fun onTabSelected(tab: TabLayout.Tab?) {
tab?.customView?.findViewById<CheckedTextView>(R.id.tab_title)?.isChecked = true
}
})
}
}
}
private fun provideTabView(title: String): View {
val view = LayoutInflater.from(requireContext()).inflate(R.layout.tab_item_ranking_home, null)
val tabTitle = view.findViewById<View>(R.id.tab_title)
if (tabTitle is CheckedTextView) {
tabTitle.text = title
if (tabStyle == TabStyle.COLUMN_COLLECTION_MAIN_WRAPPER) {
val tabBinding = TabItemMainBinding.inflate(LayoutInflater.from(requireContext()))
tabBinding.titleTv.run {
visibility = View.VISIBLE
text = title
textSize = 16F
setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(requireContext()))
}
tabBinding.invisibleTitleTv.run {
visibility = View.INVISIBLE
text = title
textSize = 16F
}
tabBindingList.add(tabBinding)
return tabBinding.root
} else {
val view = LayoutInflater.from(requireContext()).inflate(R.layout.tab_item_subject_tab, null)
val tabTitle = view.findViewById<View>(R.id.tab_title)
if (tabTitle is CheckedTextView) {
tabTitle.text = title
}
return view
}
return view
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
for (i in 0 until mBinding.subjectTab.tabCount) {
val tab = mBinding.subjectTab.getTabAt(i) ?: continue
tab.customView?.background =
if (tab.isSelected) R.drawable.border_round_theme_14.toDrawable(requireContext()) else R.drawable.border_round_eee_14.toDrawable(
requireContext()
)
tab.customView?.findViewById<TextView>(R.id.tab_title)
?.setTextColor(
if (tab.isSelected) com.gh.gamecenter.common.R.color.text_theme.toColor(requireContext()) else com.gh.gamecenter.common.R.color.text_primary.toColor(
requireContext()
)
)
when (tabStyle) {
TabStyle.COLUMN_COLLECTION_NORMAL -> {
binding.subjectTabIndicator.updateIndicatorColor(com.gh.gamecenter.common.R.color.primary_theme)
binding.subjectTab.setTabTextColors(com.gh.gamecenter.common.R.color.text_secondary.toColor(requireContext()), com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()))
}
TabStyle.COLUMN_COLLECTION_MAIN_WRAPPER -> updateTabStyle(binding.subjectViewpager.currentItem, 0F)
TabStyle.SUBJECT_NORMAL -> {
for (i in 0 until binding.subjectTab.tabCount) {
val tab = binding.subjectTab.getTabAt(i) ?: continue
tab.customView?.findViewById<CheckedTextView>(R.id.tab_title)?.run {
background = R.drawable.subject_tab_background_selector.toDrawable(context)
setTextColor(ContextCompat.getColorStateList(context, com.gh.gamecenter.common.R.color.text_tabbar_style))
}
}
}
}
settingBinding?.run {
saveTv.background = com.gh.gamecenter.common.R.drawable.button_blue_oval.toDrawable(requireContext())
cancelTv.background = com.gh.gamecenter.feature.R.drawable.button_round_gray_light.toDrawable(requireContext())
cancelTv.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(requireContext()))
followAdapter.notifyItemRangeChanged(0, followAdapter.itemCount)
moreAdapter.notifyItemRangeChanged(0, moreAdapter.itemCount)
}
}
private fun updateTabStyle(selectedPosition: Int, positionOffset: Float) {
if (tabBindingList.isEmpty()) return
val currentTabDefaultColor = com.gh.gamecenter.common.R.color.text_secondary.toColor(requireContext())
val currentTabSelectedColor = com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext())
val prePosition = selectedPosition - 1
val nextPosition = selectedPosition + 1
// positionOffset 小于零,表示 indicator 当前位置处于选中的 tab 的左边
val indicatorOnLeft = positionOffset < 0F
val selectedTabBinding = tabBindingList.safelyGetInRelease(selectedPosition) ?: return
// 前一个 tab、当前选中的 tab、后一个 tab 的显示比例
val preScaleRatio = 1 + abs(positionOffset) / 4
val selectedScaleRatio = 1 + (1 - abs(positionOffset)) / 4
val nextScaleRatio = 1 + positionOffset / 4
// 处理前一个 tab
if (prePosition != -1) {
tabBindingList[prePosition].run {
if (indicatorOnLeft) {
titleTv.scaleX = preScaleRatio
titleTv.scaleY = preScaleRatio
titleTv.setTextColor(
ColorUtils.blendARGB(
currentTabDefaultColor,
currentTabSelectedColor,
abs(positionOffset)
)
)
} else {
titleTv.setTextColor(currentTabDefaultColor)
}
}
}
// 更新当前选中的 tab
selectedTabBinding.run {
titleTv.scaleX = selectedScaleRatio
titleTv.scaleY = selectedScaleRatio
titleTv.setTextColor(
ColorUtils.blendARGB(
currentTabDefaultColor,
currentTabSelectedColor,
1 - abs(positionOffset)
)
)
}
// 处理后一个 tab
if (nextPosition < tabBindingList.size) {
tabBindingList[nextPosition].run {
if (!indicatorOnLeft) {
titleTv.scaleX = nextScaleRatio
titleTv.scaleY = nextScaleRatio
titleTv.setTextColor(
ColorUtils.blendARGB(
currentTabDefaultColor,
currentTabSelectedColor,
positionOffset
)
)
} else {
titleTv.setTextColor(currentTabDefaultColor)
}
}
}
// 多 tab 切换的时候可能会出现某些 tab 的文字没有回归到原始大小的问题的问题 (positionOffset 不保证连续)
for ((index, tabBinding) in tabBindingList.withIndex()) {
if (index != prePosition && index != selectedPosition && index != nextPosition) {
if (tabBinding.titleTv.scaleX != 1F) {
tabBinding.titleTv.scaleX = 1F
tabBinding.titleTv.scaleY = 1F
}
tabBinding.titleTv.setTextColor(currentTabDefaultColor)
if (tabBinding.titleIv.scaleX != 1F) {
tabBinding.titleIv.scaleX = 1F
tabBinding.titleIv.scaleY = 1F
}
}
if (index == selectedPosition) {
if (positionOffset == 0F) {
tabBinding.titleTv.setTextColor(currentTabSelectedColor)
}
tabBinding.titleTv.setTypeface(tabBinding.titleTv.typeface, Typeface.BOLD)
} else {
if (positionOffset == 0F) {
tabBinding.titleTv.setTextColor(currentTabDefaultColor)
}
tabBinding.titleTv.setTypeface(null, Typeface.NORMAL)
}
}
}
private fun showSettingGuideIfNeeded(listener: OnTabSelectedListener) {
if (isCustomEnabled && SPUtils.getBoolean(Constants.SP_SHOW_COLUMN_COLLECTION_CUSTOM_TAB_GUIDE, true)) {
binding.skipView.setOnClickListener {
dismissSettingGuide()
SensorsBridge.trackColumnCollectionClick(
location = "合集详情",
columnCollectionName = collectionName,
columnCollectionId = collectionId,
text = "新手引导-取消",
columnCollectionPattern = CustomPageItem.collectionTypeToComponentName[collectionStyle]
?: collectionStyle
)
}
binding.settingClickView.setOnClickListener {
dismissSettingGuide()
showSettingView()
SensorsBridge.trackColumnCollectionClick(
location = "合集详情",
columnCollectionName = collectionName,
columnCollectionId = collectionId,
text = "新手引导-立即设置",
columnCollectionPattern = CustomPageItem.collectionTypeToComponentName[collectionStyle]
?: collectionStyle
)
}
binding.settingGuideContainer.setOnClickListener {
dismissSettingGuide()
SensorsBridge.trackColumnCollectionClick(
location = "合集详情",
columnCollectionName = collectionName,
columnCollectionId = collectionId,
text = "新手引导-关闭引导",
columnCollectionPattern = CustomPageItem.collectionTypeToComponentName[collectionStyle]
?: collectionStyle
)
}
mBaseHandler.post {
binding.settingGuideContainer.isVisible = true
}
SPUtils.setBoolean(Constants.SP_SHOW_COLUMN_COLLECTION_CUSTOM_TAB_GUIDE, false)
binding.subjectTab.removeOnTabSelectedListener(listener)
}
}
private fun dismissSettingGuide() {
binding.settingGuideIv.animate()?.alpha(0F)?.setDuration(200L)?.doOnEnd {
binding.settingGuideContainer.isVisible = false
}?.start()
}
private fun showSettingView() {
if (isSettingAnimating) return
binding.background.setOnClickListener {
showDismissSettingDialogIfNeeded()
}
settingBinding = LayoutColumnCollectionSettingBinding.inflate(layoutInflater, null, false).apply {
followRv.isNestedScrollingEnabled = false
moreRv.isNestedScrollingEnabled = false
columnCollectionTabViewModel?.followListLiveData?.value?.let {
followAdapter.submitList(it) {
followRv.adapter = followAdapter
}
} ?: run {
followRv.adapter = followAdapter
}
columnCollectionTabViewModel?.moreListLiveData?.value?.let {
moreAdapter.submitList(it) {
moreRv.adapter = moreAdapter
}
} ?: run {
moreRv.adapter = moreAdapter
}
followRv.layoutManager = FlexboxLayoutManager(requireContext()).apply { justifyContent = JustifyContent.FLEX_START }
moreRv.layoutManager = FlexboxLayoutManager(requireContext()).apply { justifyContent = JustifyContent.FLEX_START }
itemTouchHelper.attachToRecyclerView(followRv)
moreContainer.isVisible = !columnCollectionTabViewModel?.moreListLiveData?.value.isNullOrEmpty()
saveTv.setOnClickListener {
dismissSettingView {
saveCustomTab()
}
}
cancelTv.setOnClickListener {
dismissSettingView()
}
root.setOnClickListener {}
}
binding.settingContainer.addView(
settingBinding!!.root,
FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT)
)
binding.settingContainer.post {
val height = binding.settingContainer.height
binding.background.visibility = View.VISIBLE
AnimatorSet().apply {
val translateAnimator = ValueAnimator.ofFloat(0F, height.toFloat()).apply {
addUpdateListener { va -> binding.settingContainer.translationY = (va.animatedValue as Float) }
}
val alphaAnimator = ValueAnimator.ofFloat(0F, 0.2F).apply {
addUpdateListener { va -> binding.background.alpha = (va.animatedValue as Float) }
}
playTogether(translateAnimator, alphaAnimator)
duration = ANIMATOR_DURATION
doOnStart {
isSettingAnimating = true
}
doOnEnd {
ConstraintSet().apply {
clone(binding.root)
clear(binding.settingContainer.id, ConstraintSet.BOTTOM)
connect(binding.settingContainer.id, ConstraintSet.TOP, binding.subjectTabContainer.id, ConstraintSet.TOP)
}.applyTo(binding.root)
binding.settingContainer.translationY = 0F
isSettingAnimating = false
}
}.start()
}
}
private fun saveCustomTab() {
if (followAdapter.currentList.isEmpty()) {
toast("至少添加1个内容哦~")
return
}
SensorsBridge.trackEvent("ColumnCollectionCustomSetting",
"column_collection_name", collectionName,
"column_collection_id", collectionId,
"original_text", columnCollectionTabViewModel?.followListLiveData?.value?.joinToString("") { it.subjectName ?: "" } ?: "",
"save_text", followAdapter.currentList.joinToString("") { it.subjectName ?: "" },
)
columnCollectionTabViewModel?.saveCustomTab(followAdapter.currentList)
}
private fun showDismissSettingDialogIfNeeded() {
if (followAdapter.currentList == columnCollectionTabViewModel?.followListLiveData?.value) {
dismissSettingView()
} else {
DialogHelper.showDialog(
requireContext(),
title = "提示",
content = "是否保存当前自定义设置",
confirmText = "保存设置",
cancelText = "退出",
confirmClickCallback = {
dismissSettingView {
saveCustomTab()
}
},
cancelClickCallback = {
dismissSettingView()
},
extraConfig = Config(centerTitle = true, centerContent = true)
)
}
}
private fun dismissSettingView(doOnEnd: (() -> Unit)? = null) {
if (settingBinding == null || isSettingAnimating) return
val height = binding.settingContainer.height
AnimatorSet().apply {
val translateAnimator = ValueAnimator.ofFloat(0F, -height.toFloat()).apply {
addUpdateListener { va -> binding.settingContainer.translationY = (va.animatedValue as Float) }
}
val alphaAnimator = ValueAnimator.ofFloat(0.2F, 0F).apply {
addUpdateListener { va -> binding.background.alpha = (va.animatedValue as Float) }
}
playTogether(translateAnimator, alphaAnimator)
duration = ANIMATOR_DURATION
doOnStart {
isSettingAnimating = true
}
doOnEnd {
binding.background.visibility = View.GONE
binding.settingContainer.removeAllViews()
binding.settingContainer.translationY = 0F
ConstraintSet().apply {
clone(binding.root)
clear(binding.settingContainer.id, ConstraintSet.TOP)
connect(binding.settingContainer.id, ConstraintSet.BOTTOM, binding.subjectTabContainer.id, ConstraintSet.TOP)
}.applyTo(binding.root)
settingBinding = null
isSettingAnimating = false
doOnEnd?.invoke()
}
}.start()
}
override fun onBackPressed(): Boolean {
if (binding.settingGuideContainer.isVisible) {
binding.settingGuideContainer.performClick()
return true
}
if (settingBinding != null) {
showDismissSettingDialogIfNeeded()
return true
}
return super.onBackPressed()
}
enum class TabStyle {
/**
* 专题详情样式
*/
SUBJECT_NORMAL,
/**
* 专题合集-关联首页底部Tab样式
*/
COLUMN_COLLECTION_MAIN_WRAPPER,
/**
* 专题合集-常规样式
*/
COLUMN_COLLECTION_NORMAL,
}
companion object {
private const val ANIMATOR_DURATION = 200L
}
}

View File

@ -4,19 +4,24 @@ import android.os.Bundle
import android.text.TextUtils
import android.view.MotionEvent
import android.view.View
import androidx.core.view.isVisible
import androidx.recyclerview.widget.GridLayoutManager
import com.gh.common.view.ConfigFilterView
import com.gh.common.view.ConfigFilterView.SortType
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.BaseFragment
import com.gh.gamecenter.common.base.fragment.ToolbarController
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.eventbus.EBReuse
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.view.GridSpacingItemDecoration
import com.gh.gamecenter.core.utils.StringUtils
import com.gh.gamecenter.core.utils.UrlFilterUtils
import com.gh.gamecenter.databinding.FragmentSubjectTiledBinding
import com.gh.gamecenter.entity.SubjectData
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.gh.gamecenter.subject.SubjectListFragment
import com.google.android.material.appbar.AppBarLayout
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import kotlin.math.abs
@ -32,6 +37,28 @@ class SubjectTileFragment : BaseFragment<Any>() {
private var mIsTouchScreen: Boolean = false
private var mMaxSize = ""
private var mMinSize = ""
private var mSelectedFilterSize = SubjectSettingEntity.Size(text = "全部大小")
private val finalFilter: String
get() {
var filter = if (mSelectedTypeName == "全部") {
// 全部的时候添加头图
UrlFilterUtils.getFilterQuery("tags", mSelectedTypeName, "type", mSelectedTypeName)
} else {
UrlFilterUtils.getFilterQuery("tags", mSelectedTypeName)
}
if (mMinSize.isNotEmpty() && mMinSize != "-1") {
filter += ",${UrlFilterUtils.getFilterQuery("min_size", mMinSize)}"
}
if (mMaxSize.isNotEmpty() && mMaxSize != "-1") {
filter += ",${UrlFilterUtils.getFilterQuery("max_size", mMaxSize)}"
}
return filter
}
override fun getLayoutId() = 0
override fun getInflatedLayout() = mBinding.root
@ -40,45 +67,75 @@ class SubjectTileFragment : BaseFragment<Any>() {
// 初始化数据
mSubjectData = arguments?.getParcelable(EntranceConsts.KEY_SUBJECT_DATA) ?: return
mSettingsEntity =
arguments?.getParcelable(SubjectSettingEntity::class.java.simpleName) ?: return
arguments?.getParcelable(EntranceConsts.KEY_SUBJECT_SETTING_DATA) ?: return
mSelectedTypeName = "全部"
mSubjectData.filter = UrlFilterUtils.getFilterQuery("type", "全部")
mSubjectData.sort = UrlFilterUtils.getFilterQuery("position", "1")
mBinding.subjectFilterContainer.visibility =
if (mSettingsEntity.filter == "on") View.VISIBLE else View.GONE
// 初始化顶部类型列表
val layoutManager = object : GridLayoutManager(context, 4) {
override fun canScrollVertically(): Boolean {
return false
mBinding.subjectTypeList.goneIf(mSettingsEntity.typeEntity.content.size <= 1) {
// 初始化顶部类型列表
val layoutManager = object : GridLayoutManager(context, 4) {
override fun canScrollVertically(): Boolean {
return false
}
}
}
mBinding.subjectTypeList.isNestedScrollingEnabled = false
mBinding.subjectTypeList.layoutManager = layoutManager
mBinding.subjectTabbarHottest.setOnClickListener(this)
mBinding.subjectTabbarNewest.setOnClickListener(this)
if (mSettingsEntity.typeEntity.content.size > 1) {
mBinding.subjectTypeList.isNestedScrollingEnabled = false
mBinding.subjectTypeList.layoutManager = layoutManager
mBinding.subjectTypeList.addItemDecoration(GridSpacingItemDecoration(4, 8F.dip2px(), false))
val adapter = SubjectTypeListAdapter(requireContext(), mItemClickListener = {
mSelectedTypeName = it
if (it == "全部") {
// 全部的时候添加头图
mSubjectData.filter = UrlFilterUtils.getFilterQuery("tags", it, "type", it)
} else {
mSubjectData.filter = UrlFilterUtils.getFilterQuery("tags", it)
}
mSubjectData.filter = finalFilter
mSubjectData.tag = it
loadData()
}, mGameType = mSettingsEntity.typeEntity.content)
mBinding.subjectTypeList.adapter = adapter
}
mBinding.filterView.goneIf(!mSettingsEntity.isFilterEnabled) {
mBinding.filterView.run {
initSubjectFilterView(mSettingsEntity)
setOnConfigSetupListener(object :
ConfigFilterView.OnConfigFilterSetupListener {
override fun onShowSortSize() {}
override fun onSetupSortSize(sortSize: SubjectSettingEntity.Size) {
mMinSize = sortSize.min.toString()
mMaxSize = sortSize.max.toString()
mSelectedFilterSize = sortSize
mSubjectData.filter = finalFilter
loadData()
}
override fun onSetupSortType(sortType: SortType) {
val clickTv = when (sortType) {
SortType.RATING -> ratingTv
SortType.NEWEST -> newestTv
SortType.UPDATE -> updateTv
else -> recommendedTv
}
val sort = when (clickTv.text) {
"更新" -> UrlFilterUtils.getFilterQuery("update_time", "-1")
"最新" -> UrlFilterUtils.getFilterQuery("publish", "-1")
"评分" -> UrlFilterUtils.getFilterQuery("star", "-1")
else -> UrlFilterUtils.getFilterQuery("position", "1")
}
if (mSubjectData.sort != sort) {
mSubjectData.sort = sort
loadData()
}
}
})
}
}
// 是否为专题合集详情子页面
val isColumnCollection = arguments?.getBoolean(EntranceConsts.KEY_IS_COLUMN_COLLECTION) ?: false
if (!isColumnCollection) {
// Appbar 交互
mBinding.subjectAppbar.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset ->
mBinding.subjectAppbar.addOnOffsetChangedListener { appBarLayout, verticalOffset ->
if (!TextUtils.isEmpty(mSubjectData.subjectName) && activity is ToolbarController) {
val totalScrollRange = appBarLayout.totalScrollRange
if (abs(verticalOffset) < totalScrollRange / 2) {
@ -94,7 +151,7 @@ class SubjectTileFragment : BaseFragment<Any>() {
)
}
}
})
}
}
loadData()
@ -116,23 +173,6 @@ class SubjectTileFragment : BaseFragment<Any>() {
}
}
override fun onClick(view: View) {
when (view.id) {
R.id.subject_tabbar_hottest -> {
mBinding.subjectTabbarHottest.isChecked = true
mBinding.subjectTabbarNewest.isChecked = false
mSubjectData.sort = UrlFilterUtils.getFilterQuery("position", "1")
loadData()
}
R.id.subject_tabbar_newest -> {
mBinding.subjectTabbarHottest.isChecked = false
mBinding.subjectTabbarNewest.isChecked = true
mSubjectData.sort = UrlFilterUtils.getFilterQuery("publish", "-1")
loadData()
}
}
}
fun loadData() {
val transaction = childFragmentManager.beginTransaction()
hideFragments(transaction)
@ -151,8 +191,10 @@ class SubjectTileFragment : BaseFragment<Any>() {
transaction.show(fragmentByTag)
} else {
fragmentByTag = SubjectListFragment()
val bundle = arguments ?: Bundle()
val bundle = arguments?.clone() as? Bundle ?: Bundle()
bundle.remove(EntranceConsts.KEY_SUBJECT_SETTING_DATA)
bundle.putParcelable(EntranceConsts.KEY_SUBJECT_DATA, mSubjectData)
bundle.putString(EntranceConsts.KEY_SUBJECT_SORT_SIZE, mSelectedFilterSize.text)
fragmentByTag.arguments = bundle
transaction.add(R.id.subject_content_rl, fragmentByTag, fmTag)
}
@ -161,6 +203,13 @@ class SubjectTileFragment : BaseFragment<Any>() {
}
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
if (mBinding.filterView.isVisible) {
mBinding.filterView.updateAllTextView()
}
}
companion object {
private const val OPEN_APPBAR = "openAppBar"
}

View File

@ -1,14 +1,16 @@
package com.gh.gamecenter.subject.tile
import android.content.Context
import android.graphics.Color
import android.graphics.Typeface
import android.text.TextUtils
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.CheckedTextView
import androidx.core.content.ContextCompat
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.toDrawable
import com.lightgame.adapter.BaseRecyclerAdapter
class SubjectTypeListAdapter(
@ -20,28 +22,26 @@ class SubjectTypeListAdapter(
private var mCurType = "全部"
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SubjectTypeViewHolder {
return SubjectTypeViewHolder(mLayoutInflater.inflate(R.layout.item_filter_size, parent, false))
return SubjectTypeViewHolder(mLayoutInflater.inflate(R.layout.subject_rows_label_item, parent, false))
}
override fun onBindViewHolder(holder: SubjectTypeViewHolder, position: Int) {
holder.itemView.layoutParams = holder.itemView.layoutParams.apply {
holder.type.updateLayoutParams<ViewGroup.MarginLayoutParams> {
width = ViewGroup.LayoutParams.MATCH_PARENT
}
holder.type.background = R.drawable.subject_label_background_style.toDrawable(mContext)
holder.type.setTextColor(ContextCompat.getColorStateList(mContext, R.color.subject_label_text_style))
holder.type.text = mGameType[position]
if (!TextUtils.isEmpty(mCurType) && mCurType == mGameType[position]) {
holder.type.background = ContextCompat.getDrawable(mContext, R.drawable.bg_tag_text)
holder.type.setTextColor(Color.WHITE)
} else {
holder.type.background = null
holder.type.setTextColor(ContextCompat.getColor(mContext, com.gh.gamecenter.common.R.color.text_757575))
}
val isChecked = !TextUtils.isEmpty(mCurType) && mCurType == mGameType[position]
holder.type.isChecked = isChecked
holder.type.setTypeface(Typeface.DEFAULT, if (isChecked) Typeface.BOLD else Typeface.NORMAL)
holder.type.setOnClickListener {
if (holder.adapterPosition == -1) return@setOnClickListener
if (holder.bindingAdapterPosition == -1) return@setOnClickListener
mCurType = mGameType[holder.adapterPosition]
mItemClickListener.invoke(mGameType[holder.adapterPosition])
mCurType = mGameType[holder.bindingAdapterPosition]
mItemClickListener.invoke(mGameType[holder.bindingAdapterPosition])
notifyDataSetChanged()
}
}
@ -51,6 +51,6 @@ class SubjectTypeListAdapter(
}
inner class SubjectTypeViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val type by lazy { itemView.findViewById<TextView>(R.id.size_tv) }
val type: CheckedTextView by lazy { itemView.findViewById(R.id.label) }
}
}

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/text_tertiary" android:state_checked="false" />
<item android:color="@color/text_theme" android:state_checked="true" />
</selector>

View File

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="@color/text_757575" android:state_checked="false" />
<item android:color="@color/white" android:state_checked="true" />
</selector>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 860 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 886 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 103 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 990 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 46 KiB

View File

@ -2,5 +2,5 @@
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="8dp" />
<solid android:color="@color/bg_EBF5FF" />
<solid android:color="@color/primary_theme_10" />
</shape>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:endColor="@color/primary_theme_10"
android:startColor="@color/primary_theme_40" />
<corners android:radius="2dp" />
</shape>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<stroke
android:width="1dp"
android:color="@color/ui_background" />
<corners android:topRightRadius="8dp" />
</shape>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="28dp" />
<stroke
android:width="1dp"
android:color="@color/ui_divider_2" />
</shape>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="28dp" />
<stroke
android:width="1dp"
android:color="@color/primary_theme_20" />
<gradient
android:endColor="@color/theme_alpha_4"
android:startColor="@color/theme_alpha_8" />
</shape>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="100dp"/>
<solid android:color="@color/white_alpha_20"/>
</shape>

View File

@ -1,9 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<solid android:color="@color/title" />
<corners android:radius="4dp" />
<corners android:radius="6dp" />
<solid android:color="@color/primary_theme_10" />
</shape>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/bg_shape_primary_theme_alpha_10_radius_6" android:state_checked="true" />
<item android:drawable="@drawable/bg_shape_primary_theme_alpha_10_radius_6" android:state_focused="true" />
<item android:drawable="@drawable/bg_shape_f8_radius_6" />
</selector>

View File

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="8dp"
android:height="6dp"
android:viewportWidth="8"
android:viewportHeight="6">
<path
android:strokeWidth="1"
android:pathData="M7,1.5L4,4.5L1,1.5"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="@color/text_primary"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="8dp"
android:height="6dp"
android:viewportWidth="8"
android:viewportHeight="6">
<path
android:strokeWidth="1"
android:pathData="M7,4.5L4,1.5L1,4.5"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="@color/text_primary"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="8dp"
android:height="6dp"
android:viewportWidth="8"
android:viewportHeight="6">
<path
android:strokeWidth="1"
android:pathData="M7,4.5L4,1.5L1,4.5"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="@color/text_primary"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="8dp"
android:height="8dp"
android:viewportWidth="8"
android:viewportHeight="8">
<group>
<clip-path
android:pathData="M0,0h8v8h-8z"/>
<path
android:pathData="M4,0C4.2761,0 4.5,0.2239 4.5,0.5V3.5H7.5C7.7761,3.5 8,3.7239 8,4C8,4.2761 7.7761,4.5 7.5,4.5H4.5V7.5C4.5,7.7761 4.2761,8 4,8C3.7239,8 3.5,7.7761 3.5,7.5V4.5H0.5C0.2239,4.5 0,4.2761 0,4C0,3.7239 0.2239,3.5 0.5,3.5H3.5V0.5C3.5,0.2239 3.7239,0 4,0Z"
android:fillColor="@color/text_secondary"
android:fillType="evenOdd"/>
</group>
</vector>

View File

@ -0,0 +1,21 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="18dp"
android:height="18dp"
android:viewportWidth="18"
android:viewportHeight="18">
<group>
<clip-path
android:pathData="M0,0h18v18h-18z"/>
<path
android:pathData="M1.875,9C1.875,5.065 5.065,1.875 9,1.875C12.935,1.875 16.125,5.065 16.125,9C16.125,12.935 12.935,16.125 9,16.125C5.065,16.125 1.875,12.935 1.875,9Z"
android:fillColor="#2496FF"
android:fillType="evenOdd"/>
<path
android:pathData="M6.75,9L8.25,10.5L11.25,7.5"
android:strokeLineJoin="round"
android:strokeWidth="1.5"
android:fillColor="#00000000"
android:strokeColor="#ffffff"
android:strokeLineCap="round"/>
</group>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<group>
<clip-path
android:pathData="M0,0h16v16h-16z"/>
<path
android:pathData="M8.5,4.667C8.5,3.102 9.769,1.833 11.333,1.833C12.898,1.833 14.167,3.102 14.167,4.667C14.167,6.232 12.898,7.5 11.333,7.5C9.769,7.5 8.5,6.232 8.5,4.667ZM11.333,3.167C10.505,3.167 9.833,3.838 9.833,4.667C9.833,5.495 10.505,6.167 11.333,6.167C12.162,6.167 12.833,5.495 12.833,4.667C12.833,3.838 12.162,3.167 11.333,3.167ZM4,2C2.895,2 2,2.895 2,4V5.333C2,6.438 2.895,7.333 4,7.333H5.333C6.438,7.333 7.333,6.438 7.333,5.333V4C7.333,2.895 6.438,2 5.333,2H4ZM3.333,4C3.333,3.632 3.632,3.333 4,3.333H5.333C5.702,3.333 6,3.632 6,4V5.333C6,5.702 5.702,6 5.333,6H4C3.632,6 3.333,5.702 3.333,5.333V4ZM4,8.667C2.895,8.667 2,9.562 2,10.667V12C2,13.105 2.895,14 4,14H5.333C6.438,14 7.333,13.105 7.333,12V10.667C7.333,9.562 6.438,8.667 5.333,8.667H4ZM3.333,10.667C3.333,10.299 3.632,10 4,10H5.333C5.702,10 6,10.299 6,10.667V12C6,12.368 5.702,12.667 5.333,12.667H4C3.632,12.667 3.333,12.368 3.333,12V10.667ZM10.667,8.667C9.562,8.667 8.667,9.562 8.667,10.667V12C8.667,13.105 9.562,14 10.667,14H12C13.105,14 14,13.105 14,12V10.667C14,9.562 13.105,8.667 12,8.667H10.667ZM10,10.667C10,10.299 10.299,10 10.667,10H12C12.368,10 12.667,10.299 12.667,10.667V12C12.667,12.368 12.368,12.667 12,12.667H10.667C10.299,12.667 10,12.368 10,12V10.667Z"
android:fillColor="@color/text_primary"
android:fillType="evenOdd"/>
</group>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="8dp"
android:height="8dp"
android:viewportWidth="8"
android:viewportHeight="8">
<group>
<clip-path
android:pathData="M0,0h8v8h-8z"/>
<path
android:pathData="M0.6464,0.6464C0.8417,0.4512 1.1583,0.4512 1.3535,0.6464L4,3.2929L6.6465,0.6464C6.8417,0.4512 7.1583,0.4512 7.3535,0.6464C7.5488,0.8417 7.5488,1.1583 7.3535,1.3535L4.7071,4L7.3535,6.6465C7.5488,6.8417 7.5488,7.1583 7.3535,7.3535C7.1583,7.5488 6.8417,7.5488 6.6465,7.3535L4,4.7071L1.3535,7.3535C1.1583,7.5488 0.8417,7.5488 0.6464,7.3535C0.4512,7.1583 0.4512,6.8417 0.6464,6.6465L3.2929,4L0.6464,1.3535C0.4512,1.1583 0.4512,0.8417 0.6464,0.6464Z"
android:fillColor="@color/text_secondary"
android:fillType="evenOdd"/>
</group>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="14dp"
android:height="14dp"
android:viewportWidth="14"
android:viewportHeight="14">
<path
android:pathData="M7,14C10.866,14 14,10.866 14,7C14,3.134 10.866,0 7,0C3.134,0 0,3.134 0,7C0,10.866 3.134,14 7,14ZM5.53,4.47C5.237,4.177 4.763,4.177 4.47,4.47C4.177,4.763 4.177,5.237 4.47,5.53L5.939,7L4.47,8.47C4.177,8.763 4.177,9.237 4.47,9.53C4.763,9.823 5.237,9.823 5.53,9.53L7,8.061L8.47,9.53C8.763,9.823 9.237,9.823 9.53,9.53C9.823,9.237 9.823,8.763 9.53,8.47L8.061,7L9.53,5.53C9.823,5.237 9.823,4.763 9.53,4.47C9.237,4.177 8.763,4.177 8.47,4.47L7,5.939L5.53,4.47Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
</vector>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="@color/ui_divider" />
</shape>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="12dp"
android:height="12dp"
android:viewportWidth="12"
android:viewportHeight="12">
<path
android:strokeWidth="1"
android:pathData="M4,1.5L8,1.5A1.5,1.5 0,0 1,9.5 3L9.5,9A1.5,1.5 0,0 1,8 10.5L4,10.5A1.5,1.5 0,0 1,2.5 9L2.5,3A1.5,1.5 0,0 1,4 1.5z"
android:fillColor="#00000000"
android:strokeColor="@color/text_tertiary"/>
<path
android:pathData="M5.5,3L6.5,3A0.5,0.5 0,0 1,7 3.5L7,3.5A0.5,0.5 0,0 1,6.5 4L5.5,4A0.5,0.5 0,0 1,5 3.5L5,3.5A0.5,0.5 0,0 1,5.5 3z"
android:fillColor="@color/text_tertiary"/>
</vector>

View File

@ -0,0 +1,19 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="12dp"
android:height="12dp"
android:viewportWidth="12"
android:viewportHeight="12">
<path
android:strokeWidth="1"
android:pathData="M7.5,5L5.5,7.5L5,5H4.5"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="@color/text_tertiary"
android:strokeLineCap="round"/>
<path
android:strokeWidth="1"
android:pathData="M3.5,2H8.5C9.328,2 10,2.672 10,3.5V8.5C10,9.328 9.328,10 8.5,10H3.5C2.672,10 2,9.328 2,8.5V3.5C2,2.672 2.672,2 3.5,2Z"
android:fillColor="#00000000"
android:strokeColor="@color/text_tertiary"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="8dp"
android:height="8dp"
android:viewportWidth="8"
android:viewportHeight="8">
<group>
<clip-path
android:pathData="M0,0h8v8h-8z"/>
<path
android:pathData="M0.6464,0.6465C0.8417,0.4512 1.1583,0.4512 1.3535,0.6465L4,3.2929L6.6465,0.6465C6.8417,0.4512 7.1583,0.4512 7.3535,0.6465C7.5488,0.8417 7.5488,1.1583 7.3535,1.3536L4.7071,4L7.3535,6.6465C7.5488,6.8417 7.5488,7.1583 7.3535,7.3536C7.1583,7.5488 6.8417,7.5488 6.6465,7.3536L4,4.7071L1.3535,7.3536C1.1583,7.5488 0.8417,7.5488 0.6464,7.3536C0.4512,7.1583 0.4512,6.8417 0.6464,6.6465L3.2929,4L0.6464,1.3536C0.4512,1.1583 0.4512,0.8417 0.6464,0.6465Z"
android:fillColor="#000000"
android:fillType="evenOdd"/>
</group>
</vector>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/ui_container_1" />
<corners android:radius="6dp"/>
</shape>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/primary_theme_10" />
<corners android:radius="6dp"/>
</shape>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:endColor="@color/ui_surface_0"
android:startColor="@color/ui_surface" />
</shape>

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient
android:endColor="@color/ui_surface"
android:startColor="@color/ui_surface_0" />
</shape>

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/bg_shape_primary_theme_alpha_10_radius_6" android:state_checked="true" />
<item android:drawable="@drawable/bg_shape_primary_theme_alpha_10_radius_6" android:state_focused="true" />
</selector>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/button_round_theme_alpha_10" android:state_pressed="true" />
<item android:drawable="@drawable/button_round_theme_alpha_10" android:state_focused="true" />
<item android:drawable="@drawable/button_round_theme_alpha_10" android:state_selected="true" />
<item android:drawable="@drawable/button_round_theme_alpha_10" android:state_checked="true" />
<item android:drawable="@drawable/bg_shape_ui_container_1_radius_999" />
</selector>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/subject_tab_down" android:state_selected="true" />
<item android:drawable="@color/white" android:state_focused="false" />
</selector>

View File

@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/bg_tag_text" android:state_checked="true" />
<item android:drawable="@drawable/bg_tag_text" android:state_focused="true" />
</selector>

View File

@ -2,20 +2,19 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="24dp"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:orientation="vertical">
android:paddingBottom="24dp">
<TextView
android:id="@+id/title"
style="@style/TextHeadline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:includeFontPadding="false"
android:textColor="@color/text_primary"
android:textStyle="bold"
android:textSize="14sp"
tools:text="玩法" />
<androidx.recyclerview.widget.RecyclerView

View File

@ -1,34 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="80dp"
android:layout_height="52dp">
<ImageView
<View
android:id="@+id/selected_tag"
android:layout_width="14dp"
android:layout_width="0dp"
android:layout_height="6dp"
android:layout_centerHorizontal="true"
android:layout_marginTop="36dp"
android:src="@drawable/ic_catalog_selected"
android:layout_marginTop="28dp"
android:background="@drawable/bg_category_selected_tag"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="@id/catalog_name"
app:layout_constraintStart_toStartOf="@id/catalog_name"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
<TextView
android:id="@+id/catalog_name"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/text_primary"
android:textSize="12sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintWidth_max="0dp"
tools:text="精选" />
<ImageView
android:id="@+id/recommend_tag"
android:layout_width="16dp"
android:layout_height="12dp"
android:layout_marginLeft="52dp"
android:layout_marginStart="52dp"
android:layout_marginTop="16dp"
android:src="@drawable/ic_recommend"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
</RelativeLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -25,7 +25,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
android:layout_marginLeft="16dp"
android:gravity="center"
android:textColor="@color/text_primary"
android:textSize="@dimen/secondary_title_text_size"
@ -35,6 +34,27 @@
app:layout_constraintTop_toTopOf="@+id/closeIv"
tools:text="其他2款游戏" />
<TextView
android:id="@+id/subtitleTv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:layout_marginEnd="16dp"
android:ellipsize="end"
android:includeFontPadding="false"
android:lines="1"
android:textColor="@color/text_theme"
android:textSize="@dimen/secondary_size"
android:visibility="gone"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="@+id/titleTv"
app:layout_constraintEnd_toStartOf="@+id/closeIv"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintStart_toEndOf="@+id/titleTv"
app:layout_constraintTop_toTopOf="@+id/titleTv"
tools:text="使用教程使用教程使用教程使用教程使用教程使用教程使用教程使用教程"
tools:visibility="visible" />
<View
android:id="@+id/divider"
android:layout_width="0dp"

View File

@ -1,18 +1,93 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/drawerLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/ui_surface">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<View
android:id="@+id/v_more_category"
android:layout_width="92dp"
android:layout_height="32dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:background="@drawable/bg_more_category_default"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/ui_divider" />
<TextView
android:id="@+id/tv_tag_number"
android:layout_width="12dp"
android:layout_height="12dp"
android:background="@drawable/background_shape_theme_radius_999"
android:gravity="center"
android:textColor="@color/text_aw_primary"
android:textSize="8sp"
android:visibility="invisible"
app:layout_constraintEnd_toEndOf="@id/v_more_category"
app:layout_constraintTop_toTopOf="@id/v_more_category"
tools:text="5"
tools:visibility="visible" />
<TextView
android:id="@+id/tv_more_category"
style="@style/TextCaption1B"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:drawablePadding="4dp"
android:gravity="center"
android:text="@string/more_category"
app:drawableStartCompat="@drawable/ic_basic_classification"
app:layout_constraintBottom_toBottomOf="@id/v_more_category"
app:layout_constraintEnd_toEndOf="@id/v_more_category"
app:layout_constraintStart_toStartOf="@id/v_more_category"
app:layout_constraintTop_toTopOf="@id/v_more_category" />
<View
android:id="@+id/v_search_category"
android:layout_width="0dp"
android:layout_height="32dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="12dp"
android:background="@drawable/bg_shape_ui_container_2_radius_999"
app:layout_constraintBottom_toBottomOf="@id/v_more_category"
app:layout_constraintEnd_toStartOf="@id/v_more_category"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/v_more_category" />
<ImageView
android:id="@+id/iv_search_category"
android:layout_width="14dp"
android:layout_height="14dp"
android:layout_marginStart="12dp"
app:layout_constraintBottom_toBottomOf="@id/v_search_category"
app:layout_constraintStart_toStartOf="@id/v_search_category"
app:layout_constraintTop_toTopOf="@id/v_search_category"
app:srcCompat="@drawable/ic_search"
app:tint="@color/text_instance" />
<TextView
android:id="@+id/tv_search_category"
style="@style/TextBody2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="@string/search_category_tags"
android:textColor="@color/text_instance"
app:layout_constraintBottom_toBottomOf="@id/v_search_category"
app:layout_constraintStart_toEndOf="@id/iv_search_category"
app:layout_constraintTop_toTopOf="@id/v_search_category" />
<RelativeLayout
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/v_more_category">
<LinearLayout
android:id="@+id/categoryContainer"
@ -21,11 +96,24 @@
android:layout_marginTop="1dp"
android:orientation="horizontal">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/categoryRv"
<com.gh.gamecenter.common.view.RadiusCardView
android:id="@+id/cv_category"
android:layout_width="80dp"
android:layout_height="match_parent"
android:background="@color/ui_background" />
android:layout_marginTop="8dp"
app:topRightRadius="8dp">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/categoryRv"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<View
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg_category_sidebar" />
</com.gh.gamecenter.common.view.RadiusCardView>
<FrameLayout
android:id="@+id/gamesContainer"
@ -51,62 +139,16 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
<ImageView
android:visibility="gone"
android:id="@+id/guideContainer"
android:layout_width="187dp"
android:layout_height="63dp"
android:layout_marginTop="20dp"
android:layout_marginLeft="66dp"
android:src="@drawable/pic_category_guide" />
</RelativeLayout>
<RelativeLayout
android:id="@+id/directoryContainer"
android:layout_width="260dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="@color/ui_surface">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/directoryRv"
android:layout_above="@+id/bottom"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<LinearLayout
android:id="@+id/bottom"
android:layout_width="match_parent"
android:layout_height="60dp"
android:paddingLeft="16dp"
android:paddingRight="16dp"
android:layout_alignParentBottom="true"
android:orientation="horizontal"
android:gravity="center">
<TextView
android:id="@+id/resetTv"
android:layout_width="0dp"
android:layout_height="36dp"
android:layout_weight="1"
android:gravity="center"
android:background="@drawable/bg_shape_ui_container_2_radius_999"
android:textColor="@color/text_primary"
android:textSize="14sp"
android:text="重置" />
<TextView
android:id="@+id/confirmTv"
android:layout_width="0dp"
android:layout_height="36dp"
android:layout_weight="1"
android:layout_marginLeft="16dp"
android:gravity="center"
android:background="@drawable/bg_tag_text"
android:textColor="@color/white"
android:textSize="14sp"
android:text="确定" />
</LinearLayout>
</RelativeLayout>
</androidx.drawerlayout.widget.DrawerLayout>
<ImageView
android:id="@+id/guideContainer"
android:layout_width="244dp"
android:layout_height="57dp"
android:layout_marginEnd="8dp"
android:src="@drawable/pic_category_guide"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/v_more_category"
tools:visibility="visible" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,37 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/ui_surface">
<FrameLayout
android:id="@+id/fl_tags_container"
android:layout_width="match_parent"
android:layout_height="40dp"
android:visibility="gone">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_tags"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="8dp"
android:clipToPadding="false"
android:paddingHorizontal="12dp" />
<View
android:layout_width="16dp"
android:layout_height="match_parent"
android:layout_gravity="start"
android:background="@drawable/shape_filtered_left" />
<View
android:layout_width="16dp"
android:layout_height="match_parent"
android:layout_gravity="end"
android:background="@drawable/shape_filtered_right" />
</FrameLayout>
<com.gh.common.view.CategoryFilterView
android:id="@+id/filterContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<com.google.android.flexbox.FlexboxLayout
android:visibility="gone"
android:layout_below="@+id/filterContainer"
android:id="@+id/selectedCategoryContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="15dp"
android:paddingBottom="8dp"
app:flexWrap="wrap" />
android:layout_height="36dp"
android:layout_below="@id/fl_tags_container" />
<FrameLayout
android:id="@+id/list_skeleton"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/selectedCategoryContainer" />
android:layout_below="@+id/filterContainer" />
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/list_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/selectedCategoryContainer">
android:layout_below="@+id/filterContainer">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_rv"

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
@ -7,9 +8,18 @@
android:id="@+id/contentContainer"
layout="@layout/fragment_list_base_skeleton" />
<com.gh.gamecenter.common.view.StatusBarView
android:id="@+id/statusBar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/ui_surface"
android:visibility="gone"
tools:visibility="visible" />
<FrameLayout
android:id="@+id/placeholder"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="match_parent"
android:layout_below="@+id/statusBar" />
</RelativeLayout>

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/ui_surface">
<com.gh.common.view.ConfigFilterView
android:id="@+id/filterView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible" />
<FrameLayout
android:id="@+id/list_skeleton"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/filterView"/>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/list_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@+id/filterView">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_rv"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<include
layout="@layout/reuse_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
<include layout="@layout/reuse_no_connection" />
<include layout="@layout/reuse_none_data" />
<include layout="@layout/reuse_data_exception" />
</RelativeLayout>

View File

@ -1,48 +1,146 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<RelativeLayout
android:id="@+id/subject_tab_container"
android:layout_width="match_parent"
android:layout_height="@dimen/tab_layout_height"
android:background="@color/ui_surface"
android:visibility="gone">
<androidx.viewpager.widget.ViewPager
android:id="@+id/subject_viewpager"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/subject_tab_container" />
<!-- <View-->
<!-- android:id="@+id/subject_tab_line"-->
<!-- android:layout_width="match_parent"-->
<!-- android:layout_height="1dp"-->
<!-- android:layout_alignParentBottom="true"-->
<!-- android:background="@color/background"-->
<!-- android:visibility="gone" />-->
<View
android:id="@+id/background"
android:layout_width="0dp"
android:layout_height="0dp"
android:alpha="0"
android:background="@color/black"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/subject_tab_container"/>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/subject_tab_container"
android:layout_width="0dp"
android:layout_height="48dp"
android:background="@color/ui_surface"
android:visibility="gone"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="@+id/subject_viewpager"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible">
<com.gh.gamecenter.common.view.TabIndicatorView
android:id="@+id/subject_tab_indicator"
android:layout_width="match_parent"
android:layout_width="0dp"
android:layout_height="@dimen/default_tab_indicator_height"
android:layout_alignParentBottom="true" />
android:layout_marginEnd="8dp"
app:disableIndicatorScaling="true"
app:indicatorColor="@color/primary_theme"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/optionIv"
app:layout_constraintStart_toStartOf="parent"
app:layout_goneMarginEnd="0dp" />
<com.google.android.material.tabs.TabLayout
<com.gh.gamecenter.common.view.NoDefaultMinWidthTabLayout
android:id="@+id/subject_tab"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:tabIndicatorColor="@color/primary_theme"
app:tabMode="scrollable"
app:tabIndicatorHeight="0dp"
app:tabRippleColor="@color/transparent"
app:tabSelectedTextColor="@color/primary_theme"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginEnd="8dp"
android:overScrollMode="never"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/optionIv"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_goneMarginEnd="0dp"
app:tabIndicator="@null"
app:tabTextAppearance="@style/TabLayoutTextAppearance" />
app:tabIndicatorHeight="0dp"
app:tabMaxWidth="0dp"
app:tabMode="scrollable"
app:tabRippleColor="@color/transparent"
app:tabTextAppearance="@style/ColumnCollectionTabDetailTabTextAppearance" />
</RelativeLayout>
<ImageView
android:id="@+id/optionIv"
android:layout_width="28dp"
android:layout_height="28dp"
android:layout_marginEnd="16dp"
android:background="@drawable/bg_shape_ui_container_2_radius_999"
android:padding="6dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/ic_options"
tools:visibility="visible" />
<androidx.viewpager.widget.ViewPager
android:id="@+id/subject_viewpager"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<View
android:id="@+id/maskView"
android:layout_width="16dp"
android:layout_height="match_parent"
android:layout_marginEnd="8dp"
android:background="@drawable/bg_game_detail_tab_gradient_mask"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/optionIv"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<FrameLayout
android:id="@+id/settingContainer"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@+id/subject_tab_container" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/settingGuideContainer"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:visibility="visible">
<ImageView
android:id="@+id/settingGuideIv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="36dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/guide_column_collection_custom_setting" />
<View
android:id="@+id/skipView"
android:layout_width="24dp"
android:layout_height="32dp"
android:layout_marginEnd="64dp"
app:layout_constraintBottom_toBottomOf="@+id/settingClickView"
app:layout_constraintEnd_toStartOf="@+id/settingClickView"
app:layout_constraintTop_toTopOf="@+id/settingClickView" />
<View
android:id="@+id/settingClickView"
android:layout_width="80dp"
android:layout_height="32dp"
android:layout_marginEnd="24dp"
android:layout_marginBottom="24dp"
app:layout_constraintBottom_toBottomOf="@+id/settingGuideIv"
app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -1,6 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
@ -17,44 +18,17 @@
android:id="@+id/subject_type_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingTop="8dp"
android:paddingBottom="12dp"
app:layout_scrollFlags="scroll|enterAlwaysCollapsed" />
<LinearLayout
android:id="@+id/subject_filter_container"
android:layout_width="wrap_content"
<com.gh.common.view.ConfigFilterView
android:id="@+id/filterView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="12dp"
android:layout_marginBottom="16dp"
android:background="@drawable/subject_tiled_tab_background">
<CheckedTextView
android:id="@+id/subject_tabbar_hottest"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:background="@drawable/subject_tiled_tab_selector"
android:checked="true"
android:gravity="center"
android:paddingLeft="24dp"
android:paddingRight="24dp"
android:text="最热"
android:textAlignment="center"
android:textColor="@color/subject_tiled_tab_color_selector"
android:textSize="12sp" />
<CheckedTextView
android:id="@+id/subject_tabbar_newest"
android:layout_width="wrap_content"
android:layout_height="24dp"
android:background="@drawable/subject_tiled_tab_selector"
android:checked="false"
android:gravity="center"
android:paddingLeft="24dp"
android:paddingRight="24dp"
android:text="最新"
android:textAlignment="center"
android:textColor="@color/subject_tiled_tab_color_selector"
android:textSize="12sp" />
</LinearLayout>
android:visibility="gone"
tools:visibility="visible" />
</com.google.android.material.appbar.AppBarLayout>

Some files were not shown because too many files have changed in this diff Show More