Compare commits

..

8 Commits

Author SHA1 Message Date
c944e529ba Merge branch 'feat/GHZSCY-8177' into 'dev'
feat: 极光认证SDK升级-客户端 https://jira.shanqu.cc/browse/GHZSCY-8176

See merge request halo/android/assistant-android!2220
2025-06-30 14:57:52 +08:00
eaa1a3609f feat: 极光认证SDK升级-客户端 https://jira.shanqu.cc/browse/GHZSCY-8176 2025-06-30 14:57:18 +08:00
2b9478a1b9 Merge branch 'feat/GHZSCY-8124' into 'dev'
feat: 新游开测相关功能优化(第五期)—客户端 https://jira.shanqu.cc/browse/GHZSCY-8124

See merge request halo/android/assistant-android!2218
2025-06-23 10:37:28 +08:00
dbd6e7bb43 feat: 新游开测相关功能优化(第五期)—客户端 https://jira.shanqu.cc/browse/GHZSCY-8124 2025-06-23 10:37:28 +08:00
09a823d4da Merge branch 'feat/GHZSCY-8150' into 'dev'
feat: GameDetailPageTabSelect埋点事件优化-客户端 https://jira.shanqu.cc/browse/GHZSCY-8150

See merge request halo/android/assistant-android!2217
2025-06-20 16:11:16 +08:00
0b49c58884 feat: GameDetailPageTabSelect埋点事件优化-客户端 https://jira.shanqu.cc/browse/GHZSCY-8150 2025-06-20 16:11:16 +08:00
2e93bcc173 Merge branch 'feat/GHZSCY-8041-pr' into 'dev'
feat:游戏搜索结果列表的插入优化—客户端 https://jira.shanqu.cc/browse/GHZSCY-8041

See merge request halo/android/assistant-android!2216
2025-06-20 14:49:04 +08:00
03f6906b46 feat:游戏搜索结果列表的插入优化—客户端 https://jira.shanqu.cc/browse/GHZSCY-8041 2025-06-20 14:49:04 +08:00
41 changed files with 2001 additions and 2213 deletions

View File

@ -9,7 +9,7 @@ import io.reactivex.Single
class GameSubjectDSPRemoteDataSource(private val api: DspApiService = RetrofitManager.getInstance().dspApiService) { class GameSubjectDSPRemoteDataSource(private val api: DspApiService = RetrofitManager.getInstance().dspApiService) {
fun getDspGames(type: String, count: Int): Single<List<GameEntity>> { fun getDspGames(count: Int): Single<List<GameEntity>> {
val meta = MetaUtil.getMeta() val meta = MetaUtil.getMeta()
val request = mapOf( val request = mapOf(
"device" to mapOf( "device" to mapOf(

View File

@ -0,0 +1,26 @@
package com.gh.gamecenter.entity
import com.gh.gamecenter.servers.gametest2.GameServerTestV2ViewModel
import com.google.gson.annotations.SerializedName
data class GameServerTestDisplaySetting(
@SerializedName("time_text_past")
val timeTextPast: String = RECENT,
@SerializedName("time_text_present")
val timeTextPresent: String = TODAY,
@SerializedName("time_text_future")
val timeTextFuture: String = FUTURE,
@SerializedName("game_category")
val gameCategory: List<String> = listOf(
GameServerTestV2ViewModel.GameCategory.Local.value,
GameServerTestV2ViewModel.GameCategory.Online.value,
GameServerTestV2ViewModel.GameCategory.Welfare.value,
GameServerTestV2ViewModel.GameCategory.Gjonline.value
),
) {
companion object {
const val RECENT = "近期"
const val TODAY = "今天"
const val FUTURE = "预约"
}
}

View File

@ -0,0 +1,33 @@
package com.gh.gamecenter.entity
import com.gh.gamecenter.feature.entity.GameEntity
import com.google.gson.annotations.SerializedName
data class SearchGameUnionEntity(
@SerializedName("type")
private val _type: String? = null,
@SerializedName("link_game")
val linkGame: GameEntity? = null,
@SerializedName("link_wechat_game_cpm_column")
val linkWechatGameCpmColumn: SearchSubjectEntity? = null,
@SerializedName("link_dsp_game_column")
val linkDspGameColumn: SearchSubjectEntity? = null,
@SerializedName("link_wechat_game")
val linkWechatGame: GameEntity? = null,
@SerializedName("link_column")
val linkColum: SearchSubjectEntity? = null,
@SerializedName("link_ad_space")
val linkAdSpace: AdConfig? = null
) {
val type: String
get() = _type ?: ""
companion object {
const val TYPE_GAME = "game"
const val TYPE_WECHAT_GAME_CPM_COLUMN = "wechat_game_cpm_column"
const val TYPE_DSP_GAME_COLUMN = "dsp_game_column"
const val TYPE_WECHAT_GAME = "wechat_game"
const val TYPE_COLUMN = "column"
const val TYPE_AD_SPACE = "ad_space"
}
}

View File

@ -2,8 +2,9 @@ package com.gh.gamecenter.entity
import android.os.Parcelable import android.os.Parcelable
import com.gh.common.filter.RegionSettingHelper import com.gh.common.filter.RegionSettingHelper
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_DSP_GAME_COLUMN
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_WECHAT_GAME_CPM_COLUMN
import com.gh.gamecenter.feature.entity.GameEntity import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.personalhome.home.UserHistoryViewModel
import com.google.gson.annotations.SerializedName import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize import kotlinx.parcelize.Parcelize
@ -18,10 +19,6 @@ data class SearchSubjectEntity(
val codeId: String = "", // 广告CODE_ID(本地字段),不为空时为广告专题 val codeId: String = "", // 广告CODE_ID(本地字段),不为空时为广告专题
@SerializedName("ad_icon_active") @SerializedName("ad_icon_active")
val adIconActive: Boolean = false, val adIconActive: Boolean = false,
// 本地字段标记是否为微信小游戏CPM专题
var isWGameSubjectCPM: Boolean = false,
// 本地字段标记是否为DSP专题
var isDspSubject: Boolean = false,
val type: String = "", val type: String = "",
@SerializedName("column_type") @SerializedName("column_type")
@ -31,10 +28,11 @@ data class SearchSubjectEntity(
val size: Int = -1, // 专题游戏数量 val size: Int = -1, // 专题游戏数量
) : Parcelable { ) : Parcelable {
companion object { val isWGameSubjectCPM: Boolean
const val TYPE_WECHAT_GAME_CPM_COLUMN = "wechat_game_cpm_column" get() = type == TYPE_WECHAT_GAME_CPM_COLUMN
const val TYPE_DSP_GAME_COLUMN = "dsp_game_column"
} val isDspSubject: Boolean
get() = type == TYPE_DSP_GAME_COLUMN
fun getFilterGame() = RegionSettingHelper.filterGame(games) fun getFilterGame() = RegionSettingHelper.filterGame(games)
} }

View File

@ -815,7 +815,10 @@ class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
downloadStatus = gameEntity?.downloadStatusChinese ?: "", downloadStatus = gameEntity?.downloadStatusChinese ?: "",
gameType = gameEntity?.categoryChinese ?: "", gameType = gameEntity?.categoryChinese ?: "",
position = position, position = position,
tabContent = tabEntity.name tabContent = tabEntity.name,
linkType = tabEntity.link?.type ?: "",
linkId = tabEntity.link?.link ?: "",
linkText = tabEntity.link?.text ?: ""
) )
val entrance = if (mEntrance.contains("论坛详情")) "论坛" else "游戏" val entrance = if (mEntrance.contains("论坛详情")) "论坛" else "游戏"

View File

@ -246,9 +246,10 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable, IB
setNavigationTitle(it.title) setNavigationTitle(it.title)
}) })
dataList.observe(viewLifecycleOwner) { (shouldScrollToTop, data) -> dataList.observe(viewLifecycleOwner) {
adapter.submitList(data) { adapter.submitList(it) {
if (shouldScrollToTop) { if (shouldScrollToTop) {
shouldScrollToTop = false
binding.gameList.scrollToPosition(0) binding.gameList.scrollToPosition(0)
} }
} }

View File

@ -29,7 +29,7 @@ interface OnCustomPageEventListener {
/** /**
* 换一批 * 换一批
*/ */
fun onChangeABatch(componentId: String, subjectEntity: SubjectEntity) fun onChangeABatch(subjectEntity: SubjectEntity)
fun onChangeAppBarColor(color: Int) fun onChangeAppBarColor(color: Int)
@ -56,10 +56,7 @@ interface OnCustomPageEventListener {
/** /**
* 点击进入专题详情 * 点击进入专题详情
*/ */
fun navigateSubjectDetailPage( fun navigateSubjectDetailPage(item: CustomSubjectCollectionItem, subject: CustomPageData.LinkColumnCollection.CustomSubjectEntity)
item: CustomSubjectCollectionItem,
subject: CustomPageData.LinkColumnCollection.CustomSubjectEntity
)
/** /**

View File

@ -35,11 +35,9 @@ class SubjectEventHelper(viewModel: CustomPageViewModel) : CustomPageItemChildEv
gameEntity.tempDspLogMap = map.toMap() // Return an immutable copy gameEntity.tempDspLogMap = map.toMap() // Return an immutable copy
} }
} }
gameEntity.isMiniGame() -> { gameEntity.isMiniGame() -> {
tracker.trackMiniGameClick(_item, gameEntity) tracker.trackMiniGameClick(_item, gameEntity)
} }
else -> { else -> {
tracker.trackColumnClick(_item, gameEntity, "游戏") tracker.trackColumnClick(_item, gameEntity, "游戏")
} }
@ -53,11 +51,9 @@ class SubjectEventHelper(viewModel: CustomPageViewModel) : CustomPageItemChildEv
gameEntity.isDspGame -> { gameEntity.isDspGame -> {
tracker.trackDspGameClick(_item, gameEntity, "按钮", "自定义页面") tracker.trackDspGameClick(_item, gameEntity, "按钮", "自定义页面")
} }
gameEntity.isMiniGame() -> { gameEntity.isMiniGame() -> {
tracker.trackMiniGameClick(_item, gameEntity) tracker.trackMiniGameClick(_item, gameEntity)
} }
else -> { else -> {
tracker.trackColumnClick(_item, gameEntity, "按钮") tracker.trackColumnClick(_item, gameEntity, "按钮")
} }
@ -71,9 +67,9 @@ class SubjectEventHelper(viewModel: CustomPageViewModel) : CustomPageItemChildEv
} }
} }
fun onChangeABatch(componentId: String, subject: SubjectEntity) { fun onChangeABatch(subject: SubjectEntity) {
tracker.trackColumnClick(_item, null, "右上角", "换一批") tracker.trackColumnClick(_item, null, "右上角", "换一批")
viewModel.onChangeABatch(componentId,subject) viewModel.onChangeABatch(subject)
} }
fun onMoreClick(link: LinkEntity) { fun onMoreClick(link: LinkEntity) {

View File

@ -1,185 +0,0 @@
package com.gh.gamecenter.home.custom.model
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.entity.PKEntity
import com.gh.gamecenter.entity.AmwayCommentEntity
import com.gh.gamecenter.entity.DiscoveryCardEntity
import com.gh.gamecenter.entity.HomeItemTestV2Entity
import com.gh.gamecenter.entity.SubjectEntity
import com.gh.gamecenter.feature.entity.AcctRecord
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_LINK_TYPE_COLUMN_COLLECTION
import com.gh.vspace.VGameItemData
/**
* 接口返回的原始数据模型,最终需要转化成 CustomPageItem 在ui层呈现
*/
sealed class CustomItemDTO(
val componentId: String, // 当前组件的唯一标识,用于后续更新单个数据时,快速找到目标数据
val link: LinkEntity
)
// 游戏专题:展开大图样式专用
class GameItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
val data: GameEntity,
val linkColumn: SubjectEntity?,
) : CustomItemDTO(_componentId, _link)
//游戏专题
data class SubjectItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
var data: SubjectEntity
) : CustomItemDTO(_componentId, _link) {
val scrollState = ScrollState()
}
// 专题合集/游戏单合集
class CollectionItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
val data: CustomPageData.LinkColumnCollection
) : CustomItemDTO(_componentId, _link) {
val isSubjectCollection: Boolean // 是否是专题合集(还有可能是游戏单合集)
get() = link.type == CUSTOM_LINK_TYPE_COLUMN_COLLECTION
var showPage: Int = 0
var loadPage: Int = 1
var isLoadedEnd: Boolean = false
val uiState = UIState()
/**
* 保存ui层状态
*/
data class UIState(
var isBackToStart: Boolean = false
)
}
// 最近在玩
data class RecentGamesItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
var data: List<VGameItemData>
) : CustomItemDTO(_componentId, _link)
// 微信小游戏-最近在玩
data class RecentWechatMiniGamesItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
var data: List<GameEntity>
) : CustomItemDTO(_componentId, _link)
// qq小游戏-最近在玩
data class RecentQqMiniGamesItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
var data: List<GameEntity>,
val linkQqGameRecentlyPlayed: CustomPageData.LinkRecentlyPlayed
) : CustomItemDTO(_componentId, _link)
// 加速游戏最近在玩
data class RecentAcceleratorGamesItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
var data: List<AcctRecord>,
val moreLink: LinkEntity?
) : CustomItemDTO(_componentId, _link)
// qq小游戏专题
data class QqMiniGamesItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
val data: SubjectEntity
) : CustomItemDTO(_componentId, _link) {
val scrollState = ScrollState()
}
// 微信小游戏专题
data class WechatMiniGamesItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
val data: SubjectEntity
) : CustomItemDTO(_componentId, _link) {
val scrollState = ScrollState()
}
// 微信小游戏CPM专题组件
data class WeChatMiniGamesCPMSubjectItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
var data: SubjectEntity
) : CustomItemDTO(_componentId, _link){
val scrollState = ScrollState()
}
// DSP专题组件
data class DSPItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
var data: SubjectEntity
) : CustomItemDTO(_componentId, _link){
val scrollState = ScrollState()
}
// 插件化区域
data class PluginItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
var data: List<GameEntity>
) : CustomItemDTO(_componentId, _link)
// 新游开测
data class GameTestV2ItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
val data: HomeItemTestV2Entity
) : CustomItemDTO(_componentId, _link)
// 光环推荐
data class DiscoverCardItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
var data: DiscoveryCardEntity?
) : CustomItemDTO(_componentId, _link)
// 安利墙
data class AmwayItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
val data: List<AmwayCommentEntity>
) : CustomItemDTO(_componentId, _link)
// 内容卡片
data class ContentCardItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
val data: CustomPageData.ContentCard
) : CustomItemDTO(_componentId, _link)
// 通用内容合集
data class CommonContentCollectionItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
val data: CustomPageData.CommonContentCollection
) : CustomItemDTO(_componentId, _link)
// PK
data class PKItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
var data: PKEntity?
) : CustomItemDTO(_componentId, _link)
class ScrollState(
var scrolledOffset: Int = 0
)

View File

@ -1,42 +0,0 @@
package com.gh.gamecenter.home.custom.model
import android.util.LruCache
import com.gh.gamecenter.feature.entity.GameEntity
import io.reactivex.Observable
import io.reactivex.Single
class CustomPageCacheDataSource {
// 计算内存缓存大小这里取应用最大可用内存的1/8单位KB
private fun calculateMemoryCacheSize(): Int {
val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
return maxMemory / 8
}
private val memoryCache: LruCache<String, List<GameEntity>> =
object : LruCache<String, List<GameEntity>>(calculateMemoryCacheSize()) {
override fun sizeOf(key: String, value: List<GameEntity>): Int {
return value.toTypedArray().size / 1024 // 单位KB
}
}
fun getGameList(subjectId: String) = Observable.create {
val gameList = memoryCache.get(subjectId)
if (gameList.isNullOrEmpty()) {
it.onComplete()
} else {
it.onNext(gameList)
}
}
fun putGameList(subjectId: String?, data: List<GameEntity>) {
if (!subjectId.isNullOrBlank()) {
memoryCache.put(subjectId, data)
}
}
fun onClear() {
memoryCache.evictAll()
}
}

View File

