Compare commits

..

65 Commits

Author SHA1 Message Date
cfde1e1e62 重构数据解析,ui组件刷新逻辑 2025-06-13 16:15:25 +08:00
0f382baec1 Merge branch 'feat/GHZSCY-8049' into 'dev'
feat: 游戏详情UI优化-客户端 https://jira.shanqu.cc/browse/GHZSCY-8049

See merge request halo/android/assistant-android!2207
2025-05-27 14:39:06 +08:00
62f80bbfdd feat: 游戏详情UI优化-客户端 https://jira.shanqu.cc/browse/GHZSCY-8049 2025-05-27 14:39:06 +08:00
63be9ac221 chore: 版本更新至 5.43.0 2025-05-27 14:04:44 +08:00
ade32ef92d Merge branch 'feat/GHZSCY-7987' into 'dev'
合并【光环助手】畅玩游戏管理游戏显示问题

See merge request halo/android/assistant-android!2206
2025-05-27 11:19:57 +08:00
58702b46aa Revert "ci: 测试 https://jira.shanqu.cc/browse/GHZSCY-7987"
This reverts commit 75ca0a8379.
2025-05-27 11:15:55 +08:00
e6d6fc5309 Merge remote-tracking branch 'origin/release' into dev
# Conflicts:
#	app/src/main/java/com/gh/gamecenter/minigame/wechat/WGameSubjectCPMRemoteDataSource.kt
#	dependencies.gradle
2025-05-27 09:29:44 +08:00
1460348582 Merge branch 'feat/GHZSCY-7972' into 'dev'
feat: 【畅玩助手】文件夹权限申请 https://jira.shanqu.cc/browse/GHZSCY-7972

See merge request halo/android/assistant-android!2205
2025-05-26 11:31:40 +08:00
c49b3c5efe feat: 【畅玩助手】文件夹权限申请 https://jira.shanqu.cc/browse/GHZSCY-7972 2025-05-26 11:27:05 +08:00
4f7c468cde fix: 畅玩加载中没有显示正在下载的游戏 https://jira.shanqu.cc/browse/GHZSCY-7987 2025-05-23 17:53:59 +08:00
df93bdda2e Merge branch 'dev' into feat/GHZSCY-7987 2025-05-23 15:54:42 +08:00
086796915b Merge branch 'feat/GHZSCY-7955' into 'dev'
feat: CPM微信小游戏推荐优化 https://jira.shanqu.cc/browse/GHZSCY-7954

See merge request halo/android/assistant-android!2204
2025-05-22 16:40:44 +08:00
f99e5f403d feat: CPM微信小游戏推荐优化 https://jira.shanqu.cc/browse/GHZSCY-7954 2025-05-22 16:25:40 +08:00
9785f4fa13 Merge branch 'feat/GHZSCY-8006' into 'dev'
fix: 广告系统-新增广告游戏的过滤条件 https://jira.shanqu.cc/browse/GHZSCY-8006

See merge request halo/android/assistant-android!2203
2025-05-20 17:17:22 +08:00
6cdff338ed fix: 广告系统-新增广告游戏的过滤条件 https://jira.shanqu.cc/browse/GHZSCY-8006 2025-05-20 17:16:45 +08:00
761093a768 chore: 版本更新至 5.41.4 2025-05-20 11:05:49 +08:00
e5a0db4513 Merge branch 'hotfix/v5.41.3-1173/dsp_crash' into 'release'
fix: 修复 DetailViewHolder 触发的弹窗在模拟器上的闪退问题

See merge request halo/android/assistant-android!2202
2025-05-20 10:05:26 +08:00
a3df62bba8 fix: 修复 DetailViewHolder 触发的弹窗在模拟器上的闪退问题 2025-05-20 09:56:20 +08:00
ee773f7e31 Merge branch 'hotfix/v5.41.3-1173/huawei-crash' into 'release'
fix: 补充屏蔽华为 Android 10 设备上的热启动广告显示

See merge request halo/android/assistant-android!2201
2025-05-19 16:00:29 +08:00
227a5e677f fix: 补充屏蔽华为 Android 10 设备上的热启动广告显示 2025-05-19 15:56:04 +08:00
c41939cd79 Merge branch 'feat/GHZSCY-7999' into 'dev'
feat: 专题合集新增自定义设置-0516运营验收优化-客户端 https://jira.shanqu.cc/browse/GHZSCY-7999

See merge request halo/android/assistant-android!2200
2025-05-19 14:10:04 +08:00
125ef60176 feat: 专题合集新增自定义设置-0516运营验收优化-客户端 https://jira.shanqu.cc/browse/GHZSCY-7999 2025-05-19 14:10:04 +08:00
e2bf2773e4 Merge branch 'fix/GHZSCY-8001-pr' into 'dev'
fix:分类新增搜索功能-0516运营验收-客户端 https://jira.shanqu.cc/browse/GHZSCY-8001

See merge request halo/android/assistant-android!2199
2025-05-19 11:09:28 +08:00
e3596c758e fix:分类新增搜索功能-0516运营验收-客户端 https://jira.shanqu.cc/browse/GHZSCY-8001 2025-05-19 11:00:38 +08:00
75ca0a8379 ci: 测试 https://jira.shanqu.cc/browse/GHZSCY-7987 2025-05-19 10:49:17 +08:00
ef7d2860e5 feat: 修复页面onStart没有刷新数据 https://jira.shanqu.cc/browse/GHZSCY-7987 2025-05-19 10:48:04 +08:00
bfc8981253 chore: 版本更新至 5.41.3 2025-05-19 10:42:28 +08:00
12d6d338aa Merge branch 'hotfix/v5.41.2-1172/huawei-crash' into 'release'
fix: 屏蔽华为系 Android 10 设备的穿山甲广告,避免启动闪退

See merge request halo/android/assistant-android!2198
2025-05-19 10:39:51 +08:00
4da3c3aec1 fix: 屏蔽华为系 Android 10 设备的穿山甲广告,避免启动闪退 2025-05-19 10:37:59 +08:00
e608a51cce feat: 修复畅玩游戏管理页面编辑状态点击删除按钮,取消操作之后导致已下载的游戏变为暂停https://jira.shanqu.cc/browse/GHZSCY-7987 2025-05-16 18:19:53 +08:00
4f2aea7fcf Merge branch 'fix/GHZSCY-7989-pr' into 'dev'
fix:【光环助手】页面刷新问题 https://jira.shanqu.cc/browse/GHZSCY-7989

See merge request halo/android/assistant-android!2197
2025-05-16 15:47:30 +08:00
3abcad5d5d fix:【光环助手】页面刷新问题 https://jira.shanqu.cc/browse/GHZSCY-7989 2025-05-16 15:29:59 +08:00
1cca908327 Merge branch 'feat/GHZSCY-7058-rebase' into 'dev'
feat: 广告系统 https://jira.shanqu.cc/browse/GHZSCY-7058

See merge request halo/android/assistant-android!2103
2025-05-16 13:52:37 +08:00
63c0b859f4 feat: 广告系统 https://jira.shanqu.cc/browse/GHZSCY-7058 2025-05-16 13:52:37 +08:00
5deb741942 Merge branch 'feat/GHZSCY-7642' into 'dev'
畅玩汉化翻译功能

See merge request halo/android/assistant-android!2196
2025-05-15 16:55:35 +08:00
e855f59ae4 Merge branch 'feat/GHZSCY-7642' of git.shanqu.cc:halo/android/assistant-android into feat/GHZSCY-7642 2025-05-15 16:50:08 +08:00
9ea4b1367e Revert .gitlab-ci.yml 2025-05-15 16:49:29 +08:00
e44c11349b feat: 替换正式环境2.0.0插件url 2025-05-15 16:49:29 +08:00
a0ea9f9f44 feat: https://jira.shanqu.cc/browse/GHZSCY-7638 替换新版插件 2025-05-15 16:49:29 +08:00
082d3297f7 feat: 修复用户没有下载新的畅玩游戏,插件更新没有触发的问题。 https://jira.shanqu.cc/browse/GHZSCY-7642 2025-05-15 16:49:29 +08:00
f81055ae7f feat: 汉化打包测试 https://jira.shanqu.cc/browse/GHZSCY-7642 2025-05-15 16:49:29 +08:00
8e3de4e441 feat: 优化汉化UI 2025-05-15 16:49:29 +08:00
7cf66c4362 feat: 畅玩汉化功能 2025-05-15 16:49:29 +08:00
d514e4ce1b Merge branch 'fix/GHZSCY-7994-pr' into 'dev'
fix:【光环助手】开屏自有广告显示问题 https://jira.shanqu.cc/browse/GHZSCY-7994

See merge request halo/android/assistant-android!2194
2025-05-15 16:44:58 +08:00
9d0f75a882 fix:【光环助手】开屏自有广告显示问题 https://jira.shanqu.cc/browse/GHZSCY-7994 2025-05-15 16:44:58 +08:00
6c8c2f2340 Revert .gitlab-ci.yml 2025-05-15 16:43:15 +08:00
8d7afea0f6 feat: 替换正式环境2.0.0插件url 2025-05-15 16:40:36 +08:00
aff6cbccdc Merge branch 'feat/GHZSCY-7948' into 'dev'
xpak兼容更多的apks安装

See merge request halo/android/assistant-android!2193
2025-05-15 16:31:15 +08:00
b19b6960ac xpak兼容更多的apks安装 2025-05-15 16:31:15 +08:00
c60b317125 Merge branch 'feat/GHZSCY-7492' into 'dev'
feat: 游戏礼包新增领取条件 https://jira.shanqu.cc/browse/GHZSCY-7492

See merge request halo/android/assistant-android!2192
2025-05-15 15:32:39 +08:00
4fbb9d0c88 feat: 游戏礼包新增领取条件 https://jira.shanqu.cc/browse/GHZSCY-7492 2025-05-15 15:12:20 +08:00
f039d71562 Merge branch 'feat/install-external' into 'dev'
feat: 从SD卡兼容所有文件管理权限

See merge request halo/android/assistant-android!2191
2025-05-15 14:52:45 +08:00
fd5a3104c4 Merge branch 'fix/game_detail_crash' into 'release'
fix: 修复游戏详情页滚动到顶部的偶发闪退...

See merge request halo/android/assistant-android!2188
2025-05-14 14:20:11 +08:00
486dd57a24 fix: 修复游戏详情页滚动到顶部的偶发闪退 https://sentry.shanqu.cc/organizations/lightgame/issues/444460/?project=22&referrer=issue-stream&statsPeriod=14d 2025-05-14 14:07:36 +08:00
058b51f050 chore: 版本更新至 5.41.2 2025-05-14 13:57:16 +08:00
149a49fdd8 Merge branch 'cherry-pick-380939b8' into 'release'
Merge branch 'fix/GHZSCY-7971-pr' into 'dev'

See merge request halo/android/assistant-android!2187
2025-05-14 13:50:11 +08:00
4314797166 Merge branch 'fix/GHZSCY-7971-pr' into 'dev' 2025-05-14 13:50:11 +08:00
09e439f143 feat: https://jira.shanqu.cc/browse/GHZSCY-7638 替换新版插件 2025-05-13 15:20:46 +08:00
c23a78a546 feat: https://jira.shanqu.cc/browse/GHZSCY-7638 https://jira.shanqu.cc/browse/GHZSCY-7879 https://jira.shanqu.cc/browse/GHZSCY-7972 打包测试 2025-05-13 14:45:21 +08:00
d3351ec0b6 feat: 读取汉化接口配置 https://jira.shanqu.cc/browse/GHZSCY-7642 2025-04-08 16:34:19 +08:00
e769c0e5f5 fix: 修复初始化汉化进程导致unity游戏卡屏的问题 https://jira.shanqu.cc/browse/GHZSCY-7642 2025-04-07 10:08:35 +08:00
bb3a849671 feat: 修复用户没有下载新的畅玩游戏,插件更新没有触发的问题。 https://jira.shanqu.cc/browse/GHZSCY-7642 2025-03-27 16:08:26 +08:00
4ed1e1e64a feat: 汉化打包测试 https://jira.shanqu.cc/browse/GHZSCY-7642 2025-03-27 13:44:54 +08:00
9f4eaa67fa feat: 优化汉化UI 2025-03-27 10:48:08 +08:00
655173482a feat: 畅玩汉化功能 2025-03-25 14:45:55 +08:00
239 changed files with 4999 additions and 3205 deletions

View File

@ -157,3 +157,4 @@ oss-upload&send-email:
only:
- dev
- release

View File

@ -154,7 +154,7 @@ android {
zipAlignEnabled false
signingConfig signingConfigs.debug
buildConfigField "String", "EXPOSURE_REPO", "\"test\""
buildConfigField "String", "EXPOSURE_REPO", "\"exposure\""
buildConfigField "String", "EXPOSURE_VERSION", "\"E4\""
multiDexKeepProguard file("tinker_multidexkeep.pro")

View File

@ -5,6 +5,7 @@ import android.app.Activity
import android.content.Context
import android.content.SharedPreferences
import android.graphics.drawable.Animatable
import android.os.Build
import android.os.Message
import android.text.TextUtils
import android.view.View
@ -43,6 +44,7 @@ import com.gh.gamecenter.feature.exposure.ExposureType
import com.gh.gamecenter.retrofit.RetrofitManager
import com.halo.assistant.HaloApp
import io.reactivex.schedulers.Schedulers
import java.util.Locale
/**
* 广告实现代理类
@ -120,7 +122,12 @@ object AdDelegateHelper {
if (isFromRetry && mAdConfigList.isNotEmpty()) {
return
}
val paramsMap = if (keyword.isNotEmpty()) mapOf("keyword" to keyword) else mapOf()
val paramsMap = if (keyword.isNotEmpty()) {
mapOf("keyword" to keyword, "android_sdk_version" to Build.VERSION.SDK_INT)
} else {
mapOf("android_sdk_version" to Build.VERSION.SDK_INT)
}
RetrofitManager.getInstance()
.newApi
.getAdConfig(paramsMap)
@ -182,11 +189,16 @@ object AdDelegateHelper {
// HarmonyOS 2.2.0 版本不展示第三方开屏广告 (因为会引起奇怪的闪退)
if (MetaUtil.getRom().name == "HarmonyOS"
&& MetaUtil.getRom().versionName == "2.2.0"
&& config.displayRule.adSource == "third_party_ads") {
&& config.displayRule.adSource == AD_TYPE_SDK) {
return
}
// 华为系 Android 10 不展示第三方开屏广告 (因为会引起奇怪的闪退)
if (isBuggyHuaweiDevice() && config.displayRule.adSource == AD_TYPE_SDK) {
return
}
mSplashAd = config
}
@ -215,6 +227,7 @@ object AdDelegateHelper {
private fun shouldShowStartUpAdWhenHotLaunch() = (mCsjAdImpl != null)
&& mSplashAd?.displayRule?.hotStartSplashAd?.type == AD_TYPE_SDK
&& mSplashAd?.hotStartThirdPartyAd != null
&& !isBuggyHuaweiDevice()
/**
* 是否需要显示下载管理广告
@ -609,32 +622,32 @@ object AdDelegateHelper {
), null, null
)
adImage.visibleIf(true)
ImageUtils.displayWithCallback(adImage, ad.img, true, object : BaseControllerListener<ImageInfo>() {
override fun onSubmit(id: String?, callerContext: Any?) {
super.onSubmit(id, callerContext)
adImage.post {
ownerSplashAdLoadTime = System.currentTimeMillis()
NewFlatLogUtils.logSplashAdLoad(ad.id)
}
}
override fun onFinalImageSet(id: String?, imageInfo: ImageInfo?, animatable: Animatable?) {
isOwnerSplashAdShown = true
adImage.post {
NewFlatLogUtils.logSplashAdShow(ad.id, System.currentTimeMillis() - ownerSplashAdLoadTime)
}
}
override fun onFailure(id: String?, throwable: Throwable?) {
super.onFailure(id, throwable)
NewFlatLogUtils.logSplashAdFail(ad.id, "启动广告图加载失败")
}
})
if (ad.isImageType) {
adVideo.visibleIf(false)
adImage.visibleIf(true)
ImageUtils.displayWithCallback(adImage, ad.img, true, object : BaseControllerListener<ImageInfo>() {
override fun onSubmit(id: String?, callerContext: Any?) {
super.onSubmit(id, callerContext)
adImage.post {
ownerSplashAdLoadTime = System.currentTimeMillis()
NewFlatLogUtils.logSplashAdLoad(ad.id)
}
}
override fun onFinalImageSet(id: String?, imageInfo: ImageInfo?, animatable: Animatable?) {
isOwnerSplashAdShown = true
adImage.post {
NewFlatLogUtils.logSplashAdShow(ad.id, System.currentTimeMillis() - ownerSplashAdLoadTime)
}
}
override fun onFailure(id: String?, throwable: Throwable?) {
super.onFailure(id, throwable)
NewFlatLogUtils.logSplashAdFail(ad.id, "启动广告图加载失败")
}
})
} else {
adImage.visibleIf(false)
adVideo.startPlay(ad.video.url)
}
startAdContainer.setOnClickListener {
@ -783,4 +796,16 @@ object AdDelegateHelper {
mCsjAdImpl?.cancelSplashAd(context)
}
/**
* 是否为有问题的华为系 Android 10 设备
*/
private fun isBuggyHuaweiDevice(): Boolean {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
val manufacturer = Build.MANUFACTURER.lowercase(Locale.CHINA) ?: ""
return manufacturer == "huawei" || manufacturer == "honor"
} else {
return false
}
}
}

View File

@ -16,12 +16,10 @@ import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.common.base.fragment.BaseDialogFragment
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.FixLinearLayoutManager
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.core.utils.SPUtils.getBoolean
import com.gh.gamecenter.databinding.DialogReserveBinding
import com.gh.gamecenter.databinding.DialogReserveItemBinding
import com.gh.gamecenter.feature.entity.GameEntity

View File

@ -0,0 +1,54 @@
package com.gh.common.exposure
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.IExposureStateChangeListener
import com.gh.gamecenter.feature.exposure.RecyclerViewExposureHelper
import com.lightgame.utils.Utils
class DefaultExposureStateChangeListener : IExposureStateChangeListener {
override fun onExposureStateChange(
exposureEvent: ExposureEvent,
position: Int,
inExposure: Boolean
) {
val exposureStatus = if (inExposure) "曝光中" else "结束曝光"
val isCPMExposureEvent = exposureEvent.payload.miniGameType == Constants.WECHAT_MINI_GAME_CPM
Utils.log(
RecyclerViewExposureHelper.TAG,
"onExposureStateChange: 名称 ${exposureEvent.payload.gameName} 位置 $position, $exposureStatus"
)
// 如果是 CPM 曝光事件,且在曝光中,直接上报 CPM 曝光 (内部有做简单的去重处理CPM 曝光追求一个及时上报,不用理会曝光时长)
if (isCPMExposureEvent && inExposure) {
Utils.log(
RecyclerViewExposureHelper.TAG,
"上报 CPM 曝光 ${exposureEvent.payload.gameName} ${exposureEvent.id}"
)
ExposureManager.logCPM(exposureEvent)
}
if (!inExposure
&& System.currentTimeMillis() - exposureEvent.timeInMillisecond > DEFAULT_VALID_EXPOSURE_THRESHOLD
) {
Utils.log(
RecyclerViewExposureHelper.TAG,
"上报曝光 ${exposureEvent.payload.gameName} ${exposureEvent.id},是否为 CPM 小游戏 -> ${isCPMExposureEvent}" +
"曝光时长为 ${System.currentTimeMillis() - exposureEvent.timeInMillisecond}ms"
)
// 标记当前 ExposureEvent 已经被使用过
exposureEvent.markIsUsed()
ExposureManager.log(exposureEvent)
}
}
companion object {
// 默认曝光有效时长
const val DEFAULT_VALID_EXPOSURE_THRESHOLD = 1000L
}
}

