Compare commits

..

129 Commits

Author SHA1 Message Date
5bd882b489 chore: 版本更新至 5.42.5 2025-07-09 10:34:02 +08:00
1c1d1ff9b9 Merge branch 'hotfix/v5.42.4-1194/GHZSCY-8199' into 'release'
fix: 鸿蒙版本识别错误 https://jira.shanqu.cc/browse/GHZSCY-8199

See merge request halo/android/assistant-android!2228
2025-07-09 09:34:06 +08:00
a4e806d536 fix: 鸿蒙版本识别错误 https://jira.shanqu.cc/browse/GHZSCY-8199 2025-07-08 17:36:11 +08:00
122a0b929f Merge branch 'hotfix/v5.42.4-1194/GHZS-8203' into 'release'
fix: 已安装游戏替换问题 https://jira.shanqu.cc/browse/GHZSCY-8203

See merge request halo/android/assistant-android!2227
2025-07-08 17:28:11 +08:00
2a069fb5e7 fix: 已安装游戏替换问题 https://jira.shanqu.cc/browse/GHZSCY-8203 2025-07-08 17:14:49 +08:00
115b6638a8 Merge branch 'hotfix/v5.42.4-1194/huawei-deadsystemexception' into 'release'
fix: 华为系 android 10 设备停用静默获取已安装应用列表功能

See merge request halo/android/assistant-android!2226
2025-07-08 15:42:42 +08:00
ecbdcdced7 fix: 华为系 android 10 设备停用静默获取已安装应用列表功能 2025-07-08 14:54:08 +08:00
01db3b07d4 Merge branch 'fix/GHZSCY-8198' into 'release'
fix:【光环助手】鸿蒙系统安装弹窗「前往设置」按钮点击无反应 https://jira.shanqu.cc/browse/GHZSCY-8198

See merge request halo/android/assistant-android!2225
2025-07-08 09:45:19 +08:00
081658873f fix:【光环助手】鸿蒙系统安装弹窗「前往设置」按钮点击无反应 https://jira.shanqu.cc/browse/GHZSCY-8198 2025-07-08 09:45:19 +08:00
8d7157095d Merge branch 'feat/GHZSCY-8150' into 'release'
feat: GameDetailPageTabSelect埋点事件优化-客户端 https://jira.shanqu.cc/browse/GHZSCY-8150

See merge request halo/android/assistant-android!2224
2025-07-07 15:29:25 +08:00
1eb84ca71a feat: GameDetailPageTabSelect埋点事件优化-客户端 https://jira.shanqu.cc/browse/GHZSCY-8150 2025-07-07 15:27:56 +08:00
9f4149358a Merge branch 'hotfix/v5.42.4-1194/simulator_crash' into 'release'
fix: 修复 mumu 模拟器上 loadDataWithBaseUrl 方法的空指针闪退问题

See merge request halo/android/assistant-android!2223
2025-07-07 14:33:46 +08:00
5451f244bc Merge branch 'hotfix/v5.42.4-1194/subject_tab_crash' into 'release'
fix: 修复进入专题合集页面后快速返回出现的闪退问题...

See merge request halo/android/assistant-android!2222
2025-07-07 11:31:14 +08:00
aca80aceea fix: 修复进入专题合集页面后快速返回出现的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/456018/?project=22 2025-07-07 11:29:16 +08:00
964b391eec fix: 修复 mumu 模拟器上 loadDataWithBaseUrl 方法的空指针闪退问题 2025-07-07 10:37:08 +08:00
7584da3734 Merge branch 'hotfix/v5.42.4-1194/exposure_crash' into 'release'
fix: 修复曝光获取当前页面名称时偶发的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/456204

See merge request halo/android/assistant-android!2221
2025-06-30 15:01:27 +08:00
62060a5bf0 fix: 修复曝光获取当前页面名称时偶发的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/456204 2025-06-30 14:59:46 +08:00
f49caa11c8 Merge branch 'hotfix/v5.42.4-1194/tab_wrapper_crash' into 'release'
fix: 修复二级多tab导航页显示引导时出现的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/447802

See merge request halo/android/assistant-android!2219
2025-06-30 14:58:41 +08:00
61c2f48a50 fix: 修复二级多tab导航页显示引导时出现的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/447802 2025-06-30 14:43:40 +08:00
b9e0585dc9 chore: 版本更新至 5.42.4 2025-06-16 17:31:54 +08:00
9f191832fd Merge branch 'hotfix/v5.42.3-1193/GHZSCY-8143' into 'release'
fix: 修复 dsp 广告曝光上报问题 https://jira.shanqu.cc/browse/GHZSCY-8143

See merge request halo/android/assistant-android!2215
2025-06-16 14:48:43 +08:00
d8ee461a0a fix: 修复 dsp 广告曝光上报问题 2025-06-16 13:58:49 +08:00
99094896b1 Merge branch 'fix/libao_crash' into 'release'
fix: 修复礼包列表偶发闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/455408

