Compare commits

..

128 Commits

Author SHA1 Message Date
cef4526b35 stash 2025-04-08 17:33:06 +08:00
0e9301f4dc chore: unify dependency version 2025-04-07 17:14:29 +08:00
8436b2e3aa feat: init sample page 2025-04-07 17:05:26 +08:00
c57af24811 feat: revamped apk manager 2025-03-13 11:01:51 +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
69241c489b chore: 版本更新至 5.40.0 2025-03-04 16:23:00 +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
d0bf23ae48 Merge remote-tracking branch 'origin/release' into dev 2025-01-17 14:15:55 +08:00
8b1f35516d feat: 第一次包名列表获取成功时初始化浏览器安装条件,提高下载按钮反应速度 2025-01-17 14:14:49 +08:00
b2fde1e0af Merge branch 'hotfix/v5.38.8-1118/meta_build_script_channel_issue' into 'release'
fix: 修复推广打包渠道不生效的问题

See merge request halo/android/assistant-android!2045
2025-01-16 16:01:39 +08:00
ab1350ff46 fix: 修复推广打包渠道不生效的问题 2025-01-16 16:00:25 +08:00
7f991e29d4 Merge branch 'fix/xiaomi_parallel_game_auth' into 'dev'
fix: 修复小米部分机型双开由于缺少权限无法授权登录的问题

See merge request halo/android/assistant-android!2044
2025-01-16 11:04:40 +08:00
40edf76aed fix: 修复小米部分机型双开由于缺少权限无法授权登录的问题 2025-01-16 11:04:40 +08:00
aaeb83c5df Merge branch 'fix/splash_flicker_on_miui' into 'dev'
fix: 修复部分小米设备修改状态栏颜色导致开屏广告闪烁的问题

See merge request halo/android/assistant-android!2043
2025-01-16 09:29:05 +08:00
5739f0a800 fix: 修复部分小米设备修改状态栏颜色导致开屏广告闪烁的问题 2025-01-15 17:58:16 +08:00
10cc81e7fc Merge branch 'feat/va-relative' into 'dev'
feat: 同步va的release代码

See merge request halo/android/assistant-android!2042
2025-01-15 15:00:42 +08:00
60a50f5722 feat: 同步va的release代码 2025-01-15 14:58:32 +08:00
644881c14f Merge branch 'fix/package_check_stuck_issue' into 'dev'
fix: 修复包名检测弹窗弹出较慢的问题

See merge request halo/android/assistant-android!2041
2025-01-15 14:48:19 +08:00
a0e43930a0 Merge remote-tracking branch 'origin/release' into dev
# Conflicts:
#	app/src/main/java/com/gh/ad/AdDelegateHelper.kt
#	dependencies.gradle
#	module_login/src/main/java/com/gh/gamecenter/login/user/UserRepository.java
#	vasdk
2025-01-15 14:27:40 +08:00
aa5f6f4f24 fix: 修复包名检测弹窗弹出较慢的问题 2025-01-15 14:22:22 +08:00
ab7668fd81 chore: 版本更新至 5.38.8 2025-01-15 09:19:40 +08:00
c6f014c984 Merge branch 'hotfix/v5.38.7-1117/pre_download_error' into 'release'
fix: 修复预下载获取资源时,服务端报错未能正确处理的问题

See merge request halo/android/assistant-android!2040
2025-01-14 16:30:31 +08:00
c215bd195e fix: 修复预下载获取资源时,服务端报错未能正确处理的问题 2025-01-14 16:27:43 +08:00
bb73598a87 Merge branch 'hotfix/va-relative' into 'release'
fix: va_core进程没有启动导致aidl的NPE问题。

See merge request halo/android/assistant-android!2039
2025-01-14 16:24:52 +08:00
c6f70d1b4c fix: va_core进程没有启动导致aidl的NPE问题。 2025-01-14 16:23:01 +08:00
ce7f75976c Merge branch 'fix/GHZSCY-7371' into 'dev'
fix: 游戏详情-多版本插件下载问题 https://jira.shanqu.cc/browse/GHZSCY-7371

See merge request halo/android/assistant-android!2038
2025-01-14 13:45:06 +08:00
c8a7999990 fix: 游戏详情-多版本插件下载问题 https://jira.shanqu.cc/browse/GHZSCY-7371 2025-01-14 10:49:52 +08:00
0cd281a53c Merge branch 'hotfix/va-relative' into 'release'
fix: 手机不支持armeabi-v7a架构时,根据游戏来选择架构

See merge request halo/android/assistant-android!2037
2025-01-10 18:01:24 +08:00
e92d89d498 fix: 手机不支持armeabi-v7a架构时,根据游戏来选择架构 2025-01-10 17:44:03 +08:00
a22858389b chore: 版本更新至 5.38.7 2025-01-10 13:55:24 +08:00
e9d091043d Merge branch 'hotfix/va-relative' into 'release'
feat: 同步商业版VA,修复权限问题。

See merge request halo/android/assistant-android!2036
2025-01-10 11:25:46 +08:00
db4ac95094 feat: 同步商业版VA,修复权限问题。
fix: 实名认证超大UI问题
fix: sentry va relative log.
2025-01-10 11:24:45 +08:00
13be47d440 Merge branch 'fix/search-page-crash' into 'release'
fix:https://sentry.shanqu.cc/organizations/lightgame/issues/423398/?project=22

See merge request halo/android/assistant-android!2035
2025-01-10 11:21:43 +08:00
beee098cfe fix:https://sentry.shanqu.cc/organizations/lightgame/issues/423398/?project=22 2025-01-10 11:17:26 +08:00
d67aaf956b Merge branch 'feat/GHZSCY-7046' into 'release'
feat: 补充启动广告图相关广告加载事件埋点—客户端 https://jira.shanqu.cc/browse/GHZSCY-7046

See merge request halo/android/assistant-android!2034
2025-01-09 17:29:21 +08:00
1ed9151b1f feat: 补充启动广告图相关广告加载事件埋点—客户端 https://jira.shanqu.cc/browse/GHZSCY-7046 2025-01-09 16:43:54 +08:00
13f20f6883 Merge branch 'hotfix/v5.38.6-1116/ad_view_background' into 'release'
fix: 还原自有开屏广告的背景颜色 https://jira.shanqu.cc/browse/GHZSCY-7360

See merge request halo/android/assistant-android!2033
2025-01-09 15:28:06 +08:00
78e320a192 fix: 还原自有开屏广告的背景颜色 https://jira.shanqu.cc/browse/GHZSCY-7360 2025-01-09 15:27:34 +08:00
e51db47fad Merge branch 'cherry-pick-f8a26ece' into 'release'
Merge branch 'fix/GHZSCY-7277' into 'dev'

See merge request halo/android/assistant-android!2032
2025-01-09 15:24:18 +08:00
ac02ea88b9 Merge branch 'fix/GHZSCY-7277' into 'dev'
修复错误的下载错误提示 https://jira.shanqu.cc/browse/GHZSCY-7277

See merge request halo/android/assistant-android!2025

(cherry picked from commit f8a26ece01)

54ee3ea3 fix: 游戏下载异常提示 https://jira.shanqu.cc/browse/GHZSCY-7277

Co-authored-by: 陈君陶 <chenjuntao@ghzhushou.com>
2025-01-09 15:23:53 +08:00
f60004fc81 Merge branch 'feat/GHZSCY-7359' into 'release'
需求合并

See merge request halo/android/assistant-android!2031
2025-01-09 15:05:29 +08:00
0cf39a82e2 Merge branch 'fix/viewstub_crashes' into 'release'
fix: 修复ViewStub偶发闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/422108/events/?project=22 https://sentry.shanqu.cc/organizations/lightgame/issues/425087/?project=22

See merge request halo/android/assistant-android!2030
2025-01-09 15:03:51 +08:00
315f244153 fix:【光环助手】包名检测及搜索页面在深色模式下的显示问题 https://jira.shanqu.cc/browse/GHZSCY-6985 2025-01-09 14:54:37 +08:00
f4bdc02d70 fix: 【光环助手】深色模式显示问题 https://jira.shanqu.cc/browse/GHZSCY-7121 2025-01-09 14:54:17 +08:00
aef39eb481 feat: 样式组件优化汇总—客户端 https://jira.shanqu.cc/browse/GHZSCY-6866 2025-01-09 14:53:37 +08:00
dc2e7147d9 fix: 修复ViewStub偶发闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/422108/events/?project=22 https://sentry.shanqu.cc/organizations/lightgame/issues/425087/?project=22 2025-01-09 14:49:58 +08:00
fdcb6342bf Merge branch 'hotfix/v5.38.6-1116/crashes' into 'release'
修复部分 5.38.6 线上闪退

See merge request halo/android/assistant-android!2029
2025-01-09 14:37:49 +08:00
b0da4f8986 Merge branch 'hotfix/v5.38.6-1116/blank_ad_view' into 'release'
fix: 优化开屏广告的显示,提高线程池的 core 线程数,限制视频开屏广告的触摸控制

See merge request halo/android/assistant-android!2028
2025-01-09 14:37:40 +08:00
f7cc906cc5 fix: 优化开屏广告的显示,提高线程池的 core 线程数,限制视频开屏广告的触摸控制 2025-01-09 14:37:40 +08:00
795fbabd90 Merge branch 'hotfix/v5.38.6-1116/login_culprit' into 'release'
修复`我的光环`页面在用户信息接口异常时的显示问题 https://jira.shanqu.cc/browse/GHZSCY-7340

See merge request halo/android/assistant-android!2027
2025-01-09 14:34:25 +08:00
e770f8a359 fix: 修复安装完成后更新安装信息时的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/395511 2025-01-09 11:49:38 +08:00
dd12b103be fix: 捕抓上报游戏启动日志的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/406179 2025-01-09 09:59:32 +08:00
21f4a398d5 fix: 处理数组越界闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/408904 2025-01-09 09:45:22 +08:00
f8a26ece01 Merge branch 'fix/GHZSCY-7277' into 'dev'
修复错误的下载错误提示 https://jira.shanqu.cc/browse/GHZSCY-7277

See merge request halo/android/assistant-android!2025
2025-01-08 10:10:32 +08:00
54ee3ea376 fix: 游戏下载异常提示 https://jira.shanqu.cc/browse/GHZSCY-7277 2025-01-07 17:56:09 +08:00
8dfb1644a8 fix: 登录状态显示问题 https://jira.shanqu.cc/browse/GHZSCY-7340 2025-01-07 17:48:53 +08:00
9744b95126 Merge branch 'fix/viewstub_crash' into 'release'
fix: 修复ViewStub偶发闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/423818/?project=22

See merge request halo/android/assistant-android!2019
2024-12-27 15:14:25 +08:00
009608165c Merge branch 'fix-issues-421826' into 'release'
fix:https://sentry.shanqu.cc/organizations/lightgame/issues/421826/?project=22

See merge request halo/android/assistant-android!2018
2024-12-27 14:02:39 +08:00
98356a7dd7 fix: 修复ViewStub偶发闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/423818/?project=22 2024-12-27 13:56:10 +08:00
8389041379 fix:https://sentry.shanqu.cc/organizations/lightgame/issues/421826/?project=22 2024-12-27 13:49:29 +08:00
494 changed files with 26092 additions and 8944 deletions

View File

@ -346,7 +346,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 +402,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'
}
@ -515,6 +515,7 @@ dependencies {
}
implementation(project(':feature:media_select'))
implementation(project(':feature:apk_manager'))
implementation(project(":module_va_api"))
implementation(project(":va-archive-common"))
@ -526,6 +527,18 @@ 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"))
}
}
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

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>
@ -83,29 +83,9 @@
tv.danmaku.ijk.media.exo2,
pl.droidsonroids.gif,
com.lzf.easyfloat,
com.airbnb.lottie.compose,
androidx.compose.ui.platform,
androidx.compose.material.icons,
androidx.activity.compose,
androidx.compose.ui.tooling,
androidx.compose.ui.tooling.data,
androidx.compose.material.ripple,
androidx.compose.foundation,
androidx.compose.animation,
androidx.compose.foundation.layout,
androidx.compose.ui.text,
androidx.compose.ui.graphics,
androidx.compose.ui.unit,
androidx.compose.ui.util,
androidx.compose.ui.geometry,
androidx.compose.runtime.saveable,
androidx.compose.animation.core,
androidx.constraintlayout.compose,
androidx.compose.ui.test.manifest,
com.bytedance.sdk.openadsdk,
com.bykv.vk.openvk,
com.bytedance.tools,
androidx.compose.ui.tooling.preview,
com.tencent.qqmini,
com.tencent.qqmini.minigame.external,
com.tencent.qqmini.minigame.opensdk,
@ -197,7 +177,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 +796,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"-->

View File

@ -4,6 +4,7 @@ import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.SharedPreferences
import android.graphics.drawable.Animatable
import android.os.Message
import android.text.TextUtils
import android.view.View
@ -13,19 +14,21 @@ import android.widget.TextView
import androidx.appcompat.content.res.AppCompatResources
import androidx.fragment.app.Fragment
import com.therouter.TheRouter
import com.facebook.drawee.controller.BaseControllerListener
import com.facebook.drawee.view.SimpleDraweeView
import com.facebook.imagepipeline.image.ImageInfo
import com.gh.common.exposure.ExposureManager
import com.gh.common.util.DirectUtils.directToLinkPage
import com.gh.common.util.LogUtils
import com.gh.common.util.NewFlatLogUtils.logOpenScreenAdSkip
import com.gh.common.util.NewFlatLogUtils
import com.gh.common.util.PackageUtils
import com.gh.gamecenter.BuildConfig
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
@ -59,6 +62,8 @@ object AdDelegateHelper {
private val mGameSearchAdList: ArrayList<AdConfig> by lazy { arrayListOf() }
private var mVGameLaunchAd: AdConfig? = null
private var ownerSplashAdLoadTime = 0L
val vGameLaunchAd: AdConfig?
get() = mVGameLaunchAd
@ -76,6 +81,7 @@ object AdDelegateHelper {
}
var isShowingSplashAd = false // 是否正在显示开屏广告
var isOwnerSplashAdShown = false // 自有开屏广告是否展示
var gameSearchKeyword = ""
fun initAdSdk(context: Context) {
@ -172,6 +178,15 @@ object AdDelegateHelper {
when (config.location) {
"halo_launch" -> {
config.ownerAd?.startAd?.let { it.id = config.ownerAd.id }
// HarmonyOS 2.2.0 版本不展示第三方开屏广告 (因为会引起奇怪的闪退)
if (MetaUtil.getRom().name == "HarmonyOS"
&& MetaUtil.getRom().versionName == "2.2.0"
&& config.displayRule.adSource == "third_party_ads") {
return
}
mSplashAd = config
}
@ -302,6 +317,7 @@ object AdDelegateHelper {
) {
val hideCallback = {
isShowingSplashAd = false
isOwnerSplashAdShown = false
hideAction.invoke()
}
if (mSplashAd != null) {
@ -574,6 +590,8 @@ object AdDelegateHelper {
handler: BaseActivity.BaseHandler,
hideCallback: () -> Unit
) {
isOwnerSplashAdShown = false
val jumpBtn = startAdContainer.findViewById<TextView>(R.id.jumpBtn)
val jumpDetailBtn: TextView = startAdContainer.findViewById(R.id.jumpDetailBtn)
val adImage: SimpleDraweeView = startAdContainer.findViewById(R.id.adImage)
@ -592,38 +610,62 @@ object AdDelegateHelper {
)
adImage.visibleIf(true)
ImageUtils.display(adImage, ad.img)
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)
} else {
adVideo.visibleIf(true)
adVideo.startPlay(ad.video.url)
}
startAdContainer.setOnClickListener {
// 拦截点击事件传递
}
jumpBtn.setOnClickListener {
handler.removeMessages(MainActivity.COUNTDOWN_AD)
hideCallback.invoke()
val linkEntity = ad.jump
logOpenScreenAdSkip(
ad.id,
(if (linkEntity.text != null) linkEntity.text else "")!!,
(if (linkEntity.type != null) linkEntity.type else "")!!,
(if (linkEntity.link != null) linkEntity.link else "")!!
)
SensorsBridge.trackEvent(
"SplashAdOwnSkip",
"splash_ad_id",
ad.id,
"link_type",
linkEntity.type ?: "",
"link_id",
linkEntity.link ?: "",
"link_text",
linkEntity.text ?: ""
)
it.debounceActionWithInterval(1000L) {
if (!isOwnerSplashAdShown) {
NewFlatLogUtils.logSplashAdFail(ad.id, "加载过程中点击跳过广告")
}
handler.removeMessages(MainActivity.COUNTDOWN_AD)
hideCallback.invoke()
val linkEntity = ad.jump
NewFlatLogUtils.logOpenScreenAdSkip(
ad.id,
(if (linkEntity.text != null) linkEntity.text else "")!!,
(if (linkEntity.type != null) linkEntity.type else "")!!,
(if (linkEntity.link != null) linkEntity.link else "")!!
)
SensorsBridge.trackEvent(
"SplashAdOwnSkip",
"splash_ad_id",
ad.id,
"link_type",
linkEntity.type ?: "",
"link_id",
linkEntity.link ?: "",
"link_text",
linkEntity.text ?: ""
)
}
}
val sources: MutableList<ExposureSource> = ArrayList()
sources.add(ExposureSource("开屏广告", ad.id))

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

@ -48,6 +48,12 @@ class SplashAdVideoView @JvmOverloads constructor(
// no nothing
}
override fun onPrepared() {
super.onPrepared()
visibility = VISIBLE
}
override fun onAutoCompletion() {
setStateAndUi(CURRENT_STATE_AUTO_COMPLETE);

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

@ -11,7 +11,6 @@ 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 com.gh.common.exposure.ExposureManager
import com.gh.common.util.*
import com.gh.common.util.LogUtils
@ -22,7 +21,8 @@ import com.gh.gamecenter.ImageViewerActivity
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 +30,22 @@ 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.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 +57,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
@ -75,6 +81,7 @@ class DefaultJsApi(
private var mBbsId: String? = "",
private var mOriginUrl: String? = "",
private val mForumName: String? = "",
private val listener: OnWebClickListener? = null
) {
companion object {
@ -699,6 +706,78 @@ 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 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可能为空
*/
@ -815,4 +894,15 @@ class DefaultJsApi(
}
}
}
interface OnWebClickListener {
fun onPreOrderWithAli(order: OrderEntity)
fun onPreOrderWithWechat(order: OrderEntity)
fun onStartGameAccelerate(accInfo: AcctRecordEntity.AccInfo)
fun onStopGameAccelerate()
}
}

View File

@ -4,6 +4,7 @@ import android.app.Activity
import android.content.Context
import com.gh.common.util.DialogUtils
import com.gh.common.util.DirectUtils
import com.gh.common.util.PackageChangeHelper
import com.gh.common.util.TempCertificationUtils
import com.gh.gamecenter.feature.entity.GameEntity
@ -25,6 +26,10 @@ class LandPageAddressHandler : DownloadChainHandler() {
processEndCallback?.invoke(asVGame, null)
}
} else {
val packageName = gameEntity.getApk().firstOrNull()?.packageName
if (packageName?.isNotEmpty() == true) {
PackageChangeHelper.addInstallPendingPackage(packageName)
}
DirectUtils.directToExternalBrowser(context, gameEntity.landPageAddressDialog!!.link!!)
}
}

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

@ -201,7 +201,8 @@ public class BindingAdapters {
gameEntity,
traceEvent,
entrance,
location + ":" + gameEntity.getName());
location + ":" + gameEntity.getName(),
null);
return null;
});
final DownloadChainHandler chainHandler = builder.buildHandlerChain();
@ -246,7 +247,8 @@ public class BindingAdapters {
gameEntity,
traceEvent,
entrance,
location + ":" + gameEntity.getName());
location + ":" + gameEntity.getName(),
null);
}
break;
case INSTALL_PLUGIN:

View File

