Compare commits

...

403 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
c284fcd531 Merge branch 'fix/game_detail_video' into 'release'
fix: 修复游戏详情页视频主动播放时退出页面没有停止播放的问题

See merge request halo/android/assistant-android!2177
2025-05-08 16:37:29 +08:00
101aa11855 fix: 修复游戏详情页视频主动播放时退出页面没有停止播放的问题 2025-05-08 16:26:55 +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
21a4ff5560 Merge branch 'revert-GHZSCY-7496' into 'dev'
Revert "feat: 专题合集新增自定义设置—客户端 https://jira.shanqu.cc/browse/GHZSCY-7496"

See merge request halo/android/assistant-android!2174
2025-05-08 10:40:50 +08:00
034b7f07ee Revert "feat: 专题合集新增自定义设置—客户端 https://jira.shanqu.cc/browse/GHZSCY-7496"
This reverts commit d874b91b
2025-05-08 10:32:37 +08:00
d3c9cb7776 Merge branch 'revert-26e272ed' into 'dev'
Revert "Merge branch 'feat/GHZSCY-7512-pr' into 'dev'"

See merge request halo/android/assistant-android!2173
2025-05-08 09:21:42 +08:00
8593f5cd0d Revert "Merge branch 'feat/GHZSCY-7512-pr' into 'dev'"
This reverts merge request !2172
2025-05-08 09:21:23 +08:00
26e272edd0 Merge branch 'feat/GHZSCY-7512-pr' into 'dev'
feat:分类新增搜索功能—客户端 https://jira.shanqu.cc/browse/GHZSCY-7512

See merge request halo/android/assistant-android!2172
2025-05-07 15:16:09 +08:00
cc0683c3c7 feat:分类新增搜索功能—客户端 https://jira.shanqu.cc/browse/GHZSCY-7512 2025-05-07 15:09:35 +08:00
7d9f36d587 Merge branch 'feat/GHZSCY-7848-pr' into 'dev'
feat:奇游加速器第三期迭代—客户端 https://jira.shanqu.cc/browse/GHZSCY-7864

See merge request halo/android/assistant-android!2168
2025-05-07 10:33:50 +08:00
eac6bc2c7f feat:奇游加速器第三期迭代—客户端 https://jira.shanqu.cc/browse/GHZSCY-7864 2025-05-07 10:33:50 +08:00
1fd0d7f215 Merge branch 'feat/GHZSCY-7889' into 'dev'
feat:神策埋点优化—客户端 https://jira.shanqu.cc/browse/GHZSCY-7889

See merge request halo/android/assistant-android!2169
2025-05-07 09:30:12 +08:00
cd5748fa1c feat:神策埋点优化—客户端 https://jira.shanqu.cc/browse/GHZSCY-7889 2025-05-07 09:30:12 +08:00
3cc4a2b9bc Merge branch 'fix/GHZSCY-7907' into 'dev'
feat:【光环助手】自定义页面-游戏组件显示问题 https://jira.shanqu.cc/browse/GHZSCY-7907

See merge request halo/android/assistant-android!2170
2025-05-07 09:29:46 +08:00
55056b772e feat:【光环助手】自定义页面-游戏组件显示问题 https://jira.shanqu.cc/browse/GHZSCY-7907 2025-05-07 09:29:46 +08:00
a11acbb8c4 Merge branch 'fix/dsp_install_error' into 'dev'
feat: 广告系统-APP启动埋点事件优化 https://jira.shanqu.cc/browse/GHZSCY-7937

See merge request halo/android/assistant-android!2166
2025-04-30 15:24:51 +08:00
24d59aaa85 fix: 广告系统-APP启动埋点事件优化 https://jira.shanqu.cc/browse/GHZSCY-7937 2025-04-30 14:46:03 +08:00
f57a95b82c feat: 广告系统-APP启动埋点事件优化 https://jira.shanqu.cc/browse/GHZSCY-7937 2025-04-30 13:55:09 +08:00
3a78b307d3 Merge branch 'feat/GHZSCY-7820-continued' into 'dev'
feat:新增dsp广告合作模式—客户端 https://jira.shanqu.cc/browse/GHZSCY-7820

See merge request halo/android/assistant-android!2157
2025-04-30 09:59:24 +08:00
c4dccb7aa7 feat:新增dsp广告合作模式—客户端 https://jira.shanqu.cc/browse/GHZSCY-7820 2025-04-30 09:59:24 +08:00
7612c6afd7 Merge branch 'feat/GHZSCY-7927' into 'dev'
feat: GameDetailModuleClick埋点上报优化-客户端 https://jira.shanqu.cc/browse/GHZSCY-7927

See merge request halo/android/assistant-android!2165
2025-04-28 15:27:29 +08:00
2c781bcbf1 feat: GameDetailModuleClick埋点上报优化-客户端 https://jira.shanqu.cc/browse/GHZSCY-7927 2025-04-28 15:27:29 +08:00
7ab09e42ca Merge branch 'feat/GHZSCY-7869' into 'dev'
feat: 游戏详情组件UI调整—客户端 https://jira.shanqu.cc/browse/GHZSCY-7869

See merge request halo/android/assistant-android!2164
2025-04-27 17:24:27 +08:00
292c96586a feat: 游戏详情组件UI调整—客户端 https://jira.shanqu.cc/browse/GHZSCY-7869 2025-04-27 17:24:27 +08:00
7681b63e27 Merge branch 'fix/GHZSCY-7922' into 'dev'
fix:【光环助手】视频播放问题 https://jira.shanqu.cc/browse/GHZSCY-7922

See merge request halo/android/assistant-android!2163
2025-04-27 15:18:28 +08:00
95966be1a1 fix:【光环助手】视频播放问题 https://jira.shanqu.cc/browse/GHZSCY-7922 2025-04-27 15:18:28 +08:00
9eab3361a0 Merge branch 'fix/iothread-livedata' into 'dev'
fix: 在 io 线程设置 livedata value 导致的问题

See merge request halo/android/assistant-android!2162
2025-04-27 14:30:14 +08:00
d0d5ccdc45 fix: 在 io 线程设置 livedata value 导致的问题 2025-04-27 14:29:09 +08:00
c61d0bc9cc Revert "feat: 重新启用在华为 Android 10 上的开屏广告,测试闪退情况"
This reverts commit aed8e50db3.
2025-04-27 11:05:24 +08:00
2da07354e7 Merge branch 'feat/GHZSCY-7496' into 'dev'
feat: 专题合集新增自定义设置—客户端 https://jira.shanqu.cc/browse/GHZSCY-7496

See merge request halo/android/assistant-android!2161
2025-04-27 11:03:31 +08:00
d874b91b7c feat: 专题合集新增自定义设置—客户端 https://jira.shanqu.cc/browse/GHZSCY-7496 2025-04-27 11:03:31 +08:00
62e62d3a93 chore: 版本更新至 5.41.0 2025-04-27 10:03:20 +08:00
2e806d77e1 Merge branch 'fix/GHZSCY-7916' into 'dev'
Resolve GHZSCY-7916 "Fix/"

Closes GHZSCY-7916

See merge request halo/android/assistant-android!2160
2025-04-25 16:33:50 +08:00
30da988ebc fix: 助手更新显示问题 https://jira.shanqu.cc/browse/GHZSCY-7916 2025-04-25 16:27:12 +08:00
fd61e83f8e Revert "Revert "fix: "点"符号设置颜色""
This reverts commit e5161ae350.
2025-04-24 15:44:34 +08:00
0712960a30 Revert "Revert "feat: 【光环助手】实名认证特殊字符输入优化 https://jira.shanqu.cc/browse/GHZSCY-7702""
This reverts commit b1b231a309.
2025-04-24 15:44:34 +08:00
5dfdaa0353 Merge remote-tracking branch 'origin/release' into dev 2025-04-24 15:16:28 +08:00
fa4dce66a6 Merge branch 'feat/GHZSCY-7704' into 'dev'
【畅玩】实名认证特殊符号以及字符限制优化

See merge request halo/android/assistant-android!2159
2025-04-22 10:09:24 +08:00
8a017df220 Revert .gitlab-ci.yml 2025-04-22 10:08:42 +08:00
3a0be0a6ee 【单机&畅玩】实名认证特殊符号以及字符限制优化 https://jira.shanqu.cc/browse/GHZSCY-7704 2025-04-21 17:47:07 +08:00
d50eeddde2 Merge branch 'fix/GHZSCY-7860' into 'release'
fix:【光环助手】游戏专题显示问题 https://jira.shanqu.cc/browse/GHZSCY-7860

See merge request halo/android/assistant-android!2158
2025-04-15 11:23:14 +08:00
89aa6e7abc fix:【光环助手】游戏专题显示问题 https://jira.shanqu.cc/browse/GHZSCY-7860 2025-04-15 11:23:14 +08:00
ab795ab538 Merge branch 'feat/more-xapk-error-log' into 'release'
feat: xapk 解压失败增加异常摘要字段

See merge request halo/android/assistant-android!2156
2025-04-10 10:08:07 +08:00
ba0854ad9a feat: xapk 解压失败增加异常摘要字段 2025-04-10 10:00:57 +08:00
fc2051387a Merge branch 'cherry-pick' into 'release'
合并部分需求到 release

See merge request halo/android/assistant-android!2155
2025-04-10 09:43:58 +08:00
ab2973c7be feat: 游戏预约相关优化—客户端 https://jira.shanqu.cc/browse/GHZSCY-7828 2025-04-10 09:35:01 +08:00
2a08ea681d fix: 修复游戏详情视频/图集Tab闪烁的问题 2025-04-10 09:35:00 +08:00
e23d510fb0 fix: 游戏详情-视频/图集tab 神策埋点补充—客户端 https://jira.shanqu.cc/browse/GHZSCY-7781 2025-04-10 09:35:00 +08:00
3c6ee0c782 chore: 版本更新至 5.40.2 2025-04-09 15:43:49 +08:00
07840822a8 Merge branch 'fix/GHZSCY-7806-pick' into 'release'
fix:【光环助手】通知栏目 滚动问题 https://jira.shanqu.cc/browse/GHZSCY-7806

See merge request halo/android/assistant-android!2154
2025-04-09 13:45:37 +08:00
12e547d333 fix:【光环助手】通知栏目 滚动问题 https://jira.shanqu.cc/browse/GHZSCY-7806 2025-04-09 13:42:14 +08:00
8bb3736ad1 Merge branch 'fix/redirect-crash' into 'release'
fix: 捕抓重定向后的下载地址 url 异常造成的闪退

See merge request halo/android/assistant-android!2153
2025-04-09 11:28:39 +08:00
aa9ba5163f fix: 捕抓重定向后的下载地址 url 异常造成的闪退 2025-04-09 11:27:54 +08:00
23033edc42 Merge branch 'fix/sentry-user-id' into 'release'
feat: sentry 日志使用 base64 转码后的 androidId 替换原始随机 id

See merge request halo/android/assistant-android!2152
2025-04-09 11:18:05 +08:00
308e134aff feat: sentry 日志使用 base64 转码后的 androidId 替换原始随机 id 2025-04-09 11:17:30 +08:00
ac78ea0498 Merge branch 'feat/upload-accelerator-set-token-log' into 'release'
feat:将奇游加速器 setToken 错误日志从sentry转移到火山云

See merge request halo/android/assistant-android!2151
2025-04-09 11:13:31 +08:00
38bab9cf4f feat:将奇游加速器 setToken 错误日志从sentry转移到火山云 2025-04-09 11:13:30 +08:00
d3351ec0b6 feat: 读取汉化接口配置 https://jira.shanqu.cc/browse/GHZSCY-7642 2025-04-08 16:34:19 +08:00
e3f883b784 Merge branch 'feat/GHZSCY-7828' into 'dev'
feat: 游戏预约相关优化—客户端 https://jira.shanqu.cc/browse/GHZSCY-7828

See merge request halo/android/assistant-android!2150
2025-04-08 15:31:12 +08:00
fbb81d7e45 feat: 游戏预约相关优化—客户端 https://jira.shanqu.cc/browse/GHZSCY-7828 2025-04-08 15:28:21 +08:00
750ec04c9d Merge branch 'fix/game_detail_cover_tab' into 'dev'
fix: 修复游戏详情视频/图集Tab闪烁的问题

See merge request halo/android/assistant-android!2149
2025-04-07 18:00:37 +08:00
e23a3d3938 fix: 修复游戏详情视频/图集Tab闪烁的问题 2025-04-07 17:57:14 +08:00
e769c0e5f5 fix: 修复初始化汉化进程导致unity游戏卡屏的问题 https://jira.shanqu.cc/browse/GHZSCY-7642 2025-04-07 10:08:35 +08:00
d75020cdb5 Merge branch 'feat/GHZSCY-7781' into 'dev'
fix: 游戏详情-视频/图集tab 神策埋点补充—客户端 https://jira.shanqu.cc/browse/GHZSCY-7781

See merge request halo/android/assistant-android!2148
2025-04-07 09:54:40 +08:00
c56c71d1f1 fix: 游戏详情-视频/图集tab 神策埋点补充—客户端 https://jira.shanqu.cc/browse/GHZSCY-7781 2025-04-07 09:54:40 +08:00
ccfa50d748 Merge branch 'feat/GHZSCY-7515' into 'dev'
feat:开服订阅通知频率优化—客户端 https://jira.shanqu.cc/browse/GHZSCY-7515

See merge request halo/android/assistant-android!2145
2025-04-03 11:33:41 +08:00
7176a5a4c4 feat:开服订阅通知频率优化—客户端 https://jira.shanqu.cc/browse/GHZSCY-7515 2025-04-03 11:33:41 +08:00
d007092193 Merge branch 'feat/GHZSCY-7702' into 'dev'
feat: 实名认证字符优化 (光环助手和畅玩)

See merge request halo/android/assistant-android!2147
2025-04-03 10:45:54 +08:00
2552743ff2 Merge branch 'feat/GHZSCY-7702' into 'dev'
feat: 实名认证字符优化 (光环助手和畅玩)

See merge request halo/android/assistant-android!2146
2025-04-03 10:42:01 +08:00
4faf15ffe7 feat: 实名认证字符优化 (光环助手和畅玩) 2025-04-03 10:42:01 +08:00
33d6ed75ad ci: revert 2025-04-03 10:39:55 +08:00
2eb4878b12 Merge branch 'fix/GHZSCY-7806' into 'dev'
fix:【光环助手】通知栏目 滚动问题 https://jira.shanqu.cc/browse/GHZSCY-7806

See merge request halo/android/assistant-android!2144
2025-04-03 10:07:41 +08:00
2bea3c72d7 fix:【光环助手】通知栏目 滚动问题 https://jira.shanqu.cc/browse/GHZSCY-7806 2025-04-03 09:59:57 +08:00
fa3db9d521 fix: 实名认证页面首页,右上角多显示了“·”的按钮 https://jira.shanqu.cc/browse/GHZSCY-7702,https://jira.shanqu.cc/browse/GHZSCY-7791 2025-04-02 16:36:30 +08:00
8084cda37d Merge branch 'hotfix/v5.40.1-1151/update_upload_sdk' into 'release'
更新火山云上传 SDK

See merge request halo/android/assistant-android!2143
2025-04-02 11:53:34 +08:00
e624f34de1 fix: 禁用火山云上传分片功能 2025-04-02 10:29:56 +08:00
b832c0c14f Merge branch 'hotfix/v5.40.1-1151/GHZSCY-7796' into 'release'
云存档引导游戏下载问题 https://jira.shanqu.cc/browse/GHZSCY-7796

See merge request halo/android/assistant-android!2142
2025-04-01 17:38:48 +08:00
6610c43937 chore: 更新火山云上传 SDK 2025-04-01 17:38:19 +08:00
ec29d94fb7 fix: 云存档引导游戏下载问题 https://jira.shanqu.cc/browse/GHZSCY-7796 2025-04-01 14:44:46 +08:00
efee9409bf Merge branch 'feat/add-room-ktx' into 'dev'
为了Room能和Flow一起使用,需要引入room-ktx

See merge request halo/android/assistant-android!2141
2025-04-01 09:31:26 +08:00
38a7eaf780 为了Room能和Flow一起使用,需要引入room-ktx 2025-04-01 09:28:05 +08:00
a1b4233fdb Merge branch 'feat/GHZSCY-7784' into 'dev'
feat:处理以 LiveData 形式直接监听数据库变更的代码 https://jira.shanqu.cc/browse/GHZSCY-7784

See merge request halo/android/assistant-android!2140
2025-03-31 17:20:33 +08:00
60c24e7457 feat:处理以 LiveData 形式直接监听数据库变更的代码 https://jira.shanqu.cc/browse/GHZSCY-7784 2025-03-31 17:20:33 +08:00
b6ec74d789 chore: 版本更新只 5.40.1 2025-03-31 14:31:26 +08:00
2d7224cf16 Merge branch 'fix/crashes' into 'release'
修复Sentry闪退

See merge request halo/android/assistant-android!2139
2025-03-31 11:43:45 +08:00
3f45344b54 Merge branch 'feat/update-qy-sdk' into 'release'
feat:更新奇游加速器sdk

See merge request halo/android/assistant-android!2138
2025-03-31 11:39:28 +08:00
1c3dbce08d feat:更新奇游加速器sdk 2025-03-31 11:39:28 +08:00
7844800b0e fix: 禁用RecyclerView动画效果避免异常闪退 https://sentry.shanqu.cc/organizations/lightgame/issues/441824/?project=22 2025-03-31 11:38:05 +08:00
ebcfd9c85d fix: 修复游戏详情页偶发空指针闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/443716/?project=22 2025-03-31 11:37:07 +08:00
3825315f65 Merge branch 'hotfix/v5.40.0-1150/router-crash' into 'release'
fix: 尝试修复进程启动时路由异常导致的闪退问题

See merge request halo/android/assistant-android!2137
2025-03-28 17:53:50 +08:00
3ffe4f9bc6 fix: 尝试修复路由异步初始化导致的闪退问题 2025-03-28 17:05:59 +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
729685e764 feat: 实名认证字符优化 (光环助手和畅玩)
https://jira.shanqu.cc/browse/GHZSCY-7702https://jira.shanqu.cc/browse/GHZSCY-7704
2025-03-25 17:54:35 +08:00
b2fc01ae48 Merge branch 'feat/va-relative' into 'release'
Revert 畅玩实名认证特殊字符

See merge request halo/android/assistant-android!2135
2025-03-25 17:29:42 +08:00
06f932af14 Revert 畅玩实名认证特殊字符 2025-03-25 17:29:13 +08:00
c2fe3bb64a Merge branch 'hotfix/GHZSCY-7702' into 'release'
Revert "fix: "点"符号设置颜色"

See merge request halo/android/assistant-android!2134
2025-03-25 17:24:30 +08:00
b1b231a309 Revert "feat: 【光环助手】实名认证特殊字符输入优化 https://jira.shanqu.cc/browse/GHZSCY-7702"
This reverts commit cc05dabb93.
2025-03-25 17:23:29 +08:00
e5161ae350 Revert "fix: "点"符号设置颜色"
This reverts commit 2ba39188f7.
2025-03-25 17:22:58 +08:00
655173482a feat: 畅玩汉化功能 2025-03-25 14:45:55 +08:00
4dfa7733ad Merge branch 'feat/va-relative' into 'dev'
feat: 5.40.0 va相关的更新

See merge request halo/android/assistant-android!2133
2025-03-25 14:32:58 +08:00
3906d9b84b feat: 5.40.0 va相关的更新 2025-03-25 14:31:39 +08:00
775e6cc689 Merge branch 'feat/remove-duplicate-log' into 'dev'
删除多余的日志上报

See merge request halo/android/assistant-android!2132
2025-03-25 14:25:39 +08:00
019b9f3f82 删除多余的日志上报 2025-03-25 14:24:05 +08:00
49f752dab5 Merge branch 'fix/jg_push_source_entrance' into 'dev'
fix: 修复极光推送跳转没有携带来源信息的问题

See merge request halo/android/assistant-android!2131
2025-03-25 10:45:05 +08:00
0cbda119d4 fix: 修复极光推送跳转没有携带来源信息的问题 2025-03-25 10:43:59 +08:00
aed8e50db3 feat: 重新启用在华为 Android 10 上的开屏广告,测试闪退情况 2025-03-25 10:17:57 +08:00
da5bb9eaf3 Merge branch 'feat/GHZSCY-7525' into 'dev'
feat: 头条推广包转化归因优化及SDK更新 https://jira.shanqu.cc/browse/GHZSCY-7525.cc/browse/GHZSCY-7525

See merge request halo/android/assistant-android!2130
2025-03-25 09:57:44 +08:00
d7593de98b feat: 头条推广包转化归因优化及SDK更新 https://jira.shanqu.cc/browse/GHZSCY-7525.cc/browse/GHZSCY-7525 2025-03-25 09:57:08 +08:00
69d2d699fe Merge branch 'feat/GHZSCY-7712' into 'dev'
feat: 后台功能面板组件交互优化—客户端 https://jira.shanqu.cc/browse/GHZSCY-7712

See merge request halo/android/assistant-android!2129
2025-03-24 14:31:32 +08:00
a438ef16c0 feat: 后台功能面板组件交互优化—客户端 https://jira.shanqu.cc/browse/GHZSCY-7712 2025-03-24 14:31:32 +08:00
d5a2cecd29 Merge branch 'fix/GHZSCY-7737' into 'dev'
fix:【光环助手】游戏详情下载触发跳转异常 https://jira.shanqu.cc/browse/GHZSCY-7737

See merge request halo/android/assistant-android!2128
2025-03-24 11:30:23 +08:00
65a4e675fa fix:【光环助手】游戏详情下载触发跳转异常 https://jira.shanqu.cc/browse/GHZSCY-7737 2025-03-24 11:30:23 +08:00
ec3e6d5f7b Merge branch 'fix/GHZSCY-7691' into 'dev'
fix: 2025/3/14 - 埋点补充 https://jira.shanqu.cc/browse/GHZSCY-7691

See merge request halo/android/assistant-android!2116
2025-03-24 10:03:53 +08:00
07b0afce03 fix: 2025/3/14 - 埋点补充 https://jira.shanqu.cc/browse/GHZSCY-7691 2025-03-24 10:03:53 +08:00
852a6d32d6 Merge branch 'fix/GHZSCY-7736' into 'dev'
fix: 间距显示问题-客户端 https://jira.shanqu.cc/browse/GHZSCY-7736

See merge request halo/android/assistant-android!2127
2025-03-24 09:44:19 +08:00
55022d8b1c fix: 间距显示问题-客户端 https://jira.shanqu.cc/browse/GHZSCY-7736 2025-03-24 09:42:26 +08:00
eb7d407e92 Merge branch 'fix/GHZSCY-7735' into 'dev'
fix: 版本求更新显示优化—客户端 https://jira.shanqu.cc/browse/GHZSCY-7735

See merge request halo/android/assistant-android!2126
2025-03-21 18:02:02 +08:00
733cd1e198 fix: 版本求更新显示优化—客户端 https://jira.shanqu.cc/browse/GHZSCY-7735 2025-03-21 17:57:02 +08:00
1d767018f6 Merge branch 'fix/va/unity-crash' into 'dev'
fix: 部分unity游戏启动闪退

See merge request halo/android/assistant-android!2125
2025-03-21 17:36:55 +08:00
0a3616a1c0 fix: 部分unity游戏启动闪退 2025-03-21 17:36:09 +08:00
05108ab478 Merge branch 'fix/crash-in-splash' into 'dev'
fix:java.lang.RuntimeException: Unable to start activity...

See merge request halo/android/assistant-android!2124
2025-03-21 11:23:33 +08:00
508c90cc63 fix:java.lang.RuntimeException: Unable to start activity... 2025-03-21 11:23:33 +08:00
6242f79f80 Merge branch 'fix/GHZSCY-7728' into 'dev'
fix: 部分位置跳转到游戏开服表无【新增】功能 https://jira.shanqu.cc/browse/GHZSCY-7728

See merge request halo/android/assistant-android!2123
2025-03-20 16:29:36 +08:00
71dd964440 fix: 部分位置跳转到游戏开服表无【新增】功能 https://jira.shanqu.cc/browse/GHZSCY-7728 2025-03-20 16:25:22 +08:00
63fc720c40 Merge remote-tracking branch 'origin/release' into dev 2025-03-20 15:42:57 +08:00
5da02c4b64 Merge branch 'hotfix/v5.39.5-1195/rank_text_width_issue' into 'release'
fix: 修复搜索页推荐榜单文字宽度问题

See merge request halo/android/assistant-android!2122
2025-03-20 15:16:40 +08:00
f94b28a085 fix: 修复搜索页推荐榜单文字宽度问题 2025-03-20 15:15:24 +08:00
e8a199b5c0 Merge branch 'feat/GHZSCY-7702' into 'dev'
fix: "点"符号设置颜色

See merge request halo/android/assistant-android!2121
2025-03-20 11:06:51 +08:00
2ba39188f7 fix: "点"符号设置颜色 2025-03-20 11:06:37 +08:00
963074d5d8 Merge branch 'feat/GHZSCY-7704' into 'dev'
fix: “点”符号大小和颜色设置

See merge request halo/android/assistant-android!2120
2025-03-20 10:51:39 +08:00
cc629819d9 fix: “点”符号大小和颜色设置 2025-03-20 10:51:13 +08:00
d82505ab5d Merge branch 'feat/GHZSCY-7704' into 'dev'
feat: [畅玩]实名认证特殊符号以及字符限制优化 https://jira.shanqu.cc/browse/GHZSCY-7704

See merge request halo/android/assistant-android!2119
2025-03-20 09:46:54 +08:00
a917214b36 feat: [畅玩]实名认证特殊符号以及字符限制优化 https://jira.shanqu.cc/browse/GHZSCY-7704 2025-03-20 09:46:34 +08:00
bdf9094799 Merge branch 'feat/common-chips' into 'dev'
feat:添加通用组件:选项标签Chips

See merge request halo/android/assistant-android!2118
2025-03-20 09:45:39 +08:00
df0d0604b9 feat:添加通用组件:选项标签Chips 2025-03-20 09:39:51 +08:00
f100d906ab Merge branch 'feat/GHZSCY-7702' into 'dev'
feat: 【光环助手】实名认证特殊字符输入优化 https://jira.shanqu.cc/browse/GHZSCY-7702

See merge request halo/android/assistant-android!2117
2025-03-19 17:51:51 +08:00
cc05dabb93 feat: 【光环助手】实名认证特殊字符输入优化 https://jira.shanqu.cc/browse/GHZSCY-7702 2025-03-19 17:45:52 +08:00
bd39ac2eaf Merge branch 'fix/drop_font_color_in_dark_mode' into 'dev'
fix: 修复部分富文本中的颜色在深色模式下没有去除的问题

See merge request halo/android/assistant-android!2115
2025-03-19 17:10:52 +08:00
31903d21c0 fix: 修复部分富文本中的颜色在深色模式下没有去除的问题 2025-03-19 17:03:45 +08:00
266fcc4c38 Merge branch 'feat/GHZSCY-7642' into 'dev'
feat: 优化汉化UI

See merge request halo/android/assistant-android!2114
2025-03-19 11:31:34 +08:00
075ed04191 feat: 优化汉化UI 2025-03-19 11:30:55 +08:00
c804c2f7fd Merge branch 'feat/GHZSCY-7642' into 'dev'
feat: 畅玩汉化翻译功能

See merge request halo/android/assistant-android!2113
2025-03-18 11:39:09 +08:00
882f0a7b3e feat: 畅玩汉化翻译功能 2025-03-18 11:37:18 +08:00
3cf86a500b Merge branch 'feat/GHZSCY-7568' into 'dev'
feat: 游戏详情改版补充优化1—客户端(1) https://jira.shanqu.cc/browse/GHZSCY-7568

See merge request halo/android/assistant-android!2112
2025-03-18 11:14:49 +08:00
3cd70b5b8f feat: 游戏详情改版补充优化1—客户端(1) https://jira.shanqu.cc/browse/GHZSCY-7568 2025-03-18 11:14:49 +08:00
60f35a89b4 Merge branch 'feat/GHZSCY-6982-2' into 'dev'
feat:【光环助手】搜索业务:神策埋点相关搜索结果点击事件新增序号属性position https://jira.shanqu.cc/browse/GHZSCY-6982

See merge request halo/android/assistant-android!2111
2025-03-17 15:43:10 +08:00
1503c23246 feat:【光环助手】搜索业务:神策埋点相关搜索结果点击事件新增序号属性position https://jira.shanqu.cc/browse/GHZSCY-6982 2025-03-17 15:38:01 +08:00
ea9e91618f Merge branch 'fix/GHZSCY-7685' into 'dev'
fix:游戏预约功能(第六期)—0313运营测试-客户端...

See merge request halo/android/assistant-android!2110
2025-03-17 11:12:07 +08:00
d52fcc2185 fix:游戏预约功能(第六期)—0313运营测试-客户端... 2025-03-17 11:12:07 +08:00
db047c36d7 Merge remote-tracking branch 'origin/release' into dev
# Conflicts:
#	dependencies.gradle
2025-03-14 15:22:00 +08:00
1e58bec4a0 Merge branch 'feat/GHZSCY-7668' into 'dev'
feat: 光环助手测试包增加相关快捷功能 https://jira.shanqu.cc/browse/GHZSCY-7668

See merge request halo/android/assistant-android!2109
2025-03-14 15:19:22 +08:00
87ff1a64b3 Merge branch 'fix/improper-router' into 'release'
fix: 修复光环未正常启动也能使用路由跳转至指定页面的问题

See merge request halo/android/assistant-android!2108
2025-03-14 15:16:21 +08:00
b3678d7a54 fix: 修复光环未正常启动也能使用路由跳转至指定页面的问题 2025-03-14 14:28:53 +08:00
c7fa04792b chore: 版本更新至 5.39.5 2025-03-14 10:14:08 +08:00
76ea531419 Merge branch 'fix/GHZSCY-7687' into 'release'
fix:【光环助手】搜索内容标签点击跳转异常 https://jira.shanqu.cc/browse/GHZSCY-7687

See merge request halo/android/assistant-android!2107
2025-03-13 18:20:31 +08:00
d821da0b2c fix:【光环助手】搜索内容标签点击跳转异常 https://jira.shanqu.cc/browse/GHZSCY-7687 2025-03-13 18:20:31 +08:00
d9b137504a Merge branch 'fix/GHZSCY-7661' into 'release'
fix:【光环助手】游戏详情自定义tab关联自定义页面的视频播放问题 https://jira.shanqu.cc/browse/GHZSCY-7661

See merge request halo/android/assistant-android!2105
2025-03-13 16:36:39 +08:00
93369f5676 fix:【光环助手】游戏详情自定义tab关联自定义页面的视频播放问题 https://jira.shanqu.cc/browse/GHZSCY-7661 2025-03-13 16:36:39 +08:00
b109c3bf6f Merge branch 'fix/GHZSCY-7681' into 'dev'
fix: 视频/图片上传问题 https://jira.shanqu.cc/browse/GHZSCY-7681

See merge request halo/android/assistant-android!2106
2025-03-13 16:03:36 +08:00
bb80560a49 fix: 视频/图片上传问题 https://jira.shanqu.cc/browse/GHZSCY-7681 2025-03-13 16:02:48 +08:00
c43a9e3b6e feat: 光环助手测试包增加相关快捷功能 https://jira.shanqu.cc/browse/GHZSCY-7668 2025-03-13 14:53:03 +08:00
34d255d258 Merge branch 'feat/GHZSCY-7676' into 'release'
fix: 新游开测相关功能优化(第四期)—0313运营测试-客户端 https://jira.shanqu.cc/browse/GHZSCY-7676

See merge request halo/android/assistant-android!2104
2025-03-13 13:43:15 +08:00
3ca17cea07 fix: 新游开测相关功能优化(第四期)—0313运营测试-客户端 https://jira.shanqu.cc/browse/GHZSCY-7676 2025-03-13 13:40:10 +08:00
d3e23bfbb5 Merge branch 'feat/GHZSCY-6834' into 'dev'
feat:游戏预约功能(第六期)—客户端 https://jira.shanqu.cc/browse/GHZSCY-6834

See merge request halo/android/assistant-android!2102
2025-03-11 16:53:11 +08:00
273a9f010d feat:游戏预约功能(第六期)—客户端 https://jira.shanqu.cc/browse/GHZSCY-6834 2025-03-11 16:53:11 +08:00
e8cbaf89ff Merge branch 'feat/GHZSCY-6817' into 'dev'
feat:游戏专题新增“竖式图文列表”样式—客户端 https://jira.shanqu.cc/browse/GHZSCY-6817

See merge request halo/android/assistant-android!2101
2025-03-11 14:45:23 +08:00
0e3586a556 Merge branch 'feat/GHZSCY-7048' into 'dev'
feat: 推广包快手API新增次留上报 https://jira.shanqu.cc/browse/GHZSCY-7048