@ -15,10 +15,10 @@ import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.vspace.VGameItemData import com.gh.vspace.VGameItemData
abstract class CustomPageItem( abstract class CustomPageItem(
// 当前模块在数据列表中的位置注意非ui页面的位置而是数据列表中的位置因为某条数据可能会拆分成多个item
val link: LinkEntity, val link: LinkEntity,
var position: Int,// 当前模块在数据列表中的位置注意非ui页面的位置而是数据列表中的位置因为某条数据可能会拆分成多个item var position: Int,
val componentPosition: Int, val componentPosition: Int
val componentId: String,
) { ) {
abstract val itemType: Int abstract val itemType: Int
@ -362,11 +362,9 @@ abstract class CustomPageItem(
data class CustomSubjectItem( data class CustomSubjectItem(
private val _link: LinkEntity, private val _link: LinkEntity,
val data: SubjectEntity, val data: SubjectEntity,
private val scrollState: ScrollState,
private val _position: Int, private val _position: Int,
private val _componentPosition: Int, private val _componentPosition: Int
private val _componentId: String, ) : CustomPageItem(_link, _position, _componentPosition) {
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int override val itemType: Int
get() = subjectTypeMap[data.type] ?: CUSTOM_PAGE_ITEM_TYPE_INVALID get() = subjectTypeMap[data.type] ?: CUSTOM_PAGE_ITEM_TYPE_INVALID
@ -381,11 +379,7 @@ data class CustomSubjectItem(
get() = data.data ?: emptyList() get() = data.data ?: emptyList()
// 临时变量,记录横向列表滚动的距离 // 临时变量,记录横向列表滚动的距离
var scrolledOffset: Int var scrolledOffset: Int = 0
get() = scrollState.scrolledOffset
set(value) {
scrollState.scrolledOffset = value
}
val exposureSource: List<ExposureSource> val exposureSource: List<ExposureSource>
get() = listOf(ExposureSource("专题", "${data.name ?: ""}+$componentStyle+${data.id}")) get() = listOf(ExposureSource("专题", "${data.name ?: ""}+$componentStyle+${data.id}"))
@ -426,7 +420,7 @@ data class CustomSubjectItem(
if (latestDayBeforeTodayPair.first >= testDayOffset) { if (latestDayBeforeTodayPair.first >= testDayOffset) {
latestDayBeforeTodayPair = Pair(testDayOffset, index) latestDayBeforeTodayPair = Pair(testDayOffset, index)
} }
} else { } else if (testDayOffset > 0) {
if (testDayOffset <= latestDayAfterTodayPair.first) { if (testDayOffset <= latestDayAfterTodayPair.first) {
latestDayAfterTodayPair = Pair(testDayOffset, index) latestDayAfterTodayPair = Pair(testDayOffset, index)
} }
@ -446,12 +440,11 @@ data class CustomSubjectItem(
data class CustomSplitSubjectItem( data class CustomSplitSubjectItem(
private val _link: LinkEntity, private val _link: LinkEntity,
var data: SubjectEntity, var data: SubjectEntity,
val step: Int,
val startChildPosition: Int,
private val _position: Int, private val _position: Int,
private val _componentPosition: Int, private val _componentPosition: Int,
private val _componentId: String, val step: Int,
) : CustomPageItem(_link, _position, _componentPosition, _componentId) { val startChildPosition: Int
) : CustomPageItem(_link, _position, _componentPosition) {
var splitItemCount: Int = -1 // 游戏专题拆分出来的子项数量 var splitItemCount: Int = -1 // 游戏专题拆分出来的子项数量
@ -516,9 +509,8 @@ data class CustomGameItem(
val linkColumn: SubjectEntity?, val linkColumn: SubjectEntity?,
val childPosition: Int, val childPosition: Int,
private val _position: Int, // 当前 item 在 ui 中的位置 private val _position: Int, // 当前 item 在 ui 中的位置
private val _componentPosition: Int, // 当前组件的位置 private val _componentPosition: Int // 当前组件的位置
private val _componentId: String ) : CustomPageItem(_link, _position, _componentPosition) {
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_GAME_HOME_GAME_ITEM get() = CUSTOM_PAGE_ITEM_TYPE_GAME_HOME_GAME_ITEM
@ -554,11 +546,9 @@ data class CustomGameItem(
data class CustomSubjectCollectionItem( data class CustomSubjectCollectionItem(
private val _link: LinkEntity, private val _link: LinkEntity,
val data: CustomPageData.LinkColumnCollection, val data: CustomPageData.LinkColumnCollection,
val uiState: CollectionItemDTO.UIState,
private val _position: Int, private val _position: Int,
private val _componentPosition: Int, private val _componentPosition: Int
private val _componentId: String ) : CustomPageItem(_link, _position, _componentPosition) {
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
val isSubjectCollection: Boolean // 是否是专题合集(还有可能是游戏单合集) val isSubjectCollection: Boolean // 是否是专题合集(还有可能是游戏单合集)
get() = link.type == CUSTOM_LINK_TYPE_COLUMN_COLLECTION get() = link.type == CUSTOM_LINK_TYPE_COLUMN_COLLECTION
@ -582,14 +572,19 @@ data class CustomSubjectCollectionItem(
emptyList() emptyList()
} }
//当前显示的页码 // 临时变量:记录合集中,轮换刷新时当前显示的页码
var showPage: Int = 0 var showPage: Int = 0
var isBackToStart: Boolean // 临时变量:记录合集中,轮换刷新时当前加载的页码
get() = uiState.isBackToStart var loadPage: Int = 1
set(value) {
uiState.isBackToStart = value // 临时变量记录点击轮换刷新时是否正在loading
} var isLoading: Boolean = false
// 临时变量,数据是否已全部加载完毕
var isLoadedEnd: Boolean = false
var isBackToStart = true
val showSubject: CustomPageData.LinkColumnCollection.CustomSubjectEntity? val showSubject: CustomPageData.LinkColumnCollection.CustomSubjectEntity?
get() = data.data.getOrNull(showPage % data.data.size) get() = data.data.getOrNull(showPage % data.data.size)
@ -597,12 +592,14 @@ data class CustomSubjectCollectionItem(
override fun doAreItemsTheSame(other: CustomPageItem): Boolean { override fun doAreItemsTheSame(other: CustomPageItem): Boolean {
return other is CustomSubjectCollectionItem return other is CustomSubjectCollectionItem
&& data.id == other.data.id && data.id == other.data.id
} }
override fun doAreContentsTheSames(other: CustomPageItem): Boolean { override fun doAreContentsTheSames(other: CustomPageItem): Boolean {
return other is CustomSubjectCollectionItem return other is CustomSubjectCollectionItem
&& showPage == other.showPage && showPage == other.showPage
&& isLoading == other.isLoading
&& isLoadedEnd == other.isLoadedEnd
&& showSubject == other.showSubject
} }
} }
@ -611,9 +608,8 @@ data class CustomRecentGamesItem(
private val _link: LinkEntity, private val _link: LinkEntity,
val data: List<VGameItemData>, val data: List<VGameItemData>,
private val _position: Int, private val _position: Int,
private val _componentPosition: Int, private val _componentPosition: Int
private val _componentId: String ) : CustomPageItem(_link, _position, _componentPosition) {
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_RECENT_PLAY get() = CUSTOM_PAGE_ITEM_TYPE_RECENT_PLAY
@ -632,9 +628,8 @@ data class CustomRecentWeChatMiniGamesItem(
private val _link: LinkEntity, private val _link: LinkEntity,
val data: List<GameEntity>, val data: List<GameEntity>,
private val _position: Int, private val _position: Int,
private val _componentPosition: Int, private val _componentPosition: Int
private val _componentId: String ) : CustomPageItem(_link, _position, _componentPosition) {
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_MINI_GAME_RECENT_PLAY get() = CUSTOM_PAGE_ITEM_TYPE_MINI_GAME_RECENT_PLAY
@ -652,9 +647,8 @@ data class CustomWeChatMiniGamesCPMSubjectItem(
private val _link: LinkEntity, private val _link: LinkEntity,
val data: SubjectEntity, val data: SubjectEntity,
private val _position: Int, private val _position: Int,
private val _componentPosition: Int, private val _componentPosition: Int
private val _componentId: String ) : CustomPageItem(_link, _position, _componentPosition) {
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_MINI_GAME_RECENT_PLAY get() = CUSTOM_PAGE_ITEM_TYPE_MINI_GAME_RECENT_PLAY
@ -673,9 +667,8 @@ data class CustomRecentQqMiniGamesItem(
val data: List<GameEntity>, val data: List<GameEntity>,
val linkQqGameRecentlyPlayed: CustomPageData.LinkRecentlyPlayed, val linkQqGameRecentlyPlayed: CustomPageData.LinkRecentlyPlayed,
private val _position: Int, private val _position: Int,
private val _componentPosition: Int, private val _componentPosition: Int
private val _componentId: String ) : CustomPageItem(_link, _position, _componentPosition) {
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_MINI_GAME_RECENT_PLAY get() = CUSTOM_PAGE_ITEM_TYPE_MINI_GAME_RECENT_PLAY
@ -697,9 +690,8 @@ data class CustomRecentAcceleratorItem(
val data: List<AcctRecord>, val data: List<AcctRecord>,
val moreLink: LinkEntity?, val moreLink: LinkEntity?,
private val _position: Int, private val _position: Int,
private val _componentPosition: Int, private val _componentPosition: Int
private val _componentId: String ) : CustomPageItem(_link, _position, _componentPosition) {
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_ACCELERATOR_RECENT_PLAYED get() = CUSTOM_PAGE_ITEM_TYPE_ACCELERATOR_RECENT_PLAYED
@ -721,9 +713,8 @@ data class CustomPluginItem(
private val _link: LinkEntity, private val _link: LinkEntity,
val data: List<GameEntity>, val data: List<GameEntity>,
private val _position: Int, private val _position: Int,
private val _componentPosition: Int, private val _componentPosition: Int
private val _componentId: String ) : CustomPageItem(_link, _position, _componentPosition) {
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_PLUGIN get() = CUSTOM_PAGE_ITEM_TYPE_PLUGIN
@ -744,9 +735,8 @@ data class CustomGameTestV2Item(
private val _link: LinkEntity, private val _link: LinkEntity,
val data: HomeItemTestV2Entity, val data: HomeItemTestV2Entity,
private val _position: Int, private val _position: Int,
private val _componentPosition: Int, private val _componentPosition: Int
private val _componentId: String ) : CustomPageItem(_link, _position, _componentPosition) {
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_NEW_GAME_TEST get() = CUSTOM_PAGE_ITEM_TYPE_NEW_GAME_TEST
@ -764,9 +754,8 @@ data class CustomDiscoverCardItem(
private val _link: LinkEntity, private val _link: LinkEntity,
val data: DiscoveryCardEntity?, val data: DiscoveryCardEntity?,
private val _position: Int, private val _position: Int,
private val _componentPosition: Int, private val _componentPosition: Int
private val _componentId: String ) : CustomPageItem(_link, _position, _componentPosition) {
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
val spanCount = 3 val spanCount = 3
@ -792,9 +781,8 @@ data class CustomAmwayItem(
private val _link: LinkEntity, private val _link: LinkEntity,
val data: List<AmwayCommentEntity>, val data: List<AmwayCommentEntity>,
private val _position: Int, private val _position: Int,
private val _componentPosition: Int, private val _componentPosition: Int
private val _componentId: String ) : CustomPageItem(_link, _position, _componentPosition) {
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_AMWAY_WALL get() = CUSTOM_PAGE_ITEM_TYPE_AMWAY_WALL
@ -812,9 +800,8 @@ data class CustomContentCardItem(
private val _link: LinkEntity, private val _link: LinkEntity,
val data: CustomPageData.ContentCard, val data: CustomPageData.ContentCard,
private val _position: Int, private val _position: Int,
private val _componentPosition: Int, private val _componentPosition: Int
private val _componentId: String ) : CustomPageItem(_link, _position, _componentPosition) {
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_CONTENT_CARD get() = CUSTOM_PAGE_ITEM_TYPE_CONTENT_CARD
@ -835,9 +822,8 @@ data class CustomCommonContentCollectionItem(
private val _link: LinkEntity, private val _link: LinkEntity,
val data: CustomPageData.CommonContentCollection, val data: CustomPageData.CommonContentCollection,
private val _position: Int, private val _position: Int,
private val _componentPosition: Int, private val _componentPosition: Int
private val _componentId: String ) : CustomPageItem(_link, _position, _componentPosition) {
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int override val itemType: Int
get() = if (data.layout == COMMON_CONTENT_COLLECTION_LAYOUT_BANNER) { get() = if (data.layout == COMMON_CONTENT_COLLECTION_LAYOUT_BANNER) {
@ -875,11 +861,10 @@ data class CustomCommonContentCollectionItem(
data class CustomSplitCommonContentCollectionItem( data class CustomSplitCommonContentCollectionItem(
private val _link: LinkEntity, private val _link: LinkEntity,
val data: CustomPageData.CommonContentCollection, val data: CustomPageData.CommonContentCollection,
val leftPosition: Int,
private val _position: Int, private val _position: Int,
private val _componentPosition: Int, private val _componentPosition: Int,
private val _componentId: String val leftPosition: Int,
) : CustomPageItem(_link, _position, _componentPosition, _componentId) { ) : CustomPageItem(_link, _position, _componentPosition) {
override val itemType override val itemType
get() = commonContentCollection[data.layout] ?: CUSTOM_PAGE_ITEM_TYPE_INVALID get() = commonContentCollection[data.layout] ?: CUSTOM_PAGE_ITEM_TYPE_INVALID
@ -912,9 +897,8 @@ data class CustomPKItem(
private val _link: LinkEntity, private val _link: LinkEntity,
val data: PKEntity?, val data: PKEntity?,
private val _position: Int, private val _position: Int,
private val _componentPosition: Int, private val _componentPosition: Int
private val _componentId: String ) : CustomPageItem(_link, _position, _componentPosition) {
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_PK get() = CUSTOM_PAGE_ITEM_TYPE_PK
@ -932,9 +916,8 @@ data class CustomDspPlaceholderItem(
private val _link: LinkEntity, private val _link: LinkEntity,
val data: SubjectEntity, val data: SubjectEntity,
private val _position: Int, private val _position: Int,
private val _componentPosition: Int, private val _componentPosition: Int
private val _componentId: String ) : CustomPageItem(_link, _position, _componentPosition) {
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_MINI_GAME_RECENT_PLAY get() = CUSTOM_PAGE_ITEM_TYPE_MINI_GAME_RECENT_PLAY
@ -947,9 +930,14 @@ data class CustomDspPlaceholderItem(
} }
} }
object CustomFooterItem : CustomPageItem(LinkEntity(), -1, -1, "") { object CustomFooterItem : CustomPageItem(LinkEntity(), -1, -1) {
override val itemType: Int override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_FOOTER get() = CUSTOM_PAGE_ITEM_TYPE_FOOTER
} }
object CustomInvalidItem : CustomPageItem(LinkEntity(), -2, -2) {
override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_INVALID
}

View File

@ -1,10 +1,8 @@
package com.gh.gamecenter.home.custom.model package com.gh.gamecenter.home.custom.model
import android.content.Context import android.content.Context
import android.util.LruCache
import com.gh.gamecenter.core.utils.SPUtils import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.feature.entity.FloatingWindowEntity import com.gh.gamecenter.feature.entity.FloatingWindowEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.halo.assistant.HaloApp import com.halo.assistant.HaloApp
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.* import java.util.*

View File

@ -56,17 +56,12 @@ class CustomPageRemoteDataSource(
} }
} }
fun loadColumnsCollectionContents( fun loadCollectionContent(item: CustomSubjectCollectionItem): Single<List<CustomPageData.LinkColumnCollection.CustomSubjectEntity>> =
collectionId: String, if (item.isSubjectCollection) {
page: Int newApi.getColumnsCollectionContents(item.data.id, "component", item.loadPage + 1, 1)
): Single<MutableList<CustomPageData.LinkColumnCollection.CustomSubjectEntity>> = } else {
newApi.getColumnsCollectionContents(collectionId, "component", page, 1) newApi.getGameCollectionContents(item.data.id, "component", item.loadPage + 1, 1)
}
fun loadGameCollectionContents(
collectionId: String,
page: Int
): Single<MutableList<CustomPageData.LinkColumnCollection.CustomSubjectEntity>> =
newApi.getGameCollectionContents(collectionId, "component", page, 1)
fun loadChangeSubjectGame(subjectEntity: SubjectEntity): Observable<List<GameEntity>> = fun loadChangeSubjectGame(subjectEntity: SubjectEntity): Observable<List<GameEntity>> =
if (subjectEntity.isQQColumn) { if (subjectEntity.isQQColumn) {

View File

@ -2,13 +2,10 @@ package com.gh.gamecenter.home.custom.model
import androidx.annotation.MainThread import androidx.annotation.MainThread
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asLiveData import androidx.lifecycle.asLiveData
import com.gh.common.filter.RegionSettingHelper import com.gh.common.filter.RegionSettingHelper
import com.gh.common.util.HomePluggableHelper
import com.gh.common.util.PackageUtils import com.gh.common.util.PackageUtils
import com.gh.download.DownloadManager
import com.gh.gamecenter.common.constant.Constants import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.Constants.SP_HIDDEN_NOTIFICATIONS import com.gh.gamecenter.common.constant.Constants.SP_HIDDEN_NOTIFICATIONS
import com.gh.gamecenter.common.utils.observableToMain import com.gh.gamecenter.common.utils.observableToMain
@ -20,9 +17,7 @@ import com.gh.gamecenter.entity.DiscoveryGameCardEntity
import com.gh.gamecenter.entity.DiscoveryGameCardLabel import com.gh.gamecenter.entity.DiscoveryGameCardLabel
import com.gh.gamecenter.feature.entity.AcctRecord import com.gh.gamecenter.feature.entity.AcctRecord
import com.gh.gamecenter.feature.entity.GameEntity import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.PluginLocation
import com.gh.gamecenter.gamedetail.accelerator.AccelerationDataBase import com.gh.gamecenter.gamedetail.accelerator.AccelerationDataBase
import com.gh.gamecenter.packagehelper.PackageRepository
import com.gh.gamecenter.retrofit.RetrofitManager import com.gh.gamecenter.retrofit.RetrofitManager
import com.google.gson.Gson import com.google.gson.Gson
import com.google.gson.reflect.TypeToken import com.google.gson.reflect.TypeToken
@ -39,30 +34,6 @@ class CustomPageShareRepository private constructor() {
private val compositeDisposable = CompositeDisposable() private val compositeDisposable = CompositeDisposable()
private val _pluginGames = MediatorLiveData<List<GameEntity>>().apply {
addSource(PackageRepository.gameUpdateLiveData) { updateList ->
val gameList = arrayListOf<GameEntity>()
updateList.forEach {
if (it.isPluggable && it.indexPlugin == "open" && it.isShowPlugin(PluginLocation.only_index)) {
val game = it.transformGameEntity()
if (HomePluggableHelper.showHomePluggable(game)) {
game.setEntryMap(DownloadManager.getInstance().getEntryMap(game.name))
gameList.add(game)
}
}
}
// 下载七天排序
gameList.sortWith { o1, o2 -> o2.download - o1.download }
value = gameList
}
}
val pluginGames: LiveData<List<GameEntity>> = _pluginGames
fun temporarilyBlockGameInPluginItem(gameId: String) {
val gameList = _pluginGames.value ?: return
_pluginGames.value = gameList.filterNot { it.id == gameId }
}
private val _discoverData = MutableLiveData<DiscoveryCardEntity>() private val _discoverData = MutableLiveData<DiscoveryCardEntity>()
val discoverData: LiveData<DiscoveryCardEntity> = _discoverData val discoverData: LiveData<DiscoveryCardEntity> = _discoverData

View File

@ -26,7 +26,10 @@ import com.gh.gamecenter.home.custom.createExposureEvent
import com.gh.gamecenter.home.custom.eventlistener.CustomPageItemChildEventHelper import com.gh.gamecenter.home.custom.eventlistener.CustomPageItemChildEventHelper
import com.gh.gamecenter.home.custom.eventlistener.GameSubjectCollectionEventHelper import com.gh.gamecenter.home.custom.eventlistener.GameSubjectCollectionEventHelper
import com.gh.gamecenter.home.custom.eventlistener.SubjectEventHelper import com.gh.gamecenter.home.custom.eventlistener.SubjectEventHelper
import com.gh.gamecenter.home.custom.model.* import com.gh.gamecenter.home.custom.model.CustomPageItem
import com.gh.gamecenter.home.custom.model.CustomSplitSubjectItem
import com.gh.gamecenter.home.custom.model.CustomSubjectCollectionItem
import com.gh.gamecenter.home.custom.model.CustomSubjectItem
import com.lightgame.download.DownloadEntity import com.lightgame.download.DownloadEntity
abstract class BaseCustomViewHolder( abstract class BaseCustomViewHolder(
@ -95,14 +98,7 @@ abstract class BaseCustomViewHolder(
titleBinding.root.goneIf(false) titleBinding.root.goneIf(false)
setSubjectTitle( setSubjectTitle(
titleBinding, titleBinding,
CustomSubjectItem( CustomSubjectItem(item.link, item.data, item.position, item.componentPosition),
item.link,
item.data,
ScrollState(),
item.position,
item.componentPosition,
item.componentId
),
eventHelper, eventHelper,
titleType titleType
) )
@ -193,7 +189,7 @@ abstract class BaseCustomViewHolder(
when (subject.home) { when (subject.home) {
HOME_CHANGE -> { HOME_CHANGE -> {
// 点击换一批 // 点击换一批
eventHelper.onChangeABatch(item.componentId, subject) eventHelper.onChangeABatch(subject)
} }
HOME_MORE -> { HOME_MORE -> {

View File

@ -88,7 +88,7 @@ class CustomRefreshIconViewHolder(
} else { } else {
_subjectEntity = subjectEntity _subjectEntity = subjectEntity
} }
println("kayn -->isBackToStart:${item.isBackToStart}")
// 如果游戏全部被屏蔽,自动加载下一页 // 如果游戏全部被屏蔽,自动加载下一页
if (subjectEntity.games.isEmpty()) { if (subjectEntity.games.isEmpty()) {
childEventHelper.onRotateRefresh() childEventHelper.onRotateRefresh()

View File

@ -38,12 +38,19 @@ object MiniGameRecentlyPlayUseCase {
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BiResponse<List<GameEntity>>() { .subscribe(object : BiResponse<List<GameEntity>>() {
override fun onSuccess(data: List<GameEntity>) { override fun onSuccess(data: List<GameEntity>) {
if (data.isNotEmpty()) { if (gameType == Constants.QQ_MINI_GAME) {
if (gameType == Constants.QQ_MINI_GAME) { _qqRecentGamesItemLiveData.value = data
_qqRecentGamesItemLiveData.value = data } else {
} else { _wechatRecentGamesItemLiveData.value = data
_wechatRecentGamesItemLiveData.value = data }
} }
override fun onFailure(exception: Exception) {
super.onFailure(exception)
if (gameType == Constants.QQ_MINI_GAME) {
_qqRecentGamesItemLiveData.value = emptyList()
} else {
_wechatRecentGamesItemLiveData.value = emptyList()
} }
} }
}) })
@ -83,7 +90,10 @@ object MiniGameRecentlyPlayUseCase {
_qqRecentGamesItemLiveData.value _qqRecentGamesItemLiveData.value
} else { } else {
_wechatRecentGamesItemLiveData.value _wechatRecentGamesItemLiveData.value
} ?: emptyList() } ?: let {
loadRecentlyPlayedMiniGameList(gameType)// 最近在玩数据为空时,则调用接口获取数据
emptyList()
}
fun clearRecentlyPlayedMiniGameList(gameType: String = "") { fun clearRecentlyPlayedMiniGameList(gameType: String = "") {
when (gameType) { when (gameType) {

View File

@ -4,7 +4,7 @@ import com.gh.gamecenter.SearchActivity.Companion.TRACK_SEARCH_TYPE_INPUT
import com.gh.gamecenter.common.constant.EntranceConsts import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.viewModelProvider import com.gh.gamecenter.common.utils.viewModelProvider
import com.gh.gamecenter.search.SearchGameResultFragment import com.gh.gamecenter.search.SearchGameResultFragment
import com.gh.gamecenter.search.SearchGameResultViewModel import com.gh.gamecenter.search.viewmodel.SearchGameResultViewModel
import com.halo.assistant.HaloApp import com.halo.assistant.HaloApp
/** /**
@ -19,7 +19,6 @@ class MiniGameSearchResultFragment : SearchGameResultFragment() {
val factory = SearchGameResultViewModel.Factory( val factory = SearchGameResultViewModel.Factory(
HaloApp.getInstance().application, HaloApp.getInstance().application,
mKey, mKey,
true,
MiniGameSearchResultRepository(), MiniGameSearchResultRepository(),
TRACK_SEARCH_TYPE_INPUT, TRACK_SEARCH_TYPE_INPUT,
activity?.intent?.getStringExtra(EntranceConsts.KEY_SOURCE_ENTRANCE) ?: "" activity?.intent?.getStringExtra(EntranceConsts.KEY_SOURCE_ENTRANCE) ?: ""

View File

@ -1,45 +1,25 @@
package com.gh.gamecenter.minigame package com.gh.gamecenter.minigame
import com.gh.gamecenter.dsp.data.GameSubjectDSPRemoteDataSource
import com.gh.gamecenter.entity.SearchSubjectEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.minigame.wechat.WGameSubjectCPMRemoteDataSource
import com.gh.gamecenter.retrofit.RetrofitManager import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.retrofit.service.ApiService import com.gh.gamecenter.retrofit.service.ApiService
import com.gh.gamecenter.search.ISearchGameResultRepository import com.gh.gamecenter.search.ISearchGameResultRepository
import com.gh.gamecenter.search.SearchItemData
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.Single
class MiniGameSearchResultRepository( class MiniGameSearchResultRepository(
private val api: ApiService = RetrofitManager.getInstance().newApi, private val api: ApiService = RetrofitManager.getInstance().newApi
private val mWGameSubjectCPMDataSource: WGameSubjectCPMRemoteDataSource = WGameSubjectCPMRemoteDataSource(),
private val mGameSubjectDSPDataSource: GameSubjectDSPRemoteDataSource = GameSubjectDSPRemoteDataSource(RetrofitManager.getInstance().dspApiService)
) : ISearchGameResultRepository { ) : ISearchGameResultRepository {
override fun getSearchGame( override fun getSearchGame(
key: String?, key: String?,
page: Int page: Int
): Observable<List<GameEntity>> { ): Observable<List<SearchItemData>> {
return api.getSearchMiniGameList(key, page, 20) return api.getSearchMiniGameList(key, page, 20)
.map { data ->
data.map {
SearchItemData(it, type = "")
}
}
} }
override fun getSearchMiniGameCPM(key: String?): Observable<List<GameEntity>> {
return Observable.just(emptyList())
}
override fun getSearchSubject(key: String?, page: Int): Observable<List<SearchSubjectEntity>> {
return Observable.just(emptyList())
}
override fun getWGameCPMGameList(size: Int): Single<MutableList<GameEntity>> {
return mWGameSubjectCPMDataSource.getUserRecommendCPMList(pageSize = size)
}
override fun getDspGameList(
columnType: String,
showDownload: Boolean,
size: Int,
): Single<List<GameEntity>> {
return mGameSubjectDSPDataSource.getDspGames(columnType, size)
}
} }

View File

@ -6,7 +6,6 @@ import com.gh.gamecenter.common.livedata.NonStickyMutableLiveData
import com.gh.gamecenter.common.retrofit.BiResponse import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.feature.entity.GameEntity import com.gh.gamecenter.feature.entity.GameEntity
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers import io.reactivex.schedulers.Schedulers
/** /**
@ -15,7 +14,11 @@ import io.reactivex.schedulers.Schedulers
class WGameSubjectCPMListUseCase( class WGameSubjectCPMListUseCase(
private val repository: WGameSubjectCPMListRepository = WGameSubjectCPMListRepository() private val repository: WGameSubjectCPMListRepository = WGameSubjectCPMListRepository()
) { ) {
private val compositeDisposable = CompositeDisposable()
/**
* 微信专题CPM请求记录用于避免重复请求以专题ID作为Key
*/
private val requestKeyList = mutableListOf<String>()
/** /**
* 微信小游戏CPM列表这里的LiveData充当类似于EventBus的角色因此使用NonStickyMutableLiveData * 微信小游戏CPM列表这里的LiveData充当类似于EventBus的角色因此使用NonStickyMutableLiveData
@ -23,18 +26,21 @@ class WGameSubjectCPMListUseCase(
private val _wechatMiniGameCPMListLiveData = NonStickyMutableLiveData<Pair<String, List<GameEntity>>>() private val _wechatMiniGameCPMListLiveData = NonStickyMutableLiveData<Pair<String, List<GameEntity>>>()
val wechatMiniGameCPMListLiveData: LiveData<Pair<String, List<GameEntity>>> = _wechatMiniGameCPMListLiveData val wechatMiniGameCPMListLiveData: LiveData<Pair<String, List<GameEntity>>> = _wechatMiniGameCPMListLiveData
fun getWechatMiniGameCPMList(componentId: String, minimumSize: Int, onlyFee: Boolean) { @SuppressLint("CheckResult")
fun getWechatMiniGameCPMList(subjectId: String?, minimumSize: Int, onlyFee: Boolean) {
if (subjectId.isNullOrEmpty() || requestKeyList.contains(subjectId)) {
return
}
requestKeyList.add(subjectId)
repository.getColumn(null, 1, null, null, null, null, minimumSize, onlyFee) repository.getColumn(null, 1, null, null, null, null, minimumSize, onlyFee)
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BiResponse<List<GameEntity>>() { .subscribe(object : BiResponse<List<GameEntity>>() {
override fun onSuccess(data: List<GameEntity>) { override fun onSuccess(data: List<GameEntity>) {
_wechatMiniGameCPMListLiveData.value = componentId to data requestKeyList.remove(subjectId)
_wechatMiniGameCPMListLiveData.value = subjectId to data
} }
}).let(compositeDisposable::add) })
}
fun onClear() {
compositeDisposable.clear()
} }
} }

View File

@ -50,6 +50,7 @@ import com.gh.gamecenter.entity.GameColumnCollection;
import com.gh.gamecenter.entity.GameData; import com.gh.gamecenter.entity.GameData;
import com.gh.gamecenter.entity.GameDigestEntity; import com.gh.gamecenter.entity.GameDigestEntity;
import com.gh.gamecenter.entity.GameGuidePopupEntity; import com.gh.gamecenter.entity.GameGuidePopupEntity;
import com.gh.gamecenter.entity.GameServerTestDisplaySetting;
import com.gh.gamecenter.entity.GameServerTestTopGame; import com.gh.gamecenter.entity.GameServerTestTopGame;
import com.gh.gamecenter.entity.GameServerTestV2Entity; import com.gh.gamecenter.entity.GameServerTestV2Entity;
import com.gh.gamecenter.entity.GameVideoInfo; import com.gh.gamecenter.entity.GameVideoInfo;
@ -77,6 +78,7 @@ import com.gh.gamecenter.entity.RatingReplyEntity;
import com.gh.gamecenter.entity.RecommendPopupEntity; import com.gh.gamecenter.entity.RecommendPopupEntity;
import com.gh.gamecenter.entity.ReserveModifyEntity; import com.gh.gamecenter.entity.ReserveModifyEntity;
import com.gh.gamecenter.entity.ReserveReminderEntity; import com.gh.gamecenter.entity.ReserveReminderEntity;
import com.gh.gamecenter.entity.SearchGameUnionEntity;
import com.gh.gamecenter.entity.SearchSubjectEntity; import com.gh.gamecenter.entity.SearchSubjectEntity;
import com.gh.gamecenter.entity.ServerPublishEntity; import com.gh.gamecenter.entity.ServerPublishEntity;
import com.gh.gamecenter.entity.ServerSubscriptionEntity; import com.gh.gamecenter.entity.ServerSubscriptionEntity;
@ -326,6 +328,18 @@ public interface ApiService {
@GET @GET
Observable<List<GameEntity>> getSearchGame(@Url String url); Observable<List<GameEntity>> getSearchGame(@Url String url);
/**
* 搜索游戏(新:整合了 cpm dsp 广告位置排序问题)
*/
@GET("./games:search?view=union")
Observable<List<SearchGameUnionEntity>> getSearchGameUnionData(
@Query("keyword") String keyword,
@Query("page") int page,
@Query("version") String version,
@Query("channel") String channel,
@Query("AD") String ad
);
/** /**
* 搜索CPM微信小游戏 * 搜索CPM微信小游戏
*/ */
@ -3092,6 +3106,12 @@ public interface ApiService {
@GET("columns/tests/v2") @GET("columns/tests/v2")
Observable<GameServerTestV2Entity> getServerTestV2(@Query("filter") String filter); Observable<GameServerTestV2Entity> getServerTestV2(@Query("filter") String filter);
/**
* 新游开测-显示配置
*/
@GET("app/column_test_v2/{link_id}/display_setting")
Single<GameServerTestDisplaySetting> getGameServerTestDisplaySetting(@Path("link_id") String linkId);
/** /**
* 新游开测-详情列表 * 新游开测-详情列表
*/ */

View File

@ -1,18 +1,7 @@
package com.gh.gamecenter.search package com.gh.gamecenter.search
import com.gh.gamecenter.entity.SearchSubjectEntity
import com.gh.gamecenter.feature.entity.GameEntity
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.Single
interface ISearchGameResultRepository { interface ISearchGameResultRepository {
fun getSearchGame(key: String?, page: Int): Observable<List<GameEntity>> fun getSearchGame(key: String?, page: Int): Observable<List<SearchItemData>>
fun getSearchMiniGameCPM(key: String?): Observable<List<GameEntity>>
fun getSearchSubject(key: String?, page: Int): Observable<List<SearchSubjectEntity>>
fun getWGameCPMGameList(size: Int): Single<MutableList<GameEntity>>
fun getDspGameList(columnType: String, showDownload: Boolean, size: Int): Single<List<GameEntity>>
} }

View File

@ -31,6 +31,7 @@ import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.SettingsEntity import com.gh.gamecenter.feature.entity.SettingsEntity
import com.gh.gamecenter.feature.exposure.addExposureHelper import com.gh.gamecenter.feature.exposure.addExposureHelper
import com.gh.gamecenter.help.HelpAndFeedbackBridge import com.gh.gamecenter.help.HelpAndFeedbackBridge
import com.gh.gamecenter.search.viewmodel.SearchGameResultViewModel
import com.halo.assistant.HaloApp import com.halo.assistant.HaloApp
import com.lightgame.download.DataWatcher import com.lightgame.download.DataWatcher
import com.lightgame.download.DownloadEntity import com.lightgame.download.DownloadEntity
@ -67,7 +68,6 @@ class SearchGameIndexFragment : ListFragment<GameEntity, SearchGameResultViewMod
SearchGameResultViewModel.Factory( SearchGameResultViewModel.Factory(
HaloApp.getInstance(), HaloApp.getInstance(),
mKey, mKey,
false,
SearchGameResultRepository(), SearchGameResultRepository(),
mType, mType,
activity?.intent?.getStringExtra(EntranceConsts.KEY_SOURCE_ENTRANCE) ?: "" activity?.intent?.getStringExtra(EntranceConsts.KEY_SOURCE_ENTRANCE) ?: ""
@ -198,7 +198,6 @@ class SearchGameIndexFragment : ListFragment<GameEntity, SearchGameResultViewMod
this.mType = type this.mType = type
mAdapter?.key = key mAdapter?.key = key
mListViewModel?.updateSearchKeyWithType(key, type) mListViewModel?.updateSearchKeyWithType(key, type)
mListViewModel?.clearSearchSubjects()
mListViewModel?.load(LoadType.REFRESH) mListViewModel?.load(LoadType.REFRESH)
} }

View File

@ -1,7 +1,6 @@
package com.gh.gamecenter.search package com.gh.gamecenter.search
import android.content.Context import android.content.Context
import android.util.SparseArray
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
@ -60,6 +59,7 @@ import com.gh.gamecenter.gamedetail.accelerator.dialog.StartingAcceleratorDialog
import com.gh.gamecenter.gamedetail.entity.GameDetailTabEntity import com.gh.gamecenter.gamedetail.entity.GameDetailTabEntity
import com.gh.gamecenter.help.HelpAndFeedbackBridge import com.gh.gamecenter.help.HelpAndFeedbackBridge
import com.gh.gamecenter.minigame.MiniGameSearchResultFragment import com.gh.gamecenter.minigame.MiniGameSearchResultFragment
import com.gh.gamecenter.search.viewmodel.SearchGameResultViewModel
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
import com.lightgame.download.DownloadEntity import com.lightgame.download.DownloadEntity
import com.lightgame.utils.Util_System_Keyboard import com.lightgame.utils.Util_System_Keyboard

View File

@ -13,7 +13,6 @@ import android.view.animation.TranslateAnimation
import android.widget.TextView import android.widget.TextView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.viewModels import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.gh.common.exposure.DefaultExposureStateChangeListener import com.gh.common.exposure.DefaultExposureStateChangeListener
import com.gh.common.util.* import com.gh.common.util.*
@ -41,10 +40,10 @@ import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.eventbus.EBStartupAcceleration import com.gh.gamecenter.eventbus.EBStartupAcceleration
import com.gh.gamecenter.feature.entity.GameEntity import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.addExposureHelper import com.gh.gamecenter.feature.exposure.addExposureHelper
import com.gh.gamecenter.feature.entity.VipEntity
import com.gh.gamecenter.help.HelpAndFeedbackBridge import com.gh.gamecenter.help.HelpAndFeedbackBridge
import com.gh.gamecenter.livedata.EventObserver import com.gh.gamecenter.livedata.EventObserver
import com.gh.gamecenter.minigame.MiniGameSearchResultFragment import com.gh.gamecenter.minigame.MiniGameSearchResultFragment
import com.gh.gamecenter.search.viewmodel.SearchGameResultViewModel
import com.gh.gamecenter.search.viewmodel.SearchTabViewModel import com.gh.gamecenter.search.viewmodel.SearchTabViewModel
import com.halo.assistant.HaloApp import com.halo.assistant.HaloApp
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
@ -126,7 +125,6 @@ open class SearchGameResultFragment : ListFragment<GameEntity, SearchGameResultV
SearchGameResultViewModel.Factory( SearchGameResultViewModel.Factory(
HaloApp.getInstance(), HaloApp.getInstance(),
mKey, mKey,
true,
SearchGameResultRepository(), SearchGameResultRepository(),
mType, mType,
activity?.intent?.getStringExtra(EntranceConsts.KEY_SOURCE_ENTRANCE) ?: "", activity?.intent?.getStringExtra(EntranceConsts.KEY_SOURCE_ENTRANCE) ?: "",
@ -444,7 +442,6 @@ open class SearchGameResultFragment : ListFragment<GameEntity, SearchGameResultV
this.mKey = key this.mKey = key
mAdapter?.key = key mAdapter?.key = key
mAdapter?.clearAdIdSet() mAdapter?.clearAdIdSet()
mListViewModel?.clearSearchSubjects()
mListViewModel?.load(LoadType.REFRESH) mListViewModel?.load(LoadType.REFRESH)
} }

View File

@ -1,88 +1,218 @@
package com.gh.gamecenter.search package com.gh.gamecenter.search
import com.gh.common.constant.Config import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.gh.ad.AdDelegateHelper
import com.gh.common.filter.RegionSettingHelper
import com.gh.common.util.AdHelper import com.gh.common.util.AdHelper
import com.gh.gamecenter.BuildConfig import com.gh.common.util.PackageUtils
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.safelyGetInRelease
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.dsp.data.GameSubjectDSPRemoteDataSource import com.gh.gamecenter.dsp.data.GameSubjectDSPRemoteDataSource
import com.gh.gamecenter.entity.AdConfig
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_AD_SPACE
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_COLUMN
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_DSP_GAME_COLUMN
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_GAME
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_WECHAT_GAME
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_WECHAT_GAME_CPM_COLUMN
import com.gh.gamecenter.entity.SearchSubjectEntity import com.gh.gamecenter.entity.SearchSubjectEntity
import com.gh.gamecenter.feature.entity.GameEntity import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.gamedetail.accelerator.AccelerationDataBase import com.gh.gamecenter.livedata.Event
import com.gh.gamecenter.minigame.wechat.WGameSubjectCPMRemoteDataSource import com.gh.gamecenter.minigame.wechat.WGameSubjectCPMRemoteDataSource
import com.gh.gamecenter.retrofit.RetrofitManager import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.retrofit.service.ApiService import com.gh.gamecenter.retrofit.service.ApiService
import com.halo.assistant.HaloApp import com.halo.assistant.HaloApp
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.Single import io.reactivex.disposables.CompositeDisposable
import io.reactivex.android.schedulers.AndroidSchedulers import java.util.*
import java.net.URLEncoder import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.TimeUnit import kotlin.random.Random
class SearchGameResultRepository( class SearchGameResultRepository(
private val mApi: ApiService = RetrofitManager.getInstance().api, private val mApi: ApiService = RetrofitManager.getInstance().api,
private val mNewApi: ApiService = RetrofitManager.getInstance().newApi, private val mNewApi: ApiService = RetrofitManager.getInstance().newApi,
private val mWGameSubjectCPMDataSource: WGameSubjectCPMRemoteDataSource = WGameSubjectCPMRemoteDataSource(), private val mWGameSubjectCPMDataSource: WGameSubjectCPMRemoteDataSource = WGameSubjectCPMRemoteDataSource(),
private val mGameSubjectDSPDataSource: GameSubjectDSPRemoteDataSource = GameSubjectDSPRemoteDataSource(RetrofitManager.getInstance().dspApiService) private val mGameSubjectDSPDataSource: GameSubjectDSPRemoteDataSource = GameSubjectDSPRemoteDataSource(
RetrofitManager.getInstance().dspApiService
)
) : ISearchGameResultRepository { ) : ISearchGameResultRepository {
private var currentSearchKey: String? = null private val compositeDisposable = CompositeDisposable()
private var currentMiniGameCPMSearchList: MutableList<GameEntity>? = null private val _dataUpdateEvent = MutableLiveData<Event<SearchItemData>>()
val dataUpdateEvent: LiveData<Event<SearchItemData>> = _dataUpdateEvent
override fun getSearchGame( private val adGameOneIdSet = Collections.newSetFromMap(ConcurrentHashMap<String, Boolean>())
key: String?,
page: Int
): Observable<List<GameEntity>> {
// 可能会有特殊字符,需要 encode 处理
val encodedKey = URLEncoder.encode(key, "utf-8")
return mApi.getSearchGame(
Config.API_HOST
+ "games:search?keyword=" + encodedKey
+ "&view=digest&page=" + page
+ "&channel=" + HaloApp.getInstance().channel
+ "&version=" + BuildConfig.VERSION_NAME
+ "&AD=" + AdHelper.getIdfaString()
)
.map { games ->
// 获取已加速成功过的区服
val gameCanSpeed = games.filter { it.canSpeed }
val gameIds = gameCanSpeed
.map { it.id }
val acctGameList = AccelerationDataBase.instance.accelerationDao().queryAcctGameInfoByGameId(gameIds)
gameCanSpeed.forEach { game ->
game.lastAcctGame = acctGameList.find { game.id == it.gameId }
}
games
}
}
override fun getSearchMiniGameCPM(key: String?): Observable<List<GameEntity>> { override fun getSearchGame(key: String?, page: Int): Observable<List<SearchItemData>> {
val currentMiniGameCPMSearchList = currentMiniGameCPMSearchList if (page == 1) {
if (key == currentSearchKey && currentMiniGameCPMSearchList != null) { adGameOneIdSet.clear()
return Observable.just(currentMiniGameCPMSearchList)
} }
return mNewApi.getSearchWechatMiniCPMGame(key) val version = PackageUtils.getGhVersionName()
.timeout(5, TimeUnit.SECONDS) val channel = HaloApp.getInstance().channel
.onErrorReturnItem(emptyList()) return mApi.getSearchGameUnionData(key, page, version, channel, AdHelper.getIdfaString())
.observeOn(AndroidSchedulers.mainThread()) .map { data ->
.doOnNext { data.map {
this.currentSearchKey = key when (it.type) {
this.currentMiniGameCPMSearchList = it TYPE_GAME -> SearchItemData(it.linkGame, type = it.type)
TYPE_WECHAT_GAME_CPM_COLUMN -> {
it.linkWechatGameCpmColumn?.let(::loadThirdPartyData)
SearchItemData(type = it.type, placeHolderId = it.linkWechatGameCpmColumn?.columnId)
}
TYPE_DSP_GAME_COLUMN -> {
it.linkDspGameColumn?.let(::loadThirdPartyData)
SearchItemData(type = it.type, placeHolderId = it.linkDspGameColumn?.columnId)
}
TYPE_WECHAT_GAME -> SearchItemData(game = it.linkWechatGame, type = it.type)
TYPE_COLUMN -> SearchItemData(subject = it.linkColum, type = it.type)
TYPE_AD_SPACE -> {
val adConfig = it.linkAdSpace
if (adConfig != null) {
val shouldShowAd = AdDelegateHelper.shouldShowGameSearchAd(adConfig)
when {
shouldShowAd && showThirdPartyAd(adConfig) -> {
SearchItemData(
ad = adConfig.thirdPartyAd,
adConfig = it.linkAdSpace,
type = TYPE_AD_SPACE
)
}
shouldShowAd && showOwnerAd(adConfig) -> {
loadOwnerAdData(adConfig)
SearchItemData(type = it.type, placeHolderId = getAdPlaceHolderId(adConfig))
}
else -> SearchItemData(type = it.type, placeHolderId = INVALID_PLACEHOLDER_ID)
}
} else {
SearchItemData(type = it.type, placeHolderId = INVALID_PLACEHOLDER_ID)
}
}
else -> {
// 无效数据
SearchItemData(type = it.type, placeHolderId = INVALID_PLACEHOLDER_ID)
}
}
}
} }
} }
override fun getSearchSubject(key: String?, page: Int): Observable<List<SearchSubjectEntity>> { private fun loadThirdPartyData(subject: SearchSubjectEntity) {
return mApi.getSearchSubject(key, page) if (subject.isWGameSubjectCPM) {
mWGameSubjectCPMDataSource.getUserRecommendCPMList(pageSize = subject.size)
} else {
mGameSubjectDSPDataSource.getDspGames(subject.size)
}.compose(singleToMain())
.subscribe(object : BiResponse<List<GameEntity>>() {
override fun onSuccess(data: List<GameEntity>) {
subject.games = data
_dataUpdateEvent.value = Event(SearchItemData(type = subject.type, subject = subject))
}
}).let(compositeDisposable::add)
} }
override fun getWGameCPMGameList(size: Int,): Single<MutableList<GameEntity>> { private fun loadOwnerAdData(adConfig: AdConfig?) {
return mWGameSubjectCPMDataSource.getUserRecommendCPMList(pageSize = size) adConfig ?: return
val pageSize = adConfig.ownerAd?.adSource?.gamesIds?.size ?: 20
val codeId = adConfig.ownerAd?.id
val paramsMap = mapOf("page" to "1,$pageSize", "code_id" to codeId)
mNewApi.getAdGames(adConfig.id, paramsMap)
.map { data ->
data.forEach {
it.run {
adIconActive = adConfig.ownerAd?.adSource?.adIconActive ?: false
adSpaceId = adConfig.id
gameAdSourceId = adConfig.ownerAd?.id ?: ""
}
}
data
}
.compose(singleToMain())
.subscribe(object : BiResponse<List<GameEntity>>() {
override fun onSuccess(data: List<GameEntity>) {
val filterGameList = RegionSettingHelper.filterGame(data)
handleOwnerAdGames(adConfig, filterGameList)
}
override fun onFailure(exception: Exception) {
super.onFailure(exception)
handleOwnerAdGames(adConfig, emptyList())
}
}).let(compositeDisposable::add)
} }
override fun getDspGameList( private fun handleOwnerAdGames(adConfig: AdConfig, games: List<GameEntity>) {
columnType: String, val showAdColumn = adConfig.ownerAd?.adSource?.displayStyle == "game_zone"
showDownload: Boolean, val showOnFailed = adConfig.displayRule.onFailedAction == "show"
size: Int, when {
): Single<List<GameEntity>> { games.isNotEmpty() && showAdColumn -> {
return mGameSubjectDSPDataSource.getDspGames(columnType, size) SearchItemData(
subject = SearchSubjectEntity(
name = adConfig.ownerAd?.adSource?.title ?: "",
games = games.take(AD_SUBJECT_GAME_MAX_COUNT),
adId = adConfig.id,
codeId = adConfig.ownerAd?.id ?: "",
adIconActive = adConfig.ownerAd?.adSource?.adIconActive ?: false
),
adConfig = adConfig,
type = TYPE_AD_SPACE
)
}
games.isNotEmpty() -> {
var randomGame = games.safelyGetInRelease(Random.nextInt(games.size))
if (games.size > 1 && adGameOneIdSet.contains(randomGame?.id)) {
// 存在重复游戏时重新获取随机游戏
randomGame = games.safelyGetInRelease(Random.nextInt(games.size))
}
randomGame?.id?.let(adGameOneIdSet::add)
SearchItemData(game = randomGame, adConfig = adConfig, type = TYPE_AD_SPACE)
}
showOnFailed && adConfig.thirdPartyAd != null -> {
// 自有广告为空时,显示第三方广告
SearchItemData(ad = adConfig.thirdPartyAd, adConfig = adConfig, type = TYPE_AD_SPACE)
}
else -> null
}?.let {
_dataUpdateEvent.value = Event(it)
}
}
fun clear() {
compositeDisposable.clear()
}
companion object {
private const val AD_SUBJECT_GAME_MAX_COUNT = 8
private const val INVALID_PLACEHOLDER_ID = "Invalid"
fun getAdPlaceHolderId(adConfig: AdConfig?) = "${adConfig?.id}-${adConfig?.position}"
fun showThirdPartyAd(adConfig: AdConfig?) =
(adConfig?.displayRule?.adSource == AdDelegateHelper.AD_TYPE_SDK && adConfig.thirdPartyAd != null) ||
(adConfig?.displayRule?.adSource == AdDelegateHelper.AD_TYPE_OWNER &&
adConfig.ownerAd == null &&
adConfig.thirdPartyAd != null &&
adConfig.displayRule.onFailedAction == "show")
fun showOwnerAd(adConfig: AdConfig?) =
(adConfig?.displayRule?.adSource == AdDelegateHelper.AD_TYPE_OWNER &&
adConfig.ownerAd != null) ||
(adConfig?.displayRule?.adSource == AdDelegateHelper.AD_TYPE_SDK &&
adConfig.thirdPartyAd == null &&
adConfig.ownerAd != null &&
adConfig.displayRule.onFailedAction == "show")
} }
} }

View File

@ -1,469 +0,0 @@
package com.gh.gamecenter.search
import android.annotation.SuppressLint
import android.app.Application
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.gh.ad.AdDelegateHelper
import com.gh.common.filter.RegionSettingHelper
import com.gh.common.util.PackageHelper
import com.gh.gamecenter.SearchActivity
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.baselist.ListViewModel
import com.gh.gamecenter.common.baselist.LoadParams
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.safelyGetInRelease
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.common.utils.toArrayList
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.entity.AdConfig
import com.gh.gamecenter.entity.SearchSubjectEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.livedata.Event
import com.gh.gamecenter.minigame.MiniGameSearchResultRepository
import com.gh.gamecenter.retrofit.RetrofitManager
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import java.util.concurrent.ConcurrentHashMap
import kotlin.random.Random
class SearchGameResultViewModel(
application: Application,
private var mSearchKey: String?,
private var mIsManuallySearch: Boolean,
private val repository: ISearchGameResultRepository,
private var mSearchType: String,
val sourceEntrance: String
) : ListViewModel<GameEntity, SearchItemData>(application) {
private var mPage = 0
private var mSearchSubjects = arrayListOf<SearchSubjectEntity>()
// 游戏广告列表,需要先本地按插入 position 排序,避免后续问题
private var mGameSearchAdList: List<AdConfig>? = null
private var mAdPositionSet: HashSet<Int>? = null
private val mAdGameMap = ConcurrentHashMap<String, List<GameEntity>>()
private var mGameEntityList = arrayListOf<GameEntity>()
fun updateSearchKeyWithType(searchKey: String, searchType: String) {
mSearchKey = searchKey
mSearchType = searchType
}
fun clearSearchSubjects() {
mSearchSubjects.clear()
}
override fun mergeResultLiveData() {
mResultLiveData.addSource(mListLiveData) { list ->
mGameEntityList = ArrayList(list)
decorateListAndPost(list)
}
}
@SuppressLint("CheckResult")
private fun decorateListAndPost(list: MutableList<GameEntity>) {
val itemDataList = ArrayList<SearchItemData>()
val combineGameList = list.toMutableList()
refreshWrongInstallStatus()
repository.getSearchMiniGameCPM(mSearchKey)
.zipWith(repository.getSearchSubject(mSearchKey, mPage)) { cpmGameList, subjectList -> // CPM游戏搜索结果列表合并
for (cpmGame in cpmGameList) {
if (cpmGame.location <= 0 || cpmGame.location > list.size) {
combineGameList.add(cpmGame)
} else {
combineGameList.add(cpmGame.location - 1, cpmGame)
}
}
itemDataList.addAll(
combineGameList.mapIndexed { index, game ->
SearchItemData(game = game, gamePosition = index, isFirst = index == 0)
}
)
subjectList
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ mutableList ->
// 数据源来自于第三方的专题列表,包括 CPM 专题和 DSP 专题
val thirdPartySearchSubjectList = mutableListOf<SearchSubjectEntity>()
for (item in mutableList) {
// 避免同一个位置重复的专题
if (!mSearchSubjects.any {
it.location == item.location && it.columnId == item.columnId
}) {
mSearchSubjects.add(item)
}
}
mSearchSubjects.forEach {
if (it.type == SearchSubjectEntity.TYPE_WECHAT_GAME_CPM_COLUMN) {
thirdPartySearchSubjectList.add(it.apply { isWGameSubjectCPM = true })
} else if (it.type == SearchSubjectEntity.TYPE_DSP_GAME_COLUMN) {
thirdPartySearchSubjectList.add(it.apply { isDspSubject = true })
}
val item = SearchItemData(subject = it)
if (it.location <= 0 || it.location > itemDataList.size) {
itemDataList.add(item)
} else {
itemDataList.add(it.location - 1, item)
}
}
// 处理初始化列表且游戏列表size为0的情况
handleLoadStatusWhenGameListIsEmpty(list, itemDataList)
if (mIsManuallySearch) {
if (mSearchKey == AdDelegateHelper.gameSearchKeyword) {
updateAdConfigAndDecorateList(itemDataList, list)
} else {
AdDelegateHelper.requestAdConfig(false, mSearchKey ?: "") {
updateAdConfigAndDecorateList(itemDataList, list)
}
}
} else {
postResultList(itemDataList, list)
}
if (thirdPartySearchSubjectList.isNotEmpty()) {
decorateListWithThirdPartyData(thirdPartySearchSubjectList, itemDataList, list)
}
}, {
it.printStackTrace()
handleLoadStatusWhenGameListIsEmpty(list, itemDataList)
mResultLiveData.postValue(itemDataList)
})
}
/**
* 请求第三方接口数据来填充专题游戏数据,包含 DSP 专题和 CPM 专题
*/
@SuppressLint("CheckResult")
private fun decorateListWithThirdPartyData(
subjects: List<SearchSubjectEntity>,
itemDataList: ArrayList<SearchItemData>,
list: MutableList<GameEntity>
) {
val subjectList = subjects.filterNot { it.games.isNotEmpty() }
if (subjectList.isEmpty()) return
val subjectSingleList = subjectList.map { subject ->
if (subject.isWGameSubjectCPM) {
repository.getWGameCPMGameList(subject.size)
.onErrorReturnItem(mutableListOf())
.map { subject.columnId to it }
} else {
repository.getDspGameList(subject.columnType, subject.showDownload, subject.size)
.onErrorReturnItem(mutableListOf())
.map { subject.columnId to it }
}
}
Single.zip(subjectSingleList) { it.map { item -> item as Pair<String, MutableList<GameEntity>> } }
.map { dataList ->
for (index in itemDataList.indices) {
val itemData = itemDataList[index]
val subject = itemData.subject ?: continue
if (subject.games.isNotEmpty()) continue
val pair = dataList.firstOrNull { data ->
data.first == subject.columnId
} ?: continue
val newItemData = SearchItemData(
subject = subject.copy(games = pair.second)
)
itemDataList[index] = newItemData
}
itemDataList
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ dataList -> postResultList(dataList, list) }, {})
}
@SuppressLint("CheckResult")
private fun updateAdConfigAndDecorateList(itemDataList: ArrayList<SearchItemData>, list: MutableList<GameEntity>) {
mGameSearchAdList =
AdDelegateHelper.getGameSearchAdList().filter { AdDelegateHelper.shouldShowGameSearchAd(it) }.toArrayList()
.apply { sortBy { it.position } }
val adPositionSet = hashSetOf<Int>()
if (!mGameSearchAdList.isNullOrEmpty()) {
for (config in mGameSearchAdList!!) {
adPositionSet.add(config.position)
}
}
mAdPositionSet = adPositionSet
if (adPositionSet.isNotEmpty()) {
val ownerAdList = arrayListOf<AdConfig>()
val thirdPartyAdList = arrayListOf<AdConfig>()
// 位置已经排过序,若插入位置大于列表的 size 那么直接 break 放弃
for ((index, position) in adPositionSet.withIndex()) {
if (position < itemDataList.size + index + 1) {
val adConfig = mGameSearchAdList!!.safelyGetInRelease(index)
val showThirdPartyAd = adConfig?.displayRule?.adSource == AdDelegateHelper.AD_TYPE_SDK
val showOwnerAd = adConfig?.displayRule?.adSource == AdDelegateHelper.AD_TYPE_OWNER
val showOnFailed = adConfig?.displayRule?.onFailedAction == "show"
if ((showThirdPartyAd && adConfig?.thirdPartyAd != null)
|| (showOwnerAd && adConfig?.ownerAd == null && adConfig?.thirdPartyAd != null && showOnFailed)
) {
thirdPartyAdList.add(adConfig)
} else if ((showOwnerAd && adConfig?.ownerAd != null)
|| (showThirdPartyAd && adConfig?.ownerAd != null && showOnFailed)
) {
ownerAdList.add(adConfig)
}
} else {
break
}
}
if (ownerAdList.isNotEmpty()) {
// 存在自有广告时获取对应广告ID的游戏
val requestSingleList = arrayListOf<Single<List<GameEntity>>>()
ownerAdList.forEach {
// 没有对应广告ID的游戏数据时才添加到请求列表
if (mAdGameMap[it.id] == null) {
requestSingleList.add(createAdGameListSingle(it))
}
}
if (requestSingleList.isNotEmpty()) {
Single.zip(requestSingleList) {}
.compose(singleToMain())
.subscribe({
decorateListWithAd(itemDataList, list)
}, {
decorateListWithAd(itemDataList, list)
})
} else {
decorateListWithAd(itemDataList, list)
}
} else {
decorateListWithThirdPartyAdOnly(itemDataList, thirdPartyAdList, list)
}
} else {
postResultList(itemDataList, list)
}
}
fun refreshWrongInstallStatus() {
if (mGameEntityList.isNotEmpty()) {
PackageHelper.refreshWrongInstallStatus(mGameEntityList)
}
}
private fun decorateListWithThirdPartyAdOnly(
itemDataList: ArrayList<SearchItemData>,
thirdPartyAdList: List<AdConfig>,
list: List<GameEntity>
) {
thirdPartyAdList.forEach {
itemDataList.add(it.position - 1, SearchItemData(ad = it.thirdPartyAd, adConfig = it))
SPUtils.setLong(Constants.SP_LAST_GAME_SEARCH_AD_SHOW_TIME + it.position, System.currentTimeMillis())
}
postResultList(itemDataList, list)
}
private fun decorateListWithAd(
itemDataList: ArrayList<SearchItemData>,
list: List<GameEntity>
) {
val adGameOneIdSet = HashSet<String>() // 展示样式为单个游戏时记录游戏ID避免重复
val decoratedItemDataSize = itemDataList.size
for ((index, position) in mAdPositionSet!!.withIndex()) {
if (position < decoratedItemDataSize + index + 1) {
val adConfig = mGameSearchAdList!!.safelyGetInRelease(index)
val showThirdPartyAd = adConfig?.displayRule?.adSource == AdDelegateHelper.AD_TYPE_SDK
val showOwnerAd = adConfig?.displayRule?.adSource == AdDelegateHelper.AD_TYPE_OWNER
val showOnFailed = adConfig?.displayRule?.onFailedAction == "show"
if ((showThirdPartyAd && adConfig?.thirdPartyAd != null)
|| (showOwnerAd && adConfig?.ownerAd == null && adConfig?.thirdPartyAd != null && showOnFailed)
) {
itemDataList.add(position - 1, SearchItemData(ad = adConfig.thirdPartyAd, adConfig = adConfig))
SPUtils.setLong(
Constants.SP_LAST_GAME_SEARCH_AD_SHOW_TIME + adConfig.position,
System.currentTimeMillis()
)
} else if ((showOwnerAd && adConfig?.ownerAd != null)
|| (showThirdPartyAd && adConfig?.ownerAd != null && showOnFailed)
) {
val gameList = mAdGameMap[adConfig.id]
if (!gameList.isNullOrEmpty()) {
if (adConfig.ownerAd.adSource?.displayStyle == "game_zone") {
// 游戏专题
itemDataList.add(
position - 1,
SearchItemData(
subject = SearchSubjectEntity(
name = adConfig.ownerAd.adSource?.title ?: "",
games = gameList.take(AD_SUBJECT_GAME_MAX_COUNT),
adId = adConfig.id,
codeId = adConfig.ownerAd.id,
adIconActive = adConfig.ownerAd.adSource?.adIconActive ?: false
),
adConfig = adConfig
)
)
} else {
// 单个游戏
var randomGameEntity = gameList.safelyGetInRelease(Random.nextInt(gameList.size))
if (gameList.size != 1 && adGameOneIdSet.contains(randomGameEntity?.id)) {
// 存在重复游戏时重新获取随机游戏
randomGameEntity = gameList.safelyGetInRelease(Random.nextInt(gameList.size))
}
randomGameEntity?.id?.let { adGameOneIdSet.add(it) }
itemDataList.add(
position - 1,
SearchItemData(game = randomGameEntity, adConfig = adConfig)
)
}
SPUtils.setLong(
Constants.SP_LAST_GAME_SEARCH_AD_SHOW_TIME + adConfig.position,
System.currentTimeMillis()
)
} else if (showOnFailed && adConfig.thirdPartyAd != null) {
// 自有广告为空时,显示第三方广告
itemDataList.add(position - 1, SearchItemData(ad = adConfig.thirdPartyAd, adConfig = adConfig))
SPUtils.setLong(
Constants.SP_LAST_GAME_SEARCH_AD_SHOW_TIME + adConfig.position,
System.currentTimeMillis()
)
}
}
} else {
break
}
}
postResultList(itemDataList, list)
}
@SuppressLint("CheckResult")
private fun createAdGameListSingle(adConfig: AdConfig) = Single.create { emitter ->
val pageSize = adConfig.ownerAd?.adSource?.gamesIds?.size ?: 20
val codeId = adConfig.ownerAd?.id
val paramsMap = mapOf("page" to "1,$pageSize", "code_id" to codeId)
RetrofitManager.getInstance().newApi.getAdGames(adConfig.id, paramsMap)
.compose(singleToMain())
.subscribe({ data ->
val filterGameList = RegionSettingHelper.filterGame(data)
filterGameList.forEach {
it.run {
adIconActive = adConfig.ownerAd?.adSource?.adIconActive ?: false
adSpaceId = adConfig.id
gameAdSourceId = adConfig.ownerAd?.id ?: ""
}
}
mAdGameMap[adConfig.id] = filterGameList
emitter.onSuccess(filterGameList)
}, {
mAdGameMap[adConfig.id] = emptyList()
emitter.onSuccess(emptyList<GameEntity>())
})
}
private fun postResultList(resultList: ArrayList<SearchItemData>, list: List<GameEntity>) {
mResultLiveData.postValue(resultList.toMutableList())
if (mPage == 1) {
if (repository is MiniGameSearchResultRepository) {
SensorsBridge.trackMiniGameSearchResultReturn(
GlobalActivityManager.getCurrentPageEntity().pageId,
GlobalActivityManager.getCurrentPageEntity().pageName,
sourceEntrance,
mSearchKey ?: "",
SearchActivity.toTrackSearchType(mSearchType),
list.isNotEmpty()
)
} else {
SensorsBridge.trackGameSearchResultReturn(
GlobalActivityManager.getCurrentPageEntity().pageId,
GlobalActivityManager.getCurrentPageEntity().pageName,
sourceEntrance,
mSearchKey ?: "",
SearchActivity.toTrackSearchType(mSearchType),
list.isNotEmpty()
)
}
}
}
private fun handleLoadStatusWhenGameListIsEmpty(list: List<GameEntity>, itemDataList: ArrayList<SearchItemData>) {
if (mPage == 1 && list.isEmpty()) {
mLoadStatusLiveData.value = if (itemDataList.isEmpty()) LoadStatus.INIT_EMPTY else LoadStatus.INIT_OVER
}
}
override fun loadStatusControl(size: Int) {
if (mCurLoadParams.loadOffset == LoadParams.DEFAULT_OFFSET) { // 初始化列表
if (size == 0) {
// 初始化列表size为0情况放到mergeResultLiveData方法处理
} else if (size == REQUEST_FAILURE_SIZE) {
mLoadStatusLiveData.setValue(LoadStatus.INIT_FAILED)
} else if (size < mOverLimitSize) { // 避免一个屏幕出现两次分页
mLoadStatusLiveData.setValue(LoadStatus.INIT_OVER)
} else {
mLoadStatusLiveData.setValue(LoadStatus.INIT_LOADED)
}
} else {
if (size == REQUEST_FAILURE_SIZE) {
mLoadStatusLiveData.setValue(LoadStatus.LIST_FAILED)
} else if (size == 0) {
mLoadStatusLiveData.setValue(LoadStatus.LIST_OVER)
} else {
mLoadStatusLiveData.setValue(LoadStatus.LIST_LOADED)
}
}
if (size == REQUEST_FAILURE_SIZE) {
mRetryParams = mCurLoadParams
} else {
mRetryParams = null
mCurLoadParams.loadOffset = mCurLoadParams.loadOffset + 1 // 页数 + 1
}
}
override fun provideDataObservable(page: Int): Observable<List<GameEntity>> {
mPage = page
return repository.getSearchGame(mSearchKey, page)
}
private val _refreshVipLoading = MutableLiveData<Event<Boolean>>()
val refreshVipLoading: LiveData<Event<Boolean>> = _refreshVipLoading
fun showRefreshVipLoading(show: Boolean) {
_refreshVipLoading.value = Event(show)
}
class Factory(
private val mApplication: Application,
private val mSearchKey: String?,
private val mIsManuallySearch: Boolean,
private val repository: ISearchGameResultRepository,
private val mSearchType: String,
private val mSourceEntrance: String
) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return SearchGameResultViewModel(
mApplication,
mSearchKey,
mIsManuallySearch,
repository,
mSearchType,
mSourceEntrance
) as T
}
}
companion object {
const val AD_SUBJECT_GAME_MAX_COUNT = 8
}
}

View File

@ -1,20 +1,40 @@
package com.gh.gamecenter.search package com.gh.gamecenter.search
import com.gh.gamecenter.entity.AdConfig import com.gh.gamecenter.entity.AdConfig
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_AD_SPACE
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_DSP_GAME_COLUMN
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_WECHAT_GAME_CPM_COLUMN
import com.gh.gamecenter.entity.SearchSubjectEntity import com.gh.gamecenter.entity.SearchSubjectEntity
import com.gh.gamecenter.feature.entity.GameEntity import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent import com.gh.gamecenter.feature.exposure.ExposureEvent
class SearchItemData( data class SearchItemData(
val game: GameEntity? = null, val game: GameEntity? = null,
val subject: SearchSubjectEntity? = null, val subject: SearchSubjectEntity? = null,
val ad: AdConfig.ThirdPartyAd? = null, val ad: AdConfig.ThirdPartyAd? = null,
val adConfig: AdConfig? = null, val adConfig: AdConfig? = null,
val gamePosition: Int = -1, var gamePosition: Int = -1,
var exposureEventList: ArrayList<ExposureEvent>? = null, var exposureEventList: ArrayList<ExposureEvent>? = null,
private val isFirst: Boolean = false, private val isFirst: Boolean = false,
val type: String,
var placeHolderId: String? = null // 如果当前itemBeam还未填充实际数据仅做占位作用placeHolderId会被赋值作为实际数据填充的唯一标识当实际数据填充后placeHolderId置为空
) { ) {
val isShowFirstSetting: Boolean val isShowFirstSetting: Boolean
get() = isFirst && game?.firstSetting != null && !game.firstSetting?.firstPosition.isNullOrBlank() get() = isFirst && game?.firstSetting != null && !game.firstSetting?.firstPosition.isNullOrBlank()
val isPlaceHolder: Boolean
get() = placeHolderId != null
fun matchPlaceHolder(other: SearchItemData) =
if (type == other.type) {
when (type) {
TYPE_WECHAT_GAME_CPM_COLUMN, TYPE_DSP_GAME_COLUMN -> placeHolderId == other.subject?.columnId
TYPE_AD_SPACE -> placeHolderId == SearchGameResultRepository.getAdPlaceHolderId(other.adConfig)
else -> false
}
} else {
false
}
} }

View File

@ -0,0 +1,255 @@
package com.gh.gamecenter.search.viewmodel
import android.app.Application
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.gh.common.util.PackageHelper
import com.gh.gamecenter.SearchActivity
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.baselist.ListViewModel
import com.gh.gamecenter.common.baselist.LoadParams
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.observableToMain
import com.gh.gamecenter.core.provider.IHandleGameResponseProvider
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_GAME
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_WECHAT_GAME
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.livedata.Event
import com.gh.gamecenter.livedata.EventObserver
import com.gh.gamecenter.minigame.MiniGameSearchResultRepository
import com.gh.gamecenter.search.ISearchGameResultRepository
import com.gh.gamecenter.search.SearchGameResultRepository
import com.gh.gamecenter.search.SearchItemData
import com.therouter.TheRouter.get
import io.reactivex.Observable
import retrofit2.HttpException
class SearchGameResultViewModel(
application: Application,
private var mSearchKey: String?,
private val repository: ISearchGameResultRepository,
private var mSearchType: String,
val sourceEntrance: String
) : ListViewModel<SearchItemData, SearchItemData>(application) {
private var mPage = 0
private var gameList = arrayListOf<GameEntity>()
private val dataUpdateObserver = EventObserver<SearchItemData> {
notifyItemChanged(it)
}
private var originalDataList = arrayListOf<SearchItemData>()
init {
if (repository is SearchGameResultRepository) {
repository.dataUpdateEvent.observeForever(dataUpdateObserver)
}
}
fun updateSearchKeyWithType(searchKey: String, searchType: String) {
mSearchKey = searchKey
mSearchType = searchType
}
override fun loadData() {
if (mCurLoadParams == null) initLoadParams()
val loadParams = if (mRetryParams != null) mRetryParams else mCurLoadParams
val curStatus = mLoadStatusLiveData.value
if (curStatus != null && curStatus != LoadStatus.INIT_LOADED && curStatus != LoadStatus.LIST_LOADED && curStatus != LoadStatus.INIT) return
if (mCurLoadParams.loadOffset == LoadParams.DEFAULT_OFFSET) {
mLoadStatusLiveData.setValue(LoadStatus.INIT_LOADING)
} else {
mLoadStatusLiveData.setValue(LoadStatus.LIST_LOADING)
}
mPage = loadParams.loadOffset
repository.getSearchGame(mSearchKey, loadParams.loadOffset)
.compose(observableToMain())
.subscribe(object : Response<List<SearchItemData>>() {
override fun onResponse(response: List<SearchItemData>?) {
handleSuccess(response ?: emptyList(), curStatus)
}
override fun onFailure(e: HttpException?) {
e?.let(::handleFailure)
}
})
}
private fun handleSuccess(response: List<SearchItemData>, curStatus: LoadStatus?) {
if (mCurLoadParams.loadOffset == LoadParams.DEFAULT_OFFSET || curStatus == LoadStatus.INIT) {
originalDataList.clear()
}
// 针对游戏的一些操作(过滤隐藏游戏过滤隐藏APK吗增加下载数据)
val handleGameResponse = get(IHandleGameResponseProvider::class.java)
val gameList = handleGameResponse?.handleGameResponse(response, mEntrance) ?: response
originalDataList.addAll(gameList as List<SearchItemData>)
mListLiveData.postValue(originalDataList)
loadStatusControl(response.size)
}
private fun handleFailure(exception: Exception) {
if (exception is HttpException && exception.code() == 404) {
loadStatusControl(0)
mLoadStatusLiveData.value = LoadStatus.INIT_EXCEPTION
mLoadExceptionLiveData.postValue(exception)
} else {
loadStatusControl(REQUEST_FAILURE_SIZE)
if (exception is HttpException) {
mLoadExceptionLiveData.postValue(exception)
}
}
}
override fun mergeResultLiveData() {
mResultLiveData.addSource(mListLiveData) { list ->
val data = arrayListOf<SearchItemData>()
var gamePosition = 0
gameList.clear()
originalDataList.forEach {
if (it.type == TYPE_GAME || it.type == TYPE_WECHAT_GAME) {
it.gamePosition = gamePosition
gamePosition++
it.game?.let(gameList::add)
}
if (!it.isPlaceHolder) {
data.add(it)
}
}
refreshWrongInstallStatus()
if (data.isEmpty()) {
handleLoadStatusWhenGameListIsEmpty(data, list)
} else {
mResultLiveData.postValue(data)
}
if (mPage == 1) {
if (repository is MiniGameSearchResultRepository) {
SensorsBridge.trackMiniGameSearchResultReturn(
GlobalActivityManager.getCurrentPageEntity().pageId,
GlobalActivityManager.getCurrentPageEntity().pageName,
sourceEntrance,
mSearchKey ?: "",
SearchActivity.toTrackSearchType(mSearchType),
list.isNotEmpty()
)
} else {
SensorsBridge.trackGameSearchResultReturn(
GlobalActivityManager.getCurrentPageEntity().pageId,
GlobalActivityManager.getCurrentPageEntity().pageName,
sourceEntrance,
mSearchKey ?: "",
SearchActivity.toTrackSearchType(mSearchType),
list.isNotEmpty()
)
}
}
}
}
private fun notifyItemChanged(newItem: SearchItemData) {
val positionList = arrayListOf<Int>()
originalDataList.forEachIndexed { index, item ->
if (item.matchPlaceHolder(newItem)) {
positionList.add(index)
}
}
positionList.forEach {
originalDataList[it] = newItem
}
val displayItems = originalDataList.filterNot { it.isPlaceHolder }
mResultLiveData.postValue(displayItems)
}
fun refreshWrongInstallStatus() {
if (gameList.isNotEmpty()) {
PackageHelper.refreshWrongInstallStatus(gameList)
}
}
private fun handleLoadStatusWhenGameListIsEmpty(list: List<SearchItemData>, itemDataList: List<SearchItemData>) {
if (mPage == 1 && list.isEmpty()) {
mLoadStatusLiveData.value = if (itemDataList.isEmpty()) LoadStatus.INIT_EMPTY else LoadStatus.INIT_OVER
}
}
override fun loadStatusControl(size: Int) {
if (mCurLoadParams.loadOffset == LoadParams.DEFAULT_OFFSET) { // 初始化列表
if (size == 0) {
// 初始化列表size为0情况放到mergeResultLiveData方法处理
} else if (size == REQUEST_FAILURE_SIZE) {
mLoadStatusLiveData.setValue(LoadStatus.INIT_FAILED)
} else if (size < mOverLimitSize) { // 避免一个屏幕出现两次分页
mLoadStatusLiveData.setValue(LoadStatus.INIT_OVER)
} else {
mLoadStatusLiveData.setValue(LoadStatus.INIT_LOADED)
}
} else {
if (size == REQUEST_FAILURE_SIZE) {
mLoadStatusLiveData.setValue(LoadStatus.LIST_FAILED)
} else if (size == 0) {
mLoadStatusLiveData.setValue(LoadStatus.LIST_OVER)
} else {
mLoadStatusLiveData.setValue(LoadStatus.LIST_LOADED)
}
}
if (size == REQUEST_FAILURE_SIZE) {
mRetryParams = mCurLoadParams
} else {
mRetryParams = null
mCurLoadParams.loadOffset = mCurLoadParams.loadOffset + 1 // 页数 + 1
}
}
override fun provideDataObservable(page: Int): Observable<List<SearchItemData>>? = null
private val _refreshVipLoading = MutableLiveData<Event<Boolean>>()
val refreshVipLoading: LiveData<Event<Boolean>> = _refreshVipLoading
fun showRefreshVipLoading(show: Boolean) {
_refreshVipLoading.value = Event(show)
}
override fun onCleared() {
super.onCleared()
if (repository is SearchGameResultRepository) {
repository.dataUpdateEvent.removeObserver(dataUpdateObserver)
}
}
class Factory(
private val mApplication: Application,
private val mSearchKey: String?,
private val repository: ISearchGameResultRepository,
private val mSearchType: String,
private val mSourceEntrance: String
) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return SearchGameResultViewModel(
mApplication,
mSearchKey,
repository,
mSearchType,
mSourceEntrance
) as T
}
}
}

View File

@ -32,6 +32,7 @@ class GameServerTestV2Fragment : LazyFragment() {
private var mSettingBinding: LayoutGameServerTestV2SettingBinding? = null private var mSettingBinding: LayoutGameServerTestV2SettingBinding? = null
private var mCurrentFragment: GameServerTestV2ListFragment? = null private var mCurrentFragment: GameServerTestV2ListFragment? = null
private var mLinkText = "" private var mLinkText = ""
private var mLinkId = ""
private var mPageLocation: PageLocation? = null private var mPageLocation: PageLocation? = null
private var mIsSettingAnimating = false private var mIsSettingAnimating = false
@ -39,6 +40,7 @@ class GameServerTestV2Fragment : LazyFragment() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
mLinkId = arguments?.getString(EntranceConsts.KEY_ID, "none") ?: "none"
mLinkText = requireArguments().getString(EntranceConsts.KEY_NAME, "") mLinkText = requireArguments().getString(EntranceConsts.KEY_NAME, "")
val topTabIndex = requireArguments().getInt(EntranceConsts.KEY_TAB_INDEX, -1) val topTabIndex = requireArguments().getInt(EntranceConsts.KEY_TAB_INDEX, -1)
val bottomTabIndex = requireArguments().getInt(EntranceConsts.KEY_BOTTOM_TAB_INDEX, -1) val bottomTabIndex = requireArguments().getInt(EntranceConsts.KEY_BOTTOM_TAB_INDEX, -1)
@ -64,22 +66,40 @@ class GameServerTestV2Fragment : LazyFragment() {
} }
override fun onFragmentFirstVisible() { override fun onFragmentFirstVisible() {
mViewModel = viewModelProviderFromParent() mViewModel = viewModelProviderFromParent(GameServerTestV2ViewModel.Factory(mLinkId))
super.onFragmentFirstVisible() super.onFragmentFirstVisible()
mViewModel?.run { mViewModel?.run {
mBinding?.filterView?.setItemList(timeFilterList, DEFAULT_TIME_FILTER_INDEX) displaySettingLiveData.observe(viewLifecycleOwner) {
selectedTimeFilterLiveData.observe(this@GameServerTestV2Fragment) { mBinding?.filterView?.setItemList(timeFilterList, DEFAULT_TIME_FILTER_INDEX)
mViewModel?.selectedTimeFilterLiveData?.value = mViewModel?.timeFilterList?.getOrNull(DEFAULT_TIME_FILTER_INDEX) ?: ""
mBinding?.filterView?.isVisible = true
mBinding?.optionIv?.setOnClickListener {
showSettingView()
SensorsBridge.trackColumnTestClick(
location = "详情页",
recommendType = mLinkText,
text = "设置",
bottomTab = mPageLocation?.bottomTab,
multiTabName = mPageLocation?.severalTabPageName,
multiTabId = mPageLocation?.severalTabPageId,
position = mPageLocation?.tabPosition,
tabContent = mPageLocation?.tabContent
)
}
mCurrentFragment?.loadData()
if (SPUtils.getBoolean(Constants.SP_SHOW_GAME_SERVER_TEST_V2_SETTING_GUIDE, true)) {
mBaseHandler.post {
showGuide()
}
SPUtils.setBoolean(Constants.SP_SHOW_GAME_SERVER_TEST_V2_SETTING_GUIDE, false)
}
}
selectedTimeFilterLiveData.observe(viewLifecycleOwner) {
mBinding?.filterView?.setCurrentItem(timeFilterList.indexOf(it)) mBinding?.filterView?.setCurrentItem(timeFilterList.indexOf(it))
} }
} }
changeFragment() changeFragment()
if (SPUtils.getBoolean(Constants.SP_SHOW_GAME_SERVER_TEST_V2_SETTING_GUIDE, true)) {
mBaseHandler.post {
showGuide()
}
SPUtils.setBoolean(Constants.SP_SHOW_GAME_SERVER_TEST_V2_SETTING_GUIDE, false)
}
} }
override fun initRealView() { override fun initRealView() {
@ -89,7 +109,7 @@ class GameServerTestV2Fragment : LazyFragment() {
filterContainer.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_background.toColor(requireContext())) filterContainer.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_background.toColor(requireContext()))
filterView.onCheckedAction = { index -> filterView.onCheckedAction = { index ->
mViewModel?.let { mViewModel?.let {
it.setSelectedTimeFilter(it.timeFilterList[index]) it.setSelectedTimeFilter(it.timeFilterList.getOrNull(index))
mCurrentFragment?.changeTimeFilter() mCurrentFragment?.changeTimeFilter()
SensorsBridge.trackColumnTestClick( SensorsBridge.trackColumnTestClick(
location = "详情页", location = "详情页",
@ -100,7 +120,7 @@ class GameServerTestV2Fragment : LazyFragment() {
multiTabId = mPageLocation?.severalTabPageId, multiTabId = mPageLocation?.severalTabPageId,
position = mPageLocation?.tabPosition, position = mPageLocation?.tabPosition,
tabContent = mPageLocation?.tabContent, tabContent = mPageLocation?.tabContent,
buttonType = it.timeFilterList[index] buttonType = it.timeFilterList.getOrNull(index) ?: ""
) )
} }
} }
@ -124,19 +144,6 @@ class GameServerTestV2Fragment : LazyFragment() {
) )
} }
} }
optionIv.setOnClickListener {
showSettingView()
SensorsBridge.trackColumnTestClick(
location = "详情页",
recommendType = mLinkText,
text = "设置",
bottomTab = mPageLocation?.bottomTab,
multiTabName = mPageLocation?.severalTabPageName,
multiTabId = mPageLocation?.severalTabPageId,
position = mPageLocation?.tabPosition,
tabContent = mPageLocation?.tabContent
)
}
} }
} }
@ -190,7 +197,7 @@ class GameServerTestV2Fragment : LazyFragment() {
val isCategoryFilterChanged = selectedCategoryFilterSet != tempCategoryFilterSet val isCategoryFilterChanged = selectedCategoryFilterSet != tempCategoryFilterSet
selectedCategoryFilterSet = HashSet(tempCategoryFilterSet) selectedCategoryFilterSet = HashSet(tempCategoryFilterSet)
SPUtils.setStringSet(Constants.SP_GAME_SERVER_TEST_V2_CATEGORY_SET, selectedCategoryFilterSet) SPUtils.setStringSet(Constants.SP_GAME_SERVER_TEST_V2_CATEGORY_SET, selectedCategoryFilterSet)
setSelectedTimeFilter(GameServerTestV2ViewModel.TODAY_GAME) setSelectedTimeFilter(timeFilterList.getOrNull(1))
if (isCategoryFilterChanged) { if (isCategoryFilterChanged) {
mCurrentFragment?.changeCategoryFilter() mCurrentFragment?.changeCategoryFilter()
} }
@ -212,8 +219,8 @@ class GameServerTestV2Fragment : LazyFragment() {
} }
val itemList = arrayListOf<TextView>() val itemList = arrayListOf<TextView>()
val allItem = getItemTextView("全部") val allItem = getItemTextView(GameServerTestV2ViewModel.GameCategory.All.displayName)
allItem.tag = "全部" allItem.tag = GameServerTestV2ViewModel.GameCategory.All.displayName
binding.flexbox.addView(allItem) binding.flexbox.addView(allItem)
toggleHighlightedTextView(allItem, mViewModel?.tempCategoryFilterSet.isNullOrEmpty()) toggleHighlightedTextView(allItem, mViewModel?.tempCategoryFilterSet.isNullOrEmpty())
allItem.setOnClickListener { allItem.setOnClickListener {
@ -338,7 +345,6 @@ class GameServerTestV2Fragment : LazyFragment() {
} }
private fun changeFragment() { private fun changeFragment() {
mViewModel?.selectedTimeFilterLiveData?.value = mViewModel?.timeFilterList?.get(DEFAULT_TIME_FILTER_INDEX) ?: ""
mCurrentFragment = mCurrentFragment =
childFragmentManager.findFragmentByTag(GameServerTestV2ViewModel::class.java.name) as? GameServerTestV2ListFragment childFragmentManager.findFragmentByTag(GameServerTestV2ViewModel::class.java.name) as? GameServerTestV2ListFragment
?: GameServerTestV2ListFragment() ?: GameServerTestV2ListFragment()

View File

@ -18,6 +18,7 @@ import com.gh.download.DownloadManager
import com.gh.gamecenter.R import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.LazyListFragment import com.gh.gamecenter.common.baselist.LazyListFragment
import com.gh.gamecenter.common.baselist.ListAdapter import com.gh.gamecenter.common.baselist.ListAdapter
import com.gh.gamecenter.common.baselist.LoadType
import com.gh.gamecenter.common.constant.Constants import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.exposure.ExposureSource import com.gh.gamecenter.common.exposure.ExposureSource
@ -76,6 +77,12 @@ class GameServerTestV2ListFragment :
) )
} }
override fun isAutomaticLoad(): Boolean = false
fun loadData() {
mListViewModel.load(LoadType.NORMAL)
}
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
mGameServerTestV2ViewModel = viewModelProviderFromParent() mGameServerTestV2ViewModel = viewModelProviderFromParent()
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -356,7 +363,7 @@ class GameServerTestV2ListFragment :
override fun onChanged(ts: MutableList<GameServerTestV2ListViewModel.ItemData>?) { override fun onChanged(ts: MutableList<GameServerTestV2ListViewModel.ItemData>?) {
super.onChanged(ts) super.onChanged(ts)
if (!ts.isNullOrEmpty() && mListViewModel.isLoadFirstPage) { if (!ts.isNullOrEmpty() && mListViewModel.isLoadFirstPage) {
scrollToTime(GameServerTestV2ViewModel.TimeFilter.TODAY.value) scrollToTime(GameServerTestV2ViewModel.TimeFilter.Today.value)
mListRv?.postDelayed({ mListRv?.postDelayed({
scroll() scroll()
mScrollCalculatorHelper.onScrollStateChanged(RecyclerView.SCROLL_STATE_IDLE) mScrollCalculatorHelper.onScrollStateChanged(RecyclerView.SCROLL_STATE_IDLE)

View File

@ -55,14 +55,9 @@ class GameServerTestV2ListViewModel(
mListLiveData.value = null mListLiveData.value = null
mLoadStatusLiveData.postValue(LoadStatus.INIT_LOADING) mLoadStatusLiveData.postValue(LoadStatus.INIT_LOADING)
var categoryFilter = "" //如果选择全部类型,则传空值 val categoryFilter = mGameServerTestV2ViewModel?.selectedCategoryFilterSet?.joinToString("-") {
mGameServerTestV2ViewModel?.selectedCategoryFilterSet?.forEachIndexed { index, category -> GameServerTestV2ViewModel.GameCategory.fromDisplayName(it).value
if (index == 0) { } ?: "" //如果选择全部类型,则传空值
categoryFilter = mGameServerTestV2ViewModel.getCategoryFilterValue(category).value
} else {
categoryFilter += "-${mGameServerTestV2ViewModel.getCategoryFilterValue(category).value}"
}
}
Observable.zip(provideDetailObservable(categoryFilter), provideTopGamesObservable()) { t1, t2 -> Observable.zip(provideDetailObservable(categoryFilter), provideTopGamesObservable()) { t1, t2 ->
mTopGameList = ArrayList(t2).apply { mTopGameList = ArrayList(t2).apply {
@ -151,11 +146,11 @@ class GameServerTestV2ListViewModel(
// 置顶游戏固定显示在今天上方,如果今天没有数据则展示在未来上方;如果今天和未来都没有数据,则显示在近期上方 // 置顶游戏固定显示在今天上方,如果今天没有数据则展示在未来上方;如果今天和未来都没有数据,则显示在近期上方
val topGamesReadableDaysOffset = if (mTodayCount != 0) { val topGamesReadableDaysOffset = if (mTodayCount != 0) {
GameServerTestV2ViewModel.TimeFilter.TODAY.value GameServerTestV2ViewModel.TimeFilter.Today.value
} else if (mFutureCount != 0) { } else if (mFutureCount != 0) {
GameServerTestV2ViewModel.TimeFilter.UPCOMING_DAY.value GameServerTestV2ViewModel.TimeFilter.Future.value
} else { } else {
GameServerTestV2ViewModel.TimeFilter.PAST_DAY.value GameServerTestV2ViewModel.TimeFilter.Recent.value
} }
var isTopGamesAdded = false var isTopGamesAdded = false
@ -179,11 +174,7 @@ class GameServerTestV2ListViewModel(
} }
} }
val readableDaysOffset = when (slice.timeType) { val readableDaysOffset = slice.timeType
"recent" -> GameServerTestV2ViewModel.TimeFilter.PAST_DAY.value
"future" -> GameServerTestV2ViewModel.TimeFilter.UPCOMING_DAY.value
else -> GameServerTestV2ViewModel.TimeFilter.TODAY.value
}
if (!mTopGameList.isNullOrEmpty() && readableDaysOffset == topGamesReadableDaysOffset && !isTopGamesAdded) { if (!mTopGameList.isNullOrEmpty() && readableDaysOffset == topGamesReadableDaysOffset && !isTopGamesAdded) {
subList.add( subList.add(
@ -335,31 +326,31 @@ class GameServerTestV2ListViewModel(
if (position >= 0) return position if (position >= 0) return position
val recentCount = val recentCount =
itemDataList.count { it.readableDaysOffset == GameServerTestV2ViewModel.TimeFilter.PAST_DAY.value } itemDataList.count { it.readableDaysOffset == GameServerTestV2ViewModel.TimeFilter.Recent.value }
val todayCount = val todayCount =
itemDataList.count { it.readableDaysOffset == GameServerTestV2ViewModel.TimeFilter.TODAY.value } itemDataList.count { it.readableDaysOffset == GameServerTestV2ViewModel.TimeFilter.Today.value }
when (timeFilter) { when (timeFilter) {
GameServerTestV2ViewModel.TimeFilter.PAST_DAY.value -> { GameServerTestV2ViewModel.TimeFilter.Recent.value -> {
position = mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.TODAY.value, -1) position = mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.Today.value, -1)
if (position >= 0) return position if (position >= 0) return position
position = position =
mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.UPCOMING_DAY.value, -1) mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.Future.value, -1)
if (position >= 0) return position if (position >= 0) return position
} }
GameServerTestV2ViewModel.TimeFilter.TODAY.value -> { GameServerTestV2ViewModel.TimeFilter.Today.value -> {
position = position =
mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.UPCOMING_DAY.value, -1) mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.Future.value, -1)
if (position >= 0) return position if (position >= 0) return position
position = position =
mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.PAST_DAY.value, -1) mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.Recent.value, -1)
if (position >= 0) return recentCount - 1 if (position >= 0) return recentCount - 1
} }
GameServerTestV2ViewModel.TimeFilter.UPCOMING_DAY.value -> { GameServerTestV2ViewModel.TimeFilter.Future.value -> {
position = mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.TODAY.value, -1) position = mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.Today.value, -1)
if (position >= 0) return recentCount + todayCount - 1 if (position >= 0) return recentCount + todayCount - 1
position = position =
mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.PAST_DAY.value, -1) mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.Recent.value, -1)
if (position >= 0) recentCount - 1 if (position >= 0) recentCount - 1
} }
} }

View File

@ -1,5 +1,6 @@
package com.gh.gamecenter.servers.gametest2 package com.gh.gamecenter.servers.gametest2
import android.annotation.SuppressLint
import android.app.Application import android.app.Application
import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
@ -7,89 +8,129 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import com.gh.common.constant.Config import com.gh.common.constant.Config
import com.gh.gamecenter.common.constant.Constants import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.core.utils.SPUtils import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.entity.GameServerTestDisplaySetting
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.servers.gametest2.GameServerTestV2ViewModel.GameCategory.*
import com.halo.assistant.HaloApp
class GameServerTestV2ViewModel(application: Application) : AndroidViewModel(application) { class GameServerTestV2ViewModel(application: Application, private val linkId: String = "none") : AndroidViewModel(application) {
val timeFilterList = arrayListOf(RECENT_GAME, TODAY_GAME, FUTURE_GAME) val timeFilterList = arrayListOf(
val categoryFilterList = arrayListOf("单机游戏", "网络游戏", "福利游戏", "国际服游戏") GameServerTestDisplaySetting.RECENT,
GameServerTestDisplaySetting.TODAY,
GameServerTestDisplaySetting.FUTURE
)
val categoryFilterList = arrayListOf(
Local.displayName,
Online.displayName,
Welfare.displayName,
Gjonline.displayName
)
var selectedCategoryFilterSet = SPUtils.getStringSet(Constants.SP_GAME_SERVER_TEST_V2_CATEGORY_SET).toHashSet() var selectedCategoryFilterSet = SPUtils.getStringSet(Constants.SP_GAME_SERVER_TEST_V2_CATEGORY_SET).toHashSet()
var tempCategoryFilterSet = SPUtils.getStringSet(Constants.SP_GAME_SERVER_TEST_V2_CATEGORY_SET).toHashSet() var tempCategoryFilterSet = SPUtils.getStringSet(Constants.SP_GAME_SERVER_TEST_V2_CATEGORY_SET).toHashSet()
var selectedTimeFilterLiveData = MutableLiveData(timeFilterList[GameServerTestV2Fragment.DEFAULT_TIME_FILTER_INDEX]) var selectedTimeFilterLiveData = MutableLiveData<String?>()
var bigImageMode = SPUtils.getBoolean( var bigImageMode = SPUtils.getBoolean(
Constants.SP_GAME_SERVER_TEST_V2_BIG_IMAGE_MODE, Constants.SP_GAME_SERVER_TEST_V2_BIG_IMAGE_MODE,
Config.getSettings() == null || Config.getSettings()?.columnTestV2Setting?.defaultView == "on" Config.getSettings() == null || Config.getSettings()?.columnTestV2Setting?.defaultView == "on"
) )
fun getCategoryFilterString(): String { val displaySettingLiveData = MutableLiveData<GameServerTestDisplaySetting>()
var selectedCategoryFilter = "全部类型"
selectedCategoryFilterSet.forEachIndexed { index, category -> init {
if (index == 0) { loadDisplaySetting()
selectedCategoryFilter = category
} else {
selectedCategoryFilter += "+$category"
}
}
return selectedCategoryFilter
} }
fun getCategoryFilterValue(category: String): GameCategory { @SuppressLint("CheckResult")
return when (category) { fun loadDisplaySetting() {
"单机游戏" -> GameCategory.LOCAL RetrofitManager.getInstance().newApi.getGameServerTestDisplaySetting(linkId)
"网络游戏" -> GameCategory.ONLINE .compose(singleToMain())
"福利游戏" -> GameCategory.WELFARE .subscribe({ setting ->
"国际服游戏" -> GameCategory.GJONLINE timeFilterList.clear()
else -> GameCategory.ALL timeFilterList.addAll(listOf(setting.timeTextPast, setting.timeTextPresent, setting.timeTextFuture))
} TimeFilter.Recent.displayName = setting.timeTextPast
TimeFilter.Today.displayName = setting.timeTextPresent
TimeFilter.Future.displayName = setting.timeTextFuture
categoryFilterList.clear()
categoryFilterList.addAll(setting.gameCategory.map { GameCategory.fromValue(it).displayName })
selectedCategoryFilterSet.removeAll { !categoryFilterList.contains(it) }
tempCategoryFilterSet.removeAll { !categoryFilterList.contains(it) }
displaySettingLiveData.postValue(setting)
}, {
it.printStackTrace()
displaySettingLiveData.postValue(GameServerTestDisplaySetting())
})
} }
fun getSelectedTimeFilterString(dayFilter: String): String { fun getCategoryFilterString() = selectedCategoryFilterSet.joinToString("+").ifEmpty { "全部类型" }
return when (dayFilter) {
TimeFilter.PAST_DAY.value -> RECENT_GAME
TimeFilter.TODAY.value -> TODAY_GAME
TimeFilter.UPCOMING_DAY.value -> FUTURE_GAME
else -> TODAY_GAME
}
}
fun getCurrentSelectedTimeFilter(): String { fun getSelectedTimeFilterString(dayFilter: String) = TimeFilter.fromValue(dayFilter).displayName
return when (selectedTimeFilterLiveData.value) {
RECENT_GAME -> TimeFilter.PAST_DAY.value fun getCurrentSelectedTimeFilter() = TimeFilter.fromDisplayName(selectedTimeFilterLiveData.value ?: "").value
TODAY_GAME -> TimeFilter.TODAY.value
FUTURE_GAME -> TimeFilter.UPCOMING_DAY.value
else -> TimeFilter.TODAY.value
}
}
fun setSelectedTimeFilter(filter: String?) { fun setSelectedTimeFilter(filter: String?) {
if (filter.isNullOrEmpty()) return
selectedTimeFilterLiveData.value = filter selectedTimeFilterLiveData.value = filter
} }
enum class TimeFilter(val value: String) { sealed class TimeFilter(val value: String, var displayName: String) {
PAST_DAY("recent"), data object Recent : TimeFilter("recent", GameServerTestDisplaySetting.RECENT)
TODAY("today"), data object Today : TimeFilter("today", GameServerTestDisplaySetting.TODAY)
UPCOMING_DAY("future") data object Future : TimeFilter("future", GameServerTestDisplaySetting.FUTURE)
companion object {
fun fromDisplayName(displayName: String): TimeFilter {
return when (displayName) {
Recent.displayName -> Recent
Future.displayName -> Future
else -> Today
}
}
fun fromValue(value: String): TimeFilter {
return when (value) {
Recent.value -> Recent
Future.value -> Future
else -> Today
}
}
}
} }
enum class GameCategory(val value: String) { sealed class GameCategory(val value: String, val displayName: String) {
ALL("all"), data object All : GameCategory("all", "全部")
LOCAL("local"), data object Local : GameCategory("local", "单机游戏")
ONLINE("online"), data object Online : GameCategory("online", "网络游戏")
WELFARE("welfare"), data object Welfare : GameCategory("welfare", "福利游戏")
GJONLINE("gjonline"), data object Gjonline : GameCategory("gjonline", "国际服游戏")
companion object {
fun fromDisplayName(displayName: String): GameCategory {
return when (displayName) {
Local.displayName -> Local
Online.displayName -> Online
Welfare.displayName -> Welfare
Gjonline.displayName -> Gjonline
else -> All
}
}
fun fromValue(value: String): GameCategory {
return when (value) {
Local.value -> Local
Online.value -> Online
Welfare.value -> Welfare
Gjonline.value -> Gjonline
else -> All
}
}
}
} }
companion object { class Factory(private val linkId: String = "none") :
const val RECENT_GAME = "近期"
const val TODAY_GAME = "今天"
const val FUTURE_GAME = "预约"
}
class Factory(private val mApplication: Application) :
ViewModelProvider.NewInstanceFactory() { ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T { override fun <T : ViewModel> create(modelClass: Class<T>): T {
return GameServerTestV2ViewModel(mApplication) as T return GameServerTestV2ViewModel(HaloApp.getInstance(), linkId) as T
} }
} }
} }

View File

@ -56,9 +56,10 @@
<com.gh.gamecenter.common.view.SegmentedIconFilterView <com.gh.gamecenter.common.view.SegmentedIconFilterView
android:id="@+id/filterView" android:id="@+id/filterView"
android:layout_width="130dp" android:layout_width="wrap_content"
android:layout_height="28dp" android:layout_height="28dp"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintTop_toTopOf="parent" />

View File

@ -143,7 +143,7 @@ ext {
acloudPush = "3.8.8.1" acloudPush = "3.8.8.1"
jpushVersion = "5.4.0" jpushVersion = "5.4.0"
jverifiationVersion = "3.2.5" jverifiationVersion = "3.4.2"
honorPushVersion = "7.0.61.303" honorPushVersion = "7.0.61.303"
volcTlsVersion = "1.1.4" volcTlsVersion = "1.1.4"

View File

@ -517,7 +517,10 @@ object SensorsBridge {
downloadStatus: String, downloadStatus: String,
gameType: String, gameType: String,
position: Int, position: Int,
tabContent: String tabContent: String,
linkType: String,
linkId: String,
linkText: String,
) { ) {
val json = json { val json = json {
KEY_GAME_ID to gameId KEY_GAME_ID to gameId
@ -532,6 +535,9 @@ object SensorsBridge {
KEY_GAME_TYPE to gameType KEY_GAME_TYPE to gameType
KEY_POSITION to position KEY_POSITION to position
KEY_TAB_CONTENT to tabContent KEY_TAB_CONTENT to tabContent
KEY_LINK_TYPE to linkType
KEY_LINK_ID to linkId
KEY_LINK_TEXT to linkText
} }
trackEvent(EVENT_GAME_DETAIL_PAGE_TAB_SELECT, json) trackEvent(EVENT_GAME_DETAIL_PAGE_TAB_SELECT, json)

View File

@ -5,10 +5,12 @@ import android.graphics.drawable.Drawable
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.core.view.setPadding
import com.gh.gamecenter.common.R import com.gh.gamecenter.common.R
import com.gh.gamecenter.common.databinding.ViewSegmentedIconFilterBinding import com.gh.gamecenter.common.databinding.ViewSegmentedIconFilterBinding
import com.gh.gamecenter.common.utils.DarkModeUtils import com.gh.gamecenter.common.utils.DarkModeUtils
import com.gh.gamecenter.common.utils.dip2px import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.toDrawable
class SegmentedIconFilterView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null): FrameLayout(context, attrs) { class SegmentedIconFilterView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null): FrameLayout(context, attrs) {
private var textSize = 12F private var textSize = 12F
@ -23,15 +25,12 @@ class SegmentedIconFilterView @JvmOverloads constructor(context: Context, attrs:
var onCheckedAction: ((Int) -> Unit)? = null var onCheckedAction: ((Int) -> Unit)? = null
init { init {
val view = LayoutInflater.from(context).inflate(R.layout.view_segmented_icon_filter, this, true) val view = LayoutInflater.from(context).inflate(R.layout.view_segmented_icon_filter, this, false)
binding = ViewSegmentedIconFilterBinding.bind(view) binding = ViewSegmentedIconFilterBinding.bind(view)
addView(view)
initView(attrs) initView(attrs)
} }
fun setContainerBackground(drawable: Drawable?) {
binding.motionLayout.background = drawable
}
private fun initView(attrs: AttributeSet?) { private fun initView(attrs: AttributeSet?) {
if (attrs != null) { if (attrs != null) {
val ta = context.obtainStyledAttributes(attrs, R.styleable.SegmentedFilterView) val ta = context.obtainStyledAttributes(attrs, R.styleable.SegmentedFilterView)
@ -47,12 +46,8 @@ class SegmentedIconFilterView @JvmOverloads constructor(context: Context, attrs:
ta.recycle() ta.recycle()
} }
binding.motionLayout.run { background = containerBackground ?: R.drawable.bg_shape_ui_container_2_radius_999.toDrawable(context)
setPadding(containerPadding, containerPadding, containerPadding, containerPadding) setPadding(containerPadding)
containerBackground?.let {
background = it
}
}
indicatorBackground?.let { indicatorBackground?.let {
binding.indicator.background = it binding.indicator.background = it
} }
@ -129,6 +124,7 @@ class SegmentedIconFilterView @JvmOverloads constructor(context: Context, attrs:
} }
fun updateView(isDarkModeOn: Boolean) { fun updateView(isDarkModeOn: Boolean) {
background = containerBackground ?: R.drawable.bg_shape_ui_container_2_radius_999.toDrawable(context)
binding.motionLayout.run { binding.motionLayout.run {
when (currentIndex) { when (currentIndex) {
0 -> jumpToState(if (isDarkModeOn) R.id.filterDark1 else R.id.filter1) 0 -> jumpToState(if (isDarkModeOn) R.id.filterDark1 else R.id.filter1)

View File

@ -1,66 +1,60 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.motion.widget.MotionLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/motionLayout"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content"> android:layout_height="24dp"
app:layoutDescription="@xml/view_segmented_icon_filter_scene">
<androidx.constraintlayout.motion.widget.MotionLayout <View
android:id="@+id/motionLayout" android:id="@+id/indicator"
android:layout_width="130dp" android:layout_width="0dp"
android:layout_height="28dp" android:layout_height="0dp"
android:background="@drawable/bg_shape_ui_container_2_radius_999" android:background="@drawable/background_shape_white_radius_999" />
android:padding="2dp"
app:layoutDescription="@xml/view_segmented_icon_filter_scene">
<View <ImageView
android:id="@+id/indicator" android:id="@+id/icon1Iv"
android:layout_width="50dp" android:layout_width="12dp"
android:layout_height="0dp" android:layout_height="12dp"
android:background="@drawable/background_shape_white_radius_999" /> app:srcCompat="@drawable/ic_segmented_icon_filter_dot" />
<ImageView <TextView
android:id="@+id/icon1Iv" android:id="@+id/filter1Tv"
android:layout_width="12dp" android:layout_width="wrap_content"
android:layout_height="12dp" android:layout_height="0dp"
app:srcCompat="@drawable/ic_segmented_icon_filter_dot" /> android:gravity="center"
android:includeFontPadding="false"
android:text="近期"
android:textSize="@dimen/secondary_size" />
<TextView <ImageView
android:id="@+id/filter1Tv" android:id="@+id/icon2Iv"
android:layout_width="wrap_content" android:layout_width="12dp"
android:layout_height="28dp" android:layout_height="12dp"
android:gravity="center" app:srcCompat="@drawable/ic_segmented_icon_filter_dot" />
android:includeFontPadding="false"
android:text="近期"
android:textSize="@dimen/secondary_size" />
<ImageView <TextView
android:id="@+id/icon2Iv" android:id="@+id/filter2Tv"
android:layout_width="12dp" android:layout_width="wrap_content"
android:layout_height="12dp" android:layout_height="0dp"
app:srcCompat="@drawable/ic_segmented_icon_filter_dot" /> android:gravity="center"
android:includeFontPadding="false"
android:text="今天"
android:textSize="@dimen/secondary_size" />
<TextView <ImageView
android:id="@+id/filter2Tv" android:id="@+id/icon3Iv"
android:layout_width="wrap_content" android:layout_width="12dp"
android:layout_height="28dp" android:layout_height="12dp"
android:gravity="center" app:srcCompat="@drawable/ic_segmented_icon_filter_dot" />
android:includeFontPadding="false"
android:text="今天"
android:textSize="@dimen/secondary_size" />
<ImageView <TextView
android:id="@+id/icon3Iv" android:id="@+id/filter3Tv"
android:layout_width="12dp" android:layout_width="wrap_content"
android:layout_height="12dp" android:layout_height="0dp"
app:srcCompat="@drawable/ic_segmented_icon_filter_dot" /> android:gravity="center"
android:includeFontPadding="false"
<TextView android:text="未来"
android:id="@+id/filter3Tv" android:textSize="@dimen/secondary_size" />
android:layout_width="wrap_content" </androidx.constraintlayout.motion.widget.MotionLayout>
android:layout_height="28dp"
android:gravity="center"
android:includeFontPadding="false"
android:text="未来"
android:textSize="@dimen/secondary_size" />
</androidx.constraintlayout.motion.widget.MotionLayout>
</FrameLayout>

View File

@ -31,9 +31,11 @@
<ConstraintSet android:id="@+id/filter1"> <ConstraintSet android:id="@+id/filter1">
<Constraint <Constraint
android:id="@+id/indicator" android:id="@+id/indicator"
android:layout_width="50dp" android:layout_width="0dp"
android:layout_height="24dp" android:layout_height="0dp"
android:layout_marginEnd="6dp"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toStartOf="@+id/filter2Tv"
motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" /> motion:layout_constraintTop_toTopOf="parent" />
@ -64,18 +66,18 @@
android:id="@+id/icon2Iv" android:id="@+id/icon2Iv"
android:layout_width="12dp" android:layout_width="12dp"
android:layout_height="12dp" android:layout_height="12dp"
android:layout_marginStart="6dp"
android:alpha="0" android:alpha="0"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toEndOf="@+id/indicator" motion:layout_constraintStart_toStartOf="@+id/filter2Tv"
motion:layout_constraintTop_toTopOf="parent" /> motion:layout_constraintTop_toTopOf="parent" />
<Constraint <Constraint
android:id="@+id/filter2Tv" android:id="@+id/filter2Tv"
android:layout_width="40dp" android:layout_width="wrap_content"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginStart="14dp"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toEndOf="@+id/indicator" motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
motion:layout_constraintTop_toTopOf="parent"> motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute <CustomAttribute
motion:attributeName="textColor" motion:attributeName="textColor"
@ -86,18 +88,19 @@
android:id="@+id/icon3Iv" android:id="@+id/icon3Iv"
android:layout_width="12dp" android:layout_width="12dp"
android:layout_height="12dp" android:layout_height="12dp"
android:layout_marginStart="6dp"
android:alpha="0" android:alpha="0"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter2Tv" motion:layout_constraintStart_toStartOf="@+id/filter3Tv"
motion:layout_constraintTop_toTopOf="parent" /> motion:layout_constraintTop_toTopOf="parent" />
<Constraint <Constraint
android:id="@+id/filter3Tv" android:id="@+id/filter3Tv"
android:layout_width="40dp" android:layout_width="wrap_content"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginStart="-2dp" android:layout_marginStart="14dp"
android:layout_marginEnd="6dp"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter2Tv" motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
motion:layout_constraintTop_toTopOf="parent"> motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute <CustomAttribute
@ -109,9 +112,12 @@
<ConstraintSet android:id="@+id/filter2"> <ConstraintSet android:id="@+id/filter2">
<Constraint <Constraint
android:id="@+id/indicator" android:id="@+id/indicator"
android:layout_width="50dp" android:layout_width="0dp"
android:layout_height="24dp" android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toStartOf="@+id/filter3Tv"
motion:layout_constraintStart_toEndOf="@+id/filter1Tv" motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
motion:layout_constraintTop_toTopOf="parent" /> motion:layout_constraintTop_toTopOf="parent" />
@ -119,17 +125,16 @@
android:id="@+id/icon1Iv" android:id="@+id/icon1Iv"
android:layout_width="12dp" android:layout_width="12dp"
android:layout_height="12dp" android:layout_height="12dp"
android:layout_marginStart="6dp"
android:alpha="0" android:alpha="0"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintStart_toStartOf="@+id/filter1Tv"
motion:layout_constraintTop_toTopOf="parent" /> motion:layout_constraintTop_toTopOf="parent" />
<Constraint <Constraint
android:id="@+id/filter1Tv" android:id="@+id/filter1Tv"
android:layout_width="40dp" android:layout_width="wrap_content"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginStart="-2dp" android:layout_marginStart="6dp"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"> motion:layout_constraintTop_toTopOf="parent">
@ -142,7 +147,7 @@
android:id="@+id/icon2Iv" android:id="@+id/icon2Iv"
android:layout_width="12dp" android:layout_width="12dp"
android:layout_height="12dp" android:layout_height="12dp"
android:layout_marginStart="6dp" android:layout_marginStart="14dp"
android:alpha="1" android:alpha="1"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter1Tv" motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
@ -173,11 +178,13 @@
<Constraint <Constraint
android:id="@+id/filter3Tv" android:id="@+id/filter3Tv"
android:layout_width="40dp" android:layout_width="wrap_content"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginEnd="-2dp" android:layout_marginStart="14dp"
android:layout_marginEnd="6dp"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent" motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
motion:layout_constraintTop_toTopOf="parent"> motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute <CustomAttribute
motion:attributeName="textColor" motion:attributeName="textColor"
@ -188,27 +195,28 @@
<ConstraintSet android:id="@+id/filter3"> <ConstraintSet android:id="@+id/filter3">
<Constraint <Constraint
android:id="@+id/indicator" android:id="@+id/indicator"
android:layout_width="50dp" android:layout_width="0dp"
android:layout_height="24dp" android:layout_height="0dp"
android:layout_marginStart="8dp"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent" motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
motion:layout_constraintTop_toTopOf="parent" /> motion:layout_constraintTop_toTopOf="parent" />
<Constraint <Constraint
android:id="@+id/icon1Iv" android:id="@+id/icon1Iv"
android:layout_width="12dp" android:layout_width="12dp"
android:layout_height="12dp" android:layout_height="12dp"
android:layout_marginStart="6dp"
android:alpha="0" android:alpha="0"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintStart_toStartOf="@+id/filter1Tv"
motion:layout_constraintTop_toTopOf="parent" /> motion:layout_constraintTop_toTopOf="parent" />
<Constraint <Constraint
android:id="@+id/filter1Tv" android:id="@+id/filter1Tv"
android:layout_width="40dp" android:layout_width="wrap_content"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginStart="-2dp" android:layout_marginStart="6dp"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"> motion:layout_constraintTop_toTopOf="parent">
@ -221,17 +229,16 @@
android:id="@+id/icon2Iv" android:id="@+id/icon2Iv"
android:layout_width="12dp" android:layout_width="12dp"
android:layout_height="12dp" android:layout_height="12dp"
android:layout_marginStart="6dp"
android:alpha="0" android:alpha="0"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter1Tv" motion:layout_constraintStart_toStartOf="@+id/filter2Tv"
motion:layout_constraintTop_toTopOf="parent" /> motion:layout_constraintTop_toTopOf="parent" />
<Constraint <Constraint
android:id="@+id/filter2Tv" android:id="@+id/filter2Tv"
android:layout_width="40dp" android:layout_width="wrap_content"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginStart="-2dp" android:layout_marginStart="14dp"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter1Tv" motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
motion:layout_constraintTop_toTopOf="parent"> motion:layout_constraintTop_toTopOf="parent">
@ -244,7 +251,7 @@
android:id="@+id/icon3Iv" android:id="@+id/icon3Iv"
android:layout_width="12dp" android:layout_width="12dp"
android:layout_height="12dp" android:layout_height="12dp"
android:layout_marginStart="6dp" android:layout_marginStart="14dp"
android:alpha="1" android:alpha="1"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter2Tv" motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
@ -255,7 +262,9 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginStart="2dp" android:layout_marginStart="2dp"
android:layout_marginEnd="6dp"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toEndOf="@+id/icon3Iv" motion:layout_constraintStart_toEndOf="@+id/icon3Iv"
motion:layout_constraintTop_toTopOf="parent"> motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute <CustomAttribute
@ -267,9 +276,11 @@
<ConstraintSet android:id="@+id/filterDark1"> <ConstraintSet android:id="@+id/filterDark1">
<Constraint <Constraint
android:id="@+id/indicator" android:id="@+id/indicator"
android:layout_width="50dp" android:layout_width="0dp"
android:layout_height="24dp" android:layout_height="0dp"
android:layout_marginEnd="8dp"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toStartOf="@+id/filter2Tv"
motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" /> motion:layout_constraintTop_toTopOf="parent" />
@ -300,18 +311,18 @@
android:id="@+id/icon2Iv" android:id="@+id/icon2Iv"
android:layout_width="12dp" android:layout_width="12dp"
android:layout_height="12dp" android:layout_height="12dp"
android:layout_marginStart="6dp"
android:alpha="0" android:alpha="0"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toEndOf="@+id/indicator" motion:layout_constraintStart_toStartOf="@+id/filter2Tv"
motion:layout_constraintTop_toTopOf="parent" /> motion:layout_constraintTop_toTopOf="parent" />
<Constraint <Constraint
android:id="@+id/filter2Tv" android:id="@+id/filter2Tv"
android:layout_width="40dp" android:layout_width="wrap_content"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginStart="14dp"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toEndOf="@+id/indicator" motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
motion:layout_constraintTop_toTopOf="parent"> motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute <CustomAttribute
motion:attributeName="textColor" motion:attributeName="textColor"
@ -322,18 +333,19 @@
android:id="@+id/icon3Iv" android:id="@+id/icon3Iv"
android:layout_width="12dp" android:layout_width="12dp"
android:layout_height="12dp" android:layout_height="12dp"
android:layout_marginStart="6dp"
android:alpha="0" android:alpha="0"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter2Tv" motion:layout_constraintStart_toStartOf="@+id/filter3Tv"
motion:layout_constraintTop_toTopOf="parent" /> motion:layout_constraintTop_toTopOf="parent" />
<Constraint <Constraint
android:id="@+id/filter3Tv" android:id="@+id/filter3Tv"
android:layout_width="40dp" android:layout_width="wrap_content"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginStart="-2dp" android:layout_marginStart="14dp"
android:layout_marginEnd="6dp"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter2Tv" motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
motion:layout_constraintTop_toTopOf="parent"> motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute <CustomAttribute
@ -345,9 +357,12 @@
<ConstraintSet android:id="@+id/filterDark2"> <ConstraintSet android:id="@+id/filterDark2">
<Constraint <Constraint
android:id="@+id/indicator" android:id="@+id/indicator"
android:layout_width="50dp" android:layout_width="0dp"
android:layout_height="24dp" android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toStartOf="@+id/filter3Tv"
motion:layout_constraintStart_toEndOf="@+id/filter1Tv" motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
motion:layout_constraintTop_toTopOf="parent" /> motion:layout_constraintTop_toTopOf="parent" />
@ -355,17 +370,16 @@
android:id="@+id/icon1Iv" android:id="@+id/icon1Iv"
android:layout_width="12dp" android:layout_width="12dp"
android:layout_height="12dp" android:layout_height="12dp"
android:layout_marginStart="6dp"
android:alpha="0" android:alpha="0"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintStart_toStartOf="@+id/filter1Tv"
motion:layout_constraintTop_toTopOf="parent" /> motion:layout_constraintTop_toTopOf="parent" />
<Constraint <Constraint
android:id="@+id/filter1Tv" android:id="@+id/filter1Tv"
android:layout_width="40dp" android:layout_width="wrap_content"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginStart="-2dp" android:layout_marginStart="6dp"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"> motion:layout_constraintTop_toTopOf="parent">
@ -378,7 +392,7 @@
android:id="@+id/icon2Iv" android:id="@+id/icon2Iv"
android:layout_width="12dp" android:layout_width="12dp"
android:layout_height="12dp" android:layout_height="12dp"
android:layout_marginStart="6dp" android:layout_marginStart="14dp"
android:alpha="1" android:alpha="1"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter1Tv" motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
@ -401,7 +415,6 @@
android:id="@+id/icon3Iv" android:id="@+id/icon3Iv"
android:layout_width="12dp" android:layout_width="12dp"
android:layout_height="12dp" android:layout_height="12dp"
android:layout_marginStart="6dp"
android:alpha="0" android:alpha="0"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="@+id/filter3Tv" motion:layout_constraintStart_toStartOf="@+id/filter3Tv"
@ -409,11 +422,13 @@
<Constraint <Constraint
android:id="@+id/filter3Tv" android:id="@+id/filter3Tv"
android:layout_width="40dp" android:layout_width="wrap_content"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginEnd="-2dp" android:layout_marginStart="14dp"
android:layout_marginEnd="6dp"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent" motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
motion:layout_constraintTop_toTopOf="parent"> motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute <CustomAttribute
motion:attributeName="textColor" motion:attributeName="textColor"
@ -424,27 +439,28 @@
<ConstraintSet android:id="@+id/filterDark3"> <ConstraintSet android:id="@+id/filterDark3">
<Constraint <Constraint
android:id="@+id/indicator" android:id="@+id/indicator"
android:layout_width="50dp" android:layout_width="0dp"
android:layout_height="24dp" android:layout_height="0dp"
android:layout_marginStart="8dp"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent" motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
motion:layout_constraintTop_toTopOf="parent" /> motion:layout_constraintTop_toTopOf="parent" />
<Constraint <Constraint
android:id="@+id/icon1Iv" android:id="@+id/icon1Iv"
android:layout_width="12dp" android:layout_width="12dp"
android:layout_height="12dp" android:layout_height="12dp"
android:layout_marginStart="6dp"
android:alpha="0" android:alpha="0"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintStart_toStartOf="@+id/filter1Tv"
motion:layout_constraintTop_toTopOf="parent" /> motion:layout_constraintTop_toTopOf="parent" />
<Constraint <Constraint
android:id="@+id/filter1Tv" android:id="@+id/filter1Tv"
android:layout_width="40dp" android:layout_width="wrap_content"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginStart="-2dp" android:layout_marginStart="6dp"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent" motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent"> motion:layout_constraintTop_toTopOf="parent">
@ -457,17 +473,16 @@
android:id="@+id/icon2Iv" android:id="@+id/icon2Iv"
android:layout_width="12dp" android:layout_width="12dp"
android:layout_height="12dp" android:layout_height="12dp"
android:layout_marginStart="6dp"
android:alpha="0" android:alpha="0"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter1Tv" motion:layout_constraintStart_toStartOf="@+id/filter2Tv"
motion:layout_constraintTop_toTopOf="parent" /> motion:layout_constraintTop_toTopOf="parent" />
<Constraint <Constraint
android:id="@+id/filter2Tv" android:id="@+id/filter2Tv"
android:layout_width="40dp" android:layout_width="wrap_content"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginStart="-2dp" android:layout_marginStart="14dp"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter1Tv" motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
motion:layout_constraintTop_toTopOf="parent"> motion:layout_constraintTop_toTopOf="parent">
@ -480,7 +495,7 @@
android:id="@+id/icon3Iv" android:id="@+id/icon3Iv"
android:layout_width="12dp" android:layout_width="12dp"
android:layout_height="12dp" android:layout_height="12dp"
android:layout_marginStart="6dp" android:layout_marginStart="14dp"
android:alpha="1" android:alpha="1"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter2Tv" motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
@ -491,7 +506,9 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="0dp" android:layout_height="0dp"
android:layout_marginStart="2dp" android:layout_marginStart="2dp"
android:layout_marginEnd="6dp"
motion:layout_constraintBottom_toBottomOf="parent" motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toEndOf="@+id/icon3Iv" motion:layout_constraintStart_toEndOf="@+id/icon3Iv"
motion:layout_constraintTop_toTopOf="parent"> motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute <CustomAttribute