@ -3,7 +3,6 @@ package com.gh.common.dialog
import android.animation.ValueAnimator
import android.content.Context
import android.content.DialogInterface
import android.content.pm.PackageInfo
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
@ -19,7 +18,6 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.DirectUtils
import com.gh.common.util.LogUtils
import com.gh.common.util.PackageHelper
import com.gh.common.util.PackageUtils
import com.gh.download.DownloadManager
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
@ -59,7 +57,6 @@ class PackageCheckDialogFragment : BaseDialogFragment() {
private val mDuration = 3000
private var mDisposable: Disposable? = null
private var mAdapter: PackageCheckAdapter? = null
private var mAllInstalledPackages = PackageHelper.getInstalledPackageNameList(HaloApp.getInstance().application, 0)
var gameEntity: GameEntity? = null
var callBack: ConfirmListener? = null
@ -195,7 +192,7 @@ class PackageCheckDialogFragment : BaseDialogFragment() {
if (binding.noRemindAgainCb.isChecked) {
saveRecord(entity)
}
val isAllPackageInstalled = isAllPackageInstalled(mAllInstalledPackages, entity)
val isAllPackageInstalled = isAllPackageInstalled(entity)
if (isAllPackageInstalled) {
mDismissByTouchInside = true
callBack?.onConfirm()
@ -299,7 +296,7 @@ class PackageCheckDialogFragment : BaseDialogFragment() {
private fun getNotInstalledLink(packageDialogEntity: PackageDialogEntity): LinkEntity? {
val links = LinkedHashSet<LinkEntity>()
packageDialogEntity.detectionObjects.forEach { obj ->
if (!checkDetectionsInstalled(mAllInstalledPackages, obj.packages)) {
if (!checkDetectionsInstalled(obj.packages)) {
obj.assignDownload.forEach {
links.add(packageDialogEntity.links[it])
}
@ -325,9 +322,8 @@ class PackageCheckDialogFragment : BaseDialogFragment() {
override fun onResume() {
super.onResume()
mAllInstalledPackages = PackageHelper.getInstalledPackageNameList(HaloApp.getInstance().application, 0)
gameEntity?.packageDialog?.let {
if (isAllPackageInstalled(mAllInstalledPackages, it)) {
if (isAllPackageInstalled(it)) {
callBack?.onConfirm()
dismissAllowingStateLoss()
}
@ -363,7 +359,6 @@ class PackageCheckDialogFragment : BaseDialogFragment() {
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(busFour: EBPackage) {
if (busFour.isInstalledOrUninstalled()) {
mAllInstalledPackages = PackageHelper.getInstalledPackageNameList(HaloApp.getInstance().application, 0)
mAdapter?.notifyDataSetChanged()
}
}
@ -388,7 +383,7 @@ class PackageCheckDialogFragment : BaseDialogFragment() {
val entity = entities[position]
holder.binding.gameNameTv.text = entity.text
if (position <= index) {
val isAllInstalled = checkDetectionsInstalled(mAllInstalledPackages, entity.packages)
val isAllInstalled = checkDetectionsInstalled(entity.packages)
if (isAllInstalled) {
holder.binding.statusTv.text = "已安装"
holder.binding.statusTv.setTextColor(ContextCompat.getColor(context, com.gh.gamecenter.common.R.color.text_theme))
@ -416,8 +411,7 @@ class PackageCheckDialogFragment : BaseDialogFragment() {
return
}
val allInstalledPackages = PackageHelper.getInstalledPackageNameList(HaloApp.getInstance().application, 0)
if (isAllPackageInstalled(allInstalledPackages, packageDialogEntity)) {
if (isAllPackageInstalled(packageDialogEntity)) {
callBack.onConfirm()
return
}
@ -453,13 +447,11 @@ class PackageCheckDialogFragment : BaseDialogFragment() {
}
private fun checkDetectionsInstalled(
allInstalledPackages: List<String>,
packages: ArrayList<String>
): Boolean {
var isPackagesInstalled = false
packages.forEach { packageName ->
val isInstalled = allInstalledPackages.find { it == packageName } != null
if (isInstalled) {
if (PackageUtils.isInstalledFromAllPackage(HaloApp.getInstance(), packageName)) {
isPackagesInstalled = true
return@forEach
}
@ -469,17 +461,14 @@ class PackageCheckDialogFragment : BaseDialogFragment() {
fun isAllPackageInstalled(
allInstalledPackages: List<String>,
packageDialogEntity: PackageDialogEntity
): Boolean {
var isAllInstalled = true
packageDialogEntity.detectionObjects.forEach loop@{ obj ->
if (!checkDetectionsInstalled(allInstalledPackages, obj.packages)) {
isAllInstalled = false
return isAllInstalled
if (!checkDetectionsInstalled(obj.packages)) {
return false
}
}
return isAllInstalled
return true
}
}
}

View File

@ -17,7 +17,7 @@ import com.gh.gamecenter.common.entity.ErrorEntity
import com.gh.gamecenter.core.utils.CurrentActivityHolder
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.databinding.DialogWechatBindingFailedBinding
import com.gh.gamecenter.login.user.UserRepository
import com.gh.gamecenter.login.user.UserManager
import com.lightgame.utils.Utils
class WechatBindingFailedDialogFragment : BaseDialogFragment() {
@ -48,10 +48,10 @@ class WechatBindingFailedDialogFragment : BaseDialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
UserRepository.getInstance().loginUserInfo.observe(viewLifecycleOwner) {
currentUserId = it.data.getShortUserId()
binding.tvCurrentName.text = it.data.name
binding.ivCurrentAvatar.displayAvatar(it.data.icon)
UserManager.getInstance().userInfoEntity?.let {
currentUserId = it.getShortUserId()
binding.tvCurrentName.text = it.name ?: ""
binding.ivCurrentAvatar.displayAvatar(it.icon)
binding.tvUserId.text = getString(R.string.user_id, currentUserId)
}

View File

@ -10,16 +10,12 @@ fun FragmentManager.popBackStackAllowStateLoss() {
fun FragmentManager.popBackStackAllowStateLoss(id: Int, flags: Int) {
if (!isStateSaved) {
popBackStack(id, flags)
} else {
hook { popBackStack(id, flags) }
}
}
fun FragmentManager.popBackStackAllowStateLoss(name: String?, flags: Int) {
if (!isStateSaved) {
popBackStack(name, flags)
} else {
hook { popBackStack(name, flags) }
}
}
@ -29,34 +25,9 @@ fun FragmentManager.popBackStackImmediateAllowStateLoss(id: Int, flags: Int) =
if (!isStateSaved) {
popBackStackImmediate(id, flags)
} else {
hook { popBackStackImmediate(id, flags) }
false
}
fun FragmentManager.popBackStackImmediateAllowStateLoss(name: String?, flags: Int): Boolean =
if (!isStateSaved) {
popBackStackImmediate(name, flags)
} else {
hook { popBackStackImmediate(name, flags) }
}
/**
* 通过反射将FragmentManager的mStateSaved和mStopped设为false否则Activity在回调onSavedInstance以后
* 调用Fragment的popBackStack和popBackStackImmediate方法会触发“java.lang.IllegalStateException: Can not perform this action after onSaveInstanceState”的异常。
* @see <a href="https://sentry.shanqu.cc/organizations/lightgame/issues/418688/?project=22">Sentry-418688</a>
*/
private fun <T> FragmentManager.hook(callback: FragmentManager.() -> T): T {
val mStateSavedField = getField(this::class.java,"mStateSaved")
val stateSaved = mStateSavedField.get(this);
mStateSavedField.set(this, false)
val mStoppedField = getField(this::class.java,"mStopped")
val stopped = mStateSavedField.get(this);
mStoppedField.set(this, false)
val result = callback.invoke(this)
mStateSavedField.set(this, stateSaved)
mStoppedField.set(this, stopped)
return result
}
@Throws(NoSuchFieldException::class)
private fun getField(clazz: Class<*>, name: String): Field {
var cls: Class<*>? = clazz

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,18 @@
package com.gh.common.provider
import com.gh.gamecenter.core.provider.IAcceleratorDataHolderProvider
import com.gh.gamecenter.feature.entity.VipEntity
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
@com.therouter.inject.ServiceProvider
class IAcceleratorDataHolderProviderImpl : IAcceleratorDataHolderProvider {
override fun setVipEntity(vip: Any) {
if (vip is VipEntity) {
AcceleratorDataHolder.instance.setVipEntity(vip)
}
}
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,54 @@
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.entity.VipEntity
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.setVipEntity(
VipEntity(
_vipStatus = true,
_isNewUser = false,
_isTryVip = false
)
)
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

@ -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;
@ -150,6 +149,7 @@ public class DataUtils {
/**
* 获取应用 gid 绑定的实名信息
*/
// TODO 这个方法启动时会被调用多次,后面考虑优化优化
@SuppressLint("CheckResult")
public static void getDeviceCertification(String gid) {
RetrofitManager.getInstance()

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,8 @@ public class DetailDownloadUtils {
viewHolder.getMultiVersionDownloadTv().setVisibility(View.GONE);
}
viewHolder.setSpeedViewsVisible(false);
// 根据预置的配置更新 ViewHolder 的状态 (譬如青少年模式、下载内容为空等)
if (updateViewHolderWithPredefinedConfig(viewHolder, gameEntity)) {
return;
@ -135,6 +138,12 @@ public class DetailDownloadUtils {
showDualDownloadButton,
downloadEntity
);
if(!showVGame){
String rawBtnText = GameUtils.getDownloadBtnText(viewHolder.getContext(), gameEntity, false, showVGame, PluginLocation.only_game);
viewHolder.checkIfShowSpeedUi(rawBtnText);
}
} else {
// 游戏包含多 APK 的情况
viewHolder.getMultiVersionDownloadTv().setText("选择下载你的版本" + (TextUtils.isEmpty(downloadAddWord) ? "" : "-" + downloadAddWord));
@ -219,7 +228,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 +270,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 +610,30 @@ public class DetailDownloadUtils {
return (int) Math.ceil(downloadEntity.getPercent());
}
private 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
}
@ -849,30 +857,37 @@ object DirectUtils {
traceEvent: ExposureEvent? = 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)
.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 +918,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 +926,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)
}
// 专栏
@ -1569,7 +1590,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 +1703,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 +2315,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

@ -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 =
@ -425,22 +455,24 @@ object DownloadItemUtils {
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)
}
}
@ -510,13 +542,18 @@ object DownloadItemUtils {
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
@ -961,7 +998,8 @@ object DownloadItemUtils {
traceEvent: ExposureEvent? = 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 +1018,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 +1046,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 +1074,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)

View File

@ -53,6 +53,7 @@ object DownloadObserver {
private const val CORE_EVENT_DOWNLOAD_COMPLETE_LOGGED = "CORE_EVENT_DOWNLOAD_COMPLETE_LOGGED"
private val mRetryableHashMap = hashMapOf<String, Boolean>()
private val mRetryableProgressMap = hashMapOf<String, Long>()
/**
* 当下载任务是 预约上线提醒 触发的,则所有弹窗均不显示
@ -146,7 +147,8 @@ object DownloadObserver {
|| DownloadStatus.timeout == status
) {
if (mRetryableHashMap[downloadEntity.url] == true
&& NetworkUtils.isWifiConnected(HaloApp.getInstance().application)
&& (NetworkUtils.isWifiConnected(HaloApp.getInstance().application)
|| NDownloadBridge.isDownloadViaTrafficAllowed(downloadEntity))
) {
downloadManager.resumeDownload(downloadEntity.url)
mRetryableHashMap[downloadEntity.url] = false
@ -257,6 +259,7 @@ object DownloadObserver {
}
mRetryableHashMap.remove(downloadEntity.url)
mRetryableProgressMap.remove(downloadEntity.url)
EventBus.getDefault().post(EBDownloadStatus("done", "", "", "", downloadEntity.packageName, ""))
}
@ -264,7 +267,9 @@ object DownloadObserver {
DownloadNotificationHelper.addOrUpdateDownloadNotification(downloadEntity)
// 如果已下载大小发生变化,表示成功恢复下载,则重置重试标记
if (status == DownloadStatus.downloading) {
if (status == DownloadStatus.downloading
&& downloadEntity.progress != mRetryableProgressMap[downloadEntity.url]) {
mRetryableProgressMap[downloadEntity.url] = downloadEntity.progress
mRetryableHashMap[downloadEntity.url] = true
}
}

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 ""
}
@ -138,7 +145,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;
@ -480,7 +481,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;
@ -620,7 +621,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) {

View File

@ -2778,4 +2778,34 @@ object NewFlatLogUtils {
parseAndPutMeta()(this)
}.let(::log)
}
// 自有开屏广告加载
fun logSplashAdLoad(id: String) {
json {
KEY_EVENT to "splash_ad_load"
"ad_id" to id
parseAndPutMeta()(this)
}.let(::log)
}
// 自有开屏广告展示
fun logSplashAdShow(id: String, duration: Long) {
json {
KEY_EVENT to "splash_ad_show"
"ad_id" to id
"duration" to duration
parseAndPutMeta()(this)
}.let(::log)
}
// 自有开屏广告加载/展示失败
@JvmStatic
fun logSplashAdFail(id: String, error: String) {
json {
KEY_EVENT to "splash_ad_fail"
"ad_id" to id
"error" to error
parseAndPutMeta()(this)
}.let(::log)
}
}

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
@ -459,6 +459,9 @@ object PackageHelper {
PackageRepository.initData {
refreshLocalPackageList()
refreshPackageNameList()
// 初始化使用浏览器安装的条件
BrowserInstallHelper.initIfConditionMatched(Config.getNewSettingsEntity())
}
}
@ -578,7 +581,6 @@ object PackageHelper {
Utils.log(TAG, "refreshWrongInstallStatus 检查安装状态异常的应用")
val uninstalledButKeepingWrongStatusPackageNameSet: HashSet<String> = hashSetOf()
val updatedButKeepingWrongStatusPackageNameSet: HashSet<String> = hashSetOf()
for (packageName in packageNameSet) {
val installedVersionName = PackageUtils.getVersionNameByPackageName(packageName)
@ -587,18 +589,9 @@ object PackageHelper {
&& installedVersionName == null
) {
uninstalledButKeepingWrongStatusPackageNameSet.add(packageName)
} else if (PackagesManager.isInstalled(packageName)
&& installedVersionName != null
&& !PackagesManager.isInstalledWithSpecificVersion(packageName, installedVersionName)
) {
updatedButKeepingWrongStatusPackageNameSet.add(packageName)
}
}
Utils.log(
TAG,
"refreshWrongInstallStatus 需要更新已更新状态的包数量为 ${updatedButKeepingWrongStatusPackageNameSet.size}"
)
Utils.log(
TAG,
"refreshWrongInstallStatus 需要移除已安装的包数量为 ${uninstalledButKeepingWrongStatusPackageNameSet.size}"
@ -616,12 +609,6 @@ object PackageHelper {
additionalWhiteListPackageNameSet.toString()
)
}
if (updatedButKeepingWrongStatusPackageNameSet.isNotEmpty()) {
for (packageName in updatedButKeepingWrongStatusPackageNameSet) {
PackageChangeHelper.addUpdate(packageName)
}
}
}
}
}
@ -635,7 +622,6 @@ object PackageHelper {
val installedButKeepingWrongStatusPackageNameSet: HashSet<String> = hashSetOf()
val uninstalledButKeepingWrongStatusPackageNameSet: HashSet<String> = hashSetOf()
val updatedButKeepingWrongStatusPackageNameSet: HashSet<String> = hashSetOf()
for (game in gameEntityList) {
for (apk in game.getApk()) {
@ -651,13 +637,6 @@ object PackageHelper {
&& installedVersionName == null
) {
uninstalledButKeepingWrongStatusPackageNameSet.add(packageName)
} else if (PackagesManager.isInstalled(packageName)
&& installedVersionName != null
&& !PackagesManager.isInstalledWithSpecificVersion(packageName, installedVersionName)
&& !PackagesManager.isCanUpdate(game.id, packageName, false)
) {
cachedPkgNameAndGameEntityMap.put(packageName, game)
updatedButKeepingWrongStatusPackageNameSet.add(packageName)
}
}
}
@ -666,10 +645,6 @@ object PackageHelper {
TAG,
"refreshWrongInstallStatus 需要更新已安装状态的包数量为 ${installedButKeepingWrongStatusPackageNameSet.size}"
)
Utils.log(
TAG,
"refreshWrongInstallStatus 需要更新已更新状态的包数量为 ${updatedButKeepingWrongStatusPackageNameSet.size}"
)
Utils.log(
TAG,
"refreshWrongInstallStatus 需要移除已安装的包数量为 ${uninstalledButKeepingWrongStatusPackageNameSet.size}"
@ -699,12 +674,6 @@ object PackageHelper {
additionalWhiteListPackageNameSet.toString()
)
}
if (updatedButKeepingWrongStatusPackageNameSet.isNotEmpty()) {
for (packageName in updatedButKeepingWrongStatusPackageNameSet) {
PackageChangeHelper.addUpdate(packageName, cachedPkgNameAndGameEntityMap.remove(packageName))
}
}
}
}
}

View File

@ -69,18 +69,24 @@ object PackageInstaller {
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)

View File

@ -81,28 +81,11 @@ object PackageLauncher {
gameEntity: GameEntity? = null,
packageName: String?
) {
if (packageName.isNullOrEmpty()) {
ToastUtils.toast("启动失败")
return
}
// 获取 GameInstall 实体,用于记录启动日志用
val gameInstall = if (gameEntity != null) {
GameInstall.transformGameInstall(gameEntity, packageName)
} else {
PackagesManager.getInstalledList().find { it.packageName == packageName }
}
if (gameInstall != null) {
NewFlatLogUtils.logGameLaunch(
gameId = gameInstall.id ?: "unknown",
gameName = gameInstall.name ?: "unknown",
gameCategory = gameInstall.category ?: "unknown",
downloadStatus = if (gameInstall.downloadStatus == "demo") "试玩" else "下载"
)
}
try {
val intent = context.applicationContext.packageManager.getLaunchIntentForPackage(packageName)
if (intent != null) {
@ -113,6 +96,26 @@ object PackageLauncher {
} catch (e: Exception) {
ToastUtils.toast( "启动失败")
}
try {
// 获取 GameInstall 实体,用于记录启动日志用
val gameInstall = if (gameEntity != null) {
GameInstall.transformGameInstall(gameEntity, packageName)
} else {
PackagesManager.getInstalledList().find { it.packageName == packageName }
}
if (gameInstall != null) {
NewFlatLogUtils.logGameLaunch(
gameId = gameInstall.id ?: "unknown",
gameName = gameInstall.name ?: "unknown",
gameCategory = gameInstall.category ?: "unknown",
downloadStatus = if (gameInstall.downloadStatus == "demo") "试玩" else "下载"
)
}
} catch (e: RuntimeException) {
// 都 DeadSystemException 了,还想啥日志上报
}
}
}

View File

@ -301,7 +301,7 @@ public class PackageUtils {
Signature[] signatures = packageInfo.signatures;
// 使用幸运破解器破解安卓签名认证可能会出现不用签名也能装的情况,这里有可能是空的
if (signatures[0] != null) {
if (signatures.length > 0 && signatures[0] != null) {
return parseSignature(signatures[0].toByteArray());
} else {
return new String[]{null, null};

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

@ -19,7 +19,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
@ -88,7 +88,7 @@ object ViewPagerFragmentHelper {
// 游戏详情页
TYPE_GAME -> {
bundle.putString(EntranceConsts.KEY_GAMEID, linkEntity.link)
GameDetailFragment().with(bundle)
GameDetailWrapperFragment().with(bundle)
}
// 我的光环
TYPE_MY_HALO -> {
@ -149,11 +149,11 @@ object ViewPagerFragmentHelper {
NewQuestionDetailFragment().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) {

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

@ -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包,则以下安装无效)
@ -65,6 +74,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优化关闭提示弹窗"
@ -121,13 +134,39 @@ object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
}
PermissionHelper.checkManageAllFilesOrStoragePermissionBeforeAction(context) {
DownloadManager.getInstance().getDownloadEntitySnapshot(downloadEntity.url, downloadEntity.gameId)
?.let {
unzipXapkFile(it)
if (showUnzipToast) {
Utils.toast(mContext, "解压过程请勿退出光环助手!")
val unzipAction = {
DownloadManager.getInstance().getDownloadEntitySnapshot(downloadEntity.url, downloadEntity.gameId)
?.let {
unzipXapkFile(it)
if (showUnzipToast) {
Utils.toast(mContext, "解压过程请勿退出光环助手!")
}
}
}
// XAPK (apks) 格式,不在乎 obb 文件夹是否可读
if (downloadEntity.format == Constants.XAPK_APKS_FORMAT) {
unzipAction.invoke()
return@checkManageAllFilesOrStoragePermissionBeforeAction
}
// 以 file 的方式访问 obb 文件夹是否可行
val isFileStyleObbFolderReadable =
if (systemHasFlaw) {
File(Environment.getExternalStorageDirectory().path, "\u200bAndroid/obb").list() != null
} else {
File(Environment.getExternalStorageDirectory().path, "Android/obb").list() != null
}
// 如果是文件夹风格的 obb 文件夹可读,或当前上下文不是 AppCompatActivity或当前上下文已经被销毁则直接解压
if (isFileStyleObbFolderReadable
|| context !is AppCompatActivity
|| context.isFinishing
) {
unzipAction.invoke()
} else {
unzipWithCulpritHandled(context, downloadEntity.url, unzipAction)
}
}
} else {
throwExceptionInDebug("如果是Apk包请使用PackageInstaller进行安装")
@ -135,6 +174,82 @@ object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
}
}
/**
* 兜底解压方案,适用于一些奇葩系统
*/
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) {
mXApkUnZipper.unzip(
XApkUnZipEntry(
@ -324,7 +439,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 +455,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,7 +476,12 @@ object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
mPendingSessionInfoMap[downloadEntity.path] = XapkPendingSessionInfo(downloadEntity.path, sessionId)
AppExecutor.ioExecutor.execute {// 有可能卡顿造成anr
PackageInstaller.installMultiple(applicationContext, downloadEntity.packageName, downloadEntity.path, sessionId)
PackageInstaller.installMultiple(
applicationContext,
downloadEntity.packageName,
downloadEntity.path,
sessionId
)
NDataChanger.notifyDataChanged(downloadEntity)
}
}

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,16 @@ 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.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 +45,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;
@ -1296,7 +1296,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 +1317,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

@ -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)
@ -426,7 +429,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 +456,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

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

@ -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
@ -127,10 +126,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 +194,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 +306,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 +320,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

@ -136,6 +136,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 +182,6 @@ public class MainActivity extends BaseActivity {
.get(MainWrapperViewModel.class);
DisplayUtils.transparentStatusBar(this);
DisplayUtils.updateGlobalScreen(this);
super.onCreate(savedInstanceState);
setStatusBarColor(Color.TRANSPARENT);
@ -554,25 +554,31 @@ public class MainActivity extends BaseActivity {
protected void handleMessage(Message msg) {
super.handleMessage(msg);
if (msg.what == COUNTDOWN_AD || msg.what == COUNTDOWN_SDK_AD) {
mCountdownCount++;
int maxCount;
// 第三秒的时候初始化 Fragment
if (mCountdownCount == 3) {
doInitMainFragment(mTempSavedInstanceState);
}
if (msg.what == COUNTDOWN_AD) {
maxCount = mCountdownMaxCount;
} else {
maxCount = COUNTDOWN_SDK_MAX_COUNT;
}
// 读秒到一半的时候初始化 MainWrapperFragment
if (mCountdownCount == maxCount / 2) {
doInitMainFragment(mTempSavedInstanceState);
}
mCountdownCount++;
if (maxCount < mCountdownCount) {
AdDelegateHelper.INSTANCE.setShowingSplashAd(false);
hideSplashAd();
if (msg.what == COUNTDOWN_AD && msg.obj instanceof StartupAdEntity) {
StartupAdEntity ad = (StartupAdEntity) msg.obj;
if (!AdDelegateHelper.INSTANCE.isOwnerSplashAdShown()) {
com.gh.common.util.NewFlatLogUtils.logSplashAdFail(ad.getId(), "广告加载超时");
}
AdDelegateHelper.INSTANCE.setOwnerSplashAdShown(false);
LinkEntity linkEntity = ad.getJump();
SensorsBridge.trackEvent(
"SplashAdOwnSkip",
@ -1078,4 +1084,12 @@ public class MainActivity extends BaseActivity {
}
}
}
@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)
}
@ -329,6 +324,19 @@ open class SearchActivity : BaseActivity() {
// MtaHelper.onEvent("游戏搜索", "主动搜索", newSearchKey)
}
override fun onResume() {
super.onResume()
val newSearchKey = searchEt.text.toString().trim { it <= ' ' }
if (newSearchKey.isBlank()) {
try {
popBackToFragment(SearchDefaultFragment::class.java.name)
} catch (e: Exception) {
// no implement
}
}
}
protected open fun provideDao(): ISearchHistoryDao = SearchHistoryDao(this)
open fun updateDisplayType(type: DisplayType) {

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

@ -14,8 +14,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
@ -91,7 +91,8 @@ class SplashScreenActivity : BaseActivity(), ISplashScreen {
} else {
val spanBuilder = buildSpannedString {
append("这个弹窗只会在右上角有环境标签的测试包出现" +
"\n进入应用以后还可以到关于我们页面长按应用图标重新选择")
"\n进入应用以后还可以到关于我们页面长按应用图标重新选择"
)
bold {
color(com.gh.gamecenter.common.R.color.text_theme.toColor(this@SplashScreenActivity)) {
append("\n点击这里进行预设置渠道")

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);

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,6 +16,7 @@ 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
@ -31,7 +35,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
@ -44,8 +47,10 @@ import com.gh.gamecenter.feature.entity.GameEntity
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 +61,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,6 +98,12 @@ class DetailViewHolder(
// 多版本下载文字
var multiVersionDownloadTv: TextView?
// 加速按钮
val speedContainer: ConstraintLayout?
private val ivFreeVipTag: ImageView?
private val gMoreZone: Group?
// 注意 View 的命名
init {
downloadBottom = view.findViewById(R.id.detail_ll_bottom)
@ -104,6 +118,10 @@ class DetailViewHolder(
localDownloadTitleTv = view.findViewById(R.id.localDownloadTitleTv)
localDownloadButton = view.findViewById(R.id.localDownloadButton)
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)
context = view.context
var gameDownloadMode = gameEntity.getGameDownloadButtonMode()
@ -115,7 +133,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 +144,8 @@ class DetailViewHolder(
mTitle = title ?: "",
mAsVGame = true,
mShowDualDownloadButton = gameDownloadMode == GameEntity.GAME_DOWNLOAD_BUTTON_MODE_DUAL,
mTraceEvent = traceEvent
mTraceEvent = traceEvent,
onDownloadClickAction = onDownloadClickAction
)
// 不支持双下载按钮的情况时,优选一个下载方式显示
@ -158,7 +178,9 @@ 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
)
)
}
}
downloadPb.putWidgetBusinessName("游戏详情页")
@ -176,6 +198,33 @@ class DetailViewHolder(
gamePermissionDialogFragment?.dismissAllowingStateLoss()
}
fun checkIfShowSpeedUi(rawBtnText: String) {
acceleratorUiHelper?.let {
when {
rawBtnText == "启动" && gameEntity.canSpeed -> {
downloadPb.goneIf(true)
localDownloadButton?.goneIf(true)
it.checkIfShowSpeedUi(true)
}
rawBtnText == "更新" && gameEntity.canSpeed -> {
it.checkIfShowSpeedUi(true)
}
else -> {
it.checkIfShowSpeedUi(false)
}
}
}
}
fun setSpeedViewsVisible(isVisible: Boolean) {
ivFreeVipTag?.goneIf(!isVisible)
speedContainer?.goneIf(!isVisible)
}
internal class OnDetailDownloadClickListener(
private val mViewHolder: DetailViewHolder,
private val mEntrance: String?,
@ -183,7 +232,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
@ -194,6 +244,10 @@ class DetailViewHolder(
if (mGameEntity.isLandPageAddressDialog() && !mGameEntity.isLandPageAddressDialogShowOnly()) {
// 第三方落地页为开启状态并且展示状态不为“仅显示弹窗”,需要在点击确认后显示弹窗
DialogUtils.showLandPageAddressDialog(mViewHolder.context, mGameEntity) {
val packageName = mGameEntity.getApk().firstOrNull()?.packageName
if (packageName?.isNotEmpty() == true) {
PackageChangeHelper.addInstallPendingPackage(packageName)
}
DirectUtils.directToExternalBrowser(mViewHolder.context, mGameEntity.landPageAddressDialog!!.link!!)
}
}
@ -277,11 +331,13 @@ class DetailViewHolder(
showLandPageAddressDialogIfNeeded()
}
}
"toast" -> {
EventBus.getDefault().post(EBReuse(GameDetailFragment.SKIP_RATING))
mViewHolder.viewModel?.performTabSelected(GameDetailTabEntity.TYPE_COMMENT)
ToastUtils.toast("该游戏因故暂不提供下载,具体详情可在相关评论中查看,敬请谅解~")
showLandPageAddressDialogIfNeeded()
}
"third_party" -> {
showLandPageAddressDialogIfNeeded()
}
@ -330,7 +386,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(
@ -350,7 +418,8 @@ class DetailViewHolder(
gameEntity = mGameEntity,
traceEvent = mTraceEvent,
entrance = StringUtils.buildString(mEntrance, "+(", mName, "[", mTitle, "])"),
location = "$mName:$mTitle"
location = "$mName:$mTitle",
onDownloadClickAction = onDownloadClickAction
)
}
}
@ -590,8 +659,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)
@ -667,6 +737,7 @@ class DetailViewHolder(
builder.addHandler(OverseaDownloadHandler())
builder.addHandler(CheckDownloadHandler())
builder.setProcessEndCallback(mGameEntity.id) { asVGame: Boolean, isSubscribe: Any? ->
performDownloadClickAction()
download(asVGame, isSubscribe as Boolean)
}
} else {
@ -684,7 +755,8 @@ class DetailViewHolder(
mTitle,
"])"
),
"$mName:$mTitle"
"$mName:$mTitle",
onDownloadClickAction
)
}
}
@ -695,5 +767,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

@ -1,5 +1,7 @@
package com.gh.gamecenter.authorization
import android.app.Activity
import android.app.Application.ActivityLifecycleCallbacks
import android.app.Dialog
import android.content.Intent
import android.os.Build
@ -22,6 +24,7 @@ import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.login.user.UserRepository
import com.gh.gamecenter.login.view.LoginActivity
import com.gh.vspace.VHelper
import com.halo.assistant.HaloApp
import com.lightgame.utils.Utils
/**
@ -139,7 +142,7 @@ class AuthorizationActivity : ToolBarActivity() {
override fun onRestart() {
super.onRestart()
if (!CheckLoginUtils.isLogin()) {
finish()
finishAndRemoveTask()
} else {
initUserInfo()
initData()
@ -150,12 +153,12 @@ class AuthorizationActivity : ToolBarActivity() {
private fun checkParam() {
val uri = intent.data
if (uri == null) {
finish()
finishAndRemoveTask()
return
}
val host = uri.host
if (host != "authorize") {
finish()
finishAndRemoveTask()
return
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
@ -168,7 +171,7 @@ class AuthorizationActivity : ToolBarActivity() {
gameName = uri.getQueryParameter(EntranceConsts.KEY_GAME_NAME) ?: ""
gameUid = uri.getQueryParameter(EntranceConsts.KEY_UID)?.toIntOrNull() ?: -1
if (mRemotePkgName == null) {
finish()
finishAndRemoveTask()
return
}
}
@ -221,7 +224,7 @@ class AuthorizationActivity : ToolBarActivity() {
val remotePkgName = mRemotePkgName
if (remotePkgName == null) {
logAuthResult(false)
finish()
finishAndRemoveTask()
return
}
if (mToken.isEmpty()) {
@ -242,13 +245,44 @@ class AuthorizationActivity : ToolBarActivity() {
intent.putExtra(EntranceConsts.KEY_USER_NAME, username)
intent.putExtra(EntranceConsts.KEY_USER_AVATAR, userAvatar)
if (gameUid != -1 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
sendBroadcastAsUser(intent, UserHandle.getUserHandleForUid(gameUid))
try {
sendBroadcastAsUser(intent, UserHandle.getUserHandleForUid(gameUid))
} catch (e: Exception) {
// 双开/分身游戏进行授权时,如果无 INTERACT_ACROSS_USERS 权限则使用Activity传递授权结果
authByActivity(intent)
return
}
} else {
sendBroadcast(intent)
}
logAuthResult(true)
backToLaunchApp()
finish()
finishAndRemoveTask()
}
private fun authByActivity(intent: Intent) {
intent.setClassName(mRemotePkgName!!, AUTHORIZATION_RESULT_ACTIVITY)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
HaloApp.getInstance().registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}
override fun onActivityStarted(activity: Activity) {}
override fun onActivityResumed(activity: Activity) {}
override fun onActivityPaused(activity: Activity) {}
override fun onActivityStopped(activity: Activity) {
if (activity == this@AuthorizationActivity) {
HaloApp.getInstance().unregisterActivityLifecycleCallbacks(this)
finishAndRemoveTask()
}
}
override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
override fun onActivityDestroyed(activity: Activity) {
if (activity == this@AuthorizationActivity) {
HaloApp.getInstance().unregisterActivityLifecycleCallbacks(this)
}
}
})
startActivity(intent)
logAuthResult(true)
}
private fun logAuthResult(isSuccess: Boolean) {
@ -277,7 +311,7 @@ class AuthorizationActivity : ToolBarActivity() {
}
override fun onBackPressed() {
super.onBackPressed()
finishAndRemoveTask()
backToLaunchApp(false)
@ -292,6 +326,7 @@ class AuthorizationActivity : ToolBarActivity() {
private const val BUTTON_TYPE_CONFIRM = "确定"
private const val BUTTON_TYPE_BACK = "返回"
private const val TYPE_PLUGIN = "plugin"
private const val AUTHORIZATION_RESULT_ACTIVITY = "com.gh.plugin.AuthorizationResultActivity"
}
}

View File

@ -2,17 +2,30 @@ package com.gh.gamecenter.cloudarchive
import android.app.Application
import com.gh.gamecenter.common.baselist.ListViewModel
import com.gh.gamecenter.common.utils.toRequestBody
import com.gh.gamecenter.entity.ArchiveEntity
import com.gh.gamecenter.retrofit.RetrofitManager
import com.lightgame.utils.Utils
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import okhttp3.*
import retrofit2.HttpException
import java.io.IOException
open class BaseCloudArchiveViewModel(application: Application, private val mConfigUrl: String): ListViewModel<ArchiveEntity, ArchiveEntity>(application) {
open class BaseCloudArchiveViewModel(
application: Application,
private val mGameId: String,
private var mConfigUrl: String
) : ListViewModel<ArchiveEntity, ArchiveEntity>(application) {
private var mArchiveConfigStr = ""
init {
getArchiveConfigString()
if (mConfigUrl.isEmpty()) {
getArchiveConfigUrl()
} else {
getArchiveConfigString()
}
}
// 通过url获取config字符串内容
@ -41,6 +54,27 @@ open class BaseCloudArchiveViewModel(application: Application, private val mConf
}
}
// 获取游戏存档配置url
private fun getArchiveConfigUrl() {
val map = mapOf(
"game_ids" to listOf(mGameId)
)
RetrofitManager.getInstance().newApi.getGamesArchiveConfigs(map.toRequestBody())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : com.gh.gamecenter.common.retrofit.Response<List<ArchiveEntity>>() {
override fun onResponse(response: List<ArchiveEntity>?) {
mConfigUrl = response?.find { it.gameId == mGameId }?.configUrl ?: return
getArchiveConfigString()
}
override fun onFailure(e: HttpException?) {
super.onFailure(e)
Utils.toast(getApplication(), "获取存档配置失败")
}
})
}
override fun provideDataObservable(page: Int): Observable<List<ArchiveEntity>>? = null
override fun mergeResultLiveData() {}

View File

@ -216,7 +216,7 @@ class CloudArchiveManagerActivity : BaseActivity_TabLayout(), ArchiveLimitSelect
private fun initDownloadBtn() {
mGameEntity?.let { gameEntity ->
DownloadItemUtils.updateDownloadButton(this, mBinding.downloadBtn, gameEntity)
DownloadItemUtils.updateDownloadButton(this, mBinding.downloadBtn, gameEntity, listener = null)
DownloadItemUtils.setOnClickListener(this, mBinding.downloadBtn, gameEntity, 0, null, mEntrance, "")
}
}

View File

@ -8,7 +8,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.gh.common.util.NewFlatLogUtils
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.toJson
import com.gh.gamecenter.common.utils.toRequestBody
import com.gh.gamecenter.core.utils.GsonUtils
import com.gh.gamecenter.entity.ArchiveEntity
@ -22,7 +21,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import okhttp3.*
import okhttp3.ResponseBody
import retrofit2.HttpException
class CloudArchiveManagerViewModel(
@ -30,7 +29,7 @@ class CloudArchiveManagerViewModel(
val gameId: String,
val gameName: String,
configUrl: String
) : BaseCloudArchiveViewModel(application, configUrl) {
) : BaseCloudArchiveViewModel(application, gameId, configUrl) {
companion object {
private const val SORT_TYPE_CREATE = "time.create:-1"

View File

@ -12,6 +12,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.gh.common.exposure.IExposable
import com.gh.common.util.DialogUtils
import com.gh.common.util.DirectUtils
import com.gh.common.util.PackageChangeHelper
import com.gh.common.util.PackageInstaller
import com.gh.common.util.PackageLauncher
import com.gh.download.DownloadManager
@ -398,6 +399,10 @@ class UpdatableGameAdapter(private var mViewModel: UpdatableGameViewModel) :
if (update.isLandPageAddressDialogShowOnly()) {
updateOrPluggable(updateBtn, update, downloadEntity, pluginDesc)
} else {
val packageName = update.packageName
if (packageName.isNotEmpty() == true) {
PackageChangeHelper.addInstallPendingPackage(packageName)
}
DirectUtils.directToExternalBrowser(it.context, update.landPageAddressDialog!!.link!!)
}
}

View File

@ -1,10 +1,10 @@
package com.gh.gamecenter.entity
import com.gh.gamecenter.feature.entity.SimpleGame
import com.gh.gamecenter.feature.entity.Count
import com.gh.gamecenter.feature.entity.SimpleGame
import com.google.gson.annotations.SerializedName
data class GameDetailRecommendGameEntity(
data class GameDetailRecommendGameCollectionEntity(
@SerializedName("_id")
val id: String = "",
val cover: String = "",

View File

@ -0,0 +1,11 @@
package com.gh.gamecenter.entity
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
data class HistoryGameDetailEntity(
@PrimaryKey
var id: String = "",
var name: String? = "",
)

View File

@ -0,0 +1,5 @@
package com.gh.gamecenter.eventbus
import com.gh.gamecenter.feature.entity.AcctGameInfo
class EBStartupAcceleration(val acctGameInfo: AcctGameInfo)

View File

@ -8,7 +8,6 @@ import android.text.SpannableStringBuilder
import android.view.View
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.common.util.*
import com.gh.common.util.DialogUtils
import com.gh.common.util.LogUtils
import com.gh.common.util.NewLogUtils
import com.gh.common.view.ImageContainerView
@ -17,22 +16,18 @@ import com.gh.gamecenter.ImageViewerActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.callback.ConfirmListener
import com.gh.gamecenter.common.entity.AdditionalParamsEntity
import com.gh.gamecenter.common.entity.CommunityEntity
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.SpanBuilder
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.databinding.CommunityAnswerItemBinding
import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.eventbus.EBUserFollow
import com.gh.gamecenter.forum.detail.ForumDetailActivity
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.qa.answer.BaseAnswerOrArticleItemViewHolder
import com.gh.gamecenter.qa.article.detail.ArticleDetailActivity
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.feature.entity.ArticleEntity
import com.gh.gamecenter.feature.entity.CommunityItemData
import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.forum.detail.ForumDetailActivity
import com.gh.gamecenter.forum.home.ArticleItemVideoView.Companion.toArticleVideoData
import com.gh.gamecenter.forum.search.CommunitySearchEventListener
import com.gh.gamecenter.forum.search.CommunitySearchEventListener.Companion.SEARCH_BUTTON_COMMENT
@ -44,6 +39,9 @@ import com.gh.gamecenter.forum.search.CommunitySearchEventListener.Companion.SEA
import com.gh.gamecenter.forum.search.CommunitySearchEventListener.Companion.SEARCH_BUTTON_VIEW_IMAGE
import com.gh.gamecenter.forum.search.CommunitySearchEventListener.Companion.SEARCH_BUTTON_VIEW_USER_DETAIL
import com.gh.gamecenter.forum.search.CommunitySearchEventListener.Companion.htmlToString
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.qa.answer.BaseAnswerOrArticleItemViewHolder
import com.gh.gamecenter.qa.article.detail.ArticleDetailActivity
import com.gh.gamecenter.qa.entity.QuestionsDetailEntity
import com.gh.gamecenter.qa.questions.invite.QuestionsInviteActivity
import com.gh.gamecenter.qa.questions.newdetail.NewQuestionDetailActivity
@ -348,8 +346,8 @@ class ForumArticleAskItemViewHolder(
.setReleaseWhenLossAudio(true)
.setLooping(false)
.setShowFullAnimation(false)
.setEnlargeImageRes(R.drawable.ic_game_detail_enter_full_screen)
.setShrinkImageRes(R.drawable.ic_game_detail_exit_full_screen)
.setEnlargeImageRes(R.drawable.ic_video_enter_full_screen)
.setShrinkImageRes(R.drawable.ic_video_exit_full_screen)
.setVideoAllCallBack(object : GSYSampleCallBack() {
override fun onQuitFullscreen(url: String?, vararg objects: Any) {
orientationUtils.backToProtVideo()

View File

@ -3,26 +3,29 @@ package com.gh.gamecenter.game.horizontal
import android.content.Context
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.*
import com.gh.common.util.DataCollectionUtils
import com.gh.common.util.DownloadItemUtils
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.adapter.viewholder.GameViewHolder
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.StringUtils
import com.gh.gamecenter.entity.SubjectEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.subjectTypeToComponentStyle
import com.gh.gamecenter.feature.minigame.MiniGameItemHelper
import com.gh.gamecenter.gamedetail.detail.viewholder.BaseGameDetailItemViewHolder
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.subjectTypeToComponentStyle
import com.lightgame.adapter.BaseRecyclerAdapter
import com.lightgame.download.DownloadEntity
class GameHorizontalAdapter(
context: Context,
private var mSubjectEntity: SubjectEntity,
var subjectEntity: SubjectEntity,
private var type: GameHorizontalListType = GameHorizontalListType.SubjectHorizontalType,
private val trackColumnClick: Boolean = true
private val trackColumnClick: Boolean = true,
private val gameDetailTrackData: BaseGameDetailItemViewHolder.GameDetailModuleTrackData? = null,
private val getGameStatus: (() -> String)? = null
) : BaseRecyclerAdapter<GameHorizontalItemViewHolder>(context) {
var gameName = ""
@ -37,15 +40,15 @@ class GameHorizontalAdapter(
init {
var dataIds = ""
mSubjectEntity.data?.forEach {
subjectEntity.data?.forEach {
dataIds += it.id
}
if (dataIds.isNotEmpty()) countAndKey = Pair(mSubjectEntity.data?.size ?: 0, dataIds)
if (dataIds.isNotEmpty()) countAndKey = Pair(subjectEntity.data?.size ?: 0, dataIds)
}
fun getIndex(): Int {
if (mSubjectEntity.data!!.isNotEmpty()) {
return if (mSubjectEntity.data!![0].image.isNullOrEmpty()) 0 else 1
if (subjectEntity.data!!.isNotEmpty()) {
return if (subjectEntity.data!![0].image.isNullOrEmpty()) 0 else 1
}
return 0
}
@ -53,13 +56,13 @@ class GameHorizontalAdapter(
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = GameHorizontalItemViewHolder(parent.toBinding())
override fun getItemCount(): Int {
val size = mSubjectEntity.data!!.size - getIndex()
val size = subjectEntity.data!!.size - getIndex()
return when (type) {
GameHorizontalListType.GameDetailHorizontalType -> {
size
}
GameHorizontalListType.MiniGameSubjectHorizontalType -> {
mSubjectEntity.data!!.size
subjectEntity.data!!.size
}
else -> when {
size < 4 -> size
@ -80,14 +83,15 @@ class GameHorizontalAdapter(
}
holder.binding.root.layoutParams = params
val gameEntity = mSubjectEntity.data!![position + getIndex()]
val gameEntity = subjectEntity.data!![position + getIndex()]
holder.binding.simpleGameContainer.run {
gameIcon.displayGameIcon(gameEntity)
GameHorizontalSimpleItemViewHolder.setHorizontalNameAndGravity(gameName, gameEntity.name)
downloadBtn.goneIf(!mSubjectEntity.showDownload)
gameName.maxLines = if (mSubjectEntity.showDownload) 1 else 2
downloadBtn.goneIf(!subjectEntity.showDownload)
gameName.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(mContext))
gameName.maxLines = if (subjectEntity.showDownload) 1 else 2
}
holder.bindGameHorizontalItem(gameEntity, mSubjectEntity)
holder.bindGameHorizontalItem(gameEntity, subjectEntity)
val entranceResult: String
val locationResult: String
if (type == GameHorizontalListType.GameDetailHorizontalType) {
@ -104,37 +108,35 @@ class GameHorizontalAdapter(
locationResult = StringUtils.buildString("游戏详情-", gameName, "-${path}", ":", gameEntity.name)
} else {
entranceResult =
StringUtils.buildString("(游戏-专题:", mSubjectEntity.name, "-列表[", (position + 1).toString(), "])")
locationResult = StringUtils.buildString("游戏-专题-", mSubjectEntity.name, ":", gameEntity.name)
StringUtils.buildString("(游戏-专题:", subjectEntity.name, "-列表[", (position + 1).toString(), "])")
locationResult = StringUtils.buildString("游戏-专题-", subjectEntity.name, ":", gameEntity.name)
}
holder.itemView.setOnClickListener {
if (type == GameHorizontalListType.GameDetailHorizontalType) {
DataCollectionUtils.uploadClick(mContext, path, "游戏详情", gameEntity.name)
NewLogUtils.logGameDetailPopularClick(gameName, gameId, "game", gameEntity.name ?: "")
SensorsBridge.trackGameDetailPagePopularClick(
gameId = gameId,
gameName = gameName,
pageName = GlobalActivityManager.getCurrentPageEntity().pageName,
pageId = GlobalActivityManager.getCurrentPageEntity().pageId,
pageBusinessId = GlobalActivityManager.getCurrentPageEntity().pageBusinessId,
lastPageName = GlobalActivityManager.getLastPageEntity().pageName,
lastPageId = GlobalActivityManager.getLastPageEntity().pageId,
lastPageBusinessId = GlobalActivityManager.getLastPageEntity().pageBusinessId,
downloadStatus = game?.downloadStatusChinese ?: "",
gameType = game?.categoryChinese ?: "",
clickGameType = gameEntity.categoryChinese,
clickGameName = gameEntity.name ?: "",
clickGameId = gameEntity.id
SensorsBridge.trackGameDetailModuleClick(
gameDetailTrackData?.gameId,
gameDetailTrackData?.gameName,
gameDetailTrackData?.gameType,
"组件内容",
gameDetailTrackData?.moduleType,
gameDetailTrackData?.moduleName,
gameDetailTrackData?.sequence,
linkType = "game",
linkId = gameEntity.id,
linkText = gameEntity.name ?: "",
gameStatus = getGameStatus?.invoke()
)
if (!gameEntity.isMiniGame() && trackColumnClick) {
SensorsBridge.trackColumnClick(
location = "游戏详情",
gameName = gameName,
gameId = gameId,
gameColumnName = mSubjectEntity.name ?: "",
gameColumnId = mSubjectEntity.id ?: "",
gameColumnName = subjectEntity.name ?: "",
gameColumnId = subjectEntity.id ?: "",
text = "游戏",
columnPattern = subjectTypeToComponentStyle[mSubjectEntity.type] ?: ""
columnPattern = subjectTypeToComponentStyle[subjectEntity.type] ?: ""
)
}
}
@ -151,7 +153,7 @@ class GameHorizontalAdapter(
}
}
if (mSubjectEntity.showDownload && type != GameHorizontalListType.GameDetailHorizontalType) {
if (subjectEntity.showDownload && type != GameHorizontalListType.GameDetailHorizontalType) {
DownloadItemUtils.setOnClickListener(
mContext,
holder.binding.simpleGameContainer.downloadBtn,
@ -179,7 +181,7 @@ class GameHorizontalAdapter(
if (downloadEntity == null) {
notifyDataSetChanged()
} else {
mSubjectEntity.data?.forEachIndexed { position, gameEntity ->
subjectEntity.data?.forEachIndexed { position, gameEntity ->
if (downloadEntity.gameId == gameEntity.id) {
notifyItemChanged(position - getIndex())
return
@ -189,7 +191,7 @@ class GameHorizontalAdapter(
}
fun notifyChildItem(packageName: String) {
mSubjectEntity.data?.forEachIndexed { position, gameEntity ->
subjectEntity.data?.forEachIndexed { position, gameEntity ->
gameEntity.getApk().forEach { apkEntity ->
if (apkEntity.packageName == packageName) {
notifyItemChanged(position - getIndex())
@ -206,11 +208,11 @@ class GameHorizontalAdapter(
dataIds += it.id
}
mSubjectEntity = subjectEntity
if (countAndKey?.first == subjectEntity.data?.size && countAndKey?.second != dataIds) { // 数量不变,内容发生改变
notifyItemRangeChanged(0, itemCount)
} else if (countAndKey?.first != subjectEntity.data?.size) { // 数量发生改变
this.subjectEntity = subjectEntity
if (countAndKey?.first != subjectEntity.data?.size) { // 数量发生改变
notifyDataSetChanged()
} else {
notifyItemRangeChanged(0, itemCount)
}
// 重新刷新数据标识

View File

@ -39,7 +39,6 @@ import com.gh.gamecenter.common.syncpage.SyncDataEntity
import com.gh.gamecenter.common.syncpage.SyncFieldConstants
import com.gh.gamecenter.common.syncpage.SyncPageRepository
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.SegmentedFilterView
import com.gh.gamecenter.core.iinterface.IScrollable
import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.databinding.FragmentGameCollectionDetailBinding
@ -48,7 +47,6 @@ import com.gh.gamecenter.entity.GamesCollectionDetailEntity
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.eventbus.EBUserFollow
import com.gh.gamecenter.gamedetail.GameDetailFragment
import com.gh.gamecenter.home.video.ScrollCalculatorHelper
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.login.user.UserViewModel
@ -187,12 +185,10 @@ class GameCollectionDetailFragment :
root.layoutParams = this
}
orderSfv.setItemList(listOf("正序", "倒序"), 0)
orderSfv.setOnCheckedCallback(object : SegmentedFilterView.OnCheckedCallback {
override fun onItemCheck(position: Int) {
getFilterVH()?.binding?.orderSfv?.performClick(position)
updateFilterView()
}
})
orderSfv.setOnCheckedCallback { position ->
getFilterVH()?.binding?.orderSfv?.performClick(position)
updateFilterView()
}
commentHintTv.text = "玩家评论"
}
}
@ -741,7 +737,7 @@ class GameCollectionDetailFragment :
if (activity != null && activity?.isFinishing != true) {
startPlayLogic(isAutoPlay = true)
}
}, GameDetailFragment.INITIAL_DELAY)
}, INITIAL_DELAY)
}
}
}
@ -1210,4 +1206,8 @@ class GameCollectionDetailFragment :
appbar.setExpanded(true)
}
}
companion object {
const val INITIAL_DELAY = 500L
}
}

View File

@ -13,15 +13,15 @@ import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.observer.MuteCallback
import com.gh.gamecenter.common.observer.VolumeObserver
import com.gh.gamecenter.core.runOnIoThread
import com.gh.common.util.*
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.debounceActionWithInterval
import com.gh.gamecenter.common.utils.rxTimer
import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.common.utils.NetworkUtils
import com.gh.gamecenter.common.utils.debounceActionWithInterval
import com.gh.gamecenter.common.utils.rxTimer
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.utils.MD5Utils
import com.gh.gamecenter.entity.GamesCollectionDetailEntity
import com.gh.gamecenter.home.video.ScrollCalculatorHelper
@ -180,7 +180,7 @@ class GameCollectionVideoView @JvmOverloads constructor(context: Context, attrs:
private fun mute(isManual: Boolean = false) {
viewModel?.videoIsMuted = true
volume.setImageResource(R.drawable.ic_game_detail_volume_off)
volume.setImageResource(R.drawable.ic_video_volume_off)
CustomManager.getCustomManager(getKey()).isNeedMute = true
if (isManual) {
Utils.toast(context, "当前处于静音状态")
@ -190,7 +190,7 @@ class GameCollectionVideoView @JvmOverloads constructor(context: Context, attrs:
private fun unMute(isManual: Boolean = false) {
viewModel?.videoIsMuted = false
volume.setImageResource(R.drawable.ic_game_detail_volume_on)
volume.setImageResource(R.drawable.ic_video_volume_on)
CustomManager.getCustomManager(getKey()).isNeedMute = false
if (isManual) {
logVideoEvent("video_game_collect_detail_mute_cancel")
@ -278,9 +278,9 @@ class GameCollectionVideoView @JvmOverloads constructor(context: Context, attrs:
if (mStartButton is ImageView) {
val imageView = mStartButton as ImageView
when (mCurrentState) {
GSYVideoView.CURRENT_STATE_PLAYING -> imageView.setImageResource(R.drawable.ic_game_detail_pause)
GSYVideoView.CURRENT_STATE_ERROR -> imageView.setImageResource(R.drawable.ic_game_detail_play)
else -> imageView.setImageResource(R.drawable.ic_game_detail_play)
GSYVideoView.CURRENT_STATE_PLAYING -> imageView.setImageResource(R.drawable.ic_video_pause)
GSYVideoView.CURRENT_STATE_ERROR -> imageView.setImageResource(R.drawable.ic_video_play)
else -> imageView.setImageResource(R.drawable.ic_video_play)
}
}
}
@ -314,11 +314,11 @@ class GameCollectionVideoView @JvmOverloads constructor(context: Context, attrs:
}
override fun getEnlargeImageRes(): Int {
return R.drawable.ic_game_detail_enter_full_screen
return R.drawable.ic_video_enter_full_screen
}
override fun getShrinkImageRes(): Int {
return R.drawable.ic_game_detail_exit_full_screen
return R.drawable.ic_video_exit_full_screen
}
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {

View File

@ -73,6 +73,7 @@ class GameCollectionHotListAdapter(
if (holder is GameCollectionPlayerCreationAdapter.GameCollectionPlayerCreationHeaderItemViewHolder) {
holder.binding.run {
root.setPadding(0, if (mIsFromDetail) 16F.dip2px() else 0, 0, 12F.dip2px())
timeSfv.visibility = View.GONE
tipsIv.visibility = View.GONE
tipsTv.text = mGameCollectionListEntity?.explain
tipsTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(mContext))

View File

@ -617,6 +617,7 @@ class GameCollectionSquareFragment : LazyListFragment<GamesCollectionEntity, Gam
mAlternativeBinding.listRv.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (!isSupportVisible) return
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
setFabStatus(mPostFabView, View.GONE)
setFabStatus(mRefreshFabView, View.GONE)

View File

@ -0,0 +1,13 @@
package com.gh.gamecenter.gamedetail
import androidx.lifecycle.ViewModel
import com.halo.assistant.accelerator.AccelerationUseCase
class AcceleratorZoneViewModel : ViewModel() {
val useCase = AccelerationUseCase()
override fun onCleared() {
useCase.onClear()
}
}

View File

@ -1,51 +0,0 @@
package com.gh.gamecenter.gamedetail
import android.content.Context
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.utils.safelyGetInRelease
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.databinding.ItemGameDetailContentCardContentBinding
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity
import com.lightgame.adapter.BaseRecyclerAdapter
class GameDetailContentCardContentAdapter(
context: Context,
private val linkEntity: ContentCardEntity,
private val isHighlightBg: Boolean = false
) : BaseRecyclerAdapter<RecyclerView.ViewHolder>(context) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
GameDetailContentCardContentItemViewHolder(parent.toBinding())
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is GameDetailContentCardContentItemViewHolder) {
holder.binding.root.setTextColor(
if (isHighlightBg) com.gh.gamecenter.common.R.color.text_secondary.toColor(mContext) else com.gh.gamecenter.common.R.color.text_tertiary.toColor(
mContext
)
)
if (linkEntity.type == "func_server" && linkEntity.server != null && linkEntity.server?.calendar?.isNotEmpty() == true) {
val calendarList = linkEntity.server!!.calendar
val realPosition = position % calendarList.size
calendarList.safelyGetInRelease(realPosition)?.let {
val serverTime =
if (TimeUtils.isToday(it.getTime()))
it.getFormatTime("今天 HH:mm")
else if (TimeUtils.isTomorrow(it.getTime()))
it.getFormatTime("明天 HH:mm")
else
it.getFormatTime("MM-dd HH:mm")
holder.binding.root.text = "$serverTime ${it.type}"
}
}
}
}
override fun getItemCount(): Int = Int.MAX_VALUE
class GameDetailContentCardContentItemViewHolder(var binding: ItemGameDetailContentCardContentBinding) :
RecyclerView.ViewHolder(binding.root)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,57 +0,0 @@
package com.gh.gamecenter.gamedetail
import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.BaseFragment
import com.gh.gamecenter.common.utils.toDrawable
import com.gh.gamecenter.feature.entity.LibaoEntity
import com.gh.gamecenter.gamedetail.desc.GameLibaoAdapter
class LibaoListFragment : BaseFragment<Any>() {
override fun getLayoutId() = R.layout.fragment_libao_list
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val recyclerView = view.findViewById<RecyclerView>(R.id.recyclerView)
val toolbarTitleTv = view.findViewById<TextView>(R.id.normal_title)
val toolbarBackContainer = view.findViewById<View>(com.gh.gamecenter.selector.R.id.backContainer)
toolbarTitleTv.setText("游戏礼包")
toolbarBackContainer.setOnClickListener { requireActivity().onBackPressed() }
val gameId = arguments?.getString(ARG_GAME_ID) ?: ""
val gameName = arguments?.getString(ARG_GAME_NAME) ?: ""
val libaoList = arguments?.getParcelableArrayList<LibaoEntity>(ARG_LIBAO_LIST) ?: arrayListOf()
recyclerView?.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = adapter ?: GameLibaoAdapter(requireContext(), libaoList, gameName, gameId, false, true)
}
}
companion object {
private const val ARG_GAME_ID = "game_id"
private const val ARG_GAME_NAME = "game_name"
private const val ARG_LIBAO_LIST = "libao_list"
fun getBundle(gameId: String, gameName: String, libaoList: ArrayList<LibaoEntity>) = Bundle().apply {
putString(ARG_GAME_ID, gameId)
putString(ARG_GAME_NAME, gameName)
putParcelableArrayList(ARG_LIBAO_LIST, libaoList)
}
fun newInstance(bundle: Bundle?) = LibaoListFragment().apply {
if (bundle != null) {
arguments = bundle
}
}
}
}

View File

@ -0,0 +1,71 @@
package com.gh.gamecenter.gamedetail
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.feature.entity.BaseEntity
import com.gh.gamecenter.feature.entity.TrialEntity
import com.gh.gamecenter.feature.entity.VipEntity
import com.gh.gamecenter.livedata.Event
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.login.user.UserRepository
import com.halo.assistant.accelerator.AccelerationUseCase
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
import io.reactivex.disposables.CompositeDisposable
class StartingAcceleratorViewModel : ViewModel() {
private val compositeDisposable = CompositeDisposable()
val useCase = AccelerationUseCase()
private val _restartingAcceleratorAction = MutableLiveData<Event<Boolean>>()
val restartingAcceleratorAction: LiveData<Event<Boolean>> = _restartingAcceleratorAction
fun loadAcceleratorToken() {
val userId = UserManager.getInstance().userId
if (userId.isNotBlank()) {
UserRepository.getInstance().setAcceleratorToken(userId) {
_restartingAcceleratorAction.value = Event(it)
}
}
}
private val _rechargeTrailResult = MutableLiveData<Event<Boolean>>()
val rechargeTrailResult: LiveData<Event<Boolean>> = _rechargeTrailResult
fun rechargeTrial() {
val userId = UserManager.getInstance().userId
useCase.rechargeTrial(userId)
.compose(singleToMain())
.subscribe(object : BiResponse<BaseEntity<TrialEntity>>() {
override fun onSuccess(data: BaseEntity<TrialEntity>) {
if (data.data?.result == true) {
// 刷新vip状态
// 这里先刷新内存数据
AcceleratorDataHolder.instance.setVipEntity(
VipEntity(
_vipStatus = true,
_isNewUser = false,
_isTryVip = true
)
)
_rechargeTrailResult.value = Event(true)
} else {
_rechargeTrailResult.value = Event(false)
}
}
override fun onFailure(exception: Exception) {
super.onFailure(exception)
_rechargeTrailResult.value = Event(false)
}
}).let(compositeDisposable::add)
}
override fun onCleared() {
super.onCleared()
compositeDisposable.clear()
}
}

View File

@ -0,0 +1,49 @@
package com.gh.gamecenter.gamedetail.accelerator
import androidx.room.Dao
import androidx.room.Query
import androidx.room.Upsert
import com.gh.gamecenter.feature.entity.AcctGameInfo
import com.gh.gamecenter.feature.entity.AcctRecord
import io.reactivex.Observable
import io.reactivex.Single
@Dao
interface AccelerationDao {
@Upsert
fun upsertAcctGameInfo(gameInfo: AcctGameInfo): Single<Long>
@Upsert
fun upsertAcctRecord(record: AcctRecord): Single<Long>
/**
* 为什么这里定义成 Observable<List<AcctGameInfo>> 而不是 Observable<AcctGameInfo>
* 因为当返回值为 Observable<T> 时,当表中未查询到数据,则不会收到任何回调
* 使用 Observable<List<AcctGameInfo>>时,为查询到数据时会返回空数组
*/
@Query("SELECT * FROM AcctGameInfo WHERE gameId = :gameId")
fun getAcctGameInfoByGameIdObservable(gameId: String): Observable<List<AcctGameInfo>>
@Query("SELECT EXISTS(SELECT 1 FROM AcctGameInfo LIMIT 1)")
fun hasAnyAcctGameInfo(): Single<Boolean>
@Query("SELECT * FROM AcctGameInfo WHERE gameId IN (:gameIds)")
fun queryAcctGameInfoByGameId(gameIds: List<String>): List<AcctGameInfo>
@Query("SELECT * FROM AcctRecord ORDER BY createTime DESC LIMIT 20")
fun loadAcctRecordsObservable(): Observable<List<AcctRecord>>
@Query("SELECT * FROM AcctRecord ORDER BY createTime DESC LIMIT 20")
fun loadAcctRecords(): List<AcctRecord>
/**
* 获取最新的记录根据createTime排序
* @return 最新的AcctRecord记录
*/
@Query("SELECT * FROM AcctRecord ORDER BY createTime DESC LIMIT 1")
fun loadLatestRecord(): Single<AcctRecord>
@Query("SELECT EXISTS(SELECT 1 FROM AcctRecord LIMIT 1)")
fun getAnyAcctRecordObservable(): Observable<Boolean>
}

View File

@ -0,0 +1,93 @@
package com.gh.gamecenter.gamedetail.accelerator
import android.content.Context
import androidx.room.Database
import androidx.room.Room
import androidx.room.RoomDatabase
import androidx.room.TypeConverters
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.gh.gamecenter.feature.entity.AcctGameInfo
import com.gh.gamecenter.feature.entity.AcctRecord
import com.gh.gamecenter.room.converter.AcctGameInfoConverter
import com.halo.assistant.HaloApp
@Database(
entities = [AcctGameInfo::class, AcctRecord::class],
version = 2,
exportSchema = false
)
@TypeConverters(
AcctGameInfoConverter::class
)
abstract class AccelerationDataBase : RoomDatabase() {
abstract fun accelerationDao(): AccelerationDao
companion object {
private const val DATABASE_NAME: String = "acceleration_db"
val instance: AccelerationDataBase by lazy { buildDatabase(HaloApp.getInstance().application) }
private fun buildDatabase(context: Context): AccelerationDataBase {
return Room.databaseBuilder(context, AccelerationDataBase::class.java, DATABASE_NAME)
.addMigrations(MIGRATION_1_2)
.allowMainThreadQueries()
.build()
}
private val MIGRATION_1_2 = object : Migration(1, 2) {
override fun migrate(db: SupportSQLiteDatabase) {
// 删除表 AcctZoneListBean
db.execSQL("DROP TABLE IF EXISTS AcctZoneListBean")
// 创建新表的SQL ,游戏加速成功记录
createAcctRecordTab(db)
renamePrimaryKeyWithAcctGameInfoTab(db)
}
private fun createAcctRecordTab(db: SupportSQLiteDatabase) {
db.execSQL(
"""
CREATE TABLE IF NOT EXISTS AcctRecord (
gameId TEXT PRIMARY KEY NOT NULL,
game TEXT NOT NULL,
zoneInfo TEXT NOT NULL,
hasMultiZone INTEGER NOT NULL,
createTime INTEGER NOT NULL
)
"""
)
}
/**
* 重命名表 AcctGameInfo 主键
* 游戏旧表中没有 gameId 字段,这里将旧表数据直接清空
*/
private fun renamePrimaryKeyWithAcctGameInfoTab(db: SupportSQLiteDatabase) {
// 1. 创建临时表(包含新的结构)
db.execSQL(
"""
CREATE TABLE IF NOT EXISTS AcctGameInfo_temp (
gameId TEXT PRIMARY KEY NOT NULL,
accGamePkgName TEXT NOT NULL,
zoneInfo TEXT NOT NULL,
notifyGameName TEXT,
notifyGameTxtTitle TEXT,
notifyGameSmallLogo INTEGER,
notifyGameLargeLogo INTEGER,
notifyClickParam TEXT
)
"""
)
// 2. 删除旧表
db.execSQL("DROP TABLE AcctGameInfo")
// 3. 重命名新表
db.execSQL("ALTER TABLE AcctGameInfo_temp RENAME TO AcctGameInfo")
}
}
}
}

View File

@ -0,0 +1,182 @@
package com.gh.gamecenter.gamedetail.accelerator
import android.app.Activity
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import androidx.core.view.updateLayoutParams
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.databinding.LayoutAcceleratorGuidePageBinding
class AcceleratorGuideView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
def: Int = 0
) : FrameLayout(context, attrs, def) {
private val binding: LayoutAcceleratorGuidePageBinding
private val rect = Rect()
private var _isShowing = false
val isShowing: Boolean
get() = _isShowing
private var isCanDismiss = false
private val guideBackgroundColor =
com.gh.gamecenter.common.R.color.black_alpha_60.toColor(context)
private val xfermode by lazy {
val mode = PorterDuff.Mode.CLEAR
PorterDuffXfermode(mode)
}
private val paint by lazy {
Paint().apply {
color = Color.YELLOW
isAntiAlias = true
isDither = true
}
}
private var onStartSpeed: (() -> Unit)? = null
init {
val inflater = LayoutInflater.from(context)
binding = LayoutAcceleratorGuidePageBinding.inflate(inflater, this, true)
binding.root.setOnClickListener {
// 点击透明区域消失
dismiss()
}
binding.clContainer.setOnClickListener {
//覆盖外部点击事件,点击此区域 guideView 不消失
}
binding.vIKnow.setOnClickListener {
dismiss()
}
binding.vSpeed.setOnClickListener {
dismiss()
onStartSpeed?.invoke()
}
}
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
super.onLayout(changed, left, top, right, bottom)
val bottom = rect.bottom
val top = bottom - binding.clContainer.height
val right = rect.right + BORDER_WIDTH.dip2px()
val left = right - binding.clContainer.width
binding.clContainer.layout(left, top, right, bottom)
}
override fun dispatchDraw(canvas: Canvas) {
val saveCount = canvas.saveLayer(0f, 0f, width.toFloat(), height.toFloat(), paint)
paint.color = guideBackgroundColor
canvas.drawRect(0f, 0f, width.toFloat(), height.toFloat(), paint)
onDrawDecoration(canvas)
paint.xfermode = xfermode
canvas.drawRoundRect(
RectF(rect),
HIGH_LIGHT_RADIUS.dip2px().toFloat(),
HIGH_LIGHT_RADIUS.dip2px().toFloat(),
paint
)
paint.xfermode = null
canvas.restoreToCount(saveCount)
super.dispatchDraw(canvas)
}
private fun onDrawDecoration(canvas: Canvas) {
paint.color = com.gh.gamecenter.common.R.color.ui_surface.toColor(context)
val border = BORDER_WIDTH.dip2px()
val radius = HIGH_LIGHT_RADIUS.dip2px().toFloat()
val dRectF = RectF(
rect.left.toFloat() - border,
rect.top.toFloat() - border,
rect.right.toFloat() + border,
rect.bottom.toFloat() + border
)
canvas.drawRoundRect(dRectF, radius, radius, paint)
}
private fun setHighLightRect(newRect: Rect) {
rect.set(newRect)
binding.vSpeed.updateLayoutParams {
width = rect.width()
}
}
fun show(anchor: View, activity: Activity, block: () -> Unit) {
onStartSpeed = block
val newRect = getLocationInWindow(anchor)
val content = activity.window.decorView as ViewGroup
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
setHighLightRect(newRect)
content.addView(this)
setOnClickListener {
if (isCanDismiss) {
dismiss()
}
}
setOnClickListener {
}
_isShowing = true
isCanDismiss = false
AppExecutor.uiExecutor.executeWithDelay({
isCanDismiss = true
}, SHOW_MIN_DURATION)
}
fun dismiss() {
(parent as? ViewGroup)?.removeView(this)
_isShowing = false
onDismissListener?.invoke()
}
private var onDismissListener: (() -> Unit)? = null
fun setDismissListener(listener: () -> Unit) {
onDismissListener = listener
}
private fun getLocationInWindow(anchor: View): Rect {
val result = Rect()
val pos = IntArray(2)
anchor.getLocationInWindow(pos)
result.left = pos[0]
result.top = pos[1]
result.right = result.left + anchor.width
result.bottom = result.top + anchor.height
return result
}
companion object {
private const val BORDER_WIDTH = 4F
private const val HIGH_LIGHT_RADIUS = 100F
private const val SHOW_MIN_DURATION = 500L
}
}

View File

@ -0,0 +1,368 @@
package com.gh.gamecenter.gamedetail.accelerator
import android.content.Context
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import com.gh.common.util.CheckLoginUtils
import com.gh.common.util.PackageLauncher
import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.callback.AccelerateState
import com.gh.gamecenter.core.callback.OnAccelerateListener
import com.gh.gamecenter.core.provider.IAcceleratorProvider
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.databinding.DetailDownloadItemBinding
import com.gh.gamecenter.feature.entity.AcctGameInfo
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.VipEntity
import com.gh.gamecenter.gamedetail.accelerator.chain.AcceleratorClient
import com.gh.gamecenter.gamedetail.accelerator.chain.AcceleratorValidator
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorDialogFragment
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorDialogFragment.Companion.SPEED_STOP
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorZoneDialogFragment
import com.gh.gamecenter.gamedetail.accelerator.dialog.StartingAcceleratorDialogFragment
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
import com.therouter.TheRouter
class GameDetailAcceleratorUiHelper(private val binding: DetailDownloadItemBinding) {
private var iAcceleratorProvider = TheRouter.get(IAcceleratorProvider::class.java)
private var guideView: AcceleratorGuideView? = null
private lateinit var game: GameEntity
private var isGuideLayerShowing = false
private var _last: AcctGameInfo? = null
private val last: AcctGameInfo?
get() = _last
private val hasMultiZone: Boolean
get() = game.serviceArea.size > 1
private val isVip: Boolean
get() = AcceleratorDataHolder.instance.isVip
val isNewUser: Boolean
get() = AcceleratorDataHolder.instance.isNewUser
private val isInit: Boolean
get() = ::game.isInitialized
val context: Context
get() = binding.root.context
private var hasAnyAcctRecord = false
private val accelerationListener = object : OnAccelerateListener {
override fun onStateChanged(state: AccelerateState) {
when (state) {
is AccelerateState.Success -> {
updateSpeedUi()
}
is AccelerateState.Normal -> {
updateSpeedUi()
}
else -> Unit
}
}
override fun onProgress(progress: Int, curGamePkgName: String?, curGameZoneFlag: String?) = Unit
}
private val onDataHolderListener = object : AcceleratorDataHolder.OnDataHolderListener {
override fun onVipStateChanged(vip: VipEntity) {
showFreeTag()
}
}
fun initUi(game: GameEntity, context: Context) {
this.game = game
iAcceleratorProvider?.bindAccRelatedListener(game.getUniquePackageName() ?: "", accelerationListener)
AcceleratorDataHolder.instance.addListener(onDataHolderListener)
binding.vSpeedContent.setOnClickListener {
startAccelerating(context, game, false)
}
binding.vMoreZone.setOnClickListener {
showZoneList(context, game)
}
binding.tvStopSpeed.setOnClickListener {
SensorsBridge.trackNetworkAccelerationOtherButtonClick(
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
AcceleratorDataHolder.instance.memberType,
BUTTON_NAME_STOP_ACCELERATOR,
SOURCE_ENTRANCE_GAME_DETAIL
)
AcceleratorDialogFragment.show(
SPEED_STOP, game.getUniquePackageName() ?: "", game.id, game.name ?: "",
SOURCE_ENTRANCE_GAME_DETAIL, context
)
}
binding.tvEnterGame.setOnClickListener {
SensorsBridge.trackNetworkAccelerationOtherButtonClick(
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
AcceleratorDataHolder.instance.memberType,
BUTTON_NAME_ENTER_GAME,
SOURCE_ENTRANCE_GAME_DETAIL
)
PackageLauncher.launchApp(context, game, game.getUniquePackageName())
}
updateSpeedUi()
}
fun updateSpeedUi() {
if (!isInit || !showSpeedUi) {
return
}
val isCurrentGameAccelerating = AcceleratorDataHolder.instance.isCurrentGameAccelerating(game.id)
when {
isCurrentGameAccelerating -> {// 如果当前游戏正处于加速状态,则需要隐藏当前下载按钮
binding.detailProgressbar.goneIf(true)
}
binding.detailProgressbar.text == "更新" -> { // 游戏没有处于加速状态,如果 下载按钮为 “更新” 状态,则需要显示出来
binding.detailProgressbar.goneIf(false)
}
}
binding.clSpeed.goneIf(isCurrentGameAccelerating)
binding.gAccelerating.goneIf(!isCurrentGameAccelerating)
binding.gMoreZone.goneIf(!hasMultiZone)
showFreeTag()
binding.tvSpeed.text = if (hasMultiZone) {
last?.zoneName
} else {
null
} ?: R.string.network_acceleration.toResString()
}
fun checkIfShowSpeedUi(show: Boolean) {
if (!isInit) {
return
}
showSpeedUi = show
binding.clSpeedContainer.goneIf(!showSpeedUi) {
binding.detailProgressbar.setBackgroundResource(com.gh.gamecenter.common.R.drawable.bg_common_button_light_fill_blue)
binding.detailProgressbar.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
// 是否需要展示弹窗
showGuideLayerIfNeed()
updateSpeedUi()
}
}
private fun showFreeTag() {
binding.ivFreeVipTag.visibleIf(
showSpeedUi && last == null && !isGuideLayerShowing &&
(!CheckLoginUtils.isLogin() || isNewUser)
)
}
/**
* 需要同时满足这四个条件,才需要判断是否需要展示弹窗
*/
var isButtonSizeDetermined = false // 底部下载按钮尺寸是否已确定
private var isLastLoaded = false // 本地存在区服记录(用户点击过选择区服/启动加速)
private var hasAnyAcctRecordLoaded = false // 已获取:是否存在加速成功的游戏记录
private var showSpeedUi = false // 显示加速器ui
fun showGuideLayerIfNeed() {
if (isButtonSizeDetermined && hasAnyAcctRecordLoaded && isLastLoaded && showSpeedUi) {
if (shouldShowGuideLayer()) {
isGuideLayerShowing = true
showGuideLayer(context)
}
showFreeTag()
}
}
private fun shouldShowGuideLayer() =
!SPUtils.getBoolean(Constants.SP_HAS_SHOW_ACCELERATION_GUIDE_LAYER) &&
!hasAnyAcctRecord &&
last == null &&
!isGuideLayerShowing
private fun showGuideLayer(context: Context) {
binding.root.post {
val uiListener = object : OnAcceleratorListener {
override fun onStartAccelerator() {
startAccelerating(context, game, true)
}
override fun onGuideLayerDismiss() {
isGuideLayerShowing = false
showFreeTag()
}
}
SPUtils.setBoolean(Constants.SP_HAS_SHOW_ACCELERATION_GUIDE_LAYER, true)
if (guideView == null) {
guideView = AcceleratorGuideView(context).apply {
setDismissListener(uiListener::onGuideLayerDismiss)
}
}
(guideView?.parent as? ViewGroup)?.removeView(guideView)
if (context is AppCompatActivity) {
SensorsBridge.trackNetworkAccelerationGuidanceDiagramShow(
game.getUniquePackageName() ?: "",
game.id,
game.name ?: ""
)
guideView?.show(binding.clSpeedContainer, context, uiListener::onStartAccelerator)
}
}
}
fun onBack(): Boolean {
val isShowing = guideView?.isShowing ?: false
if (isShowing) {
guideView?.dismiss()
return true
} else {
return false
}
}
private fun startAccelerating(context: Context, game: GameEntity, isShowing: Boolean) {
if (isInvalidClick()) {
return
}
val lastAcctGameInfo = last
val memberType = AcceleratorDataHolder.instance.memberType
val districtServer = when {
hasMultiZone && lastAcctGameInfo != null -> lastAcctGameInfo.zoneName
hasMultiZone -> DISTRICT_SERVER_EMPTY
else -> DISTRICT_SERVER_HAVA
}
SensorsBridge.trackNetworkAccelerationButtonClick(
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
memberType,
districtServer,
if (isShowing) SCENE_TYPE_HAVE_GUIDE_LAYER else SCENE_TYPE_NO_GUIDE_LAYER,
SOURCE_ENTRANCE_GAME_DETAIL
)
when {
lastAcctGameInfo != null ->
doStartAccelerating(context, game, lastAcctGameInfo.zoneInfo)
hasMultiZone -> context.ifLogin("[网络加速]") {
AcceleratorZoneDialogFragment.show(
context, last?.zoneInfo?.id, game.serviceArea, game,
SOURCE_ENTRANCE_GAME_DETAIL
)
}
else -> {
val gameInfo = game.serviceArea.firstOrNull() ?: AcctGameInfo.ZoneInfo(0)
doStartAccelerating(context, game, gameInfo)
}
}
}
private fun showZoneList(context: Context, game: GameEntity) {
if (isInvalidClick()) {
return
}
context.ifLogin("[网络加速]") {
AcceleratorZoneDialogFragment.show(
context, last?.zoneInfo?.id, game.serviceArea, game,
SOURCE_ENTRANCE_GAME_DETAIL
)
}
}
private var clickTime = 0L
private fun doStartAccelerating(context: Context, game: GameEntity, zoneInfo: AcctGameInfo.ZoneInfo) {
val request = AcceleratorValidator.Request(isVip, isNewUser, game, SOURCE_ENTRANCE_GAME_DETAIL)
AcceleratorClient.newInstance()
.execute(context, request, object : AcceleratorValidator.ValidateListener {
override fun finished(context: Context) {
StartingAcceleratorDialogFragment.show(
context, zoneInfo, game, true, hasMultiZone,
SOURCE_ENTRANCE_GAME_DETAIL,
)
}
})
}
fun setCurrentAcctGameInfo(acctGameInfo: AcctGameInfo?) {
isLastLoaded = true
if (last?.zoneInfo?.id != acctGameInfo?.zoneInfo?.id) {
_last = acctGameInfo
updateSpeedUi()
}
showGuideLayerIfNeed()
}
fun setHasAnyAcctRecord(hasAnyAcctRecord: Boolean) {
hasAnyAcctRecordLoaded = true
this.hasAnyAcctRecord = hasAnyAcctRecord
showGuideLayerIfNeed()
}
private fun isInvalidClick(): Boolean {
// 300ms内只能调用一次
val currentTime = System.currentTimeMillis()
val difTime = currentTime - clickTime
if (difTime <= CLICK_DURATION) {
return true
}
clickTime = currentTime
return false
}
fun clear() {
iAcceleratorProvider?.unBindAccRelatedListener(accelerationListener)
AcceleratorDataHolder.instance.removeListener(onDataHolderListener)
guideView?.dismiss()
}
companion object {
private const val CLICK_DURATION = 300
const val DISTRICT_SERVER_EMPTY = ""
const val DISTRICT_SERVER_HAVA = ""
private const val SCENE_TYPE_HAVE_GUIDE_LAYER = "有引导图"
const val SCENE_TYPE_NO_GUIDE_LAYER = "无引导图"
const val SOURCE_ENTRANCE_GAME_DETAIL = "游戏详情页"
const val SOURCE_ENTRANCE_MY_ASSETS = "我的资产"
const val SOURCE_ENTRANCE_SEARCH = "搜索页"
const val SOURCE_ENTRANCE_RECENTLY_PLAYED = "最近在玩"
const val BUTTON_NAME_ENTER_GAME = "进入游戏"
const val BUTTON_NAME_STOP_ACCELERATOR = "停止加速"
}
interface OnAcceleratorListener {
fun onStartAccelerator()
fun onGuideLayerDismiss()
}
}

View File

@ -0,0 +1,54 @@
package com.gh.gamecenter.gamedetail.accelerator
import android.content.Context
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.ShellActivity
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.DialogHelper
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.toResString
/**
* 会员功能 实名认证相关弹窗
*/
object UserVerifyDialogUtils {
fun showUnVerifiedDialog(context: Context, title: String, content: String) {
DialogHelper.showDialog(
context,
title = title,
content = content,
confirmText = R.string.go_to_real_name_authentication.toResString(),
cancelText = "取消",
confirmClickCallback = {
context.startActivity(
ShellActivity.getIntent(
context,
ShellActivity.Type.REAL_NAME_INFO
).apply {
putExtra(EntranceConsts.KEY_SOURCE_ENTRANCE, "游戏实名")
putExtra(EntranceConsts.KEY_IS_FORCED_TO_CERTIFICATE, true)
}
)
NewLogUtils.logCertificationHintDialogOptionsClicked("前往实名认证")
SensorsBridge.trackVerificationPopupClick("前往实名认证")
},
cancelClickCallback = {
NewLogUtils.logCertificationHintDialogOptionsClicked("取消")
SensorsBridge.trackVerificationPopupClick("取消")
}
)
}
fun showMinorsOrVerifyingDialog(context: Context, title: String, content: String) {
DialogHelper.showDialog(
context,
title = title,
content = content,
confirmText = R.string.dialog_hint_confirm.toResString(),
cancelText = "",
)
}
}

View File

@ -0,0 +1,28 @@
package com.gh.gamecenter.gamedetail.accelerator.chain
import android.content.Context
import com.gh.common.util.PackageUtils
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorDialogFragment
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorDialogFragment.Companion.SPEED_NOT_INSTALLED
class AcceleratorCheckInstallInterceptor : AcceleratorValidator.Interceptor {
override fun intercept(
context: Context,
chain: AcceleratorValidator.Chain,
listener: AcceleratorValidator.ValidateListener?
) {
val request = chain.request
if (PackageUtils.isInstalled(context, request.game.getUniquePackageName() ?: "")) {
chain.proceed(context, request, listener)
} else {
val game = request.game
AcceleratorDialogFragment.show(
SPEED_NOT_INSTALLED,
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
request.sourceEntrance, context
)
}
}
}

View File

@ -0,0 +1,29 @@
package com.gh.gamecenter.gamedetail.accelerator.chain
import android.content.Context
class AcceleratorClient(
private vararg val interceptors: AcceleratorValidator.Interceptor,
) {
fun execute(
context: Context,
request: AcceleratorValidator.Request,
listener: AcceleratorValidator.ValidateListener
) {
val chain = RealAcceleratorInterceptorChain(interceptors.toList(), 0, request)
chain.proceed(context, request, listener)
}
companion object {
fun newInstance() = AcceleratorClient(
AcceleratorCheckInstallInterceptor(),
AcceleratorLoginInterceptor(),
AcceleratorPackageCheckInterceptor(),
AcceleratorRealNameInterceptor(),
AcceleratorVipInterceptor(),
AcceleratorStateInterceptor()
)
}
}

View File

@ -0,0 +1,18 @@
package com.gh.gamecenter.gamedetail.accelerator.chain
import android.content.Context
import com.gh.gamecenter.common.utils.ifLogin
class AcceleratorLoginInterceptor : AcceleratorValidator.Interceptor {
override fun intercept(
context: Context,
chain: AcceleratorValidator.Chain,
listener: AcceleratorValidator.ValidateListener?
) {
context.ifLogin("[网络加速]") {
val request = chain.request
chain.proceed(context, request, listener)
}
}
}

View File

@ -0,0 +1,20 @@
package com.gh.gamecenter.gamedetail.accelerator.chain
import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import com.gh.common.dialog.PackageCheckDialogFragment
class AcceleratorPackageCheckInterceptor : AcceleratorValidator.Interceptor {
override fun intercept(
context: Context,
chain: AcceleratorValidator.Chain,
listener: AcceleratorValidator.ValidateListener?
) {
val request = chain.request
if (context is AppCompatActivity) {
PackageCheckDialogFragment.show(context, request.game) {
chain.proceed(context, request, listener)
}
}
}
}

View File

@ -0,0 +1,26 @@
package com.gh.gamecenter.gamedetail.accelerator.chain
import android.content.Context
import com.gh.common.util.TempCertificationUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.toResString
import com.gh.gamecenter.gamedetail.accelerator.UserVerifyDialogUtils
class AcceleratorRealNameInterceptor : AcceleratorValidator.Interceptor {
override fun intercept(
context: Context,
chain: AcceleratorValidator.Chain,
listener: AcceleratorValidator.ValidateListener?
) {
val request = chain.request
if (TempCertificationUtils.isUserVerified()) {
chain.proceed(context, request, listener)
} else {
UserVerifyDialogUtils.showUnVerifiedDialog(
context,
R.string.real_name_tips.toResString(),
R.string.acceleration_service_without_real_name_description.toResString()
)
}
}
}

View File

@ -0,0 +1,52 @@
package com.gh.gamecenter.gamedetail.accelerator.chain
import android.content.Context
import com.gh.gamecenter.core.provider.IAcceleratorProvider
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorDialogFragment
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorDialogFragment.Companion.SPEED_CURRENT_ACCELERATING
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorReplaceDialogFragment
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
import com.therouter.TheRouter
class AcceleratorStateInterceptor : AcceleratorValidator.Interceptor {
override fun intercept(
context: Context,
chain: AcceleratorValidator.Chain,
listener: AcceleratorValidator.ValidateListener?
) {
val request = chain.request
val iAcceleratorProvider = TheRouter.get(IAcceleratorProvider::class.java)
val isAccelerating = iAcceleratorProvider?.isCurAccSuccess() ?: false
if (isAccelerating) {
val game = request.game
val isCurrentAccelerating = AcceleratorDataHolder.instance.isCurrentGameAccelerating(game.id)
if (isCurrentAccelerating) {
AcceleratorDialogFragment.show(
SPEED_CURRENT_ACCELERATING,
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
request.sourceEntrance,
context
)
} else {
AcceleratorReplaceDialogFragment.show(
context,
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
request.sourceEntrance
) {
if (chain.isValidContext(context)) {
listener?.finished(context)
}
}
}
} else {
chain.proceed(context, request, listener)
}
}
}

View File

@ -0,0 +1,71 @@
package com.gh.gamecenter.gamedetail.accelerator.chain
import android.content.Context
import android.os.Parcelable
import androidx.appcompat.app.AppCompatActivity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.gamedetail.accelerator.chain.AcceleratorValidator.Request
import kotlinx.parcelize.Parcelize
class AcceleratorValidator {
interface ValidateListener {
fun finished(context: Context)
}
interface Interceptor {
fun intercept(context: Context, chain: Chain, listener: ValidateListener?)
}
interface Chain {
val request: Request
fun proceed(context: Context, request: Request, listener: ValidateListener?)
fun isValidContext(context: Context) =
context is AppCompatActivity && !context.isFinishing && !context.isDestroyed
}
@Parcelize
data class Request(
val isVip: Boolean,
val isNewUser: Boolean,
val game: GameEntity,
val sourceEntrance: String
):Parcelable
}
class RealAcceleratorInterceptorChain(
private val interceptors: List<AcceleratorValidator.Interceptor>,
private val index: Int,
private val _request: Request
) : AcceleratorValidator.Chain {
override val request: Request
get() = _request
override
fun proceed(context: Context, request: Request, listener: AcceleratorValidator.ValidateListener?) {
if (!isValidContext(context)) {
// 如果 context失效说明当前页面已销毁直接中断流程
return
}
if (index >= interceptors.size) {
// 验证完成
listener?.finished(context)
return
}
val next = RealAcceleratorInterceptorChain(
interceptors,
index + 1,
_request
)
interceptors[index].intercept(context, next, listener)
}
}

View File

@ -0,0 +1,31 @@
package com.gh.gamecenter.gamedetail.accelerator.chain
import android.content.Context
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorDialogFragment
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorDialogFragment.Companion.SPEED_ENABLE_VIP
class AcceleratorVipInterceptor : AcceleratorValidator.Interceptor {
override fun intercept(
context: Context,
chain: AcceleratorValidator.Chain,
listener: AcceleratorValidator.ValidateListener?
) {
val request = chain.request
val isVip = request.isVip
val isNewUser = request.isNewUser
if (isVip || isNewUser) {
chain.proceed(context, request, listener)
} else {
val game = request.game
AcceleratorDialogFragment.show(
SPEED_ENABLE_VIP,
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
request.sourceEntrance,
context
)
}
}
}

View File

@ -0,0 +1,260 @@
package com.gh.gamecenter.gamedetail.accelerator.dialog
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.IntDef
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.constant.EntranceConsts
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.core.provider.IAcceleratorProvider
import com.gh.gamecenter.core.utils.CurrentActivityHolder
import com.gh.gamecenter.databinding.DialogFragmentSpeedReplaceGameBinding
import com.therouter.TheRouter
/**
* 加速器相关的dialog
*/
class AcceleratorDialogFragment : BaseDialogFragment() {
private lateinit var binding: DialogFragmentSpeedReplaceGameBinding
private lateinit var uiHelper: SpeedDialogUiHelper
private var pkgName = ""
private var gameId = ""
private var gameName = ""
private var sourceEntrance = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val type = arguments?.getInt(EntranceConsts.KEY_SPEED_TYPE) ?: 0
pkgName = arguments?.getString(EntranceConsts.KEY_PACKAGENAME) ?: ""
gameId = arguments?.getString(EntranceConsts.KEY_GAMEID) ?: ""
gameName = arguments?.getString(EntranceConsts.KEY_GAMENAME) ?: ""
sourceEntrance = arguments?.getString(EntranceConsts.KEY_SOURCE_ENTRANCE) ?: ""
uiHelper = when (type) {
SPEED_ENABLE_VIP -> {
SensorsBridge.trackMembershipActivationDialogShow(pkgName, gameId, gameName, sourceEntrance)
EnableVipUi()
}
SPEED_START_FAILURE -> {
SensorsBridge.trackNetworkAccelerationFailureDialogShow(pkgName, gameId, gameName, sourceEntrance)
SpeedFailureUi()
}
SPEED_STOP -> StopSpeedUi()
SPEED_NOT_INSTALLED -> NotInstalledUi()
SPEED_CURRENT_ACCELERATING -> {
SensorsBridge.trackStopAcceleratingDialogShow(gameId, gameName, pkgName)
CurrentAcceleratingUi()
}
else -> throw IllegalArgumentException("请传递正确的参数 SpeedType !")
}
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val createDialog = super.onCreateDialog(savedInstanceState)
createDialog.setCanceledOnTouchOutside(true)
createDialog.setCancelable(true)
return createDialog
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return DialogFragmentSpeedReplaceGameBinding.inflate(inflater, container, false)
.also {
binding = it
}.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.tvContent.setText(uiHelper.contentResId)
binding.tvCancel.goneIf(!uiHelper.isShowCancelButton) {
if (uiHelper.cancelResId != -1) {
binding.tvCancel.setText(uiHelper.cancelResId)
}
binding.tvCancel.setOnClickListener {
dismiss()
}
}
binding.tvSubmit.setText(uiHelper.submitResId)
binding.tvSubmit.setTextColor(uiHelper.submitTextColorResId.toColor(view.context))
binding.tvSubmit.setBackgroundResource(uiHelper.submitBackgroundResId)
binding.tvSubmit.setOnClickListener {
dismiss()
when (uiHelper) {
is EnableVipUi -> {
SensorsBridge.trackMembershipActivationDialogClick(
pkgName,
gameId,
gameName,
sourceEntrance
)
SensorsBridge.trackMyAssetsPageShow(pkgName, gameId, gameName, sourceEntrance)
DirectUtils.navigateToMyAssetsPage(requireContext(), sourceEntrance)
}
is SpeedFailureUi -> {
SensorsBridge.trackNetworkAccelerationFailureDialogClick(pkgName, gameId, gameName, sourceEntrance)
context?.let {
DirectUtils.directToWebView(it, Constants.QQ_QIDIAN_ADDRESS, "")
}
}
is StopSpeedUi, is CurrentAcceleratingUi -> {
TheRouter.get(IAcceleratorProvider::class.java)?.stopQyGameAccelerate()
}
}
}
}
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 SPEED_ENABLE_VIP = 1
const val SPEED_START_FAILURE = 2
const val SPEED_STOP = 3
const val SPEED_NOT_INSTALLED = 4
const val SPEED_CURRENT_ACCELERATING = 5
fun show(
@SpeedType type: Int,
pkgName: String,
gameId: String,
gameName: String,
sourceEntrance: String,
context: Context
) {
if (context is AppCompatActivity) {
context.supportFragmentManager
} else {
(CurrentActivityHolder.getCurrentActivity() as? AppCompatActivity)?.supportFragmentManager
}?.let {
val fragment = AcceleratorDialogFragment().apply {
arguments = Bundle().apply {
putInt(EntranceConsts.KEY_SPEED_TYPE, type)
putString(EntranceConsts.KEY_PACKAGENAME, pkgName)
putString(EntranceConsts.KEY_GAMEID, gameId)
putString(EntranceConsts.KEY_GAMENAME, gameName)
putString(EntranceConsts.KEY_SOURCE_ENTRANCE, sourceEntrance)
}
}
fragment.show(it, AcceleratorDialogFragment::class.java.simpleName)
}
}
}
@IntDef(SPEED_ENABLE_VIP, SPEED_START_FAILURE, SPEED_STOP, SPEED_NOT_INSTALLED, SPEED_CURRENT_ACCELERATING)
@Retention(AnnotationRetention.SOURCE)
annotation class SpeedType
abstract class SpeedDialogUiHelper {
abstract val contentResId: Int
abstract val isShowCancelButton: Boolean
open val cancelResId: Int = -1
abstract val submitResId: Int
open val submitTextColorResId = com.gh.gamecenter.common.R.color.text_aw_primary
open val submitBackgroundResId = com.gh.gamecenter.common.R.drawable.bg_common_button_fill_blue
}
class EnableVipUi : SpeedDialogUiHelper() {
override val contentResId: Int
get() = R.string.enable_vip_tips
override val isShowCancelButton: Boolean
get() = false
override val submitResId: Int
get() = R.string.go_to_activate
}
class SpeedFailureUi : SpeedDialogUiHelper() {
override val contentResId: Int
get() = R.string.speed_failure_tips
override val isShowCancelButton: Boolean
get() = false
override val submitResId: Int
get() = R.string.contact_service
override val submitTextColorResId: Int
get() = com.gh.gamecenter.common.R.color.primary_theme
override val submitBackgroundResId: Int
get() = com.gh.gamecenter.common.R.drawable.bg_common_button_light_fill_blue
}
class StopSpeedUi : SpeedDialogUiHelper() {
override val contentResId: Int
get() = R.string.stop_speed_tips
override val isShowCancelButton: Boolean
get() = true
override val submitResId: Int
get() = R.string.stop_speed
override val cancelResId: Int
get() = R.string.cancel
}
class NotInstalledUi : SpeedDialogUiHelper() {
override val contentResId: Int
get() = R.string.speed_not_installed_tips
override val isShowCancelButton: Boolean
get() = false
override val submitResId: Int
get() = R.string.dialog_hint_confirm
}
class CurrentAcceleratingUi : SpeedDialogUiHelper() {
override val contentResId: Int
get() = R.string.speed_current_game_accelerating
override val isShowCancelButton: Boolean
get() = true
override val submitResId: Int
get() = R.string.stop_speed
override val cancelResId: Int
get() = R.string.cancel
}
}

View File

@ -0,0 +1,116 @@
package com.gh.gamecenter.gamedetail.accelerator.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.R
import com.gh.gamecenter.common.HaloApp
import com.gh.gamecenter.common.constant.EntranceConsts
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.databinding.DialogFragmentSpeedReplaceGameBinding
import com.lightgame.dialog.BaseDialogFragment
class AcceleratorReplaceDialogFragment : BaseDialogFragment() {
private lateinit var binding: DialogFragmentSpeedReplaceGameBinding
private var pkgName = ""
private var gameId = ""
private var gameName = ""
private var sourceEntrance = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
pkgName = arguments?.getString(EntranceConsts.KEY_PACKAGENAME) ?: ""
gameId = arguments?.getString(EntranceConsts.KEY_GAMEID) ?: ""
gameName = arguments?.getString(EntranceConsts.KEY_GAMENAME) ?: ""
sourceEntrance = arguments?.getString(EntranceConsts.KEY_SOURCE_ENTRANCE) ?: ""
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return DialogFragmentSpeedReplaceGameBinding.inflate(inflater, container, false)
.also {
binding = it
}.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
SensorsBridge.trackNetworkAccelerationConflictDialogShow(pkgName, gameId, gameName, sourceEntrance)
binding.tvContent.text = getString(R.string.speed_replace_game_tips, gameName)
binding.tvCancel.setOnClickListener {
SensorsBridge.trackNetworkAccelerationConflictDialogClick(
pkgName,
gameId,
gameName,
BUTTON_NAME_CANCEL,
sourceEntrance
)
dismiss()
}
binding.tvSubmit.setOnClickListener {
SensorsBridge.trackNetworkAccelerationConflictDialogClick(
pkgName,
gameId,
gameName,
BUTTON_NAME_CONTINUE,
sourceEntrance
)
// 加速
dismiss()
callback?.invoke()
}
}
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)
}
private var callback: (() -> Unit)? = null
fun setOnSubmitListener(callback: () -> Unit) {
this.callback = callback
}
companion object {
private const val BUTTON_NAME_CANCEL = "暂不启动"
private const val BUTTON_NAME_CONTINUE = "继续启动"
fun show(
context: Context,
pkgName: String,
gameId: String,
gameName: String,
sourceEntrance: String,
callback: () -> Unit
) {
if (context is AppCompatActivity) {
context.supportFragmentManager
} else {
(CurrentActivityHolder.getCurrentActivity() as? AppCompatActivity)?.supportFragmentManager
}?.let {
val fragment = AcceleratorReplaceDialogFragment().apply {
setOnSubmitListener(callback)
arguments = Bundle().apply {
putString(EntranceConsts.KEY_PACKAGENAME, pkgName)
putString(EntranceConsts.KEY_GAMEID, gameId)
putString(EntranceConsts.KEY_GAMENAME, gameName)
putString(EntranceConsts.KEY_SOURCE_ENTRANCE, sourceEntrance)
}
}
fragment.show(it, AcceleratorDialogFragment::class.java.simpleName)
}
}
}
}

View File

@ -0,0 +1,213 @@
package com.gh.gamecenter.gamedetail.accelerator.dialog
import android.content.Context
import android.graphics.Rect
import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.BaseBottomDialogFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.CurrentActivityHolder
import com.gh.gamecenter.databinding.DialogFragmentAcceleratorZoneBinding
import com.gh.gamecenter.databinding.RecyclerAcceleratorZoneBinding
import com.gh.gamecenter.feature.entity.AcctGameInfo
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.gamedetail.AcceleratorZoneViewModel
import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.SCENE_TYPE_NO_GUIDE_LAYER
import com.gh.gamecenter.gamedetail.accelerator.chain.AcceleratorClient
import com.gh.gamecenter.gamedetail.accelerator.chain.AcceleratorValidator
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
import kotlin.math.roundToInt
class AcceleratorZoneDialogFragment : BaseBottomDialogFragment<DialogFragmentAcceleratorZoneBinding>() {
private val viewModel by viewModels<AcceleratorZoneViewModel>()
private lateinit var adapter: ZoneAdapter
private var sourceEntrance = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
sourceEntrance = arguments?.getString(EntranceConsts.KEY_SOURCE_ENTRANCE) ?: ""
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val data =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
arguments?.getParcelableArrayList(EntranceConsts.KEY_DATA, AcctGameInfo.ZoneInfo::class.java)
} else {
arguments?.getParcelableArrayList(EntranceConsts.KEY_DATA)
} ?: listOf<AcctGameInfo.ZoneInfo>()
val game = arguments?.getParcelable<GameEntity>(EntranceConsts.KEY_GAME)!!
val selectedId = arguments?.getInt(KEY_SELECTED_ID) ?: -1
mBinding.titleView.setOnRightClickListener {
dismiss()
}
adapter = ZoneAdapter {
startAccelerating(game, it)
dismiss()
}
mBinding.recyclerZone.layoutManager = GridLayoutManager(requireContext(), 3)
mBinding.recyclerZone.addItemDecoration(MyDecorationItem())
mBinding.recyclerZone.adapter = adapter
adapter.setData(selectedId, data)
}
private fun startAccelerating(game: GameEntity, zoneInfo: AcctGameInfo.ZoneInfo) {
context?.let {
viewModel.useCase.insertAcctGameInfo(AcctGameInfo(game.id, game.getUniquePackageName() ?: "", zoneInfo))
val acceleratorDataHolder = AcceleratorDataHolder.instance
val memberType = acceleratorDataHolder.memberType
SensorsBridge.trackNetworkAccelerationButtonClick(
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
memberType,
zoneInfo.cnName ?: "",
SCENE_TYPE_NO_GUIDE_LAYER,
sourceEntrance
)
val isVip = acceleratorDataHolder.isVip
val isNewUser = acceleratorDataHolder.isNewUser
val request = AcceleratorValidator.Request(isVip, isNewUser, game, sourceEntrance)
AcceleratorClient.newInstance()
.execute(it, request, object : AcceleratorValidator.ValidateListener {
override fun finished(context: Context) {
StartingAcceleratorDialogFragment.show(
context,
zoneInfo,
game,
isNeedRecord = false,
hasMultiZone = true,
sourceEntrance = sourceEntrance
)
}
})
}
}
companion object {
private const val KEY_SELECTED_ID = "key_selected_id"
fun show(
context: Context,
selectedId: Int? = null,
data: List<AcctGameInfo.ZoneInfo>,
game: GameEntity,
sourceEntrance: String
) {
if (context is AppCompatActivity) {
context.supportFragmentManager
} else {
(CurrentActivityHolder.getCurrentActivity() as? AppCompatActivity)?.supportFragmentManager
}?.let {
val fragment = AcceleratorZoneDialogFragment().apply {
arguments = Bundle().apply {
putParcelableArrayList(EntranceConsts.KEY_DATA, data.toArrayList())
putParcelable(EntranceConsts.KEY_GAME, game)
putInt(KEY_SELECTED_ID, selectedId ?: -1)
putString(EntranceConsts.KEY_SOURCE_ENTRANCE, sourceEntrance)
}
}
fragment.show(it, fragment::class.java.simpleName)
}
}
}
private class MyDecorationItem : RecyclerView.ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
val position = parent.getChildAdapterPosition(view)
val space = (16F.dip2px() / 3F).roundToInt()
val (left, right) = when (position % 3) {
0 -> 0 to space
1 -> space to space
else -> space to 0
}
outRect.top = 8F.dip2px()
outRect.left = left
outRect.right = right
}
}
class ZoneAdapter(private val click: (AcctGameInfo.ZoneInfo) -> Unit) :
ListAdapter<AcctGameInfo.ZoneInfo, ZoneAdapter.ZoneViewHolder>(diffCallback) {
private var selectedId = -1
fun setData(selectedId: Int, data: List<AcctGameInfo.ZoneInfo>) {
this.selectedId = selectedId
submitList(data)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ZoneViewHolder {
return ZoneViewHolder(parent.toBinding())
}
override fun onBindViewHolder(holder: ZoneViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.isNotEmpty()) {
updateSelectedState(holder, position)
} else {
super.onBindViewHolder(holder, position, payloads)
}
}
override fun onBindViewHolder(holder: ZoneViewHolder, position: Int) {
val item = getItem(position) ?: return
updateSelectedState(holder, item.id)
holder.binding.tvName.text = item.cnName
holder.itemView.setOnClickListener {
click(item)
}
}
private fun updateSelectedState(holder: ZoneViewHolder, zoneId: Int) {
val (textColorResId, backgroundResId) = if (selectedId == zoneId) {
com.gh.gamecenter.common.R.color.text_theme to R.drawable.bg_shape_2496ff_alpha_10_radius_8
} else {
com.gh.gamecenter.common.R.color.text_secondary to R.drawable.bg_shape_f8_radius_8
}
holder.binding.tvName.setTextColor(textColorResId.toColor(holder.itemView.context))
holder.binding.tvName.setBackgroundResource(backgroundResId)
}
companion object {
private val diffCallback = object : DiffUtil.ItemCallback<AcctGameInfo.ZoneInfo>() {
override fun areItemsTheSame(oldItem: AcctGameInfo.ZoneInfo, newItem: AcctGameInfo.ZoneInfo): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(
oldItem: AcctGameInfo.ZoneInfo,
newItem: AcctGameInfo.ZoneInfo
): Boolean {
return oldItem == newItem
}
}
}
class ZoneViewHolder(val binding: RecyclerAcceleratorZoneBinding) : ViewHolder(binding.root)
}
}

View File

@ -0,0 +1,280 @@
package com.gh.gamecenter.gamedetail.accelerator.dialog
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.viewModels
import com.gh.common.util.PackageLauncher
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.BaseDialogFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.core.callback.AccelerateState
import com.gh.gamecenter.core.callback.OnAccelerateListener
import com.gh.gamecenter.core.provider.IAcceleratorProvider
import com.gh.gamecenter.core.utils.CurrentActivityHolder
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.databinding.DialogFragmentStartingAcceleratorBinding
import com.gh.gamecenter.feature.entity.AcctGameInfo
import com.gh.gamecenter.feature.entity.AcctRecord
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.gamedetail.StartingAcceleratorViewModel
import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.DISTRICT_SERVER_HAVA
import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.SOURCE_ENTRANCE_GAME_DETAIL
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorDialogFragment.Companion.SPEED_ENABLE_VIP
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorDialogFragment.Companion.SPEED_START_FAILURE
import com.gh.gamecenter.livedata.EventObserver
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.login.user.UserRepository
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
import com.therouter.TheRouter
import io.reactivex.disposables.CompositeDisposable
import kotlin.math.max
class StartingAcceleratorDialogFragment : BaseDialogFragment() {
private val viewModel by viewModels<StartingAcceleratorViewModel>()
private lateinit var binding: DialogFragmentStartingAcceleratorBinding
private lateinit var zoneInfo: AcctGameInfo.ZoneInfo
private lateinit var game: GameEntity
private var isNeedRecord: Boolean = false
private var iAcceleratorProvider: IAcceleratorProvider? = null
private var hasMultiZone = false
private var sourceEntrance = SOURCE_ENTRANCE_GAME_DETAIL
private val compositeDisposable = CompositeDisposable()
private var _progress = 0
private val handler = Handler(Looper.getMainLooper())
private var refreshTokenCount = 0
private val acceleratorDataHolder = AcceleratorDataHolder.instance
private val accelerationListener = object : OnAccelerateListener {
override fun onStateChanged(state: AccelerateState) {
// 如果状态能成功回,则移除计时
handler.removeCallbacksAndMessages(null)
when (state) {
is AccelerateState.Success -> {
ToastUtils.showToast("加速成功")
// 加速成功,启动游戏
PackageLauncher.launchApp(requireContext(), game, game.getUniquePackageName())
// 记录加速成功的游戏
viewModel.useCase.recordAcctGameInfo(game, zoneInfo, hasMultiZone)
trackNetworkAccelerationStartupResult("成功")
dismissAllowingStateLoss()
}
is AccelerateState.Failure -> {
// 有些错误需要额外处理,这里直接跳过错误提示(如token失效登录时token设置失败)
if ((state.isTokenExpired || state.isTokenEmpty) && refreshTokenCount < REFRESH_TOKEN_MAX_COUNT) {
// do nothing
} else {
if (!state.isSkipError) {
context?.let {
AcceleratorDialogFragment.show(
SPEED_START_FAILURE,
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
sourceEntrance,
it
)
// 加速失败上传奇游加速器log
unloadAcceleratorErrorLog(game)
}
}
if (state.isPermissionExpired) {
// 加速会员过期刷新本地vip状态
val userId = UserManager.getInstance().userId
UserRepository.getInstance().refreshVipStatus(userId, true)
// 加速失败上传奇游加速器log
unloadAcceleratorErrorLog(game)
}
dismissAllowingStateLoss()
trackNetworkAccelerationStartupResult("失败(${state.code})")
}
}
is AccelerateState.Normal -> {
if (state.isTokenExpired || state.isTokenEmpty) {
// token过期/登录时token设置失败,在此获取token并重新启动加速(最多重试三次)
if (refreshTokenCount < REFRESH_TOKEN_MAX_COUNT) {
viewModel.loadAcceleratorToken()
refreshTokenCount++
}
}
}
else -> Unit
}
}
override fun onProgress(progress: Int, curGamePkgName: String?, curGameZoneFlag: String?) {
_progress = max(_progress, progress)
binding.tvProgress.text = getString(R.string.accelerating_with_progress, "$progress")
}
}
private fun unloadAcceleratorErrorLog(game: GameEntity) {
viewModel.useCase.unloadAcceleratorErrorLog(game)
}
private fun trackNetworkAccelerationStartupResult(result: String) {
SensorsBridge.trackNetworkAccelerationStartupResult(
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
acceleratorDataHolder.memberType,
if (hasMultiZone) zoneInfo.cnName ?: "" else DISTRICT_SERVER_HAVA,
result,
sourceEntrance
)
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return super.onCreateDialog(savedInstanceState).apply {
setCancelable(false)
setCanceledOnTouchOutside(false)
this@StartingAcceleratorDialogFragment.isCancelable = false
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
iAcceleratorProvider = TheRouter.get(IAcceleratorProvider::class.java)
zoneInfo = arguments?.getParcelable(EntranceConsts.KEY_ACCT_ZONE_INFO)!!
game = arguments?.getParcelable(EntranceConsts.KEY_GAME)!!
isNeedRecord = arguments?.getBoolean(EntranceConsts.KEY_IS_NEED_RECORD) ?: false
hasMultiZone = arguments?.getBoolean(EntranceConsts.KEY_HAS_MULTI_ZONE) ?: false
sourceEntrance = arguments?.getString(EntranceConsts.KEY_SOURCE_ENTRANCE) ?: SOURCE_ENTRANCE_GAME_DETAIL
}
override fun onBack(): Boolean {
return true
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
return DialogFragmentStartingAcceleratorBinding.inflate(inflater, container, false)
.also {
binding = it
}.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.tvProgress.text = getString(R.string.accelerating_with_progress, "0")
iAcceleratorProvider?.bindAccRelatedListener("", accelerationListener)
val isVip = acceleratorDataHolder.isVip
val isNewUser = acceleratorDataHolder.isNewUser
if (isNewUser && !isVip) {
// 新用户并且还不是vip
viewModel.rechargeTrial()
} else {
startGameAccelerate()
}
viewModel.restartingAcceleratorAction.observe(viewLifecycleOwner, EventObserver {
startGameAccelerate()
})
viewModel.rechargeTrailResult.observe(viewLifecycleOwner, EventObserver {
if (it) {
startGameAccelerate()
} else {
// 充值失败
AcceleratorDialogFragment.show(
SPEED_START_FAILURE,
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
sourceEntrance,
requireContext()
)
dismissAllowingStateLoss()
}
})
}
private fun startGameAccelerate() {
handler.removeCallbacksAndMessages(null)
handler.postDelayed({
// 15s以后不管成功还是失败都要关闭当前页面
dismissAllowingStateLoss()
}, TIME_OUT)
AcceleratorDataHolder.instance.acctGameRecord = AcctRecord(game.id, game, zoneInfo, hasMultiZone, 0)
iAcceleratorProvider?.startQyGameAccelerate(game.id, game.getUniquePackageName() ?: "", zoneInfo)
if (isNeedRecord) {
viewModel.useCase.insertAcctGameInfo(
AcctGameInfo(game.id, game.getUniquePackageName() ?: "", zoneInfo)
)
}
if (refreshTokenCount == 0) {
SensorsBridge.trackNetworkAccelerationStartup(
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
acceleratorDataHolder.memberType,
if (hasMultiZone) zoneInfo.cnName ?: "" else DISTRICT_SERVER_HAVA,
sourceEntrance
)
}
}
override fun onDestroyView() {
handler.removeCallbacksAndMessages(null)
iAcceleratorProvider?.unBindAccRelatedListener(accelerationListener)
super.onDestroyView()
compositeDisposable.clear()
}
companion object {
private const val TIME_OUT = 1000 * 15L
private const val REFRESH_TOKEN_MAX_COUNT = 3
fun show(
context: Context,
zoneInfo: AcctGameInfo.ZoneInfo,
game: GameEntity,
isNeedRecord: Boolean,
hasMultiZone: Boolean,
sourceEntrance: String
) {
if (context is AppCompatActivity) {
context.supportFragmentManager
} else {
(CurrentActivityHolder.getCurrentActivity() as? AppCompatActivity)?.supportFragmentManager
}?.let {
val fragment = StartingAcceleratorDialogFragment().apply {
arguments = Bundle().apply {
putParcelable(EntranceConsts.KEY_ACCT_ZONE_INFO, zoneInfo)
putParcelable(EntranceConsts.KEY_GAME, game)
putBoolean(EntranceConsts.KEY_IS_NEED_RECORD, isNeedRecord)
putBoolean(EntranceConsts.KEY_HAS_MULTI_ZONE, hasMultiZone)
putString(EntranceConsts.KEY_SOURCE_ENTRANCE, sourceEntrance)
}
}
fragment.show(it, fragment::class.java.simpleName)
}
}
}
}

View File

@ -1,81 +1,125 @@
package com.gh.gamecenter.gamedetail.cloudarchive
import android.os.Bundle
import android.view.View
import android.view.inputmethod.EditorInfo
import androidx.core.os.bundleOf
import com.gh.common.util.NewFlatLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.catalog.SpecialCatalogFragment
import com.gh.gamecenter.cloudarchive.CloudArchiveManagerActivity
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.base.fragment.LazyFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toResString
import com.gh.gamecenter.common.json.json
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.FragmentCloudArchiveAlBinding
import com.gh.gamecenter.databinding.FragmentCloudArchiveBinding
import com.gh.gamecenter.feature.entity.GameEntity
class CloudArchiveFragment : LazyFragment() {
private var mUseAlternativeLayout = false
private var mGameEntity: GameEntity? = null
private var mBinding: FragmentCloudArchiveBinding? = null
private var mAlternativeBinding: FragmentCloudArchiveAlBinding? = null
private var mSearchFragment: CloudArchiveListFragment? = null
private var mNormalFragment: CloudArchiveListFragment? = null
private var mIsSearch = false
private var mOrderList =
listOf(CloudArchiveListViewModel.SortType.NEWEST, CloudArchiveListViewModel.SortType.HOTTEST)
override fun getRealLayoutId() = R.layout.fragment_cloud_archive
private val searchBarBinding
get() = if (mUseAlternativeLayout) mAlternativeBinding?.searchBar else mBinding?.searchBar
private val orderSfv
get() = if (mUseAlternativeLayout) mAlternativeBinding?.orderSfv else mBinding?.orderSfv
private val archiveManageTv
get() = if (mUseAlternativeLayout) mAlternativeBinding?.archiveManageTv else mBinding?.archiveManageTv
override fun onCreate(savedInstanceState: Bundle?) {
mUseAlternativeLayout = arguments?.getBoolean(EntranceConsts.KEY_USE_ALTERNATIVE_LAYOUT) ?: false
super.onCreate(savedInstanceState)
}
override fun getRealLayoutId() =
if (mUseAlternativeLayout) R.layout.fragment_cloud_archive_al else R.layout.fragment_cloud_archive
override fun onRealLayoutInflated(inflatedView: View) {
mBinding = FragmentCloudArchiveBinding.bind(inflatedView)
if (mUseAlternativeLayout) {
mAlternativeBinding = FragmentCloudArchiveAlBinding.bind(inflatedView)
} else {
mBinding = FragmentCloudArchiveBinding.bind(inflatedView)
}
}
override fun onFragmentFirstVisible() {
mGameEntity = arguments?.getParcelable(EntranceConsts.KEY_GAME)
mGameEntity = arguments?.getParcelable(EntranceConsts.KEY_GAME_ENTITY)
super.onFragmentFirstVisible()
if (mUseAlternativeLayout) {
requireActivity().updateStatusBarColor(com.gh.gamecenter.common.R.color.ui_surface, com.gh.gamecenter.common.R.color.ui_surface)
}
changeContentFragment()
SensorsBridge.trackEvent("CloudSavePageView", json {
"game_id" to mGameEntity?.id
"game_name" to mGameEntity?.name
"last_page_name" to GlobalActivityManager.getLastPageEntity().pageName
"last_page_id" to GlobalActivityManager.getLastPageEntity().pageId
})
}
override fun inflateRealView() {
super.inflateRealView()
mBinding?.run {
searchBar.etSearch.hint = R.string.game_detail_cloud_archive_search_hint.toResString()
orderSfv.setItemList(mOrderList.map { it.value }, 1)
orderSfv.setOnCheckedCallback {
mAlternativeBinding?.run {
reuseToolbar.backBtn.setOnClickListener {
requireActivity().onBackPressed()
}
reuseToolbar.normalTitle.run {
text = "${mGameEntity?.name}-云存档"
marqueeOnce()
}
}
orderSfv?.run {
setItemList(mOrderList.map { it.value }, 1)
setOnCheckedCallback {
if (mIsSearch) {
mSearchFragment?.updateSortType(mOrderList[it])
} else {
mNormalFragment?.updateSortType(mOrderList[it])
}
}
searchBar.etSearch.setOnEditorActionListener { _, actionId, _ ->
}
searchBarBinding?.run {
etSearch.hint = R.string.game_detail_cloud_archive_search_hint.toResString()
tvBack.setOnClickListener {
changeSearchStatus(false)
etSearch.setText("")
}
etSearch.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
searchBar.tvSearch.performClick()
tvSearch.performClick()
}
false
}
searchBar.tvSearch.setOnClickListener {
val keyWord = searchBar.etSearch.text.toString().trim { it <= ' ' }
tvSearch.setOnClickListener {
val keyWord = etSearch.text.toString().trim { it <= ' ' }
if (keyWord.isBlank()) {
toast(R.string.search_hint)
} else {
changeSearchStatus(true)
}
}
searchBar.tvBack.setOnClickListener {
changeSearchStatus(false)
searchBar.etSearch.setText("")
}
archiveManageTv.setOnClickListener {
startActivity(
CloudArchiveManagerActivity.getIntent(
requireContext(),
mGameEntity ?: GameEntity(),
arguments?.getString(EntranceConsts.KEY_ARCHIVE_CONFIG_URL) ?: "",
"游戏详情页"
)
}
archiveManageTv?.setOnClickListener {
startActivity(
CloudArchiveManagerActivity.getIntent(
requireContext(),
mGameEntity ?: GameEntity(),
arguments?.getString(EntranceConsts.KEY_ARCHIVE_CONFIG_URL) ?: "",
"游戏详情页"
)
}
)
}
}
@ -99,7 +143,7 @@ class CloudArchiveFragment : LazyFragment() {
EntranceConsts.KEY_ARCHIVE_CONFIG_URL to arguments?.getString(EntranceConsts.KEY_ARCHIVE_CONFIG_URL, "")
)
if (mIsSearch) {
val keyWord = mBinding?.searchBar?.etSearch?.text?.toString()?.trim { it <= ' ' }
val keyWord = searchBarBinding?.etSearch?.text?.toString()?.trim { it <= ' ' }
bundle.putString(EntranceConsts.KEY_SEARCHKEY, keyWord)
NewFlatLogUtils.logCloudArchiveSearchKeyUpload(
mGameEntity?.id ?: "",
@ -113,7 +157,7 @@ class CloudArchiveFragment : LazyFragment() {
.replace(
R.id.contentFragment,
fragment,
SpecialCatalogFragment::class.java.name
CloudArchiveListFragment::class.java.name
)
.commitAllowingStateLoss()
}
@ -122,14 +166,36 @@ class CloudArchiveFragment : LazyFragment() {
when {
mIsSearch != isSearch -> {
mIsSearch = isSearch
mBinding?.searchBar?.tvBack?.goneIf(!mIsSearch)
searchBarBinding?.tvBack?.goneIf(!mIsSearch)
changeContentFragment()
}
mIsSearch -> {
val keyWord = mBinding?.searchBar?.etSearch?.text?.toString()?.trim { it <= ' ' } ?: ""
val keyWord = searchBarBinding?.etSearch?.text?.toString()?.trim { it <= ' ' } ?: ""
NewFlatLogUtils.logCloudArchiveSearchKeyUpload(mGameEntity?.id ?: "", mGameEntity?.name ?: "", keyWord)
mSearchFragment?.updateSearchKeyWord(keyWord)
}
}
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
if (mUseAlternativeLayout) {
requireActivity().updateStatusBarColor(com.gh.gamecenter.common.R.color.ui_surface, com.gh.gamecenter.common.R.color.ui_surface)
}
mAlternativeBinding?.reuseToolbar?.run {
normalToolbar.setBackgroundColor(
com.gh.gamecenter.common.R.color.ui_surface.toColor(requireContext()))
backBtn.setImageResource(com.gh.gamecenter.common.R.drawable.ic_bar_back)
normalTitle.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()))
}
orderSfv?.run {
setContainerBackground(com.gh.gamecenter.common.R.drawable.button_round_f5f5f5.toDrawable(requireContext()))
setIndicatorBackground(R.drawable.bg_game_collection_sfv_indicator.toDrawable(requireContext()))
setTextColor(
com.gh.gamecenter.common.R.color.text_secondary.toColor(requireContext()),
com.gh.gamecenter.common.R.color.text_tertiary.toColor(requireContext())
)
}
}
}

View File

@ -15,7 +15,7 @@ class CloudArchiveListViewModel(
private val mGameId: String,
private var mKeyWord: String,
configUrl: String
) : BaseCloudArchiveViewModel(application, configUrl) {
) : BaseCloudArchiveViewModel(application, mGameId, configUrl) {
val refresh = MutableLiveData<Boolean>()
private var mSortType = SortType.HOTTEST

View File

@ -1,337 +0,0 @@
package com.gh.gamecenter.gamedetail.desc
import android.content.Context
import android.graphics.drawable.ColorDrawable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.PopupWindow
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.*
import com.gh.common.util.DialogUtils
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.baselist.ListAdapter
import com.gh.gamecenter.common.callback.ConfirmListener
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.ItemViewType
import com.gh.gamecenter.common.eventbus.EBReuse
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.SpanBuilder
import com.gh.gamecenter.databinding.ItemGameDetailRatingCommentBinding
import com.gh.gamecenter.entity.RatingComment
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.gamedetail.GameDetailFragment
import com.gh.gamecenter.gamedetail.rating.RatingReplyActivity
import com.gh.gamecenter.gamedetail.rating.edit.RatingEditActivity
import com.gh.gamecenter.gamedetail.rating.logs.CommentLogsActivity
import com.gh.gamecenter.login.user.UserManager
import org.greenrobot.eventbus.EventBus
import java.util.regex.Pattern
class DescCommentsAdapter(
context: Context,
var mViewModel: DescViewModel,
private var mEntrance: String,
private var gameName: String?
) : ListAdapter<RatingComment>(context) {
var comments = ArrayList<RatingComment>()
val path = "游戏详情:介绍"
override fun getItemViewType(position: Int): Int {
return if (position == comments.size) {
ItemViewType.ITEM_FOOTER
} else {
ItemViewType.ITEM_BODY
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == ItemViewType.ITEM_BODY) {
GameDetailRatingCommentViewHolder(parent.toBinding())
} else {
MoreViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_game_detail_comment_more, parent, false))
}
}
override fun getItemCount(): Int {
return comments.size + 1
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is GameDetailRatingCommentViewHolder) {
val commentData = comments[position]
var isChildLongClick = false
holder.binding.run {
ImageUtils.display(userIcon, commentData.user.icon)
ImageUtils.display(userBadge, commentData.user.auth?.icon)
userName.text = commentData.user.name
ratingStart.rating = commentData.star.toFloat()
val p = Pattern.compile(RatingEditActivity.LABEL_REGEX)
val m = p.matcher(commentData.content)
if (m.find()) {
val contents =
TextHelper.getCommentLabelSpannableStringBuilder(commentData.content, com.gh.gamecenter.common.R.color.text_theme)
content.setTextWithHighlightedTextWrappedInsideWrapper(
text = contents,
highlightedTextClickListener = TextHelper.DirectToWebViewHighlightedTextClick(mContext, path)
)
} else {
content.setTextWithHighlightedTextWrappedInsideWrapper(
text = commentData.content,
highlightedTextClickListener = TextHelper.DirectToWebViewHighlightedTextClick(mContext, path)
)
}
if (commentData.user.badge != null) {
sdvUserBadge.visibility = View.VISIBLE
tvBadgeName.visibility = View.VISIBLE
ImageUtils.display(sdvUserBadge, commentData.user.badge?.icon)
tvBadgeName.text = commentData.user.badge?.name
} else {
sdvUserBadge.visibility = View.GONE
tvBadgeName.visibility = View.GONE
}
ipRegionTv.goneIf(!(commentData.source != null && commentData.source.region.isNotEmpty()))
ipRegionTv.text = " · ${commentData.source?.region}"
when {
commentData.isEditContent == null -> {
time.setTextColor(ContextCompat.getColor(mContext, com.gh.gamecenter.common.R.color.text_tertiary))
time.text = if (commentData.ignore) {
val s = "${NewsUtils.getFormattedTime(commentData.time)} 保护期评论不计入总分"
SpanBuilder(s).image(s.length - 12, s.length - 11, R.drawable.ic_ignore_rating_tips)
.color(mContext, s.length - 10, s.length, com.gh.gamecenter.common.R.color.text_secondary).build()
} else {
NewsUtils.getFormattedTime(commentData.time)
}
}
commentData.isEditContent!! -> {
time.setTextColor(ContextCompat.getColor(mContext, com.gh.gamecenter.common.R.color.text_F56614))
time.text = if (commentData.ignore) {
"${NewsUtils.getFormattedTime(commentData.time)} 保护期间修改评论 >"
} else {
"${NewsUtils.getFormattedTime(commentData.time)} 已修改 >"
}
}
else -> {
time.setTextColor(ContextCompat.getColor(mContext, com.gh.gamecenter.common.R.color.text_F56614))
time.text = if (commentData.ignore) {
"${NewsUtils.getFormattedTime(commentData.time)} 保护期间修改评论"
} else {
"${NewsUtils.getFormattedTime(commentData.time)} 已修改"
}
}
}
sdvUserBadge.setOnClickListener {
DialogUtils.showViewBadgeDialog(mContext, commentData.user.badge, object : ConfirmListener {
override fun onConfirm() {
MtaHelper.onEvent(
"进入徽章墙_用户记录",
"游戏详情-玩家评论",
"${commentData.user.name}${commentData.user.id}"
)
MtaHelper.onEvent("徽章中心", "进入徽章中心", "游戏详情-玩家评论")
DirectUtils.directToBadgeWall(
mContext,
commentData.user.id,
commentData.user.name,
commentData.user.icon
)
}
})
}
userIcon.setOnClickListener {
DirectUtils.directToHomeActivity(mContext, commentData.user.id, mEntrance, "游戏详情-玩家评论")
MtaHelper.onEvent("游戏详情_新", "玩家评论_点击用户头像", mViewModel.game?.name)
NewLogUtils.logGameDetailCommentClick(
mViewModel.game?.name ?: "",
mViewModel.game?.id ?: "",
"个人主页"
)
}
userName.setOnClickListener {
userIcon.performClick()
MtaHelper.onEvent("游戏详情_新", "玩家评论_点击用户名字", mViewModel.game?.name)
}
tvBadgeName.setOnClickListener { sdvUserBadge.performClick() }
commentItem.setOnClickListener {
if (isChildLongClick) {
isChildLongClick = false
return@setOnClickListener
}
val exposureSource = arrayListOf(
ExposureSource("游戏详情"),
ExposureSource("详情tab"),
ExposureSource("玩家评价"),
).toJson()
val intent = RatingReplyActivity.getIntent(
context = mContext,
gameId = mViewModel.game?.id ?: "",
commentId = commentData.id,
exposureSource = exposureSource,
entrance = mEntrance,
path = path
)
SyncDataBetweenPageHelper.startActivityForResult(mContext, intent, RATING_REPLY_REQUEST, position)
MtaHelper.onEvent("游戏详情_新", "玩家评论_点击评论", mViewModel.game?.name)
NewLogUtils.logGameDetailCommentClick(
mViewModel.game?.name ?: "",
mViewModel.game?.id ?: "",
"评论内容"
)
}
content.setExpandCallback {
MtaHelper.onEvent("游戏详情_新", "玩家评论_点击全文", mViewModel.game?.name)
}
content.setOnLongClickListener(View.OnLongClickListener {
isChildLongClick = true
commentData.content.replace(RatingEditActivity.LABEL_REPLACE_REGEX.toRegex(), "").copyTextAndToast()
return@OnLongClickListener true
})
more.setOnClickListener {
showMorePopWindow(it, commentData.user.id == UserManager.getInstance().userId) { text ->
when (text) {
"复制" -> {
commentData.content.replace(RatingEditActivity.LABEL_REPLACE_REGEX.toRegex(), "")
.copyTextAndToast()
MtaHelper.onEvent("游戏详情_新", "玩家评论_复制", mViewModel.game?.name)
}
"修改" -> {
MtaHelper.onEvent("游戏详情_新", "玩家评论_修改", mViewModel.game?.name)
val intent = RatingEditActivity.getPatchIntent(mContext, mViewModel.game!!, commentData)
SyncDataBetweenPageHelper.startActivityForResult(
mContext,
intent,
RATING_PATCH_REQUEST,
position
)
}
"投诉" -> {
MtaHelper.onEvent("游戏详情_新", "玩家评论_投诉", mViewModel.game?.name)
mContext.ifLogin(BaseActivity.mergeEntranceAndPath(mEntrance, path)) {
DialogUtils.showReportReasonDialog(
mContext,
Constants.REPORT_LIST.toList() as java.util.ArrayList<String>
) { reason, desc ->
SimpleRequestHelper.reportGameComment(
mViewModel.game?.id ?: "",
commentData.id,
if (reason != "其他原因") reason else desc
)
}
}
}
"删除" -> {
DialogHelper.showDeleteGameCommentDialog(
mContext,
R.string.delete_game_comment.toResString()
) {
SimpleRequestHelper.deleteGameComment(
mViewModel.game?.id ?: "",
commentData.id
) {
// 删除列表中的评论(如果当前列表有的话)
val index = comments.indexOfFirst { item ->
item.id == commentData.id
}
if (index != -1) {
comments.removeAt(index)
notifyItemRemoved(index)
}
}
}
}
}
}
}
time.setOnClickListener {
if (commentData.isEditContent == null && commentData.ignore) {
MtaHelper.onEvent("游戏详情_新", "玩家评论-评论说明", mViewModel.game?.name)
DialogUtils.showStopServerExplanationDialog(
mContext,
if (mViewModel.game?.commentDescription?.isNotEmpty() == true)
mViewModel.game?.commentDescription else mContext.getString(R.string.rating_protection),
mViewModel.game?.name
?: ""
)
} else if (commentData.isEditContent == true) {
MtaHelper.onEvent("游戏详情_新", "玩家评论-点击时间", mViewModel.game?.name)
val intent = CommentLogsActivity.getIntent(mContext, mViewModel.game!!.id, commentData.id)
mContext.startActivity(intent)
}
}
}
} else if (holder is MoreViewHolder) {
holder.itemView.setOnClickListener {
EventBus.getDefault().post(EBReuse(GameDetailFragment.SKIP_RATING))
MtaHelper.onEvent("游戏详情_新", "玩家评论_查看全部评论", gameName)
NewLogUtils.logGameDetailCommentClick(
mViewModel.game?.name ?: "",
mViewModel.game?.id ?: "",
"查看全部评论"
)
}
}
}
private fun showMorePopWindow(v: View, isMyRating: Boolean, clickListener: (String) -> Unit) {
val contentList = if (isMyRating) arrayListOf("复制", "修改", "删除")
else arrayListOf("复制", "投诉")
val inflater = LayoutInflater.from(v.context)
val layout = inflater.inflate(com.gh.gamecenter.common.R.layout.layout_popup_container, null)
val popupWindow = PopupWindow(
layout,
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
popupWindow.apply {
setBackgroundDrawable(ColorDrawable(0))
isTouchable = true
isFocusable = true
isOutsideTouchable = true
}
val container = layout.findViewById<LinearLayout>(R.id.container)
for (text in contentList) {
val item = inflater.inflate(R.layout.layout_popup_option_item, container, false)
container.addView(item)
val hitText = item.findViewById<TextView>(R.id.hint_text)
hitText.text = text
item.setOnClickListener {
clickListener.invoke(text)
popupWindow.dismiss()
}
}
popupWindow.showAutoOrientation(v)
}
class MoreViewHolder(var view: View) : RecyclerView.ViewHolder(view)
class GameDetailRatingCommentViewHolder(var binding: ItemGameDetailRatingCommentBinding) :
RecyclerView.ViewHolder(binding.root)
companion object {
const val RATING_REPLY_REQUEST = 233
const val RATING_PATCH_REQUEST = 234
}
}

View File

@ -1,322 +0,0 @@
package com.gh.gamecenter.gamedetail.desc
import android.app.Activity
import android.content.Intent
import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.therouter.TheRouter
import com.gh.common.util.DirectUtils
import com.gh.common.util.OnSyncCallBack
import com.gh.common.util.SyncDataBetweenPageHelper
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.LazyFragment
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.eventbus.EBReuse
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.common.utils.viewModelProviderFromParent
import com.gh.gamecenter.core.iinterface.IScrollable
import com.gh.gamecenter.core.provider.IFloatingWindowProvider
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.databinding.FragmentDescBinding
import com.gh.gamecenter.entity.RatingComment
import com.gh.gamecenter.eventbus.EBScroll
import com.gh.gamecenter.eventbus.EBTypeChange
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.WelcomeDialogEntity
import com.gh.gamecenter.gamedetail.GameDetailFragment
import com.gh.gamecenter.gamedetail.GameDetailFragment.Companion.SKIP_DESC
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.DetailEntity
import com.gh.gamecenter.gamedetail.entity.NewGameDetailEntity
import com.gh.gamecenter.gamedetail.rating.RatingFragment
import com.gh.gamecenter.video.detail.VideoDetailActivity
import com.halo.assistant.HaloApp
import io.reactivex.disposables.CompositeDisposable
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
// TODO 处理页面重建时会生成额外 Fragment 的问题
class DescFragment: LazyFragment(), IScrollable {
private var mAdapter: DescAdapter ? = null
private var mLayoutManager: LinearLayoutManager? = null
private var mGameEntity: GameEntity? = null
private var mNewDetailEntity: NewGameDetailEntity? = null
private lateinit var mViewModel: DescViewModel
private lateinit var mBinding: FragmentDescBinding
private var mCompositeDisposable = CompositeDisposable()
var openVideoStreaming = false // 是否自动打开视频流
private var mScrollToLibao = false
private var mScrollToServer = false
override fun getRealLayoutId() = R.layout.fragment_desc
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
val adapter = mAdapter ?: return
if (resultCode == Activity.RESULT_OK) {
if (DescCommentsAdapter.RATING_REPLY_REQUEST == requestCode || DescCommentsAdapter.RATING_PATCH_REQUEST == requestCode) {
var commentPosition = 0
SyncDataBetweenPageHelper.resultHandle(data, object : OnSyncCallBack<RatingComment> {
override fun onData(dataPosition: Int): RatingComment? {
val descItemList = adapter.descItemList
for (i in 0 until descItemList.size) {
val comments = descItemList[i].comment
if (comments != null) {
commentPosition = i
val ratingComment = comments[dataPosition]
if (DescCommentsAdapter.RATING_PATCH_REQUEST == requestCode) {
ratingComment.ignore = mGameEntity?.ignoreComment ?: false
}
return ratingComment
}
}
return null
}
override fun onNotify(dataPosition: Int) {
adapter.notifyItemChanged(commentPosition)
}
})
} else if (requestCode == 100) {
val position = adapter.descItemList.indexOfFirst { it.type == DetailEntity.Type.LIBAO.value }
adapter.notifyItemChanged(position)
}
} else if (requestCode == DescCommentsAdapter.RATING_REPLY_REQUEST && resultCode == RatingFragment.RATING_DELETE_RESULT) {
data?.getParcelableExtra<RatingComment>(RatingComment::class.java.simpleName)?.run {
val descItemList = adapter.descItemList
var commentPosition = 0
for (i in 0 until descItemList.size) {
val comments = descItemList[i].comment
if (comments != null) {
commentPosition = i
val deleteCommentPosition = comments.indexOfFirst { it.id == id }
if (deleteCommentPosition != -1) comments.removeAt(deleteCommentPosition)
break
}
}
adapter.notifyItemChanged(commentPosition)
}
}
}
override fun onFragmentFirstVisible() {
mGameEntity = arguments?.getParcelable(EntranceConsts.KEY_GAME_ENTITY)
openVideoStreaming = arguments?.getBoolean(EntranceConsts.KEY_OPEN_VIDEO_STREAMING, false) ?: false
mScrollToLibao = arguments?.getBoolean(EntranceConsts.KEY_SCROLL_TO_LIBAO, false) ?: false
mScrollToServer = arguments?.getBoolean(EntranceConsts.KEY_SCROLL_TO_SERVER, false) ?: false
val gameDetailFactory =
GameDetailViewModel.Factory(HaloApp.getInstance().application, mGameEntity?.id, mGameEntity)
val gameDetailViewModel: GameDetailViewModel = viewModelProviderFromParent(gameDetailFactory, mGameEntity?.id ?: "")
mNewDetailEntity = gameDetailViewModel.gameDetailLiveData.value?.data
val factory = DescViewModel.Factory(HaloApp.getInstance().application, mGameEntity)
mViewModel = viewModelProvider(factory)
super.onFragmentFirstVisible()
gameDetailViewModel.gameDetailLiveData.observeNonNull(this) { gameDetail ->
if (gameDetail.data == null) return@observeNonNull
mAdapter?.updateDescItemList(mViewModel.decorateList(gameDetail.data!!.detailEntity))
// 非镜像游戏获取大家都在玩 (数据来源看具体方法内容) 数据
if (mGameEntity?.shouldUseMirrorInfo() == false) {
mViewModel.generateRecommendedGamesItem(gameDetail.data!!.detailEntity)
}
if (openVideoStreaming) {
gameDetail.data!!.detailEntity.forEach { entity ->
if (entity.video != null && activity !is VideoDetailActivity) {
DirectUtils.directToVideoDetail(
requireContext(), entity.video?.firstOrNull()?.videoId ?: "", entity.video?.firstOrNull()?.videoId, path = "游戏详情-介绍视频"
)
return@forEach
}
}
}
}
gameDetailViewModel.unifiedGameDetailWithUserRelatedInfoForChildLiveData.observeNonNull(this) {
mAdapter?.updateDescItemList(mViewModel.decorateList(it.detailEntity))
}
mViewModel.list.observe(this) {
mAdapter?.updateDescItemList(it)
if (mScrollToLibao && mViewModel.getLibaoIndexPosition() != -1) {
mScrollToLibao = false
mLayoutManager?.scrollToPositionWithOffset(mViewModel.getLibaoIndexPosition(), 0)
}
if (mScrollToServer && mViewModel.getServerIndexPosition() != -1) {
mScrollToServer = false
mLayoutManager?.scrollToPositionWithOffset(mViewModel.getServerIndexPosition(), 0)
}
}
mViewModel.changeColumnGameLiveData.observe(this) {
val viewHolder =
mBinding.recyclerview.findViewHolderForAdapterPosition(mViewModel.getColumnRecommendPosition()) as? DescAdapter.ColumnRecommendViewHolder
viewHolder?.binding?.run {
headPb.visibility = View.GONE
moreTv.isEnabled = true
}
if (it) {
mAdapter?.notifyItemChanged(mViewModel.getColumnRecommendPosition())
}
}
}
override fun onRealLayoutInflated(inflatedView: View) {
super.onRealLayoutInflated(inflatedView)
mBinding = FragmentDescBinding.bind(inflatedView)
showFloatingWindowIfNeeded()
mBinding.reuseLoading.root.visibility = View.GONE
mAdapter = DescAdapter(requireContext(), mEntrance, mViewModel, mNewDetailEntity)
(mBinding.recyclerview.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
mLayoutManager = LinearLayoutManager(context)
mBinding.recyclerview.layoutManager = mLayoutManager
mBinding.recyclerview.adapter = mAdapter
mBinding.recyclerview.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val firstCompletelyVisibleItemPosition = mLayoutManager!!.findFirstCompletelyVisibleItemPosition()
val lastCompletelyVisibleItemPosition = mLayoutManager!!.findLastCompletelyVisibleItemPosition()
for (i in firstCompletelyVisibleItemPosition..lastCompletelyVisibleItemPosition) {
if (i < 0) continue
if (mAdapter?.getItemViewType(i) == DescAdapter.CUSTOM_COLUMN
&& mAdapter!!.descItemList[i].customColumn?.showExpandTagsHint == true
) {
SPUtils.setBoolean(Constants.SP_HAS_SHOWN_EXPANDED_GAME_DETAIL_TAGS_HINT, true)
}
}
}
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
EventBus.getDefault().post(EBTypeChange(GameDetailFragment.EB_SCROLLING, 0))
}
exposureScroll(newState)
}
})
exposureScroll(RecyclerView.SCROLL_STATE_IDLE)
}
private fun exposureScroll(newState: Int) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
val layoutManager = mBinding.recyclerview.layoutManager as? LinearLayoutManager
val firstVisibleItem = layoutManager?.findFirstCompletelyVisibleItemPosition() ?: -1
val lastVisibleItem = layoutManager?.findLastCompletelyVisibleItemPosition() ?: -1
val childCount = mBinding.recyclerview.adapter?.itemCount ?: 0
if (firstVisibleItem == -1 || lastVisibleItem == -1) return
for (i in 0 until childCount) {
val viewHolder = mBinding.recyclerview.findViewHolderForAdapterPosition(i)
if (viewHolder != null && viewHolder is ExposureViewHolder) {
if (i in firstVisibleItem..lastVisibleItem) {
val rect = Rect()
viewHolder.itemView.getLocalVisibleRect(rect)
if (rect.top == 0 && rect.bottom == viewHolder.itemView.height) {
viewHolder.startDelayLogRunnable()
}
} else {
viewHolder.removeLogRunnable()
}
}
}
}
}
override fun onFragmentPause() {
super.onFragmentPause()
val childCount = mBinding.recyclerview.adapter?.itemCount ?: 0
for (i in 0 until childCount) {
val viewHolder = mBinding.recyclerview.findViewHolderForAdapterPosition(i)
if (viewHolder is ExposureViewHolder) {
viewHolder.removeLogRunnable()
}
}
}
override fun onDestroy() {
super.onDestroy()
mCompositeDisposable.dispose()
mAdapter?.stopHandlerThread()
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(reuse: EBReuse) {
if (SKIP_DESC == reuse.type) {
mAdapter?.notifyDataSetChanged()
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(bean: EBScroll) {
if (mGameEntity?.id == bean.id) {
val position = mViewModel.getGameInfoPosition()
(mBinding.recyclerview.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(position, 0)
}
}
override fun scrollToTop() {
if (::mBinding.isInitialized) {
mBinding.recyclerview.scrollToPosition(0)
}
}
fun scrollToRelatedVersion() {
if (mViewModel.getRelatedVersionPosition() != -1) {
mLayoutManager?.scrollToPositionWithOffset(mViewModel.getRelatedVersionPosition(), 0)
}
}
fun scrollToLibao() {
if (mViewModel.getDetailLibaoPosition() != -1) {
mLayoutManager?.scrollToPositionWithOffset(mViewModel.getDetailLibaoPosition(), 0)
}
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
if (!::mBinding.isInitialized) return
mBinding.recyclerview.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_background.toColor(requireContext()))
mBinding.recyclerview.recycledViewPool.clear()
mAdapter?.let { it.notifyItemRangeChanged(0, it.itemCount) }
}
private fun showFloatingWindowIfNeeded() {
val floatingWindowProvider = TheRouter.get(IFloatingWindowProvider::class.java)
floatingWindowProvider?.getAndShowFloatingWindow(
mGameEntity?.id ?: "",
mGameEntity?.name ?: "",
"游戏详情",
this,
mBinding.recyclerview
)?.let {
mCompositeDisposable.add(it)
}
}
}

View File

@ -1,460 +0,0 @@
package com.gh.gamecenter.gamedetail.desc
import android.app.Application
import android.os.Build
import android.text.TextUtils
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.facebook.common.util.UriUtil
import com.gh.common.util.PackageUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.exposure.meta.MetaUtil
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.entity.SubjectEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.GameInfo
import com.gh.gamecenter.gamedetail.entity.CustomColumn
import com.gh.gamecenter.gamedetail.entity.DetailEntity
import com.gh.gamecenter.retrofit.RetrofitManager
import com.google.gson.JsonArray
import com.google.gson.reflect.TypeToken
import com.halo.assistant.HaloApp
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import okhttp3.ResponseBody
import org.json.JSONObject
import retrofit2.HttpException
class DescViewModel(
application: Application,
var game: GameEntity?
) : AndroidViewModel(application) {
private var mDataList = arrayListOf<DetailEntity>()
private var mRelatedGameList = arrayListOf<GameEntity>()
private var mGameInfoPosition = 0
private var mLibaoPosition = -1
private var mServerPosition = -1
private var mDetailLibaoPosition = -1
private var mRelatedVersionPosition = -1
private var mColumnRecommendPosition = -1
private var mGameInfo: GameInfo? = null
private val mIdMaps = hashMapOf<String, String>()
private var mSubjectPage = 1
var list = MutableLiveData<ArrayList<DetailEntity>>()
var changeColumnGameLiveData = MutableLiveData<Boolean>()
var gameId = game?.id
/**
* 构建大家都在玩的item
*/
fun generateRecommendedGamesItem(mDataList: ArrayList<DetailEntity>) {
this.mDataList = mDataList
mIdMaps.clear()
val detailEntity = mDataList.find { it.type == DetailEntity.Type.RECOMMENDED_GAMES.value } ?: return
val relatedGames = detailEntity.relatedGames
val installGames = detailEntity.installGames
val downloadGames = detailEntity.downloadGames
if (relatedGames.isNullOrEmpty()
&& installGames.isNullOrEmpty()
&& downloadGames.isNullOrEmpty()
) {
assembleListWithRecommendedGames()
return
}
val labelGames = arrayListOf<String>()
for (relatedGame in relatedGames ?: listOf()) {
relatedGame.game?.let {
labelGames.addAll(it)
}
}
if (labelGames.isNotEmpty()) {
val gameIds = labelGames.take(4)
gameIds.forEach {
mIdMaps[it] = "标签推荐"
}
}
val labelGameCount = mIdMaps.size
if (installGames != null) {
val gameIds = installGames.take(4)
gameIds.forEach {
mIdMaps[it] = "安装推荐"
}
}
if (downloadGames != null) {
val gameIds = downloadGames.take(4)
gameIds.forEach {
mIdMaps[it] = "下载推荐"
}
}
if (mIdMaps.size < 6 && labelGames.size > labelGameCount) {
for (i in labelGameCount until labelGames.size) {
mIdMaps[labelGames[i]] = "标签推荐"
if (mIdMaps.size >= 6) break
}
}
if (mIdMaps.size < 6) {
val entity = mDataList.find { it.type == DetailEntity.Type.RECOMMENDED_GAMES.value }
mDataList.remove(entity)
assembleListWithRecommendedGames()
return
}
getGamesDigestByIds {
mRelatedGameList.clear()
mRelatedGameList.addAll(it)
assembleListWithRecommendedGames()
}
}
private fun getGamesDigestByIds(callback: (List<GameEntity>) -> Unit) {
val ids = mIdMaps.map { it.key }.toList().joinToString("-")
val filterQuery = UrlFilterUtils.getFilterQuery("game_ids", ids)
RetrofitManager.getInstance().api.getGamesDigestByIds(filterQuery)
.compose(observableToMain())
.subscribe(object : Response<List<GameEntity>>() {
override fun onResponse(response: List<GameEntity>?) {
super.onResponse(response)
response?.forEach { it.recommendType = mIdMaps[it.id] ?: "" }
response?.let(callback) ?: callback.invoke(listOf())
}
override fun onFailure(e: HttpException?) {
super.onFailure(e)
callback.invoke(listOf())
}
})
}
private fun assembleListWithRecommendedGames() {
for ((index, data) in mDataList.withIndex()) {
if (data.info != null) {
mGameInfoPosition = index
mGameInfo = data.info!!
}
if (data.server != null) {
mServerPosition = index
}
if (data.libao != null) {
mLibaoPosition = index
}
if (data.columnGames != null) {
mColumnRecommendPosition = index
}
if (data.relatedGames != null) {
mRelatedGameList.shuffle()
val recommendedGames = SubjectEntity().apply {
this.data = mRelatedGameList
}
data.recommendedGames = recommendedGames
}
}
list.postValue(mDataList)
}
fun changeSubjectGame(subjectId: String, gameCount: Int) {
RetrofitManager.getInstance().api
.getSubjectGame(subjectId, mSubjectPage)
.compose(observableToMain())
.subscribe(object : Response<retrofit2.Response<JsonArray>>() {
override fun onResponse(response: retrofit2.Response<JsonArray>?) {
super.onResponse(response)
if (response == null) return
val total = response.headers().get("total")?.toInt() ?: 0
val isHasNextPage = (total - mSubjectPage * 20) > 0
val type = object : TypeToken<ArrayList<GameEntity>>() {}.type
val games = GsonUtils.gson.fromJson<ArrayList<GameEntity>>(response.body()?.toJson() ?: "", type)
val detailEntity =
mDataList.find { data -> data.type == DetailEntity.Type.COLUMN_RECOMMEND.value } ?: return
val randomArray = RandomUtils.getRandomArray(gameCount, games.size)
detailEntity.columnGames?.run {
clear()
for (i in randomArray) {
add(games[i])
}
}
changeColumnGameLiveData.postValue(true)
mSubjectPage = if (isHasNextPage) mSubjectPage + 1 else 1
}
override fun onFailure(e: HttpException?) {
super.onFailure(e)
changeColumnGameLiveData.postValue(false)
}
})
}
fun sendSuggestion() {
val params = hashMapOf<String, String>()
params["from"] = ""
params["ghversion"] = PackageUtils.getGhVersionName()
params["channel"] = HaloApp.getInstance().channel
params["type"] = Build.MODEL
params["sdk"] = Build.VERSION.SDK_INT.toString()
params["version"] = Build.VERSION.RELEASE
params["source"] = HaloApp.getInstance().application.getString(R.string.app_name)
params["jnfj"] = MetaUtil.getBase64EncodedIMEI()
params["manufacturer"] = Build.MANUFACTURER
params["rom"] = MetaUtil.getRom().name + " " + MetaUtil.getRom().versionName
params["suggestion_type"] = "游戏求更新"
params["game_id"] = game?.id ?: ""
params["message"] =
"求更新:${game?.name}(${game?.getApk()?.firstOrNull()?.packageName}, ${game?.getApk()?.firstOrNull()?.version})"
val requestBody = params.createRequestBody()
RetrofitManager.getInstance().api.postSuggestion(requestBody)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Response<ResponseBody?>() {
override fun onResponse(response: ResponseBody?) {
super.onResponse(response)
ToastUtils.showToast("感谢您的反馈信息,我们将尽快处理~")
}
override fun onFailure(e: HttpException?) {
super.onFailure(e)
e?.response()?.errorBody()?.let {
val content = JSONObject(it.string())
if (content.getInt("code") == 403208) {
ToastUtils.showToast("您已经提交过反馈信息,我们将尽快处理~")
}
}
}
})
}
// TODO 装饰操作放到子线程去做
// 装饰列表数据
fun decorateList(detailEntityList: ArrayList<DetailEntity>): ArrayList<DetailEntity> {
val specialPadding = DisplayUtils.dip2px(12F)
val defaultPadding = DisplayUtils.dip2px(15F)
var containsFirstTimeExpandCustomColumnTags = false
var hasShownCustomColumnTagsExpandHint =
SPUtils.getBoolean(Constants.SP_HAS_SHOWN_EXPANDED_GAME_DETAIL_TAGS_HINT)
// A 确定每个 item 的上下内边距
for ((index, rawItem) in detailEntityList.withIndex()) {
rawItem.paddingTop = defaultPadding
rawItem.paddingBottom = defaultPadding
if (index == 0) {
rawItem.paddingTop = specialPadding
}
}
// B 将游戏介绍转为简单的自定义栏目
for (rawItem in detailEntityList) {
if (rawItem.type == DetailEntity.Type.DES.value) {
rawItem.type = DetailEntity.Type.CUSTOM_COLUMN.value
rawItem.customColumn = CustomColumn(
name = "游戏简介",
order = 1,
nameIcon = UriUtil.getUriForResourceId(R.drawable.ic_game_desc).toString(),
des = rawItem.des,
isHtmlDes = false,
showDesRowNum = 3
)
break
}
}
// C 标记"公告"、"自定义栏目"、"详细信息" 的UI样式是否合并在一起处理置顶文章的内容
for ((index, rawItem) in detailEntityList.withIndex()) {
if (rawItem.type == DetailEntity.Type.NOTICE.value
|| rawItem.type == DetailEntity.Type.CUSTOM_COLUMN.value
|| rawItem.type == DetailEntity.Type.GAME_INFO.value
) {
if (index + 1 != detailEntityList.size) {
val nextRawItem = detailEntityList[index + 1]
if (nextRawItem.type == DetailEntity.Type.CUSTOM_COLUMN.value
|| nextRawItem.type == DetailEntity.Type.NOTICE.value
|| nextRawItem.type == DetailEntity.Type.GAME_INFO.value
) {
// 默认情况下 UI 与前后相连
rawItem.shouldBoundWithNextItem = true
nextRawItem.shouldBoundWithPreviousItem = true
// 特殊情况下,如果自定义栏目类型为按钮,且后续的项不是按钮就独立显示,后续的项也为按钮时合并显示 (仅按钮类型的自定义栏目合并)
if (rawItem.type == DetailEntity.Type.CUSTOM_COLUMN.value
&& rawItem.customColumn?.link?.style == "button"
) {
val nextItemIsButtonStyleCustomColumn =
(nextRawItem.type == DetailEntity.Type.CUSTOM_COLUMN.value && nextRawItem.customColumn?.link?.style == "button")
rawItem.paddingBottom = 0
rawItem.shouldBoundWithNextItem = nextItemIsButtonStyleCustomColumn
nextRawItem.shouldBoundWithPreviousItem = nextItemIsButtonStyleCustomColumn
rawItem.paddingTop = specialPadding
nextRawItem.paddingTop = specialPadding
}
// 若当前项不是自定义栏目按钮类型的项,并且下个项是自定义栏目的项,不合并相关 UI
if (nextRawItem.type == DetailEntity.Type.CUSTOM_COLUMN.value
&& nextRawItem.customColumn?.link?.style == "button"
&& (rawItem.type != DetailEntity.Type.CUSTOM_COLUMN.value
|| rawItem.customColumn?.link?.style != "button")
) {
nextRawItem.shouldBoundWithPreviousItem = false
nextRawItem.paddingBottom = 0
}
}
}
}
// 处理置顶文章
if (rawItem.type == "article") {
if (rawItem.articleTop != null && rawItem.articleTop?.size != 0) {
for ((i, article) in rawItem.articleTop!!.withIndex()) {
article.priority = Int.MAX_VALUE
rawItem.article?.add(i, article)
}
rawItem.articleTop?.clear()
}
}
}
// D 处理自定义栏目相关的东西
for (rawItem in detailEntityList) {
if (rawItem.type == DetailEntity.Type.CUSTOM_COLUMN.value) {
// 判断自定义栏目的标签是否需要进入就自动展开
if (rawItem.customColumn?.showInfoTagDesType == "first_expand"
&& !SPUtils.getBoolean(Constants.SP_HAS_EXPANDED_GAME_DETAIL_TAGS)
) {
containsFirstTimeExpandCustomColumnTags = true
rawItem.customColumn?.showInfoTagDesType = "expand"
}
// 正文内容为空,但是后台配置了标签展开的默认展开
if (rawItem.customColumn?.showInfoTag == true
&& TextUtils.isEmpty(rawItem.customColumn?.des)
) {
rawItem.customColumn?.showInfoTagDesType = "expand"
}
// 判断是否显示标签展开提示
if (rawItem.customColumn?.showInfoTagDes == true
&& rawItem.customColumn?.infoTag?.size != 0
&& rawItem.customColumn?.showInfoTagDesType != "expand"
&& !hasShownCustomColumnTagsExpandHint
) {
rawItem.customColumn?.showExpandTagsHint = true
// 内存置为 true 避免出现多个提示
hasShownCustomColumnTagsExpandHint = true
}
// 不存在缩起正文的自定义栏目默认全显示
if (TextUtils.isEmpty(rawItem.customColumn?.desBrief) && rawItem.customColumn?.name != "游戏简介") {
rawItem.customColumn?.showDesType = "all"
}
// 将自定义栏目正文内容里低版本 android 系统不支持的 HTML Tag 转为手动处理
if (!TextUtils.isEmpty(rawItem.customColumn?.desBrief)) {
rawItem.customColumn?.desFull = rawItem.customColumn?.desFull?.replaceUnsupportedHtmlTag()
rawItem.customColumn?.desBrief = rawItem.customColumn?.desBrief?.replaceUnsupportedHtmlTag()
}
rawItem.customColumn?.des = rawItem.customColumn?.desFull
?: rawItem.customColumn?.des
}
}
for ((index, entity) in detailEntityList.withIndex()) {
if (entity.libao != null) {
mDetailLibaoPosition = index
}
if (entity.relatedVersion != null) {
mRelatedVersionPosition = index
}
}
if (containsFirstTimeExpandCustomColumnTags) {
SPUtils.setBoolean(Constants.SP_HAS_EXPANDED_GAME_DETAIL_TAGS, true)
}
loadCustomColumnImageInAdvance(detailEntityList)
return detailEntityList
}
fun getServerIndexPosition() = mServerPosition
fun getLibaoIndexPosition() = mLibaoPosition
fun getGameInfoPosition() = mGameInfoPosition
fun getDetailLibaoPosition() = mDetailLibaoPosition
fun getRelatedVersionPosition() = mRelatedVersionPosition
fun getColumnRecommendPosition() = mColumnRecommendPosition
fun getGameInfo() = mGameInfo
/**
* 预加载自定义栏目的标签小图标
*/
private fun loadCustomColumnImageInAdvance(detailEntityList: ArrayList<DetailEntity>) {
for (item in detailEntityList) {
if (item.type == DetailEntity.Type.CUSTOM_COLUMN.value) {
item.customColumn?.infoTag?.let {
for (tag in it) {
tryWithDefaultCatch {
ImageUtils.picasso.load(tag.icon).fetch()
}
}
}
}
}
}
fun postRequestSpeed() {
RetrofitManager.getInstance().newApi.postRequestSpeed(com.gh.gamecenter.login.user.UserManager.getInstance().userId, gameId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Response<ResponseBody>() {
override fun onResponse(response: ResponseBody?) {
super.onResponse(response)
ToastUtils.showToast("感谢您的反馈信息,我们将尽快处理~")
}
override fun onFailure(e: HttpException?) {
super.onFailure(e)
e?.response()?.errorBody()?.let {
val content = JSONObject(it.string())
if (content.getInt("code") == 403208) {
ToastUtils.showToast("您已经提交过反馈信息,我们将尽快处理~")
}
}
}
})
}
class Factory(
private val mApplication: Application,
private val game: GameEntity?
) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return DescViewModel(mApplication, game) as T
}
}
}

View File

@ -1,30 +0,0 @@
package com.gh.gamecenter.gamedetail.desc
import android.os.Handler
import android.view.View
import androidx.recyclerview.widget.RecyclerView
open class ExposureViewHolder(view: View, val handler: Handler) : RecyclerView.ViewHolder(view) {
private var mLogRunnable: Runnable? = null
fun startDelayLogRunnable() {
if (mLogRunnable == null) {
mLogRunnable = Runnable {
exposureLog()
}
handler.postDelayed(mLogRunnable!!, 3000)
}
}
open fun exposureLog() {
}
fun removeLogRunnable() {
if (mLogRunnable != null) {
handler.removeCallbacks(mLogRunnable!!)
mLogRunnable = null
}
}
}

View File

@ -1,84 +0,0 @@
package com.gh.gamecenter.gamedetail.desc
import android.content.Context
import android.graphics.drawable.BitmapDrawable
import android.net.Uri
import android.text.Spanned
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.common.view.CustomLinkMovementMethod
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.tryWithDefaultCatch
import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.databinding.GamedetailItemCustomColumnItemBinding
import com.gh.gamecenter.entity.TagEntity
import com.lightgame.adapter.BaseRecyclerAdapter
import com.squareup.picasso.Picasso
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
class GameDetailCustomColumnAdapter(context: Context) : BaseRecyclerAdapter<RecyclerView.ViewHolder>(context) {
private var mTags: ArrayList<TagEntity> = arrayListOf()
private var mShowTagDes: Boolean = false
fun updateData(tags: ArrayList<TagEntity>, showTagDes: Boolean) {
this.mTags = tags
this.mShowTagDes = showTagDes
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return GameDetailCustomViewHolderViewHolder(parent.toBinding())
}
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
if (viewHolder is GameDetailCustomViewHolderViewHolder) {
val (_, name, icon, des, color) = mTags[position]
val marginBetweenIconAndName = " "
val content = if (mShowTagDes) "$marginBetweenIconAndName$name $des" else "$marginBetweenIconAndName$name"
val spannable = SpanBuilder(content)
.color(
marginBetweenIconAndName.length,
name?.length?.plus(marginBetweenIconAndName.length) ?: 1,
color ?: "#000000"
)
.build()
tryWithDefaultCatch {
Single.just(icon)
.map {
ImageUtils.picasso.load(Uri.parse(it)).priority(Picasso.Priority.HIGH).get()
}.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
val bitmapDrawable = BitmapDrawable(mContext.resources, it)
bitmapDrawable.setBounds(0, 0, 16F.dip2px(), 16F.dip2px())
spannable.setSpan(CenterImageSpan(bitmapDrawable), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
viewHolder.binding.contentTv.run {
movementMethod = CustomLinkMovementMethod.getInstance()
text = spannable
}
}, {
it.printStackTrace()
})
}
}
}
override fun getItemViewType(position: Int): Int {
return position
}
override fun getItemCount(): Int {
return mTags.size
}
class GameDetailCustomViewHolderViewHolder(val binding: GamedetailItemCustomColumnItemBinding) :
BaseRecyclerViewHolder<Any>(binding.root)
}

View File

@ -1,217 +0,0 @@
package com.gh.gamecenter.gamedetail.desc
import android.content.Context
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.DefaultUrlHandler
import com.gh.common.util.DirectUtils
import com.gh.common.util.NewFlatLogUtils
import com.gh.gamecenter.WebActivity
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.databinding.ItemGameInfoBinding
import com.gh.gamecenter.feature.entity.GameInfo
import com.gh.gamecenter.gamedetail.dialog.GamePermissionDialogFragment
import com.gh.gamecenter.gamedetail.entity.GameInfoItemData
class GameDetailInfoItemAdapter(
val context: Context,
val gameInfo: GameInfo,
private val mViewModel: DescViewModel,
val gameName: String
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var datas = ArrayList<GameInfoItemData>()
init {
if (gameInfo.manufacturer.isNotEmpty()) {
when (gameInfo.manufacturerType) {
"manufacturer" -> {
datas.add(GameInfoItemData(title = "厂商", info = gameInfo.manufacturer))
}
"publisher" -> {
if (gameInfo.developer.isNotEmpty()) {
datas.add(GameInfoItemData(title = "开发商", info = gameInfo.developer))
}
datas.add(GameInfoItemData(title = "发行商", info = gameInfo.manufacturer))
}
"developer" -> {
datas.add(GameInfoItemData(title = "开发商", info = gameInfo.manufacturer))
if (gameInfo.publisher.isNotEmpty()) {
datas.add(GameInfoItemData(title = "发行商", info = gameInfo.publisher))
}
}
else -> {
//do nothing
}
}
}
if (gameInfo.supplier.isNotEmpty()) {
datas.add(GameInfoItemData(title = "供应商", info = gameInfo.supplier))
}
if (gameInfo.creditCode.isNotEmpty()) {
datas.add(GameInfoItemData(title = "统一社会信用代码", info = gameInfo.creditCode))
}
if (gameInfo.contact != null) {
datas.add(
GameInfoItemData(
title = gameInfo.contact!!.hint,
info = gameInfo.contact!!.qq,
actionStr = if (gameInfo.contact!!.type == "qq") "咨询" else if (gameInfo.contact?.key.isNullOrEmpty()) "复制" else "加入",
key = gameInfo.contact!!.key
)
)
}
if (gameInfo.version.isNotEmpty()) {
datas.add(
GameInfoItemData(
title = "当前版本",
info = gameInfo.version,
actionStr = "",
action2Str = if (gameInfo.requestUpdateStatus == "on" && mViewModel.game?.getApk()
?.isNotEmpty() == true
) "求更新" else ""
)
)
}
if (gameInfo.requestSpeedStatus == "on") {
datas.add(
GameInfoItemData(
title = "游戏加速",
info = "",
actionStr = "求加速",
action2Str = ""
)
)
}
if (gameInfo.size.isNotEmpty()) {
datas.add(GameInfoItemData(title = "游戏大小", info = gameInfo.size))
}
if (gameInfo.updateTime != 0L) {
datas.add(GameInfoItemData(title = "更新时间", info = TimeUtils.getFormatTime(gameInfo.updateTime)))
}
if (gameInfo.recommendAge.isNotEmpty()) {
datas.add(GameInfoItemData(title = "适龄等级", info = gameInfo.recommendAge))
}
if (!gameInfo.internetApp.isNullOrEmpty()) {
datas.add(GameInfoItemData(title = "联网APP", info = if (gameInfo.internetApp == "yes") "" else ""))
}
if (gameInfo.icp != null) {
datas.add(GameInfoItemData(title = "ICP备案号", info = gameInfo.icp?.number ?: ""))
}
if (!gameInfo.permissions.isNullOrEmpty()) {
datas.add(GameInfoItemData(title = "权限及用途", info = "查看"))
}
if (!gameInfo.privacyPolicyUrl.isNullOrEmpty()) {
datas.add(GameInfoItemData(title = "隐私政策", info = "查看"))
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return GameDetailInfoItemViewHolder(parent.toBinding())
}
override fun getItemCount(): Int = datas.size
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val gameInfoItemData = datas[position]
if (holder is GameDetailInfoItemViewHolder) {
holder.binding.divider.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_divider.toColor(holder.binding.divider.context))
holder.binding.infoTv.isSelected = true
holder.binding.infoTv.text = gameInfoItemData.info
holder.binding.titleTv.text = gameInfoItemData.title
holder.binding.infoTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(holder.binding.infoTv.context))
holder.binding.titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(holder.binding.infoTv.context))
holder.binding.actionTv.goneIf(gameInfoItemData.actionStr.isEmpty()) {
holder.binding.actionTv.text = gameInfoItemData.actionStr
holder.binding.actionTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(holder.binding.infoTv.context))
}
holder.binding.action2Tv.goneIf(gameInfoItemData.action2Str.isEmpty()) {
holder.binding.action2Tv.text = gameInfoItemData.action2Str
holder.binding.action2Tv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(holder.binding.infoTv.context))
}
holder.binding.infoTv.layoutParams = (holder.binding.infoTv.layoutParams as MarginLayoutParams).apply {
rightMargin = if (!holder.binding.actionTv.isVisible && !holder.binding.action2Tv.isVisible) 0 else 8F.dip2px()
}
if (gameInfoItemData.title == "权限及用途"
|| gameInfoItemData.title == "隐私政策"
|| gameInfoItemData.title == "ICP备案号") {
holder.binding.infoTv.setTextColor(ContextCompat.getColor(context, com.gh.gamecenter.common.R.color.text_theme))
}
holder.binding.root.setOnClickListener {
when (gameInfoItemData.title) {
"ICP备案号" -> {
DirectUtils.directToExternalBrowser(context, context.getString(com.gh.gamecenter.common.R.string.icp_url))
}
"权限及用途" -> {
GamePermissionDialogFragment.show(context as AppCompatActivity, mViewModel.game, gameInfo)
}
"隐私政策" -> {
gameInfo.privacyPolicyUrl?.let {
if (!DefaultUrlHandler.transformNormalScheme(holder.binding.root.context, it, "隐私政策", "")) {
val intent =
WebActivity.getIntent(holder.binding.root.context, it, "隐私政策", false, false)
holder.binding.root.context.startActivity(intent)
}
}
}
}
}
listOf(holder.binding.actionTv, holder.binding.action2Tv).forEach {
it.setOnClickListener { _ ->
when (if (it == holder.binding.actionTv) gameInfoItemData.actionStr else gameInfoItemData.action2Str) {
"求加速" -> {
it.context.ifLogin("游戏详情-求加速") {
NewFlatLogUtils.logGameDetailClickForAccelerate(mViewModel.gameId ?: "", gameName)
DialogHelper.showDialog(
context, "版本求加速", "如果游戏需要加速版本,您可以提交申请,让小助手尽快研究给您喔!",
"提交申请", "取消", {
mViewModel.postRequestSpeed()
}, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)
)
}
}
"求更新" -> {
MtaHelper.onEvent("游戏详情_新", "详细信息_我要求更新", gameName)
DialogHelper.showDialog(
context, "版本求更新", "如果游戏上线了新版本,您可以提交申请,让小助手尽快更新版本喔!",
"提交申请", "取消", {
mViewModel.sendSuggestion()
}, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)
)
}
"咨询" -> {
MtaHelper.onEvent("游戏详情_新", "详细信息_咨询", gameName)
if (ShareUtils.isQQClientAvailable(context)) {
DirectUtils.directToQqConversation(context, gameInfoItemData.info)
} else {
gameInfoItemData.info.copyTextAndToast("已复制")
}
}
"加入" -> {
MtaHelper.onEvent("游戏详情_新", "详细信息_加入", gameName)
if (ShareUtils.isQQClientAvailable(context)) {
DirectUtils.directToQqGroup(context, gameInfoItemData.key)
} else {
gameInfoItemData.info.copyTextAndToast("已复制")
}
}
"复制" -> {
gameInfoItemData.info.copyTextAndToast()
}
}
}
}
}
}
class GameDetailInfoItemViewHolder(var binding: ItemGameInfoBinding) : RecyclerView.ViewHolder(binding.root)
}

View File

@ -1,141 +0,0 @@
package com.gh.gamecenter.gamedetail.desc
import android.content.Context
import android.util.SparseArray
import android.view.View
import android.view.ViewGroup
import androidx.core.util.forEach
import androidx.recyclerview.widget.RecyclerView
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.common.util.DataCollectionUtils
import com.gh.common.util.DirectUtils
import com.gh.common.util.NewFlatLogUtils
import com.gh.gamecenter.ImageViewerActivity
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.WrapContentDraweeView
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.databinding.GalleryVideoItemBinding
import com.gh.gamecenter.databinding.GamedetailScreenshotItemBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.gamedetail.entity.Video
import com.gh.gamecenter.video.detail.VideoDetailContainerViewModel
class GameGalleryAdapter(
var context: Context,
private val mVideo: ArrayList<Video>? = null,
private val mGallery: ArrayList<String>? = null,
val mGame: GameEntity,
private val mEntrance: String
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val mImageViewArray = SparseArray<SimpleDraweeView>()
private val mDefaultHorizontalPadding by lazy { com.gh.gamecenter.common.R.dimen.game_detail_item_horizontal_padding.toPx() }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
VIDEO -> {
VideoViewHolder(parent.toBinding())
}
IMAGE -> {
GameGalleryViewHolder(parent.toBinding())
}
else -> throw NullPointerException()
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is GameGalleryViewHolder -> {
holder.binding.screenshotItemIv.setTag(ImageUtils.TAG_TARGET_WIDTH, 260F.dip2px())
ImageUtils.display(holder.binding.screenshotItemIv, mGallery?.get(position))
holder.binding.screenshotItemIv.registerLoadingCallback(object : WrapContentDraweeView.LoadingCallback {
override fun loaded() {
holder.binding.screenshotItemIv.post {
holder.binding.screenshotItemIv.layoutParams.apply {
height = holder.binding.screenshotItemIv.height
width =
(holder.binding.screenshotItemIv.height * holder.binding.screenshotItemIv.aspectRatio).toInt()
holder.binding.screenshotItemIv.layoutParams = this
}
holder.itemView.requestLayout()
}
}
})
holder.itemView.setDebouncedClickListener {
DataCollectionUtils.uploadClick(context, "游戏介绍", "游戏详情")
MtaHelper.onEvent("游戏详情_新", "点击游戏截图", mGame.name)
val imageViewList = ArrayList<View>()
mImageViewArray.forEach { _, value ->
imageViewList.add(value)
}
val intent = ImageViewerActivity.getIntent(
context,
mGallery ?: arrayListOf(),
holder.adapterPosition,
imageViewList,
mEntrance
)
context.startActivity(intent)
}
mImageViewArray.put(position, holder.binding.screenshotItemIv)
}
is VideoViewHolder -> {
val video = mVideo?.get(position)
ImageUtils.display(holder.binding.screenshotItemIv, mVideo?.get(position)!!.poster)
holder.binding.videoTitleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(holder.binding.videoTitleTv.context))
holder.binding.usernameTv.text = video?.user?.name
holder.binding.videoTitleTv.text = video?.title
holder.binding.voteCountTv.text = video?.vote.toString()
holder.binding.screenshotItemIv.setOnClickListener {
MtaHelper.onEvent("游戏详情_新", "点击视频", "${mGame.name}+${video?.title}")
NewFlatLogUtils.logClickGameDetailVideoCategory(
"video",
video?.videoId ?: "",
video?.user?.id ?: ""
)
DirectUtils.directToVideoDetail(
context, video?.videoId
?: "", VideoDetailContainerViewModel.Location.GAME_DETAIL.value,
false, mGame.id, mEntrance, "游戏详情"
)
}
}
}
val params = holder.itemView.layoutParams as RecyclerView.LayoutParams
params.leftMargin = if (position == 0) mDefaultHorizontalPadding else 8F.dip2px()
params.rightMargin = if (position == itemCount - 1) mDefaultHorizontalPadding else 0F.dip2px()
holder.itemView.layoutParams = params
}
override fun getItemCount(): Int {
return when {
mVideo != null -> mVideo.size
mGallery != null -> mGallery.size
else -> 0
}
}
override fun getItemViewType(position: Int): Int {
return when {
mVideo != null -> VIDEO
mGallery != null -> IMAGE
else -> 0
}
}
companion object {
const val IMAGE = 223
const val VIDEO = 224
}
class GameGalleryViewHolder(val binding: GamedetailScreenshotItemBinding) :
BaseRecyclerViewHolder<Any>(binding.root)
class VideoViewHolder(var binding: GalleryVideoItemBinding) : RecyclerView.ViewHolder(binding.root)
}