See merge request halo/android/assistant-android!2099
2025-03-11 14:38:02 +08:00
d546651c06 feat:游戏专题新增“竖式图文列表”样式—客户端 https://jira.shanqu.cc/browse/GHZSCY-6817 2025-03-11 14:24:46 +08:00
815f567f44 Merge branch 'feat/GHZSCY-6812' into 'dev'
feat:https://jira.shanqu.cc/browse/GHZSCY-6812 发表内容输入规则优化—客户端

See merge request halo/android/assistant-android!2100
2025-03-11 14:20:41 +08:00
ba3e9357a1 feat:https://jira.shanqu.cc/browse/GHZSCY-6812 发表内容输入规则优化—客户端 2025-03-11 14:20:41 +08:00
312448daae Merge remote-tracking branch 'origin/release' into dev
# Conflicts:
#	dependencies.gradle
2025-03-11 10:53:15 +08:00
a00a1ced7e Merge branch 'fix/message_link_skip' into 'release'
fix: 修复消息中心跳转问题

See merge request halo/android/assistant-android!2098
2025-03-11 10:42:18 +08:00
1587ca8e6a fix: 修复消息中心跳转问题 2025-03-11 10:35:39 +08:00
86b4761a1c Merge branch 'fix/game_detail_dark_mode' into 'release'
fix: 修复游戏详情页闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/379887/events/0f296b9965884a4f944867479ad7d6c7/?project=22

See merge request halo/android/assistant-android!2097
2025-03-10 10:04:15 +08:00
0446d87b24 fix: 修复游戏详情切换深色模式出现的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/440456/events/3cfa315cbb734e9688984dddf75fefbd/?project=22 2025-03-10 09:48:04 +08:00
6338fb4dd0 Merge branch 'fix/game_detail_video' into 'release'
fix: 修复游戏详情部分16:9视频存在黑边的问题

See merge request halo/android/assistant-android!2096
2025-03-07 11:54:32 +08:00
6056b7ce1f fix: 修复游戏详情部分16:9视频存在黑边的问题 2025-03-07 11:50:08 +08:00
d51d3c16fe Merge branch 'fix/GHZSCY-7609' into 'release'
fix:【光环助手】UI显示问题 https://jira.shanqu.cc/browse/GHZSCY-7609

See merge request halo/android/assistant-android!2095
2025-03-07 11:34:56 +08:00
d3858ba38f fix:【光环助手】UI显示问题 https://jira.shanqu.cc/browse/GHZSCY-7609 2025-03-07 11:31:21 +08:00
3cbee34b8a chore: 版本更新至 5.39.4 2025-03-07 09:31:21 +08:00
ec88f52603 Merge branch 'fix/GHZSCY-7625' into 'release'
fix: 2025/3/6 - 验收问题 - 客户端 https://jira.shanqu.cc/browse/GHZSCY-7625

See merge request halo/android/assistant-android!2094
2025-03-06 17:46:23 +08:00
b669e216c4 fix: 2025/3/6 - 验收问题 - 客户端 https://jira.shanqu.cc/browse/GHZSCY-7625 2025-03-06 17:46:06 +08:00
8a6d476636 Merge branch 'feat/GHZSCY-7541' into 'dev'
feat:客户端穿山甲广告SDK升级新版本—客户端 https://jira.shanqu.cc/browse/GHZSCY-7541

See merge request halo/android/assistant-android!2093
2025-03-06 14:33:05 +08:00
17b34c9e6f feat:客户端穿山甲广告SDK升级新版本—客户端 https://jira.shanqu.cc/browse/GHZSCY-7541 2025-03-06 14:31:16 +08:00
795b640870 Merge branch 'feat/GHZSCY-7623' into 'release'
feat: 神策埋点事件优化 https://jira.shanqu.cc/browse/GHZSCY-7622

See merge request halo/android/assistant-android!2092
2025-03-06 14:10:31 +08:00
099d026e73 feat: 神策埋点事件优化 https://jira.shanqu.cc/browse/GHZSCY-7622 2025-03-06 14:09:22 +08:00
162381527c Merge branch 'fix/fix-set-acct-token-failed' into 'release'
https://sentry.shanqu.cc/organizations/lightgame/issues/440135 此bug为奇游sdk setToken失败,errro_message为The SDK is not initialized, please call init in Application to initialize the SDK

See merge request halo/android/assistant-android!2091
2025-03-06 11:26:00 +08:00
dedbe1d18c https://sentry.shanqu.cc/organizations/lightgame/issues/440135 此bug为奇游sdk setToken失败,errro_message为The SDK is not initialized, please call init in Application to initialize the SDK 2025-03-06 11:26:00 +08:00
b775c2f0a9 Merge branch 'fix/game_detail_dark_mode_crash' into 'release'
fix: 修复游戏详情切换深色模式出现的闪退问题...

See merge request halo/android/assistant-android!2090
2025-03-06 09:38:36 +08:00
7e079bc8fa fix: 修复游戏详情切换深色模式出现的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/440241/?project=22&referrer=issue-stream&statsPeriod=14d 2025-03-06 09:33:48 +08:00
b719763f2d Merge branch 'fix/GHZSCY-7613' into 'release'
Revert "ci"

See merge request halo/android/assistant-android!2089
2025-03-05 17:23:50 +08:00
fdc04bf8cb fix:【光环助手】轮播banner背景显示问题 https://jira.shanqu.cc/browse/GHZSCY-7613 2025-03-05 17:23:50 +08:00
27811b027f chore: 版本更新至 5.39.3 2025-03-05 15:07:47 +08:00
4e8b8435b8 Merge branch 'hotfix/v5.39.2-1132/remove_useless_sentry_event' into 'release'
build: 移除无用的 sentry 事件

See merge request halo/android/assistant-android!2088
2025-03-05 15:04:58 +08:00
a7efd9528e build: 移除无用的 sentry 事件 2025-03-05 15:04:21 +08:00
982e3a9739 Merge branch 'fix/empty_stack_crash' into 'release'
fix: 修复更新页面BusinessId出现的闪退问题...

See merge request halo/android/assistant-android!2087
2025-03-05 14:50:21 +08:00
a942cdbe51 fix: 修复更新页面BusinessId出现的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/440154/?project=22 2025-03-05 14:43:29 +08:00
f6f0a54cb4 Merge branch 'fix/game_detail_crash' into 'release'
fix: 修复游戏详情页闪退问题...

See merge request halo/android/assistant-android!2086
2025-03-05 14:01:40 +08:00
e26856220a fix: 修复游戏详情页闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/379887/events/0f296b9965884a4f944867479ad7d6c7/?project=22 2025-03-05 13:49:38 +08:00
0f36d7ccfa Merge branch 'fix/GHZSCY-7608' into 'release'
fix:【光环助手】奇游加速器第二期迭代-03/04测试-客户端 https://jira.shanqu.cc/browse/GHZSCY-7608

See merge request halo/android/assistant-android!2084
2025-03-05 11:30:06 +08:00
d0c6a499f8 Merge branch 'fix/GHZSCY-7612' into 'release'
fix:【光环助手】功能标签引导的出现时机问题 https://jira.shanqu.cc/browse/GHZSCY-7612

See merge request halo/android/assistant-android!2085
2025-03-05 11:25:25 +08:00
c56963fab3 fix:【光环助手】功能标签引导的出现时机问题 https://jira.shanqu.cc/browse/GHZSCY-7612 2025-03-05 11:19:43 +08:00
0faa0b490a fix:【光环助手】奇游加速器第二期迭代-03/04测试-客户端 https://jira.shanqu.cc/browse/GHZSCY-7608 2025-03-05 11:19:27 +08:00
51cc1e3104 Merge branch 'fix/game_detail_web' into 'release'
fix: 修复游戏详情网页不能左右滑动切换Tab的问题

See merge request halo/android/assistant-android!2083
2025-03-05 10:50:11 +08:00
ae0ef717d5 fix: 修复游戏详情网页不能左右滑动切换Tab的问题 2025-03-05 10:40:21 +08:00
02ed9c247a Merge branch 'fix/game_detail_divider' into 'release'
fix: 修复光环助手的游戏详情页分割线显示问题

See merge request halo/android/assistant-android!2082
2025-03-04 17:03:23 +08:00
a747917370 fix: 修复光环助手的游戏详情页分割线显示问题 2025-03-04 17:01:02 +08:00
69241c489b chore: 版本更新至 5.40.0 2025-03-04 16:23:00 +08:00
66ffb5cedc chore: 版本更新值 5.39.2 2025-03-04 16:13:30 +08:00
97b3efc968 Merge branch 'fix/wrong_global_screen_width' into 'dev'
fix: 修复页面重建时全局屏幕宽度的值为 0 的问题

See merge request halo/android/assistant-android!2081
2025-03-04 15:57:35 +08:00
288f7370aa fix: 修复页面重建时全局屏幕宽度的值为 0 的问题 2025-03-04 15:56:46 +08:00
f9db7068f2 Merge branch 'fix/game_detail' into 'dev'
fix: 游戏详情Dialog入参改为使用Arguments传递

See merge request halo/android/assistant-android!2080
2025-03-04 15:36:48 +08:00
b4390f2811 fix: 游戏详情Dialog入参改为使用Arguments传递 2025-03-04 15:24:56 +08:00
dbc2be5bc6 Merge branch 'feat/GHZSCY-7568' into 'dev'
游戏详情页补充优化

See merge request halo/android/assistant-android!2079
2025-03-04 11:55:02 +08:00
dec2c35ff4 游戏详情页补充优化 2025-03-04 11:55:02 +08:00
77514aa2a9 Merge branch 'feat/GHZSCY-7446' into 'dev'
feat:【光环助手】一键登录相关UI优化-UI https://jira.shanqu.cc/browse/GHZSCY-7445

See merge request halo/android/assistant-android!2078
2025-03-04 11:07:51 +08:00
40cdda7bae feat:【光环助手】一键登录相关UI优化-UI https://jira.shanqu.cc/browse/GHZSCY-7445 2025-03-04 11:07:51 +08:00
4917b1d4ff Merge branch 'fix/GHZSCY-7589' into 'dev'
fix:【光环助手】游戏单广场遮罩错位问题 https://jira.shanqu.cc/browse/GHZSCY-7589

See merge request halo/android/assistant-android!2077
2025-03-04 10:49:09 +08:00
d646e971f7 fix:【光环助手】游戏单广场遮罩错位问题 https://jira.shanqu.cc/browse/GHZSCY-7589 2025-03-04 10:48:32 +08:00
af9ba8a4d0 Merge branch 'feat/GHZSCY-7458-rebase-from-dev' into 'dev'
feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端

See merge request halo/android/assistant-android!2075
2025-03-04 10:44:00 +08:00
738dfd3b4d feat:https://jira.shanqu.cc/browse/GHZSCY-6976 奇游加速SDK接入—客户端 2025-03-04 10:44:00 +08:00
0f0962b261 Merge branch 'feat/GHZSCY-7544' into 'dev'
feat: 实名认证相关优化 https://jira.shanqu.cc/browse/GHZSCY-7544

See merge request halo/android/assistant-android!2074
2025-03-04 09:58:57 +08:00
ffa61e8b96 feat: 实名认证相关优化 https://jira.shanqu.cc/browse/GHZSCY-7544 2025-03-04 09:54:51 +08:00
b460750f5a Merge branch 'feat/va-relative' into 'dev'
feat: 预防进程组被杀时,server_dead的误报

See merge request halo/android/assistant-android!2073
2025-02-28 17:22:31 +08:00
528d7511f9 feat: 预防进程组被杀时,server_dead的误报
feat: 启动前台服务的兼容代码
2025-02-28 17:22:01 +08:00
02523b9e33 Merge branch 'refactor/add-comment' into 'dev'
refactor: 添加关于前台服务的提示性注释

See merge request halo/android/assistant-android!2072
2025-02-28 15:24:37 +08:00
bfc4083544 refactor: 添加关于前台服务的提示性注释 2025-02-28 15:24:07 +08:00
18e14d3811 Merge branch 'feat/va-relative' into 'dev'
fix: 停止前台服务的正确使用方法

See merge request halo/android/assistant-android!2071
2025-02-28 14:30:42 +08:00
89901028ea fix: 停止前台服务的正确使用方法 2025-02-28 14:30:21 +08:00
20c3f5cf46 Merge branch 'feat/va-relative' into 'dev'
feat: 恢复VA服务进程保活机制

See merge request halo/android/assistant-android!2070
2025-02-28 10:03:07 +08:00
9bc155816a feat: 恢复VA服务进程保活机制 2025-02-28 10:02:31 +08:00
92366c5629 Merge branch 'feat/va-relative' into 'dev'
chore: va组件升级 2.0.8

See merge request halo/android/assistant-android!2069
2025-02-27 15:48:27 +08:00
a1fd894dde chore: va组件升级 2.0.8
feat: 同步商业版 b370a11df7f0c3dbe4074024b1890c015136267e
feat: 优化crash上报逻辑
2025-02-27 15:47:21 +08:00
03757b22c7 Merge branch 'fix/GHZSCY-7539' into 'dev'
fix: 处理部分华为设备 APP 闪退问题 https://jira.shanqu.cc/browse/GHZSCY-7539

See merge request halo/android/assistant-android!2068
2025-02-26 16:33:39 +08:00
ce10d82a8f Merge branch 'fix/GHZSCY-5855' into 'dev'
feat: 游戏详情页改版优化 https://jira.shanqu.cc/browse/GHZSCY-5855

See merge request halo/android/assistant-android!2067
2025-02-26 14:17:16 +08:00
74a4feee9e feat: 游戏详情页改版优化 https://jira.shanqu.cc/browse/GHZSCY-5855 2025-02-26 14:17:16 +08:00
9cb0736e30 fix: 部分华为设备APP闪退问题 https://jira.shanqu.cc/browse/GHZSCY-7539 2025-02-24 17:54:01 +08:00
b954df3267 Merge branch 'fix/GHZSCY-7530' into 'dev'
fix:【光环助手】游戏单热搜榜 显示问题 https://jira.shanqu.cc/browse/GHZSCY-7530

See merge request halo/android/assistant-android!2066
2025-02-21 10:51:33 +08:00
0607a37256 fix:【光环助手】游戏单热搜榜 显示问题 https://jira.shanqu.cc/browse/GHZSCY-7530 2025-02-21 10:48:47 +08:00
00e4c7f1bd Merge branch 'fix/GHZSCY-7470' into 'dev'
fix: 修复部分设备安装xapk时提示解压失败 https://jira.shanqu.cc/browse/GHZSCY-7470

See merge request halo/android/assistant-android!2065
2025-02-20 14:16:24 +08:00
7d94250914 fix: 修复部分设备安装xapk时提示解压失败 https://jira.shanqu.cc/browse/GHZSCY-7470 2025-02-20 11:46:10 +08:00
7b8634aa40 Merge branch 'feat/GHZSCY-5855' into 'dev'
feat: 游戏详情页改版优化 https://jira.shanqu.cc/browse/GHZSCY-5855

See merge request halo/android/assistant-android!2047
2025-02-19 10:31:33 +08:00
210e18eb34 feat: 游戏详情页改版优化 https://jira.shanqu.cc/browse/GHZSCY-5855 2025-02-19 10:31:33 +08:00
957eff3c1e Merge branch 'fix/usage_stats_crash' into 'dev'
fix: 捕抓游戏游玩时长功能接口返回内容为空时的闪退问题

See merge request halo/android/assistant-android!2064
2025-02-17 14:28:30 +08:00
72b953b7c8 fix: 捕抓游戏游玩时长功能接口返回内容为空时的闪退问题 2025-02-17 14:26:57 +08:00
53fb77d2dc Merge branch 'fix/game_collection_square_fab' into 'dev'
fix: 修复首页游戏单列表滑动过程中快速切换tab错误显示右下角按钮的问题

See merge request halo/android/assistant-android!2063
2025-02-14 15:44:55 +08:00
f78e7a10a6 fix: 修复首页游戏单列表滑动过程中快速切换tab错误显示右下角按钮的问题 2025-02-14 15:42:10 +08:00
6aebea14bc Merge branch 'feat/add_push_id_to_debug_info' into 'dev'
feat: 我的页面 debug 数据增加推送 id

See merge request halo/android/assistant-android!2062
2025-02-14 15:30:25 +08:00
fd0fc31fc3 feat: 我的页面 debug 数据增加推送 id 2025-02-14 15:28:24 +08:00
889558eb4b Merge branch 'fix/GHZSCY-7461' into 'dev'
feat: 注销页面支持由网页端管理返回操作

See merge request halo/android/assistant-android!2060
2025-02-14 13:59:33 +08:00
37ec9ae66c feat: 注销页面支持由网页端管理返回操作 2025-02-14 10:39:30 +08:00
f4d34a635a Merge branch 'fix/search_fragment_recreate_culprit' into 'dev'
fix: 修复搜索页回收重建后不能正确刷新显示内容的问题

See merge request halo/android/assistant-android!2059
2025-02-14 09:52:35 +08:00
024895838c fix: 修复搜索页回收重建后不能正确刷新显示内容的问题 2025-02-13 15:58:44 +08:00
740bdbf79a Merge branch 'fix/GHZSCY-7405' into 'dev'
游戏显示异常问题 https://jira.shanqu.cc/browse/GHZSCY-7405

See merge request halo/android/assistant-android!2058
2025-02-12 15:36:24 +08:00
73f7bee3cc fix: 游戏显示异常问题 https://jira.shanqu.cc/browse/GHZSCY-7405 2025-02-12 14:51:35 +08:00
b37f247b14 Merge branch 'fix/GHZSCY-7349' into 'dev'
fix: 游戏大小显示问题 https://jira.shanqu.cc/browse/GHZSCY-7349

See merge request halo/android/assistant-android!2054
2025-02-12 14:50:30 +08:00
811378f411 Merge branch 'fix/va-proxy-npe' into 'dev'
fix: BindService代理NPE的问题。

See merge request halo/android/assistant-android!2057
2025-02-12 13:53:43 +08:00
f910cb67da fix: BindService代理NPE的问题。 2025-02-12 13:53:27 +08:00
4f2874877c Merge branch 'fix/va-proxy-npe' into 'dev'
fix: BindService代理NPE的问题。

See merge request halo/android/assistant-android!2056
2025-02-12 11:16:42 +08:00
15b35fd6b7 Merge branch 'fix/sentry-436163' into 'dev'
fix: 多进程下没有初始化mApp导致的闪退

See merge request halo/android/assistant-android!2055
2025-02-12 11:15:08 +08:00
cd64bb79a2 fix: 多进程下没有初始化mApp导致的闪退 2025-02-12 11:14:22 +08:00
d57c03b1f8 fix: BindService代理NPE的问题。 2025-02-12 11:12:16 +08:00
1895977d4c fix: 游戏大小显示问题 https://jira.shanqu.cc/browse/GHZSCY-7349 2025-02-12 10:03:32 +08:00
08571998b6 Merge branch 'fix/search_rank_icon_reuse_culprit' into 'dev'
fix: 修复搜索页面榜单的 icon 复用问题

See merge request halo/android/assistant-android!2053
2025-02-11 10:47:22 +08:00
21cdadca13 Merge remote-tracking branch 'origin/release' into dev 2025-02-11 10:39:29 +08:00
54dfa46204 fix: 修复搜索页面榜单的 icon 复用问题 2025-02-11 10:38:13 +08:00
c9f14641c9 Merge branch 'fix/GHZSCY-7440' into 'dev'
fix: 视频合集返回按钮点击无响应 https://jira.shanqu.cc/browse/GHZSCY-7440

See merge request halo/android/assistant-android!2052
2025-02-10 16:58:52 +08:00
0e95c0cbf6 fix: 视频合集返回按钮点击无响应 https://jira.shanqu.cc/browse/GHZSCY-7440 2025-02-10 16:23:51 +08:00
2a93af56ee Merge branch 'fix/GHZSCY-7435' into 'release'
Revert "ci"

See merge request halo/android/assistant-android!2051
2025-02-08 15:41:32 +08:00
28e785745b Revert "ci" 2025-02-08 15:41:32 +08:00
317620daf4 Merge branch 'feat/make_download_more_reponsive' into 'dev'
feat: 第一次包名列表获取成功时初始化浏览器安装条件,提高下载按钮反应速度

See merge request halo/android/assistant-android!2046
2025-02-07 17:41:48 +08:00
bf95a00bc6 Merge branch 'fix/game_update_crashes' into 'release'
fix: 修复游戏更新相关多线程并发操作引起的闪退问题

See merge request halo/android/assistant-android!2050
2025-02-06 16:03:48 +08:00
cb05c8a020 fix: 修复游戏更新相关多线程并发操作引起的闪退问题 2025-02-06 15:15:07 +08:00
d458898eb1 Merge branch 'chen/fix-search-page-crash' into 'release'
fix:https://sentry.shanqu.cc/organizations/lightgame/issues/431718/?project=22

See merge request halo/android/assistant-android!2049
2025-02-06 11:18:48 +08:00
f4f422b089 fix:https://sentry.shanqu.cc/organizations/lightgame/issues/431718/?project=22 2025-02-06 11:11:02 +08:00
e48b11f19d Merge branch 'hotfix/v5.38.8-1118/quick_back_pressed_crashed' into 'release'
fix: 启动应用时快速返回可能会闪退的问题 https://sentry.shanqu.cc/organizations/lightgame/issues/432602/

See merge request halo/android/assistant-android!2048
2025-02-06 10:34:07 +08:00
b6fe834406 fix: 启动应用时快速返回可能会闪退的问题 https://sentry.shanqu.cc/organizations/lightgame/issues/432602/ 2025-02-06 10:31:19 +08:00
8b1f35516d feat: 第一次包名列表获取成功时初始化浏览器安装条件,提高下载按钮反应速度 2025-01-17 14:14:49 +08:00
5d4648b3d2 feat: 推广包快手API新增次留上报 https://jira.shanqu.cc/browse/GHZSCY-7048 2024-11-27 15:59:53 +08:00
881 changed files with 38653 additions and 13559 deletions

View File

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

View File

@ -113,6 +113,7 @@ android {
buildConfigField "String", "WECHAT_SECRET", "\"${WECHAT_SECRET}\""
buildConfigField "String", "TENCENT_APPID", "\"${TENCENT_APPID}\""
buildConfigField "String", "WEIBO_APPKEY", "\"${WEIBO_APPKEY}\""
buildConfigField "String", "DSP_API_HOST","\"${DSP_API_HOST}\""
// 一体包的32位畅玩游戏助手包名
buildConfigField "String", "EXT_PACKAGE_NAME", "\"${rootProject.ext.EXT_PACKAGE_NAME}\""
}
@ -153,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")
@ -346,7 +347,7 @@ repositories {
android.applicationVariants.configureEach { variant ->
variant.mergeAssets.doLast {
def assetDir = variant.mergeAssetsProvider.get().outputDir.get()
def unwantedAssets = ['2011394667', 'gdt_plugin/gdtadv2.jar']
def unwantedAssets = ['1832823466', 'gdt_plugin/gdtadv2.jar']
unwantedAssets.each { assetPath ->
def file = new File([assetDir, assetPath].join(File.separator))
@ -402,7 +403,7 @@ dependencies {
exclude module: "gsyvideoplayer-androidvideocache"
exclude group: "tv.danmaku.ijk.media"
})
implementation ("com.github.CarGuo.GSYVideoPlayer:gsyVideoPlayer-exo_player2:$gsyVideo") {
implementation("com.github.CarGuo.GSYVideoPlayer:gsyVideoPlayer-exo_player2:$gsyVideo") {
exclude group: 'com.google.android.exoplayer', module: 'extension-rtmp'
}
@ -526,6 +527,20 @@ dependencies {
debugImplementation 'com.bytedance.android:shadowhook:1.0.9'
debugImplementation 'io.github.shiqos:wytrace:1.0.1'
if (!gradle.ext.excludeOptionalModules || gradle.ext.enableAccelerator) {
implementation(project(":feature:accelerator"))
}
if (!gradle.ext.excludeOptionalModules || gradle.ext.enableAliPay) {
implementation(project(":feature:ali_pay"))
}
if(!gradle.ext.excludeOptionalModules || gradle.ext.enableWechatPay){
implementation(project(":feature:wechat_pay"))
}
implementation "me.xdrop:fuzzywuzzy:${fuzzyVersion}"
}
File propFile = file('sign.properties')

View File

@ -20,6 +20,7 @@
-keep class com.gh.gamecenter.db.info.* {*;}
-keep class com.gh.gamecenter.entity.** {<fields>;}
-keep class com.gh.gamecenter.qa.entity.** {<fields>;}
-keep class com.gh.gamecenter.gamedetail.entity.** {<fields>;}
-keep class com.gh.download.DownloadDataSimpleEntity {<fields>;}
-keep class com.gh.gamecenter.floatingwindow.FloatingWindowEntity {<fields>;}
-keep class com.gh.gamecenter.BR
@ -76,6 +77,7 @@
### TEA
-keep class com.gh.gamecenter.TeaHelper { *; }
-keep class com.bytedance.ads.convert.broadcast.common.EncryptionTools {*;}
### EasyFloat
-keep class com.lzf.easyfloat.* {*;}

Binary file not shown.

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

@ -10,9 +10,9 @@
<queries>
<package android:name="com.gh.toolmap" />
<intent>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="ghtoolmap"/>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="ghtoolmap" />
</intent>
</queries>
@ -71,6 +71,10 @@
<!-- 适配 双开/分身 游戏授权登录 -->
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS" />
<!-- 日历 -->
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
<uses-sdk tools:overrideLibrary="
com.shuyu.gsyvideoplayer,
com.shuyu.gsyvideoplayer.lib,
@ -197,7 +201,9 @@
android:name="io.sentry.breadcrumbs.system-events"
android:value="false" />
<meta-data android:name="module_version" android:value="${VA_VERSION_NAME}" />
<meta-data
android:name="module_version"
android:value="${VA_VERSION_NAME}" />
<service android:name="com.gh.ndownload.NDownloadService" />
@ -814,6 +820,10 @@
android:name=".video.poster.PosterEditActivity"
android:screenOrientation="portrait" />
<activity
android:name="com.halo.assistant.accelerator.MyAssetsActivity"
android:screenOrientation="portrait" />
<!-- <activity-->
<!-- android:name="${applicationId}.douyinapi.DouYinEntryActivity"-->
<!-- android:launchMode="singleTask"-->

File diff suppressed because one or more lines are too long

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
@ -27,8 +29,8 @@ import com.gh.gamecenter.MainActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.exposure.meta.MetaUtil
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.AppExecutor
@ -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)
@ -178,6 +186,20 @@ object AdDelegateHelper {
when (config.location) {
"halo_launch" -> {
config.ownerAd?.startAd?.let { it.id = config.ownerAd.id }
// 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
}
mSplashAd = config
}
@ -206,6 +228,7 @@ object AdDelegateHelper {
private fun shouldShowStartUpAdWhenHotLaunch() = (mCsjAdImpl != null)
&& mSplashAd?.displayRule?.hotStartSplashAd?.type == AD_TYPE_SDK
&& mSplashAd?.hotStartThirdPartyAd != null
&& !isBuggyHuaweiDevice()
/**
* 是否需要显示下载管理广告
@ -600,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 {
@ -774,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

@ -19,10 +19,10 @@ import java.net.URLConnection
object AdPluginDownloadHelper : InnerDownloadListener {
private const val CSJ_FILE_NAME = "2011394667"
private const val CSJ_FILE_NAME = "1832823466"
private const val GDT_FILE_NAME = "gdt_plugin/gdtadv2.jar"
private const val CSJ_PLUGIN_URL = "https://and-static.ghzs66.com/android/static/2011394667"
private const val CSJ_PLUGIN_URL = "https://and-static.ghzs66.com/android/static/1832823466"
private const val GDT_PLUGIN_URL = "https://and-static.ghzs66.com/android/static/gdtadv2.jar"
private var csjDownloadedCallback: (() -> Unit)? = null

View File

@ -2,9 +2,9 @@ package com.gh.base
import android.app.Activity
import android.app.Application
import android.content.res.Configuration
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.therouter.TheRouter
import com.gh.ad.AdDelegateHelper
import com.gh.common.util.FloatingBackViewManager
import com.gh.common.xapk.XapkInstaller
@ -15,27 +15,45 @@ import com.gh.gamecenter.SplashAdActivity
import com.gh.gamecenter.SplashScreenActivity
import com.gh.gamecenter.authorization.AuthorizationActivity
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.provider.IHelpAndFeedbackProvider
import com.gh.gamecenter.common.utils.PackageFlavorHelper
import com.gh.gamecenter.core.provider.IPushProvider
import com.gh.gamecenter.login.utils.QuickLoginHelper
import com.gh.gamecenter.login.view.LoginActivity
import com.gh.gamecenter.va.VCore
import com.gh.gamecenter.login.utils.QuickLoginHelper
import com.gh.vspace.VHelper
import com.halo.assistant.HaloApp
import com.therouter.TheRouter
// TODO移动到对应的模块
class GlobalActivityLifecycleObserver : Application.ActivityLifecycleCallbacks {
private var isFromBackgroundToForeground = false // 是否后台回到前台
override fun onActivityPreCreated(activity: Activity, savedInstanceState: Bundle?) {
if (QuickLoginHelper.isLoginAuthPage(activity)) {
try {
val resources = activity.resources
val config = Configuration(resources.configuration)
config.fontScale = 1.0f
// 更新Resources配置
val metrics = resources.displayMetrics
metrics.scaledDensity = metrics.density
resources.updateConfiguration(config, metrics)
} catch (e: Exception) {
// 设置字体失败
}
}
}
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
// do nothing
}
override fun onActivityStarted(activity: Activity) {
GlobalActivityManager.currentActivity = activity
GlobalActivityManager.activityCount ++
GlobalActivityManager.activityCount++
if (GlobalActivityManager.activityCount == 1 && isFromBackgroundToForeground) {
if (AdDelegateHelper.shouldShowStartUpAd(true)
&& !HaloApp.getInstance().isDisableSplashAdTemporarily
@ -111,7 +129,7 @@ class GlobalActivityLifecycleObserver : Application.ActivityLifecycleCallbacks {
}
override fun onActivityStopped(activity: Activity) {
GlobalActivityManager.activityCount --
GlobalActivityManager.activityCount--
isFromBackgroundToForeground = GlobalActivityManager.activityCount <= 0
}

View File

@ -3,6 +3,8 @@ package com.gh.common
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.Base64
import android.view.View
import android.webkit.JavascriptInterface
@ -11,7 +13,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentManager
import com.therouter.TheRouter
import androidx.lifecycle.Lifecycle
import com.gh.common.exposure.ExposureManager
import com.gh.common.util.*
import com.gh.common.util.LogUtils
@ -19,10 +21,12 @@ import com.gh.download.DownloadManager
import com.gh.download.PackageObserver
import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.ImageViewerActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.WebActivity
import com.gh.gamecenter.common.callback.BiCallback
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.constant.Constants.SP_MEMBER_PAYMENT_BUTTON_CLICK
import com.gh.gamecenter.common.constant.Constants.SP_MEMBER_RECHARGE_BUTTON_CLICK
import com.gh.gamecenter.common.entity.NotificationUgc
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.loghub.LoghubUtils
@ -30,20 +34,23 @@ import com.gh.gamecenter.common.provider.IHelpAndFeedbackProvider
import com.gh.gamecenter.common.tracker.Tracker
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.utils.NewFlatLogUtils
import com.gh.gamecenter.common.utils.SensorsBridge.EVENT_MEMBER_RECHARGE_BUTTON_CLICK
import com.gh.gamecenter.common.utils.SensorsBridge.EVENT_NAME
import com.gh.gamecenter.common.utils.SensorsBridge.KEY_IS_FIRST_TIME
import com.gh.gamecenter.common.view.dsbridge.CompletionHandler
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.provider.IAcceleratorProvider
import com.gh.gamecenter.core.provider.IPushProvider
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.runOnUiThread
import com.gh.gamecenter.core.utils.CurrentActivityHolder
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.entity.SensorsEvent
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.feature.entity.AcctRecordEntity
import com.gh.gamecenter.feature.entity.Badge
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.OrderEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.login.user.LoginTag
import com.gh.gamecenter.login.user.UserManager
@ -55,10 +62,14 @@ import com.gh.gamecenter.personalhome.border.AvatarBorderActivity
import com.gh.gamecenter.setting.SettingBridge
import com.gh.vspace.VHelper
import com.halo.assistant.HaloApp
import com.halo.assistant.accelerator.repository.AccelerationRepository.Companion.PAYMENT_TYPE_ALIPAY
import com.halo.assistant.accelerator.repository.AccelerationRepository.Companion.PAYMENT_TYPE_WECHAT
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
import com.lightgame.download.DataWatcher
import com.lightgame.download.DownloadEntity
import com.lightgame.download.DownloadStatus.*
import com.lightgame.utils.Utils
import com.therouter.TheRouter
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
@ -71,10 +82,11 @@ import java.util.*
class DefaultJsApi(
var context: Context,
val entrance: String = "",
private var mFragment: Fragment? = null,
private val mFragment: Fragment? = null,
private var mBbsId: String? = "",
private var mOriginUrl: String? = "",
private val mForumName: String? = "",
private val listener: OnWebClickListener? = null
) {
companion object {
@ -87,6 +99,8 @@ class DefaultJsApi(
private var mDownloadHandler: CompletionHandler<Any>? = null // 下载信息回调
private var mExposureEvent: ExposureEvent? = null // 活动曝光实体
private val handler = Handler(Looper.getMainLooper())
init {
if (mFragment != null) {
EventBus.getDefault().register(this)
@ -233,6 +247,12 @@ class DefaultJsApi(
VHelper.launch(context, packageName)
}
} else {
val wechatPkgName = "com.tencent.mm"
if (packageName == wechatPkgName && !PackageUtils.isInstalled(context, wechatPkgName)) {
// 如果是微信客户端,需要检查是否安装微信
ToastUtils.showToast(R.string.wechat_app_not_install_tips.toResString())
return@runOnUiThread
}
PackageLauncher.launchApp(context, packageName = packageName)
}
}
@ -495,6 +515,44 @@ class DefaultJsApi(
}
}
@JavascriptInterface
fun saveWechatQRCode(msg: Any) {
val base64StringData = msg.toString()
runOnUiThread {
(context as? FragmentActivity)?.checkStoragePermissionBeforeAction {
runOnIoThread {
val base64String = base64StringData.replace("data:image/png;base64", "")
tryWithDefaultCatch {
val imageFile =
File(HaloApp.getInstance().cacheDir.absolutePath + File.separator + System.currentTimeMillis() + ".png")
val decodedString = Base64.decode(base64String, Base64.DEFAULT)
val bos = BufferedOutputStream(FileOutputStream(imageFile))
bos.write(decodedString)
bos.flush()
bos.close()
ImageUtils.saveImageToFile(imageFile, "", true) {
// 这里是 ui 线程
// 保存微信二维码成功1s 以后跳转微信
if (mFragment != null && mFragment.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
handler.removeCallbacksAndMessages(null)
handler.postDelayed({
val wechatPkgName = "com.tencent.mm"
if (!PackageUtils.isInstalled(context, wechatPkgName)) {
ToastUtils.showToast(R.string.wechat_app_not_install_tips.toResString())
return@postDelayed
}
PackageLauncher.launchApp(context, packageName = wechatPkgName)
}, 1000)
}
}
}
}
}
}
}
@JavascriptInterface
fun loginWithCallback(msg: Any, handler: CompletionHandler<Any>) {
mLoginHandler = handler
@ -699,6 +757,86 @@ class DefaultJsApi(
}
}
@JavascriptInterface
fun preOrderWithAli(json: Any) {
val order = json.toString().toObject<OrderEntity>() ?: return
trackMemberPaymentButtonClick(order, PAYMENT_TYPE_ALIPAY)
listener?.onPreOrderWithAli(order)
}
@JavascriptInterface
fun preOrderWithWechat(json: Any) {
val order = json.toString().toObject<OrderEntity>() ?: return
trackMemberPaymentButtonClick(order, PAYMENT_TYPE_WECHAT)
listener?.onPreOrderWithWechat(order)
}
private fun trackMemberPaymentButtonClick(order: OrderEntity, paymentType: String) {
val isFirstTime = SPUtils.getBoolean(SP_MEMBER_PAYMENT_BUTTON_CLICK, true)
SPUtils.setBoolean(SP_MEMBER_PAYMENT_BUTTON_CLICK, false)
SensorsBridge.trackMemberPaymentButtonClick(
isFirstTime,
paymentType,
order.setMenuName,
order.paymentAmount
)
}
@JavascriptInterface
fun startGameAccelerate(acctJson: Any) {
if (acctJson is String) {
val acctRecord = GsonUtils.fromJson(acctJson, AcctRecordEntity::class.java)
val accInfo = acctRecord.accInfo
listener?.onStartGameAccelerate(accInfo)
}
}
@JavascriptInterface
fun getCurAcctGameId(): String {
return AcceleratorDataHolder.instance.getAcceleratingGameId()
}
@JavascriptInterface
fun stopGameAccelerate() {
listener?.onStopGameAccelerate()
}
@JavascriptInterface
fun getDurationRemainingTime(msg: Any, handler: CompletionHandler<Any>) {
TheRouter.get(IAcceleratorProvider::class.java)?.loadQyUserPermissionData {
val durationExpiredMinute = AcceleratorDataHolder.instance.vipEntity?.durationExpiredTime ?: 0L
handler.complete(durationExpiredMinute)
}
}
@JavascriptInterface
fun refreshToken(token: Any, handler: CompletionHandler<Any>) {
val accessToken = token.toString()
UserManager.getInstance().refreshToken(accessToken, object : UserManager.refreshCallBack {
override fun onLogin() {
handler.complete(true)
}
override fun onLoginFailure(errorMessage: String?) {
handler.complete(false)
}
})
}
@JavascriptInterface
fun trackSensorsAnalytics(json: Any) {
val hashMap = json.toString().toObject<HashMap<String, Any>>() ?: return
val eventName = hashMap.remove(EVENT_NAME) ?: return
when (eventName) {
EVENT_MEMBER_RECHARGE_BUTTON_CLICK -> {
hashMap[KEY_IS_FIRST_TIME] = SPUtils.getBoolean(SP_MEMBER_RECHARGE_BUTTON_CLICK, true)
SPUtils.setBoolean(SP_MEMBER_RECHARGE_BUTTON_CLICK, false)
}
}
SensorsBridge.trackSensorsAnalyticsFromWeb(eventName.toString(), hashMap)
}
/**
* 获取 ExposureEvent可能为空
*/
@ -719,6 +857,8 @@ class DefaultJsApi(
}
EventBus.getDefault().unregister(this@DefaultJsApi)
handler.removeCallbacksAndMessages(null)
}
}
@ -815,4 +955,15 @@ class DefaultJsApi(
}
}
}
interface OnWebClickListener {
fun onPreOrderWithAli(order: OrderEntity)
fun onPreOrderWithWechat(order: OrderEntity)
fun onStartGameAccelerate(accInfo: AcctRecordEntity.AccInfo)
fun onStopGameAccelerate()
}
}

