Compare commits

..

83 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
416 changed files with 21029 additions and 9645 deletions

View File

@ -72,8 +72,6 @@ android_build:
only:
- dev
- release
- feat/GHZSCY-6976
- feat/GHZSCY-6976-log
# 代码检查
sonarqube_analysis:
@ -105,8 +103,6 @@ sonarqube_analysis:
only:
- dev
- release
- feat/GHZSCY-6976
- feat/GHZSCY-6976-log
## 发送简易检测结果报告
send_sonar_report:
@ -125,8 +121,6 @@ send_sonar_report:
only:
- dev
- release
- feat/GHZSCY-6976
- feat/GHZSCY-6976-log
oss-upload&send-email:
tags:
@ -163,5 +157,3 @@ oss-upload&send-email:
only:
- dev
- release
- feat/GHZSCY-6976
- feat/GHZSCY-6976-log

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

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

@ -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,
@ -817,11 +797,7 @@
android:screenOrientation="portrait" />
<activity
android:name="com.halo.assistant.member.MemberActivity"
android:screenOrientation="portrait" />
<activity
android:name=".BatchRegisterActivity"
android:name="com.halo.assistant.accelerator.MyAssetsActivity"
android:screenOrientation="portrait" />
<!-- <activity-->

View File

@ -27,8 +27,8 @@ import com.gh.gamecenter.MainActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.exposure.meta.MetaUtil
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.AppExecutor
@ -178,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
}

View File

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

View File

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

View File

@ -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
@ -36,7 +35,6 @@ import com.gh.gamecenter.common.utils.SensorsBridge.EVENT_NAME
import com.gh.gamecenter.common.utils.SensorsBridge.KEY_IS_FIRST_TIME
import com.gh.gamecenter.common.view.dsbridge.CompletionHandler
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.provider.IAcceleratorProvider
import com.gh.gamecenter.core.provider.IPushProvider
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.runOnUiThread
@ -59,13 +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.member.MemberRepository
import com.halo.assistant.member.MemberRepository.Companion.PAYMENT_TYPE_ALIPAY
import com.halo.assistant.member.MemberRepository.Companion.PAYMENT_TYPE_WECHAT
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
@ -743,13 +742,7 @@ class DefaultJsApi(
@JavascriptInterface
fun getCurAcctGameId(): String {
val isSpeeding = TheRouter.get(IAcceleratorProvider::class.java)?.isCurAccSuccess() ?: false
val gameId = if (isSpeeding) {
MemberRepository.instance.acctGameRecord.gameId
} else {
""
}
return gameId
return AcceleratorDataHolder.instance.getAcceleratingGameId()
}
@JavascriptInterface

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

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

@ -3,21 +3,18 @@ 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.IAcceleratorProvider
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.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.login.user.UserRepository
import com.halo.assistant.member.MemberRepository
import com.therouter.TheRouter
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 = MemberRepository.instance
private val repository = AccelerationRepository.newInstance()
@SuppressLint("CheckResult")
override fun onPayComplete(nonceStr: String, order: Any?) {
@ -29,32 +26,28 @@ class WechatPayResultProviderImpl : IWechatPayResultProvider {
EventBus.getDefault().post(EBPayState.PaySuccess)
// 先刷新本地状态,支付成功,肯定是付费会员
TheRouter.get(IAcceleratorProvider::class.java)?.setVipEntity(
AcceleratorDataHolder.instance.setVipEntity(
VipEntity(
_vipStatus = true,
_isNewUser = false,
_isTryVip = false
)
)
val userId = UserManager.getInstance().userId
if (userId.isNotBlank()) {
UserRepository.getInstance().refreshVipStatus(userId, true)
}
SensorsBridge.trackMemberRechargeResult(
MemberRepository.PAYMENT_TYPE_WECHAT,
AccelerationRepository.PAYMENT_TYPE_WECHAT,
orderEntity?.setMenuName ?: "",
orderEntity?.paymentAmount ?: "",
MemberRepository.RECHARGE_RESULT_SUCCESS
AccelerationRepository.RECHARGE_RESULT_SUCCESS
)
}, {
// 支付失败
EventBus.getDefault().post(EBPayState.PayFail)
SensorsBridge.trackMemberRechargeResult(
MemberRepository.PAYMENT_TYPE_WECHAT,
AccelerationRepository.PAYMENT_TYPE_WECHAT,
orderEntity?.setMenuName ?: "",
orderEntity?.paymentAmount ?: "",
MemberRepository.RECHARGE_RESULT_FAILURE
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

@ -5,8 +5,6 @@ import android.text.TextUtils;
import android.view.View;
import android.widget.TextView;
import androidx.constraintlayout.widget.ConstraintLayout;
import com.gh.common.filter.RegionSetting;
import com.gh.common.filter.RegionSettingHelper;
import com.gh.common.repository.ReservationRepository;
@ -55,9 +53,6 @@ public class DetailDownloadUtils {
if (viewHolder.getMultiVersionDownloadTv() != null) {
viewHolder.getMultiVersionDownloadTv().setVisibility(View.GONE);
}
if (viewHolder.getSpeedContainer() != null) {
viewHolder.getSpeedContainer().setVisibility(View.GONE);
}
viewHolder.setSpeedViewsVisible(false);
@ -143,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));
@ -254,30 +255,22 @@ public class DetailDownloadUtils {
}
}
} else {
boolean isLaunchState = false;
// 非畅玩,显示为普通游戏
if (context.getString(com.gh.gamecenter.feature.R.string.pluggable).equals(rawBtnText)) {
downloadButton.setButtonStyle(DownloadButton.ButtonStyle.PLUGIN);
} else if (context.getString(com.gh.gamecenter.feature.R.string.launch).equals(rawBtnText)) {
downloadButton.setButtonStyle(DownloadButton.ButtonStyle.LAUNCH_OR_OPEN);
isLaunchState = true;
} else if (context.getString(com.gh.gamecenter.feature.R.string.install).equals(rawBtnText)) {
downloadButton.setButtonStyle(DownloadButton.ButtonStyle.INSTALL_NORMAL);
} else {
downloadButton.setButtonStyle(DownloadButton.ButtonStyle.NORMAL);
}
// 只有下载按钮状态为 “启动” 时才需要展示加速ui
if (isLaunchState) {
viewHolder.showAcceleratorGuideLayer();
} else {
viewHolder.hideSpeedUi();
}
if (showDualDownloadButton && viewHolder.getLocalDownloadSizeTv() != null) {
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);
}
@ -617,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

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

@ -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())
}
}

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

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