View File

@ -1,244 +0,0 @@
package com.gh.gamecenter.gamedetail.desc
import android.app.Activity
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.LibaoUtils
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.common.utils.copyTextAndToast
import com.gh.gamecenter.common.utils.fromHtml
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.SpanBuilder
import com.gh.gamecenter.databinding.ItemGameDetailMoreBinding
import com.gh.gamecenter.databinding.ItemGameLibaoBinding
import com.gh.gamecenter.feature.entity.LibaoEntity
import com.gh.gamecenter.libao.LibaoDetailActivity
import com.gh.gamecenter.login.user.UserManager
class GameLibaoAdapter(
val context: Context,
val libaos: ArrayList<LibaoEntity>,
val gameName: String,
val gameId: String,
val showExpandIcon: Boolean = true,
val standaloneStyle: Boolean = false,
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var mIsExpand = false
private val mShowItemCount: Int = if (showExpandIcon) 3 else Int.MAX_VALUE // 最多展示多少个礼包
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == LIBAO_ITEM) {
LibaoViewHolder(parent.toBinding())
} else {
MoreViewHolder(ItemGameDetailMoreBinding.inflate(LayoutInflater.from(context), parent, false))
}
}
override fun getItemViewType(position: Int): Int {
return if (libaos.size > mShowItemCount) {
if (!mIsExpand) {
if (position == mShowItemCount) MORE else LIBAO_ITEM
} else {
if (position == libaos.size) MORE else LIBAO_ITEM
}
} else {
LIBAO_ITEM
}
}
override fun getItemCount(): Int {
return if (libaos.size > mShowItemCount) {
if (mIsExpand) libaos.size + 1 else mShowItemCount + 1
} else libaos.size
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is LibaoViewHolder -> {
if (standaloneStyle) {
if (position == 0) {
holder.binding.root.setBackgroundResource(com.gh.gamecenter.common.R.drawable.background_shape_white_radius_5_top_only)
} else if (position == itemCount - 1) {
holder.binding.root.setBackgroundResource(com.gh.gamecenter.common.R.drawable.background_shape_white_radius_5_bottm_only)
} else {
holder.binding.root.setBackgroundResource(com.gh.gamecenter.common.R.color.ui_surface)
}
}
val libaoEntity = libaos[position]
holder.binding.libaoNameTv.text = libaoEntity.name
holder.binding.contentTv.text = libaoEntity.content?.fromHtml()
val isTypeCopy = libaoEntity.receiveMethod == "copy"
if (isTypeCopy) {
// 类型为复制,不需要登录也能直接领取
holder.binding.libaoSchedulePb.visibility = View.GONE
holder.binding.remainingTv.visibility = View.GONE
holder.binding.libaoCodeTv.visibility = View.VISIBLE
val text = "兑换码:${libaoEntity.code}"
holder.binding.libaoCodeTv.text = SpanBuilder(text).color(
holder.binding.root.context,
4,
text.length,
com.gh.gamecenter.common.R.color.text_theme
).build()
holder.binding.copyLibaoCodeIv.visibility = View.VISIBLE
holder.binding.copyLibaoCodeIv.setOnClickListener {
holder.binding.receiveTv.performClick()
}
} else if (libaoEntity.universal || libaoEntity.status == "check") {
//通用码礼包/或者还未添加礼包码时,不显示进度条,显示礼包码
holder.binding.libaoSchedulePb.visibility = View.GONE
holder.binding.remainingTv.visibility = View.GONE
holder.binding.libaoCodeTv.visibility = View.VISIBLE
holder.binding.copyLibaoCodeIv.visibility = View.GONE
if (!UserManager.getInstance().isLoggedIn) {
holder.binding.libaoCodeTv.text = "礼包码:-"
} else {
when (libaoEntity.status) {
"linged", "repeatLing", "repeatLinged", "taoed", "repeatTao", "repeatTaoed" -> {
val size = libaoEntity.me?.userDataLibaoList?.size ?: 0
val code = libaoEntity.me?.userDataLibaoList?.get(size - 1)?.code ?: ""
val text = "礼包码:$code"
holder.binding.libaoCodeTv.text = SpanBuilder(text).color(
holder.binding.root.context,
4,
text.length,
com.gh.gamecenter.common.R.color.text_theme
).build()
holder.binding.copyLibaoCodeIv.visibility = View.VISIBLE
holder.binding.copyLibaoCodeIv.setOnClickListener {
code.copyTextAndToast("$code 复制成功")
}
}
else -> {
holder.binding.libaoCodeTv.text = "礼包码:-"
}
}
}
} else {
if (!UserManager.getInstance().isLoggedIn) {
holder.binding.libaoSchedulePb.visibility = View.VISIBLE
holder.binding.remainingTv.visibility = View.VISIBLE
holder.binding.libaoCodeTv.visibility = View.GONE
holder.binding.copyLibaoCodeIv.visibility = View.GONE
initProgressUI(libaoEntity, holder)
} else {
when (libaoEntity.status) {
"linged", "repeatLing", "repeatLinged", "taoed", "repeatTao", "repeatTaoed" -> {
holder.binding.libaoSchedulePb.visibility = View.GONE
holder.binding.remainingTv.visibility = View.GONE
holder.binding.libaoCodeTv.visibility = View.VISIBLE
val size = libaoEntity.me?.userDataLibaoList?.size ?: 0
val code = libaoEntity.me?.userDataLibaoList?.get(size - 1)?.code ?: ""
val text = "礼包码:$code"
holder.binding.libaoCodeTv.text = SpanBuilder(text).color(
holder.binding.root.context,
4,
text.length,
com.gh.gamecenter.common.R.color.text_theme
).build()
holder.binding.copyLibaoCodeIv.visibility = View.VISIBLE
holder.binding.copyLibaoCodeIv.setOnClickListener {
code.copyTextAndToast("$code 复制成功")
}
}
else -> {
holder.binding.libaoSchedulePb.visibility = View.VISIBLE
holder.binding.remainingTv.visibility = View.VISIBLE
holder.binding.libaoCodeTv.visibility = View.GONE
holder.binding.copyLibaoCodeIv.visibility = View.GONE
initProgressUI(libaoEntity, holder)
}
}
}
}
// LibaoUtils.setLiBaoBtnStatusRound(holder.binding.receiveTv, libaoEntity,true, context)
LibaoUtils.initLibaoBtn(
context,
holder.binding.receiveTv,
libaoEntity,
false,
null,
true,
"游戏详情",
"游戏详情"
) {
notifyItemChanged(position)
}
if (!libaoEntity.packageName.isNullOrEmpty()) {
holder.binding.receiveTv.setOnClickListener {
val intent = LibaoDetailActivity.getIntent(context, libaoEntity, true, "游戏详情")
if (it.context is Activity) {
(it.context as Activity).startActivityForResult(intent, 100)
}
}
}
holder.itemView.setOnClickListener {
if (isTypeCopy) {
// do nothing
} else {
val intent = LibaoDetailActivity.getIntent(context, libaoEntity, "游戏详情")
if (it.context is Activity) {
(it.context as Activity).startActivityForResult(intent, 100)
}
NewLogUtils.logGameDetailGiftClick(gameName, gameId, "礼包详情")
}
}
}
is MoreViewHolder -> {
holder.binding.arrowIv.rotation = if (mIsExpand) 180f else 0f
holder.itemView.setOnClickListener {
if (!mIsExpand) MtaHelper.onEvent("游戏详情_新", "游戏礼包_展开", gameName)
mIsExpand = !mIsExpand
notifyDataSetChanged()
NewLogUtils.logGameDetailGiftClick(gameName, gameId, "展开")
}
}
}
}
private fun initProgressUI(libaoEntity: LibaoEntity, holder: LibaoViewHolder) {
val total = libaoEntity.total
val available = libaoEntity.available
if (total != 0) {
val availablePercent = (available) / total.toFloat() * 100
val count = when {
availablePercent >= 1F -> {
availablePercent.toInt()
}
availablePercent == 0F -> {
0
}
else -> {
1
}
}
holder.binding.remainingTv.text = "剩余${count}%"
holder.binding.libaoSchedulePb.progress = count
}
}
class LibaoViewHolder(var binding: ItemGameLibaoBinding) : RecyclerView.ViewHolder(binding.root)
class MoreViewHolder(var binding: ItemGameDetailMoreBinding) : RecyclerView.ViewHolder(binding.root)
companion object {
const val MORE = 0
const val LIBAO_ITEM = 1
}
}

