Compare commits

...

6 Commits

72 changed files with 2678 additions and 250 deletions

View File

@ -72,6 +72,7 @@ android_build:
only:
- dev
- release
- feat/GHZSCY-5294
# 代码检查
sonarqube_analysis:
@ -103,6 +104,7 @@ sonarqube_analysis:
only:
- dev
- release
- feat/GHZSCY-5294
## 发送简易检测结果报告
send_sonar_report:
@ -121,6 +123,7 @@ send_sonar_report:
only:
- dev
- release
- feat/GHZSCY-5294
oss-upload&send-email:
tags:
@ -152,4 +155,5 @@ oss-upload&send-email:
- /usr/local/bin/python /ci-android-mail-jira-comment.py
only:
- dev
- release
- release
- feat/GHZSCY-5294

View File

@ -575,7 +575,9 @@ andResGuard {
"R.id.cardMask",
"R.id.cardGradientMask",
"R.id.gameIconIv",
"R.id.titleContainer"
"R.id.titleContainer",
"R.id.v_bubble_background",
"R.id.tv_bubble"
]
compressFilePattern = [
"*.png",

View File

@ -0,0 +1,18 @@
package com.gh.common.view
import android.content.Context
import android.util.AttributeSet
import android.view.MotionEvent
import android.widget.FrameLayout
import androidx.constraintlayout.widget.ConstraintLayout
class InterceptTouchContainView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : FrameLayout(context, attrs, defStyleAttr) {
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
return true
}
}

View File