@ -1,81 +0,0 @@
package com.gh.gamecenter
import android.os.Bundle
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.core.provider.IAcceleratorProvider
import com.gh.gamecenter.feature.entity.BaseEntity
import com.gh.gamecenter.login.retrofit.RetrofitManager
import com.gh.gamecenter.login.user.UserManager
import com.therouter.TheRouter
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import retrofit2.HttpException
/**
* 奇游用户批量注册
*/
class BatchRegisterActivity : AppCompatActivity() {
private val userIds = listOf(
""
)
private var iAcceleratorProvider: IAcceleratorProvider? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_batch_register)
iAcceleratorProvider = TheRouter.get(IAcceleratorProvider::class.java)
val btnRegister = findViewById<Button>(R.id.btn_register)
btnRegister.setOnClickListener {
doRegister()
}
}
private fun doRegister() {
println("kayn -->doRegister:${userIds.size}")
Observable.fromIterable(userIds)
.flatMap({ userId ->
RetrofitManager.getInstance().newApi
.getQyToken(userId, "gjonline_vip")
.onErrorReturnItem(BaseEntity())
.flatMap { entity ->
val token = entity.data?.token ?: ""
Single.create<Pair<Boolean, String>> {
if (token.isBlank()) {
it.onSuccess(false to userId)
} else {
iAcceleratorProvider?.setQyUserToken(token) { isSuccess ->
it.onSuccess(isSuccess to userId)
}
}
}
}.toObservable()
})
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Response<Pair<Boolean, String>>() {
override fun onNext(response: Pair<Boolean, String>) {
super.onNext(response)
val (isSuccess, userId) = response
println("kayn -->isSuccess:$isSuccess --userId:$userId")
}
override fun onFailure(e: HttpException?) {
super.onFailure(e)
}
override fun onComplete() {
super.onComplete()
}
})
}
}

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);
@ -1084,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)
}

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,6 +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.TheRouter
import com.therouter.router.Route
import com.gh.common.dialog.NewPrivacyPolicyDialogFragment
import com.gh.common.util.DeviceTokenUtils
import com.gh.common.util.DialogUtils
@ -30,14 +32,13 @@ import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.iinterface.ISplashScreen
import com.gh.gamecenter.core.provider.IAppProvider
import com.gh.gamecenter.core.provider.IPackageUtilsProvider
import com.gh.gamecenter.core.provider.IPushProvider
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.feature.utils.PlatformUtils
import com.gh.gamecenter.pkg.PkgHelper
import com.halo.assistant.HaloApp
import com.therouter.TheRouter
import com.therouter.router.Route
import org.json.JSONObject
import splitties.systemservices.notificationManager
import java.text.SimpleDateFormat
@ -89,9 +90,8 @@ class SplashScreenActivity : BaseActivity(), ISplashScreen {
showPrivacyDialog()
} else {
val spanBuilder = buildSpannedString {
append(
"这个弹窗只会在右上角有环境标签的测试包出现" +
"\n进入应用以后还可以到关于我们页面长按应用图标重新选择"
append("这个弹窗只会在右上角有环境标签的测试包出现" +
"\n进入应用以后还可以到关于我们页面长按应用图标重新选择"
)
bold {
color(com.gh.gamecenter.common.R.color.text_theme.toColor(this@SplashScreenActivity)) {
@ -104,7 +104,7 @@ class SplashScreenActivity : BaseActivity(), ISplashScreen {
executeDex2OatInAdvance()
DialogHelper.showDialog(
context = this,
title = "选择环境",
title ="选择环境",
content = spanBuilder,
confirmText = "正式环境",
cancelText = "测试环境",
@ -131,7 +131,6 @@ class SplashScreenActivity : BaseActivity(), ISplashScreen {
} else {
launchMainActivity()
}
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R) {
SPUtils.setString(Constants.SP_XAPK_UNZIP_ACTIVITY, "")
SPUtils.setString(Constants.SP_XAPK_URL, "")

View File

@ -6,19 +6,17 @@ import android.os.Bundle
import android.text.TextUtils
import android.view.KeyEvent
import android.view.View
import com.gh.common.constant.Config
import com.therouter.router.Route
import com.gh.gamecenter.common.base.activity.ToolBarActivity
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.entity.ToolBoxEntity
import com.gh.gamecenter.common.utils.EnvHelper
import com.gh.gamecenter.common.utils.updateStatusBarColor
import com.gh.gamecenter.feature.entity.ConcernEntity
import com.gh.gamecenter.feature.entity.NewsEntity
import com.gh.gamecenter.common.entity.ToolBoxEntity
import com.halo.assistant.fragment.WebFragment
import com.halo.assistant.member.MemberActivity
import com.therouter.router.Route
@Route(path = RouteConsts.activity.webActivity)
open class WebActivity : ToolBarActivity() {
@ -31,8 +29,6 @@ open class WebActivity : ToolBarActivity() {
val mIsBackpressRequireConfirmation =
bundle.getBoolean(WebFragment.KEY_REQUIRE_BACK_CONFIRMATION, false)
mIsFullScreen = !TextUtils.isEmpty(mGameName) && mIsBackpressRequireConfirmation
mIsFullScreen = true
if (mIsFullScreen) {
setTheme(R.style.AppFullScreenTheme)
}
@ -309,17 +305,5 @@ open class WebActivity : ToolBarActivity() {
intent.putExtra(NORMAL_FRAGMENT_BUNDLE, bundle)
return intent
}
@JvmStatic
fun getMyAssetsIntent(context: Context): Intent {
val intent = Intent(context, MemberActivity::class.java)
val url = if (EnvHelper.isDevEnv) {
Constants.MY_ASSETS_DEV
} else {
Constants.MY_ASSETS
}
intent.putExtra(EntranceConsts.KEY_URL, url)
return intent
}
}
}

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

@ -35,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
@ -48,9 +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
@ -61,6 +61,7 @@ import java.io.File
// 虽然叫 ViewHolder但其实就是一个用来临时放 View 和相关操作的包裹类
class DetailViewHolder(
view: View,
val viewModel: GameDetailViewModel?,
val gameEntity: GameEntity,
val isNewsDetail: Boolean, // 新闻详情不显示下载的游戏名, 只显示下载状态
entrance: String?,
@ -68,7 +69,8 @@ class DetailViewHolder(
title: String?,
val traceEvent: ExposureEvent?,
val isSupportDualButton: Boolean = false, // 是否支持双下载按钮,不支持的时候跟普通列表意义选用优先级高的那个来显示,
val acceleratorUiHelper: GameDetailAcceleratorUiHelper? = null // 网速加速,只有游戏详情才有
val acceleratorUiHelper: GameDetailAcceleratorUiHelper? = null, // 网速加速,只有游戏详情才有
onDownloadClickAction: ((Boolean) -> Unit)? = null
) {
var context: Context
var downloadBottom: View
@ -131,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(
@ -141,7 +144,8 @@ class DetailViewHolder(
mTitle = title ?: "",
mAsVGame = true,
mShowDualDownloadButton = gameDownloadMode == GameEntity.GAME_DOWNLOAD_BUTTON_MODE_DUAL,
mTraceEvent = traceEvent
mTraceEvent = traceEvent,
onDownloadClickAction = onDownloadClickAction
)
// 不支持双下载按钮的情况时,优选一个下载方式显示
@ -194,12 +198,26 @@ class DetailViewHolder(
gamePermissionDialogFragment?.dismissAllowingStateLoss()
}
fun hideSpeedUi() {
acceleratorUiHelper?.showSpeedUi = false
}
fun showAcceleratorGuideLayer() {
acceleratorUiHelper?.checkIfShowGuideLayer(context)
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) {
@ -214,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
@ -312,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()
}
@ -397,7 +418,8 @@ class DetailViewHolder(
gameEntity = mGameEntity,
traceEvent = mTraceEvent,
entrance = StringUtils.buildString(mEntrance, "+(", mName, "[", mTitle, "])"),
location = "$mName:$mTitle"
location = "$mName:$mTitle",
onDownloadClickAction = onDownloadClickAction
)
}
}
@ -715,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 {
@ -732,7 +755,8 @@ class DetailViewHolder(
mTitle,
"])"
),
"$mName:$mTitle"
"$mName:$mTitle",
onDownloadClickAction
)
}
}
@ -743,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

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

@ -1,11 +1,11 @@
package com.gh.gamecenter.gamedetail
import androidx.lifecycle.ViewModel
import com.halo.assistant.member.MemberUseCase
import com.halo.assistant.accelerator.AccelerationUseCase
class AcceleratorZoneViewModel : ViewModel() {
val useCase = MemberUseCase()
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

@ -5,22 +5,21 @@ 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.core.provider.IAcceleratorProvider
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.member.MemberUseCase
import com.therouter.TheRouter
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 = MemberUseCase()
val useCase = AccelerationUseCase()
private val _restartingAcceleratorAction = MutableLiveData<Event<Boolean>>()
val restartingAcceleratorAction: LiveData<Event<Boolean>> = _restartingAcceleratorAction
@ -43,15 +42,15 @@ class StartingAcceleratorViewModel : ViewModel() {
override fun onSuccess(data: BaseEntity<TrialEntity>) {
if (data.data?.result == true) {
// 刷新vip状态
// 这里先刷新内存数据再去刷新api数据
TheRouter.get(IAcceleratorProvider::class.java)?.setVipEntity(
// 这里先刷新内存数据
AcceleratorDataHolder.instance.setVipEntity(
VipEntity(
_vipStatus = true,
_isNewUser = false,
_isTryVip = true
)
)
refreshVipStatus(userId)
_rechargeTrailResult.value = Event(true)
} else {
_rechargeTrailResult.value = Event(false)
@ -65,10 +64,6 @@ class StartingAcceleratorViewModel : ViewModel() {
}).let(compositeDisposable::add)
}
private fun refreshVipStatus(userId: String) {
UserRepository.getInstance().refreshVipStatus(userId, true)
}
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

@ -5,14 +5,16 @@ 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.AcctZoneListBean
import com.gh.gamecenter.feature.entity.AcctRecord
import com.gh.gamecenter.room.converter.AcctGameInfoConverter
import com.halo.assistant.HaloApp
@Database(
entities = [AcctGameInfo::class, AcctZoneListBean::class],
version = 1,
entities = [AcctGameInfo::class, AcctRecord::class],
version = 2,
exportSchema = false
)
@TypeConverters(
@ -20,7 +22,7 @@ import com.halo.assistant.HaloApp
)
abstract class AccelerationDataBase : RoomDatabase() {
abstract fun accelerationDao(): AcceleratorDao
abstract fun accelerationDao(): AccelerationDao
companion object {
private const val DATABASE_NAME: String = "acceleration_db"
@ -29,7 +31,63 @@ abstract class AccelerationDataBase : RoomDatabase() {
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

@ -1,25 +0,0 @@
package com.gh.gamecenter.gamedetail.accelerator
import androidx.lifecycle.LiveData
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.AcctZoneListBean
import io.reactivex.Single
@Dao
interface AcceleratorDao {
@Upsert
fun upsertAcctGameInfo(gameInfo: AcctGameInfo): Single<Long>
@Query("SELECT * FROM AcctGameInfo WHERE accGamePkgName = :pkgName")
fun findByPackageName(pkgName: String): LiveData<AcctGameInfo?>
@Upsert
fun upsertAcctZoneListBean(acctZoneListBean: AcctZoneListBean): Single<Long>
@Query("SELECT * FROM AcctZoneListBean WHERE pkgName = :pkgName")
fun findZoneListBeanByPkgName(pkgName: String): LiveData<AcctZoneListBean?>
}

View File

@ -12,16 +12,17 @@ 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.core.utils.ToastUtils
import com.gh.gamecenter.databinding.DetailDownloadItemBinding
import com.gh.gamecenter.feature.entity.AcctGameInfo
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.gamedetail.accelerator.chain.*
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.member.MemberRepository
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
import com.therouter.TheRouter
class GameDetailAcceleratorUiHelper(private val binding: DetailDownloadItemBinding) {
@ -30,54 +31,41 @@ class GameDetailAcceleratorUiHelper(private val binding: DetailDownloadItemBindi
private var guideView: AcceleratorGuideView? = null
val isGuideLayerShowing: Boolean
get() = guideView?.isShowing ?: false
private lateinit var game: GameEntity
private val zoneList = arrayListOf<AcctGameInfo>()
private var hasZoneListLoaded = false
private var isGuideLayerShowing = false
private var _last: AcctGameInfo? = null
val last: AcctGameInfo?
private val last: AcctGameInfo?
get() = _last
private val hasMultiZone: Boolean
get() = zoneList.size > 1
private val hasZone: Boolean
get() = zoneList.isNotEmpty()
get() = game.serviceArea.size > 1
private val isVip: Boolean
get() = iAcceleratorProvider?.isVip() ?: false
get() = AcceleratorDataHolder.instance.isVip
val isNewUser: Boolean
get() = iAcceleratorProvider?.isNewUser() ?: false
get() = AcceleratorDataHolder.instance.isNewUser
private var canSpeed = false
private val isInit: Boolean
get() = ::game.isInitialized
var showSpeedUi = false
val context: Context
get() = binding.root.context
private var _game: GameEntity? = null
fun setGame(game: GameEntity?) {
_game = game
}
private var hasAnyAcctRecord = false
private val accelerationListener = object : OnAccelerateListener {
override fun onStateChanged(state: AccelerateState) {
when (state) {
is AccelerateState.Success -> {
_game?.let {
val acctGameInfo = state.acctGameInfo
if (acctGameInfo is AcctGameInfo) {
setCurrentAcctGameInfo(acctGameInfo)
}
updateSpeedUi()
}
updateSpeedUi()
}
is AccelerateState.Normal -> {
if (!state.isTokenExpired) {
updateSpeedUi()
}
updateSpeedUi()
}
else -> Unit
@ -86,94 +74,78 @@ class GameDetailAcceleratorUiHelper(private val binding: DetailDownloadItemBindi
override fun onProgress(progress: Int, curGamePkgName: String?, curGameZoneFlag: String?) = Unit
override fun onVipStatusChanged(isNewUser: Boolean, isVip: Boolean) {
binding.ivFreeVipTag.visibleIf(
showSpeedUi &&
CheckLoginUtils.isLogin() &&
isNewUser &&
!isGuideLayerShowing
)
}
}
fun initSpeedUi(context: Context) {
canSpeed = true
_game?.let {
iAcceleratorProvider?.bindAccRelatedListener(it.getUniquePackageName() ?: "", accelerationListener)
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 {
_game?.let { game ->
checkDataReady {
startAccelerating(context, game, false)
}
}
startAccelerating(context, game, false)
}
binding.vMoreZone.setOnClickListener {
_game?.let { game ->
checkDataReady {
showZoneList(context, game)
}
}
showZoneList(context, game)
}
binding.tvStopSpeed.setOnClickListener {
_game?.let {
SensorsBridge.trackNetworkAccelerationOtherButtonClick(
it.getUniquePackageName() ?: "",
it.id,
it.name ?: "",
iAcceleratorProvider?.getMemberType() ?: "",
BUTTON_NAME_STOP_ACCELERATOR,
SOURCE_ENTRANCE_GAME_DETAIL
)
AcceleratorDialogFragment.show(
SPEED_STOP, it.getUniquePackageName() ?: "", it.id, it.name ?: "",
SOURCE_ENTRANCE_GAME_DETAIL, context
)
}
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 {
_game?.let { game ->
SensorsBridge.trackNetworkAccelerationOtherButtonClick(
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
iAcceleratorProvider?.getMemberType() ?: "",
BUTTON_NAME_ENTER_GAME,
SOURCE_ENTRANCE_GAME_DETAIL
)
PackageLauncher.launchApp(context, game, game.getUniquePackageName())
}
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 (!showSpeedUi) {
if (!isInit || !showSpeedUi) {
return
}
val hasGameBeingAccelerated = iAcceleratorProvider?.isCurAccSuccess() ?: false
val isCurAccSuccess = hasGameBeingAccelerated &&
MemberRepository.instance.acctGameRecord.gameId == (_game?.id ?: "")
binding.detailProgressbar.goneIf(isCurAccSuccess)
binding.clSpeed.goneIf(isCurAccSuccess)
binding.gAccelerating.goneIf(!isCurAccSuccess)
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)
binding.ivFreeVipTag.visibleIf(
showSpeedUi &&
CheckLoginUtils.isLogin() &&
isNewUser &&
!isGuideLayerShowing
)
showFreeTag()
binding.tvSpeed.text = if (hasMultiZone) {
last?.zoneName
} else {
@ -181,67 +153,81 @@ class GameDetailAcceleratorUiHelper(private val binding: DetailDownloadItemBindi
} ?: R.string.network_acceleration.toResString()
}
/**
* 在这个方法里确定是否需要展示 加速ui
* 当 clSpeedContainer没有显示时调用updateSpeedUi()无效,可以调用initSpeedUi()
* 请注意,只要是 gameDetail 发生变化,这里就会回调,所以当 canSpeed 确定之后,这里最少还会回调一次
*/
fun checkIfShowGuideLayer(context: Context) {
showSpeedUi = canSpeed
fun checkIfShowSpeedUi(show: Boolean) {
if (!isInit) {
return
}
showSpeedUi = show
binding.clSpeedContainer.goneIf(!showSpeedUi) {
// canSpeed =true 说明detail接口已完成
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()
if (CheckLoginUtils.isLogin()) {
if (!hasShow()) {
// 优先显示弹窗
binding.ivFreeVipTag.visibleIf(false)
showGuideLayer(context)
}
} else {
// 未登录
binding.ivFreeVipTag.visibleIf(false)
}
}
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) {
if (!hasShow()) {
binding.root.post {
val uiListener = object : OnAcceleratorListener {
override fun onStartAccelerator() {
_game?.let {
startAccelerating(context, it, true)
}
}
override fun onGuideLayerDismiss() {
binding.ivFreeVipTag.visibleIf(CheckLoginUtils.isLogin() && hasZone && isNewUser)
}
binding.root.post {
val uiListener = object : OnAcceleratorListener {
override fun onStartAccelerator() {
startAccelerating(context, game, true)
}
SPUtils.setBoolean(Constants.SP_HAS_SHOW_ACCELERATION_GUIDE_LAYER, true)
if (guideView == null) {
guideView = AcceleratorGuideView(context).apply {
setDismissListener(uiListener::onGuideLayerDismiss)
}
override fun onGuideLayerDismiss() {
isGuideLayerShowing = false
showFreeTag()
}
(guideView?.parent as? ViewGroup)?.removeView(guideView)
if (context is AppCompatActivity) {
_game?.let {
SensorsBridge.trackNetworkAccelerationGuidanceDiagramShow(
it.getUniquePackageName() ?: "",
it.id,
it.name ?: ""
)
}
guideView?.show(binding.clSpeedContainer, context, uiListener::onStartAccelerator)
}
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 {
@ -254,21 +240,13 @@ class GameDetailAcceleratorUiHelper(private val binding: DetailDownloadItemBindi
}
}
private fun hasShow(): Boolean {
return SPUtils.getBoolean(Constants.SP_HAS_SHOW_ACCELERATION_GUIDE_LAYER)
}
private fun startAccelerating(context: Context, game: GameEntity, isShowing: Boolean) {
if (isInvalidClick() || !hasZone) {
if (isInvalidClick()) {
return
}
val lastAcctGameInfo = last
val memberType = if (CheckLoginUtils.isLogin()) {
iAcceleratorProvider?.getMemberType() ?: ""
} else {
MEMBER_TYPE_NOT_LOGIN
}
val memberType = AcceleratorDataHolder.instance.memberType
val districtServer = when {
hasMultiZone && lastAcctGameInfo != null -> lastAcctGameInfo.zoneName
hasMultiZone -> DISTRICT_SERVER_EMPTY
@ -286,34 +264,44 @@ class GameDetailAcceleratorUiHelper(private val binding: DetailDownloadItemBindi
when {
lastAcctGameInfo != null ->
doStartAccelerating(context, game, lastAcctGameInfo)
doStartAccelerating(context, game, lastAcctGameInfo.zoneInfo)
hasMultiZone -> context.ifLogin("[网络加速]") {
AcceleratorZoneDialogFragment.show(
context, last?.zoneInfo?.id, game.serviceArea, game,
SOURCE_ENTRANCE_GAME_DETAIL
)
}
hasMultiZone -> AcceleratorZoneDialogFragment.show(context, zoneList, game)
else -> {
val gameInfo = zoneList.firstOrNull() ?: AcctGameInfo("", AcctGameInfo.ZoneInfo(0))
val gameInfo = game.serviceArea.firstOrNull() ?: AcctGameInfo.ZoneInfo(0)
doStartAccelerating(context, game, gameInfo)
}
}
}
private fun showZoneList(context: Context, game: GameEntity) {
if (isInvalidClick() || !hasZone) {
if (isInvalidClick()) {
return
}
AcceleratorZoneDialogFragment.show(context, zoneList, game)
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, acctGameInfo: AcctGameInfo) {
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, acctGameInfo, game, true, hasMultiZone,
context, zoneInfo, game, true, hasMultiZone,
SOURCE_ENTRANCE_GAME_DETAIL,
)
}
@ -321,27 +309,18 @@ class GameDetailAcceleratorUiHelper(private val binding: DetailDownloadItemBindi
}
fun setCurrentAcctGameInfo(acctGameInfo: AcctGameInfo?) {
_last = acctGameInfo
if (hasZoneListLoaded) {
isLastLoaded = true
if (last?.zoneInfo?.id != acctGameInfo?.zoneInfo?.id) {
_last = acctGameInfo
updateSpeedUi()
}
showGuideLayerIfNeed()
}
/**
* 第一次调用 setZoneList 是使用缓存中的数据
* 第二次调用是使用sdk中的最新数据
* 这里优先使用缓存中的sdk中的数据更新到数据库后待下次进入详情页在显示
* 当zoneList为null时说明磁盘上没有数据这时候使用sdk中的
*
*/
fun setZoneList(newZoneList: ArrayList<AcctGameInfo>?) {
if (newZoneList != null && zoneList.isEmpty()) {
hasZoneListLoaded = true
zoneList.clear()
zoneList.addAll(newZoneList)
updateSpeedUi()
}
fun setHasAnyAcctRecord(hasAnyAcctRecord: Boolean) {
hasAnyAcctRecordLoaded = true
this.hasAnyAcctRecord = hasAnyAcctRecord
showGuideLayerIfNeed()
}
private fun isInvalidClick(): Boolean {
@ -355,38 +334,26 @@ class GameDetailAcceleratorUiHelper(private val binding: DetailDownloadItemBindi
return false
}
private fun checkDataReady(block: () -> Unit) {
when {
!hasZoneListLoaded -> {
ToastUtils.showToast("区服信息正在加载中,请稍后再试!")
}
zoneList.isEmpty() -> {
ToastUtils.showToast("区服信息加载失败,请稍后再试!")
}
else -> {
block()
}
}
}
fun clear() {
iAcceleratorProvider?.unBindAccRelatedListener(accelerationListener)
AcceleratorDataHolder.instance.removeListener(onDataHolderListener)
guideView?.dismiss()
}
companion object {
private const val CLICK_DURATION = 300
const val MEMBER_TYPE_NOT_LOGIN = "未登录"
private const val DISTRICT_SERVER_EMPTY = ""
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 = "停止加速"

View File

@ -2,7 +2,10 @@ 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 {
@ -16,18 +19,30 @@ class AcceleratorStateInterceptor : AcceleratorValidator.Interceptor {
val isAccelerating = iAcceleratorProvider?.isCurAccSuccess() ?: false
if (isAccelerating) {
val game = request.game
AcceleratorReplaceDialogFragment.show(
context,
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
request.sourceEntrance
) {
if (chain.isValidContext(context)) {
listener?.finished(context)
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

@ -10,7 +10,6 @@ import androidx.annotation.IntDef
import androidx.appcompat.app.AppCompatActivity
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.WebActivity
import com.gh.gamecenter.common.HaloApp
import com.gh.gamecenter.common.base.fragment.BaseDialogFragment
import com.gh.gamecenter.common.constant.Constants
@ -56,6 +55,11 @@ class AcceleratorDialogFragment : BaseDialogFragment() {
SPEED_STOP -> StopSpeedUi()
SPEED_NOT_INSTALLED -> NotInstalledUi()
SPEED_CURRENT_ACCELERATING -> {
SensorsBridge.trackStopAcceleratingDialogShow(gameId, gameName, pkgName)
CurrentAcceleratingUi()
}
else -> throw IllegalArgumentException("请传递正确的参数 SpeedType !")
}
}
@ -102,8 +106,7 @@ class AcceleratorDialogFragment : BaseDialogFragment() {
sourceEntrance
)
SensorsBridge.trackMyAssetsPageShow(pkgName, gameId, gameName, sourceEntrance)
val intent = WebActivity.getMyAssetsIntent(requireContext())
startActivity(intent)
DirectUtils.navigateToMyAssetsPage(requireContext(), sourceEntrance)
}
is SpeedFailureUi -> {
@ -113,7 +116,7 @@ class AcceleratorDialogFragment : BaseDialogFragment() {
}
}
is StopSpeedUi -> {
is StopSpeedUi, is CurrentAcceleratingUi -> {
TheRouter.get(IAcceleratorProvider::class.java)?.stopQyGameAccelerate()
}
@ -134,6 +137,7 @@ class AcceleratorDialogFragment : BaseDialogFragment() {
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,
@ -163,7 +167,7 @@ class AcceleratorDialogFragment : BaseDialogFragment() {
}
}
@IntDef(SPEED_ENABLE_VIP, SPEED_START_FAILURE, SPEED_STOP, SPEED_NOT_INSTALLED)
@IntDef(SPEED_ENABLE_VIP, SPEED_START_FAILURE, SPEED_STOP, SPEED_NOT_INSTALLED, SPEED_CURRENT_ACCELERATING)
@Retention(AnnotationRetention.SOURCE)
annotation class SpeedType
@ -238,4 +242,19 @@ class AcceleratorDialogFragment : BaseDialogFragment() {
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

@ -13,26 +13,20 @@ import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.common.util.CheckLoginUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.BaseBottomDialogFragment
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.core.provider.IAcceleratorProvider
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.accelerator.GameDetailAcceleratorUiHelper.Companion.MEMBER_TYPE_NOT_LOGIN
import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.SCENE_TYPE_NO_GUIDE_LAYER
import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.SOURCE_ENTRANCE_GAME_DETAIL
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.therouter.TheRouter
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
import kotlin.math.roundToInt
class AcceleratorZoneDialogFragment : BaseBottomDialogFragment<DialogFragmentAcceleratorZoneBinding>() {
@ -41,21 +35,31 @@ class AcceleratorZoneDialogFragment : BaseBottomDialogFragment<DialogFragmentAcc
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(KEY_DATA, AcctGameInfo::class.java)
arguments?.getParcelableArrayList(EntranceConsts.KEY_DATA, AcctGameInfo.ZoneInfo::class.java)
} else {
arguments?.getParcelableArrayList(KEY_DATA)
} ?: listOf<AcctGameInfo>()
arguments?.getParcelableArrayList(EntranceConsts.KEY_DATA)
} ?: listOf<AcctGameInfo.ZoneInfo>()
val game = arguments?.getParcelable<GameEntity>(KEY_GAME)!!
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()
@ -63,39 +67,38 @@ class AcceleratorZoneDialogFragment : BaseBottomDialogFragment<DialogFragmentAcc
mBinding.recyclerZone.layoutManager = GridLayoutManager(requireContext(), 3)
mBinding.recyclerZone.addItemDecoration(MyDecorationItem())
mBinding.recyclerZone.adapter = adapter
adapter.submitList(data)
adapter.setData(selectedId, data)
}
private fun startAccelerating(game: GameEntity, acctGameInfo: AcctGameInfo) {
private fun startAccelerating(game: GameEntity, zoneInfo: AcctGameInfo.ZoneInfo) {
context?.let {
viewModel.useCase.insertAcctGameInfo(acctGameInfo)
val iAcceleratorProvider = TheRouter.get(IAcceleratorProvider::class.java)
val memberType = if (CheckLoginUtils.isLogin()) {
iAcceleratorProvider?.getMemberType() ?: ""
} else {
MEMBER_TYPE_NOT_LOGIN
}
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,
acctGameInfo.zoneInfo.cnName ?: "",
zoneInfo.cnName ?: "",
SCENE_TYPE_NO_GUIDE_LAYER,
SOURCE_ENTRANCE_GAME_DETAIL
sourceEntrance
)
val isVip = iAcceleratorProvider?.isVip() ?: false
val isNewUser = iAcceleratorProvider?.isNewUser() ?: false
val request = AcceleratorValidator.Request(isVip, isNewUser, game, SOURCE_ENTRANCE_GAME_DETAIL)
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, acctGameInfo, game, isNeedRecord = true, hasMultiZone = true,
sourceEntrance = SOURCE_ENTRANCE_GAME_DETAIL
context,
zoneInfo,
game,
isNeedRecord = false,
hasMultiZone = true,
sourceEntrance = sourceEntrance
)
}
@ -105,9 +108,14 @@ class AcceleratorZoneDialogFragment : BaseBottomDialogFragment<DialogFragmentAcc
}
companion object {
private const val KEY_DATA = "key_data"
private const val KEY_GAME = "key_game"
fun show(context: Context, data: ArrayList<AcctGameInfo>, game: GameEntity) {
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 {
@ -115,8 +123,10 @@ class AcceleratorZoneDialogFragment : BaseBottomDialogFragment<DialogFragmentAcc
}?.let {
val fragment = AcceleratorZoneDialogFragment().apply {
arguments = Bundle().apply {
putParcelableArrayList(KEY_DATA, data)
putParcelable(KEY_GAME, game)
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)
@ -140,10 +150,15 @@ class AcceleratorZoneDialogFragment : BaseBottomDialogFragment<DialogFragmentAcc
}
}
class ZoneAdapter(private val click: (AcctGameInfo) -> Unit) :
ListAdapter<AcctGameInfo, ZoneAdapter.ZoneViewHolder>(diffCallback) {
class ZoneAdapter(private val click: (AcctGameInfo.ZoneInfo) -> Unit) :
ListAdapter<AcctGameInfo.ZoneInfo, ZoneAdapter.ZoneViewHolder>(diffCallback) {
private var selectedPosition = -1
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())
@ -159,15 +174,15 @@ class AcceleratorZoneDialogFragment : BaseBottomDialogFragment<DialogFragmentAcc
override fun onBindViewHolder(holder: ZoneViewHolder, position: Int) {
val item = getItem(position) ?: return
updateSelectedState(holder, position)
holder.binding.tvName.text = item.zoneName
updateSelectedState(holder, item.id)
holder.binding.tvName.text = item.cnName
holder.itemView.setOnClickListener {
click(item)
}
}
private fun updateSelectedState(holder: ZoneViewHolder, position: Int) {
val (textColorResId, backgroundResId) = if (position == selectedPosition) {
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
@ -178,14 +193,15 @@ class AcceleratorZoneDialogFragment : BaseBottomDialogFragment<DialogFragmentAcc
companion object {
private const val SELECTED_CHANGED_PAYLOAD = "selected_changed_payload"
private val diffCallback = object : DiffUtil.ItemCallback<AcctGameInfo>() {
override fun areItemsTheSame(oldItem: AcctGameInfo, newItem: AcctGameInfo): Boolean {
return oldItem.zoneInfo.id == newItem.zoneInfo.id
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, newItem: AcctGameInfo): Boolean {
override fun areContentsTheSame(
oldItem: AcctGameInfo.ZoneInfo,
newItem: AcctGameInfo.ZoneInfo
): Boolean {
return oldItem == newItem
}

View File

@ -22,12 +22,17 @@ 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
@ -38,7 +43,7 @@ class StartingAcceleratorDialogFragment : BaseDialogFragment() {
private lateinit var binding: DialogFragmentStartingAcceleratorBinding
private lateinit var acctGameInfo: AcctGameInfo
private lateinit var zoneInfo: AcctGameInfo.ZoneInfo
private lateinit var game: GameEntity
private var isNeedRecord: Boolean = false
private var iAcceleratorProvider: IAcceleratorProvider? = null
@ -51,6 +56,8 @@ class StartingAcceleratorDialogFragment : BaseDialogFragment() {
private var refreshTokenCount = 0
private val acceleratorDataHolder = AcceleratorDataHolder.instance
private val accelerationListener = object : OnAccelerateListener {
override fun onStateChanged(state: AccelerateState) {
// 如果状态能成功回,则移除计时
@ -60,10 +67,8 @@ class StartingAcceleratorDialogFragment : BaseDialogFragment() {
ToastUtils.showToast("加速成功")
// 加速成功,启动游戏
PackageLauncher.launchApp(requireContext(), game, game.getUniquePackageName())
// 记录加速记录
if (isNeedRecord) {
viewModel.useCase.recordAcctGameInfo(game.id, acctGameInfo, hasMultiZone)
}
// 记录加速成功的游戏
viewModel.useCase.recordAcctGameInfo(game, zoneInfo, hasMultiZone)
trackNetworkAccelerationStartupResult("成功")
dismissAllowingStateLoss()
@ -84,14 +89,26 @@ class StartingAcceleratorDialogFragment : BaseDialogFragment() {
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) {
@ -110,8 +127,10 @@ class StartingAcceleratorDialogFragment : BaseDialogFragment() {
binding.tvProgress.text = getString(R.string.accelerating_with_progress, "$progress")
}
override fun onVipStatusChanged(isNewUser: Boolean, isVip: Boolean) = Unit
}
private fun unloadAcceleratorErrorLog(game: GameEntity) {
viewModel.useCase.unloadAcceleratorErrorLog(game)
}
private fun trackNetworkAccelerationStartupResult(result: String) {
@ -119,8 +138,8 @@ class StartingAcceleratorDialogFragment : BaseDialogFragment() {
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
iAcceleratorProvider?.getMemberType() ?: "",
if (hasMultiZone) acctGameInfo.zoneInfo.cnName ?: "" else DISTRICT_SERVER_HAVA,
acceleratorDataHolder.memberType,
if (hasMultiZone) zoneInfo.cnName ?: "" else DISTRICT_SERVER_HAVA,
result,
sourceEntrance
)
@ -137,7 +156,7 @@ class StartingAcceleratorDialogFragment : BaseDialogFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
iAcceleratorProvider = TheRouter.get(IAcceleratorProvider::class.java)
acctGameInfo = arguments?.getParcelable(EntranceConsts.KEY_ACCT_GAME_INFO)!!
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
@ -160,8 +179,8 @@ class StartingAcceleratorDialogFragment : BaseDialogFragment() {
binding.tvProgress.text = getString(R.string.accelerating_with_progress, "0")
iAcceleratorProvider?.bindAccRelatedListener("", accelerationListener)
val isVip = iAcceleratorProvider?.isVip() ?: false
val isNewUser = iAcceleratorProvider?.isNewUser() ?: false
val isVip = acceleratorDataHolder.isVip
val isNewUser = acceleratorDataHolder.isNewUser
if (isNewUser && !isVip) {
// 新用户并且还不是vip
viewModel.rechargeTrial()
@ -199,18 +218,21 @@ class StartingAcceleratorDialogFragment : BaseDialogFragment() {
// 15s以后不管成功还是失败都要关闭当前页面
dismissAllowingStateLoss()
}, TIME_OUT)
viewModel.useCase.setLastAcctGameRecord(game)
iAcceleratorProvider?.startQyGameAccelerate(acctGameInfo)
AcceleratorDataHolder.instance.acctGameRecord = AcctRecord(game.id, game, zoneInfo, hasMultiZone, 0)
iAcceleratorProvider?.startQyGameAccelerate(game.id, game.getUniquePackageName() ?: "", zoneInfo)
if (isNeedRecord) {
viewModel.useCase.insertAcctGameInfo(acctGameInfo)
viewModel.useCase.insertAcctGameInfo(
AcctGameInfo(game.id, game.getUniquePackageName() ?: "", zoneInfo)
)
}
if (refreshTokenCount == 0) {
SensorsBridge.trackNetworkAccelerationStartup(
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
iAcceleratorProvider?.getMemberType() ?: "",
if (hasMultiZone) acctGameInfo.zoneInfo.cnName ?: "" else DISTRICT_SERVER_HAVA,
acceleratorDataHolder.memberType,
if (hasMultiZone) zoneInfo.cnName ?: "" else DISTRICT_SERVER_HAVA,
sourceEntrance
)
}
@ -231,7 +253,7 @@ class StartingAcceleratorDialogFragment : BaseDialogFragment() {
fun show(
context: Context,
acctGameInfo: AcctGameInfo,
zoneInfo: AcctGameInfo.ZoneInfo,
game: GameEntity,
isNeedRecord: Boolean,
hasMultiZone: Boolean,
@ -244,7 +266,7 @@ class StartingAcceleratorDialogFragment : BaseDialogFragment() {
}?.let {
val fragment = StartingAcceleratorDialogFragment().apply {
arguments = Bundle().apply {
putParcelable(EntranceConsts.KEY_ACCT_GAME_INFO, acctGameInfo)
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)

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

File diff suppressed because it is too large Load Diff

View File

@ -1,4 +1,4 @@
package com.gh.gamecenter.gamedetail.desc
package com.gh.gamecenter.gamedetail.detail.adapter
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
@ -6,23 +6,26 @@ import com.gh.common.exposure.ExposureManager
import com.gh.common.filter.RegionSettingHelper
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.GamedetailItemGameCollectionBinding
import com.gh.gamecenter.entity.GameDetailRecommendGameEntity
import com.gh.gamecenter.entity.GameDetailRecommendGameCollectionEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.gamedetail.detail.viewholder.BaseGameDetailItemViewHolder
class GameCollectionAdapter(
private val mRecommendGameList: ArrayList<GameDetailRecommendGameEntity>,
private val mRecommendGameList: ArrayList<GameDetailRecommendGameCollectionEntity>,
private val mGameId: String,
private val mGameName: String,
private val mGame: GameEntity?,
private val mEntrance: String,
private val mPath: String,
private val mBasicExposureSource: List<ExposureSource>
private val mBasicExposureSource: List<ExposureSource>,
private val trackData: BaseGameDetailItemViewHolder.GameDetailModuleTrackData,
private val getGameStatus: () -> String
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val mDefaultHorizontalPadding by lazy { com.gh.gamecenter.common.R.dimen.game_detail_item_horizontal_padding.toPx() }
@ -39,7 +42,7 @@ class GameCollectionAdapter(
if (holder is GameCollectionItemViewHolder) {
val entity = mRecommendGameList[position]
holder.binding.run {
root.background = com.gh.gamecenter.common.R.drawable.background_shape_white_radius_5.toDrawable(root.context)
root.background = R.drawable.bg_shape_space_radius_5.toDrawable(root.context)
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(root.context))
titleTv.text = entity.title
ImageUtils.display(coverImage, entity.cover)
@ -72,20 +75,20 @@ class GameCollectionAdapter(
}
gameCountTv.text = "+${entity.count.game - games.size}"
root.setOnClickListener {
SensorsBridge.trackGameDetailPageGameCollectRecommendClick(
gameId = mGameId,
gameName = mGameName,
pageName = GlobalActivityManager.getCurrentPageEntity().pageName,
pageId = GlobalActivityManager.getCurrentPageEntity().pageId,
pageBusinessId = GlobalActivityManager.getCurrentPageEntity().pageBusinessId,
lastPageId = GlobalActivityManager.getCurrentPageEntity().pageId,
lastPageName = GlobalActivityManager.getCurrentPageEntity().pageName,
lastPageBusinessId = GlobalActivityManager.getCurrentPageEntity().pageBusinessId,
downloadStatus = mGame?.downloadStatusChinese ?: "",
gameType = mGame?.categoryChinese ?: "",
text = "游戏单",
gameCollectId = entity.id,
gameCollectTitle = entity.title
SensorsBridge.trackGameDetailModuleClick(
trackData.gameId,
trackData.gameName,
trackData.gameType,
"组件内容",
trackData.moduleType,
trackData.moduleName,
trackData.sequence,
entity.title,
position + 1,
"game_list_detail",
entity.id,
entity.title,
getGameStatus()
)
DirectUtils.directToGameCollectionDetail(
it.context,

View File

@ -0,0 +1,199 @@
package com.gh.gamecenter.gamedetail.detail.adapter
import android.app.Activity
import android.content.Context
import android.util.SparseArray
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.util.forEach
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.RecyclerView
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.gamecenter.ImageViewerActivity
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.toastInInternalRelease
import com.gh.gamecenter.databinding.ItemGameCoverGalleryBinding
import com.gh.gamecenter.databinding.ItemGameCoverVideoBinding
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.detail.GameDetailFragment
import com.gh.gamecenter.gamedetail.entity.CoverEntity
import com.gh.gamecenter.gamedetail.video.TopVideoView
import com.lightgame.adapter.BaseRecyclerAdapter
import com.shuyu.gsyvideoplayer.builder.GSYVideoOptionBuilder
import com.shuyu.gsyvideoplayer.listener.GSYSampleCallBack
import com.shuyu.gsyvideoplayer.utils.OrientationUtils
class GameDetailCoverAdapter(
context: Context,
private val fragment: GameDetailFragment,
private val viewModel: GameDetailViewModel,
private val showOrHideCoverFilter: ((Boolean) -> Unit)
) : BaseRecyclerAdapter<RecyclerView.ViewHolder>(context) {
private var coverRecyclerView: RecyclerView? = null
private var coverList = arrayListOf<CoverEntity>()
private var isSingleGallery = false
private var hasSetFixedHeight = false
private val imageUrlList = arrayListOf<String>()
private val imageViewArray = SparseArray<SimpleDraweeView>()
fun submitList(data: List<CoverEntity>) {
imageUrlList.clear()
coverList = ArrayList(data)
isSingleGallery = coverList.count { it.gallery != null } == 1
coverList.mapNotNullTo(imageUrlList) { it.gallery?.url }
notifyDataSetChanged()
}
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
coverRecyclerView = recyclerView
}
override fun getItemViewType(position: Int): Int {
val coverEntity = coverList.getOrNull(position)
return when {
coverEntity?.video != null -> ITEM_VIDEO
else -> ITEM_GALLERY
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ITEM_VIDEO -> GameDetailCoverVideoItemViewHolder(parent.toBinding())
else -> GameDetailCoverGalleryItemViewHolder(parent.toBinding())
}
}
override fun getItemCount(): Int = coverList.size
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
coverList.getOrNull(position)?.let {
if (holder is GameDetailCoverVideoItemViewHolder) {
bindVideoItem(holder, it, position)
}
if (holder is GameDetailCoverGalleryItemViewHolder) {
bindGalleryItem(holder, it, position)
}
}
}
private fun bindVideoItem(holder: GameDetailCoverVideoItemViewHolder, entity: CoverEntity, position: Int) {
if (position == viewModel.defaultCoverEntity?.index && !hasSetFixedHeight) {
holder.binding.root.post {
coverRecyclerView?.updateLayoutParams<ViewGroup.LayoutParams> { height = holder.binding.root.height }
hasSetFixedHeight = true
}
}
val topVideo = entity.video ?: return
val orientationUtils = OrientationUtils(mContext as Activity, holder.binding.player)
orientationUtils.isEnable = false
GSYVideoOptionBuilder()
.setIsTouchWigetFull(false)
.setIsTouchWiget(false)
.setRotateViewAuto(false)
.setShowFullAnimation(false)
.setSeekRatio(1F)
.setUrl(topVideo.url)
.setCacheWithPlay(true)
.setShowPauseCover(true)
.setReleaseWhenLossAudio(false)
.setAutoFullWithSize(true)
.setDismissControlTime(5000)
.setVideoAllCallBack(object : GSYSampleCallBack() {
override fun onQuitFullscreen(url: String?, vararg objects: Any) {
orientationUtils.backToProtVideo()
holder.binding.player.uploadVideoStreamingPlaying("退出全屏")
}
})
.build(holder.binding.player)
holder.binding.player.gameName = viewModel.game?.name ?: ""
holder.binding.player.viewModel = viewModel
holder.binding.player.showOrHideCoverFilter = showOrHideCoverFilter
holder.binding.player.video = topVideo
holder.binding.player.updateThumb(topVideo.poster)
holder.binding.player.fullscreenButton.setOnClickListener {
val horizontalVideoView =
holder.binding.player.startWindowFullscreen(mContext, true, true) as? TopVideoView
if (horizontalVideoView == null) {
toastInInternalRelease("全屏失败,请向技术人员提供具体的操作步骤")
return@setOnClickListener
}
orientationUtils.resolveByClick()
horizontalVideoView.uuid = holder.binding.player.uuid
horizontalVideoView.viewModel = viewModel
horizontalVideoView.video = topVideo
horizontalVideoView.updateThumb(topVideo.poster)
horizontalVideoView.violenceUpdateMuteStatus()
holder.binding.player.uploadVideoStreamingPlaying("开始播放")
holder.binding.player.uploadVideoStreamingPlaying("点击全屏")
}
holder.binding.player.observeVolume(fragment)
}
private fun bindGalleryItem(holder: GameDetailCoverGalleryItemViewHolder, entity: CoverEntity, position: Int) {
val imageUrl = entity.gallery?.url ?: ""
val isLastItem = position == coverList.lastIndex
val imageWidth = (entity.gallery?.width ?: return).toInt()
val imageHeight = (entity.gallery?.height ?: return).toInt()
val isLandscape = imageWidth >= imageHeight
val ratio = imageWidth / imageHeight.toFloat()
ConstraintSet().also {
it.clone(holder.binding.root)
it.setDimensionRatio(holder.binding.galleryIv.id, if (isLandscape) "h,344:193" else "")
}.applyTo(holder.binding.root)
holder.binding.root.updateLayoutParams<MarginLayoutParams> {
width = if (isLandscape || isSingleGallery) ViewGroup.LayoutParams.MATCH_PARENT else ViewGroup.LayoutParams.WRAP_CONTENT
height = if (isLandscape) ViewGroup.LayoutParams.WRAP_CONTENT else PORTRAIT_IMAGE_HEIGHT_DP.dip2px()
rightMargin = if (isLandscape || isSingleGallery || isLastItem) 8F.dip2px() else 0
}
holder.binding.galleryIv.updateLayoutParams<ConstraintLayout.LayoutParams> {
width = if (isLandscape) 0 else (PORTRAIT_IMAGE_HEIGHT_DP.dip2px() * ratio).toInt()
height = if (isLandscape) 0 else PORTRAIT_IMAGE_HEIGHT_DP.dip2px()
}
if (position == viewModel.defaultCoverEntity?.index && !hasSetFixedHeight) {
holder.binding.root.post {
coverRecyclerView?.updateLayoutParams<ViewGroup.LayoutParams> { height = holder.binding.root.height }
hasSetFixedHeight = true
}
}
holder.binding.galleryIv.setTag(ImageUtils.TAG_TARGET_WIDTH, imageWidth)
ImageUtils.display(holder.binding.galleryIv, imageUrl)
imageViewArray[entity.galleryIndex] = holder.binding.galleryIv
holder.binding.root.setOnClickListener {
val imageViewList = arrayListOf<View>()
imageViewArray.forEach { _, value -> imageViewList.add(value) }
val intent = ImageViewerActivity.getIntent(
mContext,
imageUrlList,
entity.galleryIndex,
imageViewList,
"游戏详情-视频/图片区域"
)
mContext.startActivity(intent)
}
}
companion object {
const val ITEM_VIDEO = 0
const val ITEM_GALLERY = 1
private const val PORTRAIT_IMAGE_HEIGHT_DP = 280F
}
class GameDetailCoverVideoItemViewHolder(val binding: ItemGameCoverVideoBinding) :
BaseRecyclerViewHolder<Any>(binding.root)
class GameDetailCoverGalleryItemViewHolder(val binding: ItemGameCoverGalleryBinding) :
BaseRecyclerViewHolder<Any>(binding.root)
}

View File

@ -0,0 +1,139 @@
package com.gh.gamecenter.gamedetail.detail.adapter
import android.view.ViewGroup
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.ListAdapter
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.detail.viewholder.*
import com.gh.gamecenter.gamedetail.entity.GameDetailData
class GameDetailListAdapter(
private val downloadBtn: DownloadButton?,
private val viewModel: GameDetailViewModel,
private val lifecycleOwner: LifecycleOwner
) :
ListAdapter<GameDetailData, BaseGameDetailItemViewHolder>(createDiffCallback()) {
override fun getItemViewType(position: Int): Int {
val data = currentList[position]
return when {
data?.linkContentCard != null -> {
when (data.linkContentCard.size) {
1 -> ITEM_CONTENT_CARD_SINGLE
2 -> ITEM_CONTENT_CARD_DOUBLE
else -> ITEM_CONTENT_CARD_TRIPLE
}
}
data?.linkAdvertising != null -> if (data.linkAdvertising.img.isEmpty()) ITEM_ADVERTISING else ITEM_ADVERTISING_IMAGE
data?.linkComprehensive != null -> ITEM_COMPREHENSIVE_PANEL
data?.linkCustomColumn != null -> ITEM_CUSTOM_COLUMN
data?.linkDrawer != null -> ITEM_DRAWER
data?.linkAnnouncement != null -> ITEM_ANNOUNCEMENT
data?.linkGameBrief != null -> ITEM_GAME_BRIEF
data?.linkDeveloperWord != null -> ITEM_DEVELOPER_WORD
data?.linkUpdate != null -> ITEM_UPDATE
data?.linkComment != null -> ITEM_COMMENT
data?.linkInfo != null -> ITEM_DETAIL_INFO
data?.linkContentRecommend != null -> ITEM_CONTENT_RECOMMEND
data?.linkGameVideo != null -> ITEM_GAME_VIDEO
data?.linkGameGuide != null -> ITEM_GAME_STRATEGY
data?.linkServer != null -> ITEM_SERVER
data?.linkLibao != null -> ITEM_GIFT
data?.linkRelatedGame != null -> ITEM_RELATED_GAME
data?.linkImageRecommend != null -> ITEM_RECOMMEND_IMAGE
data?.linkEveryonePlaying != null -> ITEM_RECOMMEND_GAME
data?.linkRecommendGameList != null -> ITEM_RECOMMEND_GAME_COLLECTION
else -> ITEM_RECOMMEND_COLUMN
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseGameDetailItemViewHolder {
return when (viewType) {
ITEM_CONTENT_CARD_SINGLE -> GameDetailContentCardSingleItemViewHolder(parent.toBinding(), downloadBtn, viewModel, lifecycleOwner)
ITEM_CONTENT_CARD_DOUBLE -> GameDetailContentCardDoubleItemViewHolder(parent.toBinding(), downloadBtn, viewModel, lifecycleOwner)
ITEM_CONTENT_CARD_TRIPLE -> GameDetailContentCardTripleItemViewHolder(parent.toBinding(), downloadBtn, viewModel, lifecycleOwner)
ITEM_ADVERTISING -> GameDetailAdvertisingItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_ADVERTISING_IMAGE -> GameDetailAdvertisingImageItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_COMPREHENSIVE_PANEL -> GameDetailComprehensivePanelItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_CUSTOM_COLUMN -> GameDetailCustomColumnItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_DRAWER -> GameDetailDrawerItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_ANNOUNCEMENT -> GameDetailAnnouncementItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_GAME_BRIEF -> GameDetailBriefItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_DEVELOPER_WORD -> GameDetailDeveloperWordItemViewHolder(
parent.toBinding(),
downloadBtn,
viewModel
)
ITEM_UPDATE -> GameDetailUpdateItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_COMMENT -> GameDetailCommentItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_DETAIL_INFO -> GameDetailInfoItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_CONTENT_RECOMMEND -> GameDetailContentRecommendItemViewHolder(parent.toBinding(), downloadBtn, viewModel, lifecycleOwner)
ITEM_GAME_VIDEO -> GameDetailVideoItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_GAME_STRATEGY -> GameDetailStrategyItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_SERVER -> GameDetailServerItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_GIFT -> GameDetailGiftItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_RELATED_GAME -> GameDetailRelatedGameItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_RECOMMEND_IMAGE -> GameDetailRecommendImageItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_RECOMMEND_GAME -> GameDetailRecommendGameItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_RECOMMEND_GAME_COLLECTION -> GameDetailRecommendGameCollectionItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
else -> GameDetailRecommendColumnItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
}
}
override fun onBindViewHolder(holder: BaseGameDetailItemViewHolder, position: Int) {
val item = getItem(position)
holder.bindView(item)
}
companion object {
const val ITEM_CONTENT_CARD_SINGLE = 0 // 内容卡片-单个
const val ITEM_CONTENT_CARD_DOUBLE = 1 // 内容卡片-两个
const val ITEM_CONTENT_CARD_TRIPLE = 2 // 内容卡片-三个
const val ITEM_ADVERTISING = 3 // 广告推荐-无图片
const val ITEM_ADVERTISING_IMAGE = 4 // 广告推荐-带图片
const val ITEM_COMPREHENSIVE_PANEL = 5 // 综合面板
const val ITEM_CUSTOM_COLUMN = 6 // 自定义栏目
const val ITEM_DRAWER = 7 // 列表抽屉
const val ITEM_ANNOUNCEMENT = 8 // 资讯公告
const val ITEM_GAME_BRIEF = 9 // 游戏简介
const val ITEM_DEVELOPER_WORD = 10 // 开发者的话
const val ITEM_UPDATE = 11 // 更新内容
const val ITEM_COMMENT = 12 // 玩家评论
const val ITEM_DETAIL_INFO = 13 // 详情信息
const val ITEM_CONTENT_RECOMMEND = 14 // 内容推荐PK组件
const val ITEM_GAME_VIDEO = 15 // 视频
const val ITEM_GAME_STRATEGY = 16 // 游戏攻略
const val ITEM_SERVER = 17 // 游戏开服
const val ITEM_GIFT = 18 // 游戏礼包
const val ITEM_RELATED_GAME = 19 // 相关游戏
const val ITEM_RECOMMEND_IMAGE = 20 // 图片推荐
const val ITEM_RECOMMEND_GAME = 21 // 大家都在玩
const val ITEM_RECOMMEND_GAME_COLLECTION = 22 // 游戏单推荐
const val ITEM_RECOMMEND_COLUMN = 23 // 专题推荐
fun createDiffCallback() = object : ItemCallback<GameDetailData>() {
override fun areItemsTheSame(oldItem: GameDetailData, newItem: GameDetailData): Boolean {
return oldItem.areItemsTheSame(newItem)
}
override fun areContentsTheSame(oldItem: GameDetailData, newItem: GameDetailData): Boolean {
if (oldItem.linkLibao != null && newItem.linkLibao != null) {
return false
}
if (oldItem.linkEveryonePlaying != null && newItem.linkEveryonePlaying != null) {
return false
}
if (oldItem.linkContentRecommend != null && newItem.linkContentRecommend != null) {
return false
}
if (oldItem.recommendColumn != null && newItem.recommendColumn != null) {
return false
}
return oldItem.areContentsTheSame(newItem)
}
}
}
}

View File

@ -1,39 +1,37 @@
package com.gh.gamecenter.gamedetail.desc
package com.gh.gamecenter.gamedetail.detail.adapter
import android.content.Context
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.databind.BindingAdapters
import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.StringUtils
import com.gh.gamecenter.databinding.ItemGameDetailRelatedVersionBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.game.GameItemViewHolder
import com.gh.gamecenter.gamedetail.entity.RelatedVersion
import com.gh.gamecenter.gamedetail.detail.viewholder.BaseGameDetailItemViewHolder
class GameRelatedVersionAdapter(
private val mContext: Context,
private val mGameId: String,
private val mGameName: String,
private val mGame: GameEntity?,
private val mDatas: ArrayList<RelatedVersion>,
private val mEntrance: String
class GameDetailRelatedGameAdapter(
private val context: Context,
private val game: GameEntity?,
private val relatedGameList: ArrayList<GameEntity>,
private val entrance: String,
private val trackData: BaseGameDetailItemViewHolder.GameDetailModuleTrackData,
private val getGameStatus: () -> String
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var exposureEventList: ArrayList<ExposureEvent> = arrayListOf()
private val mMaxWidth = mContext.resources.displayMetrics.widthPixels - 32F.dip2px()
private val maxWidth = context.resources.displayMetrics.widthPixels
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return GameDetailRelatedViewHolder(parent.toBinding())
}
override fun getItemCount(): Int = mDatas.size
override fun getItemCount(): Int = relatedGameList.size
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val relatedVersion = mDatas[position]
val gameEntity = relatedGameList.getOrNull(position) ?: return
if (holder is GameDetailRelatedViewHolder) {
val paddingStart = 16F.dip2px()
val isEndOfRow = position >= if (itemCount % 3 == 0) {
@ -46,48 +44,42 @@ class GameRelatedVersionAdapter(
var paddingEnd = if (isEndOfRow) 16F.dip2px() else 0F.dip2px()
holder.itemView.layoutParams = if (!isEndOfRow) {
paddingEnd += 1
ViewGroup.LayoutParams(mMaxWidth - 24F.dip2px(), ViewGroup.LayoutParams.WRAP_CONTENT)
ViewGroup.LayoutParams(maxWidth - 56F.dip2px(), ViewGroup.LayoutParams.WRAP_CONTENT)
} else {
ViewGroup.LayoutParams(mMaxWidth - 1, ViewGroup.LayoutParams.WRAP_CONTENT)
ViewGroup.LayoutParams(maxWidth - 1, ViewGroup.LayoutParams.WRAP_CONTENT)
}
holder.binding.root.setPadding(paddingStart, 8F.dip2px(), paddingEnd, 8F.dip2px())
val gameEntity = relatedVersion.game ?: return
holder.binding.gameNameTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(mContext))
holder.binding.gameNameTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
holder.binding.gameNameTv.text = gameEntity.name
BindingAdapters.setGameTags(holder.binding.gameLabelLl, gameEntity)
holder.binding.gameIconView.displayGameIcon(gameEntity)
holder.binding.gameIconView.setBorderColor(com.gh.gamecenter.common.R.color.resource_border)
holder.itemView.setOnClickListener {
MtaHelper.onEvent("游戏详情_新", "相关游戏版本", "${mGameName}+${relatedVersion.gameName}")
GameDetailActivity.startGameDetailActivity(
mContext,
relatedVersion.gameId,
context,
gameEntity.id,
StringUtils.buildString(
mEntrance,
entrance,
"+(",
"游戏详情",
"[",
relatedVersion.gameName,
gameEntity.name,
"]:相关游戏[",
(position + 1).toString(),
"])"
),
exposureEventList.safelyGetInRelease(position)
)
SensorsBridge.trackGameDetailPageRelatedGameClick(
gameId = mGameId,
gameName = mGameName,
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 = mGame?.downloadStatusChinese ?: "",
gameType = mGame?.categoryChinese ?: "",
clickGameId = gameEntity.id,
clickGameName = gameEntity.name ?: "",
clickGameType = gameEntity.categoryChinese
SensorsBridge.trackGameDetailModuleClick(
trackData.gameId,
trackData.gameName,
trackData.gameType,
"组件内容",
trackData.moduleType,
"相关游戏",
trackData.sequence,
supSequence = position + 1,
gameStatus = getGameStatus()
)
}
GameItemViewHolder.initGameSubtitleAndAdLabel(gameEntity, holder.binding.gameSubtitleTv)

View File

@ -1,4 +1,4 @@
package com.gh.gamecenter.gamedetail.desc
package com.gh.gamecenter.gamedetail.detail.adapter
import android.content.Context
import android.view.Gravity
@ -8,9 +8,9 @@ import android.view.ViewGroup
import android.view.ViewTreeObserver
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.common.utils.safelyGetInRelease
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.databinding.ItemGameDetailLatestServiceBinding
import com.gh.gamecenter.databinding.ItemGameDetailMoreBinding
import com.gh.gamecenter.feature.entity.GameEntity

View File

@ -0,0 +1,330 @@
package com.gh.gamecenter.gamedetail.detail.adapter
import android.app.Activity
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.widget.LinearLayout
import androidx.core.view.children
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.RecyclerView
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.LibaoUtils
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.ImageViewerActivity
import com.gh.gamecenter.common.databinding.RefreshFooterviewBinding
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.ItemGameDetailMoreBinding
import com.gh.gamecenter.databinding.ItemGameLibaoBinding
import com.gh.gamecenter.feature.entity.LibaoEntity
import com.gh.gamecenter.gamedetail.detail.viewholder.BaseGameDetailItemViewHolder.GameDetailModuleTrackData
import com.gh.gamecenter.libao.LibaoDetailActivity
import com.gh.gamecenter.login.user.UserManager
class GameLibaoAdapter(
val context: Context,
var libaos: ArrayList<LibaoEntity>,
val gameName: String,
val gameId: String,
showExpandIcon: Boolean = true,
private val trackData: GameDetailModuleTrackData? = null,
private val getGameStatus: (() -> String)? = null
) :
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 when(viewType) {
LIBAO_ITEM -> LibaoViewHolder(parent.toBinding())
FOOTER_ITEM -> FooterViewHolder(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 -> {
val libaoEntity = libaos[position]
holder.bindItem(context, this, gameId, gameName, libaoEntity, trackData, getGameStatus)
}
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, "展开")
}
}
is FooterViewHolder -> {
holder.binding.footerviewHint.setTextColor(com.gh.gamecenter.common.R.color.text_instance.toColor(context))
holder.binding.footerviewHint.text = "没有更多内容了"
holder.binding.footerviewLoading.isVisible = false
}
}
}
class LibaoViewHolder(var binding: ItemGameLibaoBinding) : RecyclerView.ViewHolder(binding.root) {
fun bindItem(
context: Context,
adapter: RecyclerView.Adapter<*>,
gameId: String,
gameName: String,
libaoEntity: LibaoEntity,
gameDetailModuleTrackData: GameDetailModuleTrackData? = null,
getGameStatus: (() -> String)? = null
) {
val position = bindingAdapterPosition
binding.root.updateLayoutParams<MarginLayoutParams> { topMargin = if (position == 0) 0 else 8F.dip2px() }
binding.root.background = com.gh.gamecenter.common.R.drawable.bg_shape_f8_radius_8.toDrawable(context)
binding.libaoNameTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
binding.contentTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
binding.remainingTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
binding.libaoCodeTv.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
binding.libaoNameTv.text = libaoEntity.name
binding.contentTv.text = libaoEntity.content?.fromHtml()
binding.horizontalScrollView.goneIf(libaoEntity.materials.isEmpty()) {
if (binding.imagesContainer.tag == libaoEntity.id) {
binding.imagesContainer.children.forEach { view ->
if (view is SimpleDraweeView) {
view.hierarchy.roundingParams = RoundingParams().apply {
setCornersRadius(4F.dip2px().toFloat())
setOverlayColor(com.gh.gamecenter.common.R.color.ui_container_1.toColor(context))
}
}
}
return@goneIf
}
binding.imagesContainer.removeAllViews()
val imageViewList = arrayListOf<View>()
libaoEntity.materials.forEachIndexed { index, material ->
binding.imagesContainer.addView(SimpleDraweeView(context).apply {
val roundingParams = RoundingParams().apply {
setCornersRadius(4F.dip2px().toFloat())
setOverlayColor(com.gh.gamecenter.common.R.color.ui_container_1.toColor(context))
}
hierarchy = GenericDraweeHierarchyBuilder(resources)
.setFadeDuration(500)
.setRoundingParams(roundingParams)
.setPlaceholderImage(com.gh.gamecenter.common.R.drawable.occupy, ScalingUtils.ScaleType.FIT_XY)
.build()
ImageUtils.display(this, material.img)
}.apply {
imageViewList.add(this)
setOnClickListener {
context.startActivity(ImageViewerActivity.getIntent(context, libaoEntity.getMaterialImgList(), index, imageViewList, null))
}
}, LinearLayout.LayoutParams(24F.dip2px(), 24F.dip2px()).apply {
setMargins(if (index != 0) 16F.dip2px() else 8F.dip2px(), 0, 0, 0)
})
}
binding.imagesContainer.tag = libaoEntity.id
}
val isTypeCopy = libaoEntity.receiveMethod == "copy"
if (isTypeCopy) {
// 类型为复制,不需要登录也能直接领取
binding.libaoSchedulePb.visibility = View.GONE
binding.remainingTv.visibility = View.GONE
binding.libaoCodeTv.visibility = View.VISIBLE
val text = "兑换码:${libaoEntity.code}"
binding.libaoCodeTv.text = SpanBuilder(text).color(
binding.root.context,
4,
text.length,
com.gh.gamecenter.common.R.color.text_theme
).build()
binding.copyLibaoCodeIv.visibility = View.VISIBLE
binding.copyLibaoCodeIv.setOnClickListener {
binding.receiveTv.performClick()
}
} else if (libaoEntity.universal || libaoEntity.status == "check") {
//通用码礼包/或者还未添加礼包码时,不显示进度条,显示礼包码
binding.libaoSchedulePb.visibility = View.GONE
binding.remainingTv.visibility = View.GONE
binding.libaoCodeTv.visibility = View.VISIBLE
binding.copyLibaoCodeIv.visibility = View.GONE
if (!UserManager.getInstance().isLoggedIn) {
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"
binding.libaoCodeTv.text = SpanBuilder(text).color(
binding.root.context,
4,
text.length,
com.gh.gamecenter.common.R.color.text_theme
).build()
binding.copyLibaoCodeIv.visibility = View.VISIBLE
binding.copyLibaoCodeIv.setOnClickListener {
code.copyTextAndToast("$code 复制成功")
}
}
else -> {
binding.libaoCodeTv.text = "礼包码:-"
}
}
}
} else {
if (!UserManager.getInstance().isLoggedIn) {
binding.libaoSchedulePb.visibility = View.VISIBLE
binding.remainingTv.visibility = View.VISIBLE
binding.libaoCodeTv.visibility = View.GONE
binding.copyLibaoCodeIv.visibility = View.GONE
initProgressUI(libaoEntity)
} else {
when (libaoEntity.status) {
"linged", "repeatLing", "repeatLinged", "taoed", "repeatTao", "repeatTaoed" -> {
binding.libaoSchedulePb.visibility = View.GONE
binding.remainingTv.visibility = View.GONE
binding.libaoCodeTv.visibility = View.VISIBLE
val size = libaoEntity.me?.userDataLibaoList?.size ?: 0
val code = libaoEntity.me?.userDataLibaoList?.get(size - 1)?.code ?: ""
val text = "礼包码:$code"
binding.libaoCodeTv.text = SpanBuilder(text).color(
binding.root.context,
4,
text.length,
com.gh.gamecenter.common.R.color.text_theme
).build()
binding.copyLibaoCodeIv.visibility = View.VISIBLE
binding.copyLibaoCodeIv.setOnClickListener {
code.copyTextAndToast("$code 复制成功")
}
}
else -> {
binding.libaoSchedulePb.visibility = View.VISIBLE
binding.remainingTv.visibility = View.VISIBLE
binding.libaoCodeTv.visibility = View.GONE
binding.copyLibaoCodeIv.visibility = View.GONE
initProgressUI(libaoEntity)
}
}
}
}
// LibaoUtils.setLiBaoBtnStatusRound(holder.binding.receiveTv, libaoEntity,true, context)
LibaoUtils.initLibaoBtn(
context,
binding.receiveTv,
libaoEntity,
false,
null,
true,
"游戏详情",
"游戏详情"
) {
adapter.notifyItemChanged(position)
}
if (!libaoEntity.packageName.isNullOrEmpty()) {
binding.receiveTv.setOnClickListener {
val intent = LibaoDetailActivity.getIntent(context, libaoEntity, true, "游戏详情")
if (it.context is Activity) {
(it.context as Activity).startActivityForResult(intent, LIBAO_REQUEST)
}
}
}
itemView.setOnClickListener {
if (isTypeCopy) {
// do nothing
} else {
val intent = LibaoDetailActivity.getIntent(context, libaoEntity, "游戏详情")
if (it.context is Activity) {
(it.context as Activity).startActivityForResult(intent, LIBAO_REQUEST)
}
NewLogUtils.logGameDetailGiftClick(gameName, gameId, "礼包详情")
if (gameDetailModuleTrackData != null && getGameStatus != null) {
SensorsBridge.trackGameDetailModuleClick(
gameDetailModuleTrackData.gameId,
gameDetailModuleTrackData.gameName,
gameDetailModuleTrackData.gameType,
"组件内容",
gameDetailModuleTrackData.moduleType,
gameDetailModuleTrackData.moduleName,
gameDetailModuleTrackData.sequence,
libaoEntity.name,
position + 1,
"libao",
libaoEntity.id,
libaoEntity.name,
getGameStatus()
)
}
}
}
}
private fun initProgressUI(libaoEntity: LibaoEntity) {
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
}
}
binding.remainingTv.text = "剩余${count}%"
binding.libaoSchedulePb.progress = count
}
}
}
class MoreViewHolder(var binding: ItemGameDetailMoreBinding) : RecyclerView.ViewHolder(binding.root)
class FooterViewHolder(var binding: RefreshFooterviewBinding) : RecyclerView.ViewHolder(binding.root)
companion object {
const val MORE = 0
const val LIBAO_ITEM = 1
const val FOOTER_ITEM = 2
const val LIBAO_REQUEST = 100
}
}

View File

@ -0,0 +1,95 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.content.Context
import android.view.View
import androidx.annotation.CallSuper
import androidx.recyclerview.widget.RecyclerView
import com.gh.download.DownloadManager
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.GameDetailData
import com.lightgame.download.DownloadStatus
abstract class BaseGameDetailItemViewHolder(
val itemView: View,
val downloadBtn: DownloadButton?,
val viewModel: GameDetailViewModel
) : RecyclerView.ViewHolder(itemView) {
protected val context: Context = itemView.context
protected var itemData: GameDetailData? = null
protected val gameId
get() = viewModel.game?.id ?: ""
protected val gameName
get() = viewModel.game?.name ?: ""
protected val gameType
get() = viewModel.game?.categoryChinese ?: ""
protected val moduleType
get() = itemData?.typeChinese ?: ""
protected val sequence
get() = itemData?.position?.plus(1) ?: 0
protected val gameStatus
get() = getGameStatus(downloadBtn?.buttonStyle, viewModel)
protected val baseTrackData
get() = GameDetailModuleTrackData(
gameId = gameId,
gameName = gameName,
gameType = gameId,
moduleType = moduleType,
sequence = sequence,
)
@CallSuper
open fun bindView(data: GameDetailData) {
itemData = data
}
data class GameDetailModuleTrackData(
var gameId: String = "",
var gameName: String = "",
var gameType: String = "",
var gameStatus: String = "",
var moduleType: String = "",
var moduleName: String = "",
var sequence: Int = 0,
var subModuleName: String = "",
var supSequence: Int = 0
)
companion object {
fun getGameStatus(buttonStyle: DownloadButton.ButtonStyle?, viewModel: GameDetailViewModel) = when (buttonStyle) {
DownloadButton.ButtonStyle.WAITING,
DownloadButton.ButtonStyle.UPDATING,
DownloadButton.ButtonStyle.PAUSE,
DownloadButton.ButtonStyle.FAILURE,
DownloadButton.ButtonStyle.DOWNLOADING_NORMAL,
DownloadButton.ButtonStyle.DOWNLOADING_PLUGIN -> "下载中"
DownloadButton.ButtonStyle.LAUNCH_OR_OPEN -> "已安装"
DownloadButton.ButtonStyle.RESERVABLE -> "预约"
DownloadButton.ButtonStyle.RESERVED -> "已预约"
else -> determineStatus(viewModel)
}
private fun determineStatus(viewModel: GameDetailViewModel): String {
viewModel.updateGameStatus()
if (viewModel.isGameInstalled) {
return "已安装"
}
val downloadEntity = DownloadManager.getInstance().getDownloadEntitySnapshot("", viewModel.gameId)
return when (downloadEntity?.status) {
DownloadStatus.done -> "待安装"
null,
DownloadStatus.cancel,
DownloadStatus.hijack,
DownloadStatus.notfound,
DownloadStatus.uncertificated,
DownloadStatus.unqualified,
DownloadStatus.unavailable,
DownloadStatus.banned -> "未下载"
else -> "下载中"
}
}
}
}

View File

@ -0,0 +1,91 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.content.Context
import android.graphics.drawable.Animatable
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.updateLayoutParams
import com.facebook.drawee.controller.BaseControllerListener
import com.facebook.imagepipeline.image.ImageInfo
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.databinding.ItemGameDetailImageLinkBinding
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.GameDetailData
import com.gh.gamecenter.gamedetail.entity.GameDetailLink
class GameDetailAdvertisingImageItemViewHolder(
val binding: ItemGameDetailImageLinkBinding,
downloadBtn: DownloadButton?,
viewModel: GameDetailViewModel
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val entity = data.linkAdvertising ?: return
bindImageItem(context, binding, entity, false, "游戏详情-广告推荐", baseTrackData) { gameStatus }
}
companion object {
fun bindImageItem(
context: Context,
binding: ItemGameDetailImageLinkBinding,
data: GameDetailLink,
isAnnouncement: Boolean,
entrance: String,
trackData: GameDetailModuleTrackData,
getGameStatus: () -> String
) {
binding.run {
if (data.img.isNotEmpty()) {
imageIv.updateRoundingParams {
setOverlayColor(com.gh.gamecenter.common.R.color.ui_surface.toColor(context))
}
ImageUtils.displayWithCallback(imageIv, data.img, false, object : BaseControllerListener<ImageInfo>() {
override fun onFinalImageSet(id: String?, imageInfo: ImageInfo?, animatable: Animatable?) {
if (imageInfo == null || imageInfo.height == 0) {
return
}
val scale = imageInfo.width.toFloat() / imageInfo.height.toFloat()
maskView.updateLayoutParams<ConstraintLayout.LayoutParams> {
height = if (scale > 2F) 40F.dip2px() else 60F.dip2px()
}
}
})
}
iconIv.goneIf(data.componentIcon?.icon.isNullOrEmpty()) {
iconIv.setFixedHeight(14)
iconIv.setTag(ImageUtils.TAG_TARGET_WIDTH, DisplayUtils.getScreenWidth() - 32F.dip2px())
ImageUtils.display(iconIv, data.componentIcon?.icon)
}
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_aw_primary.toColor(context))
titleTv.text = if (isAnnouncement) data.text else data.title
linkTv.goneIf(data.text.isEmpty() || isAnnouncement) {
linkTv.text = data.text
linkTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
}
arrowIv.goneIf(data.text.isNotEmpty() || isAnnouncement)
container.setOnClickListener { _ ->
data.link?.let {
DirectUtils.directToLinkPage(context, it, entrance, "")
SensorsBridge.trackGameDetailModuleClick(
trackData.gameId,
trackData.gameName,
trackData.gameType,
"组件内容",
trackData.moduleType,
trackData.moduleName,
trackData.sequence,
trackData.subModuleName,
trackData.supSequence,
it.type,
it.link,
it.link,
getGameStatus()
)
}
}
}
}
}
}

View File

@ -0,0 +1,75 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.content.Context
import androidx.core.view.updateLayoutParams
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.databinding.ItemGameDetailLinkBinding
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.GameDetailData
import com.gh.gamecenter.gamedetail.entity.GameDetailLink
class GameDetailAdvertisingItemViewHolder(
val binding: ItemGameDetailLinkBinding,
downloadBtn: DownloadButton?,
viewModel: GameDetailViewModel
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val entity = data.linkAdvertising ?: return
bindItem(context, binding, entity, false, "游戏详情-广告推荐", baseTrackData) { gameStatus }
}
companion object {
fun bindItem(
context: Context,
binding: ItemGameDetailLinkBinding,
data: GameDetailLink,
isAnnouncement: Boolean,
entrance: String,
trackData: GameDetailModuleTrackData,
getGameStatus: () -> String
) {
binding.run {
container.updateLayoutParams { height = if (isAnnouncement) 28F.dip2px() else 40F.dip2px() }
container.background = if (isAnnouncement) null else R.drawable.bg_shape_ui_container_1_radius_8_item_style.toDrawable(context)
iconIv.goneIf(data.componentIcon?.icon.isNullOrEmpty()) {
iconIv.setFixedHeight(14)
iconIv.setTag(ImageUtils.TAG_TARGET_WIDTH, DisplayUtils.getScreenWidth() - 32F.dip2px())
ImageUtils.display(iconIv, data.componentIcon?.icon)
iconIv.updateRoundingParams {
setOverlayColor(com.gh.gamecenter.common.R.color.ui_container_1.toColor(context))
}
}
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
titleTv.textSize = if (isAnnouncement) 12F else 13F
titleTv.text = if (isAnnouncement) data.text else data.title
container.setOnClickListener { _ ->
data.link?.let {
DirectUtils.directToLinkPage(context, it, entrance, "")
SensorsBridge.trackGameDetailModuleClick(
trackData.gameId,
trackData.gameName,
trackData.gameType,
"组件内容",
trackData.moduleType,
trackData.moduleName,
trackData.sequence,
trackData.subModuleName,
trackData.supSequence,
it.type,
it.link,
it.link,
getGameStatus()
)
}
}
}
}
}
}

View File

@ -0,0 +1,101 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.databinding.ItemGameDetailAnnouncementBinding
import com.gh.gamecenter.databinding.ItemGameDetailImageLinkBinding
import com.gh.gamecenter.databinding.ItemGameDetailLinkBinding
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.GameDetailData
import com.gh.gamecenter.gamedetail.entity.GameDetailLink
class GameDetailAnnouncementItemViewHolder(
val binding: ItemGameDetailAnnouncementBinding,
downloadBtn: DownloadButton?,
viewModel: GameDetailViewModel
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val entity = data.linkAnnouncement ?: return
binding.run {
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
titleTv.text = entity.name
if (recyclerView.adapter !is GameDetailAnnouncementAdapter) {
recyclerView.isNestedScrollingEnabled = false
recyclerView.layoutManager = LinearLayoutManager(context)
recyclerView.adapter = GameDetailAnnouncementAdapter(entity.data)
} else {
recyclerView.adapter?.run { notifyItemRangeChanged(0, itemCount) }
}
}
}
inner class GameDetailAnnouncementAdapter(val dataList: List<GameDetailLink>) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun getItemViewType(position: Int): Int {
val data = dataList[position]
return if (data.img.isEmpty()) ITEM_LINK else ITEM_IMAGE_LINK
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == ITEM_LINK) {
GameDetailAnnouncementItemViewHolder(parent.toBinding())
} else {
GameDetailAnnouncementImageItemViewHolder(parent.toBinding())
}
}
override fun getItemCount(): Int = dataList.size
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val data = dataList.getOrNull(position) ?: return
if (holder is GameDetailAnnouncementItemViewHolder) {
holder.binding.root.setPadding(4F.dip2px(), 0, 4F.dip2px(), 0)
GameDetailAdvertisingItemViewHolder.bindItem(
context,
holder.binding,
data,
true,
"游戏详情-资讯公告",
baseTrackData.apply {
moduleName = itemData?.linkAnnouncement?.name ?: ""
subModuleName = data.text
supSequence = position + 1
}) { gameStatus }
}
if (holder is GameDetailAnnouncementImageItemViewHolder) {
holder.binding.root.setPadding(16F.dip2px(), 0, 16F.dip2px(), if (position == itemCount - 1) 0 else 12F.dip2px())
GameDetailAdvertisingImageItemViewHolder.bindImageItem(
context,
holder.binding,
data,
true,
"游戏详情-资讯公告",
baseTrackData.apply {
moduleName = itemData?.linkAnnouncement?.name ?: ""
subModuleName = data.text
supSequence = position + 1
}
) { gameStatus }
}
}
}
inner class GameDetailAnnouncementItemViewHolder(val binding: ItemGameDetailLinkBinding) :
RecyclerView.ViewHolder(binding.root)
inner class GameDetailAnnouncementImageItemViewHolder(val binding: ItemGameDetailImageLinkBinding) :
RecyclerView.ViewHolder(binding.root)
companion object {
const val ITEM_LINK = 0
const val ITEM_IMAGE_LINK = 1
}
}

View File

@ -0,0 +1,160 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.view.forEach
import androidx.core.view.isEmpty
import androidx.core.view.isVisible
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.ItemGameDetailBriefAwardBinding
import com.gh.gamecenter.databinding.ItemGameDetailBriefBinding
import com.gh.gamecenter.databinding.ItemGameDetailBriefTagBinding
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.dialog.GameDetailScrollableTextDialogFragment
import com.gh.gamecenter.gamedetail.entity.GameBrief
import com.gh.gamecenter.gamedetail.entity.GameDetailData
import com.gh.gamecenter.tag.TagsActivity
import splitties.systemservices.layoutInflater
class GameDetailBriefItemViewHolder(
val binding: ItemGameDetailBriefBinding,
downloadBtn: DownloadButton?,
viewModel: GameDetailViewModel
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val entity = data.linkGameBrief ?: return
binding.run {
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
briefTv.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
divider.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_divider.toColor(context))
expandTv.background = R.drawable.bg_ui_surface_expand_gradient.toDrawable(context)
expandTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
titleTv.text = entity.name
tagScrollView.goneIf(entity.gameTags.isNullOrEmpty()) {
bindGamesTags(entity.gameTags)
}
briefTv.text = TextHelper.getHighlightedSpannableStringThatIsWrappedInsideWrapper(
context,
entity.des,
Constants.DEFAULT_TEXT_WRAPPER_2,
highlightedTextClickListener = TextHelper.CopyToClipboardHighlightedTextClick()
)
briefTv.post {
expandTv.isVisible = briefTv.lineCount == 3 && briefTv.layout.getEllipsisCount(2) > 0
}
expandTv.setOnClickListener {
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
entity.name,
sequence,
gameStatus = gameStatus
)
GameDetailScrollableTextDialogFragment.show(
context,
"游戏信息",
entity.des
)
}
divider.goneIf(entity.awardData.isEmpty())
awardScrollView.goneIf(entity.awardData.isEmpty()) {
bindAwards(entity.awardData)
}
}
}
private fun bindGamesTags(gameTags: List<GameBrief.GameTag>) {
if (binding.tagContainer.isEmpty()) {
gameTags.forEachIndexed { index, gameTag ->
val tagBinding = ItemGameDetailBriefTagBinding.inflate(context.layoutInflater)
tagBinding.root.text = gameTag.name
tagBinding.root.setOnClickListener {
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
itemData?.linkGameBrief?.name,
sequence,
gameStatus = gameStatus
)
context.startActivity(
TagsActivity.getIntent(
context,
gameTag.name,
gameTag.name,
"游戏详情-游戏简介",
""
)
)
}
binding.tagContainer.addView(
tagBinding.root,
LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, 24F.dip2px()).apply {
leftMargin = if (index == 0) 0 else 4F.dip2px()
})
}
} else {
binding.tagContainer.forEach {
(it as? TextView)?.run {
setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
background = com.gh.gamecenter.common.R.drawable.bg_shape_ui_container_1_stroke_radius_6.toDrawable(context)
}
}
}
}
private fun bindAwards(awardList: List<GameBrief.AwardData>) {
if (binding.awardContainer.isEmpty()) {
awardList.forEachIndexed { index, awardData ->
val awardBinding = ItemGameDetailBriefAwardBinding.inflate(context.layoutInflater)
awardBinding.iconIv.display(awardData.icon)
awardBinding.nameTv.text = awardData.award
awardBinding.typeTv.text = awardData.awardType
awardBinding.root.setOnClickListener {
awardData.link?.let { link ->
DirectUtils.directToLinkPage(context, link, "游戏详情-游戏简介", "")
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
itemData?.linkGameBrief?.name,
sequence,
awardData.award,
index + 1,
link.type,
link.link,
link.text,
gameStatus
)
}
}
binding.awardContainer.addView(
awardBinding.root,
LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, 26F.dip2px()).apply {
leftMargin = if (index == 0) 0 else 24F.dip2px()
})
}
} else {
binding.awardContainer.forEach {
it.findViewById<SimpleDraweeView>(R.id.iconIv)?.updateRoundingParams {
setOverlayColor(com.gh.gamecenter.common.R.color.ui_surface.toColor(context))
}
it.findViewById<TextView>(R.id.nameTv)?.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
it.findViewById<TextView>(R.id.typeTv)?.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
}
}
}
}

View File

@ -0,0 +1,402 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.graphics.drawable.ColorDrawable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.widget.LinearLayout
import android.widget.PopupWindow
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.*
import com.gh.common.util.NewLogUtils
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.exposure.ExposureSource
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.SpanBuilder
import com.gh.gamecenter.databinding.ItemGameDetailRecyclerViewBinding
import com.gh.gamecenter.databinding.SubItemGameDetailCommentBinding
import com.gh.gamecenter.entity.RatingComment
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.GameDetailData
import com.gh.gamecenter.gamedetail.entity.GameDetailTabEntity
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 java.util.regex.Pattern
class GameDetailCommentItemViewHolder(
val binding: ItemGameDetailRecyclerViewBinding,
downloadBtn: DownloadButton?,
viewModel: GameDetailViewModel
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val entity = data.linkComment ?: return
binding.run {
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
moreTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
moreTv.setDrawableEnd(R.drawable.ic_auxiliary_arrow_right_text_theme_12.toDrawable(context))
titleTv.text = entity.name
moreTv.isVisible = true
moreTv.text = "更多"
moreTv.setOnClickListener {
viewModel.performTabSelected(GameDetailTabEntity.TYPE_COMMENT)
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"右上角",
moduleType,
entity.name,
sequence,
gameStatus = gameStatus
)
}
if (recyclerView.adapter !is CommentItemAdapter) {
recyclerView.isNestedScrollingEnabled = false
(recyclerView.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
recyclerView.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
recyclerView.adapter = CommentItemAdapter(entity.data)
} else {
recyclerView.adapter?.run { notifyItemRangeChanged(0, itemCount) }
}
}
}
inner class CommentItemAdapter(val dataList: ArrayList<RatingComment>) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val path = "游戏详情-玩家评论"
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
CommentItemViewHolder(parent.toBinding())
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val commentData = dataList.getOrNull(position) ?: return
var isChildLongClick = false
if (holder is CommentItemViewHolder) {
holder.binding.run {
root.background = R.drawable.bg_shape_f8_radius_8.toDrawable(context)
root.updateLayoutParams<MarginLayoutParams> {
leftMargin = if (position == 0) 16F.dip2px() else 8F.dip2px()
rightMargin = if (position == itemCount - 1) 16F.dip2px() else 0
}
userNameTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
tvBadgeName.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
commentTv.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
ipRegionTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
moreIv.setImageResource(R.drawable.game_comment_more)
userIcon.display(commentData.user.border, commentData.user.icon, commentData.user.auth?.icon)
userNameTv.text = commentData.user.name
ratingStar.rating = commentData.star.toFloat()
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
}
commentTv.setExpandCallback {
root.performClick()
}
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)
commentTv.setTextWithHighlightedTextWrappedInsideWrapper(
text = contents,
highlightedTextClickListener = TextHelper.DirectToWebViewHighlightedTextClick(
context,
"游戏详情-玩家评论"
)
)
} else {
commentTv.setTextWithHighlightedTextWrappedInsideWrapper(
text = commentData.content,
highlightedTextClickListener = TextHelper.DirectToWebViewHighlightedTextClick(
context,
"游戏详情-玩家评论"
)
)
}
ipRegionTv.goneIf(!(commentData.source != null && commentData.source.region.isNotEmpty()))
ipRegionTv.text = " · ${commentData.source?.region}"
when {
commentData.isEditContent == null -> {
timeTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
timeTv.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(context, s.length - 10, s.length, com.gh.gamecenter.common.R.color.text_secondary).build()
} else {
NewsUtils.getFormattedTime(commentData.time)
}
}
commentData.isEditContent!! -> {
timeTv.setTextColor(com.gh.gamecenter.common.R.color.text_F56614.toColor(context))
timeTv.text = if (commentData.ignore) {
"${NewsUtils.getFormattedTime(commentData.time)} 保护期间修改评论 >"
} else {
"${NewsUtils.getFormattedTime(commentData.time)} 已修改 >"
}
}
else -> {
timeTv.setTextColor(com.gh.gamecenter.common.R.color.text_F56614.toColor(context))
timeTv.text = if (commentData.ignore) {
"${NewsUtils.getFormattedTime(commentData.time)} 保护期间修改评论"
} else {
"${NewsUtils.getFormattedTime(commentData.time)} 已修改"
}
}
}
sdvUserBadge.setOnClickListener {
DialogUtils.showViewBadgeDialog(context, commentData.user.badge) {
DirectUtils.directToBadgeWall(
context,
commentData.user.id,
commentData.user.name,
commentData.user.icon
)
}
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
itemData?.linkComment?.name,
sequence,
subModuleName = "玩家评论列表",
supSequence = position + 1,
gameStatus = gameStatus
)
}
userIcon.setOnClickListener {
DirectUtils.directToHomeActivity(
context,
commentData.user.id,
viewModel.entrance,
path
)
NewLogUtils.logGameDetailCommentClick(
viewModel.game?.name ?: "",
viewModel.game?.id ?: "",
"个人主页"
)
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
itemData?.linkComment?.name,
sequence,
subModuleName = "玩家评论列表",
supSequence = position + 1,
gameStatus = gameStatus
)
}
userNameTv.setOnClickListener {
userIcon.performClick()
}
tvBadgeName.setOnClickListener { sdvUserBadge.performClick() }
commentTv.setOnLongClickListener(View.OnLongClickListener {
isChildLongClick = true
commentData.content.replace(RatingEditActivity.LABEL_REPLACE_REGEX.toRegex(), "")
.copyTextAndToast()
return@OnLongClickListener true
})
moreIv.setOnClickListener {
showMorePopWindow(it, commentData.user.id == UserManager.getInstance().userId) { text ->
when (text) {
MORE_COPY -> {
commentData.content.replace(RatingEditActivity.LABEL_REPLACE_REGEX.toRegex(), "")
.copyTextAndToast()
}
MORE_MODIFY -> {
val intent =
RatingEditActivity.getPatchIntent(context, viewModel.game!!, commentData)
SyncDataBetweenPageHelper.startActivityForResult(
context,
intent,
RATING_PATCH_REQUEST,
position
)
}
MORE_REPORT -> {
context.ifLogin(BaseActivity.mergeEntranceAndPath(viewModel.entrance, path)) {
DialogUtils.showReportReasonDialog(
context,
Constants.REPORT_LIST.toList() as java.util.ArrayList<String>
) { reason, desc ->
SimpleRequestHelper.reportGameComment(
viewModel.game?.id ?: "",
commentData.id,
if (reason != "其他原因") reason else desc
)
}
}
}
MORE_DELETE -> {
DialogHelper.showDeleteGameCommentDialog(
context,
R.string.delete_game_comment.toResString()
) {
SimpleRequestHelper.deleteGameComment(
viewModel.game?.id ?: "",
commentData.id
) {
// 删除列表中的评论(如果当前列表有的话)
val index = dataList.indexOfFirst { item ->
item.id == commentData.id
}
if (index != -1) {
dataList.removeAt(index)
notifyItemRemoved(index)
}
}
}
}
}
}
}
timeTv.setOnClickListener {
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
itemData?.linkComment?.name,
sequence,
subModuleName = "玩家评论列表",
supSequence = position + 1,
gameStatus = gameStatus
)
if (commentData.isEditContent == null && commentData.ignore) {
DialogUtils.showStopServerExplanationDialog(
context,
if (viewModel.game?.commentDescription?.isNotEmpty() == true)
viewModel.game?.commentDescription else context.getString(R.string.rating_protection),
viewModel.game?.name
?: ""
)
} else if (commentData.isEditContent == true) {
val intent = CommentLogsActivity.getIntent(context, viewModel.game!!.id, commentData.id)
context.startActivity(intent)
}
}
root.setOnClickListener {
if (isChildLongClick) {
isChildLongClick = false
return@setOnClickListener
}
val exposureSource = arrayListOf(
ExposureSource("游戏详情"),
ExposureSource("详情tab"),
ExposureSource("玩家评价"),
).toJson()
val intent = RatingReplyActivity.getIntent(
context = context,
gameId = viewModel.game?.id ?: "",
commentId = commentData.id,
exposureSource = exposureSource,
entrance = viewModel.entrance ?: "",
path = path
)
SyncDataBetweenPageHelper.startActivityForResult(
context,
intent,
RATING_REPLY_REQUEST,
position
)
NewLogUtils.logGameDetailCommentClick(
viewModel.game?.name ?: "",
viewModel.game?.id ?: "",
"评论内容"
)
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
itemData?.linkComment?.name,
sequence,
subModuleName = "玩家评论列表",
supSequence = position + 1,
gameStatus = gameStatus
)
}
}
}
}
private fun showMorePopWindow(v: View, isMyRating: Boolean, clickListener: (String) -> Unit) {
val contentList = if (isMyRating) arrayListOf(MORE_COPY, MORE_MODIFY, MORE_DELETE)
else arrayListOf(MORE_COPY, MORE_REPORT)
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)
}
override fun getItemCount(): Int = dataList.size
}
inner class CommentItemViewHolder(val binding: SubItemGameDetailCommentBinding) :
RecyclerView.ViewHolder(binding.root)
companion object {
const val RATING_REPLY_REQUEST = 233
const val RATING_PATCH_REQUEST = 234
const val MORE_COPY = "复制"
const val MORE_MODIFY = "修改"
const val MORE_DELETE = "删除"
const val MORE_REPORT = "投诉"
}
}

View File

@ -0,0 +1,313 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.graphics.Typeface
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.ExtraTagHandler
import com.gh.gamecenter.databinding.*
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.GameDetailComprehensivePanelItem
import com.gh.gamecenter.gamedetail.entity.GameDetailComprehensivePanelItem.Companion.FUNCTION_TYPE_ONE_LINE_ONE_POINT
import com.gh.gamecenter.gamedetail.entity.GameDetailComprehensivePanelItem.Companion.SHOW_TYPE_PART
import com.gh.gamecenter.gamedetail.entity.GameDetailComprehensivePanelItem.ContentData
import com.gh.gamecenter.gamedetail.entity.GameDetailData
import splitties.views.topPadding
class GameDetailComprehensivePanelItemViewHolder(
val binding: ItemGameDetailComprehensivePanelBinding,
downloadBtn: DownloadButton?,
viewModel: GameDetailViewModel
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val entity = data.linkComprehensive ?: return
val dataList = entity.data ?: return
binding.run {
root.topPadding = if (entity.showName) 0 else 16F.dip2px()
panelRv.background = com.gh.gamecenter.common.R.drawable.bg_shape_f8_radius_8.toDrawable(context)
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
titleTv.goneIf(!entity.showName) {
titleTv.text = entity.name
}
if (panelRv.adapter !is ComprehensivePanelAdapter) {
panelRv.isNestedScrollingEnabled = false
(panelRv.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
panelRv.layoutManager = LinearLayoutManager(context)
panelRv.adapter = ComprehensivePanelAdapter(dataList)
} else {
if (sequence != panelRv.tag) {
(panelRv.adapter as? ComprehensivePanelAdapter)?.dataList = dataList
}
panelRv.adapter?.run { notifyItemRangeChanged(0, itemCount) }
}
panelRv.tag = sequence
}
}
inner class ComprehensivePanelAdapter(var dataList: List<GameDetailComprehensivePanelItem>) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun getItemViewType(position: Int): Int {
val data = dataList[position]
return when (data.type) {
GameDetailComprehensivePanelItem.TYPE_FUNCTION -> ITEM_FUNCTION
GameDetailComprehensivePanelItem.TYPE_FAQ -> ITEM_FAQ
else -> ITEM_DECLARATION
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ITEM_FUNCTION -> ComprehensivePanelFunctionViewHolder(parent.toBinding())
ITEM_FAQ -> ComprehensivePanelFAQViewHolder(parent.toBinding())
else -> ComprehensivePanelDeclarationViewHolder(parent.toBinding())
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val data = dataList.getOrNull(position) ?: return
if (holder is ComprehensivePanelFunctionViewHolder) {
holder.binding.run {
root.updateLayoutParams<MarginLayoutParams> {
topMargin = if (position == 0) 0 else 8F.dip2px()
}
titleTv.goneIf(!data.showTitle) {
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
titleTv.text = data.title
}
val contentData = data.data ?: return
holder.showPart = data.functionType == FUNCTION_TYPE_ONE_LINE_ONE_POINT && data.showType == SHOW_TYPE_PART
expandTv.isVisible = viewModel.comprehensivePanelExpandBtnStatusMap.getOrPut("${sequence}${position}") { holder.showPart && contentData.size > data.showRowNum }
expandTv.text = if (holder.isExpand) "收起" else "全部"
expandTv.setOnClickListener { _ ->
holder.isExpand = !holder.isExpand
(recyclerView.adapter as? ComprehensivePanelFunctionAdapter)?.dataList =
if (holder.showPart && !holder.isExpand) {
contentData.take(data.showRowNum)
} else {
contentData
}
recyclerView.adapter?.notifyDataSetChanged()
expandTv.text = if (holder.isExpand) "收起" else "全部"
}
val spanCount = if (data.functionType == FUNCTION_TYPE_ONE_LINE_ONE_POINT) 1 else 2
val finalContentData = if (holder.showPart && !holder.isExpand) {
contentData.take(data.showRowNum)
} else {
contentData
}
if (recyclerView.adapter !is ComprehensivePanelFunctionAdapter) {
recyclerView.isNestedScrollingEnabled = false
(recyclerView.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
recyclerView.layoutManager = GridLayoutManager(context, spanCount)
recyclerView.adapter = ComprehensivePanelFunctionAdapter(data, finalContentData, holder)
} else {
(recyclerView.layoutManager as? GridLayoutManager)?.spanCount = spanCount
(recyclerView.adapter as? ComprehensivePanelFunctionAdapter)?.run {
dataList = finalContentData
notifyDataSetChanged()
}
}
}
}
if (holder is ComprehensivePanelFAQViewHolder) {
holder.binding.run {
root.updateLayoutParams<MarginLayoutParams> {
topMargin = if (position == 0) 0 else 16F.dip2px()
bottomMargin = if (position == itemCount - 1) 0 else if (data.showTitle) 12F.dip2px() else 8F.dip2px()
}
titleTv.goneIf(!data.showTitle) {
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
titleTv.text = data.title
}
val contentData = data.data ?: return
if (recyclerView.adapter !is ComprehensivePanelFAQAdapter) {
recyclerView.isNestedScrollingEnabled = false
(recyclerView.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
recyclerView.layoutManager = LinearLayoutManager(context)
recyclerView.adapter = ComprehensivePanelFAQAdapter(data.title, contentData)
} else {
(recyclerView.adapter as? ComprehensivePanelFAQAdapter)?.run {
dataList = contentData
notifyDataSetChanged()
}
}
}
}
if (holder is ComprehensivePanelDeclarationViewHolder) {
holder.binding.run {
root.updateLayoutParams<MarginLayoutParams> {
topMargin = if (position == 0) 0 else 12F.dip2px()
}
titleTv.goneIf(!data.showTitle) {
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
titleTv.text = data.title
}
declarationTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
declarationTv.setTextWithInterceptingInternalUrl(
data.declaration.dropFontColorInDarkMode(declarationTv.context)
.fromHtmlCompat(PicassoImageGetter(declarationTv), ExtraTagHandler())
)
}
}
}
override fun getItemCount(): Int = dataList.size
}
inner class ComprehensivePanelFunctionAdapter(
private val gameDetailComprehensivePanelItem: GameDetailComprehensivePanelItem,
var dataList: List<ContentData>,
private val parentViewHolder: ComprehensivePanelFunctionViewHolder
) :
RecyclerView.Adapter<ComprehensivePanelFunctionItemViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ComprehensivePanelFunctionItemViewHolder =
ComprehensivePanelFunctionItemViewHolder(parent.toBinding())
override fun getItemCount(): Int = dataList.size
override fun onBindViewHolder(holder: ComprehensivePanelFunctionItemViewHolder, position: Int) {
val data = dataList.getOrNull(position) ?: return
holder.binding.numberIv.setImageResource(R.drawable.bg_game_detail_comprehensive_panel_function_number)
holder.binding.numberTv.run {
setTextColor(com.gh.gamecenter.common.R.color.text_aw_primary.toColor(context))
setTypeface(Typeface.createFromAsset(context.assets, Constants.DIN_FONT_PATH))
text = (position + 1).toString()
}
holder.binding.contentTv.run {
setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
maxLines = if (parentViewHolder.showPart && !parentViewHolder.isExpand) 1 else Int.MAX_VALUE
text = data.text
post {
val hasEllipsize = layout.getEllipsisCount(0) > 0
if (parentViewHolder.showPart && hasEllipsize && !parentViewHolder.binding.expandTv.isVisible) {
parentViewHolder.binding.expandTv.isVisible = true
viewModel.comprehensivePanelExpandBtnStatusMap["${sequence}${parentViewHolder.bindingAdapterPosition}"] = true
}
// 计算最后一行是否有足够位置放下 全部/收起 按钮
if (position == itemCount - 1) {
val lastLine = layout.lineCount - 1
val lastLineString = text.substring(layout.getLineStart(lastLine), layout.getLineEnd(lastLine))
val distanceToEnd = width - paint.measureText(lastLineString)
val showExpandInTheLastLine = distanceToEnd >= parentViewHolder.binding.expandTv.width
ConstraintSet().apply {
clone(parentViewHolder.binding.root)
clear(parentViewHolder.binding.expandTv.id, ConstraintSet.TOP)
clear(parentViewHolder.binding.expandTv.id, ConstraintSet.BOTTOM)
if (showExpandInTheLastLine) {
connect(parentViewHolder.binding.expandTv.id, ConstraintSet.BOTTOM, parentViewHolder.binding.recyclerView.id, ConstraintSet.BOTTOM)
} else {
connect(parentViewHolder.binding.expandTv.id, ConstraintSet.TOP, parentViewHolder.binding.recyclerView.id, ConstraintSet.BOTTOM)
connect(parentViewHolder.binding.expandTv.id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM)
}
}.applyTo(parentViewHolder.binding.root)
}
}
}
holder.binding.root.setOnClickListener { _ ->
data.link?.let {
DirectUtils.directToLinkPage(context, it, "游戏详情-功能说明", "")
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
"功能说明",
sequence,
gameDetailComprehensivePanelItem.title,
position + 1,
it.type,
it.link,
it.text,
gameStatus
)
}
}
}
}
inner class ComprehensivePanelFAQAdapter(private val subModuleName: String, var dataList: List<ContentData>) :
RecyclerView.Adapter<ComprehensivePanelFAQItemViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ComprehensivePanelFAQItemViewHolder =
ComprehensivePanelFAQItemViewHolder(parent.toBinding())
override fun getItemCount(): Int = dataList.size
override fun onBindViewHolder(holder: ComprehensivePanelFAQItemViewHolder, position: Int) {
val data = dataList.getOrNull(position) ?: return
holder.binding.root.updateLayoutParams<MarginLayoutParams> {
topMargin = if (position == 0) 0 else 8F.dip2px()
}
holder.binding.root.background = R.drawable.bg_shape_ui_surface_radius_4_item_style.toDrawable(context)
holder.binding.contentTv.run {
setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
text = data.text
}
holder.binding.arrowIv.goneIf(data.link == null) {
holder.binding.arrowIv.setImageResource(R.drawable.ic_auxiliary_arrow_right_8)
}
holder.binding.root.setOnClickListener { _ ->
data.link?.let {
DirectUtils.directToLinkPage(context, it, "游戏详情-功能说明", "")
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
"功能说明",
sequence,
subModuleName,
position + 1,
it.type,
it.link,
it.text,
gameStatus
)
}
}
}
}
inner class ComprehensivePanelFunctionViewHolder(val binding: ItemGameDetailComprehensivePanelRvBinding) :
RecyclerView.ViewHolder(binding.root) {
var showPart: Boolean = false
var isExpand = false
get() = viewModel.comprehensivePanelExpandTextStatusMap.getOrPut("${sequence}${bindingAdapterPosition}") { !showPart }
set(value) {
viewModel.comprehensivePanelExpandTextStatusMap["${sequence}${bindingAdapterPosition}"] = value
field = value
}
}
inner class ComprehensivePanelFAQViewHolder(val binding: ItemGameDetailComprehensivePanelRvBinding) :
RecyclerView.ViewHolder(binding.root)
inner class ComprehensivePanelDeclarationViewHolder(val binding: ItemGameDetailComprehensivePanelDeclarationBinding) :
RecyclerView.ViewHolder(binding.root)
inner class ComprehensivePanelFunctionItemViewHolder(val binding: ItemGameDetailComprehensivePanelFunctionBinding) :
RecyclerView.ViewHolder(binding.root)
inner class ComprehensivePanelFAQItemViewHolder(val binding: ItemGameDetailComprehensivePanelFaqBinding) :
RecyclerView.ViewHolder(binding.root)
companion object {
const val ITEM_FUNCTION = 0
const val ITEM_FAQ = 1
const val ITEM_DECLARATION = 2
}
}

View File

@ -0,0 +1,69 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import androidx.lifecycle.LifecycleOwner
import com.gh.gamecenter.databinding.ItemGameDetailContentCardDoubleBinding
import com.gh.gamecenter.databinding.LayoutGameDetailContentCardBinding
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity
import com.gh.gamecenter.gamedetail.entity.GameDetailData
class GameDetailContentCardDoubleItemViewHolder(
val binding: ItemGameDetailContentCardDoubleBinding,
downloadBtn: DownloadButton?,
viewModel: GameDetailViewModel,
private val lifecycleOwner: LifecycleOwner
) :
BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
data.linkContentCard?.let {
val getGameStatus = { gameStatus }
bindContentCard(
lifecycleOwner,
viewModel,
it.first(),
binding.firstCardContainer,
baseTrackData.apply { supSequence = 0 },
getGameStatus
)
bindContentCard(
lifecycleOwner,
viewModel,
it[1],
binding.secondCardContainer,
baseTrackData.apply { supSequence = 1 },
getGameStatus
)
}
}
companion object {
fun bindContentCard(
lifecycleOwner: LifecycleOwner,
viewModel: GameDetailViewModel,
contentCardEntity: ContentCardEntity,
itemBinding: LayoutGameDetailContentCardBinding,
trackData: GameDetailModuleTrackData,
getGameStatus: () -> String
) {
itemBinding.run {
GameDetailContentCardSingleItemViewHolder.bindContentCard(
root.context,
lifecycleOwner,
viewModel,
contentCardEntity,
root,
titleTv,
iconIv,
contentTv,
contentBannerView,
redDotTv,
newIv,
trackData,
getGameStatus
)
}
}
}
}

View File

@ -0,0 +1,281 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.content.Context
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import androidx.lifecycle.LifecycleOwner
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.common.constant.Config
import com.gh.common.util.DirectUtils
import com.gh.common.util.NewFlatLogUtils
import com.gh.gamecenter.ShellActivity
import com.gh.gamecenter.ShellActivity.Type
import com.gh.gamecenter.WebActivity
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.TextBannerView
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.databinding.ItemGameDetailContentCardSingleBinding
import com.gh.gamecenter.feature.entity.MeEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity.Companion.TYPE_ARCHIVE
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity.Companion.TYPE_BBS
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity.Companion.TYPE_GIFT
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity.Companion.TYPE_RELATED_VERSION
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity.Companion.TYPE_SERVER
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity.Companion.TYPE_TOOLKIT
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity.Companion.TYPE_ZONE
import com.gh.gamecenter.gamedetail.entity.GameDetailData
import com.gh.gamecenter.gamedetail.entity.GameDetailTabEntity
import com.gh.gamecenter.gamedetail.fuli.kaifu.ServersCalendarActivity
import com.gh.gamecenter.gamedetail.libao.LibaoListFragment
import com.gh.gamecenter.livedata.Event
import com.gh.gamecenter.newsdetail.NewsDetailActivity
import kotlin.math.abs
class GameDetailContentCardSingleItemViewHolder(
val binding: ItemGameDetailContentCardSingleBinding,
downloadBtn: DownloadButton?,
viewModel: GameDetailViewModel,
private val lifecycleOwner: LifecycleOwner,
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val contentCardEntity = data.linkContentCard?.first() ?: return
binding.run {
bindContentCard(
context,
lifecycleOwner,
viewModel,
contentCardEntity,
container,
titleTv,
iconIv,
contentTv,
contentBannerView,
redDotTv,
newIv,
baseTrackData.apply { supSequence = 0 }
) { gameStatus }
}
}
companion object {
fun bindContentCard(
context: Context,
lifecycleOwner: LifecycleOwner,
viewModel: GameDetailViewModel,
contentCardEntity: ContentCardEntity,
containerView: ConstraintLayout,
titleTv: TextView,
iconIv: SimpleDraweeView,
contentTv: TextView,
contentBannerView: TextBannerView,
redDotTv: TextView,
newIv: ImageView,
trackData: GameDetailModuleTrackData,
getGameStatus: () -> String
) {
if (contentCardEntity.id == containerView.tag) {
if (contentBannerView.isVisible) contentBannerView.updateView()
containerView.background = com.gh.gamecenter.common.R.drawable.bg_shape_f8_radius_8.toDrawable(context)
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
contentTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
return
}
containerView.tag = contentCardEntity.id
titleTv.text = contentCardEntity.title
ImageUtils.display(iconIv, contentCardEntity.icon)
val showContentTv =
contentCardEntity.showDes && (contentCardEntity.des.isNotEmpty() || contentCardEntity.link.type == TYPE_GIFT)
contentTv.visibleIf(showContentTv) {
contentTv.text =
if (contentCardEntity.link.type == TYPE_GIFT && contentCardEntity.des.isEmpty()) "${contentCardEntity.libao?.total}个游戏礼包" else contentCardEntity.des
contentTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
}
val showContentBannerView =
contentCardEntity.showDes && contentCardEntity.link.type == TYPE_SERVER && contentCardEntity.server != null && contentCardEntity.des.isEmpty()
contentBannerView.goneIf(!showContentBannerView) {
val server = contentCardEntity.server ?: return@goneIf
val nowTime = System.currentTimeMillis()
var timeDiff = 0L
var closestIndex = 0
val bannerTextDataList = server.calendar.mapIndexed { index, serverCalendarEntity ->
val diff = abs(nowTime - serverCalendarEntity.getTime() * 1000L)
if (diff < timeDiff || index == 0) {
timeDiff = diff
closestIndex = index
}
val serverTime =
if (TimeUtils.isToday(serverCalendarEntity.getTime()))
serverCalendarEntity.getFormatTime("今天 HH:mm")
else if (TimeUtils.isTomorrow(serverCalendarEntity.getTime()))
serverCalendarEntity.getFormatTime("明天 HH:mm")
else
serverCalendarEntity.getFormatTime("MM-dd HH:mm")
TextBannerView.BannerTextData("${serverTime ?: ""} ${serverCalendarEntity.type}")
}
contentBannerView.setDataList(bannerTextDataList, closestIndex)
contentBannerView.startBannerLoop()
}
redDotTv.goneIf(!((contentCardEntity.link.type == TYPE_SERVER && contentCardEntity.server?.total != 0) || contentCardEntity.link.type == TYPE_GIFT)) {
if ((contentCardEntity.link.type == TYPE_SERVER) && (contentCardEntity.server?.calendar?.isNotEmpty() == true))
redDotTv.text = contentCardEntity.server?.total.toString()
if ((contentCardEntity.link.type == TYPE_GIFT) && (contentCardEntity.libao != null))
redDotTv.text = contentCardEntity.libao?.total.toString()
}
val showNewTag = contentCardEntity.link.type == TYPE_ARCHIVE && contentCardEntity.archive != null && contentCardEntity.showNewTag
newIv.goneIf(!showNewTag)
containerView.setOnClickListener {
NewFlatLogUtils.logGameDetailGameContentCardClick(
contentCardEntity.title ?: "",
viewModel.game?.name ?: "",
viewModel.game?.id ?: "",
contentCardEntity.link.type ?: "",
contentCardEntity.link.link ?: "",
contentCardEntity.link.text ?: ""
)
SensorsBridge.trackGameDetailModuleClick(
gameId = trackData.gameId,
gameName = trackData.gameName,
gameType = trackData.gameType,
contentType = "组件内容",
moduleType = trackData.moduleType,
moduleName = trackData.moduleName,
sequence = trackData.sequence,
linkText = contentCardEntity.link.text ?: "",
linkType = contentCardEntity.link.type ?: "",
linkId = contentCardEntity.id,
gameStatus = getGameStatus()
)
val dialog = contentCardEntity.dialog
if (dialog != null) {// 展示内容卡片提示弹窗
DialogHelper.showDialog(
context = context,
title = dialog.title ?: "",
content = dialog.body ?: "",
confirmText = context.getString(com.gh.gamecenter.common.R.string.confirm),
cancelText = context.getString(com.gh.gamecenter.common.R.string.cancel),
confirmClickCallback = {
jumpToContentCardLink(context, contentCardEntity, viewModel)
}
)
} else {
jumpToContentCardLink(context, contentCardEntity, viewModel)
}
}
viewModel.contentCardClickedLiveData.observe(lifecycleOwner) {
if (it.peekContent() == contentCardEntity.link.type) {
it.getContentWithHandled()?.let { containerView.performClick() }
}
}
}
private fun jumpToContentCardLink(context: Context, contentCardEntity: ContentCardEntity, viewModel: GameDetailViewModel) {
val path = "游戏详情->内容卡片"
when (contentCardEntity.link.type) {
TYPE_GIFT,
TYPE_ARCHIVE -> {
val type = if (contentCardEntity.link.type == TYPE_GIFT) GameDetailTabEntity.TYPE_GIFT else GameDetailTabEntity.TYPE_ARCHIVE
val tabList = viewModel.gameDetailTabListLiveData.value?.data
if (tabList?.find { it.type == type } != null) {
viewModel.performTabSelected(type)
} else if (contentCardEntity.link.type == TYPE_GIFT) {
val bundle = LibaoListFragment.getBundle(viewModel.game, false)
context.startActivity(ShellActivity.getIntent(context, Type.SIMPLE_LIBAO_LIST, bundle))
} else if (contentCardEntity.link.type == TYPE_ARCHIVE) {
DirectUtils.directToCloudArchive(context, viewModel.game?.id ?: "", viewModel.game?.name ?: "", "", "游戏详情页-内容卡片")
}
}
TYPE_SERVER -> {
if (viewModel.game != null && contentCardEntity.server != null) {
context.startActivity(
ServersCalendarActivity.getIntent(
context,
viewModel.game!!, contentCardEntity.server!!,
MeEntity()
)
)
}
}
TYPE_RELATED_VERSION -> {
viewModel.scrollToListPositionLiveData.postValue(Event(LinkEntity(type = GameDetailData.TYPE_RELATED_GAME)))
}
TYPE_ZONE -> {
if (contentCardEntity.zoneTab && viewModel.game?.zone != null && viewModel.game?.zone!!.style == "link") {
context.startActivity(
WebActivity.getIntent(
context,
viewModel.game?.zone!!.link,
true
)
)
}
}
TYPE_BBS -> {
val funcBbs = contentCardEntity.funcBbs
funcBbs?.let {
DirectUtils.directForumDetail(context, it.link, path)
}
}
TYPE_TOOLKIT -> {
if (contentCardEntity.toolkit.isNotEmpty()) {
contentCardEntity.toolkit.safelyGetInRelease(0)?.let {
val url = it.url
if (url != null && url.contains(Config.URL_ARTICLE)) {
val newsId = url.substring(url.lastIndexOf("/") + 1, url.length - 5) // 5: ".html"
val intent = NewsDetailActivity.getIntentById(context, newsId, path)
context.startActivity(intent)
} else {
context.startActivity(
WebActivity.getWebByCollectionTools(
context,
it,
false
)
)
}
}
}
}
else -> DirectUtils.directToLinkPage(
context,
contentCardEntity.link,
viewModel.entrance ?: "",
path,
ExposureEvent.createEvent(
null,
listOf(
ExposureSource("游戏详情", viewModel.game?.id ?: ""),
ExposureSource("内容卡片", contentCardEntity.id)
)
),
"游戏详情页-内容卡片"
)
}
}
}
}

View File

@ -0,0 +1,46 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import androidx.lifecycle.LifecycleOwner
import com.gh.gamecenter.databinding.ItemGameDetailContentCardTripleBinding
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.detail.viewholder.GameDetailContentCardDoubleItemViewHolder.Companion.bindContentCard
import com.gh.gamecenter.gamedetail.entity.GameDetailData
class GameDetailContentCardTripleItemViewHolder(
val binding: ItemGameDetailContentCardTripleBinding,
downloadBtn: DownloadButton?,
viewModel: GameDetailViewModel,
private val lifecycleOwner: LifecycleOwner
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
data.linkContentCard?.let {
val getGameStatus = { gameStatus }
bindContentCard(
lifecycleOwner,
viewModel,
it.first(),
binding.firstCardContainer,
baseTrackData.apply { supSequence = 0 },
getGameStatus
)
bindContentCard(
lifecycleOwner,
viewModel,
it[1].apply { showDes = false },
binding.secondCardContainer,
baseTrackData.apply { supSequence = 1 },
getGameStatus
)
bindContentCard(
lifecycleOwner,
viewModel,
it[2].apply { showDes = false },
binding.thirdCardContainer,
baseTrackData.apply { supSequence = 2 },
getGameStatus
)
}
}
}

View File

@ -0,0 +1,157 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.content.Context
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import androidx.core.view.updateLayoutParams
import androidx.lifecycle.LifecycleOwner
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.entity.PKEntity
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.databinding.ItemPkBinding
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.GameDetailData
class GameDetailContentRecommendItemViewHolder(
val binding: ItemPkBinding,
downloadBtn: DownloadButton?,
viewModel: GameDetailViewModel,
private val lifecycleOwner: LifecycleOwner,
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
private var pkEntity: PKEntity? = null
override fun bindView(data: GameDetailData) {
super.bindView(data)
val link = data.linkContentRecommend ?: return
pkEntity = data.pkData
if (pkEntity == null) {
binding.root.updateLayoutParams<MarginLayoutParams> { height = 0 }
return
}
binding.run {
if (link.type == TYPE_PK) {
if (!pkView.isInitialized()) {
viewModel.pkVoteResultLiveData.observe(lifecycleOwner) {
if (it.peekContent().first == pkEntity?.id) {
val pair = it.getContentWithHandled()
if (pair != null && pkView.isInitialized()) {
pkView.vote(pair.second)
statisticsTv.text = "已有${pkEntity?.totalNum}人参与"
pkView.showResultWithAnimation()
}
}
}
} else {
pkView.updateView()
}
binding.root.updateLayoutParams<MarginLayoutParams> {
height = ViewGroup.LayoutParams.WRAP_CONTENT
setMargins(16F.dip2px(), 16F.dip2px(), 16F.dip2px(), 8F.dip2px())
}
bindPKItem(context, this, pkEntity!!, onPositiveClickAction = positive@{
if (pkEntity == null) return@positive
SensorsBridge.trackPKClick(
GlobalActivityManager.getCurrentPageEntity().pageId,
GlobalActivityManager.getCurrentPageEntity().pageName,
GlobalActivityManager.getCurrentPageEntity().pageBusinessId,
pkEntity!!.id,
pkEntity!!.title,
pkEntity!!.option1.text,
clickSite = "选项"
)
viewModel.postPKVote(pkEntity!!.id, true)
}, onNegativeClickAction = negative@{
if (pkEntity == null) return@negative
SensorsBridge.trackPKClick(
GlobalActivityManager.getCurrentPageEntity().pageId,
GlobalActivityManager.getCurrentPageEntity().pageName,
GlobalActivityManager.getCurrentPageEntity().pageBusinessId,
pkEntity!!.id,
pkEntity!!.title,
pkEntity!!.option2.text,
clickSite = "选项"
)
viewModel.postPKVote(pkEntity!!.id, false)
}) {
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
pkEntity?.title,
sequence,
null,
null,
pkEntity?.link?.type,
pkEntity?.link?.link,
pkEntity?.link?.text,
gameStatus
)
SensorsBridge.trackPKClick(
GlobalActivityManager.getCurrentPageEntity().pageId,
GlobalActivityManager.getCurrentPageEntity().pageName,
GlobalActivityManager.getCurrentPageEntity().pageBusinessId,
pkEntity!!.id,
pkEntity!!.title,
"",
pkEntity!!.link?.type ?: "",
pkEntity!!.link?.link ?: "",
pkEntity!!.link?.text ?: "",
clickSite = "跳转链接"
)
}
}
}
}
companion object {
const val TYPE_PK = "pk"
const val TIME_PATTERN = "yyyy.MM.dd HH:mm:ss"
fun bindPKItem(
context: Context,
binding: ItemPkBinding,
pkEntity: PKEntity,
onPositiveClickAction: () -> Unit,
onNegativeClickAction: () -> Unit,
onLinkClickAction: () -> Unit
) {
binding.run {
root.background = R.drawable.bg_shape_f8_radius_8.toDrawable(context)
pkTitleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
endDateTv.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
statisticsTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
linkTv.background = com.gh.gamecenter.common.R.drawable.button_round_theme_alpha_10.toDrawable(context)
linkTv.setTextColor(com.gh.gamecenter.common.R.color.primary_theme.toColor(context))
pkView.run {
setData(pkEntity)
onPositiveClick = onPositiveClickAction
onNegativeClick = onNegativeClickAction
}
pkTitleTv.text = pkEntity.title
val endDateText = if (pkEntity.isExpired) "" else ""
endDateTv.text = "${endDateText}${TimeUtils.getFormatTime(pkEntity.time.end, TIME_PATTERN)}结束"
statisticsTv.text = "已有${pkEntity.totalNum}人参与"
linkTv.goneIf(pkEntity.link == null) {
linkTv.setOnClickListener { _ ->
pkEntity.link?.let {
DirectUtils.directToLinkPage(
context,
it,
"内容推荐-PK",
""
)
}
onLinkClickAction.invoke()
}
}
}
}
}
}

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