View File

@ -1,117 +0,0 @@
package com.gh.gamecenter.gamedetail.desc
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.DefaultUrlHandler
import com.gh.common.util.DataCollectionUtils
import com.gh.common.util.DirectUtils
import com.gh.common.util.LogUtils
import com.gh.common.util.NewsUtils
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.databinding.ItemGameRaidersBinding
import com.gh.gamecenter.databinding.ItemGameRaidersFixedTopBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.NewsEntity
import com.gh.gamecenter.newsdetail.NewsDetailActivity
class GameRaidersAdapter(
val context: Context,
val articles: ArrayList<NewsEntity>,
val mEntrance: String,
val game: GameEntity?
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val mDefaultHorizontalPadding by lazy { com.gh.gamecenter.common.R.dimen.game_detail_item_horizontal_padding.toPx() }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == TYPE_ARTICLE) {
RaidersViewHolder(parent.toBinding())
} else {
RaidersFixedTopViewHolder(
ItemGameRaidersFixedTopBinding.inflate(
LayoutInflater.from(context),
parent,
false
)
)
}
}
override fun getItemCount(): Int = articles.size
override fun getItemViewType(position: Int): Int {
return if (articles[position].priority == Int.MAX_VALUE) {
TYPE_ARTICLE_FIXED_TOP
} else {
TYPE_ARTICLE
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val params = holder.itemView.layoutParams as RecyclerView.LayoutParams
params.leftMargin = if (position == 0) mDefaultHorizontalPadding else 8F.dip2px()
params.rightMargin = if (position == itemCount - 1) mDefaultHorizontalPadding else 0F.dip2px()
holder.itemView.layoutParams = params
if (holder is RaidersViewHolder) {
val newsEntity = articles[position]
holder.binding.root.setRootBackgroundDrawable(com.gh.gamecenter.common.R.drawable.background_shape_white_radius_5)
holder.binding.titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
holder.binding.contentTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
holder.binding.timeTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
holder.binding.line.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_divider.toColor(context))
holder.binding.titleTv.text = newsEntity.title ?: ""
holder.binding.contentTv.text = newsEntity.intro ?: ""
holder.binding.timeTv.text =
if (TimeUtils.isToday(newsEntity.publishOn)) "今天" else TimeUtils.getFormatTime(newsEntity.publishOn)
holder.itemView.setOnClickListener {
skipNewsDetail(newsEntity, position)
}
} else if (holder is RaidersFixedTopViewHolder) {
val newsEntity = articles[position]
holder.binding.root.setRootBackgroundDrawable(com.gh.gamecenter.common.R.drawable.background_shape_white_radius_5)
holder.binding.titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
holder.binding.descTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
holder.binding.titleTv.text = newsEntity.title ?: ""
ImageUtils.display(holder.binding.backgroundIv, newsEntity.thumb)
holder.binding.descTv.text = newsEntity.type
holder.itemView.setOnClickListener {
LogUtils.logGameDetailFixedTopArticleClick(game?.id, game?.name, newsEntity.link)
val isUrlIntercepted = DefaultUrlHandler.interceptUrl(
context,
newsEntity.link ?: "",
"新手攻略"
)
if (!isUrlIntercepted) {
DirectUtils.directToWebView(context, newsEntity.link ?: "")
}
}
}
}
private fun skipNewsDetail(article: NewsEntity, position: Int) {
DataCollectionUtils.uploadClick(context, "新手攻略", "游戏详情", article.title)
MtaHelper.onEvent("游戏详情_新", "新手攻略卡片", "${game?.name}+${article.title}")
// 统计阅读量
NewsUtils.statNewsViews(article.id)
NewsDetailActivity.startNewsDetailActivity(
context, article,
mEntrance + "+(游戏详情[" + game?.name + "]:新手攻略-列表[" + (position + 1) + "])"
)
}
companion object {
const val TYPE_ARTICLE = 123
const val TYPE_ARTICLE_FIXED_TOP = 124
}
class RaidersViewHolder(var binding: ItemGameRaidersBinding) : RecyclerView.ViewHolder(binding.root)
class RaidersFixedTopViewHolder(var binding: ItemGameRaidersFixedTopBinding) : RecyclerView.ViewHolder(binding.root)
}