@ -1,7 +1,14 @@
package com.gh.gamecenter.adapter.viewholder
import android.graphics.Paint
import android.widget.TextView
import androidx.core.view.marginStart
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.databinding.*
import com.gh.gamecenter.home.custom.model.CustomPageData
class SearchGameFooterViewHolder(val binding: SearchGameFooterBinding) : BaseRecyclerViewHolder<Any>(binding.root)
class PersonalHomeRatingViewHolder(val binding: PersonalHomeRatingBinding) : BaseRecyclerViewHolder<Any>(binding.root)
@ -22,4 +29,62 @@ class CommonCollectionImageTextItemViewHolder(val binding: CommonCollectionImage
BaseRecyclerViewHolder<Any>(binding.root)
class CommonCollectionDetailTwoItemHorizontalViewHolder(val binding: CommonCollectionDetailTwoItemHorizontalCustomBinding) :
BaseRecyclerViewHolder<Any>(binding.root)
BaseRecyclerViewHolder<Any>(binding.root)
class CustomCollectionDetailRecommendCardViewHolder(
val binding: RecyclerRecommendCardCommonContentCollectionDetailCustomBinding
) : BaseRecyclerViewHolder<Any>(binding.root) {
fun bind(item: CustomPageData.RecommendCard) {
ImageUtils.display(binding.ivCover, item.image)
binding.tvTitle.text = item.title
binding.tvLabel.text = item.tag
binding.tvPrice.text = item.highlight.text
binding.tvOriginalPrice.text = if (item.deletion.isShowCurrencySymbol) {
itemView.context.getString(R.string.price_with_symbol, item.deletion.text)
} else {
item.deletion.text
}
binding.tvOriginalPrice.paint.flags = Paint.STRIKE_THRU_TEXT_FLAG
binding.tvCopy.text = item.addedContent
binding.tvPriceSymbol.goneIf(!item.highlight.isShowCurrencySymbol)
binding.root.post {
val views = mutableListOf(
binding.tvPrice,
binding.tvOriginalPrice,
binding.tvCopy,
)
if (item.highlight.isShowCurrencySymbol) {
views.add(0, binding.tvPriceSymbol)
}
binding.tvLabel.goneIf(item.tag.isBlank()) {
views.add(binding.tvLabel)
}
views.forEach {
it.goneIf(false)
}
hideOutOfBoundViewsIfNeed(views)
}
}
/**
* 计算统一排的view能否完整显示在 itemView 内,如果显示不下,则根据优先级依次隐藏优先级较低的
* 数据中的View优先级由高到低排列最后一个优先级最低
*/
private fun hideOutOfBoundViewsIfNeed(views: MutableList<TextView>) {
if (views.isEmpty()) {
return
}
val totalWidth = views.sumOf {
it.width + it.marginStart
}
if (totalWidth > itemView.width) {
views.removeLast().goneIf(true)
hideOutOfBoundViewsIfNeed(views)
}
}
}

View File

@ -1,6 +1,7 @@
package com.gh.gamecenter.entity
import android.os.Parcelable
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@ -17,8 +18,14 @@ data class CommonCollectionEntity(
@SerializedName("vertical_line")
val verticalLine: String = "", // 竖排时才有数据,代表竖排行数控制
@SerializedName("common_collection_content")
val collectionList: MutableList<CommonCollectionContentEntity> = mutableListOf()
val collectionList: MutableList<CommonCollectionContentEntity> = mutableListOf(),
@SerializedName("common_collection_recommend_cards")
private val _commonCollectionRecommendCards: List<CustomPageData.RecommendCard>? = null
) {
val commonCollectionRecommendCards: List<CustomPageData.RecommendCard>
get() = _commonCollectionRecommendCards ?: emptyList()
val layoutChinese: String
get() = when (layout) {
0 -> "轮播banner"
@ -30,6 +37,10 @@ data class CommonCollectionEntity(
6 -> "双列竖式卡片"
7 -> "竖式图文列表"
8 -> "横排图文列表"
9 -> "内容标签泳道"
10 -> "通知栏目"
11 -> "公告横幅"
12 -> "推荐卡片"
else -> ""
}
}

View File

@ -1,5 +1,6 @@
package com.gh.gamecenter.entity
import android.graphics.Color
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.feature.entity.Time
import com.google.gson.annotations.SerializedName
@ -22,5 +23,87 @@ data class GameNavigationEntity(
var hintStartTime: Long = 0,
@SerializedName("hint_end_time")
var hintEndTime: Long = 0,
var time: Time? = null
)
var time: Time? = null,
@SerializedName("entry_name_status")
private val _entryNameStatus: String? = null,
@SerializedName("guide")
private val _guide: Guide? = null
) {
val isShowEntryName: Boolean
get() = ENTRY_NAME_STATUS_HIDE != _entryNameStatus
val isShowBubble: Boolean
get() = _guide != null
val guide: Guide
get() = _guide ?: Guide()
data class Guide(
@SerializedName("text")
private val _text: String? = null,
@SerializedName("color")
private val _color: String? = null
) {
val text: String
get() = _text ?: ""
val color: String
get() = _color ?: ""
/**
* 文字颜色 不透明度为 70%
* 十六进制: B2
* 十进制: 178
*/
val textColorInt: Int
get() = parseColorWithAlpha(TEXT_ALPHA, color)
/**
* 边框颜色 不透明度为 10%
* 十六进制: 4D
* 十进制: 77
*/
val borderColorInt: Int
get() = parseColorWithAlpha(BORDER_ALPHA, color)
/**
* 背景颜色 不透明度为 30%
* 十六进制: 1A
* 十进制: 26
*/
val backgroundColorInt: Int
get() = parseColorWithAlpha(BACKGROUND_ALPHA, color)
private fun parseColorWithAlpha(alpha: Int, color: String): Int {
val colorInt = try {
Color.parseColor(color)
} catch (e: IllegalArgumentException) {
// 解析失败,默认使用红色
Color.RED
}
val hsv = FloatArray(3)
Color.colorToHSV(colorInt, hsv)
return Color.HSVToColor(alpha, hsv)
}
companion object {
private const val COLOR_BLUE = "blue"
private const val COLOR_PURPLE = "purple"
private const val COLOR_RED = "red"
private const val COLOR_ORANGE = "orange"
private const val COLOR_YELLOW = "yellow"
private const val COLOR_GREEN = "green"
private const val TEXT_ALPHA = 178
private const val BORDER_ALPHA = 77
private const val BACKGROUND_ALPHA = 26
}
}
companion object {
private const val ENTRY_NAME_STATUS_HIDE = "hide"
}
}

View File

@ -20,7 +20,8 @@ data class HomeRecommend(
private val _image: String? = null,
@SerializedName("link_community")
private val community: CommunityEntity? = null,
@SerializedName("guide")
private val _guide: Guide? = null,
// 绑定的曝光实体
var exposureEvent: ExposureEvent? = null,
) {
@ -28,6 +29,12 @@ data class HomeRecommend(
val image: String
get() = _image ?: ""
val isShowGuide: Boolean
get() = _guide != null
val guide: Guide
get() = _guide ?: Guide()
fun transformLinkEntity(): LinkEntity {
return LinkEntity(
name = name,
@ -38,4 +45,12 @@ data class HomeRecommend(
community = community
)
}
data class Guide(
@SerializedName("text")
private val _text: String? = null
) {
val text: String
get() = _text ?: ""
}
}

View File

@ -36,7 +36,7 @@ data class HomeSubSlide(
return if (linkType.isNotEmpty()) {
LinkEntity(link = linkId, type = linkType, text = linkText, community = community)
} else {
LinkEntity(link = cardId, type = cardType, linkText = cardText)
LinkEntity(link = cardId, type = cardType, text = cardText)
}
}
}
@ -44,9 +44,12 @@ data class HomeSubSlide(
data class CardData(
var games: List<GameEntity> = arrayListOf(),
@SerializedName("game_total")
val gameTotal: GameTotal = GameTotal(),
private val _gameTotal: GameTotal? = null,
val stamp: String = ""
)
) {
val gameTotal: GameTotal
get() = _gameTotal ?: GameTotal()
}
data class GameTotal(
val vote: Int = 0,

View File

@ -9,10 +9,7 @@ import com.gh.common.exposure.IExposable
import com.gh.common.util.DirectUtils
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.adapter.viewholder.CommonCollectionDetailOneItemViewHolder
import com.gh.gamecenter.adapter.viewholder.CommonCollectionDetailTwoItemHorizontalViewHolder
import com.gh.gamecenter.adapter.viewholder.CommonCollectionDetailTwoItemViewHolder
import com.gh.gamecenter.adapter.viewholder.CommonCollectionImageTextItemViewHolder
import com.gh.gamecenter.adapter.viewholder.*
import com.gh.gamecenter.common.baselist.ListAdapter
import com.gh.gamecenter.common.constant.ItemViewType
import com.gh.gamecenter.common.exposure.ExposureSource
@ -21,11 +18,13 @@ import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.viewholder.FooterViewHolder
import com.gh.gamecenter.entity.CommonCollectionContentEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.game.data.CommonContentCollectionDetailItem
import com.gh.gamecenter.game.data.CommonContentCollectionDetailRecommendCardItem
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_BANNER
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_SLIDE_BANNER
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_RECOMMEND_CARD
class CustomCommonCollectionDetailAdapter(
context: Context,
@ -35,13 +34,13 @@ class CustomCommonCollectionDetailAdapter(
val mTabIndex: Int,
val mEntrance: String,
private val mBasicExposureSourceList: List<ExposureSource>?
) : ListAdapter<CommonCollectionContentEntity>(context), IExposable {
) : ListAdapter<CommonContentCollectionDetailItem>(context), IExposable {
private val mExposureEventSparseArray = SparseArray<ExposureEvent>()
override fun areItemsTheSame(
oldItem: CommonCollectionContentEntity?,
newItem: CommonCollectionContentEntity?
oldItem: CommonContentCollectionDetailItem?,
newItem: CommonContentCollectionDetailItem?
): Boolean {
return oldItem == newItem
}
@ -50,12 +49,19 @@ class CustomCommonCollectionDetailAdapter(
return if (viewType == ItemViewType.ITEM_BODY) {
when (collectionStyle) {
"1-1" -> CommonCollectionDetailOneItemViewHolder(parent.toBinding())
"1-2" -> if (mViewModel.getLayout() == COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_SLIDE_BANNER
|| mViewModel.getLayout() == COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_BANNER
) {
CommonCollectionDetailTwoItemHorizontalViewHolder(parent.toBinding())
} else {
CommonCollectionDetailTwoItemViewHolder(parent.toBinding())
"1-2" -> when (mViewModel.getLayout()) {
COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_SLIDE_BANNER,
COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_BANNER -> {
CommonCollectionDetailTwoItemHorizontalViewHolder(parent.toBinding())
}
COMMON_CONTENT_COLLECTION_LAYOUT_RECOMMEND_CARD -> {
CustomCollectionDetailRecommendCardViewHolder(parent.toBinding())
}
else -> {
CommonCollectionDetailTwoItemViewHolder(parent.toBinding())
}
}
else -> CommonCollectionImageTextItemViewHolder(parent.toBinding())
@ -79,7 +85,7 @@ class CustomCommonCollectionDetailAdapter(
}
val contentEntity = mEntityList[position]
val linkEntity = mEntityList[position].linkEntity
val linkEntity = contentEntity.link
val listener: (v: View) -> Unit = {
DirectUtils.directToLinkPage(
@ -189,6 +195,15 @@ class CustomCommonCollectionDetailAdapter(
root.setOnClickListener(listener)
}
}
is CustomCollectionDetailRecommendCardViewHolder-> {
if (contentEntity is CommonContentCollectionDetailRecommendCardItem) {
val recommendCard = contentEntity.data
holder.bind(recommendCard)
holder.itemView.setOnClickListener(listener)
}
}
}
}

View File

@ -5,8 +5,11 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.gh.gamecenter.common.baselist.ListViewModel
import com.gh.gamecenter.entity.CommonCollectionContentEntity
import com.gh.gamecenter.entity.CommonCollectionEntity
import com.gh.gamecenter.game.data.CommonContentCollectionDetailItem
import com.gh.gamecenter.game.data.CommonContentCollectionDetailOldItem
import com.gh.gamecenter.game.data.CommonContentCollectionDetailRecommendCardItem
import com.gh.gamecenter.home.custom.model.CustomPageItem
import com.gh.gamecenter.retrofit.RetrofitManager
import com.halo.assistant.HaloApp
import io.reactivex.Observable
@ -15,16 +18,16 @@ class CustomCommonCollectionDetailViewModel(
application: Application,
private val mCollectionId: String,
cachedLayout: Int,
) : ListViewModel<CommonCollectionContentEntity, CommonCollectionContentEntity>(application) {
) : ListViewModel<CommonContentCollectionDetailItem, CommonContentCollectionDetailItem>(application) {
var finalLayout = cachedLayout
val commonCollectionLiveData = MutableLiveData<CommonCollectionEntity>()
fun getLayout() : Int {
fun getLayout(): Int {
return finalLayout
}
override fun provideDataObservable(page: Int): Observable<MutableList<CommonCollectionContentEntity>>? {
override fun provideDataObservable(page: Int): Observable<List<CommonContentCollectionDetailItem>>? {
return if (page == 1) {
RetrofitManager.getInstance().api
.getCommonCollectionDetail(mCollectionId)
@ -33,11 +36,40 @@ class CustomCommonCollectionDetailViewModel(
finalLayout = it.layout
}
commonCollectionLiveData.postValue(it)
it.collectionList
if (it.layout == CustomPageItem.COMMON_CONTENT_COLLECTION_LAYOUT_RECOMMEND_CARD) {
it.commonCollectionRecommendCards
.map {
CommonContentCollectionDetailRecommendCardItem(it)
}
} else {
it.collectionList.map {
CommonContentCollectionDetailOldItem(it)
}
}
}
} else {
RetrofitManager.getInstance().api
.getCommonCollectionDetail(mCollectionId, page)
if (finalLayout == CustomPageItem.COMMON_CONTENT_COLLECTION_LAYOUT_RECOMMEND_CARD) {
RetrofitManager.getInstance().api
.getCommonCollectionDetailWithRecommenCards(mCollectionId, page)
.map {
it
.map { entity ->
CommonContentCollectionDetailRecommendCardItem(entity)
}
.toMutableList()
}
} else {
RetrofitManager.getInstance().api
.getCommonCollectionDetail(mCollectionId, page)
.map {
it
.map { entity ->
CommonContentCollectionDetailOldItem(entity)
}
.toMutableList()
}
}
}
}

View File

@ -0,0 +1,49 @@
package com.gh.gamecenter.game.data
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.entity.CommonCollectionContentEntity
import com.gh.gamecenter.home.custom.model.CustomPageData
abstract class CommonContentCollectionDetailItem {
abstract val title: String
abstract val image: String
abstract val addedContent1: String
abstract val addedContent2: String
abstract val link: LinkEntity
}
data class CommonContentCollectionDetailOldItem(
val data: CommonCollectionContentEntity
) : CommonContentCollectionDetailItem() {
override val title: String
get() = data.title
override val image: String
get() = data.image
override val addedContent1: String
get() = data.addedContent1 ?: ""
override val addedContent2: String
get() = data.addedContent2 ?: ""
override val link: LinkEntity
get() = data.linkEntity
}
data class CommonContentCollectionDetailRecommendCardItem(
val data: CustomPageData.RecommendCard
) : CommonContentCollectionDetailItem() {
override val title: String
get() = data.title
override val image: String
get() = data.image
override val addedContent1: String
get() = data.addedContent
override val addedContent2: String
get() = ""
override val link: LinkEntity
get() = LinkEntity(link = data.linkId, type = data.linkType, linkText = data.linkText)
}

View File

@ -23,6 +23,8 @@ class BannerInRecyclerController(
private var isParentScrolling = false
private var isDestroyed = false
private val onScrollListener = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
isParentScrolling = newState != RecyclerView.SCROLL_STATE_IDLE
@ -37,6 +39,7 @@ class BannerInRecyclerController(
fun onViewAttachedToWindow(parent: RecyclerView?) {
isAttachToWindow = true
parent?.addOnScrollListener(onScrollListener)
start()
}
fun onViewDetachedFromWindow(parent: RecyclerView?) {
@ -46,7 +49,7 @@ class BannerInRecyclerController(
}
fun start() {
if (isActive && !isParentScrolling) {
if (isActive && !isParentScrolling && !isDestroyed) {
handler.removeCallbacksAndMessages(null)
nextToPage()
}
@ -56,7 +59,7 @@ class BannerInRecyclerController(
handler.removeCallbacksAndMessages(null)
}
private fun destroy() {
fun destroy() {
handler.removeCallbacksAndMessages(null)
}
@ -73,6 +76,7 @@ class BannerInRecyclerController(
}
override fun onDestroy(owner: LifecycleOwner) {
isDestroyed = true
destroy()
}

View File

@ -5,6 +5,7 @@ import android.os.Build
import android.os.Bundle
import android.view.View
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.OnScrollListener
import com.gh.common.exposure.ExposureListener
@ -184,6 +185,10 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable {
}
with(viewModel) {
customPageData.observe(viewLifecycleOwner, Observer {
setNavigationTitle(it.title)
})
dataList.observe(viewLifecycleOwner) {
adapter.submitList(it)
}

View File

@ -483,6 +483,9 @@ class CustomPageViewModel(
}
}
val shareHiddenNotifications: LiveData<HashMap<String, MutableSet<String>>>
get() = repository.hiddenNotifications
private fun getPositionAndPackageMap(list: List<CustomPageItem>): HashMap<String, Int> {
val hashMap = hashMapOf<String, Int>()
list.forEach { custom ->
@ -608,10 +611,8 @@ class CustomPageViewModel(
private val _userDestination = MutableLiveData<Event<Triple<String, String, String>>>()
val userDestination: LiveData<Event<Triple<String, String, String>>> = _userDestination
override fun navigateUserHomePage(item: CustomPageItem, userId: String) {
//todo 待定
val entrance = ""
val path = ""
when {
item is CustomSubjectCollectionItem -> pageTracker.trackGameListCollectionClickWithUser(item, userId)
}
@ -634,7 +635,12 @@ class CustomPageViewModel(
private val _linkDestination = MutableLiveData<Event<Pair<LinkEntity, ExposureEvent?>>>()
val linkDestination: LiveData<Event<Pair<LinkEntity, ExposureEvent?>>> = _linkDestination
override fun navigateToLinkPage(item: CustomPageItem, link: LinkEntity, text: String, exposureEvent: ExposureEvent?) {
override fun navigateToLinkPage(
item: CustomPageItem,
link: LinkEntity,
text: String,
exposureEvent: ExposureEvent?
) {
_linkDestination.value = Event(Pair(link, exposureEvent))
}
@ -650,7 +656,8 @@ class CustomPageViewModel(
_badgeWallDestination.value = Event(comment)
}
private val _gameDetailDestinationOnAmway = MutableLiveData<Event<Triple<CustomPageTrackData, String, ExposureEvent?>>>()
private val _gameDetailDestinationOnAmway =
MutableLiveData<Event<Triple<CustomPageTrackData, String, ExposureEvent?>>>()
val gameDetailDestinationOnAmway: LiveData<Event<Triple<CustomPageTrackData, String, ExposureEvent?>>> =
_gameDetailDestinationOnAmway
@ -707,6 +714,10 @@ class CustomPageViewModel(
_gameListSquareDestination.value = Event(item)
}
fun hideNotificationItem(id: String, itemId: String) {
repository.hideNotificationItem(id, itemId)
}
private fun Disposable.addDisposable() {
compositeDisposable.add(this)
}

View File

@ -0,0 +1,98 @@
package com.gh.gamecenter.home.custom.adapter
import android.annotation.SuppressLint
import android.content.Context
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.databinding.GameCollectionBannerItemBinding
import com.gh.gamecenter.home.custom.model.CustomPageData
class AnnouncementBannerAdapter(
context: Context,
private val listener: OnChildEventListener
) :
CustomBaseChildAdapter<CustomPageData.Announcement, AnnouncementBannerAdapter.AnnouncementBannerChildViewHolder>(
context
) {
@SuppressLint("NotifyDataSetChanged")
override fun checkResetData(updates: List<CustomPageData.Announcement>?) {
if (updates.isNullOrEmpty()) {
_countAndKey = null
notifyDataSetChanged()
return
}
val countAndKey = _countAndKey ?: Pair(0, "")
val (oldSize, oldKeys) = countAndKey
val newSize = updates.size
var newKeys = ""
updates.forEach {
newKeys += it.id
}
val needRefresh = !(oldSize == newSize && oldKeys == newKeys && !isDarkModeChange())
if (needRefresh) {
if (isBanner) {
// 刷新 [前 中 后] 三个 itemView
val currentPosition = listener.getCurrentPosition()
val startPosition = if (currentPosition > 0) currentPosition - 1 else currentPosition
notifyItemRangeChanged(startPosition, 3)
} else {
notifyDataSetChanged()
}
}
}
val isBanner: Boolean
get() = dataList.size > 1
override fun getItemCount(): Int {
return if (isBanner) Int.MAX_VALUE else super.getItemCount()
}
fun getDataPosition(position: Int) =
if (isBanner) {
position % dataList.size
} else {
position
}
override fun getItem(position: Int): CustomPageData.Announcement {
return super.getItem(getDataPosition(position))
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AnnouncementBannerChildViewHolder {
return AnnouncementBannerChildViewHolder(parent.toBinding())
}
override fun onBindViewHolder(holder: AnnouncementBannerChildViewHolder, position: Int) {
val item = getItem(position)
listener.exposure(position, item)
holder.bind(item)
holder.binding.bannerIv.setOnClickListener {
listener.onItemClick(getDataPosition(position), item)
}
}
class AnnouncementBannerChildViewHolder(val binding: GameCollectionBannerItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: CustomPageData.Announcement) {
ImageUtils.display(binding.bannerIv, item.image)
}
}
interface OnChildEventListener {
fun onItemClick(childPosition: Int, announcement: CustomPageData.Announcement)
fun getCurrentPosition(): Int
fun exposure(childPosition: Int, announcement: CustomPageData.Announcement)
}
}

View File

@ -0,0 +1,57 @@
package com.gh.gamecenter.home.custom.adapter
import android.content.Context
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.databinding.RecyclerContentLabelLaneItemBinding
import com.gh.gamecenter.home.custom.model.CustomPageData
class ContentLabelLaneAdapter(
context: Context,
private val clickInvoke: (Int, CustomPageData.CommonContentCollection.ContentTag) -> Unit,
private val exposureInvoke: (Int, CustomPageData.CommonContentCollection.ContentTag) -> Unit
) : CustomBaseChildAdapter<CustomPageData.CommonContentCollection.ContentTag, ContentLabelLaneAdapter.ContentLabelChildViewHolder>(
context
) {
override fun getKey(t: CustomPageData.CommonContentCollection.ContentTag): String {
return t.id
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ContentLabelChildViewHolder {
return ContentLabelChildViewHolder(parent.toBinding())
}
override fun onBindViewHolder(holder: ContentLabelChildViewHolder, position: Int) {
val item = getItem(position)
with(holder.binding) {
ivIcon.goneIf(item.image.isBlank()){
ivIcon.displayGameIcon(item.image, null, null)
}
tvTitle.text = item.title
tvSubTitle.goneIf(item.addedContent.isBlank()) {
tvSubTitle.text = context.getString(R.string.content_tag_added_content_with_prefix, item.addedContent)
}
gBubble.goneIf(!item.isShowGuide) {
tvBubble.text = item.guide.text
}
}
exposureInvoke(position, item)
holder.itemView.setOnClickListener {
clickInvoke(position, item)
}
}
class ContentLabelChildViewHolder(
val binding: RecyclerContentLabelLaneItemBinding
) : RecyclerView.ViewHolder(binding.root) {
}
}

View File

@ -20,7 +20,7 @@ abstract class CustomBaseChildAdapter<T, VH : RecyclerView.ViewHolder>(
private var darkMode = DarkModeUtils.isDarkModeOn(context)
private var _countAndKey: Pair<Int, String>? = null
protected var _countAndKey: Pair<Int, String>? = null
@SuppressLint("NotifyDataSetChanged")
fun submitList(data: List<T>?, forceRefresh: Boolean = false) {
@ -85,7 +85,7 @@ abstract class CustomBaseChildAdapter<T, VH : RecyclerView.ViewHolder>(
}
if (oldSize == newSize) {
if (oldKeys != newKeys || darkMode != DarkModeUtils.isDarkModeOn(context)) { // 数量不变,内容发生变化 || 切换浅色模式
if (oldKeys != newKeys || isDarkModeChange()) { // 数量不变,内容发生变化 || 切换浅色模式
notifyItemRangeChanged(0, itemCount, "")
}
} else {// 数量发生变化
@ -95,4 +95,6 @@ abstract class CustomBaseChildAdapter<T, VH : RecyclerView.ViewHolder>(
}
protected open fun getKey(t: T) = ""
fun isDarkModeChange() = darkMode != DarkModeUtils.isDarkModeOn(context)
}

View File

@ -1,6 +1,7 @@
package com.gh.gamecenter.home.custom.adapter
import android.content.Context
import android.graphics.drawable.GradientDrawable
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
@ -10,10 +11,7 @@ import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.entity.ExposureEntity
import com.gh.gamecenter.common.json.json
import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.PageSwitchDataHelper
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.databinding.ItemGameNavigationCustomBinding
@ -45,44 +43,62 @@ class CustomGameNavigationAdapter(
}
override fun onBindViewHolder(holder: GameNavigationViewHolder, position: Int) {
// 是否显示小红点
var isShowHint = false
val entity = dataList[position]
ImageUtils.display(holder.binding.navigationView, entity.image)
holder.binding.navigationNameTv.text = entity.entryName
if (entity.hint) {
val record = recordMap[entity.id]
if (record == null) {
holder.binding.redHintIv.visibility = View.VISIBLE
} else {
val split = record.split(divider)//时间戳|点击次数
val updateTime = split[0].toLong()
val count = split[1].toInt()
//判断是否更新过
if (updateTime == entity.time?.update) {
holder.binding.redHintIv.visibility = View.GONE
} else {
val currentTime = System.currentTimeMillis() / 1000
holder.binding.redHintIv.goneIf(currentTime !in entity.hintStartTime..entity.hintEndTime || count >= 2)
}
}
holder.binding.navigationNameTv.text = if (entity.isShowEntryName) {
entity.entryName
} else {
holder.binding.redHintIv.visibility = View.GONE
""
}
val isFirstLine = position < 4
if (entity.isShowBubble && isFirstLine) {
isShowHint = false
showGuide(entity, holder.binding)
} else {
holder.binding.flBubbleContainer.goneIf(true)
if (entity.hint) {
val record = recordMap[entity.id]
if (record == null) {
isShowHint = true
holder.binding.redHintIv.visibility = View.VISIBLE
} else {
val split = record.split(divider)//时间戳|点击次数
val updateTime = split[0].toLong()
val count = split[1].toInt()
//判断是否更新过
if (updateTime == entity.time?.update) {
isShowHint = false
holder.binding.redHintIv.visibility = View.GONE
} else {
val currentTime = System.currentTimeMillis() / 1000
val isGone = currentTime !in entity.hintStartTime..entity.hintEndTime || count >= 2
holder.binding.redHintIv.goneIf(isGone)
isShowHint = !isGone
}
}
} else {
isShowHint = false
holder.binding.redHintIv.visibility = View.GONE
}
}
holder.itemView.setOnClickListener {
entity.linkEntity?.let {
val record = recordMap[entity.id]
if (record == null) {
recordMap[entity.id] = "${entity.time?.update}${divider}1"
} else {
val split = record.split(divider)
val updateTime = split[0].toLong()
var count = split[1].toInt()
recordMap[entity.id] = "${updateTime}${divider}${++count}"
if (isShowHint) {
val record = recordMap[entity.id]
if (record == null) {
recordMap[entity.id] = "${entity.time?.update}${divider}1"
} else {
val split = record.split(divider)
val updateTime = split[0].toLong()
var count = split[1].toInt()
recordMap[entity.id] = "${updateTime}${divider}${++count}"
}
SPUtils.setMap(Constants.SP_GAME_NAVIGATION, recordMap)
notifyItemChanged(position)
}
SPUtils.setMap(Constants.SP_GAME_NAVIGATION, recordMap)
notifyItemChanged(position)
PageSwitchDataHelper.pushCurrentPageData(
hashMapOf(
Pair(PageSwitchDataHelper.PAGE_BUSINESS_TYPE, "板块-内容列表->导航栏"),
@ -119,6 +135,30 @@ class CustomGameNavigationAdapter(
}
private fun showGuide(entity: GameNavigationEntity, binding: ItemGameNavigationCustomBinding) {
binding.flBubbleContainer.goneIf(false)
binding.tvBubble.text = entity.guide.text
binding.tvBubble.setTextColor(entity.guide.textColorInt)
val gradientDrawable =
(binding.tvBubble.background as? GradientDrawable) ?: GradientDrawable()
gradientDrawable.cornerRadii =
floatArrayOf(
8F.dip2px().toFloat(),
8F.dip2px().toFloat(),
8F.dip2px().toFloat(),
8F.dip2px().toFloat(),
2F.dip2px().toFloat(),
2F.dip2px().toFloat(),
2F.dip2px().toFloat(),
2F.dip2px().toFloat()
)
gradientDrawable.setStroke(1F.dip2px(), entity.guide.borderColorInt)
gradientDrawable.setColor(entity.guide.backgroundColorInt)
binding.tvBubble.background = gradientDrawable
}
companion object {
const val divider = "|"
}

View File

@ -4,21 +4,20 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.BaseAdapter
import androidx.core.view.ViewCompat
import com.gh.common.exposure.ExposureManager
import com.gh.common.exposure.ExposureTraceUtils
import com.gh.common.util.LogUtils
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.setDebouncedClickListener
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.PageSwitchDataHelper
import com.gh.gamecenter.databinding.ItemHomeRecommendListItemCustomBinding
import com.gh.gamecenter.entity.HomeRecommend
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.ExposureType
import com.gh.gamecenter.home.custom.eventlistener.CommonContentCollectionEventHelper
import com.gh.gamecenter.home.custom.viewholder.CustomHomeRecommendItemViewHolder.Companion.MAX_SPAN_COUNT
import org.json.JSONException
import org.json.JSONObject
@ -33,9 +32,11 @@ class CustomHomeRecommendItemGridAdapter(
private val dataList = arrayListOf<HomeRecommend>()
private var exposureEventList: List<ExposureEvent>? = null
private var spanCount = MAX_SPAN_COUNT
fun submitList(data: List<HomeRecommend>, trackEventList: List<ExposureEvent>?) {
fun submitList(data: List<HomeRecommend>, spanCount: Int, trackEventList: List<ExposureEvent>?) {
exposureEventList = trackEventList
this.spanCount = spanCount
dataList.clear()
dataList.addAll(data)
notifyDataSetChanged()
@ -63,7 +64,14 @@ class CustomHomeRecommendItemGridAdapter(
ImageUtils.display(binding.iconIv, data.image)
binding.nameTv.text = data.name
binding.nameTv.setTextColor(R.color.text_primary.toColor(binding.root.context))
// 当金刚区一排需要展示五个时,就算后台返回了引导文案,最右边 itemView 仍然会出现显示不全的情况,所以这里将此种情况的引导文案隐藏
val isRightmost = spanCount == MAX_SPAN_COUNT && (position + 1) % MAX_SPAN_COUNT == 0
binding.tvBubble.goneIf(!data.isShowGuide || isRightmost) {
binding.tvBubble.text = data.guide.text
}
}
ViewCompat.setElevation(binding.root, 999F - position)
initEvent(binding, position)
return binding.root
}
@ -114,7 +122,11 @@ class CustomHomeRecommendItemGridAdapter(
ExposureManager.log(clickEvent)
}
}
eventHelper.navigateToLinkPage(recommend.transformLinkEntity(), "金刚区", exposureEventList?.getOrNull(position))
eventHelper.navigateToLinkPage(
recommend.transformLinkEntity(),
"金刚区",
exposureEventList?.getOrNull(position)
)
}
}
}

View File

@ -178,7 +178,7 @@ class CustomHomeSlideListAdapter(
"新首页"
)
if (linkGame != null) {
eventHelper.navigateToGameDetailPage(actualPosition, linkGame, "轮播图")
eventHelper.navigateToGameDetailPage(actualPosition, null, linkGame, "轮播图")
}
}
}

View File

@ -10,6 +10,7 @@ import androidx.recyclerview.widget.RecyclerView.OnScrollListener
import com.gh.common.exposure.IExposable
import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.eventbus.EBPackage
@ -17,16 +18,18 @@ import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.game.GameAndPosition
import com.gh.gamecenter.home.custom.CustomPageViewModel
import com.gh.gamecenter.home.custom.IGameChangedNotifier
import com.gh.gamecenter.home.custom.model.CustomFooterItem
import com.gh.gamecenter.home.custom.model.CustomGameTestV2Item
import com.gh.gamecenter.home.custom.model.CustomPageItem
import com.gh.gamecenter.home.custom.model.*
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_AMWAY_WALL
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_COLLECTION_REFRESH_ICON_LANE
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_COLLECTION_REFRESH_ICON_MATRIX
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_COLLECTION_REFRESH_SLIDE_LIST
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_COMMON_ANNOUNCEMENT_BANNER
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_COMMON_BANNER
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_COMMON_BANNER_WITH_CARDS
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_COMMON_CONTENT_LABEL_LANE
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_COMMON_HORIZONTAL_SLIDE
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_COMMON_NOTIFICATION_COLUMN
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_COMMON_RECOMMEND_CARD
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_COMMON_VERTICAL_TWO_CARDS
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_CONTENT_CARD
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_DISCOVER
@ -51,7 +54,6 @@ import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_SUBJECT_COLLECTION_BANNER
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_SUBJECT_COLLECTION_HORIZONTAL_SLIDE_LARGE_CARD
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_SUBJECT_COLLECTION_HORIZONTAL_SLIDE_SMALL_CARD
import com.gh.gamecenter.home.custom.model.CustomRecentGamesItem
import com.gh.gamecenter.home.custom.viewholder.*
import com.gh.gamecenter.home.video.ScrollCalculatorHelper
import com.lightgame.download.DownloadEntity
@ -190,8 +192,20 @@ class CustomPageAdapter(
CUSTOM_PAGE_ITEM_TYPE_COLLECTION_REFRESH_SLIDE_LIST ->
CustomGameCollectionRefreshVerticalSlideViewHolder(viewModel, parent.toBinding())
CUSTOM_PAGE_ITEM_TYPE_COMMON_CONTENT_LABEL_LANE ->
ContentLabelLaneViewHolder(viewModel, parent.toBinding())
CUSTOM_PAGE_ITEM_TYPE_COMMON_NOTIFICATION_COLUMN ->
NotificationColumnViewHolder(viewModel, lifecycleOwner, parent.toBinding())
CUSTOM_PAGE_ITEM_TYPE_FOOTER -> CustomFooterViewHolder(viewModel, parent.toBinding())
CUSTOM_PAGE_ITEM_TYPE_COMMON_ANNOUNCEMENT_BANNER ->
CustomAnnouncementBannerViewHolder(viewModel, lifecycleOwner, parent.toBinding())
CUSTOM_PAGE_ITEM_TYPE_COMMON_RECOMMEND_CARD ->
CustomRecommendCardViewHolder(viewModel, parent.toBinding())
else -> CustomDoubleCardViewHolder(viewModel, parent.toBinding())
}
@ -258,6 +272,14 @@ class CustomPageAdapter(
is CustomGameCollectionRefreshVerticalSlideViewHolder -> holder.bindView(item)
is ContentLabelLaneViewHolder -> holder.bindView(item)
is NotificationColumnViewHolder -> holder.bindView(item)
is CustomAnnouncementBannerViewHolder -> holder.bindView(item)
is CustomRecommendCardViewHolder -> holder.bindView(item)
is CustomFooterViewHolder -> holder.initFooterViewHolder(
loadStatus == LoadStatus.LIST_LOADING,
loadStatus == LoadStatus.LIST_FAILED,
@ -271,6 +293,8 @@ class CustomPageAdapter(
notifyItemChanged(itemCount - 1)
}
}
else -> holder.bindView(item)
}
}

View File

@ -0,0 +1,94 @@
package com.gh.gamecenter.home.custom.adapter
import android.content.Context
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.visibleIf
import com.gh.gamecenter.databinding.RecyclerNotificationColumnItemBinding
import com.gh.gamecenter.home.custom.model.CustomPageData
class NotificationColumnAdapter(
context: Context,
private val exposureInvoke: (Int, CustomPageData.Notify) -> Unit
) :
CustomBaseChildAdapter<CustomPageData.Notify, NotificationColumnAdapter.NotificationColumChildViewHolder>(context) {
private var toBeDeletedItemIds = arrayListOf<String>()
override fun getKey(t: CustomPageData.Notify): String {
return "${t.id}-${t.title}"
}
val dataCount: Int
get() = dataList.size
private val dataCountWithoutToBeDeleted: Int
get() = dataCount - toBeDeletedItemIds.size
val isBanner: Boolean
get() = dataCount > 1
override fun getItem(position: Int): CustomPageData.Notify {
return super.getItem(getDataPosition(position))
}
override fun getItemCount(): Int {
return if (dataCount > 1) {
Int.MAX_VALUE
} else {
dataCount
}
}
fun getDataPosition(position: Int): Int {
return if (isBanner) position % dataCount else position
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NotificationColumChildViewHolder {
return NotificationColumChildViewHolder((parent.toBinding()))
}
override fun onBindViewHolder(holder: NotificationColumChildViewHolder, position: Int) {
val item = getItem(position)
with(holder.binding) {
ivIcon.displayGameIcon(item.image, null, goneIfEmpty = true, null)
tvTitle.text = item.title
tvDesc.text = item.addedContent
ivClose.visibleIf(item.isClosable)
}
exposureInvoke(position, item)
}
private fun getValidItem(position: Int): CustomPageData.Notify {
val item = getItem(position)
val hasRemoved = toBeDeletedItemIds.remove(item.id)
return if (hasRemoved) {
_dataList.remove(item)
getValidItem(position)
} else {
item
}
}
/**
* 为了不影响当前循环的数据位置,这里暂时将需要删除的数据标记为 待删除 状态,直接下一轮循环到需要删除的数据时,真正执行删除操作
*/
fun removeItem(itemId: String) {
val hasContains = _dataList.any { it.id == itemId }
if (hasContains) {
toBeDeletedItemIds.add(itemId)
if (dataCountWithoutToBeDeleted == 1) {
// 退化为普通列表
_dataList.removeAll {
toBeDeletedItemIds.contains(it.id)
}
toBeDeletedItemIds.clear()
}
}
}
class NotificationColumChildViewHolder(
val binding: RecyclerNotificationColumnItemBinding
) : RecyclerView.ViewHolder(binding.root)
}

View File

@ -0,0 +1,90 @@
package com.gh.gamecenter.home.custom.adapter
import android.content.Context
import android.graphics.Paint
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.view.marginStart
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.databinding.RecyclerRecommendCardItemBinding
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.viewholder.ui.RecommendCardUi
class RecommendCardAdapter(
context: Context,
private val listener: RecommendCardUi.OnRecommendCardEventListener
) : CustomBaseChildAdapter<CustomPageData.RecommendCard, RecommendCardAdapter.RecommendCardChildViewHolder>(context) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecommendCardChildViewHolder {
return RecommendCardChildViewHolder(parent.toBinding())
}
override fun onBindViewHolder(holder: RecommendCardChildViewHolder, position: Int) {
val item = getItem(position)
listener.onItemExposure(position, item)
holder.bind(item, position)
holder.itemView.setOnClickListener {
listener.onItemClick(position, item)
}
}
class RecommendCardChildViewHolder(val binding: RecyclerRecommendCardItemBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: CustomPageData.RecommendCard, position: Int) {
ImageUtils.display(binding.ivCover, item.image)
binding.tvTitle.text = item.title
binding.tvLabel.text = item.tag
binding.tvPrice.text = item.highlight.text
binding.tvOriginalPrice.text = if (item.deletion.isShowCurrencySymbol) {
itemView.context.getString(R.string.price_with_symbol, item.deletion.text)
} else {
item.deletion.text
}
binding.tvOriginalPrice.paint.flags = Paint.STRIKE_THRU_TEXT_FLAG
binding.tvCopy.text = item.addedContent
binding.tvPriceSymbol.goneIf(!item.highlight.isShowCurrencySymbol)
binding.root.post {
val views = mutableListOf(
binding.tvPrice,
binding.tvOriginalPrice,
binding.tvCopy,
)
if (item.highlight.isShowCurrencySymbol) {
views.add(0, binding.tvPriceSymbol)
}
binding.tvLabel.goneIf(item.tag.isBlank()) {
views.add(binding.tvLabel)
}
views.forEach {
it.goneIf(false)
}
hideOutOfBoundViewsIfNeed(views)
}
}
/**
* 计算统一排的view能否完整显示在 itemView 内,如果显示不下,则根据优先级依次隐藏优先级较低的
* 数据中的View优先级由高到低排列最后一个优先级最低
*/
private fun hideOutOfBoundViewsIfNeed(views: MutableList<TextView>) {
if (views.isEmpty()) {
return
}
val totalWidth = views.sumOf {
it.width + it.marginStart
}
if (totalWidth > itemView.width) {
views.removeLast().goneIf(true)
hideOutOfBoundViewsIfNeed(views)
}
}
}
}

View File

@ -0,0 +1,24 @@
package com.gh.gamecenter.home.custom.adapter
import android.content.Context
import android.util.AttributeSet
import com.gh.gamecenter.common.utils.dip2px
/**
* 宽度不受父布局限制的 TextView
* 使用场景 :子布局超出父布局,并且宽度可能长于父布局
* @see CustomHomeRecommendItemGridAdapter
*/
class WidthUnlimitedTextView @JvmOverloads constructor(
context: Context,
attr: AttributeSet? = null,
def: Int = 0
) : androidx.appcompat.widget.AppCompatTextView(context, attr, def) {
val MAX_WIDTH = 1000F.dip2px()
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(MeasureSpec.makeMeasureSpec(MAX_WIDTH, MeasureSpec.AT_MOST), heightMeasureSpec)
}
}

View File

@ -17,8 +17,14 @@ class CommonContentCollectionEventHelper(
viewModel.navigateToLinkPage(_item, link, text, exposureEvent)
}
fun navigateToGameDetailPage(position: Int, game: GameEntity, text: String) {
tracker.trackLinkContentCollectionClick(_item, null, text)
fun navigateToGameDetailPage(
position: Int,
link: LinkEntity?,
game: GameEntity,
text: String,
trackGame: Boolean = false
) {
tracker.trackLinkContentCollectionClick(_item, link, text, if (trackGame) game else null)
viewModel.navigateToGameDetailPageByGame(_item, position, game, text)
}

View File

@ -1,6 +1,8 @@
package com.gh.gamecenter.home.custom.model
import com.gh.common.filter.RegionSettingHelper
import com.gh.gamecenter.common.entity.CommunityEntity
import com.gh.gamecenter.common.entity.Display
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.entity.*
import com.gh.gamecenter.feature.entity.GameEntity
@ -352,6 +354,8 @@ class CustomPageData(
private val _id: String? = null,
@SerializedName("name")
private val _name: String? = null,
@SerializedName("subtitle")
private val _subtitle: String? = null,
@SerializedName("detail_style")
private val _detailStyle: String? = null,
@SerializedName("layout")
@ -370,6 +374,14 @@ class CustomPageData(
private val _navigations: List<GameNavigationEntity>? = null,
@SerializedName("recommends")
private val _recommends: List<HomeRecommend>? = null,
@SerializedName("content_tags")
private val _contentTags: List<ContentTag>? = null,
@SerializedName("notifies")
private var _notifies: MutableList<Notify>? = null,
@SerializedName("announcements")
private var _announcements: List<Announcement>? = null,
@SerializedName("recommend_cards") // TODO 待定
private var _recommendCards: List<RecommendCard>? = null
) {
val id: String
@ -378,6 +390,9 @@ class CustomPageData(
val name: String
get() = _name ?: ""
val subtitle: String
get() = _subtitle ?: ""
val detailStyle: String
get() = _detailStyle ?: ""
@ -395,6 +410,10 @@ class CustomPageData(
6 -> "双列竖式卡片"
7 -> "竖式图文列表"
8 -> "横排图文列表"
9 -> "内容标签泳道"
10 -> "通知栏目"
11 -> "公告横幅"
12 -> "推荐卡片"
else -> ""
}
@ -419,6 +438,21 @@ class CustomPageData(
val recommends: List<HomeRecommend>
get() = _recommends ?: emptyList()
val contentTags: List<ContentTag>
get() = _contentTags ?: emptyList()
var notifies: MutableList<Notify>
get() = _notifies ?: mutableListOf()
set(value) {
_notifies = value
}
val announcements: List<Announcement>
get() = _announcements ?: emptyList()
val recommendCards: List<RecommendCard>
get() = _recommendCards ?: emptyList()
data class Slides(
@SerializedName("slide")
private val _slide: List<HomeSlide>? = null,
@ -433,5 +467,258 @@ class CustomPageData(
get() = _subSlide ?: emptyList()
}
data class ContentTag(
@SerializedName("_id")
private val _id: String? = null,
@SerializedName("title")
private val _title: String? = null,
@SerializedName("image")
private val _image: String? = null,
@SerializedName("added_content")
private val _addedContent: String? = null,
@SerializedName("guide")
private val _guide: Guide? = null,
@SerializedName("link_id")
private val _linkId: String? = null, // 链接地址
@SerializedName("link_type")
private val _linkType: String? = null,
@SerializedName("link_text")
private val _linkText: String? = null,
@SerializedName("link_community")
private val _linkCommunity: CommunityEntity? = null,
@SerializedName("display")
private val _display: Display? = null
) {
val id: String
get() = _id ?: ""
val title: String
get() = _title ?: ""
val image: String
get() = _image ?: ""
val addedContent: String
get() = _addedContent ?: ""
val isShowGuide: Boolean
get() = _guide != null
val guide: Guide
get() = _guide ?: Guide()
val linkId: String
get() = _linkId ?: ""
val linkType: String
get() = _linkType ?: ""
val linkText: String
get() = _linkText ?: ""
val link: LinkEntity
get() = LinkEntity(
name = title,
type = linkType,
display = _display,
link = linkId,
text = linkText,
community = _linkCommunity
)
data class Guide(
@SerializedName("text")
private val _text: String? = null
) {
val text: String
get() = _text ?: ""
}
}
}
data class Notify(
@SerializedName("_id")
private val _id: String? = null,
@SerializedName("title")
private val _title: String? = null,
@SerializedName("image")
private val _image: String? = null,
@SerializedName("added_content")
private val _addedContent: String? = null,
@SerializedName("link_type")
private val _linkType: String? = null,
@SerializedName("link_id")
private val _linkId: String? = null,
@SerializedName("link_text")
private val _linkText: String? = null,
@SerializedName("link_community")
private val _linkCommunity: CommunityEntity? = null,
@SerializedName("display")
private val _display: Display? = null,
@SerializedName("display_status")
private val _displayStatus: String? = null
) {
val id: String
get() = _id ?: ""
val title: String
get() = _title ?: ""
val image: String
get() = _image ?: ""
val addedContent: String
get() = _addedContent ?: ""
val linkType: String
get() = _linkType ?: ""
val linkId: String
get() = _linkId ?: ""
val linkText: String
get() = _linkText ?: ""
val isClosable: Boolean
get() = _displayStatus == "closable"
val link: LinkEntity
get() = LinkEntity(
name = title,
type = linkType,
display = _display,
link = linkId,
text = linkText,
community = _linkCommunity
)
}
data class Announcement(
@SerializedName("_id")
private val _id: String? = null,
@SerializedName("image")
private val _image: String? = null,
@SerializedName("link_type")
private val _linkType: String? = null,
@SerializedName("link_id")
private val _linkId: String? = null,
@SerializedName("link_text")
private val _linkText: String? = null,
@SerializedName("link_community")
private val _linkCommunity: CommunityEntity? = null,
@SerializedName("display")
private val _display: Display? = null
) {
val id: String
get() = _id ?: ""
val image: String
get() = _image ?: ""
val linkType: String
get() = _linkType ?: ""
val linkId: String
get() = _linkId ?: ""
val linkText: String
get() = _linkText ?: ""
val link: LinkEntity
get() = LinkEntity(
name = "",
type = linkType,
display = _display,
link = linkId,
text = linkText,
community = _linkCommunity
)
}
data class RecommendCard(
@SerializedName("_id")
private val _id: String? = null,
@SerializedName("title")
private val _title: String? = null,
@SerializedName("image")
private val _image: String? = null,
@SerializedName("tag")
private val _tag: String? = null,
@SerializedName("highlight")
private val _highlight: TextWithSymbol? = null,
@SerializedName("deletion")
private val _deletion: TextWithSymbol? = null,
@SerializedName("added_content")
private val _addedContent: String? = null,
@SerializedName("link_id")
private val _linkId: String? = null,
@SerializedName("link_type")
private val _linkType: String? = null,
@SerializedName("link_text")
private val _linkText: String? = null,
@SerializedName("link_community")
private val _linkCommunity: CommunityEntity? = null,
@SerializedName("display")
private val _display: Display? = null
) {
val id: String
get() = _id ?: ""
val title: String
get() = _title ?: ""
val image: String
get() = _image ?: ""
val tag: String
get() = _tag ?: ""
val highlight: TextWithSymbol
get() = _highlight ?: TextWithSymbol()
val deletion: TextWithSymbol
get() = _deletion ?: TextWithSymbol()
val addedContent: String
get() = _addedContent ?: ""
val linkId: String
get() = _linkId ?: ""
val linkType: String
get() = _linkType ?: ""
val linkText: String
get() = _linkText ?: ""
val link: LinkEntity
get() = LinkEntity(
name = title,
type = linkType,
display = _display,
link = linkId,
text = linkText,
community = _linkCommunity
)
data class TextWithSymbol(
@SerializedName("text")
private val _text: String? = null,
@SerializedName("is_show_currency_symbol")
private val _isShowCurrencySymbol: Boolean? = null
) {
val text: String
get() = _text ?: ""
val isShowCurrencySymbol: Boolean
get() = _isShowCurrencySymbol ?: false
}
}
}

View File

@ -122,6 +122,10 @@ abstract class CustomPageItem(
const val COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_VERTICAL_CARD = 6
const val COMMON_CONTENT_COLLECTION_LAYOUT_VERTICAL_IMAGE_TEXT = 7
const val COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_IMAGE_TEXT = 8
const val COMMON_CONTENT_COLLECTION_LAYOUT_CONTENT_LABEL_LANE = 9
const val COMMON_CONTENT_COLLECTION_LAYOUT_NOTIFICATION_COLUMN = 10
const val COMMON_CONTENT_COLLECTION_LAYOUT_ANNOUNCEMENT_BANNER = 11
const val COMMON_CONTENT_COLLECTION_LAYOUT_RECOMMEND_CARD = 12
const val CUSTOM_PAGE_ITEM_TYPE_INVALID = -2
const val CUSTOM_PAGE_ITEM_TYPE_FOOTER = -1
@ -155,6 +159,10 @@ abstract class CustomPageItem(
const val CUSTOM_PAGE_ITEM_TYPE_COLLECTION_REFRESH_ICON_MATRIX = 28
const val CUSTOM_PAGE_ITEM_TYPE_COLLECTION_REFRESH_ICON_LANE = 29
const val CUSTOM_PAGE_ITEM_TYPE_COLLECTION_REFRESH_SLIDE_LIST = 30
const val CUSTOM_PAGE_ITEM_TYPE_COMMON_CONTENT_LABEL_LANE = 31
const val CUSTOM_PAGE_ITEM_TYPE_COMMON_NOTIFICATION_COLUMN = 32
const val CUSTOM_PAGE_ITEM_TYPE_COMMON_ANNOUNCEMENT_BANNER = 33
const val CUSTOM_PAGE_ITEM_TYPE_COMMON_RECOMMEND_CARD = 34
// 专题样式 to itemType
val subjectTypeMap: HashMap<String, Int> = hashMapOf(
@ -237,7 +245,12 @@ abstract class CustomPageItem(
COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_VERTICAL_CARD to CUSTOM_PAGE_ITEM_TYPE_COMMON_HORIZONTAL_SLIDE,
COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_VERTICAL_CARD to CUSTOM_PAGE_ITEM_TYPE_COMMON_VERTICAL_TWO_CARDS,
COMMON_CONTENT_COLLECTION_LAYOUT_VERTICAL_IMAGE_TEXT to CUSTOM_PAGE_ITEM_TYPE_COMMON_VERTICAL_TWO_CARDS,
COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_IMAGE_TEXT to CUSTOM_PAGE_ITEM_TYPE_COMMON_HORIZONTAL_SLIDE
COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_IMAGE_TEXT to CUSTOM_PAGE_ITEM_TYPE_COMMON_HORIZONTAL_SLIDE,
COMMON_CONTENT_COLLECTION_LAYOUT_CONTENT_LABEL_LANE to CUSTOM_PAGE_ITEM_TYPE_COMMON_CONTENT_LABEL_LANE,
COMMON_CONTENT_COLLECTION_LAYOUT_NOTIFICATION_COLUMN to CUSTOM_PAGE_ITEM_TYPE_COMMON_NOTIFICATION_COLUMN,
COMMON_CONTENT_COLLECTION_LAYOUT_ANNOUNCEMENT_BANNER to CUSTOM_PAGE_ITEM_TYPE_COMMON_ANNOUNCEMENT_BANNER,
COMMON_CONTENT_COLLECTION_LAYOUT_RECOMMEND_CARD to CUSTOM_PAGE_ITEM_TYPE_COMMON_RECOMMEND_CARD
)
// 通用内容合集 样式 to 样式名称
@ -251,7 +264,11 @@ abstract class CustomPageItem(
COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_VERTICAL_CARD to "横排竖式卡片",
COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_VERTICAL_CARD to "双列竖式卡片",
COMMON_CONTENT_COLLECTION_LAYOUT_VERTICAL_IMAGE_TEXT to "竖式图文列表",
COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_IMAGE_TEXT to "横排图文列表"
COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_IMAGE_TEXT to "横排图文列表",
COMMON_CONTENT_COLLECTION_LAYOUT_CONTENT_LABEL_LANE to "内容标签泳道",
COMMON_CONTENT_COLLECTION_LAYOUT_NOTIFICATION_COLUMN to "通知栏目",
COMMON_CONTENT_COLLECTION_LAYOUT_ANNOUNCEMENT_BANNER to "公告横幅",
COMMON_CONTENT_COLLECTION_LAYOUT_RECOMMEND_CARD to "推荐卡片"
)
}
@ -712,6 +729,10 @@ data class CustomCommonContentCollectionItem(
&& position == other.position
&& componentPosition == other.componentPosition
}
// 临时变量,当样式为 通知栏目 时,记录当前显示的位置
var selectedPosition = 0
}
// 拆分的通用内用合集

View File

@ -9,6 +9,7 @@ import com.gh.common.util.GameSubstituteRepositoryHelper
import com.gh.common.util.HomePluggableHelper
import com.gh.download.DownloadManager
import com.gh.gamecenter.R
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.utils.TextHelper
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.sp2px
@ -16,6 +17,7 @@ import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.entity.DiscoveryCardEntity
import com.gh.gamecenter.entity.GameUpdateEntity
import com.gh.gamecenter.entity.PersonalHistoryEntity.Count.Companion.write
import com.gh.gamecenter.entity.SubjectEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.PluginLocation
@ -26,6 +28,8 @@ import com.gh.gamecenter.gamedetail.rating.edit.RatingEditActivity
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_BANNER
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_BANNER
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_VERTICAL_CARD
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_NOTIFICATION_COLUMN
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_RECOMMEND_CARD
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_VERTICAL_IMAGE_TEXT
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMPONENTS_COLLECTION_STYLE_CAROUSEL
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMPONENTS_COLLECTION_STYLE_RANKING_LIST
@ -105,6 +109,9 @@ class CustomPageRepository private constructor(
refreshDiscoverDataIfNeed(it)?.let(_customDiscoverItemLiveData::setValue)
}
val hiddenNotifications: LiveData<HashMap<String, MutableSet<String>>>
get() = shareRepository.hiddenNotifications
private var lastComponentType = ""
private var lastSubjectId = ""
private var gameChildPosition = 0
@ -186,6 +193,7 @@ class CustomPageRepository private constructor(
if (lastComponentType == CUSTOM_LINK_TYPE_GAME && item.link.type != CUSTOM_LINK_TYPE_GAME) {
pageInfo.componentPositionIncrement()
}
when {
item.game != null -> {
if (!RegionSettingHelper.shouldThisGameBeFiltered(item.game.id)) {
@ -392,34 +400,59 @@ class CustomPageRepository private constructor(
}
item.linkCommonCollection != null -> {
item.linkCommonCollection != null -> { // 通用内容合集
val layout = item.linkCommonCollection.layout
if (layout == COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_BANNER ||
layout == COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_VERTICAL_CARD ||
layout == COMMON_CONTENT_COLLECTION_LAYOUT_VERTICAL_IMAGE_TEXT
if (layout == COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_BANNER
|| layout == COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_VERTICAL_CARD
|| layout == COMMON_CONTENT_COLLECTION_LAYOUT_VERTICAL_IMAGE_TEXT
) {
list.addAll(convertSplitCommonContentCollection(item))
pageInfo.componentPositionIncrement()
} else {
// 轮播图右边卡片列表, 至少需要2个卡片才能显示
val subSlideList = item.linkCommonCollection.slides.subSlide
if (layout == COMMON_CONTENT_COLLECTION_LAYOUT_BANNER && subSlideList.size > 1) {
subSlideList.forEach {
if (it.cardData.games.isNotEmpty()) {
it.cardData.games = RegionSettingHelper.filterGame(it.cardData.games)
var show = true
when (layout) {
COMMON_CONTENT_COLLECTION_LAYOUT_BANNER -> {
// 轮播图右边卡片列表, 至少需要2个卡片才能显示
val subSlideList = item.linkCommonCollection.slides.subSlide
if (subSlideList.size > 1) {
subSlideList.forEach {
if (it.cardData.games.isNotEmpty()) {
it.cardData.games = RegionSettingHelper.filterGame(it.cardData.games)
}
}
}
}
COMMON_CONTENT_COLLECTION_LAYOUT_NOTIFICATION_COLUMN -> {
// 通知栏目 初始化通知栏配置
shareRepository.initHiddenNotifications()
}
COMMON_CONTENT_COLLECTION_LAYOUT_RECOMMEND_CARD -> {
// 推荐卡片,小于两条数据不显示
if (item.linkCommonCollection.recommendCards.size < 2) {
show = false
}
}
}
list.add(
CustomCommonContentCollectionItem(
item.link,
item.linkCommonCollection,
pageInfo.position,
pageInfo.componentPosition
if (show) {
list.add(
CustomCommonContentCollectionItem(
item.link,
item.linkCommonCollection,
pageInfo.position,
pageInfo.componentPosition
)
)
)
pageInfo.positionIncrement()
pageInfo.positionIncrement()
pageInfo.componentPositionIncrement()
}
}
pageInfo.componentPositionIncrement()
}
item.link.type == "cwzs_recently_played" -> {
@ -661,7 +694,7 @@ class CustomPageRepository private constructor(
*/
private fun substituteGameIfNeeded(gameSubject: SubjectEntity) {
if (!gameSubject.relatedColumnId.isNullOrEmpty() && gameSubject.data != null) {
when(gameSubject.type) {
when (gameSubject.type) {
COMPONENTS_GAME_SUBJECT_TYPE_GAME_VERTICAL,
COMPONENTS_SUBJECT_TYPE_GAME_VERTICAL_SLIDE,
COMPONENTS_SUBJECT_TYPE_GAME_HORIZONTAL,
@ -680,6 +713,9 @@ class CustomPageRepository private constructor(
}
}
fun hideNotificationItem(id: String, itemId: String) {
shareRepository.hideNotificationItem(id, itemId)
}
/**
* 分页信息

View File

@ -1,17 +1,23 @@
package com.gh.gamecenter.home.custom.model
import androidx.annotation.MainThread
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.gh.common.filter.RegionSettingHelper
import com.gh.common.util.PackageUtils
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.Constants.SP_HIDDEN_NOTIFICATIONS
import com.gh.gamecenter.common.utils.observableToMain
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.DiscoveryCardEntity
import com.gh.gamecenter.entity.DiscoveryGameCardEntity
import com.gh.gamecenter.entity.DiscoveryGameCardLabel
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.retrofit.RetrofitManager
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
import com.halo.assistant.HaloApp
import io.reactivex.disposables.CompositeDisposable
@ -25,7 +31,6 @@ class CustomPageShareRepository private constructor() {
private val compositeDisposable = CompositeDisposable()
private val _discoverData = MutableLiveData<DiscoveryCardEntity>()
val discoverData: LiveData<DiscoveryCardEntity> = _discoverData
@ -103,6 +108,42 @@ class CustomPageShareRepository private constructor() {
return filteredLabels
}
/**
* 全局保存
*/
private val _hiddenNotifications = MutableLiveData<HashMap<String, MutableSet<String>>>()
val hiddenNotifications: LiveData<HashMap<String, MutableSet<String>>> = _hiddenNotifications
fun initHiddenNotifications() {
val json = SPUtils.getString(SP_HIDDEN_NOTIFICATIONS)
if (json.isNotBlank()) {
val typeToken = object : TypeToken<HashMap<String, MutableSet<String>>>() {}
val map: HashMap<String, MutableSet<String>> = Gson().fromJson(json, typeToken.type)
_hiddenNotifications.postValue(map)
} else {
_hiddenNotifications.postValue(hashMapOf())
}
}
@MainThread
fun hideNotificationItem(id: String, itemId: String) {
var hasUpdated = false
val notifications = _hiddenNotifications.value ?: hashMapOf()
val itemSet = notifications[id]
if (itemSet == null) {
notifications[id] = mutableSetOf(itemId)
hasUpdated = true
} else {
hasUpdated = itemSet.add(itemId)
}
if (hasUpdated) {
val json = notifications.toJson()
SPUtils.setString(SP_HIDDEN_NOTIFICATIONS, json)
_hiddenNotifications.value = notifications
}
}
// 这个方法要在用户退出首页时调用
fun onClear() {
compositeDisposable.clear()

View File

@ -2,6 +2,7 @@ package com.gh.gamecenter.home.custom.tracker
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.PageLocation
import com.gh.gamecenter.home.custom.CustomPageTracker
import com.gh.gamecenter.home.custom.model.CustomCommonContentCollectionItem
@ -10,7 +11,12 @@ import com.gh.gamecenter.home.custom.model.CustomSplitCommonContentCollectionIte
class CommonContentCollectionTracker(private val pageLocation: PageLocation) {
fun trackLinkContentCollectionClick(item: CustomPageItem, link: LinkEntity?, text: String) {
fun trackLinkContentCollectionClick(
item: CustomPageItem,
link: LinkEntity?,
text: String,
game: GameEntity? = null
) {
val (linkContentCollectionId, linkContentCollectionName) = when {
item is CustomCommonContentCollectionItem -> item.data.id to item.data.name
item is CustomSplitCommonContentCollectionItem -> item.data.id to item.data.name
@ -34,7 +40,9 @@ class CommonContentCollectionTracker(private val pageLocation: PageLocation) {
pageLocation.tabPosition,
pageLocation.tabContent,
pageLocation.pageId,
pageLocation.pageName
pageLocation.pageName,
game?.id ?: "",
game?.name ?: ""
)
}
}

View File

@ -0,0 +1,83 @@
package com.gh.gamecenter.home.custom.viewholder
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.databinding.RecyclerContentLabelLaneCustomBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.home.custom.CustomPageViewModel
import com.gh.gamecenter.home.custom.adapter.ContentLabelLaneAdapter
import com.gh.gamecenter.home.custom.createExposureEvent
import com.gh.gamecenter.home.custom.eventlistener.CommonContentCollectionEventHelper
import com.gh.gamecenter.home.custom.model.CustomCommonContentCollectionItem
import com.gh.gamecenter.home.custom.model.CustomPageItem
/**
* 通用内容合集-内容标签泳道
*/
class ContentLabelLaneViewHolder(
viewModel: CustomPageViewModel,
private val binding: RecyclerContentLabelLaneCustomBinding
) : BaseCustomViewHolder(viewModel, binding.root) {
override val childEventHelper by lazy {
CommonContentCollectionEventHelper(viewModel)
}
private val adapter by lazy {
ContentLabelLaneAdapter(itemView.context,
{ index, contentTag ->
childEventHelper.navigateToLinkPage(
contentTag.link,
"内容标签泳道",
_item.exposureEventList.getOrNull(index)
)
},
{ index, contentTag ->
val item = _item as CustomCommonContentCollectionItem
val gameEntity = if (contentTag.linkType == "game") {
GameEntity(id = contentTag.linkId, name = contentTag.linkText)
} else {
GameEntity()
}
_item.exposureEventList.add(
createExposureEvent(
gameEntity.also {
it.sequence = index
it.outerSequence = _item.position
},
listOf(
ExposureSource(
"通用内容合集",
"${item.data.name}+${item.data.layoutChinese}+${item.data.id}"
),
ExposureSource(
item.data.layoutChinese,
"${contentTag.title}+${contentTag.id}"
)
),
pageConfigure.exposureSourceList,
index,
_item.componentPosition
)
)
})
}
override fun bindView(item: CustomPageItem) {
super.bindView(item)
if (item is CustomCommonContentCollectionItem) {
item.exposureEventList.clear()
val contentTags = item.data.contentTags
if (binding.rvGames.adapter == null) {
binding.rvGames.layoutManager = LinearLayoutManager(itemView.context, RecyclerView.HORIZONTAL, false)
binding.rvGames.adapter = adapter
}
adapter.submitList(contentTags)
}
}
}

View File

@ -0,0 +1,127 @@
package com.gh.gamecenter.home.custom.viewholder
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.databinding.RecyclerAnnouncementBannerBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.home.custom.BannerInRecyclerController
import com.gh.gamecenter.home.custom.CustomPageViewModel
import com.gh.gamecenter.home.custom.adapter.AnnouncementBannerAdapter
import com.gh.gamecenter.home.custom.createExposureEvent
import com.gh.gamecenter.home.custom.eventlistener.CommonContentCollectionEventHelper
import com.gh.gamecenter.home.custom.model.CustomCommonContentCollectionItem
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.model.CustomPageItem
/**
* 通用内容合集-公告横幅
*/
class CustomAnnouncementBannerViewHolder(
viewModel: CustomPageViewModel,
lifecycleOwner: LifecycleOwner,
val binding: RecyclerAnnouncementBannerBinding
) : BaseCustomViewHolder(viewModel, binding.root) {
private val adapter by lazy {
AnnouncementBannerAdapter(itemView.context, object : AnnouncementBannerAdapter.OnChildEventListener {
override fun onItemClick(childPosition: Int, announcement: CustomPageData.Announcement) {
childEventHelper.navigateToLinkPage(
announcement.link,
"公告横幅",
_item.exposureEventList.getOrNull(childPosition)
)
}
override fun getCurrentPosition(): Int {
return binding.vpBanner.currentItem
}
override fun exposure(childPosition: Int, announcement: CustomPageData.Announcement) {
val item = _item as CustomCommonContentCollectionItem
val gameEntity = if (announcement.linkType == "game") {
GameEntity(id = announcement.linkId, name = announcement.linkText)
} else {
GameEntity()
}
_item.exposureEventList.add(
createExposureEvent(
gameEntity.also {
it.sequence = childPosition
it.outerSequence = _item.position
},
listOf(
ExposureSource(
"通用内容合集",
"${item.data.name}+${item.data.layoutChinese}+${item.data.id}"
),
ExposureSource(
item.data.layoutChinese
)
),
pageConfigure.exposureSourceList,
childPosition,
_item.componentPosition
)
)
}
})
}
private val bannerInRecyclerController = BannerInRecyclerController {
if (adapter.isBanner) {
binding.vpBanner.setCurrentItem(binding.vpBanner.currentItem + 1, true)
}
}
override val childEventHelper by lazy {
CommonContentCollectionEventHelper(viewModel)
}
init {
lifecycleOwner.lifecycle.addObserver(bannerInRecyclerController)
}
override fun bindView(item: CustomPageItem) {
super.bindView(item)
_item.exposureEventList.clear()
if (item is CustomCommonContentCollectionItem) {
if (binding.vpBanner.adapter == null) {
binding.vpBanner.adapter = adapter
binding.vpBanner.registerOnPageChangeCallback(object : OnPageChangeCallback() {
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
binding.bannerIndicator.onPageScrolled(adapter.getDataPosition(position), positionOffset)
}
override fun onPageSelected(position: Int) {
// 页面切换,重置轮播等待时间
(_item as CustomCommonContentCollectionItem).selectedPosition = position
bannerInRecyclerController.start()
}
})
}
adapter.submitList(item.data.announcements)
if (item.selectedPosition != 0) {
binding.vpBanner.setCurrentItem(item.selectedPosition, false)
}
with(binding.bannerIndicator) {
pageSize = adapter.dataList.size
notifyDataChanged()
}
}
}
override fun onViewAttach(parent: RecyclerView?) {
bannerInRecyclerController.onViewAttachedToWindow(parent)
}
override fun onViewDetach(parent: RecyclerView?) {
bannerInRecyclerController.onViewDetachedFromWindow(parent)
}
}

View File

@ -77,19 +77,24 @@ class CustomHomeRecommendItemViewHolder(
val recommends = mapData(item.data.recommends)
val size = recommends.size
if (size == 0) return
val spanCount = minOf(size, MAX_SPAN_COUNT)
if (binding.recommendGv.adapter == null) {
val spanCount = minOf(size, MAX_SPAN_COUNT)
binding.recommendGv.stretchMode = GridView.STRETCH_SPACING
binding.recommendGv.numColumns = spanCount
binding.recommendGv.columnWidth = 60F.dip2px()
} else {
val oldSpanCount = binding.recommendGv.numColumns
if (oldSpanCount != spanCount) {
binding.recommendGv.numColumns = spanCount
}
}
binding.recommendGv.adapter = adapter
adapter.submitList(recommends, _item.exposureEventList)
adapter.submitList(recommends, spanCount, _item.exposureEventList)
}
}
companion object {
//图标最多列数
private const val MAX_SPAN_COUNT = 5
const val MAX_SPAN_COUNT = 5
}
}

View File

@ -33,6 +33,7 @@ import com.gh.gamecenter.entity.HomeSubSlide
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.ExposureType
import com.gh.gamecenter.feature.view.GameIconView
import com.gh.gamecenter.home.custom.BannerInRecyclerController
import com.gh.gamecenter.home.custom.CustomPageViewModel
import com.gh.gamecenter.home.custom.OnCustomPageRefreshStateListener
@ -316,16 +317,11 @@ class CustomHomeSlideWithCardsViewHolder(
val cardCv = homeSlideCardItemBinding.root
val cardContainer = homeSlideCardItemBinding.cardContainer
val cardIv = homeSlideCardItemBinding.cardIv
val backgroundGroup = homeSlideCardItemBinding.backgroundGroup
val titleTv = homeSlideCardItemBinding.titleTv
val descTv = homeSlideCardItemBinding.descTv
val textContainer = homeSlideCardItemBinding.textContainer
val countTv = homeSlideCardItemBinding.countTv
val labelIv = homeSlideCardItemBinding.labelIv
val gameIconStackIv1 = homeSlideCardItemBinding.gameIconStackIv1
val gameIconStackIv2 = homeSlideCardItemBinding.gameIconStackIv2
val gameIconStackIv3 = homeSlideCardItemBinding.gameIconStackIv3
val gameIconStackGroup = listOf(gameIconStackIv1, gameIconStackIv2, gameIconStackIv3)
val gameIconIv1 = homeSlideCardItemBinding.gameIconIv1
val gameIconIv2 = homeSlideCardItemBinding.gameIconIv2
val gameIconIv3 = homeSlideCardItemBinding.gameIconIv3
@ -368,53 +364,10 @@ class CustomHomeSlideWithCardsViewHolder(
"column",
"column_test_v2",
"server",
"game_explore" -> {
titleTv.visibility = View.VISIBLE
descTv.visibility = View.VISIBLE
countTv.visibility = View.GONE
labelIv.visibility = View.GONE
backgroundGroup.visibility = View.GONE
gameIconStackGroup.forEach { it.isVisible = homeSubSlide.cardData.games.isNotEmpty() }
gameIconGroup.forEach { it.visibility = View.GONE }
descTv.text = homeSubSlide.cardDesc
if (homeSubSlide.cardData.games.size == 1) {
gameIconStackIv2.visibility = View.GONE
gameIconStackIv1.visibility = View.GONE
}
if (homeSubSlide.cardData.games.size == 2) {
gameIconStackIv1.visibility = View.GONE
}
homeSubSlide.cardData.games.take(3).forEachIndexed { index, gameEntity ->
_item.exposureEventList.add(
getGameExposureEvent(
item.data,
homeSubSlide,
gameEntity,
position,
basicExposureSource
)
)
when (index) {
0 -> gameIconStackIv3.displayGameIcon(gameEntity, true)
1 -> gameIconStackIv2.displayGameIcon(gameEntity, true)
2 -> gameIconStackIv1.displayGameIcon(gameEntity, true)
}
}
ConstraintSet().apply {
clone(cardContainer)
clear(textContainer.id, ConstraintSet.TOP)
clear(textContainer.id, ConstraintSet.BOTTOM)
connect(textContainer.id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP)
connect(textContainer.id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM)
}.applyTo(cardContainer)
}
"game_explore",
"game_list_detail" -> {
titleTv.visibility = View.VISIBLE
descTv.visibility = View.GONE
backgroundGroup.visibility = View.GONE
gameIconStackGroup.forEach { it.visibility = View.GONE }
gameIconGroup.forEach { it.isVisible = homeSubSlide.cardData.games.isNotEmpty() }
if (homeSubSlide.cardData.games.size == 1) {
@ -424,6 +377,25 @@ class CustomHomeSlideWithCardsViewHolder(
if (homeSubSlide.cardData.games.size == 2) {
gameIconIv3.visibility = View.GONE
}
fun GameIconView.setGame(index: Int, game: GameEntity) {
displayGameIcon(game, true)
this.setOnClickListener {
com.gh.common.util.NewFlatLogUtils.logRightSideCardClick(
homeSubSlide,
viewModel.refreshCount,
"游戏"
)
childEventHelper.navigateToGameDetailPage(
index,
homeSubSlide.toLinkEntity(),
game,
"右侧卡片-游戏",
true
)
}
}
homeSubSlide.cardData.games.take(3).forEachIndexed { index, gameEntity ->
_item.exposureEventList.add(
getGameExposureEvent(
@ -436,41 +408,11 @@ class CustomHomeSlideWithCardsViewHolder(
)
when (index) {
0 -> {
gameIconIv1.displayGameIcon(gameEntity, true)
gameIconIv1.setOnClickListener {
com.gh.common.util.NewFlatLogUtils.logRightSideCardClick(
homeSubSlide,
viewModel.refreshCount,
"游戏"
)
childEventHelper.navigateToGameDetailPage(index, gameEntity, "右侧卡片")
}
}
0 -> gameIconIv1.setGame(index, gameEntity)
1 -> {
gameIconIv2.displayGameIcon(gameEntity, true)
gameIconIv2.setOnClickListener {
com.gh.common.util.NewFlatLogUtils.logRightSideCardClick(
homeSubSlide,
viewModel.refreshCount,
"游戏"
)
childEventHelper.navigateToGameDetailPage(index, gameEntity, "右侧卡片")
}
}
1 -> gameIconIv2.setGame(index, gameEntity)
2 -> {
gameIconIv3.displayGameIcon(gameEntity, true)
gameIconIv3.setOnClickListener {
com.gh.common.util.NewFlatLogUtils.logRightSideCardClick(
homeSubSlide,
viewModel.refreshCount,
"游戏"
)
childEventHelper.navigateToGameDetailPage(index, gameEntity, "右侧卡片")
}
}
2 -> gameIconIv3.setGame(index, gameEntity)
}
}
countTv.isVisible = homeSubSlide.cardData.gameTotal.game > 3
@ -484,6 +426,15 @@ class CustomHomeSlideWithCardsViewHolder(
else -> null
}
)
val visibility = if (isFirst) View.VISIBLE else View.GONE
homeSlideCardItemBinding.cardMask.visibility = visibility
homeSlideCardItemBinding.cardGradientMask.visibility = visibility
homeSlideCardItemBinding.cardIv.visibility = View.VISIBLE
// 防止重复加载图片导致闪烁
if (homeSubSlide.image != cardIv.getTag(R.string.tag_img_url_id)) {
ImageUtils.display(cardIv, homeSubSlide.image, false, AlphaGradientProcess())
cardIv.setTag(R.string.tag_img_url_id, homeSubSlide.image)
}
ConstraintSet().apply {
clone(cardContainer)
clear(textContainer.id, ConstraintSet.TOP)
@ -511,13 +462,10 @@ class CustomHomeSlideWithCardsViewHolder(
countTv.visibility = View.GONE
labelIv.visibility = View.GONE
backgroundGroup.visibility = View.VISIBLE
val visibility = if (isFirst) View.VISIBLE else View.GONE
homeSlideCardItemBinding.cardMask.visibility = visibility
homeSlideCardItemBinding.cardGradientMask.visibility = visibility
homeSlideCardItemBinding.cardIv.visibility = View.VISIBLE
gameIconStackGroup.forEach { it.visibility = View.GONE }
gameIconGroup.forEach { it.visibility = View.GONE }
descTv.text = homeSubSlide.cardDesc
// 防止重复加载图片导致闪烁

View File

@ -0,0 +1,71 @@
package com.gh.gamecenter.home.custom.viewholder
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.databinding.RecyclerRecommendCardCustomBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.home.custom.CustomPageViewModel
import com.gh.gamecenter.home.custom.createExposureEvent
import com.gh.gamecenter.home.custom.eventlistener.CommonContentCollectionEventHelper
import com.gh.gamecenter.home.custom.model.CustomCommonContentCollectionItem
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.model.CustomPageItem
import com.gh.gamecenter.home.custom.viewholder.ui.RecommendCardUi
/**
* 通用内容合集-推荐卡片
*/
class CustomRecommendCardViewHolder(
viewModel: CustomPageViewModel,
val binding: RecyclerRecommendCardCustomBinding
) : BaseCustomViewHolder(viewModel, binding.root) {
private val recommendCardUi = RecommendCardUi(binding, object : RecommendCardUi.OnRecommendCardEventListener {
override fun onItemClick(childPosition: Int, card: CustomPageData.RecommendCard) {
childEventHelper.navigateToLinkPage(card.link, "内容卡片", _item.exposureEventList.getOrNull(childPosition))
}
override fun onItemExposure(childPosition: Int, card: CustomPageData.RecommendCard) {
val item = _item as CustomCommonContentCollectionItem
val gameEntity = if (card.linkType == "game") {
GameEntity(id = card.linkId, name = card.linkText)
} else {
GameEntity()
}
_item.exposureEventList.add(
createExposureEvent(
gameEntity.also {
it.sequence = childPosition
it.outerSequence = _item.position
},
listOf(
ExposureSource(
"通用内容合集",
"${item.data.name}+${item.data.layoutChinese}+${item.data.id}"
)
),
pageConfigure.exposureSourceList,
childPosition,
_item.componentPosition
)
)
}
override fun onAllClick() {
childEventHelper.navigateToCommonCollectionDetailPage(_item.link)
}
})
override val childEventHelper by lazy {
CommonContentCollectionEventHelper(viewModel)
}
override fun bindView(item: CustomPageItem) {
super.bindView(item)
if (item is CustomCommonContentCollectionItem) {
item.exposureEventList.clear()
recommendCardUi.bind(item.data.topRightText, item.data.name, item.data.subtitle, item.data.recommendCards)
}
}
}

View File

@ -0,0 +1,248 @@
package com.gh.gamecenter.home.custom.viewholder
import android.util.DisplayMetrics
import android.view.ViewGroup.LayoutParams
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.LinearSmoothScroller
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.OnScrollListener
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.view.ScrollEventListener
import com.gh.gamecenter.databinding.RecyclerNotificationColumnBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.home.custom.BannerInRecyclerController
import com.gh.gamecenter.home.custom.CustomPageViewModel
import com.gh.gamecenter.home.custom.adapter.NotificationColumnAdapter
import com.gh.gamecenter.home.custom.createExposureEvent
import com.gh.gamecenter.home.custom.eventlistener.CommonContentCollectionEventHelper
import com.gh.gamecenter.home.custom.model.CustomCommonContentCollectionItem
import com.gh.gamecenter.home.custom.model.CustomPageItem
import com.gh.gamecenter.home.custom.tracker.CommonContentCollectionTracker
/**
* 通用内容合集-通知栏目
*/
class NotificationColumnViewHolder(
viewModel: CustomPageViewModel,
private val lifecycleOwner: LifecycleOwner,
val binding: RecyclerNotificationColumnBinding
) : BaseCustomViewHolder(viewModel, binding.root) {
private var isScrolling = false
private val bannerController = BannerInRecyclerController {
nextToPage()
}
override val childEventHelper by lazy {
CommonContentCollectionEventHelper(viewModel)
}
private val adapter by lazy(LazyThreadSafetyMode.NONE) {
NotificationColumnAdapter(itemView.context) { index, notify ->
val item = _item as CustomCommonContentCollectionItem
val gameEntity = if (notify.linkType == "game") {
GameEntity(id = notify.linkId, name = notify.linkText)
} else {
GameEntity()
}
_item.exposureEventList.add(
createExposureEvent(
gameEntity.also {
it.sequence = index
it.outerSequence = _item.position
},
listOf(
ExposureSource(
"通用内容合集",
"${item.data.name}+${item.data.layoutChinese}+${item.data.id}"
),
ExposureSource(
item.data.layoutChinese,
"${notify.id}"
)
),
pageConfigure.exposureSourceList,
index,
_item.componentPosition
)
)
}
}
private val layoutManager by lazy(LazyThreadSafetyMode.NONE) {
object : LinearLayoutManager(itemView.context) {
override fun smoothScrollToPosition(
recyclerView: RecyclerView?,
state: RecyclerView.State?,
position: Int
) {
val linearSmoothScroller = object : LinearSmoothScroller(itemView.context) {
override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics): Float {
return MILLISECONDS_PER_INCH / displayMetrics.densityDpi
}
}
linearSmoothScroller.targetPosition = position
startSmoothScroll(linearSmoothScroller)
}
}
}
private val scrollEventListener by lazy(LazyThreadSafetyMode.NONE) {
ScrollEventListener(binding.rvNotification).apply {
setOnPageChangeCallback(object : OnPageChangeCallback() {
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
if (positionOffset > 0F) {
// position 代表的是正在移出屏幕的 itemView 的位置
layoutManager.findViewByPosition(position)?.alpha = 1 - positionOffset
layoutManager.findViewByPosition(position + 1)?.alpha = positionOffset
} else {
// position 代表的是屏幕中间的 itemView的位置
layoutManager.findViewByPosition(position)?.alpha = 1F
}
}
override fun onPageSelected(position: Int) {
(_item as? CustomCommonContentCollectionItem)?.selectedPosition = position
}
})
}
}
/**
* 有新的通知栏被隐藏,检查这里是否需要刷新
*/
private val hiddenNotifiesObserver = Observer<HashMap<String, MutableSet<String>>> {
val item = _item as CustomCommonContentCollectionItem
val hiddenIds = it[item.data.id]
val notifies = item.data.notifies
val dataList = notifies.filterNot { notify ->
hiddenIds?.contains(notify.id) == true
}
if (dataList.isNotEmpty()) {
adapter.submitList(dataList)
} else {
setVisible(false)
}
}
init {
lifecycleOwner.lifecycle.addObserver(bannerController)
binding.rvNotification.itemAnimator = null
binding.itcvContainer.setOnClickListener {
val item = _item
if (!isScrolling && item is CustomCommonContentCollectionItem) {
val childPosition = adapter.getDataPosition(item.selectedPosition)
val notify = adapter.getItem(childPosition)
childEventHelper.navigateToLinkPage(
notify.link,
"通知栏目",
item.exposureEventList.getOrNull(childPosition)
)
}
}
binding.vClose.setOnClickListener {
if (!isScrolling) {
closeItem()
}
}
}
override fun bindView(item: CustomPageItem) {
super.bindView(item)
if (item is CustomCommonContentCollectionItem) {
item.exposureEventList.clear()
if (item.data.notifies.isEmpty()) {
setVisible(false)
return
} else {
setVisible(true)
}
if (binding.rvNotification.adapter == null) {
binding.rvNotification.layoutManager = layoutManager
binding.rvNotification.addOnScrollListener(scrollEventListener)
binding.rvNotification.adapter = adapter
binding.rvNotification.addOnScrollListener(object : OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
isScrolling = newState != RecyclerView.SCROLL_STATE_IDLE
}
})
}
}
}
override fun onViewAttach(parent: RecyclerView?) {
viewModel.shareHiddenNotifications.observe(lifecycleOwner, hiddenNotifiesObserver)
bannerController.onViewAttachedToWindow(parent)
}
override fun onViewDetach(parent: RecyclerView?) {
viewModel.shareHiddenNotifications.removeObserver(hiddenNotifiesObserver)
bannerController.onViewDetachedFromWindow(parent)
}
private fun nextToPage() {
if (adapter.isBanner) {
val layoutManager = binding.rvNotification.layoutManager
if (layoutManager is LinearLayoutManager) {
val firstPosition = layoutManager.findFirstCompletelyVisibleItemPosition()
if (firstPosition != -1) {
binding.rvNotification.smoothScrollToPosition(firstPosition + 1)
}
}
}
}
private fun closeItem() {
val selectedPosition = layoutManager.findFirstCompletelyVisibleItemPosition()
if (selectedPosition != -1) {
val item = adapter.getItem(selectedPosition)
if (!item.isClosable) {
return
}
(_item as? CustomCommonContentCollectionItem)?.let {
val link = LinkEntity(link = item.linkId, type = item.linkType, text = item.linkText)
CommonContentCollectionTracker(pageLocation).trackLinkContentCollectionClick(_item, link, "关闭通知")
viewModel.hideNotificationItem(it.data.id, item.id)
}
}
}
private fun setVisible(isVisible: Boolean) {
if (isVisible) {
binding.root.goneIf(false)
binding.root.updateLayoutParams {
height = 80F.dip2px()
width = LayoutParams.MATCH_PARENT
}
} else {
binding.root.goneIf(true)
binding.root.updateLayoutParams {
height = 0
width = LayoutParams.MATCH_PARENT
}
}
}
companion object {
private const val MILLISECONDS_PER_INCH = 1000f
}
}

View File

@ -0,0 +1,71 @@
package com.gh.gamecenter.home.custom.viewholder.ui
import android.content.Context
import android.opengl.Visibility
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.DarkModeUtils
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.visibleIf
import com.gh.gamecenter.databinding.RecyclerRecommendCardCustomBinding
import com.gh.gamecenter.home.custom.adapter.RecommendCardAdapter
import com.gh.gamecenter.home.custom.model.CustomPageData
class RecommendCardUi(
private val binding: RecyclerRecommendCardCustomBinding,
private val listener: OnRecommendCardEventListener
) {
private val context: Context
get() = binding.root.context
private val adapter by lazy {
RecommendCardAdapter(context, listener)
}
fun bind(rightTop: String, title: String, subTitle: String, recommendCards: List<CustomPageData.RecommendCard>) {
if (binding.rvCards.adapter == null) {
binding.rvCards.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
binding.rvCards.adapter = adapter
}
binding.tvRight.goneIf(rightTop.isBlank()) {
binding.tvRight.setText(R.string.custom_home_text_all)
}
binding.tvTitle.goneIf(title.isBlank()) {
binding.tvTitle.text = title
}
binding.tvSubTitle.goneIf(subTitle.isBlank()) {
binding.tvSubTitle.text = subTitle
}
adapter.submitList(recommendCards)
binding.tvRight.setOnClickListener {
listener.onAllClick()
}
if (DarkModeUtils.isDarkModeOn(context)) {
binding.vBackground.setBackgroundColor(R.color.ui_background.toColor(context))
} else {
binding.vBackground.setBackgroundResource(R.drawable.bg_img_recommend_card)
}
binding.root.post {
if (binding.tvSubTitle.visibility == View.VISIBLE) {
val ellipsisStart = binding.tvSubTitle.layout?.getEllipsisStart(0) ?: 0
binding.tvSubTitle.visibleIf(ellipsisStart == 0)
}
}
}
interface OnRecommendCardEventListener {
fun onItemClick(childPosition: Int, card: CustomPageData.RecommendCard)
fun onItemExposure(childPosition: Int, card: CustomPageData.RecommendCard)
fun onAllClick()
}
}

View File

@ -2580,6 +2580,12 @@ public interface ApiService {
@GET("home/common_collection/{collection_id}/contents")
Observable<List<CommonCollectionContentEntity>> getCommonCollectionDetail(@Path("collection_id") String collectionId, @Query("page") int page);
/**
* 获取通用内容合集详情-推荐卡片分页数据
*/
@GET("home/common_collection/{collection_id}/contents")
Observable<List<CustomPageData.RecommendCard>> getCommonCollectionDetailWithRecommenCards(@Path("collection_id") String collectionId, @Query("page") int page);
/**
* 获取青少年模式状态
*/

View File

@ -2,6 +2,7 @@ package com.gh.gamecenter.wrapper
import android.os.Bundle
import android.view.View
import androidx.lifecycle.Observer
import androidx.viewpager.widget.ViewPager
import com.gh.common.iinterface.ISmartRefresh
import com.gh.common.iinterface.ISmartRefreshContent
@ -21,7 +22,7 @@ import kotlin.math.roundToInt
/**
* 二级页面-多tab导航页带Tab的外层Fragment
*/
class TabWrapperFragment: BaseTabWrapperFragment(), ISmartRefresh, ISmartRefreshContent {
class TabWrapperFragment : BaseTabWrapperFragment(), ISmartRefresh, ISmartRefreshContent {
private val mBinding by lazy { FragmentTabWrapperBinding.inflate(layoutInflater) }
private val mViewModel: TabWrapperViewModel by lazy { viewModelProvider(TabWrapperViewModel.Factory(mMultiTabNavId)) }
@ -82,6 +83,13 @@ class TabWrapperFragment: BaseTabWrapperFragment(), ISmartRefresh, ISmartRefresh
initMenu(R.menu.menu_download)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mViewModel.title.observe(viewLifecycleOwner, Observer {
setNavigationTitle(it)
})
}
override fun hideTab() {
mBinding.tabContainer.visibility = View.GONE
}

View File

@ -26,6 +26,9 @@ open class TabWrapperViewModel(
val tabListLiveData = MutableLiveData<List<MultiTabNav.LinkMultiTabNav>>()
val errorLiveData = MutableLiveData<Throwable>()
private val _title = MutableLiveData<String>()
val title: LiveData<String> = _title
internal var multiTabLoadedFlow: Channel<List<MultiTabNav.LinkMultiTabNav>> = Channel(Channel.CONFLATED)
init {
@ -53,6 +56,7 @@ open class TabWrapperViewModel(
}
private fun processMultiTabNavData(multiTabNav: MultiTabNav) {
_title.value = multiTabNav.title
val tabList = multiTabNav.linkMultiTabNav
tabList.forEachIndexed { index, tab ->
if (tab.link?.type == ViewPagerFragmentHelper.TYPE_AMWAY) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 152 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/white" />
<corners
android:bottomLeftRadius="2dp"
android:bottomRightRadius="2dp"
android:topLeftRadius="8dp"
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">
<gradient
android:endColor="#FF7C5C"
android:startColor="#FF675C" />
<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">
<corners android:topLeftRadius="8dp"
android:topRightRadius="8dp"
android:bottomLeftRadius="8dp"/>
<solid android:color="@color/ui_surface"/>
</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/ui_container_1"/>
<corners android:radius="4dp"/>
</shape>

View File

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

View File

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

View File

@ -0,0 +1,27 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<path
android:pathData="M7.463,5.96H12.616C12.833,5.889 13.052,5.821 13.271,5.755L13.29,5.749L13.291,5.748C14.838,5.275 16.761,4.687 16.761,2.627C16.761,1.552 16.171,0.756 15.223,0.552C13.721,0.23 11.5,1.42 9.996,3.896C8.475,1.42 6.255,0.232 4.765,0.556C3.826,0.763 3.242,1.556 3.242,2.627C3.242,4.675 5.146,5.253 6.678,5.715C6.94,5.794 7.204,5.874 7.463,5.96Z"
android:fillColor="#FF8D3A"
android:fillType="evenOdd"/>
<path
android:pathData="M1,4.96C0.448,4.96 0,5.408 0,5.96V7.485C0,8.037 0.448,8.485 1,8.485H19C19.552,8.485 20,8.037 20,7.485V5.96C20,5.408 19.552,4.96 19,4.96H1ZM18.312,9.5H1.646V17.5C1.646,18.605 2.541,19.5 3.646,19.5H16.312C17.417,19.5 18.312,18.605 18.312,17.5V9.5Z"
android:fillColor="#F00909"
android:fillAlpha="0.8"
android:fillType="evenOdd"/>
<path
android:pathData="M7.5,8.5H8.5V19.5H7.5V8.5Z"
android:fillColor="#D60E0D"/>
<path
android:pathData="M11.5,8.5H12.5V19.5H11.5V8.5Z"
android:fillColor="#D60E0D"/>
<path
android:pathData="M1.646,8.5H18.313V10H1.646V8.5Z"
android:fillColor="#E31C1B"/>
<path
android:pathData="M8.5,8.5H11.5V19.5H8.5V8.5Z"
android:fillColor="#FFBB0D"/>
</vector>

View File

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

View File

@ -0,0 +1,26 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="8dp"
android:viewportWidth="16"
android:viewportHeight="8">
<group>
<clip-path
android:pathData="M0,0h16v8h-16z"/>
<path
android:pathData="M4,0.25L12,0.25A3.75,3.75 0,0 1,15.75 4L15.75,4A3.75,3.75 0,0 1,12 7.75L4,7.75A3.75,3.75 0,0 1,0.25 4L0.25,4A3.75,3.75 0,0 1,4 0.25z"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#CCCCCC"/>
<group>
<clip-path
android:pathData="M6,2h4v4h-4z"/>
<path
android:pathData="M6.25,2.25L9.75,5.75M9.75,2.25L6.25,5.75"
android:strokeLineJoin="round"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#CCCCCC"
android:strokeLineCap="round"/>
</group>
</group>
</vector>

View File

@ -73,7 +73,7 @@
android:layout_marginEnd="10dp"
android:orientation="vertical"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@+id/cl_game_icon_stack"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_goneMarginEnd="10dp">
@ -97,47 +97,6 @@
tools:text="副标题" />
</LinearLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/cl_game_icon_stack"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.gh.gamecenter.feature.view.GameIconView
android:id="@+id/gameIconStackIv1"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:alpha="0.25"
app:layout_constraintBottom_toBottomOf="@+id/gameIconStackIv2"
app:layout_constraintEnd_toEndOf="@+id/gameIconStackIv2"
app:layout_constraintTop_toTopOf="@+id/gameIconStackIv2" />
<com.gh.gamecenter.feature.view.GameIconView
android:id="@+id/gameIconStackIv2"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="12dp"
android:layout_marginRight="12dp"
android:alpha="0.5"
app:layout_constraintBottom_toBottomOf="@+id/gameIconStackIv3"
app:layout_constraintEnd_toEndOf="@+id/gameIconStackIv3"
app:layout_constraintTop_toTopOf="@+id/gameIconStackIv3" />
<com.gh.gamecenter.feature.view.GameIconView
android:id="@+id/gameIconStackIv3"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginEnd="10dp"
android:layout_marginRight="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<com.gh.gamecenter.feature.view.GameIconView
android:id="@+id/gameIconIv1"
android:layout_width="24dp"

View File

@ -20,13 +20,13 @@
android:id="@+id/navigationNameTv"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:layout_marginBottom="6dp"
android:ellipsize="end"
android:gravity="center"
android:includeFontPadding="false"
android:maxLines="1"
android:paddingLeft="8dp"
android:paddingRight="8dp"
android:textColor="@color/white"
android:textSize="@dimen/secondary_size"
android:textStyle="bold"
@ -44,4 +44,25 @@
app:layout_constraintEnd_toEndOf="@+id/navigationView"
app:layout_constraintTop_toTopOf="@+id/navigationView" />
<FrameLayout
android:id="@+id/fl_bubble_container"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/bg_custom_page_navigate_bubble_background"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent">
<TextView
android:id="@+id/tv_bubble"
style="@style/TextAnnotation1"
android:layout_width="wrap_content"
android:layout_height="18dp"
android:ellipsize="end"
android:gravity="center"
android:maxLines="1"
android:paddingStart="4dp"
android:paddingEnd="4dp" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -18,6 +18,7 @@
android:scrollbars="none"
android:stretchMode="spacingWidth"
android:verticalSpacing="12dp"
tools:listitem="@layout/item_home_recommend_list_item" />
android:clipChildren="false"
tools:listitem="@layout/item_home_recommend_list_item_custom" />
</FrameLayout>

View File

@ -1,8 +1,10 @@
<?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="60dp"
android:layout_height="wrap_content"
android:clipChildren="false"
android:gravity="center_horizontal"
android:orientation="vertical">
@ -10,7 +12,10 @@
android:id="@+id/iconIv"
style="@style/frescoStyle"
android:layout_width="44dp"
android:layout_height="44dp" />
android:layout_height="44dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/nameTv"
@ -19,6 +24,30 @@
android:includeFontPadding="false"
android:textColor="@color/text_primary"
android:textSize="11sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iconIv"
tools:text="分类" />
</LinearLayout>
<Space
android:id="@+id/space"
android:layout_width="1dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<com.gh.gamecenter.home.custom.adapter.WidthUnlimitedTextView
android:id="@+id/tv_bubble"
style="@style/TextAnnotation2"
android:layout_width="wrap_content"
android:layout_height="18dp"
android:layout_marginTop="1dp"
android:background="@drawable/bg_king_kong_bubble"
android:gravity="center_horizontal"
android:paddingHorizontal="6dp"
android:paddingTop="2dp"
android:textColor="@color/text_aw_primary"
app:layout_constraintStart_toStartOf="@id/space"
app:layout_constraintTop_toTopOf="parent"
tools:text="1" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp">
<com.gh.common.view.InterceptVerticalScrollCardView
android:id="@+id/cv_banner_container"
android:layout_width="0dp"
android:layout_height="0dp"
app:cardCornerRadius="8dp"
app:cardElevation="0dp"
app:layout_constraintDimensionRatio="41:11"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.viewpager2.widget.ViewPager2
android:id="@+id/vp_banner"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<View
android:id="@+id/v_border"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/border_search_game_first_banner" />
</com.gh.common.view.InterceptVerticalScrollCardView>
<com.gh.gamecenter.common.view.ScaleIndicatorView
android:id="@+id/bannerIndicator"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
android:elevation="20dp"
app:checkedColor="@color/white"
app:checkedSliderWidth="12dp"
app:layout_constraintBottom_toBottomOf="@id/cv_banner_container"
app:layout_constraintEnd_toEndOf="@id/cv_banner_container"
app:normalColor="@color/white_alpha_40"
app:normalSliderWidth="4dp"
app:sliderGap="4dp"
app:sliderHeight="4dp" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_games"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:paddingStart="16dp"
android:paddingEnd="8dp" />
</FrameLayout>

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<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="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="8dp"
android:paddingBottom="8dp">
<View
android:id="@+id/v_background"
android:layout_width="0dp"
android:layout_height="36dp"
android:layout_marginTop="8dp"
android:background="@drawable/bg_shape_content_label_lane_item"
app:layout_constraintEnd_toEndOf="@id/space"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<com.gh.gamecenter.feature.view.GameIconView
android:id="@+id/iv_icon"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_marginStart="8dp"
app:gameIconCornerRadius="6dp"
app:gameIconBorderColor="@color/resource_border"
app:gameIconBorderWidth="0.5dp"
app:layout_constraintBottom_toBottomOf="@id/v_background"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/v_background" />
<TextView
android:id="@+id/tv_title"
style="@style/TextBody2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:textColor="@color/text_primary"
app:layout_constraintBottom_toBottomOf="@id/iv_icon"
app:layout_constraintStart_toEndOf="@id/iv_icon"
app:layout_constraintTop_toTopOf="@id/iv_icon"
app:layout_goneMarginStart="8dp"
tools:text="七日世界" />
<TextView
android:id="@+id/tv_sub_title"
style="@style/TextCaption2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="@color/primary_theme"
app:layout_constraintBottom_toBottomOf="@id/tv_title"
app:layout_constraintStart_toEndOf="@id/tv_title"
app:layout_constraintTop_toTopOf="@id/iv_icon"
tools:text=" · 超自然开放世界生存" />
<Space
android:id="@+id/space"
android:layout_width="1dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
app:layout_constraintStart_toEndOf="@id/tv_sub_title" />
<View
android:id="@+id/v_bubble_background"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/bg_shape_background_content_label_lane_item_bubble"
app:layout_constraintBottom_toBottomOf="@id/tv_bubble"
app:layout_constraintEnd_toEndOf="@id/tv_bubble"
app:layout_constraintStart_toStartOf="@id/tv_bubble"
app:layout_constraintTop_toTopOf="@id/tv_bubble" />
<TextView
android:id="@+id/tv_bubble"
style="@style/TextAnnotation1"
android:layout_width="wrap_content"
android:layout_height="16dp"
android:background="@drawable/bg_shape_content_label_lane_item_bubble"
android:gravity="center"
android:paddingHorizontal="8dp"
android:textColor="@color/primary_theme_70"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="奇迹之海提前上线" />
<androidx.constraintlayout.widget.Group
android:id="@+id/g_bubble"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:constraint_referenced_ids="v_bubble_background,tv_bubble" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -7,6 +7,7 @@
android:layout_marginLeft="16dp"
android:layout_marginRight="16dp"
android:layout_marginBottom="16dp"
android:clipChildren="false"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<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="80dp"
android:background="@drawable/bg_img_notification_column">
<com.gh.common.view.InterceptTouchContainView
android:id="@+id/itcv_container"
android:layout_width="match_parent"
android:layout_height="64dp"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_notification"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/bg_shape_notification_column_item"
tools:listitem="@layout/recycler_notification_column_item" />
</com.gh.common.view.InterceptTouchContainView>
<View
android:id="@+id/v_close"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="4dp"
app:layout_constraintEnd_toEndOf="@id/itcv_container"
app:layout_constraintTop_toTopOf="@id/itcv_container" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,74 @@
<?xml version="1.0" encoding="utf-8"?>
<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">
<com.gh.gamecenter.feature.view.GameIconView
android:id="@+id/iv_icon"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginStart="12dp"
app:gameIconBorderColor="@color/resource_border"
app:gameIconBorderWidth="0.5dp"
app:gameIconCornerRadius="10dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<Space
android:id="@+id/space"
android:layout_width="0dp"
android:layout_height="40dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<View
android:id="@+id/v_close_background"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_marginEnd="4dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/icon_notification_column_item_close"
app:layout_constraintBottom_toBottomOf="@id/v_close_background"
app:layout_constraintEnd_toEndOf="@id/v_close_background"
app:layout_constraintStart_toStartOf="@id/v_close_background"
app:layout_constraintTop_toTopOf="@id/v_close_background" />
<TextView
android:id="@+id/tv_title"
style="@style/TextBody2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:ellipsize="end"
android:includeFontPadding="false"
android:maxLines="1"
android:textColor="@color/text_theme"
app:layout_constraintEnd_toStartOf="@id/v_close_background"
app:layout_constraintStart_toEndOf="@id/iv_icon"
app:layout_constraintTop_toTopOf="@id/space"
app:layout_goneMarginStart="12dp"
tools:text="《原神》新版本快来体验《原神》新版本快来体验《原神》新版本快来体验 " />
<TextView
android:id="@+id/tv_desc"
style="@style/TextCaption2"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:ellipsize="end"
android:includeFontPadding="false"
android:maxLines="1"
android:textColor="@color/text_secondary"
app:layout_constraintBottom_toBottomOf="@id/space"
app:layout_constraintEnd_toEndOf="@id/tv_title"
app:layout_constraintStart_toStartOf="@id/tv_title"
tools:text="新地图快来开荒新地图快来开荒新地图快来开荒新地图快来开荒新地图快来开荒新地图快来开荒" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,101 @@
<?xml version="1.0" encoding="utf-8"?>
<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="wrap_content">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/iv_cover"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintDimensionRatio="h,16:9"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:roundedCornerRadius="4dp" />
<TextView
android:id="@+id/tv_title"
style="@style/TextHeadline"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/text_primary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_cover"
tools:text="少年三国志" />
<Space
android:id="@+id/space"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginTop="17dp"
app:layout_constraintTop_toBottomOf="@id/tv_title" />
<TextView
android:id="@+id/tv_label"
style="@style/TextAnnotation1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#1AFF5447"
android:paddingHorizontal="2dp"
android:textColor="#FF5447"
app:layout_constraintBottom_toBottomOf="@id/space"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/space"
tools:text="充值" />
<TextView
android:id="@+id/tv_price_symbol"
style="@style/TextAnnotation2"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:text="¥"
android:textColor="#FF5447"
app:layout_constraintBaseline_toBaselineOf="@id/tv_price"
app:layout_constraintStart_toEndOf="@id/tv_label" />
<TextView
android:id="@+id/tv_price"
style="@style/TextBody2B"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="2dp"
android:textColor="#FF5447"
app:layout_constraintBottom_toBottomOf="@id/tv_label"
app:layout_constraintStart_toEndOf="@id/tv_price_symbol"
app:layout_constraintTop_toTopOf="@id/tv_label"
tools:text="388.8" />
<TextView
android:id="@+id/tv_original_price"
style="@style/TextCaption1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:textColor="@color/text_tertiary"
app:layout_constraintBottom_toBottomOf="@id/tv_label"
app:layout_constraintStart_toEndOf="@id/tv_price"
app:layout_constraintTop_toTopOf="@id/tv_label"
tools:text="¥648" />
<TextView
android:id="@+id/tv_copy"
style="@style/TextAnnotation1"
android:layout_width="wrap_content"
android:layout_height="18dp"
android:layout_marginStart="4dp"
android:background="@drawable/bg_recommend_card_added_content"
android:gravity="center"
android:paddingHorizontal="4dp"
android:textColor="@color/ui_surface"
app:layout_constraintBottom_toBottomOf="@id/tv_label"
app:layout_constraintStart_toEndOf="@id/tv_original_price"
app:layout_constraintTop_toTopOf="@id/tv_label"
tools:text="立省421.2元" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="utf-8"?>
<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="258dp"
android:layout_marginVertical="-8dp"
android:background="@drawable/bg_img_shadow_recommend_card">
<View
android:id="@+id/v_background"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_margin="16dp"
android:background="@drawable/bg_img_recommend_card"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ImageView
android:id="@+id/iv_gift"
android:layout_width="20dp"
android:layout_height="20dp"
android:layout_marginStart="16dp"
android:layout_marginTop="12dp"
android:src="@drawable/ic_gift_recommend_card"
app:layout_constraintStart_toStartOf="@id/v_background"
app:layout_constraintTop_toTopOf="@id/v_background" />
<LinearLayout
android:id="@+id/ll_title_container"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_marginStart="8dp"
app:layout_constraintBottom_toBottomOf="@id/iv_gift"
app:layout_constraintEnd_toStartOf="@id/tv_right"
app:layout_constraintStart_toEndOf="@id/iv_gift"
app:layout_constraintTop_toTopOf="@id/iv_gift">
<TextView
android:id="@+id/tv_title"
style="@style/TextHeadline"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:paddingEnd="4dp"
android:textColor="@color/text_primary"
tools:text="新人推荐新人推荐新" />
<TextView
android:id="@+id/tv_sub_title"
style="@style/TextAnnotation1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/text_secondary"
tools:text="充648低至3块钱" />
</LinearLayout>
<TextView
android:id="@+id/tv_right"
style="@style/TextCaption1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="14dp"
android:drawablePadding="4dp"
android:textColor="@color/text_primary"
app:layout_constraintBottom_toBottomOf="@id/iv_gift"
app:layout_constraintEnd_toEndOf="@id/v_background"
app:layout_constraintTop_toTopOf="@id/iv_gift"
tools:text="更多"
app:drawableEndCompat="@drawable/ic_right" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_cards"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:clipToPadding="false"
android:paddingHorizontal="4dp"
app:layout_constraintBottom_toBottomOf="@id/v_background"
app:layout_constraintEnd_toEndOf="@id/v_background"
app:layout_constraintStart_toStartOf="@id/v_background"
app:layout_constraintTop_toBottomOf="@id/iv_gift"
tools:listitem="@layout/recycler_recommend_card_item" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,104 @@
<?xml version="1.0" encoding="utf-8"?>
<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="214dp"
android:layout_height="match_parent"
android:layout_marginHorizontal="4dp">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/iv_cover"
android:layout_width="0dp"
android:layout_height="120dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:roundBottomLeft="false"
app:roundBottomRight="false"
app:roundTopLeft="true"
app:roundTopRight="true"
app:roundedCornerRadius="4dp" />
<TextView
android:id="@+id/tv_title"
style="@style/TextHeadline"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/text_primary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/iv_cover" />
<Space
android:id="@+id/space"
android:layout_width="0dp"
android:layout_height="1dp"
android:layout_marginTop="17dp"
app:layout_constraintTop_toBottomOf="@id/tv_title" />
<TextView
android:id="@+id/tv_label"
style="@style/TextAnnotation1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="#1AFF5447"
android:paddingHorizontal="2dp"
android:textColor="#FF5447"
app:layout_constraintBottom_toBottomOf="@id/space"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="@id/space"
tools:text="充值" />
<TextView
android:id="@+id/tv_price_symbol"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:text="¥"
android:textColor="#FF5447"
android:textSize="8sp"
android:textStyle="bold"
app:layout_constraintBaseline_toBaselineOf="@id/tv_price"
app:layout_constraintStart_toEndOf="@id/tv_label" />
<TextView
android:id="@+id/tv_price"
style="@style/TextBody2B"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="2dp"
android:textColor="#FF5447"
app:layout_constraintBottom_toBottomOf="@id/tv_label"
app:layout_constraintStart_toEndOf="@id/tv_price_symbol"
app:layout_constraintTop_toTopOf="@id/tv_label"
tools:text="388.8" />
<TextView
android:id="@+id/tv_original_price"
style="@style/TextCaption1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="4dp"
android:textColor="@color/text_tertiary"
app:layout_constraintBaseline_toBaselineOf="@id/tv_price"
app:layout_constraintStart_toEndOf="@id/tv_price"
tools:text="¥648" />
<TextView
android:id="@+id/tv_copy"
style="@style/TextAnnotation1"
android:layout_width="wrap_content"
android:layout_height="18dp"
android:layout_marginStart="4dp"
android:background="@drawable/bg_recommend_card_added_content"
android:gravity="center"
android:paddingHorizontal="4dp"
android:textColor="@color/ui_surface"
app:layout_constraintBottom_toBottomOf="@id/tv_label"
app:layout_constraintStart_toEndOf="@id/tv_original_price"
app:layout_constraintTop_toTopOf="@id/tv_label"
tools:text="立省421.2元" />
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -540,5 +540,7 @@
<string name="custom_home_text_all">全部</string>
<string name="new_game_start_test">新遊開測</string>
<string name="view_all_game">查看所有遊戲</string>
<string name="content_tag_added_content_with_prefix">&#160;·&#160;%1$s</string>
<string name="price_with_symbol">¥%1$s</string>
</resources>

View File

@ -544,4 +544,6 @@
<string name="custom_home_text_all">全部</string>
<string name="new_game_start_test">新游开测</string>
<string name="view_all_game">查看全部游戏</string>
<string name="content_tag_added_content_with_prefix">&#160;·&#160;%1$s</string>
<string name="price_with_symbol">¥%1$s</string>
</resources>

View File

@ -251,6 +251,9 @@ public class Constants {
// 发现页列表数据是否强制刷新
public static final String SP_DISCOVER_FORCE_REFRESH = "discover_force_refresh";
// 用户隐藏的通知栏目
public static final String SP_HIDDEN_NOTIFICATIONS = "sp_hidden_notifications";
// 微信绑定地址
public static final String WECHAT_BIND_ADDRESS_DEV = "https://dev-and-static.ghzs.com/web/wechat_bind/index.html#/";
public static final String WECHAT_BIND_ADDRESS = "https://and-static.ghzs.com/web/wechat_bind/index.html#/";

View File

@ -1944,7 +1944,9 @@ object SensorsBridge {
sequence: Int = -1,
tabContent: String = "",
customPageId: String = "",
customPageName: String = ""
customPageName: String = "",
gameId: String = "",
gameName: String = ""
) {
val json = json {
KEY_LOCATION to location
@ -1967,6 +1969,8 @@ object SensorsBridge {
KEY_TAB_CONTENT to tabContent
KEY_CUSTOM_PAGE_ID to customPageId
KEY_CUSTOM_PAGE_NAME to customPageName
KEY_GAME_ID to gameId
KEY_GAME_NAME to gameName
}
trackEvent(EVENT_LINK_CONTENT_COLLECTION_CLICK, json)
@ -3230,6 +3234,7 @@ object SensorsBridge {
}
trackEvent(EVENT_COMMUNITY_BROWSING_DURATION, json)
}
/**
* 事件IDColumnClick
* 事件名称:游戏专题点击事件

View File

@ -5,6 +5,10 @@
<color name="primary_theme">#2888E0</color>
<!---10 -->
<color name="primary_theme_10">#1A2888E0</color>
<!---30 -->
<color name="primary_theme_30">#4D2888E0</color>
<!---70 -->
<color name="primary_theme_70">#B22888E0</color>
<!-- 辅助颜色 -->
<!---->

View File

@ -5,6 +5,10 @@
<color name="primary_theme">#2496FF</color>
<!---10 -->
<color name="primary_theme_10">#1A2496FF</color>
<!---30 -->
<color name="primary_theme_30">#4D2496FF</color>
<!---70 -->
<color name="primary_theme_70">#B22496FF</color>
<!-- 辅助颜色 -->
<!---->