See merge request halo/android/assistant-android!2214
2025-06-09 11:10:34 +08:00
23203b0c5d fix: 修复礼包列表偶发闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/455408 2025-06-09 10:46:01 +08:00
4e054aa834 chore: 版本更新至 5.42.3 2025-06-03 11:07:01 +08:00
e36cc54083 Merge branch 'fix/202506/454319' into 'release'
fix:https://sentry.shanqu.cc/organizations/lightgame/issues/454319/?project=22

See merge request halo/android/assistant-android!2213
2025-06-03 11:01:58 +08:00
bc5935d4b4 Merge branch 'hotfix/v5.42.2-1192/filter_crash' into 'release'
fix: 修复游戏屏蔽功能偶发的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/453949

See merge request halo/android/assistant-android!2212
2025-06-03 10:56:07 +08:00
8fc3bb720d fix:https://sentry.shanqu.cc/organizations/lightgame/issues/454319/?project=22 2025-06-03 10:45:27 +08:00
e8b68f889d fix: 修复游戏屏蔽功能偶发的闪退问题 2025-06-03 10:42:36 +08:00
649af64e62 Merge branch 'fix/GHZSCY-8061' into 'release'
fix:【光环助手】自定义专题合集-自定义设置点击问题 https://jira.shanqu.cc/browse/GHZSCY-8061

See merge request halo/android/assistant-android!2211
2025-05-30 13:39:50 +08:00
46516fb9c0 fix:【光环助手】自定义专题合集-自定义设置点击问题 https://jira.shanqu.cc/browse/GHZSCY-8061 2025-05-30 13:39:50 +08:00
eecfdced32 chore: 版本更新至 5.42.2 2025-05-30 11:37:13 +08:00
74d9aaf664 Merge branch 'hotfix/v5.42.1-1191/exposure_crash' into 'release'
fix: 修复曝光收集时的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/454091

See merge request halo/android/assistant-android!2210
2025-05-30 11:35:17 +08:00
28bbb128dd fix: 修复曝光收集时的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/454091 2025-05-30 09:43:19 +08:00
7640812518 chore: 版本更新至 5.42.1 2025-05-29 11:03:59 +08:00
0a30e82e17 Merge branch 'hotfix/v5.42.0-1190/exposure_crash' into 'release'
fix: 修复曝光收集时的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/453915

See merge request halo/android/assistant-android!2208
2025-05-29 11:01:45 +08:00
559951ad82 fix: 修复曝光收集时的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/453915 2025-05-29 10:43:08 +08:00
4a4cba5445 Merge branch 'feat/GHZSCY-8049-merge' into 'release'
feat: 游戏详情UI优化-客户端 https://jira.shanqu.cc/browse/GHZSCY-8049

See merge request halo/android/assistant-android!2209
2025-05-29 10:22:59 +08:00
b6d6568ac9 feat: 游戏详情UI优化-客户端 https://jira.shanqu.cc/browse/GHZSCY-8049 2025-05-29 10:21:47 +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
1d74bfc7a8 feat: 从SD卡兼容所有文件管理权限 2025-05-15 14:52:27 +08:00
3d118544bb Merge branch 'feat/GHZSCY-7890' into 'dev'
feat: 游戏详情-内容卡片 红点显示优化—客户端 https://jira.shanqu.cc/browse/GHZSCY-7890

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

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

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

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

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

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

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

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

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

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

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

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

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

View File