View File

@ -0,0 +1,36 @@
package com.gh.gamecenter.gamedetail.detail
import android.content.Context
import android.util.AttributeSet
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.R
import com.gh.gamecenter.common.view.FixAppBarLayoutBehavior
import com.google.android.material.appbar.AppBarLayout
import kotlin.math.abs
class GameDetailAppBarBehavior @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null
) : FixAppBarLayoutBehavior(context, attrs) {
private var maxScrollHeight = 0
override fun setTopAndBottomOffset(offset: Int): Boolean {
val finalOffset = if (maxScrollHeight > 0 && maxScrollHeight <= abs(offset)) -maxScrollHeight else offset
return super.setTopAndBottomOffset(finalOffset)
}
override fun onLayoutChild(parent: CoordinatorLayout, abl: AppBarLayout, layoutDirection: Int): Boolean {
calculateMaxScrollHeight(parent, abl)
return super.onLayoutChild(parent, abl, layoutDirection)
}
private fun calculateMaxScrollHeight(parent: CoordinatorLayout, child: AppBarLayout) {
val recyclerView = parent.findViewById<RecyclerView>(R.id.detailRv) ?: return
val parentHeight = parent.measuredHeight
val recyclerViewHeight = recyclerView.measuredHeight
val childScrollTotalHeight = child.totalScrollRange
maxScrollHeight = (childScrollTotalHeight - parentHeight + recyclerViewHeight).coerceAtLeast(0)
}
}

View File

@ -0,0 +1,21 @@
package com.gh.gamecenter.gamedetail.detail
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.utils.LeftPagerSnapHelper
class GameDetailCoverSnapHelper: LeftPagerSnapHelper() {
override fun findTargetSnapPosition(
layoutManager: RecyclerView.LayoutManager,
velocityX: Int,
velocityY: Int
): Int {
val closestChild = findSnapView(layoutManager) ?: return RecyclerView.NO_POSITION
val position = layoutManager.getPosition(closestChild)
return if (position == RecyclerView.NO_POSITION) {
RecyclerView.NO_POSITION
} else {
val forwardDirection = if (layoutManager.canScrollHorizontally()) velocityX > 0 else velocityY > 0
if (forwardDirection) position + 1 else position - 1
}
}
}

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