View File

@ -8,12 +8,21 @@ import com.gh.gamecenter.feature.entity.GameEntity
class PackageCheckHandler : DownloadChainHandler() {
override fun handleRequest(context: Context, gameEntity: GameEntity, asVGame: Boolean) {
PackageCheckDialogFragment.show((context as AppCompatActivity), gameEntity) {
fun nextOrProcessEnd() {
if (hasNext()) {
getNext()?.handleRequest(context, gameEntity, asVGame)
} else {
processEndCallback?.invoke(asVGame, null)
}
}
if (gameEntity.canSpeed) {
nextOrProcessEnd()
} else {
PackageCheckDialogFragment.show((context as AppCompatActivity), gameEntity) {
nextOrProcessEnd()
}
}
}
}

View File

@ -66,6 +66,8 @@ public class Config {
public static final String WGAME_CPM_BUSIAPPID = BuildConfig.WGAME_CPM_BUSIAPPID;
public static final String WGAME_CPM_API_HOST = EnvHelper.getWGameCPMHost();
public static final String DSP_API_HOST = EnvHelper.getDspHost();
// Third-Party confs
public static final String WECHAT_APPID = BuildConfig.WECHAT_APPID;
public static final String WECHAT_SECRET = BuildConfig.WECHAT_SECRET;
@ -388,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

@ -1,5 +1,10 @@
package com.gh.common.databind;
import static com.gh.gamecenter.entity.SubjectEntity.SUBJECT_TAG_SELLING_POINT;
import static com.gh.gamecenter.entity.SubjectEntity.SUBJECT_TAG_TEST;
import static com.gh.gamecenter.entity.SubjectEntity.SUBJECT_TAG_TYPE;
import static com.gh.gamecenter.entity.SubjectEntity.SUBJECT_TAG_UPDATE;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
@ -8,11 +13,14 @@ import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.view.View;
import android.view.ViewParent;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
import com.gh.common.chain.BrowserInstallHandler;
import com.gh.common.chain.CheckDownloadHandler;
@ -48,6 +56,7 @@ import com.gh.download.server.BrowserInstallHelper;
import com.gh.gamecenter.DownloadManagerActivity;
import com.gh.gamecenter.R;
import com.gh.gamecenter.WebActivity;
import com.gh.gamecenter.common.databinding.LayoutGameItemSellingPointBinding;
import com.gh.gamecenter.common.entity.LinkEntity;
import com.gh.gamecenter.common.utils.DarkModeUtils;
import com.gh.gamecenter.common.utils.ExtensionsKt;
@ -55,6 +64,7 @@ import com.gh.gamecenter.common.utils.FileUtils;
import com.gh.gamecenter.common.utils.NewFlatLogUtils;
import com.gh.gamecenter.common.utils.SensorsBridge;
import com.gh.gamecenter.core.utils.MtaHelper;
import com.gh.gamecenter.core.utils.TimeUtils;
import com.gh.gamecenter.core.utils.ToastUtils;
import com.gh.gamecenter.feature.entity.ApkEntity;
import com.gh.gamecenter.feature.entity.GameEntity;
@ -201,7 +211,8 @@ public class BindingAdapters {
gameEntity,
traceEvent,
entrance,
location + ":" + gameEntity.getName());
location + ":" + gameEntity.getName(),
null);
return null;
});
final DownloadChainHandler chainHandler = builder.buildHandlerChain();
@ -246,7 +257,8 @@ public class BindingAdapters {
gameEntity,
traceEvent,
entrance,
location + ":" + gameEntity.getName());
location + ":" + gameEntity.getName(),
null);
}
break;
case INSTALL_PLUGIN:
@ -262,7 +274,9 @@ public class BindingAdapters {
SensorsBridge.trackInstallGameClick(
gameEntity.getId(),
gameEntity.getName() != null ? gameEntity.getName() : "",
"主动安装"
"主动安装",
gameEntity.isDspGame(),
gameEntity.getDspAdId()
);
DownloadEntity downloadEntity = DownloadManager.getInstance().getDownloadEntitySnapshot(gameEntity);
@ -287,7 +301,7 @@ public class BindingAdapters {
});
break;
case RESERVED:
ReservationHelper.showCancelReservationDialog(progressBar.getContext(),gameEntity, () -> {
ReservationHelper.showCancelReservationDialog(progressBar.getContext(), gameEntity, () -> {
ReservationHelper.cancelReservation(gameEntity, () -> {
updateReservation(progressBar, gameEntity);
});
@ -470,39 +484,37 @@ public class BindingAdapters {
}
}
// 包含测试开服标签
public static void setGameTags(LinearLayout layout, GameEntity gameEntity) {
/**
* 包含测试开服标签
*
* @param layout
* @param gameEntity
* @param subjectTag 默认为 “”只有游戏专题可以配置subjectTag
*/
public static void setGameTags(LinearLayout layout, GameEntity gameEntity, String subjectTag) {
try {
if (layout.getVisibility() == View.GONE) return;
ArrayList<TagStyleEntity> tagStyle = new ArrayList<>();
TestEntity test = gameEntity.getTest();
if (test != null
// 这个判断用于开测表列表
&& !"type_tag".equals(test.getGameTag())) {
if ("custom".equals(test.getGameTag())) {
TagStyleEntity typeTag = new TagStyleEntity();
if (!TextUtils.isEmpty(test.getText())) {
typeTag.setName(test.getText() != null ? test.getText() : "");
} else {
typeTag.setName(test.getType() != null ? test.getType() : "");
}
typeTag.setBackground("E8F3FF");
typeTag.setColor("1383EB");
tagStyle.add(typeTag);
} else {
TagStyleEntity typeTag = new TagStyleEntity();
boolean isDarkModeOn = DarkModeUtils.INSTANCE.isDarkModeOn(layout.getContext());
typeTag.setName(test.getType() != null ? test.getType() : "");
typeTag.setBackground("1AFFA142");
typeTag.setColor(isDarkModeOn ? "EB9238" : "FFA142");
tagStyle.add(typeTag);
if (test != null && subjectTag.equals(SUBJECT_TAG_TEST)) {
// 显示开测表标签
TagStyleEntity typeTag = new TagStyleEntity();
boolean isDarkModeOn = DarkModeUtils.INSTANCE.isDarkModeOn(layout.getContext());
typeTag.setName(test.getType() != null ? test.getType() : "");
typeTag.setBackground("1AFFA142");
typeTag.setColor(isDarkModeOn ? "EB9238" : "FFA142");
tagStyle.add(typeTag);
TagStyleEntity timeTag = new TagStyleEntity();
TagStyleEntity timeTag = new TagStyleEntity();
if (test.getStartPending()) {
timeTag.setName(test.getStartText());
} else {
timeTag.setName(GameViewUtils.getGameTestDate(test.getStart()));
timeTag.setBackground("1A06CEA8");
timeTag.setColor(isDarkModeOn ? "07A385" : "06CEA8");
tagStyle.add(timeTag);
}
timeTag.setBackground("1A06CEA8");
timeTag.setColor(isDarkModeOn ? "07A385" : "06CEA8");
tagStyle.add(timeTag);
} else {
tagStyle = gameEntity.getTagStyle();
}
@ -512,6 +524,68 @@ public class BindingAdapters {
}
}
public static void setGameTagsWithSellingPoint(LinearLayout layout, LayoutGameItemSellingPointBinding binding, GameEntity gameEntity, String subjectTag) {
if (subjectTag.equals(SUBJECT_TAG_SELLING_POINT)) {
layout.setVisibility(View.GONE);
binding.getRoot().setVisibility(View.VISIBLE);
GameEntity.SellingPoints sellingPoints = gameEntity.getSellingPoints();
if (sellingPoints != null) {
binding.tvSellingPoints.setVisibility(View.VISIBLE);
binding.tvSellingPoints.setText(sellingPoints.getText());
} else {
binding.tvSellingPoints.setVisibility(View.GONE);
}
ArrayList<TagStyleEntity> tagStyle = gameEntity.getTagStyle();
StringBuilder tagText = new StringBuilder();
for (int i = 0; i < tagStyle.size(); i++) {
if (i < 3) {
tagText.append(i == 0 ? "" : "·").append(tagStyle.get(i).getName());
}
}
binding.gtcvTags.setText(tagText);
} else {
layout.setVisibility(View.VISIBLE);
binding.getRoot().setVisibility(View.GONE);
switch (subjectTag) {
case SUBJECT_TAG_UPDATE:
List<TagStyleEntity> updateTags = new ArrayList<>();
TagStyleEntity updateTag = new TagStyleEntity(
"local_generated",
TimeUtils.getFormatTime(gameEntity.getUpdateTime(), "MM-dd") + " 更新",
"",
"1383EB",
"E8F3FF",
"1383EB",
false
);
updateTags.add(updateTag);
GameViewUtils.setLabelList(layout.getContext(), layout, updateTags);
break;
case SUBJECT_TAG_TYPE:
GameViewUtils.setLabelList(layout.getContext(), layout, gameEntity.getTagStyle());
break;
default:
setGameTags(layout, gameEntity, subjectTag);
break;
}
}
ViewParent parent = binding.getRoot().getParent();
if (parent instanceof ConstraintLayout) {
ConstraintLayout constraintLayout = (ConstraintLayout) parent;
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(constraintLayout);
constraintSet.clear(R.id.gameDesSpace, ConstraintSet.BOTTOM);
if (subjectTag.equals(SUBJECT_TAG_SELLING_POINT)) {
constraintSet.connect(R.id.gameDesSpace, ConstraintSet.BOTTOM, R.id.layout_selling_points, ConstraintSet.TOP);
} else {
constraintSet.connect(R.id.gameDesSpace, ConstraintSet.BOTTOM, R.id.label_list, ConstraintSet.TOP);
}
constraintSet.applyTo(constraintLayout);
}
}
public static void setVideoDetailGameTags(LinearLayout layout, GameEntity gameEntity) {
try {
ArrayList<TagStyleEntity> tagStyle = new ArrayList<>();

View File

@ -0,0 +1,294 @@
package com.gh.common.dialog
import android.content.Context
import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.HaloApp
import com.gh.gamecenter.common.base.fragment.BaseDialogFragment
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.core.utils.CurrentActivityHolder
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.databinding.DialogFragmentAccelerateExporationBinding
import kotlinx.parcelize.Parcelize
public class AccelerateExpirationDialogFragment : BaseDialogFragment() {
private lateinit var binding: DialogFragmentAccelerateExporationBinding
private var _reminder: ExpirationReminder? = null
override fun onBack(): Boolean {
return true
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_reminder = arguments?.getParcelable(KEY_EXPIRATION_REMINDER)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return DialogFragmentAccelerateExporationBinding.inflate(inflater, container, false)
.also {
binding = it
}.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val reminder = _reminder ?: return
val (title, content) = when (reminder) {
is ExpirationReminder.UserExpired -> {
SensorsBridge.trackMonthlyPackageExpiresDialogShow(
reminder.gameId, reminder.gameName, reminder.pkgName, reminder.sourceEntrance
)
getString(
R.string.membership_expiration_reminder_title,
"${reminder.days}"
) to getString(R.string.membership_expiration_reminder_content)
}
is ExpirationReminder.InsufficientDuration -> {
SensorsBridge.trackInsufficientDurationDialogShow(
reminder.gameId,
reminder.gameName,
reminder.pkgName,
reminder.sourceEntrance
)
getString(
R.string.duration_time_out_reminder_title,
"${reminder.hours}"
) to getString(R.string.duration_time_out_reminder_content)
}
is ExpirationReminder.DurationExpired -> {
SensorsBridge.trackTimedPackageExpiresDialogShow(
reminder.gameId,
reminder.gameName,
reminder.pkgName,
reminder.sourceEntrance
)
getString(
R.string.duration_expiration_reminder_title,
"${reminder.days}"
) to getString(R.string.duration_expiration_reminder_content)
}
is ExpirationReminder.TimeRunsOut -> {
SensorsBridge.trackDurationExhaustedDialogShow()
getString(R.string.accelerator_member_expired_title) to getString(R.string.accelerator_member_expired_content)
}
}
binding.tvTitle.text = title
binding.tvContent.text = content
binding.tvCancel.setOnClickListener {
when (reminder) {
is ExpirationReminder.UserExpired -> {
SensorsBridge.trackMonthlyPackageExpiresDialogClick(
reminder.gameId, reminder.gameName, reminder.pkgName, reminder.sourceEntrance, BUTTON_NAME_KNOW
)
}
is ExpirationReminder.InsufficientDuration -> {
SensorsBridge.trackInsufficientDurationDialogClick(
reminder.gameId, reminder.gameName, reminder.pkgName, reminder.sourceEntrance, BUTTON_NAME_KNOW
)
}
is ExpirationReminder.DurationExpired -> {
SensorsBridge.trackTimedPackageExpiresDialogClick(
reminder.gameId, reminder.gameName, reminder.pkgName, reminder.sourceEntrance, BUTTON_NAME_KNOW
)
}
is ExpirationReminder.TimeRunsOut -> {
SensorsBridge.trackDurationExhaustedDialogClick(BUTTON_NAME_KNOW)
}
}
dismissAllowingStateLoss()
}
binding.tvSubmit.setOnClickListener {
when (reminder) {
is ExpirationReminder.UserExpired -> {
SensorsBridge.trackMonthlyPackageExpiresDialogClick(
reminder.gameId, reminder.gameName, reminder.pkgName, reminder.sourceEntrance, BUTTON_NAME_BUY
)
}
is ExpirationReminder.InsufficientDuration -> {
SensorsBridge.trackInsufficientDurationDialogClick(
reminder.gameId, reminder.gameName, reminder.pkgName, reminder.sourceEntrance, BUTTON_NAME_BUY
)
}
is ExpirationReminder.DurationExpired -> {
SensorsBridge.trackTimedPackageExpiresDialogClick(
reminder.gameId, reminder.gameName, reminder.pkgName, reminder.sourceEntrance, BUTTON_NAME_BUY
)
}
is ExpirationReminder.TimeRunsOut -> {
SensorsBridge.trackDurationExhaustedDialogClick(BUTTON_NAME_BUY)
}
}
SensorsBridge.trackMyAssetsPageShow(
reminder.pkgName,
reminder.gameId,
reminder.gameName,
reminder.dialogName
)
dismissAllowingStateLoss()
DirectUtils.navigateToMyAssetsPage(requireContext(), reminder.sourceEntrance)
}
}
override fun onStart() {
super.onStart()
val width = HaloApp.getInstance().resources.displayMetrics.widthPixels - 60F.dip2px()
val height = dialog?.window?.attributes?.height ?: ViewGroup.LayoutParams.WRAP_CONTENT
dialog?.window?.setLayout(width, height)
}
companion object {
const val KEY_EXPIRATION_REMINDER = "key_expiration_reminder"
private const val BUTTON_NAME_KNOW = "知道了"
private const val BUTTON_NAME_BUY = "前往购买"
fun checkDialogShown(context: Context, reminder: ExpirationReminder, callback: ((Boolean) -> Unit)? = null) {
when (reminder) {
is ExpirationReminder.UserExpired -> {
val days = SPUtils.getLong(Constants.SP_ACCELERATOR_USER_REMAINING_REMIND)
if (reminder.days != days) {
SPUtils.setLong(Constants.SP_ACCELERATOR_USER_REMAINING_REMIND, reminder.days)
show(context, reminder)
callback?.invoke(true)
} else {
callback?.invoke(false)
}
}
is ExpirationReminder.InsufficientDuration -> {
val hours = SPUtils.getLong(Constants.SP_ACCELERATOR_DURATION_REMAINING_HOURS)
if (reminder.hours != hours) {
SPUtils.setLong(Constants.SP_ACCELERATOR_DURATION_REMAINING_HOURS, reminder.hours)
show(context, reminder)
callback?.invoke(true)
} else {
callback?.invoke(false)
}
}
is ExpirationReminder.DurationExpired -> {
val days = SPUtils.getLong(Constants.SP_ACCELERATOR_DURATION_REMAINING_DAYS)
if (reminder.days != days) {
SPUtils.setLong(Constants.SP_ACCELERATOR_DURATION_REMAINING_DAYS, reminder.days)
show(context, reminder)
callback?.invoke(true)
} else {
callback?.invoke(false)
}
}
is ExpirationReminder.TimeRunsOut -> {
val hasShow = SPUtils.getBoolean(Constants.SP_ACCELERATOR_MEMBERSHIP_EXPIRED)
if (!hasShow) {
SPUtils.setBoolean(Constants.SP_ACCELERATOR_MEMBERSHIP_EXPIRED, true)
show(context, reminder)
callback?.invoke(true)
} else {
callback?.invoke(false)
}
}
}
}
fun show(context: Context, reminder: ExpirationReminder) {
if (context is AppCompatActivity) {
context.supportFragmentManager
} else {
(CurrentActivityHolder.getCurrentActivity() as? AppCompatActivity)?.supportFragmentManager
}?.let {
val fragment = AccelerateExpirationDialogFragment().apply {
arguments = Bundle().apply {
putParcelable(KEY_EXPIRATION_REMINDER, reminder)
}
}
fragment.show(it, AcceleratorPermissionGuideDialogFragment::class.java.name)
}
}
}
sealed class ExpirationReminder(
val gameId: String,
val gameName: String,
val pkgName: String,
val sourceEntrance: String
) :
Parcelable {
abstract val dialogName: String
@Parcelize
data class InsufficientDuration(
val hours: Long,
private val _gameId: String,
private val _gameName: String,
private val _pkgName: String,
private val _sourceEntrance: String,
) :
ExpirationReminder(_gameId, _gameName, _pkgName, _sourceEntrance) {
override val dialogName: String
get() = "时长不足提示弹窗"
}
@Parcelize
data class DurationExpired(
val days: Long,
private val _gameId: String,
private val _gameName: String,
private val _pkgName: String,
private val _sourceEntrance: String,
) :
ExpirationReminder(_gameId, _gameName, _pkgName, _sourceEntrance) {
override val dialogName: String
get() = "计时套餐临期提示弹窗"
}
@Parcelize
data class UserExpired(
val days: Long,
private val _gameId: String,
private val _gameName: String,
private val _pkgName: String,
private val _sourceEntrance: String,
) :
ExpirationReminder(_gameId, _gameName, _pkgName, _sourceEntrance) {
override val dialogName: String
get() = "包月套餐临期提示弹窗"
}
@Parcelize
data class TimeRunsOut(
private val _sourceEntrance: String,
) : ExpirationReminder("", "", "", _sourceEntrance) {
override val dialogName: String
get() = "时长耗尽提示弹窗"
}
}
}

View File

@ -0,0 +1,68 @@
package com.gh.common.dialog
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import com.gh.gamecenter.common.HaloApp
import com.gh.gamecenter.common.base.fragment.BaseDialogFragment
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.core.utils.CurrentActivityHolder
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.databinding.DialogFragmentAcceleratorPermissionGuideBinding
class AcceleratorPermissionGuideDialogFragment : BaseDialogFragment() {
private lateinit var binding: DialogFragmentAcceleratorPermissionGuideBinding
private var callback: (() -> Unit)? = null
override fun onBack(): Boolean {
return true
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return DialogFragmentAcceleratorPermissionGuideBinding.inflate(inflater, container, false)
.also {
binding = it
}.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
SPUtils.setBoolean(Constants.SP_ACCELERATOR_PERMISSION_GUIDE_SHOW, true)
binding.tvSubmit.setOnClickListener {
dismiss()
callback?.invoke()
}
}
fun setOnCallback(block: () -> Unit) {
this.callback = block
}
override fun onStart() {
super.onStart()
val width = HaloApp.getInstance().resources.displayMetrics.widthPixels - 60F.dip2px()
val height = dialog?.window?.attributes?.height ?: ViewGroup.LayoutParams.WRAP_CONTENT
dialog?.window?.setLayout(width, height)
}
companion object {
fun show(context: Context, callback: () -> Unit) {
if (context is AppCompatActivity) {
context.supportFragmentManager
} else {
(CurrentActivityHolder.getCurrentActivity() as? AppCompatActivity)?.supportFragmentManager
}?.let {
val fragment = AcceleratorPermissionGuideDialogFragment()
fragment.setOnCallback(callback)
fragment.show(it, AcceleratorPermissionGuideDialogFragment::class.java.name)
}
}
}
}

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

@ -16,6 +16,7 @@ import com.gh.common.pop.EditBindWechatPop
import com.gh.common.pop.RealNameTipsPop
import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.entity.WechatConfigEntity
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toObject
@ -83,79 +84,90 @@ class ReserveSuccessReminderDialog(
handlers.clear()
binding.flContentContainer.removeAllViews()
val smsConfig = reserveReminder.smsConfig
val wechatConfig = reserveReminder.wechatConfig
when {
reserveReminder.onlyShowWechatReminder && !wechatConfig.isReminderEnable -> { // 只显示微信:未开启
binding.tvContent.setText(R.string.reverse_success_without_reminder_tips)
arrayListOf(OnlyWechatReminderUnableHandler(16.dp, binding.flContentContainer, this))
val configTypes = mutableListOf<Int>()
// 短信
if (reserveReminder.hasSmsConfig) {
if (reserveReminder.smsConfig.notice) {
configTypes.add(SMS_REMINDER_ENABLE_TYPE)
} else {
configTypes.add(SMS_REMINDER_UNABLE_TYPE)
}
reserveReminder.onlyShowWechatReminder && wechatConfig.isReminderEnable -> { // 只显示微信:已开
binding.tvContent.setText(R.string.reverse_success_with_reminder_tips)
arrayListOf(
WechatReminderEnableHandler(
reserveReminder.wechatConfig.nickName,
8.dp,
binding.flContentContainer,
this
)
)
}
!smsConfig.notice && !wechatConfig.isReminderEnable -> { // 短信,微信未开启
binding.tvContent.setText(R.string.reverse_success_without_reminder_tips)
arrayListOf(
SmsReminderUnableHandler(16.dp, binding.flContentContainer, this),
WechatReminderUnableHandler(8.dp, binding.flContentContainer, this)
)
}
smsConfig.notice && wechatConfig.isReminderEnable -> {// 短信,微信已开启
binding.tvContent.setText(R.string.reverse_success_with_reminder_tips)
arrayListOf(
SmsReminderEnableHandler(reserveReminder.smsConfig, 8.dp, binding.flContentContainer, this),
WechatReminderEnableHandler(
reserveReminder.wechatConfig.nickName,
8.dp,
binding.flContentContainer,
this
)
)
}
smsConfig.notice && !wechatConfig.isReminderEnable -> { // 短信开启,微信未开启
binding.tvContent.setText(R.string.reverse_success_with_reminder_tips)
arrayListOf(
SmsReminderEnableHandler(smsConfig, 8.dp, binding.flContentContainer, this),
WechatReminderUnableHandler(16.dp, binding.flContentContainer, this)
)
}
!smsConfig.notice && wechatConfig.isReminderEnable -> { // 微信开启,短信未开启
binding.tvContent.setText(R.string.reverse_success_with_reminder_tips)
arrayListOf(
WechatReminderEnableHandler(
wechatConfig.nickName,
8.dp,
binding.flContentContainer,
this
),
SmsReminderUnableHandler(16.dp, binding.flContentContainer, this)
)
}
else -> {
binding.tvContent.setText(R.string.reverse_success_without_reminder_tips)
arrayListOf()
}
}.let {
handlers.clear()
handlers.addAll(it)
}
// 微信
if (reserveReminder.wechatConfig.isReminderEnable) {
configTypes.add(WECHAT_REMINDER_ENABLE_TYPE)
} else {
configTypes.add(WECHAT_REMINDER_UNABLE_TYPE)
}
// 日历
if (reserveReminder.hasCalendarConfig) {
if (reserveReminder.calendarConfig.notice) {
configTypes.add(CALENDAR_REMINDER_ENABLE_TYPE)
} else {
configTypes.add(CALENDAR_REMINDER_UNABLE_TYPE)
}
}
if (configTypes.size == 1) {
// 只有微信提醒
if (configTypes.first() == WECHAT_REMINDER_ENABLE_TYPE) {
binding.tvContent.setText(R.string.reverse_success_with_reminder_tips)
handlers.add(
WechatReminderEnableHandler(reserveReminder.wechatConfig, 8.dp, binding.flContentContainer, this)
)
} else {
binding.tvContent.setText(R.string.reverse_success_without_reminder_tips)
handlers.add(OnlyWechatReminderUnableHandler(16.dp, binding.flContentContainer, this))
}
} else {
binding.tvContent.setText(R.string.reverse_success_with_reminder_tips)
var isLargerSpacing = true
fun getPaddingTop(): Int {
val paddingTop = if (isLargerSpacing) 16.dp else 8.dp
isLargerSpacing = false
return paddingTop
}
configTypes.sorted().forEach {
when (it) {
SMS_REMINDER_ENABLE_TYPE -> {
isLargerSpacing = true
SmsReminderEnableHandler(reserveReminder.smsConfig, 8.dp, binding.flContentContainer, this)
}
SMS_REMINDER_UNABLE_TYPE -> {
SmsReminderUnableHandler(getPaddingTop(), binding.flContentContainer, this)
}
WECHAT_REMINDER_ENABLE_TYPE -> {
isLargerSpacing = true
WechatReminderEnableHandler(
reserveReminder.wechatConfig,
8.dp,
binding.flContentContainer,
this
)
}
WECHAT_REMINDER_UNABLE_TYPE ->
WechatReminderUnableHandler(getPaddingTop(), binding.flContentContainer, this)
CALENDAR_REMINDER_ENABLE_TYPE -> {
isLargerSpacing = true
CalendarReminderEnableHandler(8.dp, binding.flContentContainer, this)
}
CALENDAR_REMINDER_UNABLE_TYPE ->
CalendarReminderUnableHandler(getPaddingTop(), binding.flContentContainer, this)
else -> null
}?.let(handlers::add)
}
}
handlers.forEach {
binding.flContentContainer.addView(it.init())
}
@ -220,9 +232,14 @@ class ReserveSuccessReminderDialog(
listener.changeWechatBinding()
}
override fun updateCalendarReminder() {
listener.updateCalendarReminder()
}
override fun onStart() {
super.onStart()
window?.let {
it.setDimAmount(0.4F)
it.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
val params = it.attributes
params.width = DisplayUtils.dip2px(300F)
@ -244,6 +261,13 @@ class ReserveSuccessReminderDialog(
private val Int.dp: Int
get() = DisplayUtils.dip2px(this.toFloat())
private const val SMS_REMINDER_ENABLE_TYPE = 1
private const val WECHAT_REMINDER_ENABLE_TYPE = 2
private const val CALENDAR_REMINDER_ENABLE_TYPE = 3
private const val SMS_REMINDER_UNABLE_TYPE = 4
private const val WECHAT_REMINDER_UNABLE_TYPE = 5
private const val CALENDAR_REMINDER_UNABLE_TYPE = 6
fun create(context: Context, listener: OnReserveReminderListener) =
ReserveSuccessReminderDialog(
context,
@ -360,7 +384,7 @@ class ReserveSuccessReminderDialog(
}
class WechatReminderEnableHandler(
private val nickName: String,
private val wechatConfig: WechatConfigEntity,
topMargin: Int,
parent: ViewGroup,
listener: OnReserveSuccessListener
@ -380,7 +404,7 @@ class ReserveSuccessReminderDialog(
}.root
override fun initView() {
binding.tvWechat.text = nickName
binding.tvWechat.text = wechatConfig.nickName
binding.vModifyWechat.setOnClickListener {
editWechatPop.showAsDropDown(
binding.ivModifyWechat,
@ -411,6 +435,51 @@ class ReserveSuccessReminderDialog(
}
class CalendarReminderUnableHandler(
topMargin: Int,
parent: ViewGroup,
listener: OnReserveSuccessListener
) : ReminderContentHandler(topMargin, parent, listener) {
private lateinit var binding: LayoutReserveWechatReminderUnableBinding
// 复用微信提醒未开启状态布局
override fun createView(inflater: LayoutInflater) =
LayoutReserveWechatReminderUnableBinding.inflate(inflater, parent, false)
.also {
binding = it
}.root
override fun initView() {
binding.tvWechatReminderTitle.setText(R.string.calendar_reminders)
binding.tvWechatReminderDescription.setText(R.string.calendar_reminders_description)
binding.vWechatAdd.setOnClickListener {
listener.updateCalendarReminder()
}
}
}
class CalendarReminderEnableHandler(
topMargin: Int,
parent: ViewGroup,
listener: OnReserveSuccessListener
) : ReminderContentHandler(topMargin, parent, listener) {
private lateinit var binding: LayoutReserveCalendarReminderUnableBinding
override fun createView(inflater: LayoutInflater) =
LayoutReserveCalendarReminderUnableBinding.inflate(inflater, parent, false)
.also {
binding = it
}.root
override fun initView() {
}
}
}
@ -425,4 +494,6 @@ interface OnReserveSuccessListener {
fun verifyPhoneNumber()
fun changeWechatBinding()
fun updateCalendarReminder()
}

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

@ -19,7 +19,7 @@ class ExposureListener(var fragment: Fragment, var exposable: IExposable) : Recy
ExposureThrottleBus(
{ commitExposure(it) },
Consumer(Throwable::printStackTrace),
{ commitWXCPMExposure(it) }
{ commitExternalExposure(it) }
)
}
var layoutManager: LayoutManager? = null
@ -31,7 +31,7 @@ class ExposureListener(var fragment: Fragment, var exposable: IExposable) : Recy
override fun onFragmentPaused(fm: FragmentManager, f: Fragment) {
if (fragment == f) {
visibleState?.let { commitExposure(it) }
visibleState?.let { commitWXCPMExposure(it) }
visibleState?.let { commitExternalExposure(it) }
throttleBus.clear()
}
}
@ -97,23 +97,33 @@ class ExposureListener(var fragment: Fragment, var exposable: IExposable) : Recy
}
/**
* 微信小游戏CPM曝光事件接口上报由于普通曝光事件的上报通道存在节流特性不符合CPM接口对于数据上报的实时性和准确性的要求所以使用额外的通道进行上报
* 提交第三方的曝光事件 微信小游戏CPM 和 DSP 游戏)
* 由于普通曝光事件的上报通道存在节流特性不符合CPM接口对于数据上报的实时性和准确性的要求所以使用额外的通道进行上报
*/
private fun commitWXCPMExposure(visibleState: ExposureThrottleBus.VisibleState) {
private fun commitExternalExposure(visibleState: ExposureThrottleBus.VisibleState) {
val eventList = arrayListOf<ExposureEvent>()
val cpmEventList = arrayListOf<ExposureEvent>()
val dspEventList = arrayListOf<ExposureEvent>()
for (pos in visibleState.firstVisiblePosition..visibleState.lastVisiblePosition) {
try {
exposable.getEventByPosition(pos)?.let {
if (it.payload.miniGameType == Constants.WECHAT_MINI_GAME_CPM) {
eventList.add(it)
when (it.payload.miniGameType) {
Constants.WECHAT_MINI_GAME_CPM -> cpmEventList.add(it)
Constants.DSP_GAME -> dspEventList.add(it)
else -> {
// do nothing
}
}
}
exposable.getEventListByPosition(pos)?.let { list ->
list.forEach {
if (it.payload.miniGameType == Constants.WECHAT_MINI_GAME_CPM) {
eventList.add(it)
when (it.payload.miniGameType) {
Constants.WECHAT_MINI_GAME_CPM -> cpmEventList.add(it)
Constants.DSP_GAME -> dspEventList.add(it)
else -> {
// do nothing
}
}
}
}
@ -121,7 +131,8 @@ class ExposureListener(var fragment: Fragment, var exposable: IExposable) : Recy
// Just ignore the error.
}
}
ExposureManager.logCPM(eventList)
ExposureManager.logExternal(cpmEventList, dspEventList)
}
}

View File

@ -4,10 +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
@ -29,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.
*/
@ -36,6 +54,10 @@ object ExposureManager {
AppExecutor.logExecutor.execute {
if (event.payload.miniGameType == Constants.WECHAT_MINI_GAME_CPM) {
WGameSubjectCPMListReportHelper.reportExposure(event)
} else if (event.payload.miniGameType == Constants.DSP_GAME) {
if (event.event == ExposureType.DOWNLOAD_COMPLETE) {
DspReportHelper.report(event.payload.downloadUrl)
}
}
if (!exposureCache.contains(event.id)) {
exposureSet.add(event)
@ -63,14 +85,35 @@ object ExposureManager {
}
}
/**
* Log a collection of exposure event for external use.
*/
fun logExternal(cpmEventList: List<ExposureEvent>, dspEventList: ArrayList<ExposureEvent>) {
AppExecutor.logExecutor.execute {
if (cpmEventList.isNotEmpty()) {
WGameSubjectCPMListReportHelper.reportExposure(cpmEventList.toSet())
}
if (dspEventList.isNotEmpty()) {
for (event in dspEventList) {
DspReportHelper.report(event.payload.showUrl)
}
}
}
}
/**
* Log a wechat mini game cpm collection of exposure event.
*/
fun logCPM(eventList: List<ExposureEvent>) {
fun logCPM(event: ExposureEvent) {
AppExecutor.logExecutor.execute {
if (eventList.isNotEmpty()) {
WGameSubjectCPMListReportHelper.reportExposure(eventList.toSet())
}
WGameSubjectCPMListReportHelper.reportExposure(event)
}
}
fun logDSP(event: ExposureEvent) {
AppExecutor.logExecutor.execute {
DspReportHelper.report(event.payload.showUrl)
}
}
@ -102,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

@ -89,6 +89,7 @@ object ExposureUtils {
exposureEvent.payload.path = path
exposureEvent.payload.speed = speed
exposureEvent.payload.redirectedUrlList = redirectedUrlList
exposureEvent.payload.downloadUrl = gameEntity.downloadUrl
ExposureManager.log(exposureEvent)
ExposureManager.commitSavedExposureEvents(forcedUpload = true)

View File

@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.PackageFlavorHelper
import com.gh.gamecenter.common.utils.debounceActionWithInterval
import com.gh.gamecenter.common.utils.toJson
import com.gh.gamecenter.common.utils.toObject
@ -72,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
}
@ -123,10 +124,16 @@ object RegionSettingHelper {
mIsInit = false
}
val fakeIp = if (PackageFlavorHelper.IS_TEST_FLAVOR) {
SPUtils.getString(Constants.SP_TEST_FLAVOR_IP)
} else {
""
}
// 使用默认的 Schdulers.io() 可能会触发 OOM
RetrofitManager.getInstance()
.api
.getRegionSetting(HaloApp.getInstance().channel)
.getRegionSetting(HaloApp.getInstance().channel, fakeIp)
.subscribeOn(Schedulers.io())
.subscribe(object : BiResponse<RegionSetting>() {
override fun onSuccess(data: RegionSetting) {

View File

@ -7,19 +7,20 @@ import androidx.room.TypeConverters
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.gh.gamecenter.entity.GamesCollectionEntity
import com.gh.gamecenter.entity.HistoryGameDetailEntity
import com.gh.gamecenter.entity.HistoryGameEntity
import com.gh.gamecenter.entity.MyVideoEntity
import com.gh.gamecenter.feature.entity.NewsEntity
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.feature.entity.ArticleEntity
import com.gh.gamecenter.feature.entity.NewsEntity
import com.gh.gamecenter.feature.room.converter.*
import com.gh.gamecenter.room.converter.*
import com.gh.gamecenter.room.dao.*
import com.halo.assistant.HaloApp
@Database(
entities = [AnswerEntity::class, ArticleEntity::class, NewsEntity::class, HistoryGameEntity::class, MyVideoEntity::class, GamesCollectionEntity::class],
version = 14,
entities = [AnswerEntity::class, ArticleEntity::class, NewsEntity::class, HistoryGameEntity::class, MyVideoEntity::class, GamesCollectionEntity::class, HistoryGameDetailEntity::class],
version = 15,
exportSchema = false
)
@TypeConverters(
@ -53,6 +54,7 @@ abstract class HistoryDatabase : RoomDatabase() {
abstract fun gameDao(): GameDao
abstract fun videoHistoryDao(): VideoHistoryDao
abstract fun gamesCollectionDao(): GamesCollectionDao
abstract fun gameDetailDao(): GameDetailHistoryDao
companion object {
@ -152,6 +154,12 @@ abstract class HistoryDatabase : RoomDatabase() {
}
}
val MIGRATION_14_15: Migration = object : Migration(14, 15) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE HistoryGameDetailEntity (id TEXT NOT NULL PRIMARY KEY, name TEXT DEFAULT '')")
}
}
val instance by lazy {
Room.databaseBuilder(
HaloApp.getInstance().application,
@ -170,6 +178,7 @@ abstract class HistoryDatabase : RoomDatabase() {
.addMigrations(MIGRATION_11_12)
.addMigrations(MIGRATION_12_13)
.addMigrations(MIGRATION_13_14)
.addMigrations(MIGRATION_14_15)
.build()
}
}

View File

@ -44,6 +44,20 @@ object HistoryHelper {
runOnIoThread { tryCatchInRelease { HistoryDatabase.instance.gameDao().addGame(historyGameEntity) } }
}
@JvmStatic
fun insertGameDetail(gameEntity: GameEntity) {
val historyGameDetailEntity = HistoryGameDetailEntity(gameEntity.id, gameEntity.name)
runOnIoThread { tryCatchInRelease { HistoryDatabase.instance.gameDetailDao().addGame(historyGameDetailEntity) } }
}
@JvmStatic
fun getHistoryGameDetailById(id: String): HistoryGameDetailEntity? =
try {
HistoryDatabase.instance.gameDetailDao().getHistoryGameDetailById(id)
} catch (e: Throwable) {
null
}
private fun convertGameUpdateEntityToHistoryGameEntity(updateEntity: GameUpdateEntity): HistoryGameEntity {
val historyGame = HistoryGameEntity()
@ -142,6 +156,15 @@ object HistoryHelper {
}
}
@JvmStatic
fun deleteGameDetailEntity(gameId: String) {
runOnIoThread {
tryCatchInRelease {
HistoryDatabase.instance.gameDetailDao().deleteGame(HistoryGameDetailEntity(id = gameId))
}
}
}
@JvmStatic
fun emptyDatabase() {

View File

@ -0,0 +1,29 @@
package com.gh.common.interceptor
import com.gh.gamecenter.common.constant.RouteConsts
import com.halo.assistant.HaloApp
import com.therouter.router.RouteItem
import com.therouter.router.interceptor.RouterReplaceInterceptor
import com.therouter.router.matchRouteMap
/**
* 主拦截器
*/
class MainInterceptor: RouterReplaceInterceptor() {
override fun replace(routeItem: RouteItem?): RouteItem? {
if (routeItem == null) return null
// 用户是否已经同意隐私政策
val isUserAcceptPrivacyPolicy = HaloApp.isUserAcceptPrivacyPolicy(HaloApp.getInstance())
// 如果用户已经同意隐私政策并且应用已经启动,直接返回 routeItem ,运行跳转
if (isUserAcceptPrivacyPolicy && HaloApp.getInstance().isAlreadyUpAndRunning) {
return routeItem
}
// 指向调整为 SplashScreenActivity
return matchRouteMap(RouteConsts.activity.splashActivity)
}
}

View File

@ -59,7 +59,7 @@ class CustomFloatingWindowHandler(priority: Int) : PriorityChainHandler(priority
override fun onProcess(): Boolean {
when (getStatus()) {
STATUS_VALID -> {
_showFloatingAction.value = Event(data)
_showFloatingAction.postValue(Event(data))
return true
// floatingWindowProvider.showFloatingWindowOnly(
// mFragment!!,

View File

@ -64,7 +64,8 @@ object GlobalPriorityChainHelper : ISuperiorChain {
* 预启动所有的优先级弹窗管理链
*/
fun preStart(withSpecialDelay: Boolean) {
val launchRedirectHandler = LaunchRedirectHandler(-101)
val launchRedirectHandler = LaunchRedirectHandler(-102)
val membershipExpiredHandler = MembershipExpiredHandler(-101)
val updateDialogHandler = UpdateDialogHandler(-100)
val privacyPolicyDialogHandler = PrivacyPolicyDialogHandler(-99)
val notificationPermissionDialogHandler = NotificationPermissionDialogHandler(0)
@ -74,6 +75,7 @@ object GlobalPriorityChainHelper : ISuperiorChain {
val resumeDownloadHandler = ResumeDownloadHudHandler(4)
mainChain.addHandler(launchRedirectHandler)
mainChain.addHandler(membershipExpiredHandler)
mainChain.addHandler(updateDialogHandler)
mainChain.addHandler(privacyPolicyDialogHandler)
mainChain.addHandler(welcomeDialogHandler)
@ -83,6 +85,7 @@ object GlobalPriorityChainHelper : ISuperiorChain {
mainChain.addHandler(resumeDownloadHandler)
launchRedirectHandler.doPreProcess()
membershipExpiredHandler.doPreProcess()
updateDialogHandler.doPreProcess()
// 首次启动延迟 300ms保证请求首次启动时已经获取到了 GID 、 OAID 等标记

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

@ -0,0 +1,80 @@
package com.gh.common.prioritychain
import android.os.Bundle
import androidx.fragment.app.FragmentActivity
import com.gh.common.dialog.AccelerateExpirationDialogFragment
import com.gh.gamecenter.common.constant.Constants.SP_ACCELERATOR_MEMBERSHIP_EXPIRED
import com.gh.gamecenter.core.utils.CurrentActivityHolder
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.login.user.UserRepository
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
class MembershipExpiredHandler(priority: Int) : PriorityChainHandler(priority) {
fun doPreProcess() {
UserRepository.getInstance().setAutoLoginListener {
val hasShow = SPUtils.getBoolean(SP_ACCELERATOR_MEMBERSHIP_EXPIRED)
val vipEntity = AcceleratorDataHolder.instance.vipEntity
if (getStatus() == STATUS_PENDING) {
if (vipEntity == null ||
vipEntity.vipStatus ||
vipEntity.isNewUser ||
vipEntity.isVipRefundUser ||
vipEntity.isDurationRefundUser ||
hasShow
) {
processNext()
} else {
updateStatus(STATUS_VALID)
process()
}
} else {
if (vipEntity == null ||
vipEntity.vipStatus ||
vipEntity.isNewUser ||
vipEntity.isVipRefundUser ||
vipEntity.isDurationRefundUser ||
hasShow
) {
updateStatus(STATUS_INVALID)
} else {
updateStatus(STATUS_VALID)
}
}
}
}
override fun onProcess(): Boolean {
val currentActivity = CurrentActivityHolder.getCurrentActivity()
if (GlobalPriorityChainHelper.isThisActivityValid(currentActivity)) {
when (getStatus()) {
STATUS_VALID -> {
val reminder = AccelerateExpirationDialogFragment.ExpirationReminder.TimeRunsOut("")
SPUtils.setBoolean(SP_ACCELERATOR_MEMBERSHIP_EXPIRED, true)
val dialogFragment = AccelerateExpirationDialogFragment().apply {
arguments = Bundle().apply {
putParcelable(
AccelerateExpirationDialogFragment.KEY_EXPIRATION_REMINDER, reminder
)
}
}
dialogFragment.dialog?.setOnDismissListener {
processNext()
}
dialogFragment.show(
(currentActivity as FragmentActivity).supportFragmentManager,
AccelerateExpirationDialogFragment::class.java.name
)
return true
}
STATUS_INVALID -> {
processNext()
}
}
}
return false
}
}

View File

@ -4,8 +4,10 @@ import android.view.View
import android.widget.LinearLayout
import android.widget.TextView
import com.gh.common.databind.BindingAdapters
import com.gh.gamecenter.common.databinding.LayoutGameItemSellingPointBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.provider.IBindingAdaptersProvider
import com.gh.gamecenter.home.custom.adapter.CustomViewExt
@com.therouter.inject.ServiceProvider
class BindingAdaptersProviderImpl : IBindingAdaptersProvider {
@ -18,7 +20,7 @@ class BindingAdaptersProviderImpl : IBindingAdaptersProvider {
}
override fun setGameTags(layout: LinearLayout, gameEntity: GameEntity) {
BindingAdapters.setGameTags(layout, gameEntity)
BindingAdapters.setGameTags(layout, gameEntity, "")
}
override fun setMessageUnread(view: TextView, unreadCount: Int) {
@ -28,4 +30,17 @@ class BindingAdaptersProviderImpl : IBindingAdaptersProvider {
override fun setGame(view: View, gameEntity: GameEntity) {
BindingAdapters.setGame(view, gameEntity)
}
override fun setGameTagsWithSellingPoints(
layout: LinearLayout,
sellingPointsBinding: LayoutGameItemSellingPointBinding,
gameEntity: GameEntity,
subjectTag: String
) {
BindingAdapters.setGameTagsWithSellingPoint(layout, sellingPointsBinding, gameEntity, subjectTag)
}
override fun setGameDescription(tvDesc: TextView, briefStyle: String, game: GameEntity) {
CustomViewExt.setDescription(tvDesc, briefStyle, game)
}
}

View File

@ -31,4 +31,6 @@ class BuildConfigImpl : IBuildConfigProvider {
override fun getWGameCPMBusiAppId(): String = BuildConfig.WGAME_CPM_BUSIAPPID
override fun getLogProducerProject(): String = BuildConfig.LOG_HUB_PROJECT
override fun getDspApiHost(): String = BuildConfig.DSP_API_HOST
}

View File

@ -2,6 +2,8 @@ package com.gh.common.provider
import com.gh.common.util.NewFlatLogUtils
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.entity.GameUpdateEntity
@ -11,6 +13,7 @@ import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.provider.IDownloadButtonClickedProvider
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.packagehelper.PackageRepository
import com.halo.assistant.HaloApp
import com.lightgame.download.DownloadEntity
import com.lightgame.utils.Utils
@ -27,6 +30,11 @@ 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()
val boundedObject = downloadButton.getObject()
@ -54,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 -> {
@ -113,8 +123,10 @@ class DownloadButtonClickedProviderImpl : IDownloadButtonClickedProvider {
}
// 小游戏的启动不需要上报下载点击事件
// @see https://jira.shanqu.cc/browse/GHZSCY-7013
if (boundedObject is GameEntity && boundedObject.isMiniGame()) {
// @see https://jira.shanqu.cc/browse/GHZSCY-7013 & https://jira.shanqu.cc/browse/GHZSCY-7918
if (boundedObject is GameEntity
&& (boundedObject.isMiniGame() || boundedObject.isDspGame)
) {
return
}
@ -136,6 +148,11 @@ 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,
*customPageKV
)
}

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

@ -0,0 +1,59 @@
package com.gh.common.provider
import com.gh.common.util.PackageUtils
import android.annotation.SuppressLint
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.core.provider.IAcceleratorDataHolderProvider
import com.gh.gamecenter.feature.entity.BaseEntity
import com.gh.gamecenter.feature.entity.VipEntity
import com.gh.gamecenter.login.retrofit.RetrofitManager
import com.gh.gamecenter.login.user.UserManager
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
@com.therouter.inject.ServiceProvider
class IAcceleratorDataHolderProviderImpl : IAcceleratorDataHolderProvider {
override fun isPaidVip(): Boolean =
AcceleratorDataHolder.instance.isPaidVip
override fun getGhVersionName(): String {
return PackageUtils.getGhVersionName()
}
/**
* 请注意,由于光环后端无法获取 奇游时长套餐 加速时长,这里的 vipStatus不准
* 这里只做参考具体的状态需要奇游sdk提供如果奇游sdk返回失败则以这条接口结果为准
*/
@SuppressLint("CheckResult")
override fun loadVipEntityFromGh(userId: String, callBack: (Any?) -> Unit) {
RetrofitManager.getInstance().newApi.getVipStatus(
userId.ifBlank { UserManager.getInstance().userId },
"gjonline_vip",
true
)
.compose(singleToMain())
.subscribe(object : BiResponse<BaseEntity<VipEntity>>() {
override fun onSuccess(data: BaseEntity<VipEntity>) {
callBack(data.data)
}
override fun onFailure(exception: Exception) {
super.onFailure(exception)
callBack(null)
}
})
}
override fun updateVipEntity(data: Any) {
if (data is VipEntity) {
AcceleratorDataHolder.instance.setVipEntity(data)
}
}
override fun clear() {
AcceleratorDataHolder.instance.clear()
}
}

View File

@ -7,9 +7,6 @@ import com.gh.gamecenter.feature.provider.IRegionSettingHelperProvider
@com.therouter.inject.ServiceProvider
class RegionSettingHelperProviderImpl : IRegionSettingHelperProvider {
override fun shouldThisGameDisplayMirrorInfo(gameId: String): Boolean {
return RegionSettingHelper.shouldThisGameDisplayMirrorInfo(gameId)
}
override fun getMirrorPosition(gameId: String): Int {
return RegionSettingHelper.getMirrorPosition(gameId)

View File

@ -0,0 +1,47 @@
package com.gh.common.provider
import android.annotation.SuppressLint
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.core.provider.IWechatPayResultProvider
import com.gh.gamecenter.feature.entity.OrderEntity
import com.gh.gamecenter.feature.eventbus.EBPayState
import com.halo.assistant.accelerator.repository.AccelerationRepository
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
import org.greenrobot.eventbus.EventBus
@com.therouter.inject.ServiceProvider
class WechatPayResultProviderImpl : IWechatPayResultProvider {
private val repository = AccelerationRepository.newInstance()
@SuppressLint("CheckResult")
override fun onPayComplete(nonceStr: String, order: Any?) {
val orderEntity = order as? OrderEntity
repository.getWechatPayResult(nonceStr)
.compose(singleToMain())
.subscribe({
// 支付成功
EventBus.getDefault().post(EBPayState.PaySuccess)
// 先刷新本地状态,支付成功
AcceleratorDataHolder.instance.handleUserRechargeSuccess(orderEntity)
SensorsBridge.trackMemberRechargeResult(
AccelerationRepository.PAYMENT_TYPE_WECHAT,
orderEntity?.setMenuName ?: "",
orderEntity?.paymentAmount ?: "",
AccelerationRepository.RECHARGE_RESULT_SUCCESS
)
}, {
// 支付失败
EventBus.getDefault().post(EBPayState.PayFail)
SensorsBridge.trackMemberRechargeResult(
AccelerationRepository.PAYMENT_TYPE_WECHAT,
orderEntity?.setMenuName ?: "",
orderEntity?.paymentAmount ?: "",
AccelerationRepository.RECHARGE_RESULT_FAILURE
)
})
}
}

View File

@ -2,6 +2,7 @@ package com.gh.common.util
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.retrofit.RetrofitManager
import io.reactivex.schedulers.Schedulers
import okhttp3.ResponseBody
@ -12,15 +13,18 @@ import okhttp3.ResponseBody
object ActivationHelper {
private const val HAS_SENT_ACTIVATED_INFO = "has_sent_activated_info"
private const val SENT_ACTIVATED_INFO_TIME = "sent_activated_info_time"
private const val HAS_SENT_RETENTION_INFO = "has_sent_retention_info"
var mHasSentActivatedInfo = SPUtils.getBoolean(HAS_SENT_ACTIVATED_INFO, false)
private var hasSentActivatedInfo = SPUtils.getBoolean(HAS_SENT_ACTIVATED_INFO, false)
private var hasSentRetentionInfo = SPUtils.getBoolean(HAS_SENT_RETENTION_INFO, false)
/**
* 发送激活信息 (用于推广)
*/
@JvmStatic
fun sendActivationInfo() {
if (!mHasSentActivatedInfo) {
if (!hasSentActivatedInfo) {
RetrofitManager.getInstance()
.api.postActivationInfo()
.subscribeOn(Schedulers.io())
@ -28,8 +32,36 @@ object ActivationHelper {
override fun onResponse(response: ResponseBody?) {
super.onResponse(response)
mHasSentActivatedInfo = true
hasSentActivatedInfo = true
SPUtils.setBoolean(HAS_SENT_ACTIVATED_INFO, true)
SPUtils.setLong(SENT_ACTIVATED_INFO_TIME, System.currentTimeMillis())
}
})
}
}
/**
* 发送次日留存信息 (用于推广)
*/
@JvmStatic
fun sendRetentionInfo() {
if (hasSentActivatedInfo && !hasSentRetentionInfo) {
val activateTimeMillis = SPUtils.getLong(SENT_ACTIVATED_INFO_TIME, 0)
val currentTimeMillis = System.currentTimeMillis()
if (!TimeUtils.isNextDay(activateTimeMillis, currentTimeMillis)) {
return
}
RetrofitManager.getInstance()
.api.postRetentionInfo()
.subscribeOn(Schedulers.io())
.subscribe(object : Response<ResponseBody>() {
override fun onResponse(response: ResponseBody?) {
super.onResponse(response)
hasSentRetentionInfo = true
SPUtils.setBoolean(HAS_SENT_RETENTION_INFO, true)
}
})
}

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

@ -13,7 +13,6 @@ import com.therouter.TheRouter;
import com.gh.ad.AdDelegateHelper;
import com.gh.gamecenter.BuildConfig;
import com.gh.gamecenter.common.constant.Constants;
import com.gh.gamecenter.common.constant.RouteConsts;
import com.gh.gamecenter.common.exposure.meta.MetaUtil;
import com.gh.gamecenter.common.retrofit.BiResponse;
import com.gh.gamecenter.common.utils.SensorsBridge;
@ -112,7 +111,6 @@ public class DataUtils {
try {
HaloApp.getInstance().getContentResolver().insert(Uri.parse("content://com.gh.gamecenter.provider/device"), values);
} catch (Exception exception) {
SentryHelper.INSTANCE.onEvent("DEVICE_INSERT_ERROR", "exception_digest", exception.getLocalizedMessage());
exception.printStackTrace();
}
});
@ -150,6 +148,7 @@ public class DataUtils {
/**
* 获取应用 gid 绑定的实名信息
*/
// TODO 这个方法启动时会被调用多次,后面考虑优化优化
@SuppressLint("CheckResult")
public static void getDeviceCertification(String gid) {
RetrofitManager.getInstance()
@ -196,7 +195,6 @@ public class DataUtils {
// TODO 将 com.gh.gamecenter 改成 BuildConfig.ApplicationID
HaloApp.getInstance().getContentResolver().insert(Uri.parse("content://com.gh.gamecenter.provider/certification"), values);
} catch (Exception exception) {
SentryHelper.INSTANCE.onEvent("CERTIFICATION_INSERT_ERROR", "exception_digest", exception.getLocalizedMessage());
exception.printStackTrace();
}
}

View File

@ -41,7 +41,8 @@ public class DetailDownloadUtils {
/**
* 更新底部下载区域
* @param viewHolder 下载区域的包裹
*
* @param viewHolder 下载区域的包裹
* @param ignoreDownloadEntity 忽略下载实体(往往用于下载异常时)
*/
public static void updateViewHolder(DetailViewHolder viewHolder, boolean ignoreDownloadEntity) {
@ -53,6 +54,10 @@ public class DetailDownloadUtils {
viewHolder.getMultiVersionDownloadTv().setVisibility(View.GONE);
}
viewHolder.setSpeedViewsVisible(false);
// 默认为显示状态
viewHolder.getDownloadPb().setVisibility(View.VISIBLE);
// 根据预置的配置更新 ViewHolder 的状态 (譬如青少年模式、下载内容为空等)
if (updateViewHolderWithPredefinedConfig(viewHolder, gameEntity)) {
return;
@ -97,9 +102,9 @@ public class DetailDownloadUtils {
viewHolder.getLocalDownloadContainer().setVisibility(View.VISIBLE);
}
// 畅玩未安装,且当前下载的默认类型为"普通下载",且用户未安装时,禁用双按钮,禁用畅玩
if (!VHelper.isInstalled(gameEntity.getUniquePackageName())
&& !PackagesManager.isInstalled(gameEntity.getUniquePackageName())
// 1.畅玩未安装/当前游戏配置了加速,且当前下载的默认类型为"普通下载",且用户未安装时,禁用双按钮,禁用畅玩
if ((!VHelper.isInstalled(gameEntity.getUniquePackageName())
&& !PackagesManager.isInstalled(gameEntity.getUniquePackageName()) || gameEntity.getCanSpeed())
&& downloadEntity != null
&& ExtensionsKt.isLocalDownloadInDualDownloadMode(downloadEntity)
) {
@ -107,8 +112,22 @@ public class DetailDownloadUtils {
if (viewHolder.getOverlayTv() != null) {
viewHolder.getOverlayTv().setVisibility(View.GONE);
}
// 此时需要将 本地占位按钮样式改成单条样式
if (viewHolder.getLocalDownloadContainer() != null) {
viewHolder.getLocalDownloadContainer().setBackgroundResource(com.gh.gamecenter.common.R.drawable.bg_common_button_fill_gradient_blue);
}
if (viewHolder.getLocalDownloadTitleTv() != null) {
viewHolder.getLocalDownloadTitleTv().setTextColor(ExtensionsKt.toColor(com.gh.gamecenter.common.R.color.text_aw_primary, viewHolder.getContext()));
}
} else {
viewHolder.getDownloadPb().setVisibility(View.VISIBLE);
// 将占位按钮样式恢复成左半边样式
if (viewHolder.getLocalDownloadContainer() != null) {
viewHolder.getLocalDownloadContainer().setBackgroundResource(com.gh.gamecenter.common.R.drawable.bg_common_button_light_fill_blue);
}
if (viewHolder.getLocalDownloadTitleTv() != null) {
viewHolder.getLocalDownloadTitleTv().setTextColor(ExtensionsKt.toColor(com.gh.gamecenter.common.R.color.text_theme, viewHolder.getContext()));
}
}
}
@ -135,6 +154,11 @@ public class DetailDownloadUtils {
showDualDownloadButton,
downloadEntity
);
if (downloadEntity != null && downloadEntity.getStatus() != DownloadStatus.done) {
// 如果存在未完成的任务,则不显示加速按钮
} else {
viewHolder.checkIfShowSpeedUi(showVGame, showDualDownloadButton);
}
} else {
// 游戏包含多 APK 的情况
viewHolder.getMultiVersionDownloadTv().setText("选择下载你的版本" + (TextUtils.isEmpty(downloadAddWord) ? "" : "-" + downloadAddWord));
@ -219,7 +243,7 @@ public class DetailDownloadUtils {
downloadButton.setText("");
}
} else {
decoratedBtnText = rawBtnText + (containsAddWord? "" : downloadAddWord) + getWrappedDownloadSizeText(viewHolder);
decoratedBtnText = rawBtnText + (containsAddWord ? "" : downloadAddWord) + getWrappedDownloadSizeText(viewHolder);
if (overlayTv != null && downloadButton.getVisibility() != View.GONE) {
if (context.getString(com.gh.gamecenter.feature.R.string.launch).equals(rawBtnText)
@ -261,7 +285,7 @@ public class DetailDownloadUtils {
viewHolder.getLocalDownloadSizeTv().setVisibility(View.GONE);
String size = viewHolder.getGameEntity().getApk().isEmpty() ? "" : viewHolder.getGameEntity().getApk().get(0).getSize();
if (size != null) {
String sizeWithoutDigit = size.replaceAll("(?<=\\d)\\.[0-9]+(?!\\d)", "");
String sizeWithoutDigit = convertSizeString(size);
viewHolder.getLocalDownloadSizeTv().setText(sizeWithoutDigit);
}
@ -601,6 +625,30 @@ public class DetailDownloadUtils {
return (int) Math.ceil(downloadEntity.getPercent());
}
public static String convertSizeString(String sizeString) {
String numberPart;
String indicator;
// Check if the string ends with "MB"
if (sizeString.endsWith("MB")) {
numberPart = sizeString.substring(0, sizeString.length() - 2);
indicator = "MB";
} else if (sizeString.endsWith("G")) {
numberPart = sizeString.substring(0, sizeString.length() - 1);
indicator = "G";
} else {
return sizeString;
}
// Round number
double number = Double.parseDouble(numberPart);
long roundedNumber = Math.round(number);
// Combine rounded number and size indicator
return roundedNumber + indicator;
}
private static boolean handleDownloadButtonAsXapk(DownloadEntity downloadEntity, DownloadButton downloadButton) {
String xapkStatus = downloadEntity.getMeta().get(XapkInstaller.XAPK_UNZIP_STATUS);

View File

@ -18,6 +18,7 @@ import com.gh.common.exposure.ExposureTraceUtils.appendTrace
import com.gh.common.util.EntranceUtils.jumpActivity
import com.gh.common.util.EntranceUtils.jumpActivityCompat
import com.gh.gamecenter.*
import com.gh.gamecenter.ShellActivity.Type
import com.gh.gamecenter.amway.AmwayActivity
import com.gh.gamecenter.category2.CategoryV2Activity
import com.gh.gamecenter.common.base.activity.BaseActivity
@ -55,6 +56,7 @@ import com.gh.gamecenter.gamecollection.detail.GameCollectionDetailActivity
import com.gh.gamecenter.gamecollection.hotlist.GameCollectionHotListActivity
import com.gh.gamecenter.gamecollection.hotlist.GameCollectionListDetailActivity
import com.gh.gamecenter.gamecollection.square.GameCollectionSquareActivity
import com.gh.gamecenter.gamedetail.entity.GameDetailTabEntity
import com.gh.gamecenter.gamedetail.fuli.kaifu.ServersCalendarActivity
import com.gh.gamecenter.gamedetail.fuli.kaifu.ServersSubscribedGameListActivity
import com.gh.gamecenter.gamedetail.history.HistoryApkListActivity
@ -193,6 +195,8 @@ object DirectUtils {
"simulator",
"teen_mode",
"message_center",
"archive",
"my_assets",
)
fun directToLinkPage(
@ -613,6 +617,10 @@ object DirectUtils {
"message_center" -> directToMessageCenter(0, entrance)
"archive" -> directToCloudArchive(context, linkEntity.link ?: "", linkEntity.text ?: "", "", entrance)
"my_assets" -> navigateToMyAssetsPage(context, entrance)
"" -> {
// do nothing
}
@ -846,33 +854,42 @@ object DirectUtils {
entrance: String? = null,
autoDownload: Boolean? = null,
tab: String? = "",
traceEvent: ExposureEvent? = null
traceEvent: ExposureEvent? = null,
from: String? = null
) {
if (id.isEmpty()) return
val bundle = Bundle()
bundle.putString(KEY_ENTRANCE, entrance ?: ENTRANCE_BROWSER)
bundle.putString(KEY_TO, GameDetailActivity::class.java.simpleName)
bundle.putString(KEY_GAMEID, id)
if (!TextUtils.isEmpty(tab)) {
when (tab) {
"comment" -> bundle.putString(KEY_TARGET, EntranceConsts.TAB_TYPE_RATING)
"desc" -> bundle.putString(KEY_TARGET, EntranceConsts.TAB_TYPE_DESC)
"forum" -> bundle.putString(KEY_TARGET, EntranceConsts.TAB_TYPE_BBS)
"zone" -> bundle.putString(KEY_TARGET, EntranceConsts.TAB_TYPE_TRENDS)
val uri = Uri.Builder()
.path(RouteConsts.activity.gameDetailActivity)
.appendQueryParameter(KEY_ENTRANCE, entrance)
.appendQueryParameter(KEY_GAME_ID, id)
.appendQueryParameter(KEY_FROM, from)
.build()
TheRouter
.build(uri.toString())
.fillParams { bundle ->
if (!TextUtils.isEmpty(tab)) {
when (tab) {
"comment" -> bundle.putString(KEY_TARGET, GameDetailTabEntity.TYPE_COMMENT)
"desc" -> bundle.putString(KEY_TARGET, GameDetailTabEntity.TYPE_DETAIL)
"forum" -> bundle.putString(KEY_TARGET, GameDetailTabEntity.TYPE_BBS)
"zone" -> bundle.putString(KEY_TARGET, GameDetailTabEntity.TYPE_ZONE)
}
}
if (traceEvent != null) {
val clickEvent = createEvent(
GameEntity(id = id, name = name),
traceEvent.source,
appendTrace(traceEvent),
ExposureType.CLICK
)
log(clickEvent)
bundle.putParcelable(KEY_TRACE_EVENT, clickEvent)
}
bundle.putBoolean(KEY_AUTO_DOWNLOAD, autoDownload ?: false)
}
}
if (traceEvent != null) {
val clickEvent = createEvent(
GameEntity(id = id, name = name),
traceEvent.source,
appendTrace(traceEvent),
ExposureType.CLICK
)
log(clickEvent)
bundle.putParcelable(KEY_TRACE_EVENT, clickEvent)
}
bundle.putBoolean(KEY_AUTO_DOWNLOAD, autoDownload ?: false)
jumpActivity(context, bundle)
.navigation(context)
}
/**
@ -903,7 +920,7 @@ object DirectUtils {
bundle.putString(KEY_ENTRANCE, entrance)
bundle.putString(KEY_GAMEID, id)
bundle.putBoolean(KEY_OPEN_VIDEO_STREAMING, true)
bundle.putString(KEY_TARGET, EntranceConsts.TAB_TYPE_DESC)
bundle.putString(KEY_TARGET, GameDetailTabEntity.TYPE_DETAIL)
jumpActivity(context, bundle)
}
@ -911,15 +928,21 @@ object DirectUtils {
fun directToGameDetail(
context: Context,
id: String,
defaultTab: String = EntranceConsts.TAB_TYPE_DESC,
defaultTab: String = GameDetailTabEntity.TYPE_DETAIL,
entrance: String? = null
) {
val bundle = Bundle()
bundle.putString(KEY_TO, GameDetailActivity::class.java.name)
bundle.putString(KEY_ENTRANCE, entrance)
bundle.putString(KEY_GAMEID, id)
bundle.putString(KEY_TARGET, defaultTab)
jumpActivity(context, bundle)
val uri = Uri.Builder()
.path(RouteConsts.activity.gameDetailActivity)
.appendQueryParameter(KEY_ENTRANCE, entrance)
.appendQueryParameter(KEY_GAME_ID, id)
.build()
TheRouter
.build(uri.toString())
.fillParams { bundle ->
bundle.putString(KEY_TARGET, defaultTab)
}
.navigation(context)
}
// 专栏
@ -1107,9 +1130,15 @@ object DirectUtils {
@JvmStatic
fun directToWebView(context: Context, url: String, entrance: String? = null) {
directToWebView(context, url, entrance, null)
}
@JvmStatic
fun directToWebView(context: Context, url: String, entrance: String? = null, title: String? = null) {
if (url.isEmpty()) return
val bundle = Bundle()
bundle.putString(KEY_ENTRANCE, entrance ?: ENTRANCE_BROWSER)
bundle.putString(EntranceConsts.KEY_GAMENAME, title)
if (url.contains("android_page_type=singleton")) {
bundle.putString(KEY_TO, SingletonWebActivity::class.java.simpleName)
} else {
@ -1505,8 +1534,8 @@ object DirectUtils {
if (categoryId.isEmpty()) return
val bundle = Bundle()
bundle.putString(KEY_TO, CategoryV2Activity::class.java.name)
bundle.putString(KEY_CATEGORY_ID, categoryId)
bundle.putString(KEY_CATEGORY_TITLE, categoryTitle)
bundle.putString(KEY_PAGE_ID, categoryId)
bundle.putString(KEY_PAGE_NAME, categoryTitle)
bundle.putString(KEY_ENTRANCE, BaseActivity.mergeEntranceAndPath(entrance, path))
if (exposureEvent != null) bundle.putParcelableArrayList(
KEY_EXPOSURE_SOURCE_LIST,
@ -1569,7 +1598,7 @@ object DirectUtils {
response?.apply {
if (zone.status == "on") {
if (zone.style == "link") {
directToGameDetail(context, gameId, EntranceConsts.TAB_TYPE_TRENDS, entrance)
directToGameDetail(context, gameId, GameDetailTabEntity.TYPE_ZONE, entrance)
} else {
directToWebView(context, url, entrance)
}
@ -1682,7 +1711,7 @@ object DirectUtils {
fun directToHelpAndFeedback(context: Context, bundle: Bundle? = null) {
TheRouter.build(RouteConsts.activity.helpAndFeedbackActivity)
.fillParams {
it.putAll(bundle)
bundle?.run { it.putAll(this) }
}
.navigation(context)
}
@ -2294,4 +2323,32 @@ object DirectUtils {
.withBoolean(KEY_DISPLAY_TYPE, isLogoutStyle)
.navigation()
}
// 跳转云存档详情页
@JvmStatic
fun directToCloudArchive(
context: Context,
gameId: String,
gameName: String,
configUrl: String = "",
entrance: String = ""
) {
val bundle = Bundle()
val gameEntity = GameEntity(id = gameId, name = gameName)
bundle.putParcelable(KEY_GAME_ENTITY, gameEntity)
bundle.putString(KEY_ARCHIVE_CONFIG_URL, configUrl)
bundle.putString(KEY_ENTRANCE, entrance)
bundle.putBoolean(KEY_USE_ALTERNATIVE_LAYOUT, true)
context.startActivity(ShellActivity.getIntent(context, Type.CLOUD_ARCHIVE, bundle))
}
@JvmStatic
fun navigateToMyAssetsPage(context: Context, entrance: String?) {
if (CheckLoginUtils.isLogin()) {
TheRouter.build(RouteConsts.activity.myAssetsActivity).navigation(context)
} else {
CheckLoginUtils.checkLogin(context, entrance, null)
}
}
}

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

@ -61,7 +61,7 @@ object DownloadItemUtils {
gameEntity: GameEntity,
downloadEntity: DownloadEntity,
adapter: RecyclerView.Adapter<out RecyclerView.ViewHolder?>?,
index: Int
index: Int,
) {
if (gameEntity.id != downloadEntity.gameId) {
adapter?.notifyItemChanged(index)
@ -139,7 +139,7 @@ object DownloadItemUtils {
context: Context,
gameEntity: GameEntity,
holder: GameViewHolder,
hideDownloadBtnIfNoAvailableContent: Boolean
hideDownloadBtnIfNoAvailableContent: Boolean,
) {
updateItem(
context = context,
@ -155,7 +155,7 @@ object DownloadItemUtils {
context: Context,
gameEntity: GameEntity,
holder: GameViewHolder,
briefStyle: String?
briefStyle: String?,
) {
updateItem(
context = context,
@ -176,7 +176,8 @@ object DownloadItemUtils {
pluginLocation: PluginLocation? = PluginLocation.only_game,
hideDownloadBtnIfNoAvailableContent: Boolean = false,
briefStyle: String? = null,
isShowRecommendStar: Boolean = false
isShowRecommendStar: Boolean = false,
listener: DownloadButton.OnUpdateListener? = null,
) {
holder.gameDownloadBtn.putObject(gameEntity)
@ -189,7 +190,8 @@ object DownloadItemUtils {
holder.gameDownloadBtn,
gameEntity,
hideDownloadBtnIfNoAvailableContent,
pluginLocation
pluginLocation,
listener
)
return
}
@ -210,7 +212,8 @@ object DownloadItemUtils {
holder.gameDownloadBtn,
gameEntity,
hideDownloadBtnIfNoAvailableContent,
pluginLocation
pluginLocation,
listener
)
}
@ -219,7 +222,8 @@ object DownloadItemUtils {
downloadBtn: DownloadButton,
gameEntity: GameEntity,
hideDownloadBtnIfNoAvailableContent: Boolean = false,
pluginLocation: PluginLocation? = PluginLocation.only_game
pluginLocation: PluginLocation? = PluginLocation.only_game,
listener: DownloadButton.OnUpdateListener?,
) {
// 控制是否显示下载按钮
downloadBtn.goneIf(context.getString(R.string.app_name) == gameEntity.name)
@ -227,6 +231,7 @@ object DownloadItemUtils {
if (SPUtils.getBoolean(Constants.SP_TEENAGER_MODE) || gameEntity.isSpecialDownload()) {
downloadBtn.text = "查看"
downloadBtn.buttonStyle = DownloadButton.ButtonStyle.TEENAGER_MODE
listener?.completion(downloadBtn.text)
return
}
if (gameEntity.isReservable) {
@ -239,6 +244,7 @@ object DownloadItemUtils {
buttonStyle = DownloadButton.ButtonStyle.RESERVED
}
}
listener?.completion(downloadBtn.text)
return
}
if (RegionSettingHelper.getGameH5DownloadByGameId(gameEntity.id) != null) {
@ -248,6 +254,7 @@ object DownloadItemUtils {
setBackgroundResource(com.gh.gamecenter.common.R.drawable.download_button_normal_style)
setTextColor(com.gh.gamecenter.common.R.color.white.toColor(context))
}
listener?.completion(downloadBtn.text)
return
}
if (gameEntity.isMiniGame()) {
@ -265,6 +272,7 @@ object DownloadItemUtils {
text = context.getString(com.gh.gamecenter.feature.R.string.quick_play)
}
}
listener?.completion(downloadBtn.text)
return
}
if (gameEntity.getApk().isEmpty() || gameEntity.downloadOffStatus != null) {
@ -296,6 +304,7 @@ object DownloadItemUtils {
downloadBtn.isClickable = false
}
}
listener?.completion(downloadBtn.text)
} else if (gameEntity.getApk().size == 1) {
// 来自于下载管理的实体快照
val entityFromDownloadManager = DownloadManager.getInstance().getDownloadEntitySnapshot(gameEntity)
@ -354,43 +363,60 @@ object DownloadItemUtils {
downloadBtn.apply {
when (downloadEntity.status) {
DownloadStatus.done -> {
if (downloadEntity.isSimulatorGame() && gameEntity.simulator != null) {
GameUtils.setDownloadBtnStatus(context, gameEntity, downloadBtn, pluginLocation)
} else if (isVGamePreferred) {
buttonStyle =
if (PackagesManager.isCanUpdate(
downloadEntity.gameId,
downloadEntity.packageName,
asVGame = true
)
) {
setText(com.gh.gamecenter.feature.R.string.update)
DownloadButton.ButtonStyle.NORMAL
} else {
setText(com.gh.gamecenter.feature.R.string.launch)
DownloadButton.ButtonStyle.LAUNCH_OR_OPEN
}
} else {
val xapkStatus = downloadEntity.meta[XapkInstaller.XAPK_UNZIP_STATUS]
if (XapkUnzipStatus.SUCCESS.name == xapkStatus && isInstalling(downloadEntity.path)) {
val xapkStatus = downloadEntity.meta[XapkInstaller.XAPK_UNZIP_STATUS]
when {
downloadEntity.isSimulatorGame() && gameEntity.simulator != null -> {
GameUtils.setDownloadBtnStatus(
context,
gameEntity,
downloadBtn,
pluginLocation,
listener
)
}
isVGamePreferred -> {
buttonStyle =
if (PackagesManager.isCanUpdate(
downloadEntity.gameId,
downloadEntity.packageName,
asVGame = true
)
) {
setText(com.gh.gamecenter.feature.R.string.update)
DownloadButton.ButtonStyle.NORMAL
} else {
setText(com.gh.gamecenter.feature.R.string.launch)
DownloadButton.ButtonStyle.LAUNCH_OR_OPEN
}
listener?.completion(downloadBtn.text)
}
XapkUnzipStatus.SUCCESS.name == xapkStatus && isInstalling(downloadEntity.path) -> {
progress = 100
setText(com.gh.gamecenter.feature.R.string.installing)
buttonStyle = DownloadButton.ButtonStyle.INSTALL_NORMAL
listener?.completion(downloadBtn.text)
return
}
if (XapkUnzipStatus.UNZIPPING.name == xapkStatus) {
XapkUnzipStatus.UNZIPPING.name == xapkStatus -> {
val percent = downloadEntity.meta[XapkInstaller.XAPK_UNZIP_PERCENT]
progress = (java.lang.Float.valueOf(percent) * 10).toInt()
text = "$percent%"
buttonStyle = DownloadButton.ButtonStyle.XAPK_UNZIPPING
return
} else if (XapkUnzipStatus.FAILURE.name == xapkStatus) {
setText(com.gh.gamecenter.feature.R.string.install)
buttonStyle = DownloadButton.ButtonStyle.INSTALL_NORMAL
listener?.completion(downloadBtn.text)
return
}
if (PackagesManager.isInstalled(downloadEntity.packageName) && !downloadEntity.isUpdate) {
XapkUnzipStatus.FAILURE.name == xapkStatus -> {
setText(com.gh.gamecenter.feature.R.string.install)
buttonStyle = DownloadButton.ButtonStyle.INSTALL_NORMAL
listener?.completion(downloadBtn.text)
return
}
PackagesManager.isInstalled(downloadEntity.packageName) && !downloadEntity.isUpdate -> {
// 双下载按钮快速安装时存在已下载的安装包过时,需要重新下载的情况
if (PackagesManager.isCanUpdate(
downloadEntity.gameId,
@ -403,9 +429,13 @@ object DownloadItemUtils {
buttonStyle = DownloadButton.ButtonStyle.LAUNCH_OR_OPEN
setText(com.gh.gamecenter.feature.R.string.launch)
}
} else {
listener?.completion(downloadBtn.text)
}
else -> {
buttonStyle = DownloadButton.ButtonStyle.INSTALL_NORMAL
setText(com.gh.gamecenter.feature.R.string.install)
listener?.completion(downloadBtn.text)
}
}
buttonStyle =
@ -422,25 +452,28 @@ object DownloadItemUtils {
DownloadStatus.subscribe,
DownloadStatus.diskisfull,
DownloadStatus.diskioerror,
DownloadStatus.overflow -> {
DownloadStatus.overflow,
-> {
buttonStyle = DownloadButton.ButtonStyle.NORMAL
setText(com.gh.gamecenter.feature.R.string.resume)
listener?.completion(downloadBtn.text)
}
DownloadStatus.cancel -> {
GameUtils.setDownloadBtnStatus(context, gameEntity, downloadBtn, pluginLocation)
GameUtils.setDownloadBtnStatus(context, gameEntity, downloadBtn, pluginLocation, listener)
}
else -> {
// do nothing
listener?.completion(downloadBtn.text)
}
}
}
} else {
GameUtils.setDownloadBtnStatus(context, gameEntity, downloadBtn, pluginLocation)
GameUtils.setDownloadBtnStatus(context, gameEntity, downloadBtn, pluginLocation, listener)
}
} else {
GameUtils.setDownloadBtnStatus(context, gameEntity, downloadBtn, pluginLocation)
GameUtils.setDownloadBtnStatus(context, gameEntity, downloadBtn, pluginLocation, listener)
}
}
@ -450,7 +483,7 @@ object DownloadItemUtils {
holder: GameViewHolder,
gameEntity: GameEntity,
briefStyle: String?,
isShowRecommendStar: Boolean = false
isShowRecommendStar: Boolean = false,
) {
updateItemViewStatus(holder, briefStyle, gameEntity.columnRecommend, isShowRecommendStar)
val downloadEntity = DownloadManager.getInstance().getDownloadEntitySnapshot(gameEntity)
@ -475,7 +508,7 @@ object DownloadItemUtils {
holder: GameViewHolder,
gameEntity: GameEntity,
briefStyle: String?,
isShowRecommendStar: Boolean = false
isShowRecommendStar: Boolean = false,
) {
val entryMap = gameEntity.getEntryMap()
var downloadEntity: DownloadEntity? = null
@ -503,23 +536,30 @@ object DownloadItemUtils {
context: Context,
holder: GameViewHolder,
downloadEntity: DownloadEntity,
isMultiVersion: Boolean = false
isMultiVersion: Boolean = false,
) {
when (downloadEntity.status) {
DownloadStatus.redirected,
DownloadStatus.downloading -> {
DownloadStatus.downloading,
-> {
if (isMultiVersion) {
holder.gameDownloadBtn.buttonStyle = DownloadButton.ButtonStyle.NORMAL
val darkMode = (holder.gameDownloadTips?.getTag(com.gh.gamecenter.common.R.string.is_dark_mode_on_id) as? Boolean) ?: false
val darkMode =
(holder.gameDownloadTips?.getTag(com.gh.gamecenter.common.R.string.is_dark_mode_on_id) as? Boolean)
?: false
val isDarkModeChanged = DarkModeUtils.isDarkModeOn(context) != darkMode
if (holder.gameDownloadTips?.visibility == View.GONE || holder.gameDownloadTips?.isAnimating == false || isDarkModeChanged) {
holder.gameDownloadTips?.visibility = View.VISIBLE
holder.gameDownloadTips?.setDownloadTipsAnimation(true)
}
holder.gameDownloadTips?.setTag(com.gh.gamecenter.common.R.string.is_dark_mode_on_id, DarkModeUtils.isDarkModeOn(context))
holder.gameDownloadTips?.setTag(
com.gh.gamecenter.common.R.string.is_dark_mode_on_id,
DarkModeUtils.isDarkModeOn(context)
)
} else {
holder.gameDownloadTips?.visibility = View.GONE
holder.gameDownloadBtn.buttonStyle = DownloadButton.ButtonStyle.DOWNLOADING_NORMAL
holder.gameDownloadBtn.buttonStyle =
DownloadButton.ButtonStyle.DOWNLOADING_NORMAL
holder.gameDownloadBtn.progress = (downloadEntity.percent * 10).toInt()
holder.gameDownloadBtn.text = downloadEntity.percent.toString() + "%"
}
@ -540,13 +580,15 @@ object DownloadItemUtils {
DownloadStatus.diskioerror,
DownloadStatus.diskisfull,
DownloadStatus.subscribe,
DownloadStatus.overflow -> {
DownloadStatus.overflow,
-> {
if (isMultiVersion) {
holder.gameDownloadTips?.visibility = View.VISIBLE
holder.gameDownloadTips?.setDownloadTipsAnimation(false)
}
holder.gameDownloadBtn.buttonStyle = DownloadButton.ButtonStyle.NORMAL
holder.gameDownloadBtn.text = context.getString(com.gh.gamecenter.feature.R.string.resume)
holder.gameDownloadBtn.text =
context.getString(com.gh.gamecenter.feature.R.string.resume)
}
DownloadStatus.done -> {
@ -579,7 +621,7 @@ object DownloadItemUtils {
holder: GameViewHolder,
briefStyle: String?,
recommendStyle: LinkEntity?,
isShowRecommendStar: Boolean = false
isShowRecommendStar: Boolean = false,
) {
holder.gameDownloadTips?.visibility = View.GONE
// 推荐指数优先,现暂时为游戏单详情列表游戏使用
@ -618,7 +660,11 @@ object DownloadItemUtils {
}
// 缺省情况下回落到游戏简介
if (TextUtils.isEmpty(briefStyle) || briefStyle!!.contains("brief") || briefStyle.contains("recommend")) {
if (TextUtils.isEmpty(briefStyle)
|| briefStyle!!.contains("brief")
|| briefStyle.contains("recommend")
|| briefStyle.contains("test&appointment")
) {
holder.gameDes?.visibility = View.VISIBLE
} else {
holder.gameDes?.visibility = View.GONE
@ -634,7 +680,7 @@ object DownloadItemUtils {
adapter: RecyclerView.Adapter<out RecyclerView.ViewHolder?>?,
entrance: String,
location: String,
sourceEntrance: String = "其他"
sourceEntrance: String = "其他",
) {
setOnClickListener(
context,
@ -723,7 +769,7 @@ object DownloadItemUtils {
traceEvent: ExposureEvent?,
clickCallback: EmptyCallback?,
refreshCallback: EmptyCallback?,
allStateClickCallback: EmptyCallback?
allStateClickCallback: EmptyCallback?,
) {
// 为 downloadButton 添加游戏实体,供点击的时候上报用
downloadBtn.putObject(gameEntity)
@ -821,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()
@ -959,9 +1007,10 @@ object DownloadItemUtils {
entrance: String,
location: String,
traceEvent: ExposureEvent? = null,
refreshCallback: EmptyCallback? = null
refreshCallback: EmptyCallback? = null,
) {
val str = if (downloadBtn is DownloadButton) downloadBtn.text else context.getString(com.gh.gamecenter.feature.R.string.download)
val str =
if (downloadBtn is DownloadButton) downloadBtn.text else context.getString(com.gh.gamecenter.feature.R.string.download)
if (gameEntity.getApk().isEmpty()) return
val apk = gameEntity.getApk().safelyGetInRelease(0) ?: return
@ -980,7 +1029,16 @@ object DownloadItemUtils {
addHandler(CheckDownloadHandler())
}
.setProcessEndCallback(gameEntity.id) { asVGame, isSubscribe ->
download(context, gameEntity, downloadBtn, entrance, location, asVGame, isSubscribe as Boolean, traceEvent)
download(
context,
gameEntity,
downloadBtn,
entrance,
location,
asVGame,
isSubscribe as Boolean,
traceEvent
)
}
.buildHandlerChain()
?.handleRequest(context, gameEntity, shouldPerformAsVGame)
@ -999,7 +1057,16 @@ object DownloadItemUtils {
addHandler(CheckDownloadHandler())
}
.setProcessEndCallback(gameEntity.id) { asVGame, isSubscribe ->
download(context, gameEntity, downloadBtn, entrance, location, asVGame, isSubscribe as Boolean, traceEvent)
download(
context,
gameEntity,
downloadBtn,
entrance,
location,
asVGame,
isSubscribe as Boolean,
traceEvent
)
}
.buildHandlerChain()
?.handleRequest(context, gameEntity, shouldPerformAsVGame)
@ -1018,7 +1085,16 @@ object DownloadItemUtils {
addHandler(CheckDownloadHandler())
}
.setProcessEndCallback(gameEntity.id) { asVGame, isSubscribe ->
download(context, gameEntity, downloadBtn, entrance, location, asVGame, isSubscribe as Boolean, traceEvent)
download(
context,
gameEntity,
downloadBtn,
entrance,
location,
asVGame,
isSubscribe as Boolean,
traceEvent
)
}
.buildHandlerChain()
?.handleRequest(context, gameEntity, shouldPerformAsVGame)
@ -1040,15 +1116,17 @@ object DownloadItemUtils {
val downloadEntity = SimulatorGameManager.findDownloadEntityByUrl(apk.url)
com.gh.gamecenter.common.utils.NewFlatLogUtils.logGameInstall(
gameId = downloadEntity?.gameId ?: "",
gameName = downloadEntity?.name ?: "",
gameId = gameEntity.id,
gameName = gameEntity.name ?: "",
trigger = "主动安装"
)
SensorsBridge.trackInstallGameClick(
gameId = downloadEntity?.gameId ?: "",
gameName = downloadEntity?.name ?: "",
action = "主动安装"
gameId = gameEntity.id,
gameName = gameEntity.name ?: "",
action = "主动安装",
isDspGame = gameEntity.isDspGame,
dspAdId = gameEntity.dspAdId
)
if (gameEntity.simulator != null) {
@ -1174,7 +1252,7 @@ object DownloadItemUtils {
location: String,
asVGame: Boolean,
isSubscribe: Boolean,
traceEvent: ExposureEvent?
traceEvent: ExposureEvent?,
) {
if (gameEntity.getApk().isEmpty()) return
@ -1218,7 +1296,7 @@ object DownloadItemUtils {
entrance: String,
location: String,
isSubscribe: Boolean,
traceEvent: ExposureEvent?
traceEvent: ExposureEvent?,
) {
val msg = FileUtils.isCanDownload(context, gameEntity.getApk().firstOrNull()?.size ?: "")
if (TextUtils.isEmpty(msg)) {
@ -1245,7 +1323,7 @@ object DownloadItemUtils {
gameEntity: GameEntity,
position: Int,
adapter: RecyclerView.Adapter<out RecyclerView.ViewHolder?>?,
refreshCallback: EmptyCallback?
refreshCallback: EmptyCallback?,
) {
val apkEntity = gameEntity.getApk().firstOrNull()
val downloadEntity = DownloadManager.getInstance().getDownloadEntitySnapshot(gameEntity)
@ -1288,7 +1366,7 @@ object DownloadItemUtils {
location: String,
asVGame: Boolean,
isSubscribe: Boolean,
traceEvent: ExposureEvent?
traceEvent: ExposureEvent?,
) {
// 执行更新操作前,先清理历史下载任务,避免冲突

View File

@ -14,15 +14,16 @@ import com.gh.gamecenter.common.base.GlobalActivityManager.getLastPageEntity
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.callback.ConfirmListener
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.entity.SimpleGameEntity
import com.gh.gamecenter.common.entity.SuggestType
import com.gh.gamecenter.common.eventbus.EBShowDialog
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.utils.NewFlatLogUtils
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.dsp.DspReportHelper
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.feature.entity.CustomPageTrackData
import com.gh.gamecenter.feature.entity.GameEntity
@ -281,11 +282,10 @@ object DownloadObserver {
private fun performDownloadCompleteAction(
downloadEntity: DownloadEntity,
downloadManager: DownloadManager
downloadManager: DownloadManager,
) {
if (downloadEntity.name.contains(mApplication.getString(R.string.app_name))) {
statDoneEvent(downloadEntity)
MtaHelper.onEvent("软件更新", "下载完成")
// 会有 ActivityNotFoundException 异常catch 掉不管了
tryWithDefaultCatch {
if (Constants.SILENT_UPDATE != downloadEntity.getMetaExtra(Constants.EXTRA_DOWNLOAD_TYPE)) {
@ -298,7 +298,9 @@ object DownloadObserver {
SensorsBridge.trackInstallGameClick(
gameName = downloadEntity.name,
gameId = downloadEntity.gameId,
action = "自动安装"
action = "自动安装",
isDspGame = downloadEntity.getMetaExtra(Constants.DSP_GAME) == "true",
dspAdId = downloadEntity.getMetaExtra(Constants.DSP_AD_ID)
)
// TODO 在 Android 11 上没有授权安装未知应用的权限前第一次调用这个方法系统会杀掉我们的进程...
@ -395,7 +397,9 @@ object DownloadObserver {
SensorsBridge.trackInstallGameClick(
gameId = downloadEntity.gameId,
gameName = downloadEntity.name,
action = "自动安装"
action = "自动安装",
isDspGame = downloadEntity.getMetaExtra(Constants.DSP_GAME) == "true",
dspAdId = downloadEntity.getMetaExtra(Constants.DSP_AD_ID)
)
PackageInstaller.install(mApplication, downloadEntity, false, ignoreAsVGame = false)
}
@ -497,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,
@ -504,7 +509,8 @@ object DownloadObserver {
gameVersion = downloadEntity.versionName ?: "",
isPlatformRecommend = isPlatformRecommend,
adIconActive = downloadEntity.meta[Constants.AD_ICON_ACTIVE].toBoolean(),
isAdData = downloadEntity.meta[Constants.IS_AD_DATA].toBoolean()
isAdData = downloadEntity.meta[Constants.IS_AD_DATA].toBoolean(),
downloadUrl = downloadEntity.meta[Constants.DOWNLOAD_URL]
),
downloadEntity.platform,
downloadEntity.exposureTrace,
@ -529,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) {
@ -556,24 +564,83 @@ object DownloadObserver {
} else {
arrayOf()
}
SensorsBridge.trackEventWithExposureSource(
"DownloadProcessFinish",
exposureEvent?.source,
"game_id", downloadEntity.gameId,
"game_name", downloadEntity.meta[Constants.GAME_NAME] ?: "",
"game_type", downloadEntity.meta[Constants.GAME_CATEGORY_IN_CHINESE] ?: "",
"game_label", downloadEntity.tags.joinToString(","),
"game_schema_type", if (downloadEntity.getMetaExtra(Constants.KEY_BIT) == "32") "32位" else "64位",
"page_name", getCurrentPageEntity().pageName,
"page_id", getCurrentPageEntity().pageId,
"page_business_id", getCurrentPageEntity().pageBusinessId,
"last_page_name", getLastPageEntity().pageName,
"last_page_id", getLastPageEntity().pageId,
"last_page_business_id", getLastPageEntity().pageBusinessId,
"download_status", downloadEntity.meta[Constants.DOWNLOAD_STATUS_IN_CHINESE] ?: "",
"download_type", if (downloadEntity.asVGame()) "畅玩下载" else "本地下载",
*kvs
)
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()
if (downloadEntity.getMetaExtra(Constants.DSP_GAME) != "true") {
SensorsBridge.trackEventWithExposureSource(
"DownloadProcessFinish",
exposureEvent?.source,
"game_id",
downloadEntity.gameId,
"game_name",
downloadEntity.meta[Constants.GAME_NAME] ?: "",
"game_type",
downloadEntity.meta[Constants.GAME_CATEGORY_IN_CHINESE] ?: "",
"game_label",
downloadEntity.tags.joinToString(","),
"game_schema_type",
if (downloadEntity.getMetaExtra(Constants.KEY_BIT) == "32") "32位" else "64位",
"page_name",
getCurrentPageEntity().pageName,
"page_id",
getCurrentPageEntity().pageId,
"page_business_id",
getCurrentPageEntity().pageBusinessId,
"last_page_name",
getLastPageEntity().pageName,
"last_page_id",
getLastPageEntity().pageId,
"last_page_business_id",
getLastPageEntity().pageBusinessId,
"download_status",
downloadEntity.meta[Constants.DOWNLOAD_STATUS_IN_CHINESE] ?: "",
"download_type",
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",
pushLinkId,
*kvs
)
} else {
val searchContent = downloadEntity.getMetaExtra(Constants.SEARCH_KEY)
SensorsBridge.trackEventWithExposureSource(
"DspAdDownloadFinish",
exposureEvent?.source,
"ad_id", downloadEntity.getMetaExtra(Constants.DSP_AD_ID),
"search_content", searchContent,
"game_column_name", downloadEntity.getMetaExtra(Constants.SUBJECT_NAME),
"game_column_id", downloadEntity.getMetaExtra(Constants.SUBJECT_ID),
"game_id", downloadEntity.gameId,
"game_name", downloadEntity.meta[Constants.GAME_NAME] ?: "",
"game_type", downloadEntity.meta[Constants.GAME_CATEGORY_IN_CHINESE] ?: "",
"game_label", downloadEntity.tags.joinToString(","),
"game_schema_type", if (downloadEntity.getMetaExtra(Constants.KEY_BIT) == "32") "32位" else "64位",
"page_name", getCurrentPageEntity().pageName,
"page_id", getCurrentPageEntity().pageId,
"page_business_id", getCurrentPageEntity().pageBusinessId,
"last_page_name", getLastPageEntity().pageName,
"last_page_id", getLastPageEntity().pageId,
"last_page_business_id", getLastPageEntity().pageBusinessId,
"download_status", downloadEntity.meta[Constants.DOWNLOAD_STATUS_IN_CHINESE] ?: "",
"download_type", if (downloadEntity.asVGame()) "畅玩下载" else "本地下载",
"is_from_push_notifications", isFromPush,
"message_id", pushMessageId,
"link_id", pushLinkId,
"location", if (searchContent.isEmpty()) "自定义页面" else "游戏搜索结果列表",
*kvs
)
DspReportHelper.report(downloadEntity.getMetaExtra(Constants.DOWNLOAD_URL))
}
}
DataCollectionUtils.uploadDownload(mApplication, downloadEntity, "完成")

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

@ -25,7 +25,10 @@ object GameUtils {
/**
* 去除与重复sourceList相同的数据
*/
fun removeDuplicateData(sourceList: MutableList<GameEntity>?, rawList: MutableList<GameEntity>?): MutableList<GameEntity>? {
fun removeDuplicateData(
sourceList: MutableList<GameEntity>?,
rawList: MutableList<GameEntity>?
): MutableList<GameEntity>? {
if (sourceList.isNullOrEmpty() || rawList.isNullOrEmpty()) {
return rawList
}
@ -52,7 +55,8 @@ object GameUtils {
context: Context,
gameEntity: GameEntity,
downloadBtn: DownloadButton,
pluginLocation: PluginLocation?
pluginLocation: PluginLocation?,
listener: DownloadButton.OnUpdateListener? = null
) {
// getDownloadBtnText 里包括查询数据库、根据包名读取包体 meta 信息等
lightWeightIoExecutor.execute {
@ -73,6 +77,7 @@ object GameUtils {
} else {
downloadBtn.buttonStyle = DownloadButton.ButtonStyle.NORMAL
}
listener?.completion(downloadBtn.text)
}
}
}
@ -85,11 +90,13 @@ object GameUtils {
*/
@WorkerThread
@JvmStatic
fun getDownloadBtnText(context: Context,
gameEntity: GameEntity,
isFromList: Boolean,
fixedAsVGame: Boolean,
pluginLocation: PluginLocation?): String {
fun getDownloadBtnText(
context: Context,
gameEntity: GameEntity,
isFromList: Boolean,
fixedAsVGame: Boolean,
pluginLocation: PluginLocation?
): String {
if (gameEntity.getApk().size > 1) {
return ""
}
@ -104,7 +111,12 @@ object GameUtils {
var gh_id: Any?
apkFor@ for (apkEntity in gameEntity.getApk()) {
val isInstalledLocally = PackagesManager.isInstalled(apkEntity.packageName)
val isInstalledLocally =
if (gameEntity.isDspGame) {
PackageHelper.validLocalPackageNameSet.contains(apkEntity.packageName)
} else {
PackagesManager.isInstalled(apkEntity.packageName)
}
// filter by packageName
val settings = Config.getSettings()
@ -138,7 +150,8 @@ object GameUtils {
} else if (!isFromList) {
if (!performAsVGame
&& gameEntity.isDualBtnModeEnabled()
&& downloadEntity?.isVGameDownloadInDualDownloadMode() == true) {
&& downloadEntity?.isVGameDownloadInDualDownloadMode() == true
) {
// 下载的任务是由畅玩触发的,并且双下载按钮启用,游戏详情页不需判定为需要安装
downloadEntity = null
} else if (performAsVGame && downloadEntity?.isLocalDownloadInDualDownloadMode() == true) {

View File

@ -14,25 +14,26 @@ import androidx.core.content.ContextCompat;
import com.gh.gamecenter.BuildConfig;
import com.gh.gamecenter.R;
import com.gh.gamecenter.adapter.LibaoDetailAdapter;
import com.gh.gamecenter.common.constant.Constants;
import com.gh.gamecenter.common.entity.NotificationUgc;
import com.gh.gamecenter.common.eventbus.EBReuse;
import com.gh.gamecenter.common.retrofit.JSONObjectResponse;
import com.gh.gamecenter.common.retrofit.Response;
import com.gh.gamecenter.common.utils.DialogHelper;
import com.gh.gamecenter.common.utils.ExtensionsKt;
import com.gh.gamecenter.common.utils.NotificationHelper;
import com.gh.gamecenter.common.utils.SensorsBridge;
import com.gh.gamecenter.core.utils.ToastUtils;
import com.gh.gamecenter.core.utils.UrlFilterUtils;
import com.gh.gamecenter.eventbus.EBUISwitch;
import com.gh.gamecenter.feature.entity.ApkEntity;
import com.gh.gamecenter.feature.entity.LibaoEntity;
import com.gh.gamecenter.feature.entity.LibaoStatusEntity;
import com.gh.gamecenter.feature.entity.MeEntity;
import com.gh.gamecenter.common.entity.NotificationUgc;
import com.gh.gamecenter.feature.entity.UserDataLibaoEntity;
import com.gh.gamecenter.common.eventbus.EBReuse;
import com.gh.gamecenter.eventbus.EBUISwitch;
import com.gh.gamecenter.feature.utils.PlatformUtils;
import com.gh.gamecenter.geetest.GeetestUtils;
import com.gh.gamecenter.login.user.UserManager;
import com.gh.gamecenter.common.retrofit.JSONObjectResponse;
import com.gh.gamecenter.common.retrofit.Response;
import com.gh.gamecenter.retrofit.RetrofitManager;
import com.halo.assistant.HaloApp;
import com.lightgame.utils.Utils;
@ -323,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));
@ -337,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());
});
}
@ -345,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)) {
@ -407,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;
}
});
@ -480,7 +513,7 @@ public class LibaoUtils {
UserDataLibaoEntity me = new UserDataLibaoEntity(libaoCode, "ling", Utils.getTime(context));
initLibaoCode(libaoEntity, me);
if (adapter != null) adapter.initLibaoCode(me);
EventBus.getDefault().post(new EBReuse("libaoChanged"));
EventBus.getDefault().post(new EBReuse(Constants.LIBAO_CHANGED_TAG));
if (listener != null) listener.onLibaoStatusChange();
uploadEvent(libaoEntity, true, entrance);
String des;
@ -514,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, "礼包领取时间未开始");
@ -544,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;
}
@ -620,7 +660,7 @@ public class LibaoUtils {
UserDataLibaoEntity me = new UserDataLibaoEntity(libaoCode, "ling", Utils.getTime(context));
initLibaoCode(libaoEntity, me);
if (listener != null) listener.onLibaoStatusChange();
EventBus.getDefault().post(new EBReuse("libaoChanged"));
EventBus.getDefault().post(new EBReuse(Constants.LIBAO_CHANGED_TAG));
uploadEvent(libaoEntity, false, entrance);
if (adapter != null) {
@ -662,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, "礼包领取时间未开始");
@ -701,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

@ -2,21 +2,29 @@ package com.gh.common.util
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.gh.common.dialog.AccelerateExpirationDialogFragment
import com.gh.download.DownloadManager
import com.gh.download.PackageObserver
import com.gh.gamecenter.DownloadManagerActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.NewFlatLogUtils
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.core.provider.IAcceleratorProvider
import com.gh.gamecenter.core.utils.CurrentActivityHolder
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.hud.HeadUpDisplayHelper
import com.gh.gamecenter.hud.HeadUpDisplayLogHelper
import com.gh.gamecenter.manager.PackagesManager
import com.gh.gamecenter.packagehelper.PackageRepository
import com.halo.assistant.HaloApp
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.hud.HeadUpDisplayLogHelper
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.getMetaExtra
import com.gh.gamecenter.dsp.DspReportHelper
import com.lightgame.utils.Utils
import com.therouter.TheRouter
import org.greenrobot.eventbus.EventBus
object PackageChangeHelper : DefaultLifecycleObserver {
@ -27,12 +35,13 @@ object PackageChangeHelper : DefaultLifecycleObserver {
private const val UNINSTALL_PENDING = 2
private const val UPDATE_PENDING = 3
// <包名pending 类型,应用版本> Triple
private var pendingPackageTriple: Triple<String, Int, String>? = null
private var pendingGhId: String? = null
// <包名pending 类型,应用版本光环ID> 的 pending 集合
private var pendingPackageMap: HashMap<String, PackageEntity> = hashMapOf()
private var pendingHUDShowed: Boolean = false
private var isLaunching = true
/**
* 添加一个等待中,待确定是否已成功安装的应用
*/
@ -43,24 +52,21 @@ object PackageChangeHelper : DefaultLifecycleObserver {
Utils.log(TAG, "添加了: $packageName 包名等待安装成功")
pendingHUDShowed = false
pendingPackageTriple = Triple(packageName, INSTALL_PENDING, "")
pendingPackageMap[packageName] = PackageEntity(packageName, INSTALL_PENDING, "")
} else {
Utils.log(TAG, "添加了: $packageName 包名等待安装更新成功")
val ghId = PackageUtils.getGhId(packageName)
// 记录光环插件相关信息,用于安装成功后的处理
if (ghId != null) {
pendingGhId = ghId.toString()
}
val ghId = PackageUtils.getGhId(packageName)?.toString() ?: ""
pendingHUDShowed = false
pendingPackageTriple = Triple(packageName, UPDATE_PENDING, installData.version)
pendingPackageMap[packageName] =
PackageEntity(packageName, UPDATE_PENDING, installData.version, ghId)
}
}
fun isInstallPendingPackage(packageName: String): Boolean {
return pendingPackageTriple?.first == packageName
return pendingPackageMap[packageName]?.packageName == packageName
}
/**
@ -68,87 +74,159 @@ object PackageChangeHelper : DefaultLifecycleObserver {
*/
fun addUninstallPendingPackage(packageName: String) {
Utils.log(TAG, "添加了: $packageName 包名等待卸载成功")
pendingPackageTriple = Triple(packageName, UNINSTALL_PENDING, "")
pendingPackageMap[packageName] = PackageEntity(packageName, UNINSTALL_PENDING, "")
}
override fun onCreate(owner: LifecycleOwner) {
super.onCreate(owner)
isLaunching = true
}
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
if (pendingPackageTriple != null) {
val packageName = pendingPackageTriple?.first ?: return
val isInstallPending = pendingPackageTriple?.second == INSTALL_PENDING
val isUninstallPending = pendingPackageTriple?.second == UNINSTALL_PENDING
val isUpdatePending = pendingPackageTriple?.second == UPDATE_PENDING
if (pendingPackageMap.size > 0) {
calculateAndDispatchPackageChanges()
}
val pendingVersion = pendingPackageTriple?.third ?: ""
refreshVipStatus()
}
/**
* 应用从后台切到前台如果是登录状态需要刷新用户的vip状态
*/
private fun refreshVipStatus() {
val iAcceleratorProvider = TheRouter.get(IAcceleratorProvider::class.java)
if (iAcceleratorProvider?.hasToken() == true &&
AcceleratorDataHolder.instance.enableRefresh &&
AcceleratorDataHolder.instance.vipEntity != null &&
!isLaunching
) {
iAcceleratorProvider.loadQyUserPermissionData {
val vip = AcceleratorDataHolder.instance.vipEntity
if (vip != null && !vip.vipStatus && !vip.isNewUser && !vip.isVipRefundUser && !vip.isDurationRefundUser) {
// 获取当前
val currentActivity = CurrentActivityHolder.getCurrentActivity()
val reminder = AccelerateExpirationDialogFragment.ExpirationReminder.TimeRunsOut("")
if (currentActivity != null && HaloApp.getInstance().isRunningForeground) {
AccelerateExpirationDialogFragment.checkDialogShown(currentActivity, reminder)
}
}
}
}
}
/**
* 计算并分发包名的安装状态
*/
private fun calculateAndDispatchPackageChanges() {
val completeList = arrayListOf<PackageEntity>()
val pendingHudPackageList = arrayListOf<String>()
for (packageEntity in pendingPackageMap.values) {
val packageName = packageEntity.packageName
val pendingVersion = packageEntity.version
val pendingGhId = packageEntity.ghId
val installedVersionName = PackageUtils.getVersionNameByPackageName(packageName)
val isInstalled = installedVersionName != null
if (isInstallPending) {
if (packageEntity.isInstallPending()) {
if (isInstalled) {
pendingPackageTriple = null
pendingGhId = null
completeList += packageEntity
PackageRepository.addInstalledGame(PackageRepository.packageFilterManager, packageName)
PackageRepository.addInstalledGame(
PackageRepository.packageFilterManager,
packageName
)
// 添加到额外的包名白名单,下次启动同时查询此包的安装情况
PackageHelper.updateAdditionalWhiteListPackageName(packageName = packageName, isAdd = true)
PackageHelper.updateAdditionalWhiteListPackageName(
packageName = packageName,
isAdd = true
)
performInstallSuccessAction(packageName)
} else {
// 任务不存在了,将等待更新安装结果的 triple 置为 null
if (DownloadManager.getInstance().getDownloadEntitySnapshotByPackageName(packageName) == null) {
pendingPackageTriple = null
// 任务不存在了,将等待更新安装结果的 packageEntity 置为 null
if (DownloadManager.getInstance()
.getDownloadEntitySnapshotByPackageName(packageName) == null
) {
completeList += packageEntity
} else {
showHUDIfNeeded(packageName)
pendingHudPackageList.add(packageName)
}
}
} else if (isUninstallPending && !isInstalled) {
pendingPackageTriple = null
pendingGhId = null
} else if (packageEntity.isUninstallPending() && !isInstalled) {
completeList += packageEntity
// 从额外的包名白名单移除,下次启动时不再查询此包的安装情况
PackageHelper.updateAdditionalWhiteListPackageName(packageName = packageName, isAdd = false)
PackageHelper.updateAdditionalWhiteListPackageName(
packageName = packageName,
isAdd = false
)
performUninstallSuccessAction(packageName)
} else if (isUpdatePending) {
} else if (packageEntity.isUpdatePending()) {
val isUpdateValid = if (installedVersionName != pendingVersion) {
true
} else {
!pendingGhId.isNullOrEmpty() && pendingGhId != PackageUtils.getGhId(packageName).toString()
pendingGhId.isNotEmpty()
&& pendingGhId != PackageUtils.getGhId(packageName).toString()
}
pendingPackageTriple = null
pendingGhId = null
if (isUpdateValid) {
performUninstallSuccessAction(packageName)
performInstallSuccessAction(packageName)
completeList += packageEntity
} else {
// 任务不存在了,将等待更新安装结果的 triple 置为 null
if (DownloadManager.getInstance().getDownloadEntitySnapshotByPackageName(packageName) == null) {
pendingPackageTriple = null
// 任务不存在了,将等待更新安装结果的 packageEntity 置为 null
if (DownloadManager.getInstance()
.getDownloadEntitySnapshotByPackageName(packageName) == null
) {
completeList += packageEntity
} else {
showHUDIfNeeded(packageName)
pendingHudPackageList.add(packageName)
}
}
// 添加到额外的包名白名单,下次启动同时查询此包的安装情况
PackageHelper.updateAdditionalWhiteListPackageName(packageName = packageName, isAdd = true)
PackageHelper.updateAdditionalWhiteListPackageName(
packageName = packageName,
isAdd = true
)
}
}
for (invalidPackage in completeList) {
pendingPackageMap.remove(invalidPackage.packageName)
}
if (pendingHudPackageList.isNotEmpty()) {
showHUDIfNeeded(pendingHudPackageList.last())
}
}
override fun onStop(owner: LifecycleOwner) {
super.onStop(owner)
isLaunching = false
}
private fun showHUDIfNeeded(packageName: String) {
if (pendingHUDShowed) return
val downloadEntity = DownloadManager.getInstance().getDownloadEntitySnapshotByPackageName(packageName) ?: return
val downloadEntity =
DownloadManager.getInstance().getDownloadEntitySnapshotByPackageName(packageName)
?: return
if (downloadEntity.gameId == Constants.GHZS_GAME_ID) return
val activity = CurrentActivityHolder.getCurrentActivity() ?: return
if (activity is DownloadManagerActivity) return
pendingHUDShowed = true
HeadUpDisplayHelper.showHUD(activity,
HeadUpDisplayHelper.showHUD(
activity,
downloadEntity.icon ?: "",
activity.getString(R.string.hud_has_pending_install_game),
activity.getString(R.string.hud_head_to_download_manager),
@ -189,16 +267,26 @@ object PackageChangeHelper : DefaultLifecycleObserver {
private fun performInstallSuccessAction(
packageName: String,
cachedGameEntity: GameEntity? = null,
withLog: Boolean = true
withLog: Boolean = true,
) {
Utils.log(TAG, "安装了: $packageName 包名的程序")
val downloadEntity = DownloadManager.getInstance().getDownloadEntitySnapshotByPackageName(packageName)
val gameId = if (downloadEntity != null && downloadEntity.gameId != null) downloadEntity.gameId else ""
val gameName = if (downloadEntity != null && downloadEntity.name != null) downloadEntity.name else ""
val downloadEntity =
DownloadManager.getInstance().getDownloadEntitySnapshotByPackageName(packageName)
val gameId = downloadEntity?.gameId ?: ""
val gameName = downloadEntity?.name ?: ""
if (withLog) {
if (withLog && gameId.isNotEmpty()) {
NewFlatLogUtils.logGameInstallComplete(gameId, gameName)
SensorsBridge.trackInstallGameFinish(gameId, gameName)
SensorsBridge.trackInstallGameFinish(
gameId,
gameName,
downloadEntity?.getMetaExtra(Constants.DSP_GAME) == "true",
downloadEntity?.getMetaExtra(Constants.DSP_AD_ID) ?: ""
)
if (downloadEntity?.getMetaExtra(Constants.DSP_GAME) == "true") {
DspReportHelper.report(downloadEntity.getMetaExtra(Constants.INSTALL_URL))
}
}
InstallUtils.getInstance().removeInstall(packageName)
@ -237,4 +325,23 @@ object PackageChangeHelper : DefaultLifecycleObserver {
EventBus.getDefault().post(uninstallEb)
}
private class PackageEntity(
val packageName: String,
val type: Int,
val version: String,
val ghId: String = "",
) {
fun isInstallPending(): Boolean {
return type == INSTALL_PENDING
}
fun isUninstallPending(): Boolean {
return type == UNINSTALL_PENDING
}
fun isUpdatePending(): Boolean {
return type == UPDATE_PENDING
}
}
}

View File

@ -12,6 +12,7 @@ import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.gh.common.constant.Config
import com.gh.download.server.BrowserInstallHelper
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.DialogHelper
import com.gh.gamecenter.common.utils.PermissionHelper
@ -24,7 +25,6 @@ import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.entity.WhitePackageListEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.SettingsEntity
import com.gh.gamecenter.feature.utils.SentryHelper
import com.gh.gamecenter.manager.PackagesManager
import com.gh.gamecenter.packagehelper.PackageRepository
import com.gh.gamecenter.retrofit.RetrofitManager
@ -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)
@ -459,6 +470,9 @@ object PackageHelper {
PackageRepository.initData {
refreshLocalPackageList()
refreshPackageNameList()
// 初始化使用浏览器安装的条件
BrowserInstallHelper.initIfConditionMatched(Config.getNewSettingsEntity())
}
}
@ -475,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
@ -786,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,23 +64,27 @@ 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
val currentActivity = AppManager.getInstance().currentActivity() ?: return
val properContext = if (context is AppCompatActivity && !context.isFinishing) {
context
} else {
AppManager.getInstance().currentActivity()
}
properContext ?: return
if (!ignoreAsVGame && isDownloadAsVGame) {
VHelper.install(currentActivity, downloadEntity)
VHelper.install(properContext, downloadEntity)
return
}
// 已知问题
// 1. 此处可能遇到 activity 是 WXEntryActivity因为 WXEntryActivity 不是 AppCompatActivity 调不起弹窗
// 2. 当 activity 全部出栈,但是应用还在下载游戏,下载完会唤不起安装
if (currentActivity is AppCompatActivity && !currentActivity.isFinishing) {
InstallPermissionDialogFragment.show(currentActivity, downloadEntity) { isFromPermissionGrantedCallback ->
if (properContext is AppCompatActivity && !properContext.isFinishing) {
InstallPermissionDialogFragment.show(properContext, downloadEntity) { isFromPermissionGrantedCallback ->
// 取消状态栏下载完成的通知,若存在
downloadEntity.meta[Constants.MARK_ALREADY_TRIGGERED_INSTALLATION] = "YES"
DownloadNotificationHelper.addOrUpdateDownloadNotification(downloadEntity)
@ -93,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)
@ -205,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()
)
@ -95,7 +97,8 @@ object ReservationHelper {
it.categoryChinese,
data.wechatConfig.isReminderEnable,
if (data.hasSmsConfig) data.smsConfig.notice else null,
if (data.isEnableAutoDownload) data.wifiAutoDownload else null
if (data.isEnableAutoDownload) data.wifiAutoDownload else null,
if (data.hasCalendarConfig) false else null
)
}
@ -117,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

@ -6,6 +6,7 @@ import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.runOnUiThread
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.login.user.UserManager
import com.gh.ndownload.NHttpClient
import com.lightgame.utils.AppManager
import org.json.JSONObject
@ -89,4 +90,12 @@ object TempCertificationUtils {
return stringBuffer.toString()
}
/**
* 检测光环是否实名
*/
fun isUserVerified(): Boolean {
val idCard = UserManager.getInstance().userInfoEntity?.idCard
// 账号已实名
return idCard != null && idCard.status == 0
}
}

View File

@ -32,6 +32,7 @@ import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.ResponseBody
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import java.util.*
@ -229,15 +230,19 @@ object UsageStatsHelper {
mApi.getUsageStatusUpdateTime(HaloApp.getInstance().gid)
.subscribe(object : BiResponse<ResponseBody>() {
override fun onSuccess(data: ResponseBody) {
val body = JSONObject(data.string())
val lastPostTime = body.getLong("update_time") * 1000
try {
val body = JSONObject(data.string())
val lastPostTime = body.getLong("update_time") * 1000
val beginTime = if (lastPostTime == 0L) {
getDefaultBeginTime()
} else {
lastPostTime
val beginTime = if (lastPostTime == 0L) {
getDefaultBeginTime()
} else {
lastPostTime
}
postUsageStats(beginTime)
} catch (e: JSONException) {
Utils.log("UsageStats: 获取上次上传时间失败,错误信息:${e.message}")
}
postUsageStats(beginTime)
}
})
}

View File

@ -2,12 +2,10 @@ package com.gh.common.util
import android.os.Bundle
import androidx.fragment.app.Fragment
import com.therouter.TheRouter
import com.gh.common.iinterface.ISuperiorChain
import com.gh.gamecenter.amway.AmwayFragment
import com.gh.gamecenter.category2.CategoryV2Fragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.provider.IHelpAndFeedbackProvider
import com.gh.gamecenter.discovery.DiscoveryFragment
@ -19,7 +17,7 @@ import com.gh.gamecenter.game.columncollection.detail.ColumnCollectionDetailFrag
import com.gh.gamecenter.game.commoncollection.detail.CustomCommonCollectionDetailFragment
import com.gh.gamecenter.gamecollection.hotlist.GameCollectionHotListWrapperFragment
import com.gh.gamecenter.gamecollection.square.GameCollectionSquareFragment
import com.gh.gamecenter.gamedetail.GameDetailFragment
import com.gh.gamecenter.gamedetail.GameDetailWrapperFragment
import com.gh.gamecenter.info.InfoWrapperFragment
import com.gh.gamecenter.libao.LibaoDetailFragment
import com.gh.gamecenter.libao.LibaoFragment
@ -38,6 +36,7 @@ import com.gh.gamecenter.toolbox.ToolboxFragment
import com.gh.gamecenter.video.detail.HomeVideoFragment
import com.gh.gamecenter.wrapper.ToolbarWrapperFragment
import com.halo.assistant.fragment.WebFragment
import com.therouter.TheRouter
/**
* 通用跳转Fragment方法
@ -88,7 +87,7 @@ object ViewPagerFragmentHelper {
// 游戏详情页
TYPE_GAME -> {
bundle.putString(EntranceConsts.KEY_GAMEID, linkEntity.link)
GameDetailFragment().with(bundle)
GameDetailWrapperFragment().with(bundle)
}
// 我的光环
TYPE_MY_HALO -> {
@ -148,12 +147,21 @@ object ViewPagerFragmentHelper {
bundle.putString(EntranceConsts.KEY_QUESTIONS_ID, linkEntity.link)
NewQuestionDetailFragment().with(bundle)
}
// 专题合集详情页
TYPE_COLUMN_COLLECTION -> {
bundle.putString(EntranceConsts.KEY_COLLECTION_ID, linkEntity.link)
bundle.putInt(EntranceConsts.KEY_POSITION, 0)
bundle.putString(EntranceConsts.KEY_COLUMNNAME, linkEntity.text)
bundle.putBoolean(EntranceConsts.KEY_IS_COLUMN_COLLECTION, true)
bundle.putString(EntranceConsts.KEY_SUBJECT_TYPE, "tab")
ColumnCollectionDetailFragment().with(bundle)
}
// 其他原来带Toolbar的Fragment
else -> createToolbarWrapperFragment(parentFragment, bundle, linkEntity, isTabWrapper)
else -> createToolbarWrapperFragment(bundle, linkEntity, isTabWrapper)
}
}
private fun createToolbarWrapperFragment(parentFragment: Fragment?, bundle: Bundle, entity: LinkEntity, isTabWrapper: Boolean): Fragment {
private fun createToolbarWrapperFragment(bundle: Bundle, entity: LinkEntity, isTabWrapper: Boolean): Fragment {
var className = ReloadFragment::class.java.name
when (entity.type) {
@ -177,15 +185,6 @@ object ViewPagerFragmentHelper {
bundle.putString(EntranceConsts.KEY_SUBJECT_TYPE, "detail")
bundle.putBoolean(EntranceConsts.KEY_SHOW_DOWNLOAD_MENU, !isTabWrapper)
}
// 专题合集详情页
TYPE_COLUMN_COLLECTION -> {
className = ColumnCollectionDetailFragment::class.java.name
bundle.putString(EntranceConsts.KEY_COLLECTION_ID, entity.link)
bundle.putInt(EntranceConsts.KEY_POSITION, 0)
bundle.putString(EntranceConsts.KEY_COLUMNNAME, entity.text)
bundle.putBoolean(EntranceConsts.KEY_IS_COLUMN_COLLECTION, true)
bundle.putString(EntranceConsts.KEY_SUBJECT_TYPE, "tab")
}
// 开服表
TYPE_SERVER -> {
className = GameServersPublishFragment::class.java.name
@ -200,8 +199,8 @@ object ViewPagerFragmentHelper {
// 分类2.0
TYPE_CATEGORY_V2 -> {
className = CategoryV2Fragment::class.java.name
bundle.putString(EntranceConsts.KEY_CATEGORY_ID, entity.link)
bundle.putString(EntranceConsts.KEY_CATEGORY_TITLE, entity.text)
bundle.putString(EntranceConsts.KEY_PAGE_ID, entity.link)
bundle.putString(EntranceConsts.KEY_PAGE_NAME, entity.text)
bundle.putBoolean(EntranceConsts.KEY_SHOW_DOWNLOAD_MENU, !isTabWrapper)
}
// 通用内容合集详情页

View File

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

View File

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

View File

@ -32,6 +32,7 @@ class FlexLinearLayout @JvmOverloads constructor(context: Context, attrs: Attrib
private var mLastItemWidth = 0//最后更多按钮宽度
private var mTotalWidth = 0
private var mStrokeWidth = 0
private var mShowMore = true
var onClickListener: OnItemClickListener? = null
init {
@ -44,11 +45,12 @@ class FlexLinearLayout @JvmOverloads constructor(context: Context, attrs: Attrib
mTextSize = ta.getDimension(R.styleable.FlexLinearLayout_itemTextSize, 10F.sp2px().toFloat())
mLastItemWidth = ta.getDimensionPixelSize(R.styleable.FlexLinearLayout_lastItemWidth, 18F.dip2px())
mStrokeWidth = ta.getDimensionPixelSize(R.styleable.FlexLinearLayout_strokeWidth, 0.5F.dip2px())
mShowMore = ta.getBoolean(R.styleable.FlexLinearLayout_showMore, true)
ta.recycle()
}
fun setTags(tags: ArrayList<TagStyleEntity>) {
fun setTags(tags: List<TagStyleEntity>) {
mTags.clear()
mTotalCount = tags.size
mTotalWidth = measuredWidth
@ -86,7 +88,7 @@ class FlexLinearLayout @JvmOverloads constructor(context: Context, attrs: Attrib
mTags.forEachIndexed { index, tag ->
addView(createView(tag, index))
}
if (mTotalCount != mTags.size) {
if (mShowMore && mTotalCount != mTags.size) {
val imageView = ImageView(context).apply {
val params = LayoutParams(mLastItemWidth, mItemHeight)
layoutParams = params

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

@ -3,9 +3,15 @@ package com.gh.common.xapk
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.DocumentsContract
import android.provider.Settings
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import androidx.documentfile.provider.DocumentFile
import com.gh.common.constant.Config
import com.gh.common.util.DirectUtils
import com.gh.common.util.DownloadNotificationHelper
@ -24,6 +30,7 @@ import com.gh.gamecenter.xapk.core.XApkUnZipCallback
import com.gh.gamecenter.xapk.core.XApkUnZipEntry
import com.gh.gamecenter.xapk.core.XApkUnZipOutputFactory
import com.gh.gamecenter.xapk.io.NonSplitApksOutput
import com.gh.gamecenter.xapk.io.OBBDocOutput
import com.gh.gamecenter.xapk.io.OBBFileOutput
import com.gh.gamecenter.xapk.io.SplitApksOutput
import com.gh.gamecenter.xapk.io.XApkFileOutput
@ -34,6 +41,8 @@ import com.lightgame.download.DownloadEntity
import com.lightgame.utils.Utils
import java.io.File
import java.util.*
import androidx.core.net.toUri
import com.gh.gamecenter.core.utils.SPUtils
/**
* 目前已知的Xapk内容是:只有一个apk包和一个或做多个obb数据包(多余文件不解压,如果存在多个apk包,则以下安装无效)
@ -43,6 +52,12 @@ import java.util.*
*
* 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 {
@ -65,6 +80,10 @@ object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
systemMatched && fileListMatched
}
// 是否使用 DocumentUi 的方式访问 obb 文件夹
private var useDocStyleToUnzip = false
private var tempLauncher: ActivityResultLauncher<Uri>? = null
private const val GUIDE_TYPE_MIUI_OPTIMIZATION = "miui_optimization"
private const val MIUI_OPTIMIZATION_WARNING_DIALOG_ENTRANCE = "MIUI优化关闭提示弹窗"
@ -79,69 +98,171 @@ 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))
}
}
)
return
}
val isXapkApks = isXapkApks(downloadEntity)
PermissionHelper.checkManageAllFilesOrStoragePermissionBeforeAction(context) {
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
}
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))
}
}
)
return
}
PermissionHelper.checkManageAllFilesOrStoragePermissionBeforeAction(context) {
val unzipAction = {
DownloadManager.getInstance().getDownloadEntitySnapshot(downloadEntity.url, downloadEntity.gameId)
?.let {
unzipXapkFile(it)
unzipXapkFile(it, isXapkApks)
if (showUnzipToast) {
Utils.toast(mContext, "解压过程请勿退出光环助手!")
}
}
}
} else {
throwExceptionInDebug("如果是Apk包请使用PackageInstaller进行安装")
PackageInstaller.install(mContext, downloadEntity)
// 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)
}
}
}
private fun unzipXapkFile(downloadEntity: DownloadEntity) {
mXApkUnZipper.unzip(
XApkUnZipEntry(
downloadEntity.path,
File(downloadEntity.path)
)
)
/**
* 兜底解压方案,适用于一些奇葩系统
*/
private fun unzipWithCulpritHandled(activity: AppCompatActivity, xApkUrl: String, unzipAction: () -> Unit?) {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R
|| Build.VERSION.SDK_INT == Build.VERSION_CODES.S
) {
val isDocStyleObbFolderReadable = isDocStyleObbFolderReadable(activity)
if (isDocStyleObbFolderReadable) {
useDocStyleToUnzip = true
unzipAction.invoke()
} else {
DialogHelper.showDialog(
context = activity,
title = "安装提示",
content = "为了安装 XAPK 文件,请授予 OBB 文件夹的访问权限",
confirmText = "确定",
cancelText = "取消",
confirmClickCallback = {
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
intent.setFlags(
Intent.FLAG_GRANT_READ_URI_PERMISSION
and Intent.FLAG_GRANT_WRITE_URI_PERMISSION
and Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
and Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
)
val obbUri = OBBDocOutput.ANDROID_OBB_DOC_STYLE_URI.toUri()
val df = DocumentFile.fromTreeUri(activity, obbUri)
if (df != null) {
intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, df.uri)
}
tempLauncher =
activity.registerActivityResultLauncher(ActivityResultContracts.OpenDocumentTree()) { uri ->
if (uri != null) {
activity.contentResolver.takePersistableUriPermission(
uri,
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
)
useDocStyleToUnzip = true
unzipAction.invoke()
}
tempLauncher?.unregister()
}
tempLauncher?.launch(obbUri)
},
cancelClickCallback = {
unzipAction.invoke()
}
)
}
} else {
PermissionHelper.checkStoragePermissionBeforeAction(activity) {
// 设备大于 12需要重启才能生效 (是的,我们又重启了!)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
// 记录应用重启前需要重解压的信息
SPUtils.setString(Constants.SP_XAPK_UNZIP_ACTIVITY, activity.javaClass.name)
SPUtils.setString(Constants.SP_XAPK_URL, xApkUrl)
val pm = activity.packageManager
val intent = pm.getLaunchIntentForPackage(activity.packageName)
val mainIntent = Intent.makeRestartActivityTask(intent!!.component)
activity.startActivity(mainIntent)
Runtime.getRuntime().exit(0)
} else {
unzipAction.invoke()
}
}
}
}
private fun unzipXapkFile(downloadEntity: DownloadEntity, isXapkApks: Boolean) {
XApkUnZipEntry(downloadEntity.path, File(downloadEntity.path), isXapkApks).let(mXApkUnZipper::unzip)
mDownloadEntityMap[downloadEntity.path] = downloadEntity
}
@ -191,21 +312,14 @@ object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
DownloadNotificationHelper.addOrUpdateDownloadNotification(downloadEntity)
NDataChanger.notifyDataChanged(downloadEntity)
DownloadManager.getInstance().updateDownloadEntity(downloadEntity)
// 仅官网渠道上报 XAPK 异常信息
if (HaloApp.getInstance().channel == "GH_206") {
SentryHelper.onEvent(
"XAPK_UNZIP_ERROR",
"gameName", downloadEntity.name,
"errorDigest", exception.localizedMessage
)
}
DownloadDataHelper.uploadDownloadStatusEvent(downloadEntity, "xapk解压失败")
SensorsBridge.trackGameDecompressionFailed(
downloadEntity.gameId,
downloadEntity.name,
downloadEntity.categoryChinese
downloadEntity.categoryChinese,
exception.localizedMessage ?: "unknown error"
)
}
@ -324,7 +438,7 @@ object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
}
override fun onCreateOBBOutput(apk: XApkFile): XApkFileOutput<Unit> {
return OBBFileOutput()
return if (useDocStyleToUnzip) OBBDocOutput(mContext) else OBBFileOutput()
}
override fun onCreateApkOutput(apk: XApkFile): XApkFileOutput<IPackageInstaller> {
@ -340,6 +454,16 @@ object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
}
}
/**
* 是否能以 DocumentUi 的方式访问 obb 文件夹
*/
private fun isDocStyleObbFolderReadable(context: Context): Boolean {
return context
.contentResolver
.persistedUriPermissions
.any { it.uri == OBBDocOutput.ANDROID_OBB_URI.toUri() }
}
private class SplitApksInstaller(
private val xApkFile: XApkFile,
private val sessionId: Int,
@ -351,8 +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

@ -17,15 +17,6 @@ import androidx.collection.ArrayMap;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import com.gh.gamecenter.common.base.GlobalActivityManager;
import com.gh.gamecenter.common.utils.ExtensionsKt;
import com.gh.gamecenter.common.utils.SensorsBridge;
import com.gh.gamecenter.core.AppExecutor;
import com.gh.gamecenter.common.constant.Constants;
import com.gh.gamecenter.core.GHThreadFactory;
import com.gh.gamecenter.feature.entity.TagStyleEntity;
import com.gh.gamecenter.feature.entity.CustomPageTrackData;
import com.gh.gamecenter.feature.exposure.ExposureEvent;
import com.gh.common.exposure.ExposureUtils;
import com.gh.common.history.HistoryHelper;
import com.gh.common.simulator.SimulatorGameManager;
@ -36,10 +27,17 @@ import com.gh.common.util.LunchType;
import com.gh.common.util.PackageInstaller;
import com.gh.common.util.PackageUtils;
import com.gh.gamecenter.BuildConfig;
import com.gh.gamecenter.common.base.GlobalActivityManager;
import com.gh.gamecenter.common.constant.Constants;
import com.gh.gamecenter.common.entity.LinkEntity;
import com.gh.gamecenter.common.exposure.meta.MetaUtil;
import com.gh.gamecenter.common.utils.DeviceUtils;
import com.gh.gamecenter.common.utils.ExtensionsKt;
import com.gh.gamecenter.common.utils.FileUtils;
import com.gh.gamecenter.common.utils.NetworkUtils;
import com.gh.gamecenter.common.utils.SensorsBridge;
import com.gh.gamecenter.core.AppExecutor;
import com.gh.gamecenter.core.GHThreadFactory;
import com.gh.gamecenter.core.utils.GsonUtils;
import com.gh.gamecenter.core.utils.PageSwitchDataHelper;
import com.gh.gamecenter.core.utils.SPUtils;
@ -48,8 +46,11 @@ import com.gh.gamecenter.entity.GameUpdateEntity;
import com.gh.gamecenter.entity.HomePluggableFilterEntity;
import com.gh.gamecenter.eventbus.EBDownloadStatus;
import com.gh.gamecenter.feature.entity.ApkEntity;
import com.gh.gamecenter.feature.entity.CustomPageTrackData;
import com.gh.gamecenter.feature.entity.GameEntity;
import com.gh.gamecenter.feature.entity.PluginLocation;
import com.gh.gamecenter.feature.entity.TagStyleEntity;
import com.gh.gamecenter.feature.exposure.ExposureEvent;
import com.gh.gamecenter.feature.utils.SentryHelper;
import com.gh.gamecenter.login.user.UserManager;
import com.gh.gamecenter.manager.PackagesManager;
@ -59,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;
@ -360,6 +362,17 @@ public class DownloadManager implements DownloadStatusListener {
ExtensionsKt.addMetaExtra(downloadEntity, VHelper.KEY_REQUIRED_G_APPS, gameEntity.getGAppsSwitch());
}
if (gameEntity.isDspGame()) {
ExtensionsKt.addMetaExtra(downloadEntity, Constants.DSP_GAME, "true");
ExtensionsKt.addMetaExtra(downloadEntity, Constants.SEARCH_KEY, gameEntity.getSearchKey());
ExtensionsKt.addMetaExtra(downloadEntity, Constants.SUBJECT_NAME, gameEntity.getSubjectName());
ExtensionsKt.addMetaExtra(downloadEntity, Constants.SUBJECT_ID, gameEntity.getSubjectId());
ExtensionsKt.addMetaExtra(downloadEntity, Constants.INSTALL_URL, gameEntity.getInstallUrl());
ExtensionsKt.addMetaExtra(downloadEntity, Constants.DOWNLOAD_URL, gameEntity.getDownloadUrl());
ExtensionsKt.addMetaExtra(downloadEntity, Constants.DSP_AD_ID, gameEntity.getDspAdId());
}
ExtensionsKt.addMetaExtra(downloadEntity, Constants.KEY_BIT, apkEntity.getBit());
// 记录是否为双下载按钮模式
@ -401,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()) {
@ -433,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) {
@ -451,11 +469,20 @@ public class DownloadManager implements DownloadStatusListener {
trackDownloadType = "本地下载";
}
String pushMessageId = HaloApp.get(Constants.PUSH_MESSAGE_ID, false) instanceof String
? (String) HaloApp.get(Constants.PUSH_MESSAGE_ID, false)
: "";
String pushLinkId = HaloApp.get(Constants.PUSH_LINK_ENTITY, false) instanceof LinkEntity
? ((LinkEntity) HaloApp.get(Constants.PUSH_LINK_ENTITY, false)).getLink()
: "";
boolean isFromPush = !pushMessageId.isEmpty();
String[] arrayKv = {
Object[] arrayKv = {
"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(),
@ -465,17 +492,49 @@ public class DownloadManager implements DownloadStatusListener {
"last_page_id", GlobalActivityManager.getLastPageEntity().getPageId(),
"last_page_business_id", GlobalActivityManager.getLastPageEntity().getPageBusinessId(),
"download_status", gameEntity.getDownloadStatusChinese(),
"download_type", trackDownloadType
"download_type", trackDownloadType,
"is_from_push_notifications", isFromPush,
"message_id", pushMessageId,
"link_id", pushLinkId,
};
List<String> kvs = new ArrayList<>(Arrays.asList(arrayKv));
List<Object> kvs = new ArrayList<>(Arrays.asList(arrayKv));
if (customPageTrackData != null) {
kvs.addAll(Arrays.asList(customPageTrackData.toKV()));
}
SensorsBridge.trackEventWithExposureSource("DownloadProcessBegin",
downloadExposureEvent.getSource(), kvs.toArray(new String[0])
);
if (!gameEntity.isDspGame()) {
SensorsBridge.trackEventWithExposureSource(
"DownloadProcessBegin",
downloadExposureEvent.getSource(), kvs.toArray(new Object[0])
);
} else {
String searchKey = gameEntity.getSearchKey();
String loc;
if (TextUtils.isEmpty(searchKey)) {
loc = "自定义页面";
} else {
loc = "游戏搜索结果列表";
}
kvs.add("ad_id");
kvs.add(gameEntity.getDspAdId());
kvs.add("search_content");
kvs.add(searchKey);
kvs.add("game_column_name");
kvs.add(gameEntity.getSubjectName());
kvs.add("game_column_id");
kvs.add(gameEntity.getSubjectId());
kvs.add("location");
kvs.add(loc);
SensorsBridge.trackEventWithExposureSource(
"DspAdDownloadBegin",
downloadExposureEvent.getSource(), kvs.toArray(new Object[0])
);
}
//TODO remove
DownloadManager.getInstance().putStatus(downloadEntity.getUrl(), DownloadStatus.downloading);
@ -1296,7 +1355,7 @@ public class DownloadManager implements DownloadStatusListener {
getInstance().packageExecutor.execute(() -> {
boolean markHasChanged = false;
ArrayList<GameUpdateEntity> updates = PackageRepository.INSTANCE.getGameUpdate();
List<GameUpdateEntity> updates = PackageRepository.INSTANCE.getGameUpdate();
for (GameUpdateEntity update : updates) {
if (update == null) continue;
String mark = update.getId() + update.getPackageName();
@ -1317,7 +1376,7 @@ public class DownloadManager implements DownloadStatusListener {
* 将可用更新标记为已读的事件
*/
public void saveUpdateMarkToStorage() {
ArrayList<GameUpdateEntity> updates = PackageRepository.INSTANCE.getGameUpdate();
List<GameUpdateEntity> updates = PackageRepository.INSTANCE.getGameUpdate();
if (updates.size() == mUpdateMarks.size()) {
SPUtils.setStringSet(UPDATE_IS_READ_MARK, mUpdateMarks);
return;

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
@ -175,7 +170,9 @@ object PackageObserver {
SensorsBridge.trackInstallGameClick(
gameId = mDownloadEntity.gameId,
gameName = mDownloadEntity.name,
action = "自动安装"
action = "自动安装",
isDspGame = mDownloadEntity.getMetaExtra(Constants.DSP_GAME) == "true",
dspAdId = mDownloadEntity.getMetaExtra(Constants.DSP_AD_ID)
)
PackageInstaller.install(application, mDownloadEntity)
}

View File

@ -15,7 +15,8 @@ import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProviders
import androidx.recyclerview.widget.*
import androidx.recyclerview.widget.RecyclerView.SmoothScroller
import com.gh.common.util.*
import com.gh.common.util.DialogUtils
import com.gh.common.util.DirectUtils
import com.gh.download.DownloadManager
import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.R
@ -30,9 +31,9 @@ import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.utils.TimeElapsedHelper
import com.gh.gamecenter.databinding.DialogDownloadBinding
import com.gh.gamecenter.entity.GamePlatform
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.feature.entity.ApkEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.halo.assistant.HaloApp
import com.lightgame.download.DataWatcher
@ -70,6 +71,8 @@ class DownloadDialog : BaseDraggableDialogFragment() {
private var mEntrance: String = "" // 入口位置
private var mLocation: String = "" // 最终位置
private var onDownloadClickAction: ((Boolean) -> Unit)? = null
private val mDataWatcher = object : DataWatcher() {
override fun onDataChanged(downloadEntity: DownloadEntity) {
@ -122,7 +125,7 @@ class DownloadDialog : BaseDraggableDialogFragment() {
}
mViewModel.listLiveData.observeNonNull(this, callback = { itemList ->
mAdapter =
DownloadDialogAdapter(requireContext(), mViewModel, itemList, false, mTraceEvent, mEntrance, mLocation)
DownloadDialogAdapter(requireContext(), mViewModel, itemList, false, mTraceEvent, mEntrance, mLocation, onDownloadClickAction)
mBinding.contentList.layoutManager = createLayoutManager(itemList)
mBinding.contentList.adapter = mAdapter
performAutoDownload(itemList, mBinding.contentList)
@ -161,7 +164,8 @@ class DownloadDialog : BaseDraggableDialogFragment() {
true,
mTraceEvent,
mEntrance,
mLocation
mLocation,
onDownloadClickAction
)
mBinding.collectionList.layoutManager = createLayoutManager(itemList)
mBinding.collectionList.adapter = mCollectionAdapter
@ -426,7 +430,8 @@ class DownloadDialog : BaseDraggableDialogFragment() {
gameEntity: GameEntity,
traceEvent: ExposureEvent?,
entrance: String?,
location: String?
location: String?,
onDownloadClickAction: ((Boolean) -> Unit)? = null
) {
val fragmentActivity: FragmentActivity = if (context is FragmentActivity) {
context
@ -452,6 +457,7 @@ class DownloadDialog : BaseDraggableDialogFragment() {
bundle.putParcelable(EntranceConsts.KEY_TRACE_EVENT, traceEvent)
arguments = bundle
}
downloadDialog.onDownloadClickAction = onDownloadClickAction
downloadDialog.show(fragmentActivity.supportFragmentManager, DownloadDialog::class.java.name)
}

View File

@ -24,7 +24,8 @@ class DownloadDialogAdapter(
val isCollectionPage: Boolean,
private val mTraceEvent: ExposureEvent?,
private val mEntrance: String,
private val mLocation: String
private val mLocation: String,
private val onDownloadClickAction: ((Boolean) -> Unit)? = null
) : BaseRecyclerAdapter<RecyclerView.ViewHolder>(context) {
private val mPath = if (isCollectionPage) {
@ -170,7 +171,8 @@ class DownloadDialogAdapter(
mTraceEvent,
mEntrance,
mPath,
mLocation
mLocation,
onDownloadClickAction
)
}
}

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

@ -41,7 +41,8 @@ class DownloadDialogItemViewHolder(val binding: DownloadDialogItemBinding) : Bas
traceEvent: ExposureEvent?,
entrance: String,
path: String,
location: String
location: String,
onDownloadClickAction: ((Boolean) -> Unit)? = null
) {
val apkEntity = listData[position].normal!!
@ -181,7 +182,7 @@ class DownloadDialogItemViewHolder(val binding: DownloadDialogItemBinding) : Bas
}
}
setDownloadClickListener(itemView, apkEntity, viewModel, traceEvent, entrance, path, location)
setDownloadClickListener(itemView, apkEntity, viewModel, traceEvent, entrance, path, location, onDownloadClickAction)
}
private fun changeRecommendUI(apkEntity: ApkEntity, listData: List<DownloadDialogItemData>, position: Int) {
@ -226,7 +227,8 @@ class DownloadDialogItemViewHolder(val binding: DownloadDialogItemBinding) : Bas
traceEvent: ExposureEvent?,
entrance: String,
path: String,
location: String
location: String,
onDownloadClickAction: ((Boolean) -> Unit)? = null
) {
val gameEntity = viewModel.gameEntity
@ -234,6 +236,7 @@ class DownloadDialogItemViewHolder(val binding: DownloadDialogItemBinding) : Bas
when (itemView.getTag(DownloadDialogAdapter.ITEM_TAG_KEY)) {
DownloadDialogItemStatus.DOWNLOAD -> {
createDownloadTask(it.context, apkEntity, gameEntity, traceEvent, entrance, location)
onDownloadClickAction?.invoke(false)
}
DownloadDialogItemStatus.LAUNCH -> {
PackageLauncher.launchApp(it.context, gameEntity, apkEntity.packageName)
@ -291,6 +294,7 @@ class DownloadDialogItemViewHolder(val binding: DownloadDialogItemBinding) : Bas
}
DownloadDialogItemStatus.UPDATE -> {
createDownloadTask(it.context, apkEntity, gameEntity, traceEvent, entrance, location)
onDownloadClickAction?.invoke(true)
}
DownloadDialogItemStatus.COLLECTION -> {
val apkCollection = apkEntity.apkCollection

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

@ -3,6 +3,7 @@ package com.gh.download.server
import android.content.Context
import android.os.Build
import android.util.Base64
import androidx.annotation.WorkerThread
import com.gh.common.constant.Config
import com.gh.common.util.DirectUtils
import com.gh.common.util.LogUtils
@ -246,6 +247,19 @@ object BrowserInstallHelper {
}
}
/**
* 初始化是否满足开启浏览器安装的条件(后续会使用缓存来判断)
* @param settingsEntity 服务器返回的配置
*
* 因为可能需要查询已安装的应用,所以需要在子线程中调用
*/
@WorkerThread
fun initIfConditionMatched(settingsEntity: NewSettingsEntity?) {
settingsEntity?.let {
isConditionMatched(it)
}
}
/**
* 是否满足开启浏览器安装的条件
*/

View File

@ -5,15 +5,10 @@ import android.content.ContextWrapper
import android.content.Intent
import android.os.Bundle
import android.view.View
import com.therouter.router.Autowired
import com.therouter.router.Route
import com.therouter.TheRouter
import com.gh.base.DownloadToolbarActivity
import com.gh.common.exposure.ExposureManager
import com.gh.common.exposure.ExposureTraceUtils.appendTrace
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.base.activity.ToolBarActivity.NORMAL_FRAGMENT_BUNDLE
import com.gh.gamecenter.common.base.activity.ToolBarActivity.NORMAL_FRAGMENT_NAME
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.utils.toArrayList
@ -24,7 +19,11 @@ import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.ExposureEvent.Companion.createEvent
import com.gh.gamecenter.feature.exposure.ExposureType
import com.gh.gamecenter.gamedetail.GameDetailFragment
import com.gh.gamecenter.gamedetail.GameDetailWrapperFragment
import com.gh.gamecenter.gamedetail.entity.GameDetailTabEntity
import com.therouter.TheRouter
import com.therouter.router.Autowired
import com.therouter.router.Route
@Route(
path = RouteConsts.activity.gameDetailActivity,
@ -74,11 +73,11 @@ class GameDetailActivity : DownloadToolbarActivity() {
generateDataFromRoute()
super.onCreate(savedInstanceState)
DisplayUtils.transparentStatusBar(this)
DisplayUtils.setStatusBarColor(this, com.gh.gamecenter.common.R.color.transparent, !mIsDarkModeOn)
}
override fun provideNormalIntent(): Intent {
return getTargetIntent(this, GameDetailActivity::class.java, GameDetailFragment::class.java)
return getTargetIntent(this, GameDetailActivity::class.java, GameDetailWrapperFragment::class.java)
}
override fun getLayoutId() = R.layout.activity_game_detail
@ -115,7 +114,6 @@ class GameDetailActivity : DownloadToolbarActivity() {
view,
listOf(
R.id.menu_download_iv,
R.id.gameBigEvent,
R.id.cardContainer,
R.id.iv_reserve,
R.id.iv_concern,
@ -127,10 +125,15 @@ class GameDetailActivity : DownloadToolbarActivity() {
private fun generateDataFromRoute() {
val bundle = intent.extras
intent?.putExtra(NORMAL_FRAGMENT_NAME, GameDetailFragment::class.java.canonicalName)
intent?.putExtra(NORMAL_FRAGMENT_NAME, GameDetailWrapperFragment::class.java.canonicalName)
intent?.putExtra(NORMAL_FRAGMENT_BUNDLE, bundle)
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
DisplayUtils.setStatusBarColor(this, com.gh.gamecenter.common.R.color.transparent, !mIsDarkModeOn)
}
companion object {
@JvmStatic
@ -190,12 +193,12 @@ class GameDetailActivity : DownloadToolbarActivity() {
}
if (scrollToLibao) {
bundle.putString(EntranceConsts.KEY_TARGET, EntranceConsts.TAB_TYPE_DESC)
bundle.putString(EntranceConsts.KEY_TARGET, GameDetailTabEntity.TYPE_DETAIL)
bundle.putBoolean(EntranceConsts.KEY_SCROLL_TO_LIBAO, true)
}
if (scrollToServer) {
bundle.putString(EntranceConsts.KEY_TARGET, EntranceConsts.TAB_TYPE_DESC)
bundle.putString(EntranceConsts.KEY_TARGET, GameDetailTabEntity.TYPE_DETAIL)
bundle.putBoolean(EntranceConsts.KEY_SCROLL_TO_SERVER, true)
}
@ -302,7 +305,7 @@ class GameDetailActivity : DownloadToolbarActivity() {
}
if (openVideoStreaming) {
bundle.putBoolean(EntranceConsts.KEY_OPEN_VIDEO_STREAMING, true)
bundle.putString(EntranceConsts.KEY_TARGET, EntranceConsts.TAB_TYPE_DESC)
bundle.putString(EntranceConsts.KEY_TARGET, GameDetailTabEntity.TYPE_DETAIL)
}
if (openPlatformWindow) {
bundle.putBoolean(EntranceConsts.KEY_OPEN_PLATFORM_WINDOW, true)
@ -316,7 +319,7 @@ class GameDetailActivity : DownloadToolbarActivity() {
}
}
if (scrollToLibao) {
bundle.putString(EntranceConsts.KEY_TARGET, EntranceConsts.TAB_TYPE_DESC)
bundle.putString(EntranceConsts.KEY_TARGET, GameDetailTabEntity.TYPE_DETAIL)
bundle.putBoolean(EntranceConsts.KEY_SCROLL_TO_LIBAO, true)
}
bundle.putString(EntranceConsts.KEY_GAME_ID, gameId)

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;
@ -119,6 +120,7 @@ import com.sina.weibo.sdk.auth.AuthInfo;
import com.sina.weibo.sdk.openapi.IWBAPI;
import com.sina.weibo.sdk.openapi.WBAPIFactory;
import com.therouter.TheRouter;
import org.greenrobot.eventbus.Subscribe;
import org.greenrobot.eventbus.ThreadMode;
import org.jetbrains.annotations.NotNull;
@ -136,6 +138,7 @@ import io.reactivex.SingleSource;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.functions.Function;
import io.reactivex.schedulers.Schedulers;
import kotlin.Pair;
import kotlin.Unit;
import kotlin.jvm.functions.Function0;
import okhttp3.RequestBody;
@ -181,7 +184,6 @@ public class MainActivity extends BaseActivity {
.get(MainWrapperViewModel.class);
DisplayUtils.transparentStatusBar(this);
DisplayUtils.updateGlobalScreen(this);
super.onCreate(savedInstanceState);
setStatusBarColor(Color.TRANSPARENT);
@ -222,14 +224,11 @@ public class MainActivity extends BaseActivity {
DialogHelper.showCenterWarningDialog(this, "发生闪退", "光环助手发生了闪退,建议安装到最新版本修复异常"
, "马上反馈", "马上安装修复",
() -> {
DirectUtils.directToGameDetail(this, Constants.GHZS_GAME_ID, "", "crash", true, "desc", null);
DirectUtils.directToGameDetail(this, Constants.GHZS_GAME_ID, "", "crash", true, "desc", null, "");
return null;
},
() -> {
DirectUtils.directToSuggestion(MainActivity.this, SuggestType.APP, "APP闪退", false, 100);
MtaHelper.onEventWithBasicDeviceInfo(
"闪退弹窗",
"玩家操作", "点击反馈");
return null;
});
} else {
@ -237,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);
}
}
@ -255,9 +246,12 @@ public class MainActivity extends BaseActivity {
// 跳转至其它页面
if (getIntent() != null
&& getIntent().getExtras() != null
&& getIntent().getBooleanExtra(EntranceConsts.KEY_REQUIRE_REDIRECT, false)) {
doSkip();
&& getIntent().getExtras() != null) {
if (getIntent().getBooleanExtra(EntranceConsts.KEY_REQUIRE_REDIRECT, false)) {
doSkip();
} else if (!TextUtils.isEmpty(getIntent().getStringExtra(EntranceConsts.KEY_THE_ROUTER_PATH))) {
doRedirect(getIntent().getStringExtra(EntranceConsts.KEY_THE_ROUTER_PATH));
}
}
// debug 模式下的快速跳转页面
@ -389,7 +383,6 @@ public class MainActivity extends BaseActivity {
HistoryHelper.deleteAttentionVideoRecord();
}
});
}
}
@ -456,6 +449,10 @@ public class MainActivity extends BaseActivity {
handler.removeCallbacksAndMessages(null);
releaseExoSourceCache();
// 移除推送触发启动记录
HaloApp.remove(Constants.PUSH_MESSAGE_ID);
HaloApp.remove(Constants.PUSH_LINK_ENTITY);
}
/**
@ -820,6 +817,19 @@ public class MainActivity extends BaseActivity {
}, 500);
}
/**
* 重定向至 TheRouter 配置的页面
*/
private void doRedirect(String path) {
if (getIntent().getExtras() != null) {
// 更新 intent 数据,避免页面重建重新跳转
getIntent().getExtras().putString(EntranceConsts.KEY_THE_ROUTER_PATH, "");
AppExecutor.getUiExecutor().executeWithDelay(() -> {
TheRouter.build(path).navigation(this);
}, 500L);
}
}
@NonNull
private Function0<Unit> launchGame(String gamePackageName) {
return () -> {
@ -923,7 +933,9 @@ public class MainActivity extends BaseActivity {
SensorsBridge.trackInstallGameClick(
finalDownloadEntity.getGameId(),
finalDownloadEntity.getName(),
"主动安装"
"主动安装",
"true".equals(ExtensionsKt.getMetaExtra(finalDownloadEntity, Constants.DSP_GAME)),
ExtensionsKt.getMetaExtra(finalDownloadEntity, Constants.DSP_AD_ID)
);
PackageInstaller.install(MainActivity.this, finalDownloadEntity);
}, 200);
@ -1044,7 +1056,9 @@ public class MainActivity extends BaseActivity {
SensorsBridge.trackInstallGameClick(
downloadEntity.getGameId(),
downloadEntity.getName(),
"自动安装"
"自动安装",
"true".equals(ExtensionsKt.getMetaExtra(downloadEntity, Constants.DSP_GAME)),
ExtensionsKt.getMetaExtra(downloadEntity, Constants.DSP_AD_ID)
);
PackageInstaller.install(this, downloadEntity, false, false);
}
@ -1084,4 +1098,34 @@ 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) {
return mMainWrapperFragment.getBusinessId();
}
return super.getBusinessId();
}
}

View File

@ -91,7 +91,6 @@ open class SearchActivity : BaseActivity() {
mSourceEntrance = intent.getStringExtra(EntranceConsts.KEY_SOURCE_ENTRANCE) ?: ""
val hint = intent.getStringExtra(EntranceConsts.KEY_HINT)
val searchImmediately = intent.getBooleanExtra(KEY_SEARCH_IMMEDIATELY, false)
var ignoreTextChanges = savedInstanceState != null
mPublishSubject = PublishSubject.create()
@ -102,12 +101,9 @@ open class SearchActivity : BaseActivity() {
.subscribe {
if (searchEt.text.isNotEmpty()
&& searchEt.text != searchEt.hint
&& !ignoreTextChanges
) {
search(SearchType.AUTO, it)
}
ignoreTextChanges = false
}
initSearchBar()
@ -202,7 +198,6 @@ open class SearchActivity : BaseActivity() {
protected open fun handleEmptySearch(newSearchKey: String) {
popBackToFragment(SearchDefaultFragment::class.java.name)// 回退到搜索首页
updateDisplayType(DisplayType.DEFAULT)
mPublishSubject?.onNext(newSearchKey)
}

View File

@ -5,11 +5,12 @@ import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import android.view.View
import com.gh.gamecenter.amway.AmwaySuccessFragment
import com.gh.gamecenter.common.base.activity.ToolBarActivity
import com.gh.gamecenter.common.base.fragment.BaseFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.amway.AmwaySuccessFragment
import com.gh.gamecenter.gamedetail.LibaoListFragment
import com.gh.gamecenter.gamedetail.cloudarchive.CloudArchiveFragment
import com.gh.gamecenter.gamedetail.libao.LibaoListFragment
import com.halo.assistant.fragment.SwitchInstallMethodFragment
import com.halo.assistant.fragment.user.ManuallyRealNameFragment
import com.halo.assistant.fragment.user.RealNameInfoFragment
@ -38,6 +39,7 @@ class ShellActivity : ToolBarActivity() {
Type.REAL_NAME_INFO -> startFragment(RealNameInfoFragment().with(bundle))
Type.MANUALLY_REAL_NAME -> startFragment(ManuallyRealNameFragment().with(extraData))
Type.SIMPLE_LIBAO_LIST -> startFragment(LibaoListFragment.newInstance(extraData))
Type.CLOUD_ARCHIVE -> startFragment(CloudArchiveFragment().with(extraData))
}
}
@ -72,7 +74,8 @@ class ShellActivity : ToolBarActivity() {
SWITCH_INSTALL_METHOD("switch_install_method"),
REAL_NAME_INFO("real_name_info"),
MANUALLY_REAL_NAME("manually_real_name"),
SIMPLE_LIBAO_LIST("simple_libao_list");
SIMPLE_LIBAO_LIST("simple_libao_list"),
CLOUD_ARCHIVE("cloud_archive");
companion object {
fun fromString(typeString: String): Type {

View File

@ -1,6 +1,7 @@
package com.gh.gamecenter;
import static com.gh.gamecenter.common.constant.EntranceConsts.ENTRANCE_BROWSER;
import static com.gh.gamecenter.common.constant.EntranceConsts.ENTRANCE_OTHER;
import static com.gh.gamecenter.common.constant.EntranceConsts.ENTRANCE_PUSH;
import static com.gh.gamecenter.common.constant.EntranceConsts.HOST_ANSWER;
import static com.gh.gamecenter.common.constant.EntranceConsts.HOST_ARCHIVE_LOGIN;
@ -35,6 +36,7 @@ import static com.gh.gamecenter.common.constant.EntranceConsts.HOST_VIDEO_STREAM
import static com.gh.gamecenter.common.constant.EntranceConsts.HOST_VIDEO_STREAMING_HOME;
import static com.gh.gamecenter.common.constant.EntranceConsts.HOST_WEB;
import static com.gh.gamecenter.common.constant.EntranceConsts.KEY_DATA;
import static com.gh.gamecenter.common.constant.EntranceConsts.KEY_FROM;
import static com.gh.gamecenter.common.constant.EntranceConsts.KEY_GAME_NAME;
import static com.gh.gamecenter.common.constant.EntranceConsts.KEY_NAME;
import static com.gh.gamecenter.common.constant.EntranceConsts.KEY_PACKAGENAME;
@ -100,7 +102,6 @@ public class SkipActivity extends BaseActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Uri uri = getIntent().getData();
Bundle bundle;
if (uri != null) {
@ -160,7 +161,8 @@ public class SkipActivity extends BaseActivity {
DirectUtils.directToArticle(this, path, entrance);
break;
case HOST_GAME:
DirectUtils.directToGameDetail(this, path, "", entrance, "true".equals(uri.getQueryParameter("auto_download")), to, null);
String from = uri.getQueryParameter(KEY_FROM);
DirectUtils.directToGameDetail(this, path, "", entrance, "true".equals(uri.getQueryParameter("auto_download")), to, null, from);
break;
case HOST_COLUMN:
DirectUtils.directToSubject(this, path, uri.getQueryParameter(KEY_NAME), entrance, null, SubjectData.SubjectType.NORMAL);
@ -180,7 +182,7 @@ public class SkipActivity extends BaseActivity {
DirectUtils.directToAnswerDetail(this, path, entrance, pathName);
break;
case HOST_QUESTION:
DirectUtils.directToQuestionDetail(this, path, entrance, pathName, "");
DirectUtils.directToQuestionDetail(this, path, entrance, pathName, isFromPush ? ENTRANCE_PUSH : ENTRANCE_OTHER);
break;
case HOST_TOOLBOX:
DirectUtils.directToToolbox(this, uri.getQueryParameter("gameId"), uri.getQueryParameter("toolboxUrl"), entrance);
@ -190,7 +192,7 @@ public class SkipActivity extends BaseActivity {
break;
// 社区文章格式一
case "community.article":
DirectUtils.directToCommunityArticle(this, uri.getQueryParameter("articleId"), uri.getQueryParameter("communityId"), entrance, pathName, "");
DirectUtils.directToCommunityArticle(this, uri.getQueryParameter("articleId"), uri.getQueryParameter("communityId"), entrance, pathName, isFromPush ? ENTRANCE_PUSH : ENTRANCE_OTHER);
break;
// 社区文章格式二
case "communities":
@ -211,13 +213,13 @@ public class SkipActivity extends BaseActivity {
}
}
if ("articles".equals(type)) {
DirectUtils.directToCommunityArticle(this, typeId, communityId, entrance, pathName, "");
DirectUtils.directToCommunityArticle(this, typeId, communityId, entrance, pathName, isFromPush ? ENTRANCE_PUSH : ENTRANCE_OTHER);
break;
}
break;
case HOST_VIDEO:
DirectUtils.directToVideoDetail(this, path, VideoDetailContainerViewModel.Location.HOTTEST_GAME_VIDEO.getValue(),
false, id, entrance, pathName, TextUtils.isEmpty(referer) ? "" : referer, "");
false, id, entrance, pathName, TextUtils.isEmpty(referer) ? "" : referer, isFromPush ? ENTRANCE_PUSH : ENTRANCE_OTHER);
break;
case HOST_UPLOAD_VIDEO://跳转上传视频
String titleParameter = uri.getQueryParameter("title");
@ -237,7 +239,7 @@ public class SkipActivity extends BaseActivity {
break;
case HOST_VIDEO_SINGLE:
DirectUtils.directToVideoDetail(this, path, VideoDetailContainerViewModel.Location.SINGLE_VIDEO.getValue(),
false, "", entrance, pathName, TextUtils.isEmpty(referer) ? "" : referer, "");
false, "", entrance, pathName, TextUtils.isEmpty(referer) ? "" : referer, isFromPush ? ENTRANCE_PUSH : ENTRANCE_OTHER);
break;
case HOST_VIDEO_MORE:
gameId = uri.getQueryParameter("gameId");
@ -294,7 +296,7 @@ public class SkipActivity extends BaseActivity {
EntranceUtils.jumpActivityCompat(this, bundle);
break;
case EntranceConsts.HOST_VIDEO_DETAIL:
DirectUtils.directToVideoDetail(this, path, entrance, "", "");
DirectUtils.directToVideoDetail(this, path, entrance, "", isFromPush ? ENTRANCE_PUSH : ENTRANCE_OTHER);
break;
case HOST_LIBAO:
DirectUtils.directToGiftDetail(this, path, entrance);
@ -315,7 +317,7 @@ public class SkipActivity extends BaseActivity {
break;
case HOST_COLUMN_COLLECTION:
DirectUtils.directToColumnCollection(this, path, -1, entrance, "", "", "", "", null,false);
DirectUtils.directToColumnCollection(this, path, -1, entrance, "", "", "", "", null, false);
break;
case EntranceConsts.HOST_BLOCK:
name = uri.getQueryParameter("name");
@ -357,7 +359,7 @@ public class SkipActivity extends BaseActivity {
byte[] linkData = Base64.decode(dataString, Base64.DEFAULT);
String linkDataString = new String(linkData, "UTF-8");
LaunchRedirect launchRedirect = GsonUtils.fromJson(linkDataString, LaunchRedirect.class);
DirectUtils.directToLinkPage(this, launchRedirect, entrance, "", "");
DirectUtils.directToLinkPage(this, launchRedirect, entrance, "", isFromPush ? ENTRANCE_PUSH : ENTRANCE_OTHER);
}
} catch (Exception e) {
e.printStackTrace();
@ -414,13 +416,13 @@ public class SkipActivity extends BaseActivity {
break;
case HOST_ARCHIVE_LOGIN:
String gamePkg = uri.getQueryParameter(EntranceConsts.KEY_GAME_PKG);
if(CheckLoginUtils.isLogin()) {
if (CheckLoginUtils.isLogin()) {
VHelper.INSTANCE.updateAuthorizeInfo(true);
} else {
Bundle newBundle = new Bundle();
newBundle.putString(EntranceConsts.KEY_TO, LoginActivity.class.getName());
EntranceUtils.jumpActivityCompat(this, newBundle, null, (resultCode, data) -> {
if(CheckLoginUtils.isLogin()) {
if (CheckLoginUtils.isLogin()) {
VHelper.INSTANCE.updateAuthorizeInfo(true);
}
VHelper.launch(this, gamePkg, false, false);

View File

@ -5,6 +5,7 @@ import android.app.NotificationManager
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.preference.PreferenceManager
@ -14,8 +15,8 @@ import androidx.core.app.NotificationCompat
import androidx.core.text.bold
import androidx.core.text.buildSpannedString
import androidx.core.text.color
import com.therouter.router.Route
import com.therouter.TheRouter
import com.therouter.router.Route
import com.gh.common.dialog.NewPrivacyPolicyDialogFragment
import com.gh.common.util.DeviceTokenUtils
import com.gh.common.util.DialogUtils
@ -26,6 +27,9 @@ import com.gh.common.util.UsageStatsHelper.checkAndPostUsageStats
import com.gh.download.DownloadManager
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.EntranceConsts.KEY_FROM
import com.gh.gamecenter.common.constant.EntranceConsts.KEY_GAMEID
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.tracker.TrackerLogger
import com.gh.gamecenter.common.utils.*
@ -90,8 +94,10 @@ class SplashScreenActivity : BaseActivity(), ISplashScreen {
showPrivacyDialog()
} else {
val spanBuilder = buildSpannedString {
append("这个弹窗只会在右上角有环境标签的测试包出现" +
"\n进入应用以后还可以到关于我们页面长按应用图标重新选择")
append(
"这个弹窗只会在右上角有环境标签的测试包出现" +
"\n进入应用以后还可以到关于我们页面长按应用图标重新选择"
)
bold {
color(com.gh.gamecenter.common.R.color.text_theme.toColor(this@SplashScreenActivity)) {
append("\n点击这里进行预设置渠道")
@ -103,7 +109,7 @@ class SplashScreenActivity : BaseActivity(), ISplashScreen {
executeDex2OatInAdvance()
DialogHelper.showDialog(
context = this,
title ="选择环境",
title = "选择环境",
content = spanBuilder,
confirmText = "正式环境",
cancelText = "测试环境",
@ -144,6 +150,10 @@ class SplashScreenActivity : BaseActivity(), ISplashScreen {
val trackEvent = JSONObject()
// 是否首次使用神策
val isFirstTime = SPUtils.getBoolean(Constants.SP_SENSORS_IS_FIRST_TIME, true)
val therouterPath = intent.extras?.getString(EntranceConsts.KEY_THE_ROUTER_PATH) ?: ""
val uri = Uri.parse(therouterPath)
val isFromWechat = WECHAT_APPOINTMENT == if (!uri.isOpaque) uri.getQueryParameter(KEY_FROM) else false
val gameId = if (!uri.isOpaque) uri.getQueryParameter(KEY_GAMEID) else ""
tryCatchInRelease {
trackEvent.run {
put("\$is_first_time", isFirstTime)
@ -153,6 +163,10 @@ class SplashScreenActivity : BaseActivity(), ISplashScreen {
put("signature", signatureHash)
put("app_name", appProvider?.getAppName())
put("install_first_time", if (HaloApp.getInstance().isBrandNewInstall) "" else "")
if (isFromWechat) {
put("source_entrance", WECHAT_NOTIFICATION)
put("page_business_id", gameId)
}
}
}
SensorsBridge.trackEvent("AppLaunch", trackEvent)
@ -316,6 +330,9 @@ class SplashScreenActivity : BaseActivity(), ISplashScreen {
}
}
override fun initPageLevel(savedInstanceState: Bundle?) {
// do nothing
}
companion object {
private const val KEY_REGISTRATION_ID = "registration_id"
@ -323,6 +340,9 @@ class SplashScreenActivity : BaseActivity(), ISplashScreen {
const val HONOR_CULPRIT_ID = 12324
const val HONOR_CULPRIT_CHANNEL = "荣耀通道"
private const val WECHAT_APPOINTMENT = "wechat_appointment"
private const val WECHAT_NOTIFICATION = "微信通知"
@JvmStatic
fun getSplashScreenIntent(context: Context?, bundle: Bundle?): Intent {
val intent = Intent(context, SplashScreenActivity::class.java)

View File

@ -2,6 +2,7 @@ package com.gh.gamecenter.adapter;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.text.Html;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
@ -12,6 +13,7 @@ import android.text.style.ClickableSpan;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
@ -20,9 +22,14 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.generic.RoundingParams;
import com.facebook.drawee.view.SimpleDraweeView;
import com.gh.common.util.DirectUtils;
import com.gh.common.util.LibaoUtils;
import com.gh.gamecenter.GameDetailActivity;
import com.gh.gamecenter.ImageViewerActivity;
import com.gh.gamecenter.R;
import com.gh.gamecenter.adapter.viewholder.LibaoDetailContentViewHolder;
import com.gh.gamecenter.adapter.viewholder.LibaoDetailTopViewHolder;
@ -31,6 +38,7 @@ import com.gh.gamecenter.common.entity.SimpleGameEntity;
import com.gh.gamecenter.common.entity.SuggestType;
import com.gh.gamecenter.common.retrofit.Response;
import com.gh.gamecenter.common.utils.ExtensionsKt;
import com.gh.gamecenter.common.utils.ImageUtils;
import com.gh.gamecenter.common.utils.PicassoImageGetter;
import com.gh.gamecenter.common.viewholder.FooterViewHolder;
import com.gh.gamecenter.core.utils.DisplayUtils;
@ -79,6 +87,7 @@ public class LibaoDetailAdapter extends BaseRecyclerAdapter<ViewHolder> {
private String mEntrance;
private final int TYPE_FOOTER = 100;
public LibaoDetailTopViewHolder libaoDetailTopViewHolder;
private ArrayList<View> mImageViewList = new ArrayList<>();
public LibaoDetailAdapter(Context context, OnRequestCallBackListener onRequestCallBackListener,
OnCodeScrollListener onCodeScrollListener, LibaoEntity libaoEntity,
@ -317,6 +326,35 @@ public class LibaoDetailAdapter extends BaseRecyclerAdapter<ViewHolder> {
holder.binding.libaodetailContentLl.setVisibility(View.VISIBLE);
holder.binding.libaodetailContent.setText(Html.fromHtml(mLibaoEntity.getContent()));
}
if (mLibaoEntity.getMaterials().isEmpty()) {
holder.binding.horizontalScrollView.setVisibility(View.GONE);
} else {
holder.binding.horizontalScrollView.setVisibility(View.VISIBLE);
holder.binding.imagesContainer.removeAllViews();
mImageViewList.clear();
for (int i = 0; i < mLibaoEntity.getMaterials().size(); i++) {
String imageUrl = mLibaoEntity.getMaterials().get(i).getImg();
SimpleDraweeView imageView = new SimpleDraweeView(mContext);
RoundingParams roundingParams = new RoundingParams();
roundingParams.setCornersRadius(DisplayUtils.dip2px(4F));
roundingParams.setOverlayColor(ContextCompat.getColor(mContext, com.gh.gamecenter.common.R.color.ui_surface));
imageView.setHierarchy(new GenericDraweeHierarchyBuilder(mContext.getResources())
.setFadeDuration(500)
.setRoundingParams(roundingParams)
.setPlaceholderImage(com.gh.gamecenter.common.R.drawable.occupy, ScalingUtils.ScaleType.FIT_XY)
.build());
ImageUtils.display(imageView, imageUrl);
final int index = i;
imageView.setOnClickListener(v -> {
Intent intent = ImageViewerActivity.getIntent(mContext, mLibaoEntity.getMaterialImgList(), index, mImageViewList, mEntrance);
mContext.startActivity(intent);
});
mImageViewList.add(imageView);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(DisplayUtils.dip2px(24F), DisplayUtils.dip2px(24F));
layoutParams.setMargins(DisplayUtils.dip2px(i == 0 ? 8F : 16F), 0, 0, 0);
holder.binding.imagesContainer.addView(imageView, layoutParams);
}
}
if (mLibaoDetailEntity != null) {
holder.binding.libaodetailTimeLl.setVisibility(View.VISIBLE);
@ -384,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

@ -4,8 +4,11 @@ import android.content.Context
import android.content.Intent
import android.text.TextUtils
import android.view.View
import android.widget.ImageView
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.Group
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.FragmentActivity
import com.airbnb.lottie.LottieAnimationView
@ -13,12 +16,14 @@ import com.gh.common.chain.*
import com.gh.common.constant.Config
import com.gh.common.dialog.DeviceRemindDialog
import com.gh.common.dialog.GameOffServiceDialogFragment
import com.gh.common.dialog.PackageCheckDialogFragment
import com.gh.common.filter.RegionSettingHelper
import com.gh.common.history.HistoryHelper
import com.gh.common.simulator.NewSimulatorGameManager
import com.gh.common.simulator.SimulatorDownloadManager
import com.gh.common.simulator.SimulatorGameManager
import com.gh.common.util.*
import com.gh.common.util.GameUtils.getDownloadBtnText
import com.gh.common.util.LogUtils
import com.gh.common.xapk.XapkInstaller
import com.gh.common.xapk.XapkUnzipStatus
@ -31,7 +36,6 @@ import com.gh.gamecenter.common.base.GlobalActivityManager.getCurrentPageEntity
import com.gh.gamecenter.common.base.GlobalActivityManager.getLastPageEntity
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.eventbus.EBReuse
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.utils.NewFlatLogUtils
import com.gh.gamecenter.core.runOnIoThread
@ -41,11 +45,14 @@ import com.gh.gamecenter.core.utils.StringUtils
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.eventbus.EBScroll
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.PluginLocation
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.feature.view.DownloadButton.ButtonStyle
import com.gh.gamecenter.gamedetail.GameDetailFragment
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper
import com.gh.gamecenter.gamedetail.dialog.GamePermissionDialogFragment
import com.gh.gamecenter.gamedetail.entity.GameDetailTabEntity
import com.gh.gamecenter.teenagermode.TeenagerModeActivity.Companion.getIntent
import com.gh.vspace.VHelper
import com.lightgame.download.DownloadEntity
@ -56,13 +63,16 @@ import java.io.File
// 虽然叫 ViewHolder但其实就是一个用来临时放 View 和相关操作的包裹类
class DetailViewHolder(
view: View,
val viewModel: GameDetailViewModel?,
val gameEntity: GameEntity,
val isNewsDetail: Boolean, // 新闻详情不显示下载的游戏名, 只显示下载状态
entrance: String?,
name: String?,
title: String?,
val traceEvent: ExposureEvent?,
val isSupportDualButton: Boolean = false // 是否支持双下载按钮,不支持的时候跟普通列表意义选用优先级高的那个来显示
val isSupportDualButton: Boolean = false, // 是否支持双下载按钮,不支持的时候跟普通列表意义选用优先级高的那个来显示,
val acceleratorUiHelper: GameDetailAcceleratorUiHelper? = null, // 网速加速,只有游戏详情才有
onDownloadClickAction: ((Boolean) -> Unit)? = null
) {
var context: Context
var downloadBottom: View
@ -90,21 +100,43 @@ class DetailViewHolder(
// 多版本下载文字
var multiVersionDownloadTv: TextView?
// 加速按钮
val speedContainer: ConstraintLayout?
private val ivFreeVipTag: ImageView?
private val gMoreZone: Group?
private val gUpdate: Group?
private val ivUpdate: ImageView?
private val tvSize: TextView?
private val vUpdate: View?
private val tvUpdate: TextView?
private var isShowVaUpdate = true
// 注意 View 的命名
init {
downloadBottom = view.findViewById(R.id.detail_ll_bottom)
downloadPb = view.findViewById(R.id.detail_progressbar)
downloadTips = view.findViewById(R.id.downloadTipsLottie)
downloadPb = downloadBottom.findViewById(R.id.detail_progressbar)
downloadTips = downloadBottom.findViewById(R.id.downloadTipsLottie)
overlayTv = view.findViewById(R.id.overlayTv)
overlayContainer = view.findViewById(R.id.overlayContainer)
extraOverlayTv = view.findViewById(R.id.extraOverlayTv)
multiVersionDownloadTv = view.findViewById(R.id.multiVersionDownloadTv)
multiVersionDownloadTv = downloadBottom.findViewById(R.id.multiVersionDownloadTv)
localDownloadContainer = view.findViewById(R.id.localDownloadContainer)
localDownloadSizeTv = view.findViewById(R.id.localDownloadSizeTv)
localDownloadTitleTv = view.findViewById(R.id.localDownloadTitleTv)
localDownloadButton = view.findViewById(R.id.localDownloadButton)
context = view.context
speedContainer = view.findViewById(R.id.cl_speed_container)
ivFreeVipTag = view.findViewById(R.id.iv_free_vip_tag)
gMoreZone = view.findViewById(R.id.g_more_zone)
gUpdate = view.findViewById(R.id.g_update)
ivUpdate = view.findViewById(R.id.iv_update)
tvSize = view.findViewById(R.id.tv_size)
vUpdate = view.findViewById(R.id.v_update)
tvUpdate = view.findViewById(R.id.tv_update)
context = view.context.getActivity() ?: view.context
com.gh.gamecenter.common.R.color.text_aw_primary.toColor()
var gameDownloadMode = gameEntity.getGameDownloadButtonMode()
@ -115,7 +147,8 @@ class DetailViewHolder(
mTitle = title ?: "",
mAsVGame = false,
mShowDualDownloadButton = gameDownloadMode == GameEntity.GAME_DOWNLOAD_BUTTON_MODE_DUAL,
mTraceEvent = traceEvent
mTraceEvent = traceEvent,
onDownloadClickAction = onDownloadClickAction
)
val vGameDownloadListener = OnDetailDownloadClickListener(
@ -125,7 +158,8 @@ class DetailViewHolder(
mTitle = title ?: "",
mAsVGame = true,
mShowDualDownloadButton = gameDownloadMode == GameEntity.GAME_DOWNLOAD_BUTTON_MODE_DUAL,
mTraceEvent = traceEvent
mTraceEvent = traceEvent,
onDownloadClickAction = onDownloadClickAction
)
// 不支持双下载按钮的情况时,优选一个下载方式显示
@ -158,9 +192,22 @@ class DetailViewHolder(
localDownloadButton?.putObject(gameEntity)
localDownloadButton?.setTag(
com.gh.gamecenter.feature.R.string.download, context.getString(
com.gh.gamecenter.feature.R.string.download_local))
com.gh.gamecenter.feature.R.string.download_local
)
)
vUpdate?.setOnClickListener {
if (isShowVaUpdate) {
vGameDownloadListener.onClick(vUpdate)
} else {
localDownloadListener.onClick(vUpdate)
}
}
}
}
downloadPb.putWidgetBusinessName("游戏详情页")
// "DownLoadbuttonClick" 埋点需要上报traceEvent信息
@ -170,12 +217,97 @@ 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()
}
fun checkIfShowSpeedUi(showVGame: Boolean, showDualDownloadButton: Boolean) {
acceleratorUiHelper?.let {
val showSpeedUi = when {
gameEntity.canSpeed && showDualDownloadButton -> { // 双按钮
val localText = getDownloadBtnText(context, gameEntity, false, false, PluginLocation.only_game);
val downloadText =
getDownloadBtnText(context, gameEntity, false, true, PluginLocation.only_game)
when {
localText.contains(com.gh.gamecenter.feature.R.string.update.toResString()) -> { // 本地游戏需要更新
localDownloadContainer?.goneIf(true)
downloadPb.goneIf(true)
overlayTv?.goneIf(true)
gUpdate?.visibility = View.VISIBLE
ivUpdate?.goneIf(true)
tvSize?.goneIf(false)
tvSize?.text =
DetailDownloadUtils.convertSizeString(gameEntity.getApk().firstOrNull()?.size ?: "")
tvUpdate?.setText(R.string.update)
isShowVaUpdate = false
true
}
localText.contains(com.gh.gamecenter.feature.R.string.launch.toResString()) && downloadText == "更新" -> { // 畅玩游戏需要更新:显示 加速/更新
localDownloadContainer?.goneIf(true)
downloadPb.goneIf(true)
overlayTv?.goneIf(true)
gUpdate?.visibility = View.VISIBLE
ivUpdate?.goneIf(false)
tvSize?.goneIf(true)
tvUpdate?.setText(R.string.update)
isShowVaUpdate = true
true
}
localText.contains(com.gh.gamecenter.feature.R.string.launch.toResString()) -> { // 本地游戏为启动状态:显示 加速/畅玩
localDownloadContainer?.goneIf(true)
downloadPb.goneIf(true)
overlayTv?.goneIf(true)
gUpdate?.visibility = View.VISIBLE
tvUpdate?.setText(R.string.v_play)
tvSize?.goneIf(true)
isShowVaUpdate = true
true
}
else -> {
gUpdate?.visibility = View.GONE
tvSize?.goneIf(true)
false
}
}
}
gameEntity.canSpeed && !showVGame -> {
gUpdate?.visibility = View.GONE
val downloadText = getDownloadBtnText(context, gameEntity, false, false, PluginLocation.only_game)
when {
downloadText.contains(com.gh.gamecenter.feature.R.string.launch.toResString()) -> {
localDownloadContainer?.goneIf(true)
downloadPb.goneIf(true)
true
}
downloadText.contains(R.string.update.toResString()) -> true
else -> false
}
}
else -> false
}
it.checkIfShowSpeedUi(showSpeedUi, showDualDownloadButton)
}
}
fun setSpeedViewsVisible(isVisible: Boolean) {
ivFreeVipTag?.goneIf(!isVisible)
speedContainer?.goneIf(!isVisible)
}
internal class OnDetailDownloadClickListener(
private val mViewHolder: DetailViewHolder,
private val mEntrance: String?,
@ -183,7 +315,8 @@ class DetailViewHolder(
private val mTitle: String,
private val mAsVGame: Boolean,
private val mShowDualDownloadButton: Boolean,
private val mTraceEvent: ExposureEvent?
private val mTraceEvent: ExposureEvent?,
private val onDownloadClickAction: ((Boolean) -> Unit)? = null
) : View.OnClickListener {
private val mGameEntity: GameEntity = mViewHolder.gameEntity
@ -261,7 +394,9 @@ class DetailViewHolder(
SensorsBridge.trackInstallGameClick(
mGameEntity.id,
mGameEntity.name ?: "",
"主动安装"
"主动安装",
mGameEntity.isDspGame,
mGameEntity.dspAdId
)
PackageInstaller.install(mViewHolder.context, mDownloadEntity)
}
@ -281,11 +416,13 @@ class DetailViewHolder(
showLandPageAddressDialogIfNeeded()
}
}
"toast" -> {
EventBus.getDefault().post(EBReuse(GameDetailFragment.SKIP_RATING))
mViewHolder.viewModel?.performTabSelected(GameDetailTabEntity.TYPE_COMMENT)
ToastUtils.toast("该游戏因故暂不提供下载,具体详情可在相关评论中查看,敬请谅解~")
showLandPageAddressDialogIfNeeded()
}
"third_party" -> {
showLandPageAddressDialogIfNeeded()
}
@ -334,7 +471,19 @@ class DetailViewHolder(
if (mAsVGame) {
VHelper.installOrLaunch(mViewHolder.context, mGameEntity, null)
} else {
PackageLauncher.launchApp(mViewHolder.context, mGameEntity, mGameEntity.getUniquePackageName())
// 如果游戏配置了加速,则启动时需要进行包名检测
if (mGameEntity.canSpeed) {
PackageCheckDialogFragment.show(mViewHolder.context as AppCompatActivity, mGameEntity) {
PackageLauncher.launchApp(
mViewHolder.context, mGameEntity, mGameEntity.getUniquePackageName()
)
}
} else {
PackageLauncher.launchApp(
mViewHolder.context, mGameEntity, mGameEntity.getUniquePackageName()
)
}
}
} else {
GamePermissionDialogFragment.show(
@ -354,7 +503,8 @@ class DetailViewHolder(
gameEntity = mGameEntity,
traceEvent = mTraceEvent,
entrance = StringUtils.buildString(mEntrance, "+(", mName, "[", mTitle, "])"),
location = "$mName:$mTitle"
location = "$mName:$mTitle",
onDownloadClickAction = onDownloadClickAction
)
}
}
@ -371,7 +521,9 @@ class DetailViewHolder(
SensorsBridge.trackInstallGameClick(
gameId = mGameEntity.id,
gameName = mGameEntity.name ?: "",
action = "主动安装"
action = "主动安装",
isDspGame = mGameEntity.isDspGame,
dspAdId = mGameEntity.dspAdId
)
val url = mGameEntity.getApk().firstOrNull()?.url
val downloadEntity = SimulatorGameManager.findDownloadEntityByUrl(url)
@ -453,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) {
@ -594,8 +748,9 @@ class DetailViewHolder(
val apkEntity = mGameEntity.getApk().firstOrNull()
val msg = FileUtils.isCanDownload(mViewHolder.context, apkEntity?.size ?: "")
if (TextUtils.isEmpty(msg)) {
val btnContainsUpdateText = mViewHolder.context.getString(com.gh.gamecenter.feature.R.string.update_v) == buttonText
|| buttonText.contains(mViewHolder.context.getString(com.gh.gamecenter.feature.R.string.update))
val btnContainsUpdateText =
mViewHolder.context.getString(com.gh.gamecenter.feature.R.string.update_v) == buttonText
|| buttonText.contains(mViewHolder.context.getString(com.gh.gamecenter.feature.R.string.update))
if (asVGame && btnContainsUpdateText) {
VHelper.updateOrReDownload(mGameEntity)
@ -671,6 +826,7 @@ class DetailViewHolder(
builder.addHandler(OverseaDownloadHandler())
builder.addHandler(CheckDownloadHandler())
builder.setProcessEndCallback(mGameEntity.id) { asVGame: Boolean, isSubscribe: Any? ->
performDownloadClickAction()
download(asVGame, isSubscribe as Boolean)
}
} else {
@ -688,7 +844,8 @@ class DetailViewHolder(
mTitle,
"])"
),
"$mName:$mTitle"
"$mName:$mTitle",
onDownloadClickAction
)
}
}
@ -699,5 +856,20 @@ class DetailViewHolder(
mAsVGame
)
}
private fun performDownloadClickAction() {
val buttonText = if (mShowDualDownloadButton && !mAsVGame) {
mViewHolder.localDownloadTitleTv?.text?.ifEmpty {
mViewHolder.downloadPb.text.ifEmpty {
mViewHolder.overlayTv?.text ?: ""
}
}
} else {
mViewHolder.downloadPb.text.ifEmpty { mViewHolder.overlayTv?.text ?: "" }
}
val isUpdate =
buttonText?.contains(mViewHolder.context.getString(com.gh.gamecenter.feature.R.string.update)) == true
onDownloadClickAction?.invoke(isUpdate)
}
}
}

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

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

View File

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

View File

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

View File

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

View File

@ -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>()
@ -76,8 +75,15 @@ class CategoryV2ListAdapter(
ItemViewType.GAME_NORMAL -> {
CategoryGameItemViewHolder(parent.toBinding())
}
else -> {
FooterViewHolder(mLayoutInflater.inflate(com.gh.gamecenter.common.R.layout.refresh_footerview, parent, false))
FooterViewHolder(
mLayoutInflater.inflate(
com.gh.gamecenter.common.R.layout.refresh_footerview,
parent,
false
)
)
}
}
}
@ -94,20 +100,12 @@ class CategoryV2ListAdapter(
holder.bindGameItem(gameEntity)
holder.initServerType(gameEntity)
val categoryTitle = mCategoryViewModel.categoryTitle
val selectedCategoryName = mCategoryViewModel.selectedCategoryName
val builder = StringBuilder()
mCategoryViewModel.selectedCategoryList.run {
forEachIndexed { index, entity ->
builder.append(entity.name)
if (index != size - 1) {
builder.append("_")
}
}
}
val selectedSubCatalogName = builder.toString()
val sortType = mViewModel.sortType.value
val sortSize = mViewModel.sortSize.text
val categoryTitle = mCategoryViewModel.pageName
val selectedCategoryName = mCategoryViewModel.selectedSidebarsName
val selectedSubCatalogName =
mCategoryViewModel.selectedSubCategories.value?.joinToString("-") { it.category.name ?: "" }
val sortType = mViewModel.gameFiltered.sortType.value
val sortSize = mViewModel.gameFiltered.size.text
val exposureSources = ArrayList<ExposureSource>()
if (!mViewModel.exposureSourceList.isNullOrEmpty()) {
@ -126,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(
@ -172,8 +176,13 @@ class CategoryV2ListAdapter(
) {
val trackEvent = JSONObject()
try {
trackEvent.put("navigation_bar_name", mCategoryViewModel.selectedCategoryName)
trackEvent.put("game_tag", mCategoryViewModel.selectedCategoryList.map { it.name })
trackEvent.put("navigation_bar_name", mCategoryViewModel.selectedSidebarsName)
trackEvent.put(
"game_tag",
(mCategoryViewModel.selectedSubCategories.value ?: listOf())
.map {
it.category.name
})
trackEvent.put("game_status", gameEntity.category)
trackEvent.put(
"inclusion_size",
@ -227,22 +236,18 @@ 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
BindingAdapters.setGameName(gameName, gameEntity, false)
BindingAdapters.setGameTags(labelList, gameEntity)
BindingAdapters.setGameTags(labelList, gameEntity, "")
gameRating.setDrawableStart(if (gameEntity.commentCount > 3) com.gh.gamecenter.feature.R.drawable.game_horizontal_rating.toDrawable() else null)
gameRating.text = if (gameEntity.commentCount > 3) {
if (gameEntity.star == 10.0F) "10" else gameEntity.star.toString()
@ -266,24 +271,34 @@ class CategoryV2ListAdapter(
binding.gameKaifuType.visibility = View.GONE
binding.gameKaifuType.text = ""
}
serverLabel != null -> {
binding.gameKaifuType.visibility = View.VISIBLE
binding.gameKaifuType.text = serverLabel.value
if (gameEntity.isUseDefaultServerStyle()) {
binding.gameKaifuType.background =
com.gh.gamecenter.feature.R.drawable.server_label_default_bg.toDrawable(binding.root.context)
binding.gameKaifuType.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(binding.root.context))
binding.gameKaifuType.setTextColor(
com.gh.gamecenter.common.R.color.text_secondary.toColor(
binding.root.context
)
)
} else {
binding.gameKaifuType.background = DrawableView.getServerDrawable(serverLabel.color)
binding.gameKaifuType.setTextColor(com.gh.gamecenter.common.R.color.white.toColor(binding.root.context))
}
}
else -> binding.gameKaifuType.visibility = View.GONE
}
// 由于RecyclerView的复用机制 需要每次测量gameName的宽
binding.gameName.requestLayout()
}
override fun provideExposureData(): ExposureEvent? {
return boundedGameEntity?.exposureEvent?.getFreshExposureEvent()
}
}
inner class CategoryGameViewHolder(val binding: CategoryGameItemBinding) : GameViewHolder(binding.root) {

View File

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

View File

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

View File

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

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