@ -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
@ -17,6 +18,7 @@ import com.therouter.TheRouter
import com.facebook.drawee.controller.BaseControllerListener
import com.facebook.drawee.view.SimpleDraweeView
import com.facebook.imagepipeline.image.ImageInfo
import com.g00fy2.versioncompare.Version
import com.gh.common.exposure.ExposureManager
import com.gh.common.util.DirectUtils.directToLinkPage
import com.gh.common.util.LogUtils
@ -43,6 +45,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 +123,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)
@ -179,11 +187,16 @@ object AdDelegateHelper {
"halo_launch" -> {
config.ownerAd?.startAd?.let { it.id = config.ownerAd.id }
// HarmonyOS 2.2.0 版本不展示第三方开屏广告 (因为会引起奇怪的闪退)
if (MetaUtil.getRom().name == "HarmonyOS"
&& MetaUtil.getRom().versionName == "2.2.0"
&& config.displayRule.adSource == "third_party_ads") {
// HarmonyOS 4.2.0 版本不展示第三方开屏广告 (因为会引起奇怪的闪退)
if (MetaUtil.getRom().romName == "HarmonyOS"
&& Version(MetaUtil.getRom().romVersion).isLowerThan(Version("4.2.0"))
&& config.displayRule.adSource == AD_TYPE_SDK
) {
return
}
// 华为系 Android 10 不展示第三方开屏广告 (因为会引起奇怪的闪退)
if (isBuggyHuaweiDevice() && config.displayRule.adSource == AD_TYPE_SDK) {
return
}
@ -215,6 +228,7 @@ object AdDelegateHelper {
private fun shouldShowStartUpAdWhenHotLaunch() = (mCsjAdImpl != null)
&& mSplashAd?.displayRule?.hotStartSplashAd?.type == AD_TYPE_SDK
&& mSplashAd?.hotStartThirdPartyAd != null
&& !isBuggyHuaweiDevice()
/**
* 是否需要显示下载管理广告
@ -609,32 +623,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 +797,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

@ -390,7 +390,7 @@ public class Config {
"manufacturer", Build.MANUFACTURER,
"model", Build.MODEL,
"android_sdk_version", String.valueOf(Build.VERSION.SDK_INT),
"rom", MetaUtil.INSTANCE.getRom().name() + " " + MetaUtil.INSTANCE.getRom().getVersionName()
"rom", MetaUtil.INSTANCE.getRom().getRomName() + " " + MetaUtil.INSTANCE.getRom().getRomVersion()
);
RetrofitManager.getInstance()

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,61 @@
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
val isDSPExposureEvent = exposureEvent.payload.miniGameType == Constants.DSP_GAME
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)
} else if (isDSPExposureEvent && inExposure) {
Utils.log(
RecyclerViewExposureHelper.TAG,
"上报 DSP 曝光 ${exposureEvent.payload.gameName} ${exposureEvent.id}"
)
ExposureManager.logDSP(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,21 @@ object ExposureManager {
}
}
/**
* Log a wechat mini game cpm collection of exposure event.
*/
fun logCPM(event: ExposureEvent) {
AppExecutor.logExecutor.execute {
WGameSubjectCPMListReportHelper.reportExposure(event)
}
}
fun logDSP(event: ExposureEvent) {
AppExecutor.logExecutor.execute {
DspReportHelper.report(event.payload.showUrl)
}
}
/**
* @param forcedUpload Ignore all restrictions.
*/
@ -114,14 +145,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

@ -73,7 +73,7 @@ object RegionSettingHelper {
if (list is ArrayList) return list
}
val listCopy: ArrayList<GameEntity> = if (list is ArrayList) list else ArrayList(list)
val listCopy: ArrayList<GameEntity> = ArrayList(list)
listCopy.removeAll { mFilterGameIdSet?.contains(it.id) ?: false }
return listCopy
}

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

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

View File

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

View File

@ -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,25 +99,41 @@ object GameSubstituteRepositoryHelper {
continue
}
// 广告系统的广告游戏不替换
if (game.adGroupId.isNotEmpty()) {
continue
}
// 这个 position 的游戏是否需要被替换
var thisPositionNeedToBeReplaced = false
// 检查是否已安装该游戏里同包名的 APK
for (apk in game.getApk()) {
if (PackageHelper.validLocalPackageNameSet.contains(apk.packageName)) {
if (game.getApk().size == 1) {
val apk = game.getApk().firstOrNull()
// 若该游戏只有一个 APK且该 APK 的包名在本地已安装的包名列表中
if (PackageHelper.localPackageNameSet.contains(apk?.packageName)) {
// 将该位置的游戏标记为需要替换
positionOfGameToBeReplacedList.add(index)
thisPositionNeedToBeReplaced = true
break
}
} else {
// 检查是否已安装该游戏里同包名的 APK
for (apk in game.getApk()) {
if (PackageHelper.validLocalPackageNameSet.contains(apk.packageName)) {
// 将该位置的游戏标记为需要替换
positionOfGameToBeReplacedList.add(index)
thisPositionNeedToBeReplaced = true
break
}
}
}
// 检查是否已安装该游戏 id
if (PackagesManager.getInstalledDataByGameId(game.id) != null) {
if (!thisPositionNeedToBeReplaced && PackagesManager.getInstalledDataByGameId(game.id) != null) {
// 将该位置的游戏标记为需要替换
positionOfGameToBeReplacedList.add(index)
thisPositionNeedToBeReplaced = true
break
continue
}
// 若此游戏所包含的 apk 没有已安装,那么再检查是否已安装有预设相关包名
@ -147,11 +163,19 @@ object GameSubstituteRepositoryHelper {
if (mGameCollectionList.isNullOrEmpty()) return
// 临时的游戏 ID 列表(包含 gameList 的游戏),避免重复替换
val tempDisplayingGameIdSet = HashSet(displayingGameIdSet)
gameList.forEach {
tempDisplayingGameIdSet.add(it.id)
}
for (position in positionOfGameToBeReplacedList) {
val validGame = getValidGame(relatedCollectionId, displayingGameIdSet)
val validGame = getValidGame(relatedCollectionId, tempDisplayingGameIdSet)
validGame?.let {
gameList[position] = it
displayingGameIdSet.add(it.id)
tempDisplayingGameIdSet.add(it.id)
}
}
}

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

@ -245,6 +245,7 @@ public class LogUtils {
object.put("jnfj", MetaUtil.getBase64EncodedIMEI());
object.put("G_ID", UserManager.getInstance().getDeviceId());
object.put("oaid", HaloApp.getInstance().getOAID());
object.put("rom", MetaUtil.getMeta().getRom());
} catch (JSONException e) {
e.printStackTrace();
}
@ -264,6 +265,7 @@ public class LogUtils {
object.put("channel", HaloApp.getInstance().getChannel());
object.put("dia", MetaUtil.getBase64EncodedAndroidId());
object.put("oaid", MetaUtil.INSTANCE.getMeta().getOaid());
object.put("rom", MetaUtil.getMeta().getRom());
object.put("time", Utils.getTime(context));
object.put("network", DeviceUtils.getNetwork(context));
object.put("user_id", UserManager.getInstance().getUserId());
@ -289,6 +291,7 @@ public class LogUtils {
metaObject.put("channel", meta.getChannel());
metaObject.put("gid", meta.getGid());
metaObject.put("oaid", meta.getOaid());
metaObject.put("rom", MetaUtil.getMeta().getRom());
metaObject.put("jnfj", MetaUtil.getBase64EncodedIMEI());
metaObject.put("mac", meta.getMac());
metaObject.put("manufacturer", meta.getManufacturer());
@ -495,6 +498,7 @@ public class LogUtils {
metaObject.put("os", meta.getOs());
metaObject.put("userId", meta.getUserId());
metaObject.put("oaid", HaloApp.getInstance().getOAID());
metaObject.put("rom", MetaUtil.getMeta().getRom());
} catch (JSONException e) {
e.printStackTrace();

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

@ -33,6 +33,7 @@ import com.lightgame.utils.Utils
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.*
import java.util.HashMap
import java.util.Locale
import java.util.concurrent.Executors
import kotlin.collections.ArrayList
import kotlin.collections.HashSet
@ -288,12 +289,22 @@ object PackageHelper {
* 用户是否已经使用另类方式获取已安装应用列表
*/
private fun isUseAlternativeWayToGetInstalledPackages(): Boolean {
if (isBuggyHuaweiDevice()) {
useAlternativeWayToGetInstalledPackages = false
return false
}
return useAlternativeWayToGetInstalledPackages
|| (SPUtils.getBoolean(SP_GET_INSTALLED_PACKAGES_BY_ALTERNATIVE_API)
.also { useAlternativeWayToGetInstalledPackages = it })
}
private fun updateUseAlternativeWayToGetInstalledPackages() {
if (isBuggyHuaweiDevice()) {
useAlternativeWayToGetInstalledPackages = false
return
}
// 启用另类获取已安装应用列表的 API
useAlternativeWayToGetInstalledPackages = true
SPUtils.setBoolean(SP_GET_INSTALLED_PACKAGES_BY_ALTERNATIVE_API, true)
@ -478,7 +489,12 @@ object PackageHelper {
/**
* 是否支持动态获取已安装应用列表权限
*/
fun isSupportGetInstalledAppsPermission(context: Context): Boolean {
private fun isSupportGetInstalledAppsPermission(context: Context): Boolean {
if (isBuggyHuaweiDevice()) {
// 华为系 Android 10 设备存在 bug调用过多可能会触发 DeadSystemException
return false
}
if (isUseAlternativeWayToGetInstalledPackages()) {
// 已经使用另类获取已安装应用列表形式,强制判定为不支持动态获取已安装应用列表权限
return false
@ -789,4 +805,13 @@ object PackageHelper {
return packageList
}
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

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

@ -37,7 +37,7 @@ public class PostCommentUtils {
device.put("model", Build.MODEL);
device.put("manufacturer", Build.MANUFACTURER);
device.put("android_version", android.os.Build.VERSION.RELEASE);
device.put("rom", MetaUtil.INSTANCE.getRom().name() + " " + MetaUtil.INSTANCE.getRom().getVersionName());
device.put("rom", MetaUtil.INSTANCE.getRom().getRomName() + " " + MetaUtil.INSTANCE.getRom().getRomVersion());
content.put("device", device);
} catch (Exception e) {
e.printStackTrace();

View File

@ -9,15 +9,14 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.gh.common.constant.Config
import com.gh.gamecenter.common.utils.DialogHelper
import com.gh.gamecenter.entity.NewApiSettingsEntity
import com.lightgame.utils.Utils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.DialogHelper
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.enlargeTouchArea
import com.gh.gamecenter.common.utils.setDrawableEnd
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.entity.NewApiSettingsEntity
import com.halo.assistant.HaloApp
import com.lightgame.utils.Utils
/**
* 处理厂商纯净/安全模式的辅助类
@ -313,9 +312,11 @@ object PureModeHelper {
intent.setPackage("com.huawei.security.privacycenter")
intent.setAction("com.huawei.securitycenter.PURE_MODE_ACTIVITY")
intent.putExtra("intent_from_settings", true)
context.startActivity(intent)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
HaloApp.getInstance().startActivity(intent)
} catch (_: Exception) {
toSystemSettings(context)
}
}

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

@ -9,7 +9,11 @@ import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.exposure.meta.MetaUtil
import com.gh.gamecenter.common.exposure.meta.MetaUtil.getMeta
import com.gh.gamecenter.common.loghub.LoghubUtils
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.utils.DeviceUtils
import com.gh.gamecenter.common.utils.asVGame
import com.gh.gamecenter.common.utils.getExtension
import com.gh.gamecenter.common.utils.getMetaExtra
import com.gh.gamecenter.common.utils.isSimulatorGame
import com.gh.ndownload.NDataChanger
import com.gh.ndownload.NDownloadBridge
import com.halo.assistant.HaloApp
@ -427,6 +431,7 @@ object DownloadDataHelper {
metaObject.put("channel", meta.channel)
metaObject.put("gid", meta.gid)
metaObject.put("oaid", meta.oaid)
metaObject.put("rom", meta.rom)
metaObject.put("manufacturer", meta.manufacturer)
metaObject.put("model", meta.model)
metaObject.put("network", DeviceUtils.getNetwork(context))

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

@ -3,9 +3,6 @@ package com.gh.download
import android.annotation.SuppressLint
import android.text.TextUtils
import com.gh.common.util.*
import com.gh.common.util.DataCollectionUtils
import com.gh.common.util.PackageInstaller
import com.gh.common.util.PackageUtils
import com.gh.common.xapk.XapkInstaller
import com.gh.download.server.BrowserInstallHelper
import com.gh.gamecenter.common.constant.Constants
@ -33,9 +30,7 @@ import com.lightgame.download.DownloadEntity
import com.lightgame.utils.Utils
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONException
import org.json.JSONObject

View File

@ -7,6 +7,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.common.DefaultUrlHandler
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.loadDataCompat
import com.gh.gamecenter.databinding.DownloadDialogInstructionItemBinding
class DownloadDialogInstructionItemViewHolder(val binding: DownloadDialogInstructionItemBinding) :
@ -14,11 +15,8 @@ class DownloadDialogInstructionItemViewHolder(val binding: DownloadDialogInstruc
fun bindItem(listData: List<DownloadDialogItemData>, position: Int, entrance: String) {
val instruction = listData[position].instruction
binding.webView.loadDataWithBaseURL(
null,
binding.webView.loadDataCompat(
"<body style='margin:0;padding:0;color:#666666;font-size:12px;line-height:18px;'>$instruction</body>",
"text/html",
"utf-8", null
)
binding.webView.settings
binding.webView.setBackgroundColor(Color.TRANSPARENT)

View File

@ -9,6 +9,7 @@ import androidx.fragment.app.FragmentActivity
import com.gh.gamecenter.common.base.fragment.BaseDialogFragment
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.common.utils.loadDataCompat
import com.gh.gamecenter.databinding.DialogDownloadLinkBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.halo.assistant.HaloApp
@ -27,10 +28,7 @@ class DownloadLinkDialog : BaseDialogFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val binding: DialogDownloadLinkBinding = DialogDownloadLinkBinding.inflate(layoutInflater, container, false)
binding.title.text = mLinkEntity?.title
binding.webView.loadDataWithBaseURL(
null,
mLinkEntity?.content ?: "", "text/html", "utf-8", null
)
binding.webView.loadDataCompat(mLinkEntity?.content ?: "")
binding.confirm.setOnClickListener {
dismissAllowingStateLoss()

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()
@ -217,7 +217,7 @@ class DetailViewHolder(
}
private fun restoreDialogFragment() {
val gamePermissionDialogFragment = (context as AppCompatActivity).supportFragmentManager.findFragmentByTag(
val gamePermissionDialogFragment = (context.getActivity() as? AppCompatActivity)?.supportFragmentManager?.findFragmentByTag(
GamePermissionDialogFragment::class.java.name
) as DialogFragment?
gamePermissionDialogFragment?.dismissAllowingStateLoss()
@ -233,7 +233,6 @@ class DetailViewHolder(
getDownloadBtnText(context, gameEntity, false, true, PluginLocation.only_game)
when {
localText.contains(com.gh.gamecenter.feature.R.string.update.toResString()) -> { // 本地游戏需要更新
localDownloadButton?.goneIf(true)
localDownloadContainer?.goneIf(true)
downloadPb.goneIf(true)
overlayTv?.goneIf(true)
@ -250,7 +249,6 @@ class DetailViewHolder(
localText.contains(com.gh.gamecenter.feature.R.string.launch.toResString()) && downloadText == "更新" -> { // 畅玩游戏需要更新:显示 加速/更新
localDownloadContainer?.goneIf(true)
localDownloadButton?.goneIf(true)
downloadPb.goneIf(true)
overlayTv?.goneIf(true)
@ -263,7 +261,6 @@ class DetailViewHolder(
}
localText.contains(com.gh.gamecenter.feature.R.string.launch.toResString()) -> { // 本地游戏为启动状态:显示 加速/畅玩
localDownloadButton?.goneIf(true)
localDownloadContainer?.goneIf(true)
downloadPb.goneIf(true)
overlayTv?.goneIf(true)
@ -288,20 +285,20 @@ class DetailViewHolder(
val downloadText = getDownloadBtnText(context, gameEntity, false, false, PluginLocation.only_game)
when {
downloadText.contains(com.gh.gamecenter.feature.R.string.launch.toResString()) -> {
localDownloadButton?.goneIf(true)
localDownloadContainer?.goneIf(true)
downloadPb.goneIf(true)
true
}
downloadText == com.gh.gamecenter.feature.R.string.launch.toResString() -> true
downloadText.contains(R.string.update.toResString()) -> true
else -> false
}
}
else -> false
}
it.checkIfShowSpeedUi(showSpeedUi)
it.checkIfShowSpeedUi(showSpeedUi, showDualDownloadButton)
}
}
@ -608,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
@ -37,7 +38,6 @@ import com.gh.gamecenter.gamedetail.detail.viewholder.GameDetailContentRecommend
import com.gh.gamecenter.gamedetail.entity.*
import com.gh.gamecenter.livedata.Event
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.login.user.UserRepository
import com.gh.gamecenter.manager.PackagesManager
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.vspace.VHelper
@ -105,7 +105,7 @@ class GameDetailViewModel(
private var relatedGameList = arrayListOf<GameEntity>()
// 内容卡片相关
private var contentCardList: List<ContentCardEntity>?= null
private var contentCardList: List<ContentCardEntity>? = null
private val contentCardSp by lazy {
HaloApp.getInstance().getSharedPreferences(SP_CONTENT_CARD, Context.MODE_PRIVATE)
}
@ -134,9 +134,9 @@ class GameDetailViewModel(
private var isGameUpdatable = false
private val compositeDisposable = CompositeDisposable()
private var userRelatedInfoReceivedCallback: (() -> Unit)? = null
init {
loadData()
}
@ -191,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()
}
@ -558,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 {
@ -882,7 +886,7 @@ class GameDetailViewModel(
params["source"] = HaloApp.getInstance().application.getString(R.string.app_name)
params["jnfj"] = MetaUtil.getBase64EncodedIMEI()
params["manufacturer"] = Build.MANUFACTURER
params["rom"] = MetaUtil.getRom().name + " " + MetaUtil.getRom().versionName
params["rom"] = MetaUtil.getRom().romName + " " + MetaUtil.getRom().romVersion
params["suggestion_type"] = "游戏求更新"
params["game_id"] = game?.id ?: ""
@ -1117,6 +1121,14 @@ class GameDetailViewModel(
compositeDisposable.clear()
}
fun markContentCardRedDot(contentCardEntity: ContentCardEntity) {
SPUtils.setString(contentCardSp, RED_DOT_PREFIX + (gameId ?: "") + contentCardEntity.link.type, contentCardEntity.redDot.toString())
}
fun shouldShowContentCardRedDot(contentCardEntity: ContentCardEntity): Boolean {
return SPUtils.getString(contentCardSp, RED_DOT_PREFIX + (gameId ?: "") + contentCardEntity.link.type) != contentCardEntity.redDot.toString()
}
class Factory(
private val application: Application,
private val gameId: String?,
@ -1133,6 +1145,7 @@ class GameDetailViewModel(
companion object {
const val SP_CONTENT_CARD = "content_card"
const val TAG = "GameDetailViewModel"
const val RED_DOT_PREFIX = "red_dot_"
//[已领取/已淘号/再领/再淘]的礼包置底显示
fun sortLibaoList(libaoList: ArrayList<LibaoEntity>?) {

View File

@ -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)
@ -635,7 +647,7 @@ class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
DialogHelper.showDialogWithHtmlContent(
requireContext(),
dialog.title,
dialog.content,
dialog.content.replaceLineBreakWithBr(),
dialog.confirmButton.text.toString(),
dialog.closeButtonText,
{
@ -669,7 +681,8 @@ class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
"game_type", gameEntity?.categoryChinese ?: "",
"button_name", "关闭弹窗"
)
}
},
extraConfig = DialogHelper.Config(centerTitle = true)
)
}
@ -802,7 +815,10 @@ class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
downloadStatus = gameEntity?.downloadStatusChinese ?: "",
gameType = gameEntity?.categoryChinese ?: "",
position = position,
tabContent = tabEntity.name
tabContent = tabEntity.name,
linkType = tabEntity.link?.type ?: "",
linkId = tabEntity.link?.link ?: "",
linkText = tabEntity.link?.text ?: ""
)
val entrance = if (mEntrance.contains("论坛详情")) "论坛" else "游戏"
@ -905,7 +921,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 ?: "")
@ -932,7 +948,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

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

View File

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

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

View File

@ -7,7 +7,7 @@ import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.widget.LinearLayout
import androidx.core.view.children
import androidx.core.view.forEach
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.RecyclerView
@ -117,7 +117,7 @@ class GameLibaoAdapter(
binding.horizontalScrollView.goneIf(libaoEntity.materials.isEmpty()) {
if (binding.imagesContainer.tag == libaoEntity.id) {
binding.imagesContainer.children.forEach { view ->
binding.imagesContainer.forEach { view ->
if (view is SimpleDraweeView) {
view.hierarchy.roundingParams = RoundingParams().apply {
setCornersRadius(4F.dip2px().toFloat())
@ -253,7 +253,7 @@ class GameLibaoAdapter(
null,
true,
"游戏详情",
"游戏详情"
"礼包列表页"
) {
adapter.notifyItemChanged(position)
}

View File

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

View File

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

View File

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

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

View File

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

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

View File

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

View File

@ -541,7 +541,7 @@ class RatingEditActivity : ToolBarActivity(), KeyboardHeightObserver {
jsonObject.put("game_version", gameVersion)
jsonObject.put("source", if (mFromAmway) "anliwall" else "game_detail")
jsonObject.put("plugin_version", PackageUtils.getMetaData(this, mInstallPackageName, "gh_version"))
jsonObject.put("rom", MetaUtil.getRom().name + " " + MetaUtil.getRom().versionName)
jsonObject.put("rom", MetaUtil.getRom().romName + " " + MetaUtil.getRom().romVersion)
jsonObject.put("again", again)
val body = jsonObject.toString().toRequestBody("application/json".toMediaTypeOrNull())

View File

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

View File

@ -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,
@ -230,7 +247,12 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable, IB
})
dataList.observe(viewLifecycleOwner) {
adapter.submitList(it)
adapter.submitList(it) {
if (shouldScrollToTop) {
shouldScrollToTop = false
binding.gameList.scrollToPosition(0)
}
}
}
loadStatus.observe(viewLifecycleOwner) { (status, isPullToRefresh) ->
@ -352,7 +374,6 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable, IB
)
}
}
})
subjectDestination.observe(viewLifecycleOwner, EventObserver { subject ->
@ -553,6 +574,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 +592,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.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.gh.common.util.GameUtils
import com.gh.common.util.NewFlatLogUtils
import com.gh.common.util.NewLogUtils
@ -31,10 +33,24 @@ import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.home.PageConfigure
import com.gh.gamecenter.home.custom.GamePositionAndPackageHelper.Companion.putGameWithPosition
import com.gh.gamecenter.home.custom.eventlistener.OnCustomPageEventListener
import com.gh.gamecenter.home.custom.model.*
import com.gh.gamecenter.home.custom.model.CustomCommonContentCollectionItem
import com.gh.gamecenter.home.custom.model.CustomDspPlaceholderItem
import com.gh.gamecenter.home.custom.model.CustomPKItem
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.model.CustomPageItem
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMPONENTS_COLLECTION_STYLE_REFRESH_ICONS_4_2
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMPONENTS_COLLECTION_STYLE_REFRESH_SLIDE_LIST
import com.gh.gamecenter.home.custom.model.CustomPageRepository
import com.gh.gamecenter.home.custom.model.CustomPluginItem
import com.gh.gamecenter.home.custom.model.CustomRecentGamesItem
import com.gh.gamecenter.home.custom.model.CustomSplitCommonContentCollectionItem
import com.gh.gamecenter.home.custom.model.CustomSplitSubjectItem
import com.gh.gamecenter.home.custom.model.CustomSubjectCollectionItem
import com.gh.gamecenter.home.custom.model.CustomSubjectItem
import com.gh.gamecenter.home.custom.model.CustomWeChatMiniGamesCPMSubjectItem
import com.gh.gamecenter.livedata.Event
import com.gh.gamecenter.login.user.UserRepository
import com.gh.gamecenter.login.user.UserViewModel
import com.gh.gamecenter.wrapper.SearchToolbarTabWrapperViewModel
import com.lightgame.utils.Utils
import io.reactivex.android.schedulers.AndroidSchedulers
@ -44,9 +60,7 @@ import io.reactivex.schedulers.Schedulers
import retrofit2.HttpException
import kotlin.collections.set
class CustomPageViewModel(
application: Application
) : AndroidViewModel(application), OnCustomPageEventListener {
class CustomPageViewModel(application: Application) : AndroidViewModel(application), OnCustomPageEventListener {
private val compositeDisposable = CompositeDisposable()
@ -108,8 +122,6 @@ class CustomPageViewModel(
*/
private val cpmSubjectChangedPageMap: ArrayMap<String, Int> = ArrayMap()
var slideDiscoveryGamesPage = -1
private lateinit var _pageTracker: CustomPageTracker
val pageTracker: CustomPageTracker
@ -120,10 +132,12 @@ class CustomPageViewModel(
val pkVoteResultLiveData = MutableLiveData<Event<Pair<String, Boolean>>>()
var shouldScrollToTop: Boolean = false
fun init(
pageConfigure: PageConfigure,
searchToolbarTabWrapperViewModel: SearchToolbarTabWrapperViewModel?,
pageLocation: PageLocation
pageLocation: PageLocation,
) {
this.searchToolbarTabWrapperViewModel = searchToolbarTabWrapperViewModel
_pageConfigure = pageConfigure
@ -397,7 +411,7 @@ class CustomPageViewModel(
if (gameList != null) {// 直接读取缓存数据
notifyWGameCPMABatchChanged(gameList, subjectId, page)
} else {
repository.loadChangeSubjectWGameCPM(page)
repository.loadChangeSubjectWGameCPM(page, subjectEntity.size.limit, subjectEntity.onlyFee)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Response<List<GameEntity>>() {
@ -459,7 +473,7 @@ class CustomPageViewModel(
// 随机产生专题数据(换一换)
private fun getRandomGameList(
oldList: MutableList<GameEntity>?,
sourceList: MutableList<GameEntity>
sourceList: MutableList<GameEntity>,
): MutableList<GameEntity> {
val resultGameList = ArrayList<GameEntity>()
val filterGameList = GameUtils.removeDuplicateData(oldList, sourceList)//排除重复
@ -570,6 +584,11 @@ class CustomPageViewModel(
subjectItem.data
)
if (customPageItemList.isEmpty()) return
if (index == 0) {
shouldScrollToTop = true
}
newData[index] = customPageItemList[0]
newData.addAll(index + 1, customPageItemList.subList(1, customPageItemList.size))
newData.forEachIndexed { pos, customPageItem ->
@ -607,6 +626,10 @@ class CustomPageViewModel(
subjectItem.data
)
if (customPageItemList.isEmpty()) return
if (index == 0) {
shouldScrollToTop = true
}
newData[index] = customPageItemList[0]
newData.addAll(index + 1, customPageItemList.subList(1, customPageItemList.size))
newData.forEachIndexed { pos, customPageItem ->
@ -701,7 +724,7 @@ class CustomPageViewModel(
private fun getPositionAndPackageMap(
customPageItem: CustomPageItem,
gameList: List<GameEntity>?
gameList: List<GameEntity>?,
): HashMap<String, Int> {
val hashMap = hashMapOf<String, Int>()
if (customPageItem.shouldShowDownloadButton) {
@ -720,7 +743,7 @@ class CustomPageViewModel(
item: CustomPageItem,
childPosition: Int,
game: GameEntity,
text: String
text: String,
) {
_gameDetailDestination.value = Event(Pair("", game))
}
@ -746,7 +769,7 @@ class CustomPageViewModel(
override fun navigateSubjectDetailPage(
item: CustomSubjectCollectionItem,
subject: CustomPageData.LinkColumnCollection.CustomSubjectEntity
subject: CustomPageData.LinkColumnCollection.CustomSubjectEntity,
) {
_subjectDestinationWithCustom.value = Event(subject to item.data.subjectType)
}
@ -759,7 +782,7 @@ class CustomPageViewModel(
override fun navigateSubjectCollectionPage(
item: CustomSubjectCollectionItem,
childPosition: Int,
subject: CustomPageData.LinkColumnCollection.CustomSubjectEntity?
subject: CustomPageData.LinkColumnCollection.CustomSubjectEntity?,
) {
_subjectCollectionDestination.value = Event(Triple(item, childPosition, subject))
}
@ -772,7 +795,7 @@ class CustomPageViewModel(
override fun navigateGameListDetailPage(
item: CustomSubjectCollectionItem,
childPosition: Int,
subject: CustomPageData.LinkColumnCollection.CustomSubjectEntity
subject: CustomPageData.LinkColumnCollection.CustomSubjectEntity,
) {
_gameListDetailDestination.value = Event(Triple(item, childPosition, subject))
}
@ -808,7 +831,7 @@ class CustomPageViewModel(
item: CustomPageItem,
link: LinkEntity,
text: String,
exposureEvent: ExposureEvent?
exposureEvent: ExposureEvent?,
) {
_linkDestination.value = Event(Pair(link, exposureEvent))
}
@ -834,7 +857,7 @@ class CustomPageViewModel(
item: CustomPageItem,
gameId: String,
gameName: String?,
childPosition: Int
childPosition: Int,
) {
val exposureEvent = item.exposureEventList.getOrNull(childPosition)
_gameDetailDestinationOnAmway.value = Event(Pair(gameId, exposureEvent))
@ -909,7 +932,7 @@ class CustomPageViewModel(
class SubjectChanged(
val subjectId: String,
val page: Int
val page: Int,
) {
companion object {
private const val HASH = 30
@ -929,4 +952,13 @@ class CustomPageViewModel(
}
class Factory(private val mApplication: Application)
: ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return CustomPageViewModel(mApplication) as T
}
}
}

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
@ -91,7 +90,8 @@ class CustomGamePluginAdapter(
binding.gameRating.textSize =
(if (gameEntity.commentCount > 3) 12 else 10).toFloat()
BindingAdapters.setGameName(binding.gameName, gameEntity, true)
BindingAdapters.setGameTags(binding.labelList, gameEntity, "")
binding.gamePlayCount.visibility = View.GONE
BindingAdapters.setGameTagsWithSellingPoint(binding.labelList, binding.layoutSellingPoints, gameEntity, "")
binding.gameRating.setDrawableStart(
if (gameEntity.commentCount > 3) com.gh.gamecenter.feature.R.drawable.game_horizontal_rating.toDrawable() else null,
null,
@ -119,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)
@ -133,7 +136,8 @@ class CustomGameRefreshVerticalAdapter(
with(ui) {
gameNameTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
serverTypeTv.setTextColor(com.gh.gamecenter.common.R.color.primary_theme.toColor(context))
downloadTv.background = com.gh.gamecenter.common.R.drawable.download_button_normal_style.toDrawable(context)
downloadTv.background =
com.gh.gamecenter.common.R.drawable.download_button_normal_style.toDrawable(context)
gameDesTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
BindingAdapters.setGameName(
@ -142,7 +146,7 @@ class CustomGameRefreshVerticalAdapter(
false
)
BindingAdapters.setGame(iconIv, gameEntity)
BindingAdapters.setGameTags(gameTagContainer, gameEntity, "")
BindingAdapters.setGameTagsWithSellingPoint(gameTagContainer, sellingPointsBinding, gameEntity, "")
GameItemViewHolder.initServerType(gameNameTv, serverTypeTv, gameEntity)
gameDesTv.text = gameEntity.decoratedDes
GameItemViewHolder.initGameSubtitleAndAdLabel(
@ -209,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)
}
}
}

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