View File

@ -4,12 +4,14 @@ import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.loghub.TLogHubHelper
import com.gh.gamecenter.common.utils.FixedSizeLinkedHashSet
import com.gh.gamecenter.common.utils.toJson
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.dsp.DspReportHelper
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.ExposureType
import com.gh.gamecenter.feature.minigame.wechat.WGameSubjectCPMListReportHelper
import com.google.gson.ExclusionStrategy
import com.google.gson.FieldAttributes
import com.google.gson.GsonBuilder
import com.lightgame.utils.Utils
import com.volcengine.model.tls.LogItem
@ -31,6 +33,20 @@ object ExposureManager {
private val exposureSet by lazy { hashSetOf<ExposureEvent>() }
private val exposureCache by lazy { FixedSizeLinkedHashSet<String>(300) }
private val gson by lazy {
GsonBuilder()
.addSerializationExclusionStrategy(object: ExclusionStrategy {
override fun shouldSkipClass(clazz: Class<*>): Boolean {
return false
}
override fun shouldSkipField(f: FieldAttributes): Boolean {
return f.name == "additional"
}
})
.create()
}
/**
* Log a single exposure event.
*/
@ -86,6 +102,15 @@ object ExposureManager {
}
}
/**
* Log a wechat mini game cpm collection of exposure event.
*/
fun logCPM(event: ExposureEvent) {
AppExecutor.logExecutor.execute {
WGameSubjectCPMListReportHelper.reportExposure(event)
}
}
/**
* @param forcedUpload Ignore all restrictions.
*/
@ -114,14 +139,14 @@ object ExposureManager {
private fun buildLog(event: ExposureEvent) = LogItem(System.currentTimeMillis()).apply {
addContent("__id", event.id)
addContent("payload", event.payload.toJson())
addContent("payload", gson.toJson(event.payload))
addContent("event", event.event.toString())
addContent("source", eliminateMultipleBrackets(event.source.toJson()))
addContent("meta", event.meta.toJson())
addContent("source", eliminateMultipleBrackets(gson.toJson(event.source)))
addContent("meta", gson.toJson(event.meta))
addContent("real_millisecond", event.timeInMillisecond.toString())
addContent(
"e-traces", if (event.eTrace != null) {
eliminateMultipleBrackets(event.eTrace?.toJson() ?: "")
eliminateMultipleBrackets(gson.toJson(event.eTrace))
} else ""
)
}

View File

@ -8,13 +8,13 @@ object ExposureTraceUtils {
val traceList = arrayListOf<ExposureEvent>()
event?.let {
//这里使用deepCopy是为了防止循环引用调用hashCode方法触发StackOverflowError错误
val deepCopy = it.deepCopy()
if (deepCopy.eTrace == null) {
traceList.add(deepCopy)
//这里使用 copy是为了防止循环引用调用hashCode方法触发StackOverflowError错误
val exposureCopy = it.shallowCopy()
if (exposureCopy.eTrace == null) {
traceList.add(exposureCopy)
} else {
traceList.addAll(deepCopy.eTrace!!)
traceList.add(flattenTrace(deepCopy))
traceList.addAll(exposureCopy.eTrace!!)
traceList.add(flattenTrace(exposureCopy))
}
}

View File

@ -5,6 +5,7 @@ import com.gh.common.util.DirectUtils
import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.common.entity.LaunchRedirect
import com.gh.gamecenter.common.entity.LaunchRedirectWrapper
import com.gh.gamecenter.common.pagelevel.PageLevelManager
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.core.AppExecutor
@ -65,9 +66,11 @@ class LaunchRedirectHandler(priority: Int) : PriorityChainHandler(priority) {
override fun onProcess(): Boolean {
val currentActivity = CurrentActivityHolder.getCurrentActivity()
if (GlobalPriorityChainHelper.isThisActivityValid(currentActivity)) {
if (GlobalPriorityChainHelper.isThisActivityValid(currentActivity) && launchData?.type != "bottom_tab") {
if (getStatus() == STATUS_VALID) {
// 当 type 为 "bottom_tab" 时上面 doPreProcess 中已经处理过了,但再选中一次好像也没有什么问题,先不特殊处理这个 case 了
// 设置下一个页面为顶级页面
PageLevelManager.setNextPageAsSupremePageLevel()
DirectUtils.directToLinkPage(currentActivity!!, launchData!!, "首次启动跳转", "")
// 跳转页面不管回调,延迟 500ms 后执行下一个 handler
AppExecutor.uiExecutor.executeWithDelay({

View File

@ -30,6 +30,8 @@ class DownloadButtonClickedProviderImpl : IDownloadButtonClickedProvider {
var packageName = ""
var exposureSourceList: List<ExposureSource>? = null
var customPageTrackData: CustomPageTrackData? = null
var isAd = false
var adGroupId = ""
val pushMessageId = (HaloApp.get(Constants.PUSH_MESSAGE_ID, false) as? String) ?: ""
val pushLinkId = (HaloApp.get(Constants.PUSH_LINK_ENTITY, false) as? LinkEntity)?.link ?: ""
val isFromPush = pushMessageId.isNotEmpty()
@ -60,6 +62,8 @@ class DownloadButtonClickedProviderImpl : IDownloadButtonClickedProvider {
packageName = boundedObject.getUniquePackageName() ?: ""
exposureSourceList = boundedObject.exposureEvent?.source
customPageTrackData = boundedObject.customPageTrackData
isAd = boundedObject.adGroupId.isNotEmpty()
adGroupId = boundedObject.adGroupId
}
is GameUpdateEntity -> {
@ -144,6 +148,8 @@ class DownloadButtonClickedProviderImpl : IDownloadButtonClickedProvider {
"last_page_name", GlobalActivityManager.getLastPageEntity().pageName,
"last_page_id", GlobalActivityManager.getLastPageEntity().pageId,
"last_page_business_id", GlobalActivityManager.getLastPageEntity().pageBusinessId,
"is_ad", isAd.toString(),
"ad_group_id", adGroupId,
"is_from_push_notifications", isFromPush,
"message_id", pushMessageId,
"link_id", pushLinkId,

View File

@ -1,9 +1,6 @@
package com.gh.common.provider
import android.content.Context
import com.therouter.router.Route
import com.gh.common.exposure.ExposureManager
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.provider.IExposureManagerProvider
@ -12,4 +9,8 @@ class ExposureManagerProviderImpl: IExposureManagerProvider {
override fun logExposure(exposureEvent: ExposureEvent) {
ExposureManager.log(exposureEvent)
}
override fun logExposureList(exposureEventList: List<ExposureEvent>) {
ExposureManager.log(exposureEventList)
}
}

View File

@ -1,8 +1,20 @@
package com.gh.common.util
import android.annotation.SuppressLint
import android.text.TextUtils
import com.gh.common.constant.Config
import com.gh.gamecenter.common.entity.IdfaData
import com.gh.gamecenter.common.pagelevel.IPageLevelProvider
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.tracker.IBusiness
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.core.utils.CurrentActivityHolder
import com.gh.gamecenter.entity.StartupAdEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.SettingsEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.retrofit.RetrofitManager
import io.reactivex.schedulers.Schedulers
object AdHelper {
@ -12,6 +24,102 @@ object AdHelper {
const val LOCATION_SUGGESTION_FUNCTION = "suggestion_function"
const val LOCATION_SIMULATOR_GAME = "simulator_game"
// 广告标识 https://jira.shanqu.cc/browse/GHZSCY-7041
private var idfa = ""
@SuppressLint("CheckResult")
@JvmStatic
fun getIdfa(versionName: String, channel: String) {
RetrofitManager.getInstance().newApi.getIdfa(versionName, channel)
.subscribeOn(Schedulers.io())
.subscribe(object : BiResponse<IdfaData>() {
override fun onSuccess(data: IdfaData) {
idfa = data.AD
SensorsBridge.trackGetAdTag(data.AD)
}
override fun onFailure(exception: Exception) {
super.onFailure(exception)
SensorsBridge.trackGetAdTag("")
}
})
}
fun getIdfaString() : String {
return idfa
}
/**
* 记录广告游戏填充
*/
fun reportAdRequest(exposureEvent: ExposureEvent) {
val payload = exposureEvent.payload
NewFlatLogUtils.logAdRequest(
pageLevel = payload.pageLevel ?: "",
currentPageCode = payload.currentPageCode ?: "",
currentPageId = payload.currentPageId ?: "",
gameName = payload.gameName ?: "",
gameId = payload.gameId ?: "",
moduleType = payload.moduleType ?: "",
moduleStyle = payload.moduleStyle ?: "",
moduleName = payload.moduleName ?: "",
moduleId = payload.moduleId ?: "",
searchContent = payload.searchContent ?: "",
sequence = payload.sequence ?: -1,
outerSequence = payload.outerSequence ?: -1,
adGroupId = payload.adGroupId ?: "",
)
}
/**
* 记录广告游戏填充
*/
fun reportAdRequest(gameEntity: GameEntity) {
val currentActivity = CurrentActivityHolder.getCurrentActivity()
val subPageCode = gameEntity.subPageCode
val subPageId = gameEntity.subPageId
val currentActivitySimpleName = if (currentActivity != null) currentActivity::class.simpleName else "unknown"
val businessId = if (currentActivity is IBusiness) currentActivity.getBusinessId() else null
var currentPageCode = currentActivitySimpleName
var currentPageId = businessId?.first
// 子页面的 pageCode 添加到主页面后, pageId 优先级高于主页面 pageId
if (!subPageCode.isNullOrEmpty()) {
currentPageCode = "$currentActivitySimpleName-$subPageCode"
if (!subPageId.isNullOrEmpty()) {
currentPageId = subPageId
}
}
var pageLevelString: String? = gameEntity.pageLevelString
if (TextUtils.isEmpty(pageLevelString)) {
val pageLevelProvider = currentActivity as? IPageLevelProvider
pageLevelString = pageLevelProvider?.getPageLevel()?.toFormattedString()
}
NewFlatLogUtils.logAdRequest(
pageLevel = pageLevelString ?: "",
currentPageCode = currentPageCode ?: "",
currentPageId = currentPageId ?: "",
gameName = gameEntity.name ?: "",
gameId = gameEntity.id,
moduleStyle = gameEntity.customPageTrackData?.modulePattern ?: "",
moduleType = gameEntity.customPageTrackData?.moduleType ?: "",
moduleName = gameEntity.customPageTrackData?.linkContentName ?: "",
moduleId = gameEntity.customPageTrackData?.linkContentId ?: "",
searchContent = "",
sequence = gameEntity.sequence ?: -1,
outerSequence = gameEntity.outerSequence ?: -1,
adGroupId = gameEntity.adGroupId,
)
}
@JvmStatic
fun getStartUp(): StartupAdEntity? {
return Config.getNewApiSettingsEntity()?.startup

View File

@ -867,6 +867,8 @@ object DownloadItemUtils {
"last_page_id", getLastPageEntity().pageId,
"last_page_business_id", getLastPageEntity().pageBusinessId,
"source", gameEntity.exposureEvent?.source?.toString() ?: "",
"is_ad", if (gameEntity.adGroupId.isEmpty()) "false" else "true",
"ad_group_id", gameEntity.adGroupId,
*gameEntity.customPageTrackData?.toKV() ?: arrayOf()
)
allStateClickCallback?.onCallback()

View File

@ -501,6 +501,7 @@ object DownloadObserver {
val isPlatformRecommend =
java.lang.Boolean.parseBoolean(downloadEntity.getMetaExtra(Constants.IS_PLATFORM_RECOMMEND))
val adGroupId = downloadEntity.getMetaExtra(Constants.AD_GROUP_ID)
val exposureEvent = ExposureUtils.logADownloadCompleteExposureEvent(
GameEntity(
_id = downloadEntity.gameId,
@ -534,6 +535,8 @@ object DownloadObserver {
"game_id", downloadEntity.gameId,
"game_type", downloadEntity.categoryChinese,
"game_schema_type", if (downloadEntity.getMetaExtra(Constants.KEY_BIT) == "32") "32位" else "64位",
"is_ad", if (adGroupId.isEmpty()) "false" else "true",
"ad_group_id", adGroupId,
*kvs
)
} else if (downloadEntity.gameId == Constants.HALO_FUN_GAME_ID) {
@ -598,6 +601,8 @@ object DownloadObserver {
if (downloadEntity.asVGame()) "畅玩下载" else "本地下载",
"is_from_push_notifications",
isFromPush,
"is_ad", if (adGroupId.isEmpty()) "false" else "true",
"ad_group_id", adGroupId,
"message_id",
pushMessageId,
"link_id",

View File

@ -99,6 +99,11 @@ object GameSubstituteRepositoryHelper {
continue
}
// 广告系统的广告游戏不替换
if (game.adGroupId.isNotEmpty()) {
continue
}
// 这个 position 的游戏是否需要被替换
var thisPositionNeedToBeReplaced = false

View File

@ -324,7 +324,19 @@ public class LibaoUtils {
libaoBtn.setText(com.gh.gamecenter.feature.R.string.libao_finish);
libaoBtn.setBackgroundResource(R.drawable.button_border_round_gray);
libaoBtn.setTextColor(context.getResources().getColor(com.gh.gamecenter.common.R.color.button_gray));
libaoBtn.setOnClickListener(v -> ToastUtils.toast("兑换码领取已结束"));
libaoBtn.setOnClickListener(v ->
{
ToastUtils.toast("兑换码领取已结束");
SensorsBridge.trackEvent("GameGiftDraw",
"gift_type", "兑换码",
"game_name", libaoEntity.getGame().getName(),
"game_id", libaoEntity.getGame().getId(),
"gift_id", libaoEntity.getId(),
"gift_name", libaoEntity.getName(),
"source_entrance", sourceEntrance,
"button_name", libaoBtn.getText().toString()
);
});
} else {
libaoBtn.setText(R.string.libao_copy);
libaoBtn.setTextColor(ExtensionsKt.toColor(com.gh.gamecenter.common.R.color.white, context));
@ -338,6 +350,23 @@ public class LibaoUtils {
libaoEntity.getGame().getId(),
libaoEntity.getGame().getName()
);
SensorsBridge.trackEvent("GameGiftDraw",
"gift_type", "兑换码",
"game_name", libaoEntity.getGame().getName(),
"game_id", libaoEntity.getGame().getId(),
"gift_id", libaoEntity.getId(),
"gift_name", libaoEntity.getName(),
"source_entrance", sourceEntrance,
"button_name", libaoBtn.getText().toString()
);
SensorsBridge.trackEvent("GameGiftDrawResult",
"gift_type", "兑换码",
"game_name", libaoEntity.getGame().getName(),
"game_id", libaoEntity.getGame().getId(),
"gift_id", libaoEntity.getId(),
"gift_name", libaoEntity.getName(),
"source_entrance", sourceEntrance
);
ExtensionsKt.copyTextAndToast(libaoEntity.getCode(), libaoEntity.getToast());
});
}
@ -346,6 +375,25 @@ public class LibaoUtils {
libaoBtn.setOnClickListener(v -> {
String btnStatus = libaoBtn.getText().toString();
String giftType;
if ("ling".equals(libaoEntity.getStatus()) || "linged".equals(libaoEntity.getStatus())) {
giftType = "普通礼包";
} else if ("copy".equals(libaoEntity.getReceiveMethod())) {
giftType = "兑换码";
} else {
giftType = "淘号礼包";
}
SensorsBridge.trackEvent("GameGiftDraw",
"gift_type", giftType,
"game_name", libaoEntity.getGame().getName(),
"game_id", libaoEntity.getGame().getId(),
"gift_id", libaoEntity.getId(),
"gift_name", libaoEntity.getName(),
"source_entrance", sourceEntrance,
"button_name", btnStatus
);
// 领取限制
CheckLoginUtils.checkLogin(context, "礼包详情-[" + btnStatus + "]", () -> {
if ("领取".equals(btnStatus) || "淘号".equals(btnStatus)) {
@ -408,27 +456,11 @@ public class LibaoUtils {
} else {
libaoLing(context, libaoBtn, libaoEntity, adapter, isInstallRequired, null, entrance, sourceEntrance, listener);
}
SensorsBridge.trackEvent("GameGiftDraw",
"gift_type", "普通礼包",
"game_name", libaoEntity.getGame().getName(),
"game_id", libaoEntity.getGame().getId(),
"gift_id", libaoEntity.getId(),
"gift_name", libaoEntity.getName(),
"source_entrance", sourceEntrance
);
break;
case "再淘":
case "再淘一个":
case "淘号":
libaoTao(context, libaoBtn, libaoEntity, isInstallRequired, adapter, status, entrance, sourceEntrance, listener);
SensorsBridge.trackEvent("GameGiftDraw",
"gift_type", "淘号礼包",
"game_name", libaoEntity.getGame().getName(),
"game_id", libaoEntity.getGame().getId(),
"gift_id", libaoEntity.getId(),
"gift_name", libaoEntity.getName(),
"source_entrance", sourceEntrance
);
break;
}
});
@ -515,6 +547,7 @@ public class LibaoUtils {
try {
JSONObject errorJson = new JSONObject(exception.response().errorBody().string());
String detail = errorJson.getString("detail").toLowerCase();
int code = errorJson.getInt("code");
switch (detail) {
case "coming":
Utils.toast(context, "礼包领取时间未开始");
@ -545,7 +578,13 @@ public class LibaoUtils {
Utils.toast(context, "淘号失败,稍后重试");
break;
default:
Utils.toast(context, "操作失败");
if (code == 403211) {
Utils.toast(context, "条件不符,请先提交游戏评价");
} else if (code == 403212) {
Utils.toast(context, "条件不符,请修改游戏评价");
} else {
Utils.toast(context, "操作失败");
}
break;
}
@ -663,6 +702,7 @@ public class LibaoUtils {
String string = exception.response().errorBody().string();
JSONObject errorJson = new JSONObject(string);
String detail = errorJson.getString("detail").toLowerCase();
int code = errorJson.getInt("code");
switch (detail) {
case "coming":
Utils.toast(context, "礼包领取时间未开始");
@ -702,7 +742,13 @@ public class LibaoUtils {
Utils.toast(context, "网络状态异常,请稍后再试");
break;
default:
Utils.toast(context, "操作失败");
if (code == 403211) {
Utils.toast(context, "条件不符,请先提交游戏评价");
} else if (code == 403212) {
Utils.toast(context, "条件不符,请修改游戏评价");
} else {
Utils.toast(context, "操作失败");
}
break;
}
} catch (Exception ex) {

View File

@ -2779,6 +2779,44 @@ object NewFlatLogUtils {
}.let(::log)
}
/**
* 广告游戏填充
* 广告游戏填充到广告位置时,上报该广告游戏插入的位置信息(用于数据回收验证策略的效果,无须曝光,只要请求了广告,就上报)
*/
fun logAdRequest(
pageLevel: String,
currentPageCode: String,
currentPageId: String,
gameName: String,
gameId: String,
moduleType: String,
moduleStyle: String,
moduleName: String,
moduleId: String,
searchContent: String,
sequence: Int,
outerSequence: Int,
adGroupId: String,
) {
json {
KEY_EVENT to "ad_request"
"page_level" to pageLevel
"current_page_code" to currentPageCode
"current_page_id" to currentPageId
KEY_GAME_NAME to gameName
KEY_GAME_ID to gameId
"module_type" to moduleType
"module_style" to moduleStyle
"module_name" to moduleName
"module_id" to moduleId
"search_content" to searchContent
"sequence" to sequence
"outer_sequence" to outerSequence
"ad_group_id" to adGroupId
parseAndPutMeta()(this)
}.let(::log)
}
// 自有开屏广告加载
fun logSplashAdLoad(id: String) {
json {

View File

@ -64,8 +64,6 @@ object PackageInstaller {
showUnzipToast: Boolean,
ignoreAsVGame: Boolean,
) {
val pkgPath = downloadEntity.path
val isXapk = XapkInstaller.XAPK_EXTENSION_NAME == pkgPath.getExtension()
val isDownloadAsVGame = downloadEntity.getMetaExtra(Constants.EXTRA_DOWNLOAD_TYPE) == Constants.VGAME
|| downloadEntity.getMetaExtra(Constants.EXTRA_DOWNLOAD_TYPE) == Constants.DUAL_DOWNLOAD_VGAME
@ -99,7 +97,7 @@ object PackageInstaller {
context.startActivity(mainIntent)
Runtime.getRuntime().exit(0)
} else {
if (isXapk) {
if (XapkInstaller.isXapk(downloadEntity)) {
XapkInstaller.install(context, downloadEntity, showUnzipToast)
} else {
install(context, downloadEntity.isPlugin, downloadEntity.path, downloadEntity)
@ -211,9 +209,6 @@ object PackageInstaller {
pkgPath: String,
sessionId: Int = -1
) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return
}
val installer = context.packageManager.packageInstaller
val session = installer.openSession(sessionId)
// 监听安装回调的组件可以是Activity、Service或者是BroadcastReceiver

View File

@ -80,6 +80,8 @@ object ReservationHelper {
"last_page_id", getLastPageEntity().pageId,
"last_page_business_id", getLastPageEntity().pageBusinessId,
"source", game?.exposureEvent?.source?.toString() ?: "",
"is_ad", if (game?.adGroupId.isNullOrEmpty() == true) "false" else "true",
"ad_group_id", game?.adGroupId ?: "",
*game?.customPageTrackData?.toKV() ?: arrayOf()
)
@ -118,6 +120,8 @@ object ReservationHelper {
"last_page_id", getLastPageEntity().pageId,
"last_page_business_id", getLastPageEntity().pageBusinessId,
"source", game?.exposureEvent?.source?.toString() ?: "",
"is_ad", if (game?.adGroupId.isNullOrEmpty() == true) "false" else "true",
"ad_group_id", game?.adGroupId ?: "",
*game?.customPageTrackData?.toKV() ?: arrayOf()
)
ToastUtils.showToast(exception.message ?: "")

View File

@ -20,6 +20,7 @@ import com.gh.gamecenter.common.utils.setDrawableEnd
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.google.android.flexbox.FlexboxLayout
import splitties.views.leftPadding
class ConfigFilterView @JvmOverloads constructor(
context: Context,
@ -213,6 +214,10 @@ class ConfigFilterView @JvmOverloads constructor(
fun initSubjectFilterView(subjectSetting: SubjectSettingEntity) {
ratingTv.visibility = View.VISIBLE
if (subjectSetting.typeEntity.layout == "hide") {
container.leftPadding = 8F.dip2px()
}
if (subjectSetting.filterOptions.size > 1) {
// 重排序
subjectSetting.filterOptions.forEachIndexed { index, s ->

View File

@ -1,15 +0,0 @@
package com.gh.common.xapk
import com.lightgame.download.DownloadEntity
interface IXapkUnzipListener {
fun onProgress(downloadEntity: DownloadEntity, unzipPath: String, unzipSize: Long, unzipProgress: Long)
fun onNext(downloadEntity: DownloadEntity, unzipPath: String)
fun onCancel(downloadEntity: DownloadEntity)
fun onFailure(downloadEntity: DownloadEntity, exception: Exception)
fun onSuccess(downloadEntity: DownloadEntity)
}

View File

@ -52,6 +52,12 @@ import com.gh.gamecenter.core.utils.SPUtils
*
* obb文件直接解压至根目录即可,无需操作文件
* apk文件这解压的gh-files文件夹中
*
* update: 2025/4/29
* 简单来说有两种XAPK文件的格式
* XAPK = Android App Bundle基础APK + 配置APK) 【downloadentity.format == xapk(apks)】
* XAPK = APK文件 + OBB数据文件 【downloadentity.format == xapk】
*
*/
@SuppressLint("StaticFieldLeak")
object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
@ -92,85 +98,90 @@ object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
private val mPendingSessionInfoMap = HashMap<String, XapkPendingSessionInfo>()
fun isXapk(downloadEntity: DownloadEntity) = XAPK_EXTENSION_NAME == downloadEntity.path.getExtension()
private fun isXapkApks(downloadEntity: DownloadEntity) = downloadEntity.format == Constants.XAPK_APKS_FORMAT
/**
* precondition: assert_true(isXapk())
*
*/
// 按并行解压
@JvmStatic
fun install(context: Context, downloadEntity: DownloadEntity, showUnzipToast: Boolean = false) {
this.mContext = context
val filePath = downloadEntity.path
if (XAPK_EXTENSION_NAME == filePath.getExtension()) {
if (MiuiUtils.isMiui() && !MiuiUtils.isMiuiOptimizationDisabled() && downloadEntity.format == Constants.XAPK_APKS_FORMAT) {// 小米手机开启miui以后需要引导用户关闭miui优化
DialogHelper.showMiuiOptimizationWarning(
context,
downloadEntity,
onHintClick = {
val guides = Config.getNewApiSettingsEntity()?.install
val miuiOptimizationGuide = guides?.guides?.findLast {
it.type == GUIDE_TYPE_MIUI_OPTIMIZATION
}
if (miuiOptimizationGuide != null) {
DirectUtils.directToLinkPage(
context,
miuiOptimizationGuide.link,
MIUI_OPTIMIZATION_WARNING_DIALOG_ENTRANCE,
""
)
}
},
onConfirmClick = {
if (SystemUtils.isDevelopmentSettingsEnabled(context)) {
context.startActivity(
Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
it.markDismissByTouchInside()
it.dismiss()
} else {
ToastUtils.showToast(context.getString(R.string.miui_open_adb_hint))
}
val isXapkApks = isXapkApks(downloadEntity)
if (isXapkApks && MiuiUtils.isMiui() && !MiuiUtils.isMiuiOptimizationDisabled()) {
// 小米手机开启miui以后需要引导用户关闭miui优化
DialogHelper.showMiuiOptimizationWarning(
context,
downloadEntity,
onHintClick = {
val guides = Config.getNewApiSettingsEntity()?.install
val miuiOptimizationGuide = guides?.guides?.findLast {
it.type == GUIDE_TYPE_MIUI_OPTIMIZATION
}
)
return
}
PermissionHelper.checkManageAllFilesOrStoragePermissionBeforeAction(context) {
val unzipAction = {
DownloadManager.getInstance().getDownloadEntitySnapshot(downloadEntity.url, downloadEntity.gameId)
?.let {
unzipXapkFile(it)
if (showUnzipToast) {
Utils.toast(mContext, "解压过程请勿退出光环助手!")
}
}
}
// XAPK (apks) 格式,不在乎 obb 文件夹是否可读
if (downloadEntity.format == Constants.XAPK_APKS_FORMAT) {
unzipAction.invoke()
return@checkManageAllFilesOrStoragePermissionBeforeAction
}
// 以 file 的方式访问 obb 文件夹是否可行
val isFileStyleObbFolderReadable =
if (systemHasFlaw) {
File(Environment.getExternalStorageDirectory().path, "\u200bAndroid/obb").list() != null
if (miuiOptimizationGuide != null) {
DirectUtils.directToLinkPage(
context,
miuiOptimizationGuide.link,
MIUI_OPTIMIZATION_WARNING_DIALOG_ENTRANCE,
""
)
}
},
onConfirmClick = {
if (SystemUtils.isDevelopmentSettingsEnabled(context)) {
context.startActivity(
Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS)
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
)
it.markDismissByTouchInside()
it.dismiss()
} else {
File(Environment.getExternalStorageDirectory().path, "Android/obb").list() != null
ToastUtils.showToast(context.getString(R.string.miui_open_adb_hint))
}
// 如果是文件夹风格的 obb 文件夹可读,或当前上下文不是 AppCompatActivity或当前上下文已经被销毁则直接解压
if (isFileStyleObbFolderReadable
|| context !is AppCompatActivity
|| context.isFinishing
) {
unzipAction.invoke()
} else {
unzipWithCulpritHandled(context, downloadEntity.url, unzipAction)
}
)
return
}
PermissionHelper.checkManageAllFilesOrStoragePermissionBeforeAction(context) {
val unzipAction = {
DownloadManager.getInstance().getDownloadEntitySnapshot(downloadEntity.url, downloadEntity.gameId)
?.let {
unzipXapkFile(it, isXapkApks)
if (showUnzipToast) {
Utils.toast(mContext, "解压过程请勿退出光环助手!")
}
}
}
// XAPK (apks) 格式,不在乎 obb 文件夹是否可读
if (isXapkApks) {
unzipAction.invoke()
return@checkManageAllFilesOrStoragePermissionBeforeAction
}
// 以 file 的方式访问 obb 文件夹是否可行
val isFileStyleObbFolderReadable =
if (systemHasFlaw) {
File(Environment.getExternalStorageDirectory().path, "\u200bAndroid/obb").list() != null
} else {
File(Environment.getExternalStorageDirectory().path, "Android/obb").list() != null
}
// 如果是文件夹风格的 obb 文件夹可读,或当前上下文不是 AppCompatActivity或当前上下文已经被销毁则直接解压
if (isFileStyleObbFolderReadable
|| context !is AppCompatActivity
|| context.isFinishing
) {
unzipAction.invoke()
} else {
unzipWithCulpritHandled(context, downloadEntity.url, unzipAction)
}
} else {
throwExceptionInDebug("如果是Apk包请使用PackageInstaller进行安装")
PackageInstaller.install(mContext, downloadEntity)
}
}
@ -250,13 +261,8 @@ object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
}
}
private fun unzipXapkFile(downloadEntity: DownloadEntity) {
mXApkUnZipper.unzip(
XApkUnZipEntry(
downloadEntity.path,
File(downloadEntity.path)
)
)
private fun unzipXapkFile(downloadEntity: DownloadEntity, isXapkApks: Boolean) {
XApkUnZipEntry(downloadEntity.path, File(downloadEntity.path), isXapkApks).let(mXApkUnZipper::unzip)
mDownloadEntityMap[downloadEntity.path] = downloadEntity
}
@ -469,13 +475,16 @@ object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
mPendingSessionInfoMap[downloadEntity.path] = XapkPendingSessionInfo(downloadEntity.path, sessionId)
AppExecutor.ioExecutor.execute {// 有可能卡顿造成anr
PackageInstaller.installMultiple(
applicationContext,
downloadEntity.packageName,
downloadEntity.path,
sessionId
)
NDataChanger.notifyDataChanged(downloadEntity)
try {
PackageInstaller.installMultiple(
applicationContext,
downloadEntity.packageName,
downloadEntity.path,
sessionId
)
NDataChanger.notifyDataChanged(downloadEntity)
} catch (ignore: Exception) {
}
}
}
}

View File

@ -1,243 +0,0 @@
package com.gh.common.xapk
import android.os.Build
import android.os.Environment
import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.MD5Utils
import com.halo.assistant.HaloApp
import com.lightgame.download.DownloadEntity
import com.lightgame.utils.Utils
import net.lingala.zip4j.progress.ProgressMonitor
import java.io.File
import java.util.zip.ZipFile
class XapkUnzipThread(
private var mDownloadEntity: DownloadEntity,
private var mUnzipListener: IXapkUnzipListener
) : Thread() {
private val mDefaultBufferSize = 1024 * 1024
var canceled = false
override fun run() {
super.run()
try {
val path = mDownloadEntity.path
var unzipSize = 0L
try {
unzipSize = getUnzipSize(path)
} catch (e: Exception) {
planB()
return
}
val msg = FileUtils.isCanDownload(HaloApp.getInstance().application, unzipSize)
if (!msg.isNullOrEmpty()) {
// 空间不足应该不用刷新页面,保持不变就好
Utils.toast(HaloApp.getInstance().application, "设备存储空间不足,请清理后重试!")
mUnzipListener.onCancel(mDownloadEntity)
return
}
var unzipProgress = 0L
val absolutePath = Environment.getExternalStorageDirectory().absolutePath
val xapkFile = File(path)
val unzipClosure: (zip: ZipFile) -> Unit = { zip ->
for (zipEntry in zip.entries().asSequence()) {
val outputFile = if (zipEntry.name.getExtension() == XapkInstaller.XAPK_DATA_EXTENSION_NAME) {
File(absolutePath + File.separator + zipEntry.name)
} else if (zipEntry.name.getExtension() == XapkInstaller.PACKAGE_EXTENSION_NAME) {
// apk文件名称 = xapk文件名 + MD5(本身的文件名称) + ".apk"
// 如 abc_com.gh.gamecenter.apk 这样的文件名,在使用浏览器安装时系统浏览器可能会抹掉文件类型,导致无法唤起下载完自动安装,这里去掉多个 "." 号,保证浏览器安装正常使用
val fileName = xapkFile.nameWithoutExtension + "_" + MD5Utils.getContentMD5(zipEntry.name) + "." + XapkInstaller.PACKAGE_EXTENSION_NAME
File(FileUtils.getDownloadPath(HaloApp.getInstance().application, fileName))
} else continue // 暂时只需要解压xpk/obb文件
if (zipEntry.isDirectory) {
if (!outputFile.exists()) {
throwException("unzip create file path failure", !outputFile.mkdirs())
}
continue
}
if (!outputFile.parentFile.exists()) {
throwException("unzip create file path failure", !outputFile.parentFile.mkdirs())
}
// 如果存在同名且大小一致,可以认为该文件已经解压缩(在不解压缩的情况下如果如果获取压缩文件的MD5????)
if (!outputFile.exists()) {
throwException("unzip create file failure", !outputFile.createNewFile())
} else if (outputFile.length() != zipEntry.size) {
throwException("unzip delete existing file failure", !outputFile.delete())
throwException("unzip create file failure", !outputFile.createNewFile())
} else {
unzipProgress += zipEntry.size
mUnzipListener.onProgress(mDownloadEntity, outputFile.path, unzipSize, unzipProgress)
mUnzipListener.onNext(mDownloadEntity, outputFile.path)
continue
}
// unzip
zip.getInputStream(zipEntry).use { input ->
outputFile.outputStream().use { output ->
val buffer = ByteArray(mDefaultBufferSize)
var bytes = input.read(buffer)
while (bytes >= 0) {
output.write(buffer, 0, bytes)
unzipProgress += bytes
bytes = input.read(buffer)
if (canceled) {
mUnzipListener.onCancel(mDownloadEntity)
return@use
} else {
// 防止多次短时间内多次触发onProgress方法导致阻塞主线程低端机会出现十分明显的卡顿
debounceActionWithInterval(-1, 500) {
mUnzipListener.onProgress(
mDownloadEntity,
outputFile.path,
unzipSize,
unzipProgress
)
}
}
}
}
}
mUnzipListener.onNext(mDownloadEntity, outputFile.path)
}
}
// Kotlin 1.4.X 在安卓 4.4 以下使用 use 默认关闭 ZipFile 的流时会触发
// java.lang.IncompatibleClassChangeError: interface not implemented 的 Error (Throwable)
// 但实测是不影响解压的,所以这里换用 let 不关闭流,确保不闪退,并且不影响解压结果
// 帮用户解压了,但游戏能不能安装就看天吧 (毕竟支持4.X的游戏也不多了)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
ZipFile(File(path)).use { unzipClosure.invoke(it) }
} else {
ZipFile(File(path)).let { unzipClosure.invoke(it) }
}
mUnzipListener.onSuccess(mDownloadEntity)
} catch (e: Exception) {
if (BuildConfig.DEBUG) throw e
mUnzipListener.onFailure(mDownloadEntity, e)
}
}
/**
* 部分未知情况(有可能是项目配置问题有可能时Zip包破损)原生的ZipFile无法打开压缩包,则启动以下备用方案
* 具体复现机型:小米 9(Android 9)
* 具体复现的压缩包:太空竞速2 具体可以咨询陈思雨
*
* 实现方式请参考:https://github.com/srikanth-lingala/zip4j
* 注意使用该库的ZipInputStream依然无法解压,提示头文件丢失
*
* 后续如果有需要可以直接使用该方法进行解压
*/
private fun planB() {
val zipPath = mDownloadEntity.path
val absolutePath = Environment.getExternalStorageDirectory().absolutePath
val zipFile = net.lingala.zip4j.ZipFile(zipPath)
val progressMonitor = zipFile.progressMonitor
zipFile.isRunInThread = true
val fileHeaders = zipFile.fileHeaders
var unzipSize = 0L
for (fileHeader in fileHeaders) {
unzipSize += fileHeader.uncompressedSize;
}
var unzipProgress = 0L
var completedSize = 0L
for (fileHeader in fileHeaders) {
if (canceled) {
mUnzipListener.onCancel(mDownloadEntity)
return
}
// 暂时只需要解压xpk/obb文件
val extension = fileHeader.fileName.getExtension()
if (extension != XapkInstaller.XAPK_DATA_EXTENSION_NAME && extension != XapkInstaller.PACKAGE_EXTENSION_NAME) continue
var unzipPath = ""
if (extension == XapkInstaller.XAPK_DATA_EXTENSION_NAME) {
unzipPath = absolutePath + File.separator + fileHeader.fileName
if (hasUnzipFile(unzipPath, fileHeader.uncompressedSize)) {
mUnzipListener.onNext(mDownloadEntity, unzipPath)
continue
}
zipFile.extractFile(fileHeader.fileName, absolutePath)
}
if (extension == XapkInstaller.PACKAGE_EXTENSION_NAME) {
val downloadDir = FileUtils.getDownloadDir(HaloApp.getInstance().application)
val unzipFileName = File(zipPath).nameWithoutExtension + "_" + fileHeader.fileName
unzipPath = downloadDir + File.separator + unzipFileName
if (hasUnzipFile(unzipPath, fileHeader.uncompressedSize)) {
mUnzipListener.onNext(mDownloadEntity, unzipPath)
continue
}
zipFile.extractFile(fileHeader.fileName, downloadDir, unzipFileName)
}
throwExceptionInDebug("check unzipPath", unzipPath.isEmpty())
// 回调太频繁了,变态吗? 4K回调一次,还不能改,FUCK
var filterCounter = 0
val filterInterval = 1024 * 10
while (progressMonitor.state != ProgressMonitor.State.READY) {
filterCounter++
if (filterCounter % filterInterval == 0) {
unzipProgress = completedSize + progressMonitor.workCompleted
mUnzipListener.onProgress(mDownloadEntity, unzipPath, unzipSize, unzipProgress)
if (canceled) {
progressMonitor.isCancelAllTasks = true
mUnzipListener.onCancel(mDownloadEntity)
return
}
}
}
completedSize += fileHeader.uncompressedSize
mUnzipListener.onNext(mDownloadEntity, unzipPath)
}
mUnzipListener.onSuccess(mDownloadEntity)
}
private fun hasUnzipFile(unzipPath: String, uncompressedSize: Long): Boolean {
val unzipFile = File(unzipPath)
if (unzipFile.exists() || unzipFile.length() == uncompressedSize) return true
return false
}
private fun getUnzipSize(path: String): Long {
var totalSize = 0L
val calculateSizeClosure: (zip: ZipFile) -> Unit = { zip ->
for (entry in zip.entries()) {
totalSize += entry.size
}
}
// Kotlin 1.4.X 在安卓 4.4 以下使用 use 默认ZipFile 的流时会触发
// java.lang.IncompatibleClassChangeError: interface not implemented 的 Error (Throwable)
// 实测是不影响解压,所以这里换用 let 不关闭流,确保不闪退
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
ZipFile(File(path)).use { calculateSizeClosure.invoke(it) }
} else {
ZipFile(File(path)).let { calculateSizeClosure.invoke(it) }
}
return totalSize
}
}

View File

@ -60,6 +60,7 @@ import com.gh.ndownload.NDownloadBridge;
import com.gh.ndownload.NDownloadService;
import com.gh.vspace.VHelper;
import com.halo.assistant.HaloApp;
import com.lg.vspace.archive.common.Const;
import com.lightgame.download.DataWatcher;
import com.lightgame.download.DownloadConfig;
import com.lightgame.download.DownloadDao;
@ -413,6 +414,9 @@ public class DownloadManager implements DownloadStatusListener {
// 将下载事件放入 downloadEntity 中供下载完成时取出使用
downloadEntity.setExposureTrace(GsonUtils.toJson(downloadExposureEvent));
// 记录广告组 id
ExtensionsKt.addMetaExtra(downloadEntity, Constants.AD_GROUP_ID, gameEntity.getAdGroupId());
// 保存所有游戏标签
List<String> tags = new ArrayList<>();
for (TagStyleEntity tag : gameEntity.getTagStyle()) {
@ -445,7 +449,9 @@ public class DownloadManager implements DownloadStatusListener {
"game_name", gameEntity.getName(),
"game_id", gameEntity.getId(),
"game_type", gameEntity.getCategoryChinese(),
"game_schema_type", gameEntity.getGameBitChinese()
"game_schema_type", gameEntity.getGameBitChinese(),
"is_ad", TextUtils.isEmpty(gameEntity.getAdGroupId()) ? "false" : "true",
"ad_group_id", gameEntity.getAdGroupId(),
};
List<String> vaList = new ArrayList<>(Arrays.asList(vaKvs));
if (customPageTrackData != null) {
@ -475,6 +481,8 @@ public class DownloadManager implements DownloadStatusListener {
"game_id", gameEntity.getId(),
"game_name", gameEntity.getName(),
"game_type", gameEntity.getCategoryChinese(),
"is_ad", TextUtils.isEmpty(gameEntity.getAdGroupId()) ? "false" : "true",
"ad_group_id", gameEntity.getAdGroupId(),
"game_label", String.join(",", tags),
"game_schema_type", gameEntity.getGameBitChinese(),
"page_name", GlobalActivityManager.getCurrentPageEntity().getPageName(),

View File

@ -76,6 +76,8 @@ import com.gh.gamecenter.common.entity.SuggestType;
import com.gh.gamecenter.common.eventbus.EBNetworkState;
import com.gh.gamecenter.common.eventbus.EBReuse;
import com.gh.gamecenter.common.exposure.meta.MetaUtil;
import com.gh.gamecenter.common.pagelevel.PageLevel;
import com.gh.gamecenter.common.pagelevel.PageLevelManager;
import com.gh.gamecenter.common.retrofit.BiResponse;
import com.gh.gamecenter.common.retrofit.Response;
import com.gh.gamecenter.common.utils.DialogHelper;
@ -88,7 +90,6 @@ import com.gh.gamecenter.core.AppExecutor;
import com.gh.gamecenter.core.utils.ClassUtils;
import com.gh.gamecenter.core.utils.DisplayUtils;
import com.gh.gamecenter.core.utils.GsonUtils;
import com.gh.gamecenter.core.utils.MtaHelper;
import com.gh.gamecenter.core.utils.SPUtils;
import com.gh.gamecenter.core.utils.ToastUtils;
import com.gh.gamecenter.core.utils.UrlFilterUtils;
@ -228,9 +229,6 @@ public class MainActivity extends BaseActivity {
},
() -> {
DirectUtils.directToSuggestion(MainActivity.this, SuggestType.APP, "APP闪退", false, 100);
MtaHelper.onEventWithBasicDeviceInfo(
"闪退弹窗",
"玩家操作", "点击反馈");
return null;
});
} else {
@ -238,17 +236,9 @@ public class MainActivity extends BaseActivity {
, "暂不", "马上反馈",
() -> {
DirectUtils.directToSuggestion(MainActivity.this, SuggestType.APP, "APP闪退", false, 100);
MtaHelper.onEventWithBasicDeviceInfo(
"闪退弹窗",
"玩家操作", "点击反馈");
return null;
},
() -> {
MtaHelper.onEventWithBasicDeviceInfo(
"闪退弹窗",
"玩家操作", "点击关闭");
return null;
});
() -> null);
}
}
@ -393,7 +383,6 @@ public class MainActivity extends BaseActivity {
HistoryHelper.deleteAttentionVideoRecord();
}
});
}
}
@ -1110,6 +1099,28 @@ public class MainActivity extends BaseActivity {
}
}
@Override
public void initPageLevel(@Nullable Bundle savedInstanceState) {
if (savedInstanceState != null) {
if (savedInstanceState.getParcelable(PageLevelManager.KEY_PAGE_LEVEL) != null) {
mPageLevel = savedInstanceState.getParcelable(PageLevelManager.KEY_PAGE_LEVEL);
}
}
if (mPageLevel == null) {
mPageLevel = new PageLevel(
PageLevel.TYPE_T,
-1,
-1,
new HashMap<>(),
-1,
null);
}
PageLevelManager.INSTANCE.setCurrentPageLevel(mPageLevel);
}
@Override
public Pair<String, String> getBusinessId() {
if (mMainWrapperFragment != null) {

View File

@ -330,6 +330,9 @@ class SplashScreenActivity : BaseActivity(), ISplashScreen {
}
}
override fun initPageLevel(savedInstanceState: Bundle?) {
// do nothing
}
companion object {
private const val KEY_REGISTRATION_ID = "registration_id"

View File

@ -422,9 +422,66 @@ public class LibaoDetailAdapter extends BaseRecyclerAdapter<ViewHolder> {
ExtensionsKt.setDrawableEnd(holder.binding.libaodetailCondition, com.gh.gamecenter.common.R.drawable.ic_libao_activity_arrow, null, null);
holder.binding.libaodetailCondition.setCompoundDrawablePadding(DisplayUtils.dip2px(4F));
}
} else if (mLibaoDetailEntity.getReceiveCondition() != null) {
holder.binding.libaodetailCondition.setVisibility(View.VISIBLE);
holder.binding.libaodetailConditionDescTv.setVisibility(View.VISIBLE);
holder.binding.libaodetailCondition.setText("领取条件:");
holder.binding.libaodetailConditionDescTv.setText(
getConditionDescText(
mGameEntity.getName(),
mLibaoDetailEntity.getReceiveCondition().getStar(),
mLibaoDetailEntity.getReceiveCondition().getWords()
)
);
}
}
/**
* 根据评分和字数限制生成文案。
*
* @param gameName 游戏名称
* @param star 评分选项 (-1: 无限制, 5: 5星好评, 4: 4星及以上, 3: 3星及以上, 2: 2星及以上, 1: 1星及以上)
* @param words 字数限制 (-1: 无限制, n: 字数不少于n)
* @return 生成的文案
*/
private String getConditionDescText(String gameName, int star, int words) {
StringBuilder text = new StringBuilder();
text.append("发表《").append(gameName).append("");
if (words == -1) {
text.append("的游戏评价");
} else {
text.append("不少于").append(words).append("字的游戏评价");
}
if (star != -1) {
text.append("并给予");
switch (star) {
case 5:
text.append("5星好评");
break;
case 4:
text.append("4星及以上评分");
break;
case 3:
text.append("3星及以上评分");
break;
case 2:
text.append("2星及以上评分");
break;
case 1:
text.append("1星及以上评分");
break;
default:
//throw new IllegalArgumentException("Invalid star value: " + star); // 或者返回一个默认值,比如空字符串或错误消息
return ""; // or return a default string or throw an exception.
}
}
return text.toString();
}
public LibaoEntity getLibaoEntity() {
return mLibaoEntity;
}

View File

@ -135,7 +135,7 @@ class DetailViewHolder(
vUpdate = view.findViewById(R.id.v_update)
tvUpdate = view.findViewById(R.id.tv_update)
context = view.context
context = view.context.getActivity() ?: view.context
com.gh.gamecenter.common.R.color.text_aw_primary.toColor()
var gameDownloadMode = gameEntity.getGameDownloadButtonMode()
@ -605,6 +605,8 @@ class DetailViewHolder(
"last_page_id", getLastPageEntity().pageId,
"last_page_business_id", getLastPageEntity().pageBusinessId,
"source", mGameEntity.exposureEvent?.source?.toString() ?: "",
"is_ad", if (mGameEntity.adGroupId.isEmpty()) "false" else "true",
"ad_group_id", mGameEntity.adGroupId,
*mGameEntity.customPageTrackData?.toKV() ?: arrayOf()
)
CheckLoginUtils.checkLogin(mViewHolder.context, mEntrance) {

View File

@ -8,14 +8,20 @@ import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.databinding.GameImageItemBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.IExposureProvider
/**
* 游戏专题-大图-显示/只显示
*/
class GameImageViewHolder(var binding: GameImageItemBinding) : BaseRecyclerViewHolder<Any>(binding.root) {
class GameImageViewHolder(var binding: GameImageItemBinding) : BaseRecyclerViewHolder<Any>(binding.root),
IExposureProvider {
private var boundedGameEntity: GameEntity? = null
// 注意:专题详情的大图不能用此方法
fun bindImage(entity: GameEntity, applyRoundCorner: Boolean = false) {
boundedGameEntity = entity
binding.run {
gameContainer.goneIf(!(entity.type == "game" && entity.getApk().isNotEmpty()))
gameIcon.displayGameIcon(entity)
@ -28,11 +34,17 @@ class GameImageViewHolder(var binding: GameImageItemBinding) : BaseRecyclerViewH
if (applyRoundCorner) {
val roundingParams = RoundingParams.fromCornersRadius(
binding.root.resources.getDimensionPixelSize(com.gh.gamecenter.common.R.dimen.home_large_image_radius).toFloat()
binding.root.resources.getDimensionPixelSize(com.gh.gamecenter.common.R.dimen.home_large_image_radius)
.toFloat()
)
binding.gameImageIcon.hierarchy.roundingParams = roundingParams
}
ImageUtils.displayWithAdaptiveHeight(binding.gameImageIcon, entity.image, width)
}
override fun provideExposureData(): ExposureEvent? {
return boundedGameEntity?.exposureEvent?.getFreshExposureEvent()
}
}

View File

@ -28,7 +28,6 @@ class BannerAdapter(
private var mExposureSourceList = ArrayList<ExposureSource>()
init {
mItemData.exposureEventList = arrayListOf()
mExposureSourceList.addAll(mExposureSource)
mExposureSourceList.add(ExposureSource("精选页轮播图"))
}
@ -67,7 +66,6 @@ class BannerAdapter(
payload.sourcePageName = it[PageSwitchDataHelper.PAGE_BUSINESS_NAME]
}
}
mItemData.exposureEventList?.add(exposureEvent)
}
view.setOnClickListener {
@ -88,12 +86,6 @@ class BannerAdapter(
mBanners = banners
}
if (mItemData.exposureEventList != null) {
mItemData.exposureEventList?.clear()
mItemData = itemData
mItemData.exposureEventList = arrayListOf()
}
notifyDataSetChanged()
}
}

View File

@ -13,27 +13,27 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.common.constant.ItemViewType
import com.gh.common.exposure.IExposable
import com.gh.common.util.*
import com.gh.gamecenter.R
import com.gh.gamecenter.common.viewholder.FooterViewHolder
import com.gh.gamecenter.common.baselist.ListAdapter
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.utils.PageSwitchDataHelper
import com.gh.gamecenter.databinding.*
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.entity.SpecialCatalogEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.IExposureProvider
import com.gh.gamecenter.subject.SubjectActivity.Companion.startSubjectActivity
class SpecialCatalogAdapter(
context: Context,
private val mCatalogViewModel: SpecialCatalogViewModel,
private val mLastPageDataMap: HashMap<String, String>? = null
) : ListAdapter<SpecialCatalogItemData>(context), IExposable {
) : ListAdapter<SpecialCatalogItemData>(context) {
private val mExposureEventSparseArray: SparseArray<ExposureEvent> = SparseArray()
var isAutoScroll = true
var isBannerSizeMoreThanOne = false
@ -153,8 +153,8 @@ class SpecialCatalogAdapter(
payload.sourcePageName = it[PageSwitchDataHelper.PAGE_BUSINESS_NAME]
}
}
mExposureEventSparseArray.append(position, exposureEvent)
}
holder.exposureEvent = exposureEvent
root.setOnClickListener {
DirectUtils.directToLinkPage(
mContext,
@ -208,28 +208,32 @@ class SpecialCatalogAdapter(
is CatalogSubjectItemHolder -> {
val subject = mEntityList[position].subject!!
val exposureList = arrayListOf<ExposureEvent>()
for ((index, game) in subject.link.data.withIndex()) {
game.sequence = index
game.subjectName = subject.link.text
game.outerSequence = mEntityList[position].position
runOnIoThread(isLightWeightTask = true) {
for ((index, game) in subject.link.data.withIndex()) {
game.sequence = index
game.subjectName = subject.link.text
game.outerSequence = mEntityList[position].position
val exposureEvent = ExposureEvent.createEventWithSourceConcat(
game,
mCatalogViewModel.basicExposureSource,
listOf(ExposureSource("精选页专题", subject.link.text ?: ""))
).apply {
mLastPageDataMap?.let {
payload.sourcePage = it[PageSwitchDataHelper.PAGE_BUSINESS_TYPE]
payload.sourcePageId = it[PageSwitchDataHelper.PAGE_BUSINESS_ID]
payload.sourcePageName = it[PageSwitchDataHelper.PAGE_BUSINESS_NAME]
val exposureEvent = ExposureEvent.createEventWithSourceConcat(
game,
mCatalogViewModel.basicExposureSource,
listOf(ExposureSource("精选页专题", subject.link.text ?: ""))
).apply {
mLastPageDataMap?.let {
payload.sourcePage = it[PageSwitchDataHelper.PAGE_BUSINESS_TYPE]
payload.sourcePageId = it[PageSwitchDataHelper.PAGE_BUSINESS_ID]
payload.sourcePageName = it[PageSwitchDataHelper.PAGE_BUSINESS_NAME]
}
}
game.exposureEvent = exposureEvent
if (game.adGroupId.isNotEmpty() && !game.isAdRequestReported) {
AdHelper.reportAdRequest(exposureEvent)
game.isAdRequestReported = true
}
}
exposureList.add(exposureEvent)
game.exposureEvent = exposureEvent
}
mEntityList[position].exposureEventList = exposureList
holder.bindSubject(subject.link.data, mEntityList[position].position)
}
@ -265,10 +269,6 @@ class SpecialCatalogAdapter(
}
}
override fun getEventByPosition(pos: Int): ExposureEvent? = mExposureEventSparseArray.get(pos)
override fun getEventListByPosition(pos: Int): List<ExposureEvent>? = mEntityList[pos].exposureEventList
inner class CatalogBannerItemHolder(val binding: CatalogBannerItemBinding) :
BaseRecyclerViewHolder<Any>(binding.root) {
@ -393,7 +393,14 @@ class SpecialCatalogAdapter(
}
}
inner class CatalogImageItemHolder(val binding: CatalogImageItemBinding) : BaseRecyclerViewHolder<Any>(binding.root)
inner class CatalogImageItemHolder(val binding: CatalogImageItemBinding) :
BaseRecyclerViewHolder<Any>(binding.root), IExposureProvider {
var exposureEvent: ExposureEvent? = null
override fun provideExposureData(): ExposureEvent? {
return exposureEvent?.getFreshExposureEvent()
}
}
inner class CatalogHeaderItemHolder(val binding: CatalogHeaderItemBinding) :
BaseRecyclerViewHolder<Any>(binding.root)

View File

@ -3,14 +3,15 @@ package com.gh.gamecenter.catalog
import android.os.Bundle
import android.view.View
import com.ethanhua.skeleton.Skeleton
import com.gh.common.exposure.DefaultExposureStateChangeListener
import com.gh.gamecenter.common.constant.Constants
import com.gh.common.exposure.ExposureListener
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.viewModelProvider
import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.ListFragment
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.databinding.FragmentListBaseSkeletonBinding
import com.gh.gamecenter.feature.exposure.addExposureHelper
class SpecialCatalogFragment : ListFragment<SpecialCatalogItemData, SpecialCatalogViewModel>() {
@ -21,8 +22,6 @@ class SpecialCatalogFragment : ListFragment<SpecialCatalogItemData, SpecialCatal
private var mAdapter: SpecialCatalogAdapter? = null
private var mLastPageDataMap: HashMap<String, String>? = null
private lateinit var mExposureListener: ExposureListener
override fun getLayoutId() = 0
override fun getInflatedLayout() = mBinding.root
@ -43,7 +42,6 @@ class SpecialCatalogFragment : ListFragment<SpecialCatalogItemData, SpecialCatal
mLastPageDataMap
).apply {
mAdapter = this
mExposureListener = ExposureListener(this@SpecialCatalogFragment, this)
}
override fun getItemDecoration() = null
@ -51,7 +49,6 @@ class SpecialCatalogFragment : ListFragment<SpecialCatalogItemData, SpecialCatal
override fun isAutomaticLoad(): Boolean = false
override fun onCreate(savedInstanceState: Bundle?) {
mIsCategoryV2 = arguments?.getBoolean(EntranceConsts.KEY_IS_CATEGORY_V2) ?: false
mCatalogId = arguments?.getString(EntranceConsts.KEY_CATALOG_ID) ?: ""
mCatalogTitle = arguments?.getString(EntranceConsts.KEY_CATALOG_TITLE) ?: ""
@ -65,6 +62,8 @@ class SpecialCatalogFragment : ListFragment<SpecialCatalogItemData, SpecialCatal
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mListRv.addExposureHelper(this, DefaultExposureStateChangeListener())
val skeletonLayoutId =
if (mIsCategoryV2) R.layout.fragment_special_catalog_second_skeleton else R.layout.fragment_special_catalog_first_skeleton
mSkeletonScreen = Skeleton.bind(mBinding.listSkeleton)
@ -77,8 +76,6 @@ class SpecialCatalogFragment : ListFragment<SpecialCatalogItemData, SpecialCatal
.load(skeletonLayoutId)
.show()
onLoadRefresh()
mListRv.addOnScrollListener(mExposureListener)
}
override fun onResume() {

View File

@ -1,6 +1,5 @@
package com.gh.gamecenter.catalog
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.entity.SpecialCatalogEntity
class SpecialCatalogItemData(
@ -11,5 +10,4 @@ class SpecialCatalogItemData(
val subjectCollection: SpecialCatalogEntity? = null,
var position: Int = 0,
var exposureEventList: ArrayList<ExposureEvent>? = null
)

View File

@ -9,6 +9,8 @@ import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.databinding.CatalogSubjectGameItemBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.IExposureProvider
import com.lightgame.adapter.BaseRecyclerAdapter
class SpecialCatalogSubjectAdapter(
@ -41,6 +43,8 @@ class SpecialCatalogSubjectAdapter(
}
val entity = mList[position]
holder.bindGameEntity(entity)
gameIcon.displayGameIcon(entity)
gameName.text = entity.name
gameName.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(mContext))
@ -84,5 +88,15 @@ class SpecialCatalogSubjectAdapter(
}
class CatalogSubjectGameItemViewHolder(val binding: CatalogSubjectGameItemBinding) :
BaseRecyclerViewHolder<Any>(binding.root)
BaseRecyclerViewHolder<Any>(binding.root), IExposureProvider {
private var boundedGameEntity: GameEntity? = null
fun bindGameEntity(gameEntity: GameEntity) {
boundedGameEntity = gameEntity
}
override fun provideExposureData(): ExposureEvent? {
return boundedGameEntity?.exposureEvent?.getFreshExposureEvent()
}
}
}

View File

@ -35,7 +35,6 @@ class SpecialCatalogSubjectCollectionAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
CatalogSubjectCollectionListItemViewHolder(parent.toBinding())
override fun onBindViewHolder(holder: CatalogSubjectCollectionListItemViewHolder, position: Int) {
holder.binding.run {
root.layoutParams = (root.layoutParams as ViewGroup.MarginLayoutParams).apply {

View File

@ -9,6 +9,7 @@ import com.gh.gamecenter.common.baselist.ListViewModel
import com.gh.gamecenter.common.entity.ExposureEntity
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.entity.SpecialCatalogEntity
import com.gh.gamecenter.feature.exposure.ExposureConstants
import com.gh.gamecenter.retrofit.RetrofitManager
import com.halo.assistant.HaloApp
import io.reactivex.Observable
@ -60,6 +61,8 @@ class SpecialCatalogViewModel(
game.containerType =
if (mIsCategoryV2) ExposureEntity.CATEGORY_V2_ID else ExposureEntity.CATEGORY_ID
game.containerId = mCatalogId
game.subPageCode = ExposureConstants.CATEGORY_V2
game.subPageId = mCatalogId
}
}

View File

@ -28,6 +28,11 @@ class CategoryV2Activity : DownloadToolbarActivity() {
override fun isAutoResetViewBackgroundEnabled() = true
override fun getBusinessId(): Pair<String, String> {
val categoryId = intent.extras?.getString(EntranceConsts.KEY_CATEGORY_ID, "") ?: ""
return Pair(categoryId, "")
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
updateStatusBarColor(com.gh.gamecenter.common.R.color.ui_surface, com.gh.gamecenter.common.R.color.ui_surface)

View File

@ -290,7 +290,6 @@ class CategoryV2Fragment : LazyFragment() {
mEntity?.run {
viewModel.run {
clearSelectedTag()
childFragmentManager.fragments.find { it.isAdded }
val targetFragment =
if (hasSpecial && position == 1) {
val fragment = childFragmentManager.findFragmentByTag(SpecialCatalogFragment::class.java.name)
@ -319,7 +318,6 @@ class CategoryV2Fragment : LazyFragment() {
)
fragment
}
childFragmentManager
.beginTransaction()
.replace(R.id.gamesContainer, targetFragment, targetFragment::class.java.name)

View File

@ -1,12 +1,11 @@
package com.gh.gamecenter.category2
import android.content.Context
import android.util.SparseArray
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.databind.BindingAdapters
import com.gh.common.exposure.IExposable
import com.gh.common.util.AdHelper
import com.gh.common.util.DownloadItemUtils
import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.adapter.viewholder.GameViewHolder
@ -17,6 +16,7 @@ import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.DrawableView
import com.gh.gamecenter.common.viewholder.FooterViewHolder
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.utils.PageSwitchDataHelper
import com.gh.gamecenter.core.utils.StringUtils
import com.gh.gamecenter.databinding.CategoryGameItemBinding
@ -24,6 +24,7 @@ import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.ExposureType
import com.gh.gamecenter.feature.exposure.IExposureProvider
import com.gh.gamecenter.feature.game.GameItemViewHolder
import com.lightgame.download.DownloadEntity
import org.json.JSONException
@ -35,9 +36,7 @@ class CategoryV2ListAdapter(
private val mCategoryViewModel: CategoryV2ViewModel,
private val mEntrance: String?,
private var mLastPageDataMap: HashMap<String, String>? = null
) : ListAdapter<GameEntity>(context), IExposable {
private val mExposureEventSparseArray: SparseArray<ExposureEvent> = SparseArray()
) : ListAdapter<GameEntity>(context) {
val positionAndPackageMap = HashMap<String, Int>()
@ -125,7 +124,13 @@ class CategoryV2ListAdapter(
payload.sourcePageName = it[PageSwitchDataHelper.PAGE_BUSINESS_NAME]
}
}
mExposureEventSparseArray.put(position, event)
runOnIoThread(isLightWeightTask = true) {
if (gameEntity.adGroupId.isNotEmpty() && !gameEntity.isAdRequestReported) {
AdHelper.reportAdRequest(event)
gameEntity.isAdRequestReported = true
}
}
holder.itemView.setOnClickListener {
GameDetailActivity.startGameDetailActivity(
@ -231,17 +236,13 @@ class CategoryV2ListAdapter(
}
}
override fun getEventByPosition(pos: Int): ExposureEvent? {
return mExposureEventSparseArray.get(pos)
}
override fun getEventListByPosition(pos: Int): List<ExposureEvent>? {
return null
}
inner class CategoryGameItemViewHolder(val binding: CategoryGameItemBinding) :
BaseRecyclerViewHolder<Any>(binding.root) {
BaseRecyclerViewHolder<Any>(binding.root), IExposureProvider {
private var boundedGameEntity: GameEntity? = null
fun bindGameItem(gameEntity: GameEntity) {
boundedGameEntity = gameEntity
binding.run {
gameIconView.displayGameIcon(gameEntity)
gameRating.textSize = if (gameEntity.commentCount > 3) 12F else 10F
@ -294,6 +295,10 @@ class CategoryV2ListAdapter(
// 由于RecyclerView的复用机制 需要每次测量gameName的宽
binding.gameName.requestLayout()
}
override fun provideExposureData(): ExposureEvent? {
return boundedGameEntity?.exposureEvent?.getFreshExposureEvent()
}
}
inner class CategoryGameViewHolder(val binding: CategoryGameItemBinding) : GameViewHolder(binding.root) {

View File

@ -6,7 +6,7 @@ import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.ethanhua.skeleton.Skeleton
import com.gh.common.exposure.ExposureListener
import com.gh.common.exposure.DefaultExposureStateChangeListener
import com.gh.common.util.DialogUtils
import com.gh.common.view.CategoryFilterView
import com.gh.common.xapk.XapkInstaller
@ -24,6 +24,8 @@ import com.gh.gamecenter.databinding.FragmentCategoryListBinding
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.feature.exposure.addExposureHelper
import com.google.android.flexbox.FlexboxLayout
import com.gh.gamecenter.feature.entity.GameEntity
import com.lightgame.download.DataWatcher
import com.lightgame.download.DownloadEntity
@ -113,7 +115,7 @@ class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>
onRefresh()
}
mListRv.addOnScrollListener(ExposureListener(this, provideListAdapter()))
mListRv.addExposureHelper(this, DefaultExposureStateChangeListener())
mSkeletonScreen = Skeleton.bind(mBinding?.listSkeleton)
.shimmer(true)

View File

@ -5,12 +5,14 @@ import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.gh.common.exposure.ExposureUtils
import com.gh.common.util.AdHelper
import com.gh.common.view.CategoryFilterView
import com.gh.gamecenter.common.baselist.ListViewModel
import com.gh.gamecenter.common.entity.ExposureEntity
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.core.utils.UrlFilterUtils
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureConstants
import com.gh.gamecenter.retrofit.RetrofitManager
import com.halo.assistant.HaloApp
import io.reactivex.Observable
@ -33,7 +35,7 @@ class CategoryV2ListViewModel(
return RetrofitManager
.getInstance()
.api
.getCategoryV2Games(categoryId, getFilter(), getSortType(), page)
.getCategoryV2Games(categoryId, getFilter(), getSortType(), AdHelper.getIdfaString(), page)
}
override fun mergeResultLiveData() {
@ -43,7 +45,11 @@ class CategoryV2ListViewModel(
containerId = categoryId,
containerType = ExposureEntity.CATEGORY_V2_ID
)
it.forEach { game -> game.hideSizeInsideDes = true }
it.forEach { game ->
game.hideSizeInsideDes = true
game.subPageCode = ExposureConstants.CATEGORY_V2
game.subPageId = categoryId
}
mResultLiveData.postValue(it)
}
}
@ -62,7 +68,6 @@ class CategoryV2ListViewModel(
"min_size", gameFiltered.size.min.toString(),
"max_size", gameFiltered.size.max.toString()
)
}
private fun getSortType(): String? {

View File

@ -166,7 +166,7 @@ class CategoryV2ViewModel : ViewModel() {
if (position != oldPosition) {
_selectedSidebarsPosition.value = position
// 如果是点击搜索而被动切换到 “全部” tab则这里不需要更新筛选条件
if (triggerSearch && position != 1) {
if (triggerSearch && position != INVALID_POSITION) {
updateGameFiltered()
}
}

View File

@ -33,8 +33,10 @@ import com.gh.gamecenter.discovery.interestedgame.InterestedGameActivity
import com.gh.gamecenter.entity.DiscoveryGameCardLabel
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureConstants
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.ExposureType
import com.gh.gamecenter.feature.exposure.IExposureProvider
import com.gh.gamecenter.feature.game.GameItemViewHolder
import com.lightgame.download.DownloadEntity
@ -137,6 +139,7 @@ class DiscoveryAdapter(
if (mOuterSequence >= 0) {
outerSequence = mOuterSequence
}
subPageCode = ExposureConstants.DISCOVERY
},
mBaseExposureSource,
exposureSources,
@ -346,7 +349,9 @@ class DiscoveryAdapter(
return null
}
class DiscoveryGameViewHolder(val binding: DiscoveryGameItemBinding) : GameViewHolder(binding.root) {
class DiscoveryGameViewHolder(val binding: DiscoveryGameItemBinding) : GameViewHolder(binding.root), IExposureProvider {
private var boundedGameEntity: GameEntity? = null
init {
gameDownloadBtn = binding.downloadBtn
gameDes = binding.gameDes
@ -358,6 +363,7 @@ class DiscoveryAdapter(
}
fun bindGameItem(gameEntity: GameEntity) {
boundedGameEntity = gameEntity
binding.run {
root.background = com.gh.gamecenter.common.R.drawable.reuse_listview_item_style.toDrawable(root.context)
gameKaifuType.setBackgroundColor(com.gh.gamecenter.common.R.color.primary_theme.toColor(root.context))
@ -415,8 +421,11 @@ class DiscoveryAdapter(
)
}
}
}
override fun provideExposureData(): ExposureEvent? {
return boundedGameEntity?.exposureEvent?.getFreshExposureEvent()
}
}
class RecommendInterestViewHolder(val binding: ItemRecommendInterestBinding) :
BaseRecyclerViewHolder<Any>(binding.root)

View File

@ -4,6 +4,7 @@ import android.os.Parcelable
import com.gh.gamecenter.common.entity.IconFloat
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.TagStyleEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
@ -42,12 +43,16 @@ data class AmwayCommentEntity(
// 曝光用的位置
var sequence: Int = 0,
var outerSequence: Int = 0
) : Parcelable {
@IgnoredOnParcel
val name: String?
get() = mName.removeSuffix(".")
@IgnoredOnParcel
var exposureEvent: ExposureEvent? = null
fun toGameEntity(): GameEntity {
val gameEntity = GameEntity()
gameEntity.id = id

View File

@ -13,6 +13,14 @@ class LibaoDetailEntity {
var des: String? = null
// 领取限制 发表游戏评价 (game_comment)
@SerializedName("receive_limit")
var receiveLimit: String? = ""
// 领取条件
@SerializedName("receive_condition")
var receiveCondition: Condition ? = null
@SerializedName("new_des")
var newDes: String? = null
@ -24,4 +32,10 @@ class LibaoDetailEntity {
@SerializedName("me")
var me: MeEntity? = null
class Condition(
// 评分,-1/5/4/3/2/1 => 无限制、5星好评、4星及以上评分、3星及以上评分、2星及以上评分、1星及以上评分
val star: Int = 0,
// 字数,-1/n => 无限制、数量
val words: Int = 0
)
}

View File

@ -122,7 +122,9 @@ data class SubjectEntity(
@SerializedName("column_type")
private val _columnType: String? = null,
@SerializedName("size")
private val _size: Size? = null
private val _size: Size? = null,
@SerializedName("onlyFee")
private val _onlyFee: Boolean? = false,
) : Parcelable {
@IgnoredOnParcel
@ -161,6 +163,9 @@ data class SubjectEntity(
val size: Size
get() = _size ?: Size()
val onlyFee: Boolean
get() = _onlyFee ?: false
var isDspSubject: Boolean = false
companion object {
@ -173,9 +178,13 @@ data class SubjectEntity(
@Parcelize
data class Size(
@SerializedName("index")
private val _index: Int? = null
private val _index: Int? = null,
@SerializedName("limit")
private val _limit: Int? = null,
) : Parcelable {
val index: Int
get() = _index ?: 0
val limit: Int
get() = _limit ?: -1
}
}

View File

@ -33,7 +33,6 @@ class FollowCommonCollectionViewHolder(
override fun addExposureEvent(childPosition: Int, link: ExposureLinkEntity) = Unit
override fun onChildItemClick(childPosition: Int, entity: CommonCollectionContentEntity) {
val linkEntity = entity.linkEntity
NewLogUtils.logCommonCollectionClick(

View File

@ -50,9 +50,6 @@ class FollowHomeSlideListViewHolder(
}
override fun updateImmersiveColor(color: Int) = Unit
override fun createExposureEvent(childPosition: Int, game: GameEntity?): ExposureEvent? = null
})
}

View File

@ -29,15 +29,12 @@ class FollowHomeSlideWithCardsViewHolder(
useCase,
lifecycleOwner,
binding,
0,
null,
"",
object : CommonContentHomeSlideWithCardsUi.HomeSLideWithCardsEventListener {
override fun updateImmersiveColor(color: Int) = Unit
override fun createEventWithSourceConcat(game: GameEntity, subSlideId: String) = Unit
override fun createExposureEvent(actualPosition: Int, game: GameEntity?): ExposureEvent? = null
override fun addGameExposureEvent(position: Int, game: GameEntity, subSlideId: String) = Unit
override fun navigateToGameDetailPage(
childPosition: Int,
gameEntity: GameEntity,

View File

@ -399,7 +399,8 @@ class GameFragmentAdapter(
position = prefixedPosition,
gameColumnName = gameEntity.name ?: "",
gameColumnId = gameEntity.link ?: "",
text = "游戏专题"
text = "游戏专题",
adGroupId = gameEntity.adGroupId
)
}
@ -574,7 +575,7 @@ class GameFragmentAdapter(
position = sequence,
gameColumnName = subject.name ?: "",
gameColumnId = subject.id ?: "",
text = "游戏专题"
text = "游戏专题",
)
}

View File

@ -21,6 +21,8 @@ import com.gh.gamecenter.common.viewholder.FooterViewHolder
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.PageSwitchDataHelper
import com.gh.gamecenter.databinding.GameColumnCollectionItemBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureConstants
import com.gh.gamecenter.feature.exposure.ExposureEvent
class ColumnCollectionDetailAdapter(
@ -95,8 +97,13 @@ class ColumnCollectionDetailAdapter(
text = "游戏专题"
)
}
val fakeGameEntity = GameEntity(_id = ExposureConstants.COLUMN_COLLECTION_DETAIL)
fakeGameEntity.subPageCode = ExposureConstants.COLUMN_COLLECTION_DETAIL
fakeGameEntity.subPageId = mViewModel.collectionId
val exposureEvent = ExposureEvent.createEventWithSourceConcat(
null,
fakeGameEntity,
mBasicExposureSourceList ?: arrayListOf(),
arrayListOf(
ExposureSource("合集详情", ""),

View File

@ -18,6 +18,7 @@ import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.viewholder.FooterViewHolder
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureConstants
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.game.data.CommonContentCollectionDetailItem
import com.gh.gamecenter.game.data.CommonContentCollectionDetailRecommendCardItem
@ -142,6 +143,8 @@ class CustomCommonCollectionDetailAdapter(
ExposureEvent.createEventWithSourceConcat(
gameEntity.also {
it.sequence = position
it.subPageCode = ExposureConstants.CUSTOM_COMMON_COLLECTION_DETAIL
it.subPageId = commonCollectionEntity?.id ?: ""
},
basicSource = mBasicExposureSourceList ?: listOf(),
listOf(

View File

@ -18,6 +18,8 @@ import com.gh.gamecenter.gamedetail.detail.viewholder.BaseGameDetailItemViewHold
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.subjectTypeToComponentStyle
import com.lightgame.adapter.BaseRecyclerAdapter
import com.lightgame.download.DownloadEntity
import kotlin.collections.forEachIndexed
import kotlin.collections.isNotEmpty
class GameHorizontalAdapter(
context: Context,
@ -136,7 +138,8 @@ class GameHorizontalAdapter(
gameColumnName = subjectEntity.name ?: "",
gameColumnId = subjectEntity.id ?: "",
text = "游戏",
columnPattern = subjectTypeToComponentStyle[subjectEntity.type] ?: ""
columnPattern = subjectTypeToComponentStyle[subjectEntity.type] ?: "",
adGroupId = gameEntity.adGroupId,
)
}
}

View File

@ -6,9 +6,22 @@ import android.widget.TextView
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.databinding.GameHorizontalSimpleItemBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.IExposureProvider
class GameHorizontalSimpleItemViewHolder(val binding: GameHorizontalSimpleItemBinding) :
BaseRecyclerViewHolder<GameEntity>(binding.root) {
BaseRecyclerViewHolder<GameEntity>(binding.root), IExposureProvider {
private var boundedGameEntity: GameEntity? = null
fun bindData(game: GameEntity) {
boundedGameEntity = game
}
override fun provideExposureData(): ExposureEvent? {
return boundedGameEntity?.exposureEvent?.getFreshExposureEvent()
}
companion object {
@JvmStatic
fun setHorizontalNameAndGravity(view: TextView, name: String?) {

View File

@ -26,8 +26,9 @@ class HotGameListViewModel(
subjectData.subjectId,
subjectData.sort,
subjectData.filter.ifEmpty { "type:全部" },
page
)
"",
"",
page)
}
override fun mergeResultLiveData() {

View File

@ -30,6 +30,7 @@ import com.gh.gamecenter.entity.GamesCollectionDetailEntity
import com.gh.gamecenter.eventbus.EBUserFollow
import com.gh.gamecenter.feature.entity.CommentEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureConstants
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.qa.article.detail.CommentItemData
import com.gh.gamecenter.qa.comment.base.BaseCommentViewModel
@ -136,6 +137,9 @@ open class GameCollectionDetailViewModel(
games?.forEach {
it.isAdData = adIconActive
it.subPageCode = ExposureConstants.GAME_COLLECTION_DETAIL
it.subPageId = gameCollectionId
add(CommentItemData(game = it))
gameList?.add(it)
}

View File

@ -30,6 +30,7 @@ import com.gh.gamecenter.entity.RecommendPopupEntity
import com.gh.gamecenter.entity.SubjectEntity
import com.gh.gamecenter.entity.UnifiedUserTrendEntity
import com.gh.gamecenter.feature.entity.*
import com.gh.gamecenter.feature.exposure.ExposureConstants
import com.gh.gamecenter.feature.utils.ApkActiveUtils
import com.gh.gamecenter.feature.utils.ConcernUtils
import com.gh.gamecenter.feature.utils.ContentBlockedHelper
@ -190,6 +191,7 @@ class GameDetailViewModel(
.subscribe(object : Response<GameEntity>() {
override fun onResponse(response: GameEntity?) {
game = response
game?.subPageCode = ExposureConstants.GAME_DETAIL
gameLiveData.postValue(Resource.success(game))
loadGameDetailData()
}
@ -557,6 +559,9 @@ class GameDetailViewModel(
detailDataList.find { it.linkEveryonePlaying != null }?.let {
if (relatedGameList.isNotEmpty()) {
relatedGameList.shuffle()
relatedGameList.forEach { game ->
game.subPageCode = ExposureConstants.GAME_DETAIL
}
val recommendedGames = SubjectEntity().apply { data = relatedGameList }
it.linkEveryonePlaying?.recommendedGames = recommendedGames
} else {

View File

@ -14,6 +14,7 @@ import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.menu.ActionMenuItemView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.doOnNextLayout
import androidx.core.view.isVisible
@ -96,6 +97,7 @@ import io.reactivex.disposables.Disposable
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import retrofit2.HttpException
import splitties.views.horizontalPadding
import java.util.*
class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
@ -286,7 +288,7 @@ class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
initSkeleton()
binding.reuseNoneData.reuseNoneDataTv.text = "页面不见了"
bodyBinding.tabIndicator.setIndicatorWidth(12)
bodyBinding.tabIndicator.setIndicatorWidth(16)
bodyBinding.viewPager.offscreenPageLimit = 4
binding.expandSpecialDownloadIv.enlargeTouchArea()
@ -357,8 +359,14 @@ class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
}
backBtn.setOnClickListener { requireActivity().finish() }
moreMenuItem = actionMenuView.menu.findItem(R.id.menu_more)
downloadMenuItem = actionMenuView.menu.findItem(R.id.menu_download)
downloadMenuItem = actionMenuView.menu.findItem(R.id.menu_download)?.apply {
actionView?.updateLayoutParams<LayoutParams> { width = 40F.dip2px() }
}
downloadMenuItem?.isVisible = Config.isShow()
actionMenuView.findViewById<ActionMenuItemView>(R.id.menu_more)?.run {
updateLayoutParams<LayoutParams> { width = 40F.dip2px() }
horizontalPadding = 8F.dip2px()
}
}
downloadMenuIcon = downloadMenuItem?.actionView?.findViewById(R.id.menu_download_iv)
@ -467,6 +475,10 @@ class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
gameEntity?.gameBitChinese ?: "",
"download_type",
gameEntity?.downloadType ?: "",
"is_ad",
traceEvent?.payload?.isAd ?: false,
"ad_group_id",
traceEvent?.payload?.adGroupId ?: "",
*(traceEvent?.additional ?: emptyArray())
)
}, 120)
@ -906,7 +918,7 @@ class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
tab.customView = tabItemBinding.root
updateTabStyle(tab, i == bodyBinding.viewPager.currentItem)
tab.view.clipChildren = false
tab.view.setPadding(0, 0, 0, 0)
tab.view.setPadding(0, 0, if (i == tabEntityList.size - 1) 8F.dip2px() else 0, 0)
tab.view.setOnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
handleTabTouchEvent(tabEntity?.name ?: "")
@ -933,7 +945,7 @@ class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
tab.customView?.findViewById<TextView>(R.id.tab_title)
?.setTypeface(if (isChecked) Typeface.DEFAULT_BOLD else Typeface.DEFAULT)
tab.customView?.findViewById<TextView>(R.id.tab_title)?.setTextColor(
if (isChecked) com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()) else com.gh.gamecenter.common.R.color.text_tertiary.toColor(
if (isChecked) com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()) else com.gh.gamecenter.common.R.color.text_secondary.toColor(
requireContext()
)
)

View File

@ -922,8 +922,10 @@ class GameDetailFragment : LazyFragment(), IScrollable {
}
override fun scrollToTop() {
binding.gamedetailAppbar.setExpanded(true)
binding.detailRv.scrollToPosition(0)
if (::binding.isInitialized) {
binding.gamedetailAppbar.setExpanded(true)
binding.detailRv.scrollToPosition(0)
}
}
// 登录事件/礼包状态变更事件

View File

@ -253,7 +253,7 @@ class GameLibaoAdapter(
null,
true,
"游戏详情",
"游戏详情"
"礼包列表页"
) {
adapter.notifyItemChanged(position)
}

View File

@ -20,6 +20,8 @@ import com.gh.gamecenter.game.horizontal.GameHorizontalListType
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.GameDetailData
import com.gh.gamecenter.gamedetail.entity.GameDetailRecommendColumn
import kotlin.collections.firstOrNull
import kotlin.collections.withIndex
class GameDetailRecommendColumnItemViewHolder(
val binding: ItemGameDetailRecyclerViewBinding,
@ -73,7 +75,8 @@ class GameDetailRecommendColumnItemViewHolder(
linkType = columnData.display?.moreLink?.type ?: "",
linkId = columnData.display?.moreLink?.link ?: "",
text = "右上角",
buttonType = columnData.display?.home ?: ""
buttonType = columnData.display?.home ?: "",
adGroupId = viewModel.game?.adGroupId ?: ""
)
when (columnData.display?.home) {

View File

@ -8,10 +8,9 @@ import android.view.*
import android.widget.ImageView
import android.widget.TextView
import androidx.lifecycle.ViewModelProvider
import com.gh.common.util.DirectUtils
import com.gh.common.util.PackageInstaller
import com.gh.common.util.PackageLauncher
import com.gh.common.util.PackageUtils
import com.gh.common.util.*
import com.gh.common.xapk.XapkInstaller
import com.gh.common.xapk.XapkUnzipStatus
import com.gh.download.DownloadManager
import com.gh.gamecenter.R
import com.gh.gamecenter.ShellActivity
@ -80,6 +79,15 @@ class SpecialDownloadDialogFragment : BaseDraggableDialogFragment() {
DownloadStatus.done -> {
downloadBtn?.setProgress(1.0F)
downloadBtn?.setText(context?.getString(com.gh.gamecenter.feature.R.string.install) ?: "")
val xapkStatus = downloadEntity.meta[XapkInstaller.XAPK_UNZIP_STATUS]
when {
(XapkUnzipStatus.SUCCESS.name == xapkStatus || XapkUnzipStatus.INSTALLED.name == xapkStatus) && XapkInstaller.isInstalling(downloadEntity.path) -> {
downloadBtn?.setText(context?.getString(com.gh.gamecenter.feature.R.string.installing) ?: "")
}
XapkUnzipStatus.UNZIPPING.name == xapkStatus -> {
downloadBtn?.setText(context?.getString(com.gh.gamecenter.feature.R.string.unzipping) ?: "")
}
}
}
DownloadStatus.cancel -> {

View File

@ -360,7 +360,8 @@ class LegacyHomeFragmentAdapterAssistant(
position = prefixedPosition,
gameColumnName = gameEntity.name ?: "",
gameColumnId = gameEntity.link ?: "",
text = "游戏专题"
text = "游戏专题",
adGroupId = gameEntity.adGroupId
)
}

View File

@ -1,9 +1,11 @@
package com.gh.gamecenter.home.custom
import com.gh.common.util.AdHelper
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.feature.entity.CustomPageTrackData
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureConstants
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.home.custom.model.CustomSubjectCollectionItem
@ -18,6 +20,9 @@ fun createExposureEvent(
game?.sequence = childPosition
game?.outerSequence = position
game?.customPageTrackData = customPageTrackData
game?.pageLevelString = customPageTrackData.pageLocation.pageLevelString
game?.subPageCode = ExposureConstants.CUSTOM_PAGE
game?.subPageId = customPageTrackData.pageLocation.pageId
val event = ExposureEvent.createEventWithSourceConcat(
gameEntity = game,
basicSource = base,
@ -32,9 +37,7 @@ fun fillExposureInSubjectCollection(
base: List<ExposureSource>,
customPageTrackData: CustomPageTrackData
) {
val eventList = arrayListOf<ExposureEvent>()
runOnIoThread(true) {
runOnIoThread(isLightWeightTask = true) {
item.data.data.forEachIndexed { index, customSubject ->
val source = if (item.isSubjectCollection) {
listOf(
@ -51,8 +54,9 @@ fun fillExposureInSubjectCollection(
)
}
customSubject.games.forEach { game ->
game.pageLevelString = customPageTrackData.pageLocation.pageLevelString
game.isAdData = customSubject.adIconActive
val event = createExposureEvent(
game.exposureEvent = createExposureEvent(
game,
source,
base,
@ -60,10 +64,15 @@ fun fillExposureInSubjectCollection(
item.componentPosition,
customPageTrackData
)
eventList.add(event)
game.subPageCode = ExposureConstants.CUSTOM_PAGE
game.subPageId = customPageTrackData.pageLocation.pageId
game.exposureSource = game.exposureEvent?.source
if (game.adGroupId.isNotEmpty() && !game.isAdRequestReported) {
AdHelper.reportAdRequest(game.exposureEvent!!)
game.isAdRequestReported = true
}
}
}
}
item.exposureEventList = eventList
}

View File

@ -12,7 +12,7 @@ import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.OnScrollListener
import com.gh.common.exposure.ExposureListener
import com.gh.common.exposure.DefaultExposureStateChangeListener
import com.gh.common.exposure.ExposureManager
import com.gh.common.iinterface.ISearchToolbarTab
import com.gh.common.iinterface.ISmartRefresh
@ -41,6 +41,7 @@ import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.eventbus.EBReuse
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.tracker.IBusiness
import com.gh.gamecenter.common.pagelevel.IPageLevelProvider
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.ScrollableLinearLayoutManager
import com.gh.gamecenter.core.AppExecutor
@ -59,6 +60,7 @@ import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.PageLocation
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.ExposureType
import com.gh.gamecenter.feature.exposure.addExposureHelper
import com.gh.gamecenter.feature.minigame.MiniGameItemHelper
import com.gh.gamecenter.feature.utils.SentryHelper
import com.gh.gamecenter.game.commoncollection.detail.CustomCommonCollectionDetailActivity
@ -88,7 +90,11 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable, IB
private var searchToolbarTabWrapperViewModel: SearchToolbarTabWrapperViewModel? = null
private var mainWrapperViewModel: MainWrapperViewModel? = null
private val viewModel by viewModels<CustomPageViewModel>()
private val viewModel by viewModels<CustomPageViewModel>(
factoryProducer = {
CustomPageViewModel.Factory(com.gh.gamecenter.login.HaloApp.getInstance())
}
)
private lateinit var binding: FragmentCustomBinding
@ -111,6 +117,7 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable, IB
private var customPageName = ""
private var bottomTabId = ""
private var bottomTabName = ""
private var bottomTabIndex = -1
private var tabIndex = -1
private var showFloatingWindow = true
private var showPullDownPush = true
@ -144,6 +151,7 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable, IB
customPageName = arguments?.getString(EntranceConsts.KEY_CUSTOM_PAGE_NAME, "") ?: ""
bottomTabId = arguments?.getString(EntranceConsts.KEY_BOTTOM_TAB_ID, "") ?: ""
bottomTabName = arguments?.getString(EntranceConsts.KEY_BOTTOM_TAB_NAME, "") ?: ""
bottomTabIndex = arguments?.getInt(EntranceConsts.KEY_BOTTOM_TAB_INDEX, -1) ?: -1
tabIndex = arguments?.getInt(EntranceConsts.KEY_TAB_INDEX, -1) ?: -1
showFloatingWindow = arguments?.getBoolean(EntranceConsts.KEY_SHOW_FLOATING_WINDOW, true) ?: true
showPullDownPush = arguments?.getBoolean(EntranceConsts.KEY_SHOW_PULL_DOWN_PUSH, true) ?: true
@ -182,8 +190,17 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable, IB
exposureSourceList,
isInSearchToolbarTabWrapperPage
)
val precisePageLevelString = (activity as? IPageLevelProvider)
?.getPageLevel()
?.getTempNewPageLevel(topTabPosition = tabIndex, bottomTabPosition = bottomTabIndex)
?.toFormattedString(ignoreBottomTabPositionMap = true)
?: ""
pageLocation = PageLocation(
bottomTabName,
bottomTabIndex,
precisePageLevelString,
multiTabNavName,
multiTabNavId,
tabIndex,
@ -229,8 +246,12 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable, IB
setNavigationTitle(it.title)
})
dataList.observe(viewLifecycleOwner) {
adapter.submitList(it)
dataList.observe(viewLifecycleOwner) { (shouldScrollToTop, data) ->
adapter.submitList(data) {
if (shouldScrollToTop) {
binding.gameList.scrollToPosition(0)
}
}
}
loadStatus.observe(viewLifecycleOwner) { (status, isPullToRefresh) ->
@ -352,7 +373,6 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable, IB
)
}
}
})
subjectDestination.observe(viewLifecycleOwner, EventObserver { subject ->
@ -553,6 +573,7 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable, IB
binding.gameList.itemAnimator = null
binding.gameList.layoutManager = layoutManager
binding.gameList.adapter = adapter
binding.gameList.addExposureHelper(this, DefaultExposureStateChangeListener())
var listScrollHeight = 0
binding.gameList.addOnScrollListener(object : OnScrollListener() {
@ -570,9 +591,6 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable, IB
}
})
val exposureListener = ExposureListener(this, adapter)
binding.gameList.addOnScrollListener(exposureListener)
binding.gameRefresh.setOnRefreshListener {
onRefresh()
}

View File

@ -7,6 +7,8 @@ import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.databinding.GameCollectionBannerItemBinding
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.IExposureProvider
import com.gh.gamecenter.home.custom.model.CustomPageData
class AnnouncementBannerAdapter(
@ -44,7 +46,6 @@ class AnnouncementBannerAdapter(
} else {
notifyDataSetChanged()
}
}
}
@ -72,7 +73,6 @@ class AnnouncementBannerAdapter(
override fun onBindViewHolder(holder: AnnouncementBannerChildViewHolder, position: Int) {
val item = getItem(position)
listener.exposure(position, item)
holder.bind(item)
holder.binding.bannerIv.setOnClickListener {
listener.onItemClick(getDataPosition(position), item)
@ -80,19 +80,23 @@ class AnnouncementBannerAdapter(
}
class AnnouncementBannerChildViewHolder(val binding: GameCollectionBannerItemBinding) :
RecyclerView.ViewHolder(binding.root) {
RecyclerView.ViewHolder(binding.root), IExposureProvider {
var exposureEvent: ExposureEvent? = null
fun bind(item: CustomPageData.Announcement) {
exposureEvent = item.exposureEvent
ImageUtils.display(binding.bannerIv, item.image)
}
override fun provideExposureData(): ExposureEvent? {
return exposureEvent?.getFreshExposureEvent()
}
}
interface OnChildEventListener {
fun onItemClick(childPosition: Int, announcement: CustomPageData.Announcement)
fun getCurrentPosition(): Int
fun exposure(childPosition: Int, announcement: CustomPageData.Announcement)
}
}

View File

@ -9,12 +9,13 @@ import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.toDrawable
import com.gh.gamecenter.databinding.RecyclerContentLabelLaneItemBinding
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.IExposureProvider
import com.gh.gamecenter.home.custom.model.CustomPageData
class ContentLabelLaneAdapter(
context: Context,
private val clickInvoke: (Int, CustomPageData.CommonContentCollection.ContentTag) -> Unit,
private val exposureInvoke: (Int, CustomPageData.CommonContentCollection.ContentTag) -> Unit
) : CustomBaseChildAdapter<CustomPageData.CommonContentCollection.ContentTag, ContentLabelLaneAdapter.ContentLabelChildViewHolder>(
context
) {
@ -29,6 +30,9 @@ class ContentLabelLaneAdapter(
override fun onBindViewHolder(holder: ContentLabelChildViewHolder, position: Int) {
val item = getItem(position)
holder.exposureEvent = item.exposureEvent
with(holder.binding) {
vBackground.background = R.drawable.bg_shape_content_label_lane_item.toDrawable(context)
tvTitle.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
@ -47,7 +51,6 @@ class ContentLabelLaneAdapter(
}
}
exposureInvoke(position, item)
holder.itemView.setOnClickListener {
clickInvoke(position, item)
}
@ -55,9 +58,12 @@ class ContentLabelLaneAdapter(
class ContentLabelChildViewHolder(
val binding: RecyclerContentLabelLaneItemBinding
) : RecyclerView.ViewHolder(binding.root) {
) : RecyclerView.ViewHolder(binding.root), IExposureProvider {
var exposureEvent : ExposureEvent? = null
override fun provideExposureData(): ExposureEvent? {
return exposureEvent?.getFreshExposureEvent()
}
}
}

View File

@ -9,6 +9,8 @@ import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.CommonCollectionItemBinding
import com.gh.gamecenter.entity.CommonCollectionContentEntity
import com.gh.gamecenter.entity.ExposureLinkEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.IExposureProvider
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_IMAGE_TEXT
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_SLIDE_BANNER
@ -57,6 +59,7 @@ class CustomCommonCollectionAdapter(
}
}
listener.addExposureEvent(position, item.linkEntity)
holder.boundedLinkEntity = item.linkEntity
holder.binding.apply {
ImageUtils.display(commonCollectionImage, item.image)
@ -109,10 +112,15 @@ class CustomCommonCollectionAdapter(
}
class CustomCommonCollectionItemViewHolder(val binding: CommonCollectionItemBinding) :
BaseRecyclerViewHolder<Any>(binding.root)
BaseRecyclerViewHolder<Any>(binding.root), IExposureProvider {
var boundedLinkEntity: ExposureLinkEntity? = null
override fun provideExposureData(): ExposureEvent? {
return boundedLinkEntity?.exposureEvent?.getFreshExposureEvent()
}
}
interface OnEventListener {
fun addExposureEvent(childPosition: Int, exposureLinkEntity: ExposureLinkEntity)
fun onChildItemClick(childPosition: Int, item: CommonCollectionContentEntity)

View File

@ -166,7 +166,6 @@ class CustomDiscoverCardGameAdapter(
RetrofitManager.getInstance().api.discorveryFeedback(gameId, paramsMap.toRequestBody())
.compose(singleToMain())
.subscribe(object : BiResponse<ResponseBody>() {
override fun onSuccess(data: ResponseBody) {
callback.invoke()
}
@ -194,7 +193,6 @@ class CustomDiscoverCardGameAdapter(
notifyChildItem(busFour.packageName)
}
private fun notifyChildItem(packageName: String) {
dataList.forEachIndexed { position, gameEntity ->
gameEntity.getApk().forEach { apkEntity ->

View File

@ -25,8 +25,9 @@ import com.gh.gamecenter.core.utils.StringUtils
import com.gh.gamecenter.databinding.RecyclerFoldSlideLargeImageItemBinding
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.feature.entity.CustomPageTrackData
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.IExposureProvider
import com.gh.gamecenter.feature.game.GameItemViewHolder
import com.gh.gamecenter.feature.minigame.MiniGameItemHelper
import com.gh.gamecenter.home.custom.eventlistener.SubjectEventHelper
@ -53,7 +54,6 @@ class CustomFoldSlideLargeImageItemAdapter(
}
submitList(list, true)
}
}
override fun getKey(t: GameEntity): String {
@ -88,10 +88,8 @@ class CustomFoldSlideLargeImageItemAdapter(
holder.itemView.setOnClickListener {
eventHelper.navigateToGameDetailPage(realPosition, game)
}
}
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
_recyclerView = recyclerView
}
@ -107,7 +105,6 @@ class CustomFoldSlideLargeImageItemAdapter(
return@forEach
}
}
}
override fun notifyDownloadDeleted(status: EBDownloadStatus) {
@ -139,7 +136,6 @@ class CustomFoldSlideLargeImageItemAdapter(
action(position, game)
}
}
}
fun getInitPosition() =
@ -153,7 +149,7 @@ class CustomFoldSlideLargeImageItemAdapter(
private val eventHelper: SubjectEventHelper,
val binding: RecyclerFoldSlideLargeImageItemBinding
) :
RecyclerView.ViewHolder(binding.root) {
RecyclerView.ViewHolder(binding.root), IExposureProvider {
private lateinit var item: GameEntity
@ -200,7 +196,6 @@ class CustomFoldSlideLargeImageItemAdapter(
}
if (data.shouldShowDownloadButton) {
binding.btnDownload.goneIf(false)
DownloadItemUtils.setOnClickListener(
itemView.context, binding.btnDownload, game, bindingAdapterPosition,
@ -226,7 +221,6 @@ class CustomFoldSlideLargeImageItemAdapter(
} else {
binding.btnDownload.goneIf(true)
}
}
private fun getBottomBackground(oColor: String): Pair<Int, Drawable> {
@ -303,6 +297,10 @@ class CustomFoldSlideLargeImageItemAdapter(
}
}
override fun provideExposureData(): ExposureEvent? {
return item.exposureEvent?.getFreshExposureEvent()
}
companion object {
private const val BUBBLE_SHOW_DURATION = 4000L
}

View File

@ -24,7 +24,6 @@ class CustomGameHorizontalSlideAdapter(
var gameName = ""
var entrance = ""
private var _exposureEventList: List<ExposureEvent>? = null
private var isShowFirstLine = false
private var isShowSecondLine = false
@ -42,12 +41,11 @@ class CustomGameHorizontalSlideAdapter(
get() = _data.data
fun setData(data: CustomSubjectItem, exposureEventList: List<ExposureEvent>?) {
fun setData(data: CustomSubjectItem) {
isShowFirstLine = false
isShowSecondLine = false
hasTwoLinesName = false
_data = data
_exposureEventList = exposureEventList
data.data.data?.forEach {
if (!isShowFirstLine && it.assignRemark.firstLine.isNotEmpty()) {
isShowFirstLine = true
@ -86,7 +84,6 @@ class CustomGameHorizontalSlideAdapter(
}
holder.bindGameHorizontalItem(gameEntity, subjectEntity, isShowFirstLine, isShowSecondLine)
holder.itemView.setOnClickListener {
gameEntity.exposureEvent = _exposureEventList?.getOrNull(position)
eventHelper.navigateToGameDetailPage(position, gameEntity)
}
@ -99,7 +96,7 @@ class CustomGameHorizontalSlideAdapter(
this,
StringUtils.buildString("(游戏-专题:", subjectEntity.name, "-列表[", (position + 1).toString(), "])"),
location = StringUtils.buildString("游戏-专题-", subjectEntity.name, ":", gameEntity.name),
traceEvent = _exposureEventList?.getOrNull(position)
traceEvent = gameEntity.exposureEvent
) {
eventHelper.onDownloadButtonClick(position, gameEntity)
}

View File

@ -20,6 +20,7 @@ import com.gh.gamecenter.entity.GameNavigationEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.ExposureType
import com.gh.gamecenter.feature.exposure.IExposureProvider
class CustomGameNavigationAdapter(
context: Context,
@ -46,6 +47,7 @@ class CustomGameNavigationAdapter(
// 是否显示小红点
var isShowHint = false
val entity = dataList[position]
holder.exposureEvent = exposureEventList?.getOrNull(position)
ImageUtils.display(holder.binding.navigationView, entity.image)
holder.binding.navigationNameTv.text = if (entity.isShowEntryName) {
entity.entryName
@ -132,7 +134,6 @@ class CustomGameNavigationAdapter(
listener.navigateToLinkPage(it, "导航栏", exposureEvent)
}
}
}
private fun showGuide(entity: GameNavigationEntity, binding: ItemGameNavigationCustomBinding) {
@ -156,7 +157,6 @@ class CustomGameNavigationAdapter(
gradientDrawable.setStroke(1F.dip2px(), entity.guide.borderColorInt)
gradientDrawable.setColor(entity.guide.backgroundColorInt)
binding.tvBubble.background = gradientDrawable
}
companion object {
@ -164,7 +164,14 @@ class CustomGameNavigationAdapter(
}
class GameNavigationViewHolder(val binding: ItemGameNavigationCustomBinding) :
BaseRecyclerViewHolder<RecyclerView.ViewHolder>(binding.root)
BaseRecyclerViewHolder<RecyclerView.ViewHolder>(binding.root), IExposureProvider {
var exposureEvent: ExposureEvent? = null
override fun provideExposureData(): ExposureEvent? {
return exposureEvent?.getFreshExposureEvent()
}
}
interface OnEventListener {
fun navigateToLinkPage(link: LinkEntity, text: String, exposureEvent: ExposureEvent?)

View File

@ -15,7 +15,6 @@ import com.gh.common.util.HomePluggableHelper
import com.gh.gamecenter.common.constant.ItemViewType
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.core.utils.MtaHelper.onEvent
import com.gh.gamecenter.core.utils.StringUtils
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.PluginLocation
@ -120,7 +119,6 @@ class CustomGamePluginAdapter(
null
)
holder.itemView.setOnClickListener { v: View? ->
onEvent("首页_新", "点击", "插件化" + (position + 1) + "_" + gameEntity.name)
DataCollectionUtils.uploadClick(
context,
"插件化" + "-列表",

View File

@ -11,10 +11,10 @@ import com.gh.gamecenter.adapter.viewholder.GameViewHolder
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.feature.entity.CustomPageTrackData
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.IExposureProvider
import com.gh.gamecenter.feature.game.GameItemViewHolder
import com.gh.gamecenter.feature.minigame.MiniGameItemHelper
import com.gh.gamecenter.game.vertical.GameItemUi
import com.gh.gamecenter.home.custom.eventlistener.GameSubjectCollectionEventHelper
import com.gh.gamecenter.home.custom.model.CustomPageData
@ -95,12 +95,13 @@ class CustomGameRefreshVerticalAdapter(
return
}
}
}
inner class SimpleGameItemViewHolder(private val ui: GameItemUi) : ViewHolder(ui.root) {
inner class SimpleGameItemViewHolder(private val ui: GameItemUi) : ViewHolder(ui.root), IExposureProvider {
var placeholderGameViewHolder: GameViewHolder? = null
private var exposureEvent: ExposureEvent? = null
fun bindSimpleGameItem(
adapter: RecyclerView.Adapter<ViewHolder>,
gameEntity: GameEntity,
@ -124,6 +125,8 @@ class CustomGameRefreshVerticalAdapter(
var paddingEnd = if (isEndOfRow) 16F.dip2px() else 0F.dip2px()
val height = 80F.dip2px()
exposureEvent = gameEntity.exposureEvent
itemView.layoutParams = if (!isEndOfRow) {
paddingEnd += 1
ViewGroup.LayoutParams(maxWidth - 24F.dip2px(), height)
@ -210,6 +213,9 @@ class CustomGameRefreshVerticalAdapter(
root.setPadding(paddingStart, 8F.dip2px(), paddingEnd, 8F.dip2px())
}
}
}
override fun provideExposureData(): ExposureEvent? {
return exposureEvent?.getFreshExposureEvent()
}
}
}

View File

@ -13,8 +13,9 @@ import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.entity.SubjectEntity
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.feature.entity.CustomPageTrackData
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.IExposureProvider
import com.gh.gamecenter.feature.game.GameItemViewHolder
import com.gh.gamecenter.feature.minigame.MiniGameItemHelper
import com.gh.gamecenter.game.vertical.GameItemUi
@ -103,9 +104,11 @@ class CustomGameVerticalAdapter(
class SimpleGameItemViewHolder(
private val ui: GameItemUi,
private val eventHelper: SubjectEventHelper
) : ViewHolder(ui.root) {
) : ViewHolder(ui.root), IExposureProvider {
var placeholderGameViewHolder: GameViewHolder? = null
private var boundedGameEntity: GameEntity? = null
fun bindSimpleGameItem(
adapter: RecyclerView.Adapter<ViewHolder>,
gameEntity: GameEntity,
@ -122,6 +125,8 @@ class CustomGameVerticalAdapter(
) {
val context = itemView.context
boundedGameEntity = gameEntity
val paddingStart = 16F.dip2px()
val isEndOfRow = position >= if (adapter.itemCount % spanCount == 0) {
adapter.itemCount - spanCount
@ -220,6 +225,10 @@ class CustomGameVerticalAdapter(
root.setPadding(paddingStart, root.paddingTop, paddingEnd, root.paddingBottom)
}
}
override fun provideExposureData(): ExposureEvent? {
return boundedGameEntity?.exposureEvent?.getFreshExposureEvent()
}
}
}

View File

@ -12,6 +12,8 @@ import com.gh.gamecenter.common.utils.safelyGetInRelease
import com.gh.gamecenter.common.view.AsyncCell
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.databinding.HomeGameCollectionCardItemCustomBinding
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.IExposureListProvider
import com.gh.gamecenter.home.PageConfigure
import com.gh.gamecenter.home.custom.eventlistener.GameSubjectCollectionEventHelper
import com.gh.gamecenter.home.custom.model.CustomPageData
@ -21,10 +23,9 @@ class CustomHomeGameCollectionCarouselAdapter(
context: Context,
private val pageConfigure: PageConfigure,
private val eventHelper: GameSubjectCollectionEventHelper
) :
CustomBaseChildAdapter<CustomPageData.LinkColumnCollection.CustomSubjectEntity, CustomHomeGameCollectionCarouselAdapter.HomeGameCollectionCardViewHolder>(
context
) {
) : CustomBaseChildAdapter<
CustomPageData.LinkColumnCollection.CustomSubjectEntity,
CustomHomeGameCollectionCarouselAdapter.HomeGameCollectionCardViewHolder>(context) {
private val mPosterWidth = DisplayUtils.getScreenWidth() - 50F.dip2px()
@ -48,10 +49,8 @@ class CustomHomeGameCollectionCarouselAdapter(
pageConfigure.entrance,
position
)
}
}
}
override fun getItemViewType(position: Int) = position
@ -68,15 +67,18 @@ class CustomHomeGameCollectionCarouselAdapter(
}
inner class HomeGameCollectionCardViewHolder(cell: HomeGameCollectionCarouselItemCell) :
BaseRecyclerViewHolder<Any>(cell) {
BaseRecyclerViewHolder<Any>(cell), IExposureListProvider {
private var boundedItemData: CustomPageData.LinkColumnCollection.CustomSubjectEntity? = null
fun bindGameCollectionCard(
binding: HomeGameCollectionCardItemCustomBinding,
itemData: CustomPageData.LinkColumnCollection.CustomSubjectEntity,
entrance: String,
itemPosition: Int
) {
boundedItemData = itemData
binding.run {
val context = root.context
root.layoutParams.width = mPosterWidth
if (itemData.user.isValid) {
@ -121,8 +123,15 @@ class CustomHomeGameCollectionCarouselAdapter(
root.setOnClickListener {
eventHelper.navigateSubjectCollectionPage(itemPosition, itemData)
}
}
}
override fun provideExposureData(): ExposureEvent? = null
override fun provideExposureDataList(): List<ExposureEvent>? {
return boundedItemData?.games?.map {
it.exposureEvent?.getFreshExposureEvent()!!
}
}
}
}

View File

@ -18,8 +18,9 @@ import com.gh.gamecenter.databinding.ItemHomeGameCollectionBigSlideCardGameBindi
import com.gh.gamecenter.databinding.ItemHomeGameCollectionSmallSlideCardCustomBinding
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.feature.entity.CustomPageTrackData
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.IExposureListProvider
import com.gh.gamecenter.home.custom.IGameChangedNotifier
import com.gh.gamecenter.home.custom.eventlistener.GameSubjectCollectionEventHelper
import com.gh.gamecenter.home.custom.model.CustomPageData
@ -106,6 +107,7 @@ class CustomHomeGameCollectionSlideAdapter(
// 小卡片
if (holder is HomeGameCollectionSmallSlideCardViewHolder) {
holder.updateSubject(subject)
holder.binding.run {
val params = root.layoutParams as ViewGroup.MarginLayoutParams
params.leftMargin = if (position == 0) 0 else (-24F).dip2px()
@ -184,8 +186,10 @@ class CustomHomeGameCollectionSlideAdapter(
}
}
inner class HomeGameCollectionBigSlideCardViewHolder(view: View) : BaseRecyclerViewHolder<Any>(view),
IGameChangedNotifier {
inner class HomeGameCollectionBigSlideCardViewHolder(view: View) :
BaseRecyclerViewHolder<Any>(view),
IGameChangedNotifier,
IExposureListProvider {
private var subject: CustomPageData.LinkColumnCollection.CustomSubjectEntity? = null
@ -294,8 +298,33 @@ class CustomHomeGameCollectionSlideAdapter(
eventHelper.navigateToGameDetailPage(bindingAdapterPosition, gameEntity, subject)
}
}
override fun provideExposureDataList(): List<ExposureEvent>? {
return subject?.games?.map {
it.exposureEvent?.getFreshExposureEvent()!!
}
}
override fun provideExposureData(): ExposureEvent? = null
}
class HomeGameCollectionSmallSlideCardViewHolder(val binding: ItemHomeGameCollectionSmallSlideCardCustomBinding) :
RecyclerView.ViewHolder(binding.root)
RecyclerView.ViewHolder(binding.root),
IExposureListProvider {
private var boundedSubject: CustomPageData.LinkColumnCollection.CustomSubjectEntity? = null
fun updateSubject(subject: CustomPageData.LinkColumnCollection.CustomSubjectEntity) {
boundedSubject = subject
}
override fun provideExposureDataList(): List<ExposureEvent>? {
return boundedSubject?.games?.map {
it.exposureEvent?.getFreshExposureEvent()!!
}
}
override fun provideExposureData(): ExposureEvent? = null
}
}

View File

@ -5,20 +5,28 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.common.util.DownloadItemUtils
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.core.utils.StringUtils
import com.gh.gamecenter.entity.GameDataWrapper
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.feature.entity.CustomPageTrackData
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.PageLocation
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.home.PageConfigure
import com.gh.gamecenter.home.custom.createExposureEvent
import com.gh.gamecenter.home.custom.eventlistener.OtherItemEventHelper
import com.gh.gamecenter.home.custom.model.CustomPageItem
import com.gh.gamecenter.home.test_v2.HomeGameTestV2GameListPlaceHolderViewHolder
import com.gh.gamecenter.home.test_v2.HomeGameTestV2GameListSpaceViewHolder
import com.gh.gamecenter.home.test_v2.HomeGameTestV2GameListViewHolder
import com.lightgame.download.DownloadEntity
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
/**
* @author : liujiarui
@ -30,7 +38,9 @@ class CustomHomeGameTestV2GameListRvAdapter(
private val childEventHelper: OtherItemEventHelper,
private val linkText: String,
private val pageLocation: PageLocation,
private val exposureInvoke: (Int, GameEntity) -> ExposureEvent
private val pageConfigure: PageConfigure,
private val componentPosition: Int,
private val trackData: CustomPageTrackData,
) : CustomBaseChildAdapter<GameDataWrapper, ViewHolder>(context) {
private val mEntrance: String = "新游开测"
@ -62,7 +72,6 @@ class CustomHomeGameTestV2GameListRvAdapter(
&& oldItem.gameData?.id == newItem.gameData?.id
&& oldItem.gameData?.name == newItem.gameData?.name
}
}, false).dispatchUpdatesTo(this)
}
@ -175,7 +184,6 @@ class CustomHomeGameTestV2GameListRvAdapter(
notifyItemChanged(index)
}
}
}
override fun notifyDownloadDeleted(status: EBDownloadStatus) {
@ -201,17 +209,21 @@ class CustomHomeGameTestV2GameListRvAdapter(
gameEntity: GameEntity,
position: Int
): ExposureEvent {
return exposureInvoke(position, gameEntity)
val time = gameEntity.time?.time ?: 0L
val date = Date(time * 1000L)
val sdf = SimpleDateFormat("MM.dd", Locale.CHINA)
val format = sdf.format(date)
gameEntity.exposureEvent = createExposureEvent(
gameEntity,
listOf(ExposureSource("新游开测", "$linkText-$format")),
pageConfigure.exposureSourceList,
position,
componentPosition,
trackData
)
return gameEntity.exposureEvent!!
}
/**
* 左右滑动曝光条目
*/
fun exposureItem(firstItemPosition: Int) {
val gameList = dataList
for (position in firstItemPosition..firstItemPosition + 2) {
val gameEntity = gameList.getOrNull(position)?.gameData ?: continue
putExposureEvent(gameEntity, position)
}
}
}

View File

@ -6,9 +6,7 @@ import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.feature.entity.CustomPageTrackData
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.home.PageConfigure
import com.gh.gamecenter.home.custom.eventlistener.SubjectEventHelper
import com.gh.gamecenter.home.custom.model.CustomSubjectItem
@ -23,7 +21,6 @@ class CustomHomeHorizontalSlideVideoAdapter(
context: Context,
private val pageConfigure: PageConfigure,
private val eventHelper: SubjectEventHelper,
private val exposureClosure: (GameEntity, Int) -> ExposureEvent,
) : CustomBaseChildAdapter<GameEntity, CustomHomeHorizontalSlideVideoItemViewHolder>(context) {
private lateinit var item: CustomSubjectItem
@ -44,8 +41,6 @@ class CustomHomeHorizontalSlideVideoAdapter(
override fun onBindViewHolder(holder: CustomHomeHorizontalSlideVideoItemViewHolder, position: Int) {
val gameEntity = dataList[position]
val event = exposureClosure(gameEntity, position)
holder.binding.root.setPadding(
16F.dip2px(),
holder.binding.root.topPadding,
@ -67,13 +62,11 @@ class CustomHomeHorizontalSlideVideoAdapter(
gameEntity,
this,
pageConfigure.entrance,
event,
item.data.showIndexIconSubscript,
item.data.showIndexSubtitle
)
}
override fun notifyDownload(download: DownloadEntity) {
dataList.forEachIndexed { index, game ->
if (game.id == download.gameId) {

View File

@ -10,6 +10,8 @@ import com.gh.gamecenter.common.entity.IconFloat
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.databinding.ItemHomeVgameRefactorBinding
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.IExposureProvider
import com.gh.gamecenter.home.custom.eventlistener.OtherItemEventHelper
import com.gh.gamecenter.manager.PackagesManager
import com.gh.vspace.VGameItemData
@ -52,9 +54,13 @@ class CustomHomeRecentVGameAdapter(
private var binding: ItemHomeVgameRefactorBinding,
private val childEventHelper: OtherItemEventHelper
) :
RecyclerView.ViewHolder(binding.root) {
RecyclerView.ViewHolder(binding.root), IExposureProvider {
private var boundedItem: VGameItemData? = null
fun bindView(entity: VGameItemData) {
boundedItem = entity
if (binding.gameIconIv.getTag(R.string.app_name) != entity.downloadEntity.packageName) {
val iconFloat = IconFloat(
entity.downloadEntity.getMetaExtra(Constants.GAME_ICON_FLOAT_TOP_TEXT),
@ -176,5 +182,9 @@ class CustomHomeRecentVGameAdapter(
binding.controlTv.text = itemData.controlText
binding.progressBar.progress = downloadEntity.percent.toInt()
}
override fun provideExposureData(): ExposureEvent? {
return boundedItem?.exposureEvent?.getFreshExposureEvent()
}
}
}

View File

@ -130,7 +130,6 @@ class CustomHomeRecommendItemGridAdapter(
}
interface OnEventListener {
fun navigateToLinkPage(link: LinkEntity, text: String, exposureEvent: ExposureEvent?)
}
}

View File

@ -15,8 +15,6 @@ import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.utils.DataLogUtils
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.PageSwitchDataHelper
import com.gh.gamecenter.entity.HomeSlide
import com.gh.gamecenter.feature.entity.GameEntity
@ -50,7 +48,6 @@ class CustomHomeSlideListAdapter(
}
}
val dataCount: Int
get() = dataList.size
@ -77,21 +74,8 @@ class CustomHomeSlideListAdapter(
return
}
val homeSlide = dataList[actualPosition]
val game = homeSlide.linkGame
if (homeSlide.linkType == "game") {
game?.exposureEvent = listener.createExposureEvent(actualPosition, game)
} else {
game?.exposureEvent = listener.createExposureEvent(actualPosition, game)
?.also {
it.payload.controlType = "轮播图"
it.payload.controlName = homeSlide.title
it.payload.controlLinkName = homeSlide.linkText
it.payload.controlLinkType = homeSlide.linkType
}
}
if (holder is CustomHomeSlideListItemViewHolder) {
holder.bindSlideListItem(homeSlide)
@ -129,11 +113,6 @@ class CustomHomeSlideListAdapter(
) {
rootView.setOnClickListener {
val actualPositionString = (actualPosition + 1).toString()
if (homeSlide.linkType == "video") {
MtaHelper.onEvent("首页_新", "轮播_点击", actualPositionString + "_视频详情")
} else if (homeSlide.linkType == "game") {
MtaHelper.onEvent("首页_新", "轮播_点击", actualPositionString + "_游戏详情")
}
PageSwitchDataHelper.pushCurrentPageData(
hashMapOf(
@ -240,11 +219,8 @@ class CustomHomeSlideListAdapter(
fun getActualSize() = dataList.size
interface OnEventListener {
fun createExposureEvent(actualPosition: Int, game: GameEntity?): ExposureEvent?
fun navigateToGameDetailPage(actualPosition: Int, game: GameEntity, text: String, link: LinkEntity?)
fun navigateToLinkPage(link: LinkEntity, text: String, exposureEvent: ExposureEvent?)
}
}

View File

@ -1,20 +1,18 @@
package com.gh.gamecenter.home.custom.adapter
import android.content.Context
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.common.util.DownloadItemUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.adapter.viewholder.GameViewHolder
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.StringUtils
import com.gh.gamecenter.databinding.RecyclerChildIconMatrixCustomBinding
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.feature.entity.CustomPageTrackData
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.IExposureProvider
import com.gh.gamecenter.home.custom.adapter.CustomPageAdapter.Companion.PAYLOAD_REFRESH_GAME_CHANGED
import com.gh.gamecenter.home.custom.eventlistener.SubjectEventHelper
import com.gh.gamecenter.home.custom.model.CustomSplitSubjectItem
@ -50,6 +48,9 @@ class CustomIconMatrixAdapter(
val isRefreshDownloadStatus = payloads.any {
PAYLOAD_REFRESH_GAME_CHANGED == it
}
holder.boundedGameEntity = getItem(position)
if (isRefreshDownloadStatus) {
DownloadItemUtils.updateItem(
context,
@ -77,7 +78,6 @@ class CustomIconMatrixAdapter(
item.name
)
gameName.maxLines = if (subject.showDownload) 1 else 2
}
val showStar = subject.showStar
&& subject.data?.any { it.commentCount > 3 } ?: false
@ -123,7 +123,6 @@ class CustomIconMatrixAdapter(
notifyItemChanged(index, PAYLOAD_REFRESH_GAME_CHANGED)
}
}
}
override fun notifyDownloadDeleted(status: EBDownloadStatus) {
@ -147,5 +146,11 @@ class CustomIconMatrixAdapter(
class CustomChildIconMatrixViewHolder(
val binding: RecyclerChildIconMatrixCustomBinding
) : ViewHolder(binding.root)
) : ViewHolder(binding.root), IExposureProvider {
var boundedGameEntity: GameEntity? = null
override fun provideExposureData(): ExposureEvent? {
return boundedGameEntity?.exposureEvent?.getFreshExposureEvent()
}
}
}

View File

@ -9,13 +9,11 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.OnScrollListener
import com.gh.common.exposure.IExposable
import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.game.GameAndPosition
import com.gh.gamecenter.home.custom.CustomPageViewModel
import com.gh.gamecenter.home.custom.IGameChangedNotifier
@ -69,7 +67,7 @@ class CustomPageAdapter(
private val viewModel: CustomPageViewModel,
private val lifecycleOwner: LifecycleOwner,
private val scrollCalculatorHelper: ScrollCalculatorHelper,
) : ListAdapter<CustomPageItem, BaseCustomViewHolder>(CALLBACK), IExposable, IGameChangedNotifier {
) : ListAdapter<CustomPageItem, BaseCustomViewHolder>(CALLBACK), IGameChangedNotifier {
private var loadStatus: LoadStatus? = null
@ -86,13 +84,13 @@ class CustomPageAdapter(
}
val firstShowPosition: LiveData<Int> = _firstShowPosition
override fun submitList(list: List<CustomPageItem>?) {
override fun submitList(list: List<CustomPageItem>?, commitCallback: Runnable?) {
val dataWithFooter = if (list.isNullOrEmpty()) {
ArrayList(list)
} else {
list + CustomFooterItem
}
super.submitList(dataWithFooter)
super.submitList(dataWithFooter, commitCallback)
}
/**
@ -306,10 +304,10 @@ class CustomPageAdapter(
val item = getItem(position)
when (holder) {
is CustomDoubleCardViewHolder -> holder.bindView(item)
is CustomHomeHorizontalSlideVideoListViewHolder -> holder.bindView(item)
is CustomGameItemViewHolder -> {
holder.bindView(item, this)
}
is CustomGameItemViewHolder -> holder.bindView(item, this)
is CustomGameVerticalSlideViewHolder -> holder.bindView(item)
@ -339,9 +337,7 @@ class CustomPageAdapter(
is CustomHomeItemGameTestV2ViewHolder -> holder.bindView(item, position)
is CustomHomeDiscoverCardViewHolder -> {
holder.bindView(item, position)
}
is CustomHomeDiscoverCardViewHolder -> holder.bindView(item, position)
is CustomBigImageRecommendViewHolder -> holder.bindView(item)
@ -415,14 +411,6 @@ class CustomPageAdapter(
holder.onViewDetach(recyclerView)
}
override fun getEventByPosition(pos: Int): ExposureEvent? {
return getItem(pos).exposureEvent
}
override fun getEventListByPosition(pos: Int): List<ExposureEvent>? {
return getItem(pos).exposureEventList
}
override fun notifyDownload(download: DownloadEntity) {
val data = getGameEntityByPackage(download.packageName)
data.forEach { (game, position, _) ->
@ -448,7 +436,6 @@ class CustomPageAdapter(
it.notifyDownloadDeleted(status)
}
}
}
override fun notifyInstalled(busFour: EBPackage) {
@ -517,7 +504,6 @@ class CustomPageAdapter(
private val onLoadMoreListener = object : OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val layoutManger = recyclerView.layoutManager
@ -528,7 +514,6 @@ class CustomPageAdapter(
}
}
}
}

View File

@ -10,8 +10,9 @@ import com.gh.gamecenter.common.view.AsyncCell
import com.gh.gamecenter.databinding.RankCollectionItemCustomBinding
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.feature.entity.CustomPageTrackData
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.IExposureListProvider
import com.gh.gamecenter.game.rank.CustomRankGameItem
import com.gh.gamecenter.game.rank.RankGameItemUi
import com.gh.gamecenter.home.custom.IGameChangedNotifier
@ -109,8 +110,6 @@ class CustomRankCollectionAdapter(
holder.gameItemList[i].rankItemUi.root.visibility = View.GONE
}
}
}
}
}
@ -138,7 +137,6 @@ class CustomRankCollectionAdapter(
override fun onViewAttachedToWindow(holder: RankCollectionItemViewHolder) {
super.onViewAttachedToWindow(holder)
viewHolderList.add(holder)
}
override fun onViewDetachedFromWindow(holder: RankCollectionItemViewHolder) {
@ -162,7 +160,7 @@ class CustomRankCollectionAdapter(
}
inner class RankCollectionItemViewHolder(view: View) :
BaseRecyclerViewHolder<Any>(view), IGameChangedNotifier {
BaseRecyclerViewHolder<Any>(view), IGameChangedNotifier, IExposureListProvider {
val gameItemList = arrayListOf<CustomRankGameItem>()
@ -235,5 +233,13 @@ class CustomRankCollectionAdapter(
}
}
}
override fun provideExposureData(): ExposureEvent? = null
override fun provideExposureDataList(): List<ExposureEvent>? {
return subjectEntity?.games?.map {
it.exposureEvent?.getFreshExposureEvent()!!
}
}
}
}

View File

@ -9,6 +9,8 @@ import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.databinding.RecyclerRefreshIconItemCustomBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.IExposureProvider
import com.gh.gamecenter.game.horizontal.GameHorizontalSimpleItemViewHolder
import com.gh.gamecenter.home.custom.eventlistener.GameSubjectCollectionEventHelper
import com.gh.gamecenter.home.custom.model.CustomPageData
@ -16,7 +18,6 @@ import com.gh.gamecenter.home.custom.model.CustomPageData
class CustomRefreshIconAdapter(
context: Context,
private val eventHelper: GameSubjectCollectionEventHelper,
private val exposureInvoke: (Int, GameEntity) -> Unit
) : CustomBaseChildAdapter<GameEntity, CustomRefreshIconAdapter.RefreshIconChildViewHolder>(context) {
private var collectionName = ""
@ -45,8 +46,7 @@ class CustomRefreshIconAdapter(
override fun onBindViewHolder(holder: RefreshIconChildViewHolder, position: Int) {
val gameEntity = getItem(position)
exposureInvoke(position, gameEntity)
holder.exposureEvent = gameEntity.exposureEvent
holder.binding.run {
val padR = if (position == itemCount - 1) 16F.dip2px() else 0
root.setPadding(16F.dip2px(), 0, padR, 0)
@ -65,10 +65,15 @@ class CustomRefreshIconAdapter(
)
eventHelper.navigateToGameDetailPage(position, gameEntity, subject)
}
}
}
class RefreshIconChildViewHolder(val binding: RecyclerRefreshIconItemCustomBinding) : ViewHolder(binding.root)
class RefreshIconChildViewHolder(val binding: RecyclerRefreshIconItemCustomBinding) :
ViewHolder(binding.root), IExposureProvider {
var exposureEvent: ExposureEvent? = null
override fun provideExposureData(): ExposureEvent? {
return exposureEvent?.getFreshExposureEvent()
}
}
}

View File

@ -6,12 +6,11 @@ import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.visibleIf
import com.gh.gamecenter.databinding.RecyclerNotificationColumnItemBinding
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.IExposureProvider
import com.gh.gamecenter.home.custom.model.CustomPageData
class NotificationColumnAdapter(
context: Context,
private val exposureInvoke: (Int, CustomPageData.Notify) -> Unit
) :
class NotificationColumnAdapter(context: Context, ) :
CustomBaseChildAdapter<CustomPageData.Notify, NotificationColumnAdapter.NotificationColumChildViewHolder>(context) {
private var toBeDeletedItemIds = arrayListOf<String>()
@ -51,13 +50,14 @@ class NotificationColumnAdapter(
override fun onBindViewHolder(holder: NotificationColumChildViewHolder, position: Int) {
val item = getItem(position)
holder.exposureEvent = item.exposureEvent
with(holder.binding) {
ivIcon.displayGameIcon(item.image, null, goneIfEmpty = true, null)
tvTitle.text = item.title
tvDesc.text = item.addedContent
ivClose.visibleIf(item.isClosable)
}
exposureInvoke(position, item)
}
private fun getValidItem(position: Int): CustomPageData.Notify {
@ -90,5 +90,13 @@ class NotificationColumnAdapter(
class NotificationColumChildViewHolder(
val binding: RecyclerNotificationColumnItemBinding
) : RecyclerView.ViewHolder(binding.root)
) : RecyclerView.ViewHolder(binding.root), IExposureProvider {
var exposureEvent: ExposureEvent? = null
override fun provideExposureData(): ExposureEvent? {
return exposureEvent?.getFreshExposureEvent()
}
}
}

View File

@ -9,6 +9,8 @@ import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.RecyclerRecommendCardItemBinding
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.IExposureProvider
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.ui.RecommendCardUi
@ -23,7 +25,6 @@ class RecommendCardAdapter(
override fun onBindViewHolder(holder: RecommendCardChildViewHolder, position: Int) {
val item = getItem(position)
listener.onItemExposure(position, item)
holder.bind(item, position)
holder.itemView.setOnClickListener {
listener.onItemClick(position, item)
@ -31,9 +32,13 @@ class RecommendCardAdapter(
}
class RecommendCardChildViewHolder(val binding: RecyclerRecommendCardItemBinding) :
RecyclerView.ViewHolder(binding.root) {
RecyclerView.ViewHolder(binding.root), IExposureProvider {
private var exposureEvent: ExposureEvent? = null
fun bind(item: CustomPageData.RecommendCard, position: Int) {
exposureEvent = item.exposureEvent
binding.tvTitle.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(itemView.context))
binding.tvOriginalPrice.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(itemView.context))
@ -87,5 +92,9 @@ class RecommendCardAdapter(
hideOutOfBoundViewsIfNeed(views)
}
}
override fun provideExposureData(): ExposureEvent? {
return exposureEvent?.getFreshExposureEvent()
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,185 @@
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

@ -0,0 +1,42 @@
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

@ -8,6 +8,7 @@ import com.gh.gamecenter.common.entity.PKEntity
import com.gh.gamecenter.entity.*
import com.gh.gamecenter.feature.entity.FloatingWindowEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.IgnoredOnParcel
@ -566,6 +567,8 @@ class CustomPageData(
community = _linkCommunity
)
var exposureEvent: ExposureEvent? = null
data class Guide(
@SerializedName("text")
private val _text: String? = null
@ -633,6 +636,8 @@ class CustomPageData(
text = linkText,
community = _linkCommunity
)
var exposureEvent: ExposureEvent? = null
}
data class Announcement(
@ -675,6 +680,8 @@ class CustomPageData(
text = linkText,
community = _linkCommunity
)
var exposureEvent: ExposureEvent? = null
}
data class LinkRecentlyPlayed(
@ -755,6 +762,8 @@ class CustomPageData(
community = _linkCommunity
)
var exposureEvent: ExposureEvent? = null
data class TextWithSymbol(
@SerializedName("text")
private val _text: String? = null,

View File

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

View File

@ -1,6 +1,7 @@
package com.gh.gamecenter.home.custom.model
import android.annotation.SuppressLint
import com.gh.common.util.AdHelper
import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.entity.PKEntity
@ -31,9 +32,15 @@ class CustomPageRemoteDataSource(
}
@SuppressLint("CheckResult")
fun loadCustomPageData(pageId: String, page: Int, forceLoad: Boolean = false): Single<CustomPageData> {
fun loadCustomPageData(
pageId: String,
page: Int,
forceLoad: Boolean = false,
): Single<CustomPageData> {
val isDefaultPage = pageId == mainWrapperRepository.defaultCustomPageId
return if (page == 1 &&
pageId == mainWrapperRepository.defaultCustomPageId &&
isDefaultPage &&
mainWrapperRepository.customPageLiveData.value != null &&
!forceLoad
) {
@ -41,16 +48,25 @@ class CustomPageRemoteDataSource(
it.onSuccess(mainWrapperRepository.customPageLiveData.value!!)
}
} else {
newApi.getCustomPageData(pageId, page)
newApi.getCustomPageData(
pageId,
page,
if (!isDefaultPage) AdHelper.getIdfaString() else ""
)
}
}
fun loadCollectionContent(item: CustomSubjectCollectionItem): Single<List<CustomPageData.LinkColumnCollection.CustomSubjectEntity>> =
if (item.isSubjectCollection) {
newApi.getColumnsCollectionContents(item.data.id, "component", item.loadPage + 1, 1)
} else {
newApi.getGameCollectionContents(item.data.id, "component", item.loadPage + 1, 1)
}
fun loadColumnsCollectionContents(
collectionId: String,
page: Int
): Single<MutableList<CustomPageData.LinkColumnCollection.CustomSubjectEntity>> =
newApi.getColumnsCollectionContents(collectionId, "component", page, 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>> =
if (subjectEntity.isQQColumn) {

View File

@ -2,10 +2,13 @@ package com.gh.gamecenter.home.custom.model
import androidx.annotation.MainThread
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asLiveData
import com.gh.common.filter.RegionSettingHelper
import com.gh.common.util.HomePluggableHelper
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.SP_HIDDEN_NOTIFICATIONS
import com.gh.gamecenter.common.utils.observableToMain
@ -17,7 +20,9 @@ import com.gh.gamecenter.entity.DiscoveryGameCardEntity
import com.gh.gamecenter.entity.DiscoveryGameCardLabel
import com.gh.gamecenter.feature.entity.AcctRecord
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.packagehelper.PackageRepository
import com.gh.gamecenter.retrofit.RetrofitManager
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
@ -34,6 +39,30 @@ class CustomPageShareRepository private constructor() {
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>()
val discoverData: LiveData<DiscoveryCardEntity> = _discoverData

View File

@ -90,7 +90,8 @@ class GameSubjectCollectionTracker(private val pageLocation: PageLocation) {
game?.name ?: "",
link?.type ?: "",
link?.link ?: "",
link?.text ?: ""
link?.text ?: "",
adGroupId = game?.adGroupId ?: ""
)
}

View File

@ -46,7 +46,8 @@ class SubjectTracker(private val pageLocation: PageLocation) {
"自定义页面",
item.componentStyle,
text,
buttonType
buttonType,
adGroupId = game?.adGroupId ?: "",
)
}

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