Compare commits

...

80 Commits

Author SHA1 Message Date
d7d91b002e 移除冗余代码 2024-05-27 09:31:05 +08:00
81f96577d0 feat:社区新增关注页面 https://jira.shanqu.cc/browse/GHZSCY-5098 2024-05-27 09:27:20 +08:00
44a2e616ab Merge branch 'feat/log_all_redirected_host' into 'dev'
feat: 游戏下载host上报 https://jira.shanqu.cc/browse/GHZSCY-5476

See merge request halo/android/assistant-android!1680
2024-05-24 15:54:35 +08:00
fffd3ef789 feat: 游戏下载host上报 https://jira.shanqu.cc/browse/GHZSCY-5476 2024-05-24 15:18:22 +08:00
d53782b8de Merge branch 'fix/GHZSCY-5231' into 'dev'
fix:【光环助手】轮播banner刷新时蒙层消失 https://jira.shanqu.cc/browse/GHZSCY-5231

See merge request halo/android/assistant-android!1679
2024-05-22 15:01:47 +08:00
b16c5d723b fix:【光环助手】轮播banner刷新时蒙层消失 https://jira.shanqu.cc/browse/GHZSCY-5231 2024-05-22 14:44:00 +08:00
1dfd636283 Merge branch 'feature-GHZS-5385' into 'dev'
feat: 【光环助手】QQ小游戏支付唤起问题 https://jira.shanqu.cc/browse/GHZSCY-5385

See merge request halo/android/assistant-android!1668
2024-05-22 11:02:38 +08:00
65feed01f4 feat: 【光环助手】QQ小游戏支付唤起问题 https://jira.shanqu.cc/browse/GHZSCY-5385 2024-05-22 11:02:38 +08:00
2a596bd2a0 Merge branch 'fix/vivo_crash' into 'dev'
fix: 修复在 vivo 的 Android 14  设备上点击剪贴板功能时的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/277509

See merge request halo/android/assistant-android!1677
2024-05-21 11:03:28 +08:00
16c4f1016c fix: 修复在 vivo 的 Android 14 设备上点击剪贴板功能时的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/277509 2024-05-20 18:19:23 +08:00
1ced82c1f6 Merge branch 'fix/GHZSCY-5372' into 'dev'
fix:【光环助手】开服表bug https://jira.shanqu.cc/browse/GHZSCY-5372

See merge request halo/android/assistant-android!1676
2024-05-20 11:43:37 +08:00
078c9204af fix:【光环助手】开服表bug https://jira.shanqu.cc/browse/GHZSCY-5372 2024-05-20 11:43:37 +08:00
68818b3409 Merge branch 'feat/GHZSCY-5080' into 'dev'
feat:【光环助手】工具新增跳转类型配置 https://jira.shanqu.cc/browse/GHZSCY-4618

See merge request halo/android/assistant-android!1675
2024-05-17 17:53:13 +08:00
7d4aa34d12 feat:【光环助手】工具新增跳转类型配置 https://jira.shanqu.cc/browse/GHZSCY-4618 2024-05-17 17:53:13 +08:00
2a27e0f467 Merge remote-tracking branch 'origin/release' into dev
# Conflicts:
#	dependencies.gradle
2024-05-16 16:01:28 +08:00
54ee8a9c69 chore: 版本更新至 5.35.5 2024-05-16 11:36:57 +08:00
2b44efd6b3 Merge branch 'hotfix/v5.35.4-1054/wrong_install_status' into 'release'
fix: 修复安装游戏回到光环时的安装状态更新问题

See merge request halo/android/assistant-android!1673
2024-05-16 11:32:46 +08:00
d62c8beb30 fix: 修复安装游戏回到光环时的安装状态更新问题 2024-05-16 11:22:04 +08:00
155ec08280 Merge branch 'fix/image_display_issue' into 'dev'
fix: 修复图片显示问题

See merge request halo/android/assistant-android!1672
2024-05-16 10:54:14 +08:00
d46aa81dbe chore: 版本更新至 5.35.4 2024-05-15 16:30:39 +08:00
51c0bf27cf Merge branch 'hotfix/v5.35.3-1053/wrong_update_status' into 'release'
fix: 修复使用跳转落地页下载安装的游戏更新时下载按钮的更新问题;修复安装同包名同版本不同游戏 id 的游戏时下载按钮的更新问题

See merge request halo/android/assistant-android!1670
2024-05-15 16:29:48 +08:00
43eb4c88c9 fix: 修复普通图片显示的锯齿问题 (还原为加载 View 大小的图片),移除冗余的图片加载高度入参 2024-05-15 16:21:15 +08:00
06b2d2b416 Merge branch 'hotfix/v5.35.3-1053/packagename_null_crash' into 'release'
fix: 修复部分位置跳转安装因为包名为空而产生的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/385367

See merge request halo/android/assistant-android!1671
2024-05-15 16:00:11 +08:00
af7580a6a6 fix: 修复部分位置跳转安装因为包名为空而产生的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/385367 2024-05-15 15:57:37 +08:00
1e4375ec8a fix: 修复使用跳转落地页下载安装的游戏更新时下载按钮的更新问题;修复安装同包名同版本不同游戏 id 的游戏时下载按钮的更新问题 2024-05-15 15:31:41 +08:00
195247a5c3 fix: 修复重登录时,用户头像不能正常显示的问题 2024-05-14 18:10:58 +08:00
dd65bc8bc2 Merge remote-tracking branch 'origin/release' into dev
# Conflicts:
#	dependencies.gradle
2024-05-14 17:38:18 +08:00
196e719358 chore: 版本更新至 5.35.3 2024-05-14 17:13:10 +08:00
b1c267b179 Merge branch 'fix/rank_loading_issue' into 'dev'
fix: 修复进入排行榜页面时全量加载所有页面导致页面卡顿的问题

See merge request halo/android/assistant-android!1669
2024-05-14 16:18:34 +08:00
a2679c8dbd fix: 修复进入排行榜页面时全量加载所有页面导致页面卡顿的问题
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-05-14 16:14:36 +08:00
c9e1816be0 Merge branch 'feat/remove_useless_legacy_code' into 'dev'
feat: 移除部分冗余的历史代码,关闭部分意义不大的日志输出(有需要时再手动开启)

See merge request halo/android/assistant-android!1667
2024-05-14 15:07:00 +08:00
a1f0455d5a feat: 移除部分冗余的历史代码,关闭部分意义不大的日志输出(有需要时再手动开启)
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-05-14 15:02:00 +08:00
5b3f4b6104 Merge branch 'chen/202405/GHZSCY-5378' into 'dev'
fix:【光环助手】游戏专题点击神策埋点问题 https://jira.shanqu.cc/browse/GHZSCY-5378

See merge request halo/android/assistant-android!1666
2024-05-14 11:02:42 +08:00
fe4e9d9d3b fix:【光环助手】游戏专题点击神策埋点问题 https://jira.shanqu.cc/browse/GHZSCY-5378 2024-05-14 10:58:57 +08:00
dd051b4d13 Merge branch 'hotfix/v5.35.2-1052/logout_crash' into 'release'
fix: 修复退出登录出现的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/385028/?project=22

See merge request halo/android/assistant-android!1665
2024-05-14 09:45:26 +08:00
e7f756555c fix: 修复退出登录出现的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/385028/?project=22 2024-05-14 09:39:57 +08:00
9488837e9e Merge remote-tracking branch 'origin/release' into dev
# Conflicts:
#	dependencies.gradle
2024-05-10 14:14:17 +08:00
b8c4f1403b chore: 版本更新至 5.35.2 2024-05-10 11:18:37 +08:00
b88698c2a3 Merge branch 'hotfix/v5.35.1-1051/crashes' into 'release'
fix: 修复 5.35.1 线上闪退问题

See merge request halo/android/assistant-android!1662
2024-05-10 11:17:13 +08:00
c897d5ad0f fix: 修复多线程更新已安装列表时的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/384726 https://sentry.shanqu.cc/organizations/lightgame/issues/384682
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-05-10 10:53:41 +08:00
037f453a75 fix: 修复小米设备安装 APKS 格式 XAPK 包时候的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/384710
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-05-10 10:51:38 +08:00
ad3a3c1341 Merge branch 'hotfix/v5.35.1-1051/hot_launch_splash_ad' into 'release'
fix: 修复移除第三方广告SDK后错误展示热启动广告的问题

See merge request halo/android/assistant-android!1661
2024-05-09 17:45:06 +08:00
ba320f7740 fix: 修复移除第三方广告SDK后错误展示热启动广告的问题 2024-05-09 17:19:33 +08:00
f3dbc0b779 Merge branch 'hotfix/v5.35.1-1051/update_notify_issue' into 'release'
fix: 修复更新应用时遗漏模拟卸载监听导致的状态变更异常问题

See merge request halo/android/assistant-android!1660
2024-05-09 15:40:05 +08:00
0c518ac40e fix: 修复更新应用时遗漏模拟卸载监听导致的状态变更异常问题 2024-05-09 15:37:45 +08:00
bf57118900 Merge branch 'hotfix/v5.35.1-1051/init_crash' into 'release'
fix: 修复部分 downloadEntity versionName 而导致的启动闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/384612

See merge request halo/android/assistant-android!1659
2024-05-09 13:42:04 +08:00
b80a14f2b1 fix: 修复部分 downloadEntity versionName 而导致的启动闪退问题
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-05-09 11:48:43 +08:00
1450064640 chore: 版本更新至 5.35.1 2024-05-09 10:26:59 +08:00
90e19d5099 Merge branch 'feat/GHZSCY-5342' into 'release'
feat: 关于移除监听广播后的相关优化 https://jira.shanqu.cc/browse/GHZSCY-5342

See merge request halo/android/assistant-android!1657
2024-05-09 10:25:59 +08:00
383124dc36 feat: 关于移除监听广播后的相关优化(处理错误注释) https://jira.shanqu.cc/browse/GHZSCY-5342
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-05-09 10:21:28 +08:00
6d6ce2613a feat: 关于移除监听广播后的相关优化 https://jira.shanqu.cc/browse/GHZSCY-5342 2024-05-09 09:21:34 +08:00
8569264b82 feat: 合规调整-已安装列表权限-2024/4/22 https://jira.shanqu.cc/browse/GHZSCY-5250 2024-05-09 09:21:34 +08:00
59667abf09 feat: 合规调整-2024/4/30-后台静默监听行为 https://jira.shanqu.cc/browse/GHZSCY-5331 2024-05-09 09:21:34 +08:00
5201637326 Merge branch 'docs/GHZSCY-5350' into 'dev'
docs: 补充 Activity 类名注释 https://jira.shanqu.cc/browse/GHZSCY-5350

See merge request halo/android/assistant-android!1656
2024-05-08 17:04:41 +08:00
417c41c8a0 docs: 补充 Activity 类名注释 https://jira.shanqu.cc/browse/GHZSCY-5350 2024-05-08 16:59:53 +08:00
b651ef8617 Merge branch 'hotfix/v5.35.0-1050/GHZSCY-5329' into 'release'
fix: 优化 gid 的获取逻辑,避免部分接口请求时因为 gid 为空供导致异常 https://jira.shanqu.cc/browse/GHZSCY-5329

See merge request halo/android/assistant-android!1654
2024-05-08 11:14:46 +08:00
af67894d4e Merge branch 'fix/GHZSCY-5339' into 'dev'
fix: 游戏专题-游戏替换问题 https://jira.shanqu.cc/browse/GHZSCY-5329

See merge request halo/android/assistant-android!1655
2024-05-08 11:10:23 +08:00
76e17eddd7 fix: 优化 gid 的获取逻辑,避免部分接口请求时因为 gid 为空供导致异常 https://jira.shanqu.cc/browse/GHZSCY-5329 2024-05-08 10:03:02 +08:00
f8c9c41eb0 Merge branch 'feat/GHZSCY-5281' into 'dev'
feat: 搜索业务-游戏搜索结果页面补充神策埋点—客户端 https://jira.shanqu.cc/browse/GHZSCY-5281

See merge request halo/android/assistant-android!1653
2024-05-07 18:01:46 +08:00
40ac173389 feat: 搜索业务-游戏搜索结果页面补充神策埋点—客户端 https://jira.shanqu.cc/browse/GHZSCY-5281 2024-05-07 18:01:46 +08:00
c6bc7ca6cc fix: 游戏专题-游戏替换问题 https://jira.shanqu.cc/browse/GHZSCY-5329
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-05-07 11:51:50 +08:00
f01e08aec9 Merge branch 'hotfix/v5.35.0-1050/crashes' into 'release'
fix: 修复线上闪退问题

See merge request halo/android/assistant-android!1652
2024-05-07 11:02:11 +08:00
faddf5d7b6 fix: 收到卸载广播时移除调用 MutableCollections.removeAll() 方法引起的数组越界异常 (MutableCollections.removeAll()线程不安全) https://sentry.shanqu.cc/organizations/lightgame/issues/242447
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-05-07 10:38:55 +08:00
81cf2f0ddc fix: 部分镜像游戏开关切换时由于不同接口更新不及时导致的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/378659
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-05-07 09:56:29 +08:00
198561d15a fix: 收到卸载广播时移除调用 MutableCollections.removeAll() 方法引起的数组越界异常 (MutableCollections.removeAll()线程不安全) https://sentry.shanqu.cc/organizations/lightgame/issues/242447
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-05-07 09:42:44 +08:00
1e721b699c fix: 修复存储空间不足时进行数据库删除操作引起的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/381076
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-05-06 17:58:50 +08:00
8f48dfd347 Merge branch 'fix-community_browsing_duration_error' into 'release'
fix: 使用ViewPager2导致CommunityBrowsingDuration埋点上报异常的问题

See merge request halo/android/assistant-android!1651
2024-04-30 15:04:50 +08:00
bc811a2882 fix: 使用ViewPager2导致CommunityBrowsingDuration埋点上报异常的问题 2024-04-30 14:56:42 +08:00
bdccbc4c28 chore: 版本更新至 5.36.0 2024-04-29 14:51:06 +08:00
bfd986fdfd Merge remote-tracking branch 'origin/release' into dev 2024-04-28 10:59:46 +08:00
a2bd5e01e0 Merge branch 'hotfix/v5.35.0-1050/jpush_clear_badge' into 'release'
fix: 修复极光推送清除角标提前获取信息的问题

See merge request halo/android/assistant-android!1649
2024-04-28 10:43:11 +08:00
844d227f19 fix: 修复极光推送清除角标提前获取信息的问题 2024-04-28 10:26:20 +08:00
8b1f92e9c4 Merge branch 'chen/202404/GHZSCY-5293' into 'dev'
fix:【光环助手】新游开测右上角显示 https://jira.shanqu.cc/browse/GHZSCY-5293

See merge request halo/android/assistant-android!1648
2024-04-28 10:12:25 +08:00
f5834d440b fix:【光环助手】新游开测右上角显示 https://jira.shanqu.cc/browse/GHZSCY-5293 2024-04-28 10:09:20 +08:00
f982bf6478 Merge branch 'hotfix/v5.35.0-1050/disable_jpush_auto_init' into 'release'
fix: 移除极光推送通过 ContentProvider 自动初始化的问题

See merge request halo/android/assistant-android!1647
2024-04-26 14:01:09 +08:00
33d7afec71 fix: 移除极光推送通过 ContentProvider 自动初始化相关代码 2024-04-26 13:40:16 +08:00
37ef50f323 Merge branch 'feat/GHZSCY-5243' into 'dev'
feat: 穿山甲广告SDK更换新版本与信息流广告优化 https://jira.shanqu.cc/browse/GHZSCY-5243

See merge request halo/android/assistant-android!1641
2024-04-26 10:10:33 +08:00
4c6acdee3a Merge branch 'fix/GHZSCY-5261' into 'dev'
fix: 【光环助手】开屏广告神策埋点数据上报问题 https://jira.shanqu.cc/browse/GHZSCY-5261

See merge request halo/android/assistant-android!1646
2024-04-25 10:36:50 +08:00
6beb060e63 fix: 【光环助手】开屏广告神策埋点数据上报问题 https://jira.shanqu.cc/browse/GHZSCY-5261 2024-04-25 10:31:22 +08:00
d11ccba0b7 feat: 穿山甲广告SDK更换新版本与信息流广告优化 https://jira.shanqu.cc/browse/GHZSCY-5243
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-04-22 16:29:42 +08:00
340 changed files with 12523 additions and 3170 deletions

View File

@ -401,7 +401,7 @@ dependencies {
implementation(project(':feature:pkg'))
implementation(project(':feature:oaid'))
implementation(project(':feature:floating-window'))
implementation(project(':feature:csj_ad'))
// implementation(project(':feature:csj_ad'))
// implementation(project(':feature:beizi_startup_ad'))
implementation(project(':feature:xapk-installer'))
implementation(project(':feature:qq_game')) {
@ -409,12 +409,13 @@ dependencies {
}
internalImplementation(project(':module_internal_test'))
// 根据BUILD_PUSH_TYPE决定使用哪个推送SDK目前默认使用阿里云推送
def pushProject = findProperty('BUILD_PUSH_TYPE') == 'jg'
? project(':feature:jg_push') : project(':feature:acloud_push')
implementation(pushProject) {
exclude group: 'androidx.swiperefreshlayout'
}
// def pushProperty = findProperty('BUILD_PUSH_TYPE')
// // 根据BUILD_PUSH_TYPE决定使用哪个推送SDK目前默认使用极光推送
// def pushProject = (pushProperty == null || pushProperty == 'jg')
// ? project(':feature:jg_push') : project(':feature:acloud_push')
// implementation(pushProject) {
// exclude group: 'androidx.swiperefreshlayout'
// }
}
File propFile = file('sign.properties')

View File

@ -770,8 +770,19 @@
android:name="com.gh.gamecenter.wrapper.ToolbarWrapperActivity"
android:screenOrientation="portrait" />
<activity android:name=".forum.home.CommunityActivity"
android:screenOrientation="portrait"/>
<activity
android:name=".forum.home.CommunityActivity"
android:screenOrientation="portrait" />
<activity
android:name=".forum.home.follow.FollowDynamicActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.Transparent" />
<activity
android:name=".forum.home.follow.AllFollowedActivity"
android:screenOrientation="portrait"
android:theme="@style/AppCompatTheme.APP" />
<!-- <activity-->
<!-- android:name="${applicationId}.douyinapi.DouYinEntryActivity"-->

View File

@ -191,10 +191,11 @@ object AdDelegateHelper {
}
/**
* 热启动是否需要显示开屏广告
* 热启动是否需要显示开屏广告(目前只展示第三方广告)
*/
private fun shouldShowStartUpAdWhenHotLaunch() =
mSplashAd?.displayRule?.hotStartSplashAd?.type == AD_TYPE_SDK && mSplashAd?.hotStartThirdPartyAd != null
private fun shouldShowStartUpAdWhenHotLaunch() = (mCsjAdImpl != null || mBeiziAdImpl != null)
&& mSplashAd?.displayRule?.hotStartSplashAd?.type == AD_TYPE_SDK
&& mSplashAd?.hotStartThirdPartyAd != null
/**
* 是否需要显示下载管理广告

View File

@ -45,6 +45,14 @@ class GlobalActivityLifecycleObserver : Application.ActivityLifecycleCallbacks {
}
isFromBackgroundToForeground = false
}
if (activityCount == 1) {
// 清除桌面角标
if (activity !is SplashScreenActivity && activity !is AuthorizationActivity) {
val pushProvider = ARouter.getInstance().build(RouteConsts.provider.push).navigation() as? IPushProvider
pushProvider?.cleanBadgeNumber(activity.applicationContext)
}
}
}
override fun onActivityResumed(activity: Activity) {
@ -84,10 +92,6 @@ class GlobalActivityLifecycleObserver : Application.ActivityLifecycleCallbacks {
}
XapkInstaller.updateCurrentInstallStatus()
// 清除桌面角标
val pushProvider = ARouter.getInstance().build(RouteConsts.provider.push).navigation() as? IPushProvider
pushProvider?.cleanBadgeNumber(activity.applicationContext)
}
override fun onActivityPaused(activity: Activity) {

View File

@ -1,6 +1,7 @@
package com.gh.common.constant;
import android.annotation.SuppressLint;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.os.Build;
import android.preference.PreferenceManager;
@ -28,6 +29,7 @@ import com.gh.gamecenter.entity.VSetting;
import com.gh.gamecenter.feature.entity.SettingsEntity;
import com.gh.gamecenter.feature.entity.SimulatorEntity;
import com.gh.gamecenter.feature.utils.ContentBlockedHelper;
import com.gh.gamecenter.receiver.PackageChangeBroadcastReceiver;
import com.gh.gamecenter.retrofit.RetrofitManager;
import com.gh.vspace.VHelper;
import com.halo.assistant.HaloApp;
@ -94,8 +96,7 @@ public class Config {
getPreferences().edit().putString(SETTINGS_KEY, GsonUtils.toJson(settingsEntity)).apply();
mSettingsEntity = settingsEntity;
// 加载完设置后刷新下
PackageHelper.initList();
PackageHelper.refreshList();
}
@Nullable
@ -332,8 +333,31 @@ public class Config {
if (mNewApiSettingsEntity.getGameShieldContents() != null) {
ContentBlockedHelper.INSTANCE.init(mNewApiSettingsEntity.getGameShieldContents());
}
// 更新安装列表是否开启的配置
// if (mNewApiSettingsEntity.getInstalledComplianceSwitch() != null) {
// PackageHelper.INSTANCE.updateIsGetInstalledPackagesApiAgreedRequired(mNewApiSettingsEntity.getInstalledComplianceSwitch());
// } else {
// PackageHelper.INSTANCE.updateIsGetInstalledPackagesApiAgreedRequired(false);
// }
// 更新包名监听是否开启
if (mNewApiSettingsEntity.isPackageObserveEnable()) {
observePackageChange(mNewApiSettingsEntity.getPackageObserveActions());
}
}
});
}
}
public static void observePackageChange(NewApiSettingsEntity.PackageObserveActions packageObserveActions) {
PackageChangeBroadcastReceiver receiver = new PackageChangeBroadcastReceiver(packageObserveActions);
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(packageObserveActions.getAdd());
intentFilter.addAction(packageObserveActions.getRem());
intentFilter.addAction(packageObserveActions.getRep());
intentFilter.addDataScheme("package");
HaloApp.getInstance().registerReceiver(receiver, intentFilter);
}
}

View File

@ -19,6 +19,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.DirectUtils
import com.gh.common.util.LogUtils
import com.gh.common.util.PackageHelper
import com.gh.common.util.PackageUtils
import com.gh.download.DownloadManager
import com.gh.gamecenter.R
@ -59,7 +60,7 @@ class PackageCheckDialogFragment : BaseDialogFragment() {
private val mDuration = 3000
private var mDisposable: Disposable? = null
private var mAdapter: PackageCheckAdapter? = null
private var mAllInstalledPackages = PackageUtils.getInstalledPackages(HaloApp.getInstance().application, 0)
private var mAllInstalledPackages = PackageHelper.getInstalledPackages(HaloApp.getInstance().application, 0)
var gameEntity: GameEntity? = null
var callBack: ConfirmListener? = null
@ -325,7 +326,7 @@ class PackageCheckDialogFragment : BaseDialogFragment() {
override fun onResume() {
super.onResume()
mAllInstalledPackages = PackageUtils.getInstalledPackages(HaloApp.getInstance().application, 0)
mAllInstalledPackages = PackageHelper.getInstalledPackages(HaloApp.getInstance().application, 0)
gameEntity?.packageDialog?.let {
if (isAllPackageInstalled(mAllInstalledPackages, it)) {
callBack?.onConfirm()
@ -363,7 +364,7 @@ class PackageCheckDialogFragment : BaseDialogFragment() {
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(busFour: EBPackage) {
if (busFour.isInstalledOrUninstalled()) {
mAllInstalledPackages = PackageUtils.getInstalledPackages(HaloApp.getInstance().application, 0)
mAllInstalledPackages = PackageHelper.getInstalledPackages(HaloApp.getInstance().application, 0)
mAdapter?.notifyDataSetChanged()
}
}
@ -416,7 +417,7 @@ class PackageCheckDialogFragment : BaseDialogFragment() {
return
}
val allInstalledPackages = PackageUtils.getInstalledPackages(HaloApp.getInstance().application, 0)
val allInstalledPackages = PackageHelper.getInstalledPackages(HaloApp.getInstance().application, 0)
if (isAllPackageInstalled(allInstalledPackages, packageDialogEntity)) {
callBack.onConfirm()
return

View File

@ -9,10 +9,12 @@ import androidx.fragment.app.FragmentActivity
import com.gh.common.iinterface.ISuperiorChain
import com.gh.common.util.CheckLoginUtils
import com.gh.common.util.PackageUtils
import com.gh.gamecenter.SplashAdActivity
import com.gh.gamecenter.SplashScreenActivity
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.entity.SimpleGameEntity
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.entity.DialogEntity
import com.gh.gamecenter.feature.entity.WelcomeDialogEntity
@ -42,12 +44,13 @@ object GlobalPriorityChainHelper : ISuperiorChain {
return activity is FragmentActivity
&& !activity.isFinishing
&& activity !is SplashScreenActivity
&& activity !is SplashAdActivity
}
/**
* 预启动所有的优先级弹窗管理链
*/
fun preStart() {
fun preStart(withSpecialDelay: Boolean) {
val launchRedirectHandler = LaunchRedirectHandler(-101)
val updateDialogHandler = UpdateDialogHandler(-100)
val privacyPolicyDialogHandler = PrivacyPolicyDialogHandler(-99)
@ -64,8 +67,13 @@ object GlobalPriorityChainHelper : ISuperiorChain {
launchRedirectHandler.doPreProcess()
updateDialogHandler.doPreProcess()
requestOpeningDialogData(welcomeDialogHandler, privacyPolicyDialogHandler)
requestReserveDialogData(reserveDialogHandler)
// 首次启动延迟 300ms保证请求首次启动时已经获取到了 GID 、 OAID 等标记
val requestDelay = if (withSpecialDelay) 300L else 0L
AppExecutor.uiExecutor.executeWithDelay({
requestOpeningDialogData(welcomeDialogHandler, privacyPolicyDialogHandler)
requestReserveDialogData(reserveDialogHandler)
}, requestDelay)
}
/**
@ -123,6 +131,17 @@ object GlobalPriorityChainHelper : ISuperiorChain {
mainChain.resume()
}
/**
* 添加新的 handler 到优先级弹窗管理链 (插队!)
*/
fun queueNewHandler(handler: PriorityChainHandler) {
if (mainChain.isHandlerQueueEmpty()) {
observeLifecycle()
}
mainChain.addHandler(handler)
}
/**
* 请求首页启动弹窗相关的数据并执行相关 handler 的 preProcess
*/

View File

@ -0,0 +1,28 @@
package com.gh.common.prioritychain
import androidx.fragment.app.FragmentActivity
import com.gh.common.util.PackageHelper
import com.gh.gamecenter.common.base.GlobalActivityManager
class RequestInstalledListPermissionHandler : PriorityChainHandler(-1000) {
init {
updateStatus(STATUS_VALID)
}
override fun onProcess(): Boolean {
val currentActivity = GlobalActivityManager.currentActivity ?: return false
if (currentActivity !is FragmentActivity) return false
PackageHelper.showGetInstallAppsListDialogAndRequestPermissionIfNeeded(
activity = currentActivity,
ignorePermanentlyDenied = true
) {
processNext()
}
return true
}
}

View File

@ -32,5 +32,6 @@ class BuildConfigImpl : IBuildConfigProvider {
override fun getVApiHost(): String = BuildConfig.VAPI_HOST
override fun getVDevApiHost(): String = BuildConfig.DEV_VAPI_HOST
override fun getLogProducerProject(): String = BuildConfig.LOG_HUB_PROJECT
}

View File

@ -1,11 +1,23 @@
package com.gh.common.provider
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.viewbinding.ViewBinding
import com.alibaba.android.arouter.facade.annotation.Route
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.common.util.ConcernContentUtils
import com.gh.common.view.ImageContainerView
import com.gh.gamecenter.ImageViewerActivity.Companion.getIntent
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.utils.toArrayList
import com.gh.gamecenter.core.provider.IConcernContentUtilsProvider
import com.gh.gamecenter.databinding.RecyclerGameArticleBinding
import com.gh.gamecenter.message.R
@Route(path = RouteConsts.provider.concernContentUtils, name = "ConcernContentUtils暴露服务")
class ConcernContentUtilsProviderImpl : IConcernContentUtilsProvider {
@ -19,6 +31,71 @@ class ConcernContentUtilsProviderImpl : IConcernContentUtilsProvider {
ConcernContentUtils.addContentPic(context, linearLayout, list, entrance, width)
}
override fun createArticleBinding(parent: ViewGroup): ViewBinding {
val inflater = LayoutInflater.from(parent.context)
return RecyclerGameArticleBinding.inflate(inflater, parent, false)
}
override fun initArticleStyle(binding: ViewBinding) {
if (binding is RecyclerGameArticleBinding) {
val context = binding.root.context
binding.root
.setBackground(ContextCompat.getDrawable(context, R.drawable.reuse_listview_item_style))
binding.tvGameName.setTextColor(ContextCompat.getColor(context, R.color.text_primary))
binding.tvTime.setTextColor(ContextCompat.getColor(context, R.color.text_tertiary))
binding.tvTitle.setTextColor(ContextCompat.getColor(context, R.color.text_primary))
binding.tvDescription.setTextColor(ContextCompat.getColor(context, R.color.text_secondary))
binding.tvComment.setTextColor(ContextCompat.getColor(context, R.color.text_tertiary))
binding.tvShare.setTextColor(ContextCompat.getColor(context, R.color.text_tertiary))
}
}
override fun getIvIcon(binding: ViewBinding): View =
(binding as RecyclerGameArticleBinding).ivIcon
override fun getTvGameName(binding: ViewBinding): TextView =
(binding as RecyclerGameArticleBinding).tvGameName
override fun getTvTime(binding: ViewBinding): TextView = (binding as RecyclerGameArticleBinding).tvTime
override fun getTvTitle(binding: ViewBinding): TextView = (binding as RecyclerGameArticleBinding).tvTitle
override fun getTvDescription(binding: ViewBinding): TextView =
(binding as RecyclerGameArticleBinding).tvDescription
override fun bindImgs(binding: ViewBinding, img: List<String>) {
if (binding is RecyclerGameArticleBinding) {
val images = img.map {
ImageContainerView.ImageContainerData.ImageInfo(it, 3, 2)
}
val imageContainerData =
ImageContainerView.ImageContainerData("", false, images, show = images.isNotEmpty())
binding.ivImagesContainer.bindData(imageContainerData,
object : ImageContainerView.OnImageContainerEventListener {
override fun onImageClick(
images: List<String>,
position: Int,
imageViewList: ArrayList<SimpleDraweeView>
) {
val checkIntent = getIntent(
binding.root.context,
images.toArrayList(),
position,
imageViewList,
""
)
binding.root.context.startActivity(checkIntent)
}
override fun onVideoCLick(videoId: String) = Unit
})
}
}
override fun getTvComment(binding: ViewBinding): TextView = (binding as RecyclerGameArticleBinding).tvComment
override fun getTvShare(binding: ViewBinding): TextView = (binding as RecyclerGameArticleBinding).tvShare
override fun init(context: Context?) {
// Do nothing

View File

@ -3,6 +3,7 @@ package com.gh.common.provider
import android.content.Context
import android.content.pm.PackageInfo
import com.alibaba.android.arouter.facade.annotation.Route
import com.gh.common.util.PackageHelper
import com.gh.common.util.PackageUtils
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.core.provider.IPackageUtilsProvider
@ -23,7 +24,7 @@ class PackageUtilsProviderImpl : IPackageUtilsProvider {
}
override fun getInstalledPackages(context: Context, flag: Int): List<PackageInfo> {
return PackageUtils.getInstalledPackages(context, flag)
return PackageHelper.getInstalledPackages(context, flag)
}
override fun getApkSignatureByPackageName(context: Context, packageName: String): Array<String> {
@ -38,6 +39,10 @@ class PackageUtilsProviderImpl : IPackageUtilsProvider {
return PackageUtils.isSignedByGh(context, packageName)
}
override fun isInstalledWithLauncherIcon(context: Context, packageName: String): Boolean {
return PackageUtils.isInstalled(context, packageName)
}
override fun getInstalledTime(context: Context, packageName: String): Long {
return PackageUtils.getInstalledTime(context, packageName)
}

View File

@ -166,162 +166,160 @@ class SimulatorDownloadManager private constructor() {
this.gameName = gameName
this.gameType = gameCategoryChinese
PermissionHelper.checkGetInstalledAppsListBeforeAction(context, object : EmptyCallback {
override fun onCallback() {
val isInstalledNewSimulator =
SimulatorGameManager.isNewSimulatorInstalled(HaloApp.getInstance().application)
//当没有安装新版本模拟器时候 判断是否隐藏
if (simulator?.active == false && !isInstalledNewSimulator) {
showNoneEmulatorDialog(context)
return
}
var isInstalled = PackageUtils.isInstalledFromAllPackage(
context,
simulator?.apk?.packageName
)
//模拟器管理界面还是用之前的逻辑
if (isInstalledNewSimulator && location != SimulatorLocation.SIMULATOR_MANAGE) {
isInstalled = isInstalledNewSimulator
}
// val versionFromInstalledApp = PackageUtils.getVersionNameByPackageName(simulator?.apk?.packageName)
val shouldShowUpdate =
PackageUtils.isInstalledApkMatchedMd5(simulator?.apk?.packageName, simulator?.apk?.md5)
val showAlertTag = SPUtils.getString(SimulatorGameManager.SIMULATOR_UPDATE_SHOW_ALERT_TAG, "") //当天是否弹过
val todayIsShow = showAlertTag == TimeUtils.getToday()
downloadType = if (shouldShowUpdate && isInstalled) "update" else "download"
if (downloadType == "update" && todayIsShow && location != SimulatorLocation.SIMULATOR_MANAGE) {
return
}
if (downloadType == "download" && isInstalled) {
return
}
val title = if (shouldShowUpdate && isInstalled) "更新模拟器" else "安装模拟器"
val message =
if (shouldShowUpdate && isInstalled) "检测到模拟器存在更高版本,是否前往更新" else "模拟器游戏需要先下载安装对应的模拟器,才可以运行"
val positiveText =
if (shouldShowUpdate && isInstalled) "更新(${simulator?.apk?.size}" else "下载(${simulator?.apk?.size}"
val negativeText = if (shouldShowUpdate && isInstalled) "下次再说" else "取消"
val trackableEntity = TrackableEntity(
"模拟器下载",
key = if (shouldShowUpdate && isInstalled) "更新弹窗" else "下载弹窗",
logShowEvent = true
)
if (shouldShowUpdate && isInstalled) {
NewFlatLogUtils.logSimulatorUpdateAlertShow()
}
if (shouldShowUpdate && isInstalled) {
SensorsBridge.trackSimulatorUpdateDialogShow(
gameId = gameId,
gameName = gameName,
gameType = gameCategoryChinese,
lastPageId = GlobalActivityManager.getLastPageEntity().pageId,
lastPageName = GlobalActivityManager.getLastPageEntity().pageName,
lastPageBusinessId = GlobalActivityManager.getLastPageEntity().pageBusinessId
)
} else {
SensorsBridge.trackSimulatorInstallDialogShow(
gameId = gameId,
gameName = gameName,
gameType = gameCategoryChinese,
lastPageId = GlobalActivityManager.getLastPageEntity().pageId,
lastPageName = GlobalActivityManager.getLastPageEntity().pageName,
lastPageBusinessId = GlobalActivityManager.getLastPageEntity().pageBusinessId
)
}
DialogHelper.showDialog(
context,
title,
message,
positiveText,
negativeText,
trackMtaEvent = true,
cancelClickCallback = {
if (shouldShowUpdate && isInstalled) {
cancelCallback?.invoke()
NewFlatLogUtils.logSimulatorUpdateAlertClick("取消")
MtaHelper.onEvent(trackableEntity.event, trackableEntity.key, "点击下次再说")
SensorsBridge.trackSimulatorUpdateDialogClick(
buttonName = negativeText,
gameId = gameId,
gameName = gameName,
gameType = gameCategoryChinese,
lastPageId = GlobalActivityManager.getLastPageEntity().pageId,
lastPageName = GlobalActivityManager.getLastPageEntity().pageName,
lastPageBusinessId = GlobalActivityManager.getLastPageEntity().pageBusinessId
)
} else {
SensorsBridge.trackSimulatorInstallDialogClick(
buttonName = negativeText,
gameId = gameId,
gameName = gameName,
gameType = gameCategoryChinese,
lastPageId = GlobalActivityManager.getLastPageEntity().pageId,
lastPageName = GlobalActivityManager.getLastPageEntity().pageName,
lastPageBusinessId = GlobalActivityManager.getLastPageEntity().pageBusinessId
)
}
},
confirmClickCallback = {
showDownloadingDialog(context, simulator, gameId, gameName, gameCategoryChinese)
NewFlatLogUtils.logSimulatorUpdateAlertClick("更新")
MtaHelper.onEvent(
trackableEntity.event,
trackableEntity.key,
if (shouldShowUpdate && isInstalled) "点击更新" else "点击下载"
)
if (shouldShowUpdate && isInstalled) {
SensorsBridge.trackSimulatorUpdateDialogClick(
buttonName = positiveText,
gameId = gameId,
gameName = gameName,
gameType = gameCategoryChinese,
lastPageId = GlobalActivityManager.getLastPageEntity().pageId,
lastPageName = GlobalActivityManager.getLastPageEntity().pageName,
lastPageBusinessId = GlobalActivityManager.getLastPageEntity().pageBusinessId
)
} else {
SensorsBridge.trackSimulatorInstallDialogClick(
buttonName = positiveText,
gameId = gameId,
gameName = gameName,
gameType = gameCategoryChinese,
lastPageId = GlobalActivityManager.getLastPageEntity().pageId,
lastPageName = GlobalActivityManager.getLastPageEntity().pageName,
lastPageBusinessId = GlobalActivityManager.getLastPageEntity().pageBusinessId
)
}
},
touchOutsideCallback = {
if (shouldShowUpdate && isInstalled) {
SensorsBridge.trackSimulatorUpdateDialogClick(
buttonName = "关闭弹窗",
gameId = gameId,
gameName = gameName,
gameType = gameCategoryChinese,
lastPageId = GlobalActivityManager.getLastPageEntity().pageId,
lastPageName = GlobalActivityManager.getLastPageEntity().pageName,
lastPageBusinessId = GlobalActivityManager.getLastPageEntity().pageBusinessId
)
} else {
SensorsBridge.trackSimulatorInstallDialogClick(
buttonName = "关闭弹窗",
gameId = gameId,
gameName = gameName,
gameType = gameCategoryChinese,
lastPageId = GlobalActivityManager.getLastPageEntity().pageId,
lastPageName = GlobalActivityManager.getLastPageEntity().pageName,
lastPageBusinessId = GlobalActivityManager.getLastPageEntity().pageBusinessId
)
}
},
mtaEvent = trackableEntity.event, mtaKey = trackableEntity.key,
extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)
)
if (downloadType == "update" && location != SimulatorLocation.SIMULATOR_MANAGE) {
SPUtils.setString(SimulatorGameManager.SIMULATOR_UPDATE_SHOW_ALERT_TAG, TimeUtils.getToday())
}
PermissionHelper.checkGetInstalledAppsListBeforeAction(context) { _ ->
val isInstalledNewSimulator =
SimulatorGameManager.isNewSimulatorInstalled(HaloApp.getInstance().application)
//当没有安装新版本模拟器时候 判断是否隐藏
if (simulator?.active == false && !isInstalledNewSimulator) {
showNoneEmulatorDialog(context)
return@checkGetInstalledAppsListBeforeAction
}
})
var isInstalled = PackageUtils.isInstalledFromAllPackage(
context,
simulator?.apk?.packageName
)
//模拟器管理界面还是用之前的逻辑
if (isInstalledNewSimulator && location != SimulatorLocation.SIMULATOR_MANAGE) {
isInstalled = isInstalledNewSimulator
}
// val versionFromInstalledApp = PackageUtils.getVersionNameByPackageName(simulator?.apk?.packageName)
val shouldShowUpdate =
PackageUtils.isInstalledApkMatchedMd5(simulator?.apk?.packageName, simulator?.apk?.md5)
val showAlertTag = SPUtils.getString(SimulatorGameManager.SIMULATOR_UPDATE_SHOW_ALERT_TAG, "") //当天是否弹过
val todayIsShow = showAlertTag == TimeUtils.getToday()
downloadType = if (shouldShowUpdate && isInstalled) "update" else "download"
if (downloadType == "update" && todayIsShow && location != SimulatorLocation.SIMULATOR_MANAGE) {
return@checkGetInstalledAppsListBeforeAction
}
if (downloadType == "download" && isInstalled) {
return@checkGetInstalledAppsListBeforeAction
}
val title = if (shouldShowUpdate && isInstalled) "更新模拟器" else "安装模拟器"
val message =
if (shouldShowUpdate && isInstalled) "检测到模拟器存在更高版本,是否前往更新" else "模拟器游戏需要先下载安装对应的模拟器,才可以运行"
val positiveText =
if (shouldShowUpdate && isInstalled) "更新(${simulator?.apk?.size}" else "下载(${simulator?.apk?.size}"
val negativeText = if (shouldShowUpdate && isInstalled) "下次再说" else "取消"
val trackableEntity = TrackableEntity(
"模拟器下载",
key = if (shouldShowUpdate && isInstalled) "更新弹窗" else "下载弹窗",
logShowEvent = true
)
if (shouldShowUpdate && isInstalled) {
NewFlatLogUtils.logSimulatorUpdateAlertShow()
}
if (shouldShowUpdate && isInstalled) {
SensorsBridge.trackSimulatorUpdateDialogShow(
gameId = gameId,
gameName = gameName,
gameType = gameCategoryChinese,
lastPageId = GlobalActivityManager.getLastPageEntity().pageId,
lastPageName = GlobalActivityManager.getLastPageEntity().pageName,
lastPageBusinessId = GlobalActivityManager.getLastPageEntity().pageBusinessId
)
} else {
SensorsBridge.trackSimulatorInstallDialogShow(
gameId = gameId,
gameName = gameName,
gameType = gameCategoryChinese,
lastPageId = GlobalActivityManager.getLastPageEntity().pageId,
lastPageName = GlobalActivityManager.getLastPageEntity().pageName,
lastPageBusinessId = GlobalActivityManager.getLastPageEntity().pageBusinessId
)
}
DialogHelper.showDialog(
context,
title,
message,
positiveText,
negativeText,
trackMtaEvent = true,
cancelClickCallback = {
if (shouldShowUpdate && isInstalled) {
cancelCallback?.invoke()
NewFlatLogUtils.logSimulatorUpdateAlertClick("取消")
MtaHelper.onEvent(trackableEntity.event, trackableEntity.key, "点击下次再说")
SensorsBridge.trackSimulatorUpdateDialogClick(
buttonName = negativeText,
gameId = gameId,
gameName = gameName,
gameType = gameCategoryChinese,
lastPageId = GlobalActivityManager.getLastPageEntity().pageId,
lastPageName = GlobalActivityManager.getLastPageEntity().pageName,
lastPageBusinessId = GlobalActivityManager.getLastPageEntity().pageBusinessId
)
} else {
SensorsBridge.trackSimulatorInstallDialogClick(
buttonName = negativeText,
gameId = gameId,
gameName = gameName,
gameType = gameCategoryChinese,
lastPageId = GlobalActivityManager.getLastPageEntity().pageId,
lastPageName = GlobalActivityManager.getLastPageEntity().pageName,
lastPageBusinessId = GlobalActivityManager.getLastPageEntity().pageBusinessId
)
}
},
confirmClickCallback = {
showDownloadingDialog(context, simulator, gameId, gameName, gameCategoryChinese)
NewFlatLogUtils.logSimulatorUpdateAlertClick("更新")
MtaHelper.onEvent(
trackableEntity.event,
trackableEntity.key,
if (shouldShowUpdate && isInstalled) "点击更新" else "点击下载"
)
if (shouldShowUpdate && isInstalled) {
SensorsBridge.trackSimulatorUpdateDialogClick(
buttonName = positiveText,
gameId = gameId,
gameName = gameName,
gameType = gameCategoryChinese,
lastPageId = GlobalActivityManager.getLastPageEntity().pageId,
lastPageName = GlobalActivityManager.getLastPageEntity().pageName,
lastPageBusinessId = GlobalActivityManager.getLastPageEntity().pageBusinessId
)
} else {
SensorsBridge.trackSimulatorInstallDialogClick(
buttonName = positiveText,
gameId = gameId,
gameName = gameName,
gameType = gameCategoryChinese,
lastPageId = GlobalActivityManager.getLastPageEntity().pageId,
lastPageName = GlobalActivityManager.getLastPageEntity().pageName,
lastPageBusinessId = GlobalActivityManager.getLastPageEntity().pageBusinessId
)
}
},
touchOutsideCallback = {
if (shouldShowUpdate && isInstalled) {
SensorsBridge.trackSimulatorUpdateDialogClick(
buttonName = "关闭弹窗",
gameId = gameId,
gameName = gameName,
gameType = gameCategoryChinese,
lastPageId = GlobalActivityManager.getLastPageEntity().pageId,
lastPageName = GlobalActivityManager.getLastPageEntity().pageName,
lastPageBusinessId = GlobalActivityManager.getLastPageEntity().pageBusinessId
)
} else {
SensorsBridge.trackSimulatorInstallDialogClick(
buttonName = "关闭弹窗",
gameId = gameId,
gameName = gameName,
gameType = gameCategoryChinese,
lastPageId = GlobalActivityManager.getLastPageEntity().pageId,
lastPageName = GlobalActivityManager.getLastPageEntity().pageName,
lastPageBusinessId = GlobalActivityManager.getLastPageEntity().pageBusinessId
)
}
},
mtaEvent = trackableEntity.event, mtaKey = trackableEntity.key,
extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)
)
if (downloadType == "update" && location != SimulatorLocation.SIMULATOR_MANAGE) {
SPUtils.setString(SimulatorGameManager.SIMULATOR_UPDATE_SHOW_ALERT_TAG, TimeUtils.getToday())
}
}
}
fun showDownloadingDialog(

View File

@ -7,8 +7,8 @@ import android.widget.LinearLayout;
import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.view.SimpleDraweeView;
import com.gh.gamecenter.ImageViewerActivity;
import com.gh.gamecenter.core.utils.DisplayUtils;
import com.gh.gamecenter.common.utils.ImageUtils;
import com.gh.gamecenter.core.utils.DisplayUtils;
import java.util.ArrayList;
import java.util.List;
@ -121,5 +121,4 @@ public class ConcernContentUtils {
}
return imageView;
}
}

View File

@ -1,17 +1,14 @@
package com.gh.common.util;
import android.content.Context;
import android.os.Build;
import com.gh.gamecenter.common.constant.Constants;
import com.gh.gamecenter.common.constant.EntranceConsts;
import com.gh.gamecenter.common.exposure.meta.MetaUtil;
import com.gh.gamecenter.common.utils.NetworkUtils;
import com.gh.gamecenter.feature.entity.GameEntity;
import com.gh.gamecenter.entity.NewsDetailEntity;
import com.gh.gamecenter.feature.utils.PlatformUtils;
import com.gh.gamecenter.manager.DataCollectionManager;
import com.gh.gamecenter.manager.PackagesManager;
import com.lightgame.download.DownloadEntity;
import java.util.HashMap;
@ -32,7 +29,7 @@ public class DataCollectionUtils {
map.put("type", android.os.Build.MODEL);
map.put("system", android.os.Build.VERSION.SDK_INT + "=" + android.os.Build.VERSION.RELEASE);
// WIFI实时
DataCollectionManager.onEvent(context, "error", map, NetworkUtils.isWifiConnected(context));
DataCollectionManager.onEvent(context, "error", map, true);
}
// 上传下载数据(开始、完成)

View File

@ -14,12 +14,10 @@ import com.gh.gamecenter.BuildConfig;
import com.gh.gamecenter.common.base.GlobalActivityManager;
import com.gh.gamecenter.common.base.activity.BaseActivity;
import com.gh.gamecenter.common.constant.Constants;
import com.gh.gamecenter.common.eventbus.EBReuse;
import com.gh.gamecenter.common.exposure.meta.MetaUtil;
import com.gh.gamecenter.common.retrofit.BiResponse;
import com.gh.gamecenter.common.utils.ExtensionsKt;
import com.gh.gamecenter.core.AppExecutor;
import com.gh.gamecenter.core.utils.GsonUtils;
import com.gh.gamecenter.core.utils.MtaHelper;
import com.gh.gamecenter.core.utils.SPUtils;
import com.gh.gamecenter.core.utils.SentryHelper;
import com.gh.gamecenter.login.entity.IdCardEntity;
@ -32,8 +30,6 @@ import com.halo.assistant.HaloApp;
import com.lightgame.config.CommonDebug;
import com.lightgame.utils.Utils;
import org.greenrobot.eventbus.EventBus;
import io.reactivex.schedulers.Schedulers;
import io.sentry.Sentry;
import io.sentry.android.core.SentryAndroid;
@ -108,45 +104,51 @@ public class DataUtils {
}
public static void getGid() {
GidHelper.getInstance().registerDevice(HaloApp.getInstance().getApplication(), new GidCallback() {
@Override
public void onSuccess(String gid) {
Utils.log("Gid", gid);
PreferenceManager.getDefaultSharedPreferences(HaloApp.getInstance().getApplication()).edit().putString(Constants.DEVICE_KEY, gid).apply();
// 默认用 APP 级已存储的 GID 来使用,不使用外部 GID
String savedGid = SPUtils.getString(Constants.GID);
if (!TextUtils.isEmpty(savedGid)) {
HaloApp.getInstance().setGid(savedGid);
onGidReceived(savedGid);
} else {
GidHelper.getInstance().registerDevice(HaloApp.getInstance().getApplication(), new GidCallback() {
@Override
public void onSuccess(String gid) {
Utils.log("Gid", gid);
PreferenceManager.getDefaultSharedPreferences(HaloApp.getInstance().getApplication()).edit().putString(Constants.DEVICE_KEY, gid).apply();
// 默认用 APP 级已存储的 GID 来使用,不使用外部 GID
String savedGid = SPUtils.getString(Constants.GID);
if (!TextUtils.isEmpty(savedGid)) {
gid = savedGid;
} else {
SPUtils.setString(Constants.GID, gid);
onGidReceived(gid);
}
HaloApp.getInstance().setGid(gid);
// 更新广告配置
AdDelegateHelper.INSTANCE.requestAdConfig(false, "", null);
getDeviceCertification(gid);
// 避免初始化顺序问题导致 MetaUtil 一直持有空的 gid
MetaUtil.INSTANCE.refreshMeta();
ContentValues values = new ContentValues();
values.put(GhContentProvider.KEY_GID, gid);
values.put(GhContentProvider.KEY_ANDROID_ID, MetaUtil.getBase64EncodedAndroidId());
try {
HaloApp.getInstance().getContentResolver().insert(Uri.parse("content://com.gh.gamecenter.provider/device"), values);
} catch (Exception exception) {
SentryHelper.INSTANCE.onEvent("DEVICE_INSERT_ERROR", "exception_digest", exception.getLocalizedMessage());
exception.printStackTrace();
@Override
public void onFailure(String s) {
// 更新广告配置
AdDelegateHelper.INSTANCE.requestAdConfig(false, "", null);
}
}
});
}
}
@Override
public void onFailure(String s) {
// 更新广告配置
AdDelegateHelper.INSTANCE.requestAdConfig(false, "", null);
private static void onGidReceived(String gid) {
HaloApp.getInstance().setGid(gid);
// 更新广告配置
AdDelegateHelper.INSTANCE.requestAdConfig(false, "", null);
getDeviceCertification(gid);
// 避免初始化顺序问题导致 MetaUtil 一直持有空的 gid
MetaUtil.INSTANCE.refreshMeta();
AppExecutor.getIoExecutor().execute(() -> {
ContentValues values = new ContentValues();
values.put(GhContentProvider.KEY_GID, gid);
values.put(GhContentProvider.KEY_ANDROID_ID, MetaUtil.getBase64EncodedAndroidId());
try {
HaloApp.getInstance().getContentResolver().insert(Uri.parse("content://com.gh.gamecenter.provider/device"), values);
} catch (Exception exception) {
SentryHelper.INSTANCE.onEvent("DEVICE_INSERT_ERROR", "exception_digest", exception.getLocalizedMessage());
exception.printStackTrace();
}
});
}

View File

@ -1988,7 +1988,7 @@ object DirectUtils {
val qGameProvider = ARouter
.getInstance()
.build(RouteConsts.provider.qGame)
.navigation() as IQGameProvider<*>
.navigation() as IQGameProvider
qGameProvider.setLoginInfo(activity, userId, userName, userToken)
qGameProvider.launchGame(activity, qqGameId) { _, _ ->
RetrofitManager

View File

@ -50,7 +50,7 @@ public class InstallUtils {
public void handleMessage(Message msg) {
if (msg.what == INSTALL_WHAT && packageManager != null) {
ArrayList<String> list = new ArrayList<>();
List<PackageInfo> packageInfos = PackageUtils.getInstalledPackages(context, 0);
List<PackageInfo> packageInfos = PackageHelper.INSTANCE.getInstalledPackages(context, 0);
for (PackageInfo packageInfo : packageInfos) {
if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
list.add(packageInfo.packageName);

View File

@ -768,7 +768,7 @@ public class LibaoUtils {
}
public static boolean isAppInstalled(Context context, String packageName) {
List<PackageInfo> pinfo = PackageUtils.getInstalledPackages(context, 0);
List<PackageInfo> pinfo = PackageHelper.INSTANCE.getInstalledPackages(context, 0);
if (pinfo != null) {
for (int i = 0; i < pinfo.size(); i++) {
String pn = pinfo.get(i).packageName;

View File

@ -0,0 +1,161 @@
package com.gh.common.util
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.gh.download.DownloadManager
import com.gh.download.PackageObserver
import com.gh.gamecenter.common.utils.NewFlatLogUtils
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.manager.PackagesManager
import com.gh.gamecenter.packagehelper.PackageRepository
import com.lightgame.utils.Utils
import org.greenrobot.eventbus.EventBus
object PackageChangeHelper : DefaultLifecycleObserver {
private const val TAG = "PackageChangeHelper"
private const val INSTALL_PENDING = 1
private const val UNINSTALL_PENDING = 2
private const val UPDATE_PENDING = 3
// <包名pending 类型,应用版本> Triple
private var pendingPackagePair: Triple<String, Int, String>? = null
private var pendingGhId: String ? = null
/**
* 添加一个等待中,待确定是否已成功安装的应用
*/
fun addInstallPendingPackage(packageName: String) {
val installData = PackagesManager.getInstalledData(packageName)
if (installData == null) {
Utils.log(TAG, "添加了: $packageName 包名等待安装成功")
pendingPackagePair = Triple(packageName, INSTALL_PENDING, "")
} else {
Utils.log(TAG, "添加了: $packageName 包名等待安装更新成功")
val ghId = PackageUtils.getGhId(packageName)
// 记录光环插件相关信息,用于安装成功后的处理
if (ghId != null) {
pendingGhId = ghId.toString()
}
pendingPackagePair = Triple(packageName, UPDATE_PENDING, installData.version)
}
}
/**
* 添加一个等待中,待确定是否已成功卸载的应用
*/
fun addUninstallPendingPackage(packageName: String) {
Utils.log(TAG, "添加了: $packageName 包名等待卸载成功")
pendingPackagePair = Triple(packageName, UNINSTALL_PENDING, "")
}
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
if (pendingPackagePair != null) {
val packageName = pendingPackagePair?.first ?: return
val isInstallPending = pendingPackagePair?.second == INSTALL_PENDING
val isUninstallPending = pendingPackagePair?.second == UNINSTALL_PENDING
val isUpdatePending = pendingPackagePair?.second == UPDATE_PENDING
val pendingVersion = pendingPackagePair?.third ?: ""
val installedVersionName = PackageUtils.getVersionNameByPackageName(packageName)
val isInstalled = installedVersionName != null
if (isInstallPending && isInstalled) {
pendingPackagePair = null
pendingGhId = null
PackageRepository.addInstalledGame(packageName)
performInstallSuccessAction(packageName)
} else if (isUninstallPending && !isInstalled) {
pendingPackagePair = null
pendingGhId = null
performUninstallSuccessAction(packageName)
} else if (isUpdatePending) {
val isUpdateValid = if (installedVersionName != pendingVersion) {
true
} else {
!pendingGhId.isNullOrEmpty() && pendingGhId != PackageUtils.getGhId(packageName).toString()
}
pendingPackagePair = null
pendingGhId = null
if (isUpdateValid) {
performUninstallSuccessAction(packageName)
performInstallSuccessAction(packageName)
}
}
}
}
fun addInstall(packageName: String) {
performInstallSuccessAction(packageName)
}
fun addUpdate(packageName: String) {
performUninstallSuccessAction(packageName)
performInstallSuccessAction(packageName)
}
/**
* 对应包名安装成功后的操作,继承至 PackageChangeBroadcastObserver
*/
private fun performInstallSuccessAction(packageName: String, withLog: Boolean = true) {
Utils.log(TAG, "安装了: $packageName 包名的程序")
val downloadEntity = DownloadManager.getInstance().getDownloadEntitySnapshotByPackageName(packageName)
val gameId = if (downloadEntity != null && downloadEntity.gameId != null) downloadEntity.gameId else ""
val gameName = if (downloadEntity != null && downloadEntity.name != null) downloadEntity.name else ""
if (withLog) {
NewFlatLogUtils.logGameInstallComplete(gameId, gameName)
SensorsBridge.trackInstallGameFinish(gameId, gameName)
}
InstallUtils.getInstance().removeInstall(packageName)
PackageHelper.refreshLocalPackageList()
val versionName = PackageUtils.getVersionNameByPackageName(packageName)
val installEb = EBPackage(EBPackage.TYPE_INSTALLED, packageName, versionName)
PackageObserver.onPackageChanged(installEb)
EventBus.getDefault().post(installEb)
}
fun addUninstall(packageName: String) {
performUninstallSuccessAction(packageName)
}
/**
* 对应包名卸载成功后的操作,继承至 PackageChangeBroadcastObserver
*/
private fun performUninstallSuccessAction(packageName: String, withLog: Boolean = true) {
Utils.log(TAG, "卸载了: $packageName 包名的程序")
val install = PackagesManager.getInstalledData(packageName)
val gameId = if (install?.id != null) install.id else ""
val gameName = if (install?.name != null) install.name else ""
if (withLog) {
NewFlatLogUtils.logGameUninstallComplete(gameId!!, gameName!!)
SensorsBridge.trackUnloadGameFinish(gameId, gameName)
}
InstallUtils.getInstance().removeUninstall(packageName)
PackageHelper.refreshLocalPackageList()
val uninstallEb = EBPackage(EBPackage.TYPE_UNINSTALLED, packageName, "")
PackageObserver.onPackageChanged(uninstallEb)
EventBus.getDefault().post(uninstallEb)
}
}

View File

@ -1,24 +1,81 @@
package com.gh.common.util
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.content.pm.PackageManager
import android.content.pm.PermissionInfo
import android.os.Build
import android.provider.Settings
import androidx.fragment.app.FragmentActivity
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.gh.common.constant.Config
import com.gh.common.prioritychain.GlobalPriorityChainHelper
import com.gh.common.prioritychain.RequestInstalledListPermissionHandler
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.DialogHelper
import com.gh.gamecenter.common.utils.PermissionHelper
import com.gh.gamecenter.common.utils.PermissionHelper.isGetInstalledListPermissionDisabled
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.runOnUiThread
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.entity.WhitePackageListEntity
import com.gh.gamecenter.feature.entity.SettingsEntity
import com.gh.gamecenter.manager.PackagesManager
import com.gh.gamecenter.packagehelper.PackageRepository
import com.gh.gamecenter.retrofit.RetrofitManager
import com.halo.assistant.HaloApp
import com.lightgame.utils.Utils
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.*
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
import kotlin.collections.ArrayList
import kotlin.collections.HashSet
object PackageHelper {
private const val TAG = "PackageHelper"
private const val SP_GET_INSTALLED_API_AGREED = "get_installed_api_agreed"
private const val UNKNOWN = -1
private const val UNSUPPORTED = 0
private const val SUPPORTED = 1
private const val ENABLED = 2
private const val DISABLED = 3
private var lastInstalledPackageListTime = 0L
private var cachedInstalledPackagesList: List<PackageInfo> = arrayListOf()
private var isGetInstalledPackagesApiAgreed = true // 用户是否已经同意使用已安装应用列表 API
private var isGetInstalledPackagesApiAgreedRequired = DISABLED // 需要用户手动授权才获取已安装应用列表的功能的开关
private var isGetInstalledPackagesPermissionSupported = UNKNOWN // 设备是否支持禁用获取已安装应用列表
// 评论黑名单包名列表,避免用户安装了 Xposed Installer 这样的工具,也能在包含该安装包的游戏详情页评论
var commentPackageNameBlackList = arrayListOf<String>()
private var _commentPackageNameBlackList = arrayListOf<String>()
val commentPackageNameBlackList: ArrayList<String> = _commentPackageNameBlackList
// 关闭下载的包列表
var downloadPackageNameBlackList = arrayListOf<String>()
private var _downloadPackageNameBlackList = arrayListOf<String>()
val downloadPackageNameBlackList: ArrayList<String> = _downloadPackageNameBlackList
// 本地已安装的包去掉关闭下载的包后的列表
var validLocalPackageNameSet = hashSetOf<String>()
private var _validLocalPackageNameSet = hashSetOf<String>()
val validLocalPackageNameSet: HashSet<String> = _validLocalPackageNameSet
// 游戏包名匹配列表
var relatedPackageList = arrayListOf<SettingsEntity.GameWithPackages>()
private var _relatedPackageList = arrayListOf<SettingsEntity.GameWithPackages>()
val relatedPackageList: ArrayList<SettingsEntity.GameWithPackages> = _relatedPackageList
// 接口控制的已安装应用列表获取开关状态 (UI 显示)
private var _installedPackageApiSwitchStatusLiveData = MutableLiveData<Boolean>()
val installedPackageApiSwitchStatusLiveData: LiveData<Boolean> = _installedPackageApiSwitchStatusLiveData
// 本地已安装包的列表
var localPackageNameSet = hashSetOf<String>()
@ -31,6 +88,22 @@ object PackageHelper {
}
}
/**
* 获取已安装的白名单列表(为了在没有已安装应用列表获取能力的时候也能正常判断更新、插件化)
*/
@SuppressLint("CheckResult")
fun getInstalledWhiteList() {
RetrofitManager.getInstance().newApi.installWhitelist
.subscribeOn(Schedulers.io())
.subscribe(object : BiResponse<WhitePackageListEntity>() {
override fun onSuccess(data: WhitePackageListEntity) {
data.data?.let {
addInstalledButMissingPackages(it)
}
}
})
}
@JvmStatic
fun refreshLocalPackageList() {
localPackageNameSet = getAllPackageName(HaloApp.getInstance().application)
@ -38,33 +111,26 @@ object PackageHelper {
}
@JvmStatic
fun initList() {
Config.getSettings()?.gameCommentBlackList?.let {
commentPackageNameBlackList = ArrayList(it)
}
Config.getSettings()?.gameDownloadBlackList?.let {
downloadPackageNameBlackList = ArrayList(it)
}
Config.getSettings()?.gamePackageMatch?.let {
relatedPackageList = ArrayList(it)
}
fun refreshList() {
Config.getSettings()?.gameCommentBlackList?.let { _commentPackageNameBlackList = ArrayList(it) }
Config.getSettings()?.gameDownloadBlackList?.let { _downloadPackageNameBlackList = ArrayList(it) }
Config.getSettings()?.gamePackageMatch?.let { _relatedPackageList = ArrayList(it) }
Config.getSettings()?.gameDownloadBlackList
updateValidPackageNameList()
}
private fun updateValidPackageNameList() {
validLocalPackageNameSet =
_validLocalPackageNameSet =
localPackageNameSet.filterNot { p -> downloadPackageNameBlackList.contains(p) }.toHashSet()
}
/*
/**
* 获取所有已安装的软件的包名、版本(非系统应用)
*/
private fun getAllPackageName(context: Context): HashSet<String> {
val set = HashSet<String>()
return try {
val packageInfos = PackageUtils.getInstalledPackages(context, 0)
val packageInfos = getInstalledPackages(context, 0)
for (packageInfo in packageInfos) {
if (packageInfo.applicationInfo.flags and ApplicationInfo.FLAG_SYSTEM == 0) {
if (context.packageName != packageInfo.packageName) {
@ -79,4 +145,357 @@ object PackageHelper {
}
}
/**
* 弃用已安装列表缓存
*/
fun dumpInstalledListCache() {
lastInstalledPackageListTime = 0
}
/**
* 在超时后,若后台没有开启获取已安装应用列表的功能,默认以接口不控制的方式获取已安装应用列表
*/
fun fallbackInstalledPackageApiSwitchAfterTimeout(timeout: Long) {
CoroutineScope(SupervisorJob()).launch {
delay(timeout)
if (isGetInstalledPackagesApiAgreedRequired == UNKNOWN) {
Utils.log(TAG, "后台没有开启获取已安装应用列表的功能,超时后默认以接口不控制的方式获取已安装应用列表")
updateIsGetInstalledPackagesApiAgreedRequired(false)
}
}
}
/**
* 更新已安装应用列表获取开关状态
*/
fun updateIsGetInstalledPackagesApiAgreedRequired(isEnabled: Boolean) {
// 若状态不为 unknown 或者用户已经同意使用了,无需再更新
if (isGetInstalledPackagesApiAgreedRequired != UNKNOWN || isGetInstalledPackagesApiAgreed()) {
Utils.log(TAG, "installedPackageApiSwitchStatus 不为 UNKNOWN无需再更新")
return
}
if (isEnabled) {
getInstalledWhiteList()
_installedPackageApiSwitchStatusLiveData.postValue(true)
isGetInstalledPackagesApiAgreedRequired = ENABLED
} else {
isGetInstalledPackagesApiAgreedRequired = DISABLED
if (isSupportGetInstalledAppsPermission(HaloApp.getInstance())) {
GlobalPriorityChainHelper.queueNewHandler(RequestInstalledListPermissionHandler())
} else {
agreeOnGetInstalledPackagesApi()
}
}
}
/**
* 用户是否已经允许了调用获取已安装应用列表接口
* 优先用内存的值,没有再从 SP 中获取并更新
*/
fun isGetInstalledPackagesApiAgreed(): Boolean {
return isGetInstalledPackagesApiAgreed
|| (SPUtils.getBoolean(SP_GET_INSTALLED_API_AGREED).also { isGetInstalledPackagesApiAgreed = it })
}
fun isGetInstalledPackagesApiAgreedRequired(): Boolean {
return isGetInstalledPackagesApiAgreedRequired == ENABLED
}
/**
* 同意使用已安装应用列表 API
*/
private fun agreeOnGetInstalledPackagesApi() {
isGetInstalledPackagesApiAgreed = true
SPUtils.setBoolean(SP_GET_INSTALLED_API_AGREED, true)
_installedPackageApiSwitchStatusLiveData.postValue(false)
}
/**
* 获取已安装应用列表
*/
fun getInstalledPackages(context: Context?, flags: Int): List<PackageInfo> {
Utils.log(TAG, "即将获取已安装应用列表")
// 用户未同意使用已安装应用列表 API返回空列表
if (!isGetInstalledPackagesApiAgreed()) {
Utils.log(TAG, "用户未同意使用已安装应用列表 API返回空列表")
return cachedInstalledPackagesList
}
// 简单 debounce 过于频繁的获取已安装应用列表调用
if (System.currentTimeMillis() - lastInstalledPackageListTime < 3000 && cachedInstalledPackagesList.isNotEmpty()) {
Utils.log(TAG, "使用了缓存的已安装应用列表")
return cachedInstalledPackagesList
}
var shouldGetNewInstalledPackagedList = false
// 当前设备是否支持限制获取已安装应用列表的功能
if (isSupportGetInstalledAppsPermission(context!!)) {
Utils.log(TAG, "当前设备支持限制获取已安装应用列表的功能")
// 当前设备是否支持禁用了获取已安装应用列表
if (!isGetInstalledListPermissionDisabled(context)) {
Utils.log(TAG, "当前设备没有限制获取已安装应用列表的功能")
shouldGetNewInstalledPackagedList = true
} else {
Utils.log(TAG, "当前设备已限制获取已安装应用列表的功能")
}
} else {
Utils.log(TAG, "当前设备不支持限制获取已安装应用列表的功能")
shouldGetNewInstalledPackagedList = true
}
if (shouldGetNewInstalledPackagedList) {
lastInstalledPackageListTime = System.currentTimeMillis()
cachedInstalledPackagesList = getInstalledPackagesInternal(context, flags)
}
return cachedInstalledPackagesList
}
/**
* 显示获取已安装应用列表的对话框并请求权限
*/
fun showGetInstallAppsListDialogAndRequestPermissionIfNeeded(
activity: FragmentActivity,
ignorePermanentlyDenied: Boolean = false,
resultClosure: (Boolean) -> Unit
) {
val globalOnPermissionGrantedClosure = {
agreeOnGetInstalledPackagesApi()
// 进行包名初始化相关的操作
PackageRepository.initData()
refreshLocalPackageList()
refreshList()
}
if (isSupportGetInstalledAppsPermission(activity)) {
// 若系统已经授予了获取应用列表的权限,直接进行授权成功回调
if (!isGetInstalledListPermissionDisabled(activity)) {
globalOnPermissionGrantedClosure.invoke()
resultClosure.invoke(true)
return
}
PermissionHelper.showGetInstalledAppsListPermissionDialog(
activity = activity,
requestPermission = true,
ignorePermanentlyDenied = ignorePermanentlyDenied
) { isGranted ->
if (isGranted) {
SensorsBridge.trackInstalledListPermissionsResult("成功")
globalOnPermissionGrantedClosure.invoke()
resultClosure.invoke(true)
trackInstalledListAfterDelay()
} else {
resultClosure.invoke(false)
SensorsBridge.trackInstalledListPermissionsResult("拒绝")
}
}
} else {
val hintDialog = PermissionHelper.showGetInstalledAppsListPermissionDialog(
activity = activity,
requestPermission = false,
) {
// do nothing
}
SensorsBridge.trackInstalledListPermissionsCustomDialogShow()
val noticeDialog = DialogHelper.showGuideDialog(
context = activity,
title = "权限申请",
content = "是否允许“光环助手”获取已安装的应用信息",
confirmText = "开启",
cancelText = "拒绝",
confirmClickCallback = {
SensorsBridge.trackInstalledListPermissionsCustomClick("开启")
globalOnPermissionGrantedClosure.invoke()
resultClosure.invoke(true)
trackInstalledListAfterDelay()
},
cancelClickCallback = {
resultClosure.invoke(false)
SensorsBridge.trackInstalledListPermissionsCustomClick("拒绝")
}
)
noticeDialog?.setOnDismissListener {
hintDialog?.dismiss()
}
}
}
/**
* 延迟5秒后上报已安装应用列表
*/
private fun trackInstalledListAfterDelay() {
CoroutineScope(SupervisorJob()).launch {
delay(5000)
SensorsBridge.trackNumberOfInstalledList(localPackageNameSet.size, localPackageNameSet)
}
}
/**
* 是否支持动态获取已安装应用列表权限
*/
fun isSupportGetInstalledAppsPermission(context: Context): Boolean {
// 若存在缓存,直接返回缓存结果。
if (isGetInstalledPackagesPermissionSupported != UNKNOWN) {
return isGetInstalledPackagesPermissionSupported != UNSUPPORTED
}
try {
// 根据官方提供的方法来判定是否支持限制获取已安装应用列表
val flag =
Settings.Secure.getInt(context.contentResolver, "oem_installed_apps_runtime_permission_enable", 0)
if (flag == 1) {
isGetInstalledPackagesPermissionSupported = SUPPORTED
return true
}
// 部分未升级的手机没有上面配置项,有定义下面危险权限也认为是支持设备软件列表管控
val packageManager = context.packageManager
val permissionInfo = packageManager.getPermissionInfo("com.android.permission.GET_INSTALLED_APPS", 0)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
if (permissionInfo.protection == PermissionInfo.PROTECTION_DANGEROUS) {
isGetInstalledPackagesPermissionSupported = SUPPORTED
return true
} else {
isGetInstalledPackagesPermissionSupported = UNSUPPORTED
return false
}
} else {
isGetInstalledPackagesPermissionSupported = UNSUPPORTED
return false
}
} catch (e: PackageManager.NameNotFoundException) {
isGetInstalledPackagesPermissionSupported = UNSUPPORTED
return false
}
}
/**
* 确保指定包名的应用在已安装了的情况下能正常收录
*/
fun addInstalledButMissingPackages(packageNameSet: HashSet<String>) {
Utils.log(TAG, "addInstalledButMissingPackages 检查已安装但未收录的应用")
val installedPackageNameSet: HashSet<String> = hashSetOf()
for (packageName in packageNameSet) {
if (!PackagesManager.isInstalled(packageName)
&& PackageUtils.getVersionNameByPackageName(packageName) != null
) {
installedPackageNameSet.add(packageName)
}
}
Utils.log(TAG, "addInstalledButMissingPackages 需要请求接口获取的包数量为 ${installedPackageNameSet.size}")
PackageRepository.addInstalledGames(
pkgNameList = ArrayList(installedPackageNameSet),
updateInstallStatus = true
)
}
fun refreshWrongInstallStatus(packageNameSet: MutableSet<String>) {
runOnIoThread {
Utils.log(TAG, "refreshWrongInstallStatus 检查安装状态异常的应用")
val installedButKeepingWrongStatusPackageNameSet: HashSet<String> = hashSetOf()
val uninstalledButKeepingWrongStatusPackageNameSet: HashSet<String> = hashSetOf()
val updatedButKeepingWrongStatusPackageNameSet: HashSet<String> = hashSetOf()
for (packageName in packageNameSet) {
val installedVersionName = PackageUtils.getVersionNameByPackageName(packageName)
if (!PackagesManager.isInstalled(packageName)
&& installedVersionName != null
) {
installedButKeepingWrongStatusPackageNameSet.add(packageName)
} else if (PackagesManager.isInstalled(packageName)
&& installedVersionName == null) {
uninstalledButKeepingWrongStatusPackageNameSet.add(packageName)
} else if (PackagesManager.isInstalled(packageName)
&& installedVersionName != null
&& !PackagesManager.isInstalledWithSpecificVersion(packageName, installedVersionName)) {
updatedButKeepingWrongStatusPackageNameSet.add(packageName)
}
}
Utils.log(TAG, "refreshWrongInstallStatus 需要更新已安装状态的包数量为 ${installedButKeepingWrongStatusPackageNameSet.size}")
Utils.log(TAG, "refreshWrongInstallStatus 需要更新已更新状态的包数量为 ${updatedButKeepingWrongStatusPackageNameSet.size}")
Utils.log(TAG, "refreshWrongInstallStatus 需要移除已安装的包数量为 ${uninstalledButKeepingWrongStatusPackageNameSet.size}")
runOnUiThread {
if (installedButKeepingWrongStatusPackageNameSet.isNotEmpty()) {
for (packageName in installedButKeepingWrongStatusPackageNameSet) {
PackageChangeHelper.addInstall(packageName)
}
}
if (uninstalledButKeepingWrongStatusPackageNameSet.isNotEmpty()) {
for (packageName in uninstalledButKeepingWrongStatusPackageNameSet) {
PackageChangeHelper.addUninstall(packageName)
}
}
if (updatedButKeepingWrongStatusPackageNameSet.isNotEmpty()) {
for (packageName in updatedButKeepingWrongStatusPackageNameSet) {
PackageChangeHelper.addUpdate(packageName)
}
}
}
}
}
/**
* 在5.1系统手机使用PackageManager获取已安装应用容易发生Package manager has died异常
* https://stackoverflow.com/questions/13235793/transactiontoolargeeception-when-trying-tÏo-get-a-list-of-applications-installed/30062632#30062632
*/
private fun getInstalledPackagesInternal(context: Context, flags: Int): List<PackageInfo> {
Utils.log(TAG, "调用系统 API 获取已安装应用列表")
val pm = context.packageManager
try {
return pm.getInstalledPackages(flags)
} catch (ignored: java.lang.Exception) {
//we don't care why it didn't succeed. We'll do it using an alternative way instead
}
// use fallback:
val process: Process
val result: MutableList<PackageInfo> = java.util.ArrayList()
var bufferedReader: BufferedReader? = null
try {
process = Runtime.getRuntime().exec("pm list packages")
bufferedReader = BufferedReader(InputStreamReader(process.inputStream))
var line: String
while ((bufferedReader.readLine().also { line = it }) != null) {
val packageName = line.substring(line.indexOf(':') + 1)
val packageInfo = pm.getPackageInfo(packageName, flags)
result.add(packageInfo)
}
process.waitFor()
} catch (e: java.lang.Exception) {
e.printStackTrace()
if (e is InterruptedException) {
Thread.currentThread().interrupt()
}
} finally {
if (bufferedReader != null) try {
bufferedReader.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
return result
}
}

View File

@ -108,6 +108,12 @@ object PackageInstaller {
return
}
val packageName = downloadEntity?.packageName ?: PackageUtils.getPackageNameByPath(context, pkgPath)
packageName?.let {
PackageChangeHelper.addInstallPendingPackage(packageName)
}
try {
// 判断是否需要使用浏览器来进行安装
if (BrowserInstallHelper.isUseBrowserToInstallEnabled()
@ -250,6 +256,8 @@ object PackageInstaller {
fun uninstallForPackageName(context: Context, pkn: String?) {
if (pkn.isNullOrEmpty()) return
PackageChangeHelper.addUninstallPendingPackage(pkn)
val uninstallIntent = Intent()
uninstallIntent.action = Intent.ACTION_DELETE
uninstallIntent.addCategory(Intent.CATEGORY_DEFAULT)

View File

@ -7,14 +7,12 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PermissionInfo;
import android.content.pm.Signature;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.PowerManager;
import android.provider.Settings;
import android.text.TextUtils;
import androidx.annotation.NonNull;
@ -29,8 +27,6 @@ import com.gh.common.xapk.XapkInstaller;
import com.gh.gamecenter.BuildConfig;
import com.gh.gamecenter.common.constant.Constants;
import com.gh.gamecenter.common.utils.ExtensionsKt;
import com.gh.gamecenter.common.utils.PackageFlavorHelper;
import com.gh.gamecenter.common.utils.PermissionHelper;
import com.gh.gamecenter.core.utils.MD5Utils;
import com.gh.gamecenter.core.utils.SentryHelper;
import com.gh.gamecenter.feature.entity.ApkEntity;
@ -49,12 +45,10 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
@ -68,20 +62,14 @@ import java.util.zip.ZipFile;
public class PackageUtils {
private static long mLastInstalledPackageListTime = 0L;
private static List<PackageInfo> mInstalledPackageList = null;
private static final String TAG = "PackageUtils";
// 设备是否支持禁用获取已安装应用列表。-1 代表支持情况未知0 代表不支持, 1 代表支持
private static int mIsSupportGetInstalledListPermission = -1;
public static String getInstallPackageInfoSourceDir(String packageName) {
try {
return HaloApp.getInstance().getApplication().getPackageManager().getPackageInfo(packageName,
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT).applicationInfo.sourceDir;
} catch (NameNotFoundException e) {
e.printStackTrace();
// do nothing
}
return null;
}
@ -319,7 +307,7 @@ public class PackageUtils {
return new String[]{null, null};
}
} catch (NameNotFoundException e) {
e.printStackTrace();
// do nothing
}
return new String[]{null, null};
}
@ -599,7 +587,7 @@ public class PackageUtils {
.getPackageInfo(packageName, 0);
return packageInfo.firstInstallTime;
} catch (NameNotFoundException e) {
e.printStackTrace();
// do nothing
}
return 0;
}
@ -626,7 +614,7 @@ public class PackageUtils {
return HaloApp.getInstance().getApplication().getPackageManager()
.getPackageInfo(BuildConfig.APPLICATION_ID, 0).lastUpdateTime;
} catch (NameNotFoundException e) {
e.printStackTrace();
// do nothing
}
return 0;
@ -640,7 +628,7 @@ public class PackageUtils {
return HaloApp.getInstance().getApplication().getPackageManager().getPackageInfo(packageName,
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT).versionName;
} catch (NameNotFoundException e) {
e.printStackTrace();
// do nothing
}
return null;
}
@ -653,7 +641,7 @@ public class PackageUtils {
return HaloApp.getInstance().getApplication().getPackageManager().getPackageInfo(packageName,
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT).versionCode;
} catch (NameNotFoundException e) {
e.printStackTrace();
// do nothing
}
return 0;
}
@ -667,7 +655,7 @@ public class PackageUtils {
PackageManager packageManager = context.getApplicationContext().getPackageManager();
return packageManager.getApplicationIcon(packageName);
} catch (NameNotFoundException e) {
e.printStackTrace();
// do nothing
}
return null;
}
@ -677,7 +665,7 @@ public class PackageUtils {
*/
public static ArrayList<String> getAllPackageName(Context context) {
ArrayList<String> list = new ArrayList<>();
List<PackageInfo> packageInfos = getInstalledPackages(context, 0);
List<PackageInfo> packageInfos = PackageHelper.INSTANCE.getInstalledPackages(context, 0);
for (PackageInfo packageInfo : packageInfos) {
if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
if (!context.getPackageName().equals(packageInfo.packageName)) {
@ -690,7 +678,7 @@ public class PackageUtils {
public static ArrayList<String> getAllPackageNameIncludeGh(Context context) {
ArrayList<String> list = new ArrayList<>();
List<PackageInfo> packageInfos = getInstalledPackages(context, 0);
List<PackageInfo> packageInfos = PackageHelper.INSTANCE.getInstalledPackages(context, 0);
for (PackageInfo packageInfo : packageInfos) {
if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
list.add(packageInfo.packageName);
@ -704,11 +692,9 @@ public class PackageUtils {
*/
public static ArrayList<String> getAllPackageNameIncludeSystemApps(Context context) {
ArrayList<String> list = new ArrayList<>();
List<PackageInfo> packageInfos = getInstalledPackages(context, 0);
List<PackageInfo> packageInfos = PackageHelper.INSTANCE.getInstalledPackages(context, 0);
for (PackageInfo packageInfo : packageInfos) {
if (!context.getPackageName().equals(packageInfo.packageName)) {
list.add(packageInfo.packageName);
}
list.add(packageInfo.packageName);
}
return list;
}
@ -717,7 +703,7 @@ public class PackageUtils {
JSONArray jsonArray = new JSONArray();
try {
PackageManager pm = context.getPackageManager();
List<PackageInfo> packageInfos = getInstalledPackages(context, 0);
List<PackageInfo> packageInfos = PackageHelper.INSTANCE.getInstalledPackages(context, 0);
for (PackageInfo packageInfo : packageInfos) {
if ((packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
JSONObject jsonObject = new JSONObject();
@ -758,7 +744,6 @@ public class PackageUtils {
}
}
/**
* 启动应用
* 请使用 PackageLauncher.launchApp()
@ -778,22 +763,6 @@ public class PackageUtils {
}
}
/*
* 根据包名,获取软件名称
*/
public static String getNameByPackageName(Context context, String packageName) {
try {
PackageManager pm = context.getApplicationContext().getPackageManager();
ApplicationInfo applicationInfo = pm.getApplicationInfo(
packageName, 0);
return applicationInfo.loadLabel(pm).toString();
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return null;
}
/**
* todo 统一判断
* <p>
@ -912,8 +881,8 @@ public class PackageUtils {
String packageName = context.getApplicationContext().getPackageName();
for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
// The name of the process that this object is associated with.
if (appProcess.processName.equals(packageName) && appProcess.importance
== ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
if (appProcess.processName.equals(packageName)
&& appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
return true;
}
}
@ -924,132 +893,6 @@ public class PackageUtils {
return false;
}
/**
* 弃用已安装列表缓存
*/
public static void dumpInstalledListCache() {
mLastInstalledPackageListTime = 0;
}
public static List<PackageInfo> getInstalledPackages(Context context, int flags) {
Utils.log(TAG, "即将获取已安装应用列表");
// 简单 debounce 掉过于频繁的调用获取已安装列表调用
if (System.currentTimeMillis() - mLastInstalledPackageListTime < 1000
&& mInstalledPackageList != null
&& mInstalledPackageList.size() > 0) {
Utils.log(TAG, "使用了缓存的已安装应用列表");
return new ArrayList<>(mInstalledPackageList);
}
// 是否需要调用系统 API 获取最新的已安装应用列表
boolean shouldGetNewInstalledPackagedList = false;
// 当前设备是否支持限制获取已安装应用列表的功能
if (isSupportGetInstalledAppsPermission(context)) {
Utils.log(TAG, "当前设备支持限制获取已安装应用列表的功能");
// 当前设备是否支持禁用了获取已安装应用列表
if (!PermissionHelper.isGetInstalledListPermissionDisabled(context)) {
Utils.log(TAG, "当前设备没有限制获取已安装应用列表的功能");
shouldGetNewInstalledPackagedList = true;
} else {
Utils.log(TAG, "当前设备已限制获取已安装应用列表的功能");
}
} else {
Utils.log(TAG, "当前设备不支持限制获取已安装应用列表的功能");
shouldGetNewInstalledPackagedList = true;
}
if (shouldGetNewInstalledPackagedList) {
mLastInstalledPackageListTime = System.currentTimeMillis();
mInstalledPackageList = getInstalledPackagesInternal(context, flags);
}
if (mInstalledPackageList == null) {
mInstalledPackageList = new ArrayList<>();
}
return mInstalledPackageList;
}
public static boolean isSupportGetInstalledAppsPermission(Context context) {
// 若存在缓存,直接返回缓存结果。为 0 代表不支持,为 1 代表支持
if (mIsSupportGetInstalledListPermission != -1) {
return mIsSupportGetInstalledListPermission != 0;
}
try {
// 根据官方提供的方法来判定是否支持限制获取已安装应用列表
int flag = Settings.Secure.getInt(context.getContentResolver(), "oem_installed_apps_runtime_permission_enable", 0);
if (flag == 1) {
mIsSupportGetInstalledListPermission = 1;
return true;
}
// 部分未升级的手机没有上面配置项,有定义下面危险权限也认为是支持设备软件列表管控
PackageManager packageManager = context.getPackageManager();
PermissionInfo permissionInfo = packageManager.getPermissionInfo("com.android.permission.GET_INSTALLED_APPS", 0);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
if (permissionInfo.getProtection() == PermissionInfo.PROTECTION_DANGEROUS) {
mIsSupportGetInstalledListPermission = 1;
return true;
} else {
mIsSupportGetInstalledListPermission = 0;
return false;
}
} else {
mIsSupportGetInstalledListPermission = 0;
return false;
}
} catch (NameNotFoundException e) {
mIsSupportGetInstalledListPermission = 0;
return false;
}
}
/**
* 在5.1系统手机使用PackageManager获取已安装应用容易发生Package manager has died异常
* https://stackoverflow.com/questions/13235793/transactiontoolargeeception-when-trying-tÏo-get-a-list-of-applications-installed/30062632#30062632
*/
private static List<PackageInfo> getInstalledPackagesInternal(Context context, int flags) {
Utils.log(TAG, "调用系统 API 获取已安装应用列表");
final PackageManager pm = context.getPackageManager();
try {
return pm.getInstalledPackages(flags);
} catch (Exception ignored) {
//we don't care why it didn't succeed. We'll do it using an alternative way instead
}
// use fallback:
Process process;
List<PackageInfo> result = new ArrayList<>();
BufferedReader bufferedReader = null;
try {
process = Runtime.getRuntime().exec("pm list packages");
bufferedReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = bufferedReader.readLine()) != null) {
final String packageName = line.substring(line.indexOf(':') + 1);
final PackageInfo packageInfo = pm.getPackageInfo(packageName, flags);
result.add(packageInfo);
}
process.waitFor();
} catch (Exception e) {
e.printStackTrace();
if (e instanceof InterruptedException) {
Thread.currentThread().interrupt();
}
} finally {
if (bufferedReader != null)
try {
bufferedReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return result;
}
public static String getWebviewPath(Context context) {
final PackageInfo webViewPackageInfo = WebViewCompat.getCurrentWebViewPackage(context);
return webViewPackageInfo != null ? webViewPackageInfo.applicationInfo.sourceDir : null;

View File

@ -27,8 +27,8 @@ import com.gh.gamecenter.gamedetail.rating.RatingReplyActivity
object SyncDataBetweenPageHelper {
private const val REQUEST_CODE_TAG = "REQUEST_CODE_TAG"
private const val DATA_POSITION_TAG = "DATA_POSITION_TAG"
const val REQUEST_CODE_TAG = "REQUEST_CODE_TAG"
const val DATA_POSITION_TAG = "DATA_POSITION_TAG"
private const val DEFAULT_NUMBER = -1111
fun startActivityForResult(context: Context, intent: Intent, requestCode: Int, dataPosition: Int) {

View File

@ -6,25 +6,21 @@ import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import androidx.core.content.ContextCompat
import androidx.core.view.get
import com.facebook.drawee.drawable.ScalingUtils
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.ImageViewerActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.common.utils.debounceActionWithInterval
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.toResString
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.core.utils.TopCutProcess
import com.gh.gamecenter.databinding.ItemCommunityImageBinding
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.feature.entity.CommunityVideoEntity
import com.gh.gamecenter.feature.entity.ImageInfo
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.video.detail.VideoDetailContainerViewModel
class ImageContainerView : LinearLayout {
private var mAnswerEntity: AnswerEntity? = null
private var data: ImageContainerData? = null
//三图默认宽度
private var mDefaultWidth = 0f
@ -41,9 +37,6 @@ class ImageContainerView : LinearLayout {
//长图比例
private var mLongPictureRatio = 9 / 18f
private var mEntrance = ""
private var mPath = ""
//图片之间的间距
private val mItemSpace = 4f.dip2px()
private var mOffset = 0
@ -51,6 +44,8 @@ class ImageContainerView : LinearLayout {
private val imageViewList = arrayListOf<SimpleDraweeView>()
private var onImageContainerEventListener: OnImageContainerEventListener? = null
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
@ -75,40 +70,34 @@ class ImageContainerView : LinearLayout {
calculateWidth()
}
fun bindData(entity: AnswerEntity, entrance: String = "", path: String = "", imageClick: (() -> Unit)? = null) {
fun bindData(
data: ImageContainerData,
listener: OnImageContainerEventListener? = null
) {
this.data = data
onImageContainerEventListener = listener
imageViewList.clear()
if (entity.id != mAnswerEntity?.id) {
removeAllViews()
}
mAnswerEntity = entity
mEntrance = entrance
mPath = path
removeAllViews()
index = 0
if ((entity.user.id == UserManager.getInstance().userId && entity.videos.isNotEmpty()) ||
(entity.user.id != UserManager.getInstance().userId && entity.getPassVideos().isNotEmpty()) ||
entity.images.isNullOrEmpty()
) {
if (!data.show) {
visibility = View.GONE
return
}
visibility = View.VISIBLE
if (mAnswerEntity?.type == "community_article") {
if (data.isPostCard) {
//若文章内容含有图片及视频,则信息流卡片,仅展示图片,且标题后带有‘有视频’标签
//若文章内容仅含有图片,则信息流卡片,仅展示图片,无标签
//若文章内容仅含有视频,则信息流卡片,仅展示视频,无标签
when {
entity.images.isNotEmpty() -> {
val imagesInfo = entity.imagesInfo
val images = entity.images.take(3)
images.forEachIndexed { index, url ->
val width = if (index <= entity.imagesInfo.size - 1) imagesInfo[index].width else 0
val height = if (index <= entity.imagesInfo.size - 1) imagesInfo[index].height else 0
bindImage(url, width, height, images.size == 1, imageClick)
}
data.images.isNotEmpty() -> {
data.images.take(3)
.forEach {
bindImage(it.url, it.width, it.height, data.images.size == 1)
}
}
entity.getPassVideos().isNotEmpty() -> {
val video = entity.getPassVideos()[0]
data.video != null -> {
val video = data.video
bindVideo(video, video.width, video.height, true)
}
@ -120,28 +109,22 @@ class ImageContainerView : LinearLayout {
//若问答内容含有图片及视频,则信息流卡片,同时展示图片及视频,且参考以往排序逻辑(视频优先放置第一位),无标签
//若问答内容仅含有图片,则信息流卡片,仅展示图片,无标签
//若问答内容仅含有视频,则信息流卡片,仅展示视频,无标签
if (entity.getPassVideos().isNotEmpty()) {
val video = entity.getPassVideos()[0]
bindVideo(video, video.width, video.height, mAnswerEntity?.images.isNullOrEmpty())
entity.images.take(2).forEachIndexed { index, url ->
val imagesInfo = entity.imagesInfo
val width = if (index <= entity.imagesInfo.size - 1) imagesInfo[index].width else 0
val height = if (index <= entity.imagesInfo.size - 1) imagesInfo[index].height else 0
bindImage(url, width, height, false, imageClick)
if (data.video != null) {
val video = data.video
bindVideo(video, video.width, video.height, data.images.isNullOrEmpty())
data.images.take(2).forEach {
bindImage(it.url, it.width, it.height, false)
}
} else {
val images = entity.images.take(3)
images.forEachIndexed { index, url ->
val imagesInfo = entity.imagesInfo
val width = if (index <= entity.imagesInfo.size - 1) imagesInfo[index].width else 0
val height = if (index <= entity.imagesInfo.size - 1) imagesInfo[index].height else 0
bindImage(url, width, height, images.size == 1, imageClick)
}
data.images.take(3)
.forEach {
bindImage(it.url, it.width, it.height, data.images.size == 1)
}
}
}
}
private fun bindVideo(video: CommunityVideoEntity, width: Int, height: Int, isChangeRatio: Boolean) {
private fun bindVideo(video: ImageContainerData.VideoInfo, width: Int, height: Int, isChangeRatio: Boolean) {
val oldView = if (childCount == 0 || index >= childCount) null else getChildAt(index)
val binding = if (oldView != null) {
ItemCommunityImageBinding.bind(oldView)
@ -158,16 +141,7 @@ class ImageContainerView : LinearLayout {
displayImage(binding, video.poster, width.toFloat(), height.toFloat(), isChangeRatio, true)
binding.root.setOnClickListener {
debounceActionWithInterval(it.id, 1000) {
if (mAnswerEntity == null) return@debounceActionWithInterval
val videoEntity = mAnswerEntity!!.getPassVideos().firstOrNull()
DirectUtils.directToVideoDetail(
context,
videoEntity?.id ?: "",
VideoDetailContainerViewModel.Location.VIDEO_HOT.value,
showComment = false,
entrance = mEntrance,
path = mPath
)
onImageContainerEventListener?.onVideoCLick(video.id)
}
}
index++
@ -177,8 +151,7 @@ class ImageContainerView : LinearLayout {
url: String,
width: Int,
height: Int,
isChangeRatio: Boolean,
imageClick: (() -> Unit)?
isChangeRatio: Boolean
) {
val oldView = if (childCount == 0 || index >= childCount) null else getChildAt(index)
val binding = if (oldView != null) {
@ -195,25 +168,21 @@ class ImageContainerView : LinearLayout {
binding.videoPlay.visibility = View.GONE
displayImage(binding, url, width.toFloat(), height.toFloat(), isChangeRatio)
binding.root.setOnClickListener {
if (mAnswerEntity?.status == "pending" || mAnswerEntity?.status == "fail") return@setOnClickListener
imageClick?.invoke()
if (data?.status == "pending" || data?.status == "fail") return@setOnClickListener
debounceActionWithInterval(it.id, 1000) {
if (mAnswerEntity == null) return@debounceActionWithInterval
val position = if (mAnswerEntity?.type == "community_article") {
binding.root.tag as Int
} else {
if (mAnswerEntity!!.getPassVideos()
.isNullOrEmpty()
) binding.root.tag as Int else (binding.root.tag as Int) - 1
data?.run {
val position = if (isPostCard) {
binding.root.tag as Int
} else {
if (video == null) binding.root.tag as Int else (binding.root.tag as Int) - 1
}
onImageContainerEventListener?.onImageClick(
images.map(ImageContainerData.ImageInfo::url),
position,
imageViewList
)
}
if (mAnswerEntity?.communityId.isNullOrEmpty()) {
mAnswerEntity?.communityId = mAnswerEntity?.bbs?.id
}
val intent = ImageViewerActivity.getIntent(
context, mAnswerEntity!!.images as ArrayList<String>, position, imageViewList,
if (mAnswerEntity?.type == "community_article") mAnswerEntity else null, mEntrance, true
)
context.startActivity(intent)
}
}
index++
@ -279,7 +248,7 @@ class ImageContainerView : LinearLayout {
}
binding.pendingView.run {
when (mAnswerEntity?.status) {
when (data?.status) {
"pending" -> {
visibility = View.VISIBLE
text = R.string.pending_status.toResString()
@ -297,7 +266,7 @@ class ImageContainerView : LinearLayout {
}
val imageCount = mAnswerEntity?.images?.size ?: 0
val imageCount = data?.images?.size ?: 0
if (!isVideo && index == 2 && imageCount > 3) {
binding.labelIcon.visibility = View.GONE
binding.durationOrNumTv.visibility = View.VISIBLE
@ -308,4 +277,80 @@ class ImageContainerView : LinearLayout {
if (index != 0) params.leftMargin = mItemSpace
binding.root.layoutParams = params
}
companion object {
private const val COMMUNITY_ARTICLE = "community_article"
fun AnswerEntity.toImageContainerData(): ImageContainerData {
val imageInfoList = arrayListOf<ImageContainerData.ImageInfo>()
images.forEachIndexed { index, url ->
if (index < 3) {
imageInfoList.add(ImageContainerData.ImageInfo(url))
}
}
imageInfoList.forEachIndexed { index, imageInfo ->
val item = imagesInfo.getOrNull(index)
if (item != null) {
imageInfo.width = item.width
imageInfo.height = item.height
}
}
val video =
getPassVideos().firstOrNull()?.let {
ImageContainerData.VideoInfo(
it.id,
it.duration,
it.poster,
it.width,
it.height
)
}
val show = !((user.id == UserManager.getInstance().userId && videos.isNotEmpty())
|| (user.id != UserManager.getInstance().userId && getPassVideos().isNotEmpty())
|| images.isNullOrEmpty())
return ImageContainerData(
status = status,
isPostCard = type == COMMUNITY_ARTICLE,
images = imageInfoList,
video = video,
show
)
}
}
data class ImageContainerData(
val status: String,
val isPostCard: Boolean, // 是否是帖子卡片
val images: List<ImageInfo>,
val video: VideoInfo? = null,
val show: Boolean
) {
data class ImageInfo(
val url: String,
var width: Int = 0,
var height: Int = 0
)
data class VideoInfo(
val id: String,
val duration: String,
val poster: String,
var width: Int = 0,
var height: Int = 0,
)
}
interface OnImageContainerEventListener {
fun onImageClick(
images: List<String>,
position: Int,
imageViewList: ArrayList<SimpleDraweeView>
)
fun onVideoCLick(videoId: String)
}
}

View File

@ -10,8 +10,8 @@ import com.gh.gamecenter.common.exposure.meta.MetaUtil
import com.gh.gamecenter.common.exposure.meta.MetaUtil.getMeta
import com.gh.gamecenter.common.loghub.LoghubUtils
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.SentryHelper
import com.gh.ndownload.NDataChanger
import com.gh.ndownload.NDownloadBridge
import com.halo.assistant.HaloApp
import com.lightgame.download.DownloadConfig
import com.lightgame.download.DownloadEntity
@ -124,6 +124,7 @@ object DownloadDataHelper {
val payloadObject = JSONObject()
payloadObject.put("host", downloadEntity.meta[DownloadEntity.DOWNLOAD_HOST_KEY] ?: "unknown")
payloadObject.put("path", downloadEntity.meta[DownloadEntity.DOWNLOAD_PATH_KEY] ?: "unknown")
payloadObject.put("redirected_host_list", downloadEntity.meta[NDownloadBridge.REDIRECTED_HOST_LIST] ?: "unknown")
payloadObject.put("game_id", downloadEntity.gameId)
payloadObject.put("gameName", downloadEntity.name)
payloadObject.put("platform", downloadEntity.platform)
@ -212,6 +213,7 @@ object DownloadDataHelper {
payloadObject.put("host", downloadEntity.meta[DownloadEntity.DOWNLOAD_HOST_KEY] ?: "unknown")
payloadObject.put("path", downloadEntity.meta[DownloadEntity.DOWNLOAD_PATH_KEY] ?: "unknown")
payloadObject.put("redirected_host_list", downloadEntity.meta[NDownloadBridge.REDIRECTED_HOST_LIST] ?: "unknown")
payloadObject.put("game_id", downloadEntity.gameId)
payloadObject.put("gameName", downloadEntity.name)
payloadObject.put("platform", downloadEntity.platform)
@ -251,6 +253,7 @@ object DownloadDataHelper {
payloadObject.put("host", downloadEntity.meta[DownloadEntity.DOWNLOAD_HOST_KEY] ?: "unknown")
payloadObject.put("path", downloadEntity.meta[DownloadEntity.DOWNLOAD_PATH_KEY] ?: "unknown")
payloadObject.put("redirected_host_list", downloadEntity.meta[NDownloadBridge.REDIRECTED_HOST_LIST] ?: "unknown")
payloadObject.put("game_id", downloadEntity.gameId)
payloadObject.put("gameName", downloadEntity.name)
payloadObject.put("platform", downloadEntity.platform)
@ -315,6 +318,7 @@ object DownloadDataHelper {
payloadObject.put("host", downloadEntity.meta[DownloadEntity.DOWNLOAD_HOST_KEY] ?: "unknown")
payloadObject.put("path", downloadEntity.meta[DownloadEntity.DOWNLOAD_PATH_KEY] ?: "unknown")
payloadObject.put("redirected_host_list", downloadEntity.meta[NDownloadBridge.REDIRECTED_HOST_LIST] ?: "unknown")
payloadObject.put("game_id", downloadEntity.gameId)
payloadObject.put("gameName", downloadEntity.name)
payloadObject.put("platform", downloadEntity.platform)
@ -357,6 +361,7 @@ object DownloadDataHelper {
sheet = JSONObject()
sheet.put("host", downloadEntity.meta[DownloadEntity.DOWNLOAD_HOST_KEY] ?: "unknown")
sheet.put("path", downloadEntity.meta[DownloadEntity.DOWNLOAD_PATH_KEY] ?: "unknown")
sheet.put("redirected_host_list", downloadEntity.meta[NDownloadBridge.REDIRECTED_HOST_LIST] ?: "unknown")
sheet.put("game_id", downloadEntity.gameId)
sheet.put("platform", downloadEntity.platform)
sheet.put("package", downloadEntity.packageName)
@ -376,6 +381,7 @@ object DownloadDataHelper {
"path",
downloadEntity.meta[DownloadEntity.DOWNLOAD_PATH_KEY] ?: "unknown"
) // 初始化记录的 path 为空
sheet.put("redirected_host_list", downloadEntity.meta[NDownloadBridge.REDIRECTED_HOST_LIST] ?: "unknown")
sheet.put("total_size", downloadEntity.size / 1024 / 1024) // 初始化记录的 total_size 有可能为0
sheet.put("progress_size", downloadEntity.progress / 1024 - progressSize)
sheet.put("current_progress_size", downloadEntity.progress / 1024)

View File

@ -22,7 +22,6 @@ import com.gh.gamecenter.core.AppExecutor;
import com.gh.gamecenter.common.constant.Constants;
import com.gh.gamecenter.feature.entity.TagStyleEntity;
import com.gh.gamecenter.feature.entity.CustomPageTrackData;
import com.gh.gamecenter.feature.entity.TagStyleEntity;
import com.gh.gamecenter.feature.exposure.ExposureEvent;
import com.gh.common.exposure.ExposureUtils;
import com.gh.common.history.HistoryHelper;
@ -34,16 +33,10 @@ 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.utils.AppDebugConfig;
import com.gh.gamecenter.core.utils.GsonUtils;
import com.gh.gamecenter.core.utils.PageSwitchDataHelper;
import com.gh.gamecenter.core.utils.SPUtils;
@ -55,7 +48,6 @@ import com.gh.gamecenter.eventbus.EBDownloadStatus;
import com.gh.gamecenter.feature.entity.ApkEntity;
import com.gh.gamecenter.feature.entity.GameEntity;
import com.gh.gamecenter.feature.entity.PluginLocation;
import com.gh.gamecenter.feature.exposure.ExposureEvent;
import com.gh.gamecenter.login.user.UserManager;
import com.gh.gamecenter.manager.PackagesManager;
import com.gh.gamecenter.packagehelper.PackageRepository;
@ -64,7 +56,6 @@ import com.gh.ndownload.NDownloadBridge;
import com.gh.ndownload.NDownloadService;
import com.gh.vspace.VHelper;
import com.halo.assistant.HaloApp;
import com.lightgame.download.ConnectionUtils;
import com.lightgame.download.DataWatcher;
import com.lightgame.download.DownloadConfig;
import com.lightgame.download.DownloadDao;
@ -179,9 +170,6 @@ public class DownloadManager implements DownloadStatusListener {
mUpdateMarks = SPUtils.getStringSet(UPDATE_IS_READ_MARK);
// 只有下载模块需要这坨东西,因此移动到这里初始化
ConnectionUtils.initHttpsUrlConnection(mContext);
updateDownloadMetaMap();
lastTimeMap = new ArrayMap<>();

View File

@ -28,10 +28,10 @@ import org.json.JSONObject
import java.io.File
import java.net.URLEncoder
import java.util.*
import kotlin.collections.ArrayList
object BrowserInstallHelper {
// 随便选的 32321 居然在部分 vivo 手机上被占用,喷了
private const val PORT = 40705
private const val RESERVE_PORT = 40706
@ -40,15 +40,12 @@ object BrowserInstallHelper {
private val mContext by lazy { HaloApp.getInstance().application }
private var mUseReservePort = false
private val mAllInstalledPackageList: ArrayList<String> by lazy {
PackageUtils.getAllPackageNameIncludeSystemApps(HaloApp.getInstance().applicationContext).apply {
add(HaloApp.getInstance().applicationContext.packageName)
}
}
private var mValidInstalledPackageList: ArrayList<String> = arrayListOf()
private var mValidConditionMatchedCache: Boolean? = null
private fun getServer(port: Int): DownloadServer {
val server = DownloadServer(port)
for (packageName in mAllInstalledPackageList) {
for (packageName in getAllInstalledPackageList()) {
if (packageName.contains("com.freeme") || packageName.contains("com.zhuoyi")) {
server.isBuggyDevice = true
break
@ -57,6 +54,23 @@ object BrowserInstallHelper {
return server
}
private fun getAllInstalledPackageList(): ArrayList<String> {
when {
mValidInstalledPackageList.isNotEmpty() -> {
return mValidInstalledPackageList
}
else -> {
val allInstalledPackageList = PackageUtils.getAllPackageNameIncludeSystemApps(mContext)
if (allInstalledPackageList.isNotEmpty()) {
mValidInstalledPackageList = allInstalledPackageList
}
return mValidInstalledPackageList
}
}
}
fun downloadFile(filePath: String) {
if (!::mServer.isInitialized) mServer = if (mUseReservePort) getServer(RESERVE_PORT) else getServer(PORT)
if (!mServer.isAlive && !startServer()) {
@ -237,32 +251,43 @@ object BrowserInstallHelper {
* 是否满足开启浏览器安装的条件
*/
private fun isConditionMatched(settingsEntity: NewSettingsEntity): Boolean {
if (mValidConditionMatchedCache != null) {
return mValidConditionMatchedCache!!
}
val packageList = getAllInstalledPackageList()
if (packageList.isEmpty()) return false
settingsEntity.installModel?.whiteList?.let {
for (packageName in it) {
if (mAllInstalledPackageList.contains(packageName)) {
return false
if (packageList.contains(packageName)) {
mValidConditionMatchedCache = false
break
}
}
}
settingsEntity.installModel?.packages?.let {
for (packageName in it) {
if (mAllInstalledPackageList.contains(packageName)) {
return true
if (packageList.contains(packageName)) {
mValidConditionMatchedCache = true
break
}
}
}
// 匹配部分字符串即可
settingsEntity.installModel?.regexPackages?.let {
for (packageNamePieces in it) {
for (installedPackageName in mAllInstalledPackageList) {
for (installedPackageName in packageList) {
if (installedPackageName.contains(packageNamePieces)) {
return true
mValidConditionMatchedCache = true
break
}
}
}
}
return false
return mValidConditionMatchedCache ?: false
}
fun onApkInstalled(path: String?) {

View File

@ -10,6 +10,9 @@ import com.gh.gamecenter.common.utils.updateStatusBarColor
import com.gh.gamecenter.entity.SubjectRecommendEntity
import com.gh.gamecenter.game.GameFragment
/**
* 板块
*/
class BlockActivity : DownloadToolbarActivity() {
companion object {

View File

@ -14,6 +14,7 @@ import com.halo.assistant.fragment.ApkCleanerFragment;
/**
* Created by khy on 2017/1/24.
* 清理安装包
*/
@Route(path = RouteConsts.activity.cleanApkActivity)
public class CleanApkActivity extends ToolBarActivity {

View File

@ -16,6 +16,7 @@ import java.util.ArrayList;
/**
* Created by khy on 18/07/17.
* 我的收藏
*/
public class CollectionActivity extends ToolBarActivity {
@Override

View File

@ -23,6 +23,9 @@ import java.lang.ref.SoftReference;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
/**
* 裁剪图片
*/
public class CropImageActivity extends ToolBarActivity {
protected CropImageCustom mCropImageCustom;

View File

@ -24,7 +24,7 @@ import com.halo.assistant.HaloApp
/**
* Created by khy on 2017/3/24.
* 游戏详情适配器
* 游戏详情
*/
class GameDetailActivity : DownloadToolbarActivity() {

View File

@ -16,6 +16,7 @@ import java.util.ArrayList;
/**
* Created by khy on 2016/12/12.
* 礼包中心
*/
@Deprecated
public class LibaoActivity extends ToolBarActivity {

View File

@ -285,9 +285,6 @@ public class MainActivity extends BaseActivity {
// 耗时操作
AppExecutor.getIoExecutor().execute(() -> {
// 上传数据
DataCollectionManager.getInstance().upload();
// 初始化PlatformUtils
PlatformUtils.getInstance(getApplicationContext());
@ -556,7 +553,10 @@ public class MainActivity extends BaseActivity {
} else {
TextView jumpBtn = findViewById(R.id.jumpBtn);
jumpBtn.setText(String.format(Locale.CHINA, "跳过 %d", COUNTDOWN_MAX_COUNT - mCountdownCount));
mBaseHandler.sendEmptyMessageDelayed(COUNTDOWN_AD, 1000);
Message newMsg = Message.obtain();
newMsg.what = COUNTDOWN_AD;
newMsg.obj = msg.obj;
mBaseHandler.sendMessageDelayed(newMsg, 1000);
}
}
}
@ -877,13 +877,6 @@ public class MainActivity extends BaseActivity {
return true;
}
@Override
public void finish() {
// 上传数据
DataCollectionManager.getInstance().statClickData();
super.finish();
}
@Override
protected void onSaveInstanceState(@NotNull Bundle outState) {
super.onSaveInstanceState(outState);

View File

@ -29,6 +29,9 @@ import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import java.util.concurrent.TimeUnit
/**
* 游戏搜索页
*/
open class SearchActivity : BaseActivity() {
lateinit var searchEt: EditText

View File

@ -48,6 +48,7 @@ import io.reactivex.functions.Consumer;
/**
* Created by khy on 2016/11/7.
* 分享卡片
*/
public class ShareCardPicActivity extends ToolBarActivity {

View File

@ -21,6 +21,7 @@ import com.tencent.tauth.Tencent;
/**
* Created by khy on 2017/2/6.
* 分享光环
*/
public class ShareGhActivity extends ToolBarActivity {

View File

@ -197,7 +197,7 @@ class SplashScreenActivity : BaseActivity() {
// 尝试获取安装应用列表权限并启动首页(不在乎结果)
private fun requestGetInstallListPermissionAndLaunchMainActivity() {
if (PackageUtils.isSupportGetInstalledAppsPermission(this)
if (PackageHelper.isSupportGetInstalledAppsPermission(this)
&& PermissionHelper.isGetInstalledListPermissionDisabled(this)
) {
PermissionHelper.requestGetInstalledAppsListPermission(this, true) {

View File

@ -7,6 +7,9 @@ import com.gh.gamecenter.common.base.activity.ToolBarActivity
import com.gh.gamecenter.common.utils.updateStatusBarColor
import com.halo.assistant.fragment.user.UserInfoFragment
/**
* 编辑资料
*/
class UserInfoActivity : ToolBarActivity() {
override fun onCreate(savedInstanceState: Bundle?) {

View File

@ -8,6 +8,9 @@ import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.login.user.UserViewModel
import com.halo.assistant.fragment.user.UserInfoEditFragment
/**
* 修改个人信息
*/
class UserInfoEditActivity : ToolBarActivity() {
companion object {
fun getIntent(context: Context, editType: String): Intent {

View File

@ -8,6 +8,7 @@ import com.halo.assistant.fragment.user.SelectRegionFragment;
/**
* Created by khy on 25/09/17.
* 选择地区
*/
public class UserRegionActivity extends ToolBarActivity {

View File

@ -18,6 +18,7 @@ import android.widget.TextView;
import androidx.collection.ArrayMap;
import androidx.core.content.ContextCompat;
import com.gh.common.util.PackageHelper;
import com.gh.common.util.PackageInstaller;
import com.gh.common.util.PackageUtils;
import com.gh.gamecenter.R;
@ -188,7 +189,7 @@ public class CleanApkAdapter extends BaseRecyclerAdapter<KcSelectGameViewHolder>
}
private int doType(String packageName) {
List<PackageInfo> pakageinfos = PackageUtils.getInstalledPackages(mContext, 0);
List<PackageInfo> pakageinfos = PackageHelper.INSTANCE.getInstalledPackages(mContext, 0);
for (PackageInfo pi : pakageinfos) {
if ((pi.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
String pi_packageName = pi.packageName;

View File

@ -8,6 +8,9 @@ import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.updateStatusBarColor
/**
* 新分类2.0
*/
class CategoryV2Activity : DownloadToolbarActivity() {
override fun onCreate(savedInstanceState: Bundle?) {

View File

@ -41,6 +41,9 @@ import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import java.io.File
/**
* 云存档管理
*/
class CloudArchiveManagerActivity : BaseActivity_TabLayout(), ArchiveLimitSelectedListener {
private lateinit var mBinding: ActivityCloudArchiveManagerBinding

View File

@ -1,120 +0,0 @@
package com.gh.gamecenter.db;
import android.content.Context;
import com.gh.gamecenter.db.info.DataCollectionInfo;
import com.j256.ormlite.dao.Dao;
import java.sql.SQLException;
import java.util.List;
// TODO 这个数据库其实没有用了,上传到 loghub 已经有相关的逻辑处理,有空删掉它
public class DataCollectionDao {
private DatabaseHelper helper;
private Dao<DataCollectionInfo, String> dao;
public DataCollectionDao(Context context) {
try {
helper = DatabaseHelper.getHelper(context);
dao = helper.getDao(DataCollectionInfo.class);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 查找一个数据
*/
public List<DataCollectionInfo> findByType(String type) {
try {
return dao.queryForEq("type", type);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 添加一个数据
*/
public void add(DataCollectionInfo entity) {
try {
dao.create(entity);
} catch (Exception e) {
// java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase:
e.printStackTrace();
}
}
/**
* 删除一个数据
*/
public void delete(String id) {
try {
dao.deleteById(id);
} catch (Exception e) {
// java.lang.IllegalStateException: attempt to re-open an already-closed object: SQLiteDatabase:
e.printStackTrace();
}
}
/**
* 删除一组数据
*/
public void delete(List<String> ids) {
try {
dao.deleteIds(ids);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 根据id获取某一个数据
*/
public DataCollectionInfo find(String id) {
try {
return dao.queryForId(id);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 获取所有的数据
*/
public List<DataCollectionInfo> getAll() {
try {
return dao.queryForAll();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 获取点击数据
*/
public List<DataCollectionInfo> getClickData() {
try {
return dao.queryForEq("type", "click-item");
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 更新数据
*/
public void update(DataCollectionInfo entity) {
try {
dao.update(entity);
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@ -5,9 +5,7 @@ import android.database.sqlite.SQLiteDatabase;
import androidx.collection.ArrayMap;
import com.gh.gamecenter.db.info.DataCollectionInfo;
import com.gh.gamecenter.db.info.GameTrendsInfo;
import com.gh.gamecenter.db.info.PackageInfo;
import com.gh.gamecenter.db.info.SearchHistoryInfo;
import com.j256.ormlite.android.apptools.OrmLiteSqliteOpenHelper;
import com.j256.ormlite.dao.Dao;
@ -52,8 +50,6 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
try {
Utils.log("DatabaseHelper onCreate");
TableUtils.createTable(connectionSource, SearchHistoryInfo.class);
TableUtils.createTable(connectionSource, DataCollectionInfo.class);
TableUtils.createTable(connectionSource, PackageInfo.class);
TableUtils.createTable(connectionSource, GameTrendsInfo.class);
} catch (SQLException e) {
e.printStackTrace();
@ -65,8 +61,6 @@ public class DatabaseHelper extends OrmLiteSqliteOpenHelper {
try {
Utils.log("DatabaseHelper onUpgrade");
TableUtils.dropTable(connectionSource, SearchHistoryInfo.class, true);
TableUtils.dropTable(connectionSource, DataCollectionInfo.class, true);
TableUtils.dropTable(connectionSource, PackageInfo.class, true);
TableUtils.dropTable(connectionSource, GameTrendsInfo.class, true);
onCreate(database, connectionSource);
} catch (SQLException e) {

View File

@ -1,65 +0,0 @@
package com.gh.gamecenter.db.info;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import java.io.Serializable;
@DatabaseTable(tableName = "tb_datacollection")
public class DataCollectionInfo implements Serializable {
/**
*
*/
private static final long serialVersionUID = 3196892351397147390L;
@DatabaseField(id = true, columnName = "id")
private String id;
@DatabaseField(columnName = "type")
private String type;
@DatabaseField(columnName = "data")
private String data;
public DataCollectionInfo() {
}
public DataCollectionInfo(String id, String type, String data) {
this.id = id;
this.type = type;
this.data = data;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
@Override
public String toString() {
return "DataCollectionEntity [id=" + id + ", type=" + type + ", data="
+ data + "]";
}
}

View File

@ -1,42 +0,0 @@
package com.gh.gamecenter.db.info;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.table.DatabaseTable;
import java.io.Serializable;
@DatabaseTable(tableName = "tb_package")
public class PackageInfo implements Serializable {
@DatabaseField(id = true, columnName = "packageName")
private String packageName;
@DatabaseField(columnName = "time")
private long time;
public PackageInfo() {
}
public PackageInfo(String packageName, long time) {
this.packageName = packageName;
this.time = time;
}
public String getPackageName() {
return packageName;
}
public void setPackageName(String packageName) {
this.packageName = packageName;
}
public long getTime() {
return time;
}
public void setTime(long time) {
this.time = time;
}
}

View File

@ -21,6 +21,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.gh.ad.AdDelegateHelper
import com.gh.common.util.HomePluggableHelper
import com.gh.common.util.NewFlatLogUtils
import com.gh.common.util.PackageHelper
import com.gh.common.util.PackageInstaller
import com.gh.download.DownloadManager
import com.gh.gamecenter.DownloadManagerActivity
@ -276,6 +277,12 @@ class DownloadFragment : BaseFragment_TabLayout() {
if (mBinding.adGameItemContainer.isVisible) {
DownloadManager.getInstance().addObserver(mDataWatcher)
}
refreshInstallStatus()
}
private fun refreshInstallStatus() {
PackageHelper.refreshWrongInstallStatus(PackagesManager.getInstalledSet())
}
override fun onParentActivityFinish() {

View File

@ -10,8 +10,8 @@ import com.ethanhua.skeleton.ViewSkeletonScreen
import com.gh.common.exposure.ExposureListener
import com.gh.common.util.DirectUtils
import com.gh.common.util.DownloadItemUtils
import com.gh.common.util.PackageHelper
import com.gh.download.DownloadManager
import com.gh.gamecenter.MainActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.ToolbarFragment
import com.gh.gamecenter.common.eventbus.EBReuse
@ -29,7 +29,6 @@ import com.gh.gamecenter.feature.entity.GameInstall
import com.gh.gamecenter.manager.PackagesManager
import com.gh.gamecenter.packagehelper.PackageRepository
import com.gh.gamecenter.packagehelper.PackageViewModel
import com.gh.gamecenter.wrapper.MainWrapperFragment
import com.lightgame.download.DataWatcher
import com.lightgame.download.DownloadEntity
import com.lightgame.download.DownloadStatus
@ -147,15 +146,20 @@ class NewInstalledGameFragment : ToolbarFragment() {
}
mBinding.run {
if (PermissionHelper.isGetInstalledListPermissionDisabled(requireContext())) {
val isGetInstalledListDisagreed = PackageHelper.isGetInstalledPackagesApiAgreedRequired()
&& !PackageHelper.isGetInstalledPackagesApiAgreed()
val isGetInstalledListPermissionDisabled = PermissionHelper.isGetInstalledListPermissionDisabled(requireContext())
if (isGetInstalledListDisagreed || isGetInstalledListPermissionDisabled) {
reuseNoneData.reuseNoneDataIv.visibility = View.GONE
reuseNoneData.reuseNoneDataTv.text = "开启应用列表权限"
reuseNoneData.reuseNoneDataDescTv.text = " 及时获悉游戏最新的更新消息"
reuseNoneData.reuseResetLoadTv.text = "去开启"
reuseNoneData.reuseResetLoadTv.setOnClickListener {
PermissionHelper.requestGetInstalledAppsListPermission(requireActivity()) {
updateNoDataView()
PackageRepository.initData()
PackageHelper.showGetInstallAppsListDialogAndRequestPermissionIfNeeded(requireActivity()) { isGranted ->
if (isGranted) {
updateNoDataView()
}
}
}
} else {

View File

@ -3,8 +3,8 @@ package com.gh.gamecenter.download
import android.view.View
import com.gh.common.exposure.ExposureListener
import com.gh.common.util.DirectUtils
import com.gh.common.util.PackageHelper
import com.gh.download.DownloadManager
import com.gh.gamecenter.MainActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.LazyFragment
import com.gh.gamecenter.common.constant.EntranceConsts
@ -12,12 +12,10 @@ import com.gh.gamecenter.common.eventbus.EBReuse
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.FixLinearLayoutManager
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.core.utils.EmptyCallback
import com.gh.gamecenter.databinding.FragmentGameUpdatableBinding
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.packagehelper.PackageRepository
import com.gh.gamecenter.packagehelper.PackageViewModel
import com.gh.gamecenter.wrapper.MainWrapperFragment
import com.lightgame.download.DataWatcher
import com.lightgame.download.DownloadEntity
import org.greenrobot.eventbus.Subscribe
@ -40,7 +38,6 @@ class UpdatableGameFragment : LazyFragment() {
}
}
override fun getRealLayoutId() = R.layout.fragment_game_updatable
override fun onRealLayoutInflated(inflatedView: View) {
mBinding = FragmentGameUpdatableBinding.bind(inflatedView)
@ -98,21 +95,21 @@ class UpdatableGameFragment : LazyFragment() {
noDataContainer.reuseResetLoadTv.layoutParams = layoutParam
noDataContainer.reuseResetLoadTv.visibility = View.VISIBLE
noDataContainer.reuseNoneDataDescTv.visibility = View.VISIBLE
if (PermissionHelper.isGetInstalledListPermissionDisabled(requireContext())) {
val isGetInstalledListDisagreed = !PackageHelper.isGetInstalledPackagesApiAgreed()
val isGetInstalledListPermissionDisabled = PermissionHelper.isGetInstalledListPermissionDisabled(requireContext())
if (isGetInstalledListDisagreed || isGetInstalledListPermissionDisabled) {
noDataContainer.reuseNoneDataIv.visibility = View.GONE
noDataContainer.reuseNoneDataTv.text = "开启应用列表权限"
noDataContainer.reuseNoneDataDescTv.text = "及时获悉游戏最新的更新消息"
noDataContainer.reuseResetLoadTv.text = "去开启"
noDataContainer.reuseResetLoadTv.setOnClickListener {
PermissionHelper.requestGetInstalledAppsListPermission(
requireActivity(),
false,
object : EmptyCallback {
override fun onCallback() {
updateNoDataView()
PackageRepository.initData()
}
})
PackageHelper.showGetInstallAppsListDialogAndRequestPermissionIfNeeded(requireActivity()) { isGranted ->
if (isGranted) {
updateNoDataView()
}
}
}
} else {
noDataContainer.reuseNoneDataIv.visibility = View.VISIBLE

View File

@ -0,0 +1,138 @@
package com.gh.gamecenter.entity
import android.os.Parcelable
import com.gh.gamecenter.feature.entity.*
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
data class FollowDynamicEntity(
@SerializedName("type")
private val _type: String? = null,
@SerializedName("libao")
val libao: LibaoEntity? = null,
@SerializedName("libao_exchange")
val libaoExchange: LibaoEntity? = null,
@SerializedName("game")
private val _game: GameEntity? = null,
@SerializedName("me")
private val _me: MeEntity? = null,
@SerializedName("time")
private val _time: Long? = null,
@SerializedName("article")
private val _article: Article? = null,
@SerializedName("user_post")
private val _userPost: ArticleEntity? = null
) : Parcelable {
val type: String
get() = _type ?: ""
val article: Article
get() = _article ?: Article()
val game: GameEntity
get() = _game ?: GameEntity()
val me: MeEntity
get() = _me ?: MeEntity()
val userPost: ArticleEntity
get() = _userPost ?: ArticleEntity()
val time: Long
get() = _time ?: 0L
companion object {
const val FOLLOW_UPDATE_TYPE_LIBAO = "libao"
const val FOLLOW_UPDATE_TYPE_LIBAO_EXCHANGE = "libao_exchange"
const val FOLLOW_UPDATE_TYPE_ARTICLE = "article"
const val FOLLOW_UPDATE_TYPE_USER_POST = "user_post"
}
@Parcelize
data class Article(
@SerializedName("_id")
private val _id: String? = null,
@SerializedName("title")
private val _title: String? = null,
@SerializedName("content")
private val _content: String? = null,
@SerializedName("img")
private val _img: List<String>? = null,
@SerializedName("count")
private val _count: Count? = null,
@SerializedName("game")
private val _game: GameEntity? = null,
@SerializedName("time")
private val _time: Long? = null
) : Parcelable {
val id: String
get() = _id ?: ""
val title: String
get() = _title ?: ""
val content: String
get() = _content ?: ""
val img: List<String>
get() = _img ?: listOf()
val count: Count
get() = _count ?: Count()
val game: GameEntity
get() = _game ?: GameEntity()
val time: Long
get() = _time ?: 0L
@Parcelize
data class Count(
@SerializedName("comment")
private val _comment: Int? = null
) : Parcelable {
val comment: Int
get() = _comment ?: 0
}
}
@Parcelize
data class LibaoExchange(
@SerializedName("_id")
private val _id: String? = null,
@SerializedName("libao_id")
private val _libaoId: String? = null,
@SerializedName("libao_code")
private val _libaoCode: String? = null,
@SerializedName("name")
private val _name: String? = null,
@SerializedName("content")
private val _content: String? = null,
@SerializedName("active")
private val _active: Boolean? = null
) : Parcelable {
val id: String
get() = _id ?: ""
val libaoId: String
get() = _libaoId ?: ""
val libaoCode: String
get() = _libaoCode ?: ""
val name: String
get() = _name ?: ""
val content: String
get() = _content ?: ""
val active: Boolean
get() = _active ?: false
}
}

View File

@ -0,0 +1,159 @@
package com.gh.gamecenter.entity
import android.os.Parcelable
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.User
import com.gh.gamecenter.feature.entity.UserEntity
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
import java.util.Objects
@Parcelize
data class FollowUserEntity(
@SerializedName("_id")
private val _id: String? = null,
@SerializedName("type")
private val _type: String? = null, // user用户、bbs论坛
@SerializedName("user")
private val _user: UserEntity? = null,
@SerializedName("bbs")
private var _bbs: Bbs? = null,
@SerializedName("is_top")
private val _isTop: Int? = null,
@SerializedName("is_show_tip")
private val _isShowTip: Int? = null,
) : Parcelable {
val id: String
get() = _id ?: ""
val type: String
get() = _type ?: ""
val user: UserEntity
get() = _user ?: UserEntity()
val isTop: Boolean
get() = _isTop == 1
val isShowTip: Boolean
get() = _isShowTip == 1
val bbs: Bbs
get() = _bbs ?: Bbs()
val isUser: Boolean
get() = type == TYPE_USER
val name: String
get() = if (isUser) {
user.name ?: ""
} else {
bbs.name
}
val icon: String
get() = when {
type == TYPE_USER -> user.icon ?: ""
bbs.type == BBS_TYPE_GAME -> bbs.game.icon ?: ""
else -> bbs.icon
}
override fun equals(other: Any?): Boolean {
return other is FollowUserEntity
&& id == other.id
&& type == other.type
&& user.id == other.user.id
&& user.name == other.user.name
&& bbs.id == other.bbs.id
&& bbs.name == other.bbs.name
&& bbs.type == other.bbs.type
&& bbs.icon == other.bbs.icon
&& bbs.game.icon == other.bbs.game.icon
&& isTop == other.isTop
&& isShowTip == other.isShowTip
&& icon == other.icon
}
override fun hashCode(): Int {
return Objects.hash(
_id,
type,
user.id,
user.name,
bbs.id,
bbs.name,
bbs.type,
bbs.icon,
bbs.game.icon,
isTop,
isShowTip,
icon
)
}
companion object {
private const val TYPE_USER = "user"
private const val BBS_TYPE_GAME = "game_bbs"
}
@Parcelize
data class Bbs(
@SerializedName("_id")
private val _id: String? = null,
@SerializedName("name")
private val _name: String? = null,
@SerializedName("type")
private val _type: String? = null,
@SerializedName("icon")
private val _icon: String? = null,
@SerializedName("game")
private val _game: GameEntity? = null
) : Parcelable {
val id: String
get() = _id ?: ""
val name: String
get() = _name ?: ""
val type: String
get() = _type ?: ""
val icon: String
get() = _icon ?: ""
val game: GameEntity
get() = _game ?: GameEntity()
}
@Parcelize
data class Game(
@SerializedName("_id")
private val _id: String? = null,
@SerializedName("name")
private val _name: String? = null,
@SerializedName("icon")
private val _icon: String? = null,
@SerializedName("ori_icon")
private val _oriIcon: String? = null,
@SerializedName("active")
private val _active: Boolean? = null
) : Parcelable {
val id: String
get() = _id ?: ""
val name: String
get() = _name ?: ""
val icon: String
get() = _icon ?: ""
val oriIcon: String
get() = _oriIcon ?: ""
val active: Boolean
get() = _active ?: false
}
}

View File

@ -11,6 +11,12 @@ class NewApiSettingsEntity(
var startup: StartupAdEntity? = null,//启动文案广告
@SerializedName("user_interested_game")
var userInterestedGame: Boolean = false, //偏好设置状态开关
@SerializedName("installed_compliance_switch")
var installedComplianceSwitch: Boolean? = false, //安装合规开关
@SerializedName("listen_switch")
var isPackageObserveEnable: Boolean = false, // 安装包监听开关
@SerializedName("listen_str")
var packageObserveActions: PackageObserveActions? = null, // 安装包监听的三个 action
var install: Install, // 安装相关的
@SerializedName("game_shield_contents")
var gameShieldContents: List<String>? = listOf(),//游戏屏蔽内容
@ -46,4 +52,13 @@ class NewApiSettingsEntity(
val type: String,
val link: LinkEntity
)
class PackageObserveActions(
@SerializedName("ADD")
val add: String,
@SerializedName("REM")
val rem: String,
@SerializedName("REP")
val rep: String
)
}

View File

@ -4,10 +4,7 @@ import android.os.Parcelable
import com.alibaba.android.arouter.launcher.ARouter
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.core.provider.IConfigProvider
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.MeEntity
import com.gh.gamecenter.feature.entity.SourceEntity
import com.gh.gamecenter.feature.entity.UserEntity
import com.gh.gamecenter.feature.entity.*
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
@ -62,6 +59,16 @@ open class VideoEntity(
@SerializedName("_source")
val ipSource: SourceEntity? = null,
// 关注首页新增字段
@SerializedName("choiceness")
private val _choiceness: Boolean? = null,
@SerializedName("video_info")
private val _videoInfo: VideoInfo? = null,
@SerializedName("show_me_only")
private val _showMeOnly: Boolean? = null,
@SerializedName("count")
private val _count: Count? = null,
//本地数据
@IgnoredOnParcel
var videoIsMuted: Boolean = false,//是否静音标记
@ -73,6 +80,18 @@ open class VideoEntity(
val gameName: String
get() = mGameName.removeSuffix(".")
val choiceness: Boolean
get() = _choiceness ?: false
val videoInfo: VideoInfo
get() = _videoInfo ?: VideoInfo()
val showMeOnly: Boolean
get() = _showMeOnly ?: false
val count: Count
get() = _count ?: Count()
fun getThumb(): String {
val configProvider = ARouter.getInstance().build(RouteConsts.provider.config).navigation() as? IConfigProvider
return if (!configProvider?.getVideoSnapshotSuffix().isNullOrEmpty()) {

View File

@ -0,0 +1,5 @@
package com.gh.gamecenter.entity
class WhitePackageListEntity {
var data: HashSet<String>? = null
}

View File

@ -42,6 +42,10 @@ class ForumArticleAskListAdapter(
private var mVideoOrderList = listOf("推荐", "发布")
private var mFilterPosition = if (path == "视频") 1 else 0
override fun setListData(updateData: MutableList<AnswerEntity>?) {
super.setListData(updateData)
}
override fun areItemsTheSame(oldItem: AnswerEntity?, newItem: AnswerEntity?): Boolean {
return oldItem?.id == newItem?.id
}

View File

@ -10,6 +10,9 @@ import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.updateStatusBarColor
/**
* 论坛详情
*/
class ForumDetailActivity : BaseActivity() {
private var mContainerFragment: Fragment? = null

View File

@ -0,0 +1,86 @@
package com.gh.gamecenter.forum.home
import android.app.Activity
import com.gh.gamecenter.common.entity.AdditionalParamsEntity
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.ShareUtils
import com.gh.gamecenter.common.utils.isPublishEnv
import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.login.user.UserManager
import com.shuyu.gsyvideoplayer.utils.OrientationUtils
class AnswerArticleVideoViewEventHelper(
private val entity: ForumVideoEntity,
private val orientationUtils: OrientationUtils
) : ArticleItemVideoView.OnArticleItemVideoViewEventListener {
override fun onTrackVideoStartPlaying() {
SensorsBridge.trackVideoStartPlaying(
articleId = entity.id,
bbsId = entity.bbs?.id ?: "",
bbsType = entity.bbs?.typeChinese ?: "综合论坛",
customerType = entity.user.auth?.text ?: "",
videoId = entity.id,
playType = "视频贴",
gameForumType = entity.bbs?.game?.categoryChinese ?: "",
activityTag = entity.tagActivityName,
articleType = entity.typeChinese
)
}
override fun onTrackVideoEndPlaying(maxPlayedProgress: Int) {
SensorsBridge.trackVideoEndPlaying(
articleId = entity.id,
bbsId = entity.bbs?.id ?: "",
bbsType = entity.bbs?.typeChinese ?: "综合论坛",
customerType = entity.user.auth?.text ?: "",
videoId = entity.id,
playType = "视频贴",
gameForumType = entity.bbs?.game?.categoryChinese ?: "",
activityTag = entity.tagActivityName,
articleType = entity.typeChinese,
result = if (maxPlayedProgress >= 95) "" else ""
)
}
override fun onShare(videoView: ArticleItemVideoView) {
val shareIcon = entity.poster
val shareUrl = if (isPublishEnv()) {
"https://m.ghzs666.com/video/${entity.id}"
} else {
"https://resource.ghzs.com/page/video_play/video/video.html?video=${entity.id}"
}
val additionalParams = AdditionalParamsEntity().apply {
contentType = "视频帖"
contentId = entity.id ?: ""
bbsId = entity.bbs?.id ?: ""
bbsType = entity.bbs?.typeChinese ?: "综合论坛"
customerType = entity.user.auth?.text ?: ""
activityTagName = entity.tagActivityName ?: ""
gameForumType = entity.bbs?.game?.categoryChinese ?: ""
refUserId = UserManager.getInstance().userId
}
val activity = (videoView.context as? Activity) ?: return
ShareUtils.getInstance(videoView.context).showShareWindowsCallback(activity,
videoView,
shareUrl,
shareIcon,
entity.title,
entity.des,
ShareUtils.ShareEntrance.video,
entity.id,
additionalParams,
object : ShareUtils.ShareCallBack {
override fun onSuccess(label: String) = Unit
override fun onCancel() = Unit
})
}
override fun onMutedChanged(isMuted: Boolean) {
entity.videoIsMuted = isMuted
}
override fun onStartWindowFullscreen() {
orientationUtils.resolveByClick()
}
}

View File

@ -1,7 +1,8 @@
package com.gh.gamecenter.forum.home
import android.app.Activity
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.Surface
@ -12,11 +13,9 @@ import android.widget.TextView
import androidx.core.content.ContextCompat
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.gamecenter.R
import com.gh.gamecenter.common.entity.AdditionalParamsEntity
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.MD5Utils
import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.video.detail.CustomManager
import com.lightgame.utils.Utils
import com.shuyu.gsyvideoplayer.utils.CommonUtil
@ -25,13 +24,12 @@ import com.shuyu.gsyvideoplayer.video.base.GSYVideoView
import com.shuyu.gsyvideoplayer.video.base.GSYVideoViewBridge
import io.reactivex.disposables.Disposable
import java.util.*
import android.os.Handler
import android.os.Looper
class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
StandardGSYVideoPlayer(context, attrs) {
private var mVideoEntity: ForumVideoEntity? = null
var data: ArticleVideoData? = null
private var mMuteDisposable: Disposable? = null
private var mIsAutoPlay = false
var uuid = UUID.randomUUID().toString()
@ -53,6 +51,8 @@ class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: At
private val mTrackHandler = Handler(Looper.getMainLooper())
private var onEventListener: OnArticleItemVideoViewEventListener? = null
override fun getLayoutId(): Int {
return R.layout.layout_article_item_video
}
@ -81,7 +81,7 @@ class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: At
fun startPlayLogic(isAutoPlay: Boolean) {
mIsAutoPlay = isAutoPlay
if (mIsAutoPlay) {
val seekTime = ForumScrollCalculatorHelper.getPlaySchedule(MD5Utils.getContentMD5(mVideoEntity?.url))
val seekTime = ForumScrollCalculatorHelper.getPlaySchedule(MD5Utils.getContentMD5(data?.url))
seekOnStart = seekTime
}
startPlayLogic()
@ -137,7 +137,7 @@ class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: At
}
override fun onClickUiToggle(e: MotionEvent?) {
if (mVideoEntity?.status != "pending" && mVideoEntity?.status != "fail") {
if (data?.status != "pending" && data?.status != "fail") {
if (mCurrentState == CURRENT_STATE_PLAYING) {
if (mStartButton.visibility == View.VISIBLE) {
changeUiToPlayingClear()
@ -156,7 +156,6 @@ class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: At
super.onSurfaceUpdated(surface)
if (mThumbImageViewLayout != null && mThumbImageViewLayout.visibility == View.VISIBLE) {
mThumbImageViewLayout.visibility = View.INVISIBLE
uploadVideoStreamingPlaying("开始播放")
}
}
@ -169,14 +168,9 @@ class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: At
// }
override fun releaseVideos() {
uploadVideoStreamingPlaying("结束播放")
CustomManager.releaseAllVideos(getKey())
}
override fun onVideoPause() {
super.onVideoPause()
uploadVideoStreamingPlaying("暂停播放")
}
// 重载以减少横竖屏切换的时间
override fun checkoutState() {
@ -206,9 +200,7 @@ class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: At
private fun showBackBtn() {
mTopContainer.background = ContextCompat.getDrawable(context, R.drawable.video_title_bg)
back.visibility = View.VISIBLE
mVideoEntity?.run {
titleTv.text = title
}
data?.title?.let(titleTv::setText)
}
private fun hideBackBtn() {
@ -217,19 +209,32 @@ class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: At
titleTv.text = ""
}
fun updateThumb(url: String) {
ImageUtils.display(thumbImage, url)
}
fun updateDurationTv(duration: String) {
durationTv.text = duration
}
fun updateVideoData(video: ForumVideoEntity) {
fun updateVideoData(video: ArticleVideoData, listener: OnArticleItemVideoViewEventListener) {
this.onEventListener = listener
this.mStopTrackRunnable = null
mVideoEntity = video
data = video
titleTv.text = if (mIfCurrentIsFullscreen) video.title else ""
ImageUtils.display(thumbImage, video.poster)
if (!mIfCurrentIsFullscreen) {
durationTv.text = video.duration
setVideoStatus(video.status)
fullscreenButton.setOnClickListener {
val horizontalVideoView =
startWindowFullscreen(context, true, true) as? ArticleItemVideoView
if (horizontalVideoView == null) {
toastInInternalRelease("全屏失败,请向技术人员提供具体的操作步骤")
return@setOnClickListener
}
onEventListener?.onStartWindowFullscreen()
horizontalVideoView.uuid = uuid
horizontalVideoView.updateVideoData(video, listener)
horizontalVideoView.violenceUpdateMuteStatus()
horizontalVideoView.setFullViewStatus()
}
}
}
override fun setViewShowState(view: View?, visibility: Int) {
@ -266,25 +271,16 @@ class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: At
override fun setStateAndUi(state: Int) {
if (state != currentState
&& state == CURRENT_STATE_PLAYING
&& currentState != CURRENT_STATE_PLAYING_BUFFERING_START) {// 上报开始视频播放埋点
&& currentState != CURRENT_STATE_PLAYING_BUFFERING_START
) {// 上报开始视频播放埋点
// 视频停止播放后再恢复播放的间隔时间未超过3秒则取消上报结束视频播放埋点
mStopTrackRunnable?.let { runnable ->
mTrackHandler.removeCallbacks(runnable)
}
mVideoEntity?.let {
data?.let {
val startTrackRunnable = Runnable {
SensorsBridge.trackVideoStartPlaying(
articleId = it.id,
bbsId = it.bbs?.id ?: "",
bbsType = it.bbs?.typeChinese ?: "综合论坛",
customerType = it.user.auth?.text ?: "",
videoId = it.id,
playType = "视频贴",
gameForumType = it.bbs?.game?.categoryChinese ?: "",
activityTag = it.tagActivityName,
articleType = it.typeChinese
)
onEventListener?.onTrackVideoStartPlaying()
mStartTrackRunnable = null
}.also {
mStartTrackRunnable = it
@ -303,20 +299,9 @@ class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: At
mTrackHandler.removeCallbacks(it)
mStartTrackRunnable = null
} ?: let {
mVideoEntity?.let {
data?.let {
val stopTrackRunnable = Runnable {
SensorsBridge.trackVideoEndPlaying(
articleId = it.id,
bbsId = it.bbs?.id ?: "",
bbsType = it.bbs?.typeChinese ?: "综合论坛",
customerType = it.user.auth?.text ?: "",
videoId = it.id,
playType = "视频贴",
gameForumType = it.bbs?.game?.categoryChinese ?: "",
activityTag = it.tagActivityName,
articleType = it.typeChinese,
result = if (mMaxPlayedProgress >= 95) "" else ""
)
onEventListener?.onTrackVideoEndPlaying(mMaxPlayedProgress)
}.also {
this.mStopTrackRunnable = it
}
@ -342,10 +327,9 @@ class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: At
replayTv.setOnClickListener {
startButton.performClick()
violenceUpdateMuteStatus()
uploadVideoStreamingPlaying("重新播放")
}
shareTv.setOnClickListener {
share()
onEventListener?.onShare(this)
}
} else {
setViewShowState(completeContainer, View.GONE)
@ -363,88 +347,33 @@ class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: At
}
private fun toggleMute() {
if (mVideoEntity?.videoIsMuted == true) {
unMute(true)
if (data?.videoIsMuted == true) {
unMute()
} else {
mute(true)
mute()
}
}
fun updateMuteStatus() {
if (mVideoEntity?.videoIsMuted == true) {
private fun updateMuteStatus() {
if (data?.videoIsMuted == true) {
mute()
} else {
unMute()
}
}
fun mute(isManual: Boolean = false) {
mVideoEntity?.videoIsMuted = true
private fun mute() {
onEventListener?.onMutedChanged(true)
data?.videoIsMuted = true
volume.setImageResource(R.drawable.ic_article_video_volume_off)
CustomManager.getCustomManager(getKey()).isNeedMute = true
if (isManual) {
// Utils.toast(context, "当前处于静音状态")
uploadVideoStreamingPlaying("点击静音")
}
}
fun unMute(isManual: Boolean = false) {
mVideoEntity?.videoIsMuted = false
private fun unMute() {
onEventListener?.onMutedChanged(false)
data?.videoIsMuted = false
volume.setImageResource(R.drawable.ic_article_video_volume_on)
CustomManager.getCustomManager(getKey()).isNeedMute = false
if (isManual) {
uploadVideoStreamingPlaying("取消静音")
}
}
override fun onAutoCompletion() {
super.onAutoCompletion()
uploadVideoStreamingPlaying("播放完毕")
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
super.onStopTrackingTouch(seekBar)
uploadVideoStreamingPlaying("拖动")
}
private fun share() {
mVideoEntity?.let {
val shareIcon = it.poster
val shareUrl = if (isPublishEnv()) {
"https://m.ghzs666.com/video/${it.id}"
} else {
"https://dev-and-static.ghzs66.com/page/video_play/video/video.html?video=${it.id}"
}
val additionalParams = AdditionalParamsEntity().apply {
contentType = "视频帖"
contentId = mVideoEntity?.id ?: ""
bbsId = mVideoEntity?.bbs?.id ?: ""
bbsType = mVideoEntity?.bbs?.typeChinese ?: "综合论坛"
customerType = mVideoEntity?.user?.auth?.text ?: ""
activityTagName = mVideoEntity?.tagActivityName ?: ""
gameForumType = mVideoEntity?.bbs?.game?.categoryChinese ?: ""
refUserId = UserManager.getInstance().userId
}
ShareUtils.getInstance(context).showShareWindowsCallback(context as Activity,
this,
shareUrl,
shareIcon,
it.title,
it.des,
ShareUtils.ShareEntrance.video, it.id, additionalParams, object : ShareUtils.ShareCallBack {
override fun onSuccess(label: String) {
// if ("短信" == label || "复制链接" == label) viewModel?.shareVideoStatistics(it)
}
override fun onCancel() {
uploadVideoStreamingPlaying("取消分享")
}
})
}
}
fun uploadVideoStreamingPlaying(action: String) {
}
fun setFullViewStatus() {
@ -456,7 +385,7 @@ class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: At
durationTv.visibility = View.GONE
}
fun setVideoStatus(status: String) {
private fun setVideoStatus(status: String) {
if (status == "pending" || status == "fail") {
pendingView.text = if (status == "pending") "审核中...请耐心等待" else "审核不通过"
pendingView.visibility = View.VISIBLE
@ -494,4 +423,32 @@ class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: At
super.onDetachedFromWindow()
mMuteDisposable?.dispose()
}
companion object {
fun ForumVideoEntity.toArticleVideoData() =
ArticleVideoData(url, title, status, poster, duration, videoIsMuted)
}
data class ArticleVideoData(
val url: String,
val title: String,
val status: String,
val poster: String,
val duration: String,
var videoIsMuted: Boolean
)
interface OnArticleItemVideoViewEventListener {
fun onTrackVideoStartPlaying()
fun onTrackVideoEndPlaying(maxPlayedProgress: Int)
fun onShare(videoView: ArticleItemVideoView)
fun onMutedChanged(isMuted: Boolean)
fun onStartWindowFullscreen()
}
}

View File

@ -8,6 +8,9 @@ import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.constant.EntranceConsts.IS_DETAIL_PAGE
import com.gh.gamecenter.video.detail.HomeVideoFragment
/**
* 社区首页
*/
class CommunityActivity : BaseActivity() {
override fun getLayoutId(): Int = R.layout.activity_community

View File

@ -17,11 +17,9 @@ import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.viewpager.widget.ViewPager
import com.gh.common.browse.BrowseTimer
import com.gh.common.browse.withLifecycle
import com.gh.common.util.NewFlatLogUtils
import com.gh.common.util.DirectUtils
import com.gh.common.util.NewFlatLogUtils
import com.gh.common.util.NewLogUtils
import com.gh.common.util.ViewPagerFragmentHelper
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.TrackableDialog
import com.gh.gamecenter.common.base.adapter.FragmentAdapter
@ -32,6 +30,7 @@ import com.gh.gamecenter.common.constant.EntranceConsts.IS_DETAIL_PAGE
import com.gh.gamecenter.common.retrofit.ApiResponse
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.AvatarBorderView
import com.gh.gamecenter.core.iinterface.IScrollable
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.SPUtils
@ -43,8 +42,11 @@ import com.gh.gamecenter.eventbus.EBSkip
import com.gh.gamecenter.eventbus.EBTypeChange
import com.gh.gamecenter.feature.entity.ArticleEntity
import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.forum.home.follow.FollowHomeFilterPopWindow
import com.gh.gamecenter.forum.home.follow.fragment.FollowHomeFragment
import com.gh.gamecenter.forum.search.ForumOrUserSearchActivity
import com.gh.gamecenter.login.entity.UserInfoEntity
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.login.user.UserRepository
import com.gh.gamecenter.qa.article.detail.ArticleDetailWebCacheManager
import com.gh.gamecenter.qa.article.edit.ArticleEditActivity
@ -54,6 +56,7 @@ import com.gh.gamecenter.qa.video.publish.VideoPublishActivity
import com.gh.gamecenter.video.detail.VideoDetailContainerViewModel
import com.gh.gamecenter.wrapper.MainWrapperViewModel
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
import com.halo.assistant.HaloApp
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
@ -66,7 +69,7 @@ class CommunityHomeFragment : LazyFragment() {
private var mViewModel: CommunityHomeViewModel? = null
private var mMainWrapperViewModel: MainWrapperViewModel? = null
private var mFragmentList = arrayListOf<Fragment>()
private var mTitleList = arrayListOf("推荐", "论坛", "活动")
private var mTitleList = arrayListOf("专注", "推荐", "论坛", "活动")
private var mTabList = arrayListOf<Any>()
private var mDefaultSelectedTab = -1
private var mNavigationBitmap: Bitmap? = null
@ -74,11 +77,46 @@ class CommunityHomeFragment : LazyFragment() {
private var mBottomTabId = ""
private val browseTimer = BrowseTimer()
.withLifecycle(this)
.withResult {
SensorsBridge.trackCommunityBrowsingDuration(it / 1000.0)
}
private val followFilterPopWindow by lazy {
FollowHomeFilterPopWindow.create(requireContext()).apply {
setOnDismissListener {
resetFollowTab()
}
setFilterListener(object : FollowHomeFilterPopWindow.OnFilteredChangedListener {
override fun filterFollowed(position: Int) {
mViewModel?.filterFollowed(position)
}
})
}
}
private val obTabSelectedListener = object : OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
if (tab?.position == TAB_FOLLOW_INDEX) {
mViewModel?.updateFilterResId(R.drawable.ic_follow_arrow_down)
}
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
if (tab?.position == TAB_FOLLOW_INDEX) {
mViewModel?.updateFilterResId(null)
}
}
override fun onTabReselected(tab: TabLayout.Tab?) {
if (!followFilterPopWindow.isShowing && tab?.position == TAB_FOLLOW_INDEX) {
mViewModel?.updateFilterResId(R.drawable.ic_follow_arrow_up)
mBinding?.tabLayout?.let(followFilterPopWindow::showAsDropDown)
}
}
}
override fun getRealLayoutId(): Int {
return R.layout.fragment_community_home
}
@ -97,6 +135,19 @@ class CommunityHomeFragment : LazyFragment() {
super.onFragmentFirstVisible()
initViewPager()
mViewModel?.followFilterStatus?.observe(viewLifecycleOwner, Observer { resId ->
val tabLayout = mBinding?.tabLayout ?: return@Observer
val tab = tabLayout.getTabAt(TAB_FOLLOW_INDEX)
val tvTitle = tab?.customView?.findViewById<TextView>(R.id.tab_title)
if (resId != null) {
tvTitle?.setDrawableEnd(resId, 8F.dip2px(), 8F.dip2px())
} else {
tvTitle?.setDrawableEnd(null, 8F.dip2px(), 8F.dip2px())
}
})
}
override fun initRealView() {
@ -147,7 +198,13 @@ class CommunityHomeFragment : LazyFragment() {
topBg.visibleIf(!mIsDarkModeOn)
videoLottie.setOnClickListener {
DirectUtils.directToLegacyVideoDetail(requireContext(), "", VideoDetailContainerViewModel.Location.VIDEO_ACTIVITY.value, referer = "视频流-社区右上角", isHomeVideo = true)
DirectUtils.directToLegacyVideoDetail(
requireContext(),
"",
VideoDetailContainerViewModel.Location.VIDEO_ACTIVITY.value,
referer = "视频流-社区右上角",
isHomeVideo = true
)
}
searchIconIv.setOnClickListener {
NewLogUtils.logCommunitySearchClick()
@ -202,13 +259,14 @@ class CommunityHomeFragment : LazyFragment() {
mBottomTabId = arguments?.getString(EntranceConsts.KEY_BOTTOM_TAB_ID, "") ?: ""
}
override fun setUserVisibleHint(isVisibleToUser: Boolean) {
super.setUserVisibleHint(isVisibleToUser)
if (isVisibleToUser) {
browseTimer.start()
} else {
browseTimer.stop()
}
override fun onResume() {
super.onResume()
browseTimer.start()
}
override fun onPause() {
super.onPause()
browseTimer.stop()
}
override fun onSaveInstanceState(outState: Bundle) {
@ -236,7 +294,12 @@ class CommunityHomeFragment : LazyFragment() {
mTabList.clear()
mFragmentList.clear()
val tag = "android:switcher:${viewPager.id}:"
val forumArticleListFragment = childFragmentManager.findFragmentByTag("${tag}0")
val followFragment = childFragmentManager.findFragmentByTag("$tag$TAB_FOLLOW_INDEX")
?: FollowHomeFragment()
mFragmentList.add(followFragment)
val forumArticleListFragment = childFragmentManager.findFragmentByTag("$tag$TAB_RECOMMEND_INDEX")
?: ForumArticleListFragment().with(
bundleOf(
EntranceConsts.KEY_ENTRANCE to "社区",
@ -245,11 +308,11 @@ class CommunityHomeFragment : LazyFragment() {
)
mFragmentList.add(forumArticleListFragment)
val forumFragment = childFragmentManager.findFragmentByTag("${tag}1")
val forumFragment = childFragmentManager.findFragmentByTag("${tag}$TAB_FORUM_INDEX")
?: ForumFragment().with(bundleOf(EntranceConsts.KEY_ENTRANCE to "社区"))
mFragmentList.add(forumFragment)
val activityFragment = childFragmentManager.findFragmentByTag("${tag}2")
val activityFragment = childFragmentManager.findFragmentByTag("${tag}$TAB_ACTIVITY_INDEX")
?: ForumActivityFragment().with(bundleOf(EntranceConsts.KEY_ENTRANCE to "活动"))
mFragmentList.add(activityFragment)
@ -263,8 +326,16 @@ class CommunityHomeFragment : LazyFragment() {
adapter = FragmentAdapter(childFragmentManager, mFragmentList, mTitleList)
doOnScroll(
onPageSelected = { position ->
communityEditBtn.goneIf(position != 0)
communityEditBtn.goneIf(position != TAB_RECOMMEND_INDEX && position != TAB_FOLLOW_INDEX)
when (position) {
TAB_FOLLOW_INDEX -> {
root.setBackgroundColor(R.color.ui_background.toColor(requireContext()))
topBg.translationY = 0F
changeNavigationBg()
NewLogUtils.logCommunityHomeEvent("click_for_you_tab")
SensorsBridge.trackCommunityTopTabSelected("关注")
}
TAB_RECOMMEND_INDEX -> {
root.setBackgroundColor(R.color.ui_background.toColor(requireContext()))
topBg.translationY = 0F
@ -275,7 +346,7 @@ class CommunityHomeFragment : LazyFragment() {
TAB_FORUM_INDEX -> {
root.setBackgroundColor(R.color.ui_surface.toColor(requireContext()))
(mFragmentList[1] as ForumFragment).translationY.run {
(mFragmentList[2] as ForumFragment).translationY.run {
topBg.translationY = -this.toFloat()
changeNavigationBg(this)
}
@ -285,7 +356,7 @@ class CommunityHomeFragment : LazyFragment() {
TAB_ACTIVITY_INDEX -> {
root.setBackgroundColor(R.color.ui_background.toColor(requireContext()))
(mFragmentList[2] as ForumActivityFragment).translationY.run {
(mFragmentList[3] as ForumActivityFragment).translationY.run {
topBg.translationY = -this.toFloat()
changeNavigationBg(this)
}
@ -358,6 +429,7 @@ class CommunityHomeFragment : LazyFragment() {
}
)
}
tabLayout.addOnTabSelectedListener(obTabSelectedListener)
tabLayout.setupWithViewPager(viewPager)
indicatorView.run {
@ -365,13 +437,20 @@ class CommunityHomeFragment : LazyFragment() {
setupWithViewPager(viewPager)
setIndicatorWidth(18)
}
val selectedPosition = if (UserManager.getInstance().isLoggedIn) {
TAB_FOLLOW_INDEX
} else {
TAB_RECOMMEND_INDEX
}
for (i in 0 until tabLayout.tabCount) {
val tab = tabLayout.getTabAt(i) ?: continue
val tabTitle = if (tab.text != null) tab.text.toString() else ""
val tabViewBinding = generateTabView(tabTitle, i)
val tabViewBinding = generateTabView(tabTitle, i, selectedPosition)
tab.customView = tabViewBinding.root
tab.view.setPadding(0, 0, 0, 0)
}
viewPager.setCurrentItem(selectedPosition, false)
}
}
@ -451,7 +530,7 @@ class CommunityHomeFragment : LazyFragment() {
}
}
private fun generateTabView(title: String, index: Int): TabItemCommunityBinding {
private fun generateTabView(title: String, index: Int, selectedPosition: Int): TabItemCommunityBinding {
val binding = TabItemCommunityBinding.inflate(LayoutInflater.from(requireContext()))
binding.run {
if (index == TAB_ACTIVITY_INDEX) {
@ -476,6 +555,11 @@ class CommunityHomeFragment : LazyFragment() {
text = title
textSize = DEFAULT_TAB_TEXT_SIZE
setTextColor(TAB_DEFAULT_COLOR.toColor(requireContext()))
if (index == TAB_FOLLOW_INDEX && selectedPosition == TAB_FOLLOW_INDEX) {
mViewModel?.updateFilterResId(R.drawable.ic_follow_arrow_down)
// 如果关注页为默认选中刚页面TabLayout还来不及初始化tabLayout.getTabAt(0)为null所以需要在创建tabView的时候提前设置
setDrawableEnd(R.drawable.ic_follow_arrow_down, 8F.dip2px(), 8F.dip2px())
}
}
}
}
@ -570,6 +654,18 @@ class CommunityHomeFragment : LazyFragment() {
}
}
private fun resetFollowTab() {
mBinding?.tabLayout?.run {
if (selectedTabPosition == TAB_FOLLOW_INDEX) {
mViewModel?.updateFilterResId(R.drawable.ic_follow_arrow_down)
} else {
mViewModel?.updateFilterResId(null)
}
}
mBinding?.tabLayout?.removeOnTabSelectedListener(obTabSelectedListener)
mBinding?.tabLayout?.addOnTabSelectedListener(obTabSelectedListener)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
@ -596,13 +692,13 @@ class CommunityHomeFragment : LazyFragment() {
}
private fun insertDataToRecommendTab(entity: ArticleEntity) {
(mFragmentList[0] as? ForumArticleListFragment)?.insertDataToFirstIndex(entity)
(mFragmentList[TAB_RECOMMEND_INDEX] as? ForumArticleListFragment)?.insertDataToFirstIndex(entity)
}
override fun onBackPressed(): Boolean {
mBinding?.viewPager?.run {
if (currentItem == 0) {
return (mFragmentList[0] as ForumArticleListFragment).onBackPressed()
if (currentItem == 1) {
return (mFragmentList[1] as ForumArticleListFragment).onBackPressed()
}
}
return super.onBackPressed()
@ -610,7 +706,10 @@ class CommunityHomeFragment : LazyFragment() {
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(status: EBTypeChange) {
if (mBinding?.viewPager?.currentItem != 0) return
val currentPosition = mBinding?.viewPager?.currentItem ?: 0
if (currentPosition != TAB_FOLLOW_INDEX && currentPosition != TAB_RECOMMEND_INDEX) {
return
}
if (status.type == EB_SHOW_QUESTION_BUTTON) {
setPutQuestionButtonStatus(View.VISIBLE)
@ -636,8 +735,14 @@ class CommunityHomeFragment : LazyFragment() {
private fun scrollToTop() {
if (mFragmentList.isEmpty()) return
(mFragmentList[0] as ForumArticleListFragment).scrollToTop()
setPutQuestionButtonStatus(View.VISIBLE)
val currentPosition = mBinding?.viewPager?.currentItem ?: 0
val fragment = mFragmentList.getOrNull(currentPosition)
if (fragment is IScrollable) {
fragment.scrollToTop()
}
if (currentPosition == TAB_FOLLOW_INDEX || currentPosition == TAB_RECOMMEND_INDEX) {
setPutQuestionButtonStatus(View.VISIBLE)
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
@ -732,9 +837,10 @@ class CommunityHomeFragment : LazyFragment() {
var SCALE_TAB_IMG_WIDTH = 42F
var SCALE_TAB_IMG_HEIGHT = 22F
const val EB_TAB = "forum_tab"
const val TAB_RECOMMEND_INDEX = 0
const val TAB_FORUM_INDEX = 1
const val TAB_ACTIVITY_INDEX = 2
const val TAB_FOLLOW_INDEX = 0
const val TAB_RECOMMEND_INDEX = 1
const val TAB_FORUM_INDEX = 2
const val TAB_ACTIVITY_INDEX = 3
const val ARTICLE_REQUEST_CODE = 200
const val QUESTION_REQUEST_CODE = 201
const val VIDEO_REQUEST_CODE = 202

View File

@ -1,19 +1,20 @@
package com.gh.gamecenter.forum.home
import android.app.Application
import androidx.lifecycle.*
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import com.gh.gamecenter.common.entity.CommunityEntity
import com.gh.gamecenter.common.utils.clearHtmlFormatCompletely
import com.gh.gamecenter.common.utils.observableToMain
import com.gh.gamecenter.common.utils.removeInsertedContent
import com.gh.gamecenter.common.utils.removeVideoContent
import com.gh.gamecenter.qa.entity.ArticleDetailEntity
import com.gh.gamecenter.feature.entity.ArticleEntity
import com.gh.gamecenter.qa.entity.QuestionsDetailEntity
import com.gh.gamecenter.feature.entity.TimeEntity
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.feature.entity.ArticleEntity
import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.feature.entity.TimeEntity
import com.gh.gamecenter.feature.entity.UserEntity
import com.gh.gamecenter.livedata.Event
import com.gh.gamecenter.qa.entity.ArticleDetailEntity
import com.gh.gamecenter.qa.entity.QuestionsDetailEntity
import com.gh.gamecenter.retrofit.RetrofitManager
class CommunityHomeViewModel(application: Application) : AndroidViewModel(application) {
@ -131,4 +132,64 @@ class CommunityHomeViewModel(application: Application) : AndroidViewModel(applic
return articleEntity
}
private val _hasFollowedUsers = MutableLiveData<Boolean>()
val hasFollowedUser: LiveData<Boolean> = _hasFollowedUsers
fun updateHasFollowUser(show: Boolean) {
_hasFollowedUsers.value = show
}
private val filterDrawableResId = MutableLiveData<Int?>()
fun updateFilterResId(resId: Int?) {
filterDrawableResId.value = resId
}
val followFilterStatus = MediatorLiveData<Int?>().apply {
addSource(hasFollowedUser) {
if (it) {
value = filterDrawableResId.value
} else {
value = null
}
}
addSource(filterDrawableResId) { resId ->
// 当 hasFollowedUser.value == null 时不需要调用setValue
if (hasFollowedUser.value == true) {
value = resId
} else if (hasFollowedUser.value == false) {
value = null
}
when {
resId == null ||
hasFollowedUser.value == false -> {
// 当resId == null 时表示已切换到别的tab 直接setValue
// 当 resId != null && hasFollowedUser,value 为false时说明关注页数据已加载完毕并且没有关注任何用户直接setValue
value = null
}
hasFollowedUser.value == true -> {
// 当 resId = null 且 有关注用户,显示当前 resId
value = resId
}
else -> {
// do Nothing 当resId != null 但是hasFollowedUser.value == null,说明关注页数据正在loading等待无需setValue
}
}
}
}
private val _filterFollowedAction = MutableLiveData<Event<Int>>()
val filterFollowedAction: LiveData<Event<Int>> = _filterFollowedAction
fun filterFollowed(position: Int) {
val filterName = when (position) {
0 -> "全部"
1 -> "关注的人"
else -> "关注的游戏"
}
SensorsBridge.trackFollowTabFilterOptionClick(filterName)
_filterFollowedAction.value = Event(position)
}
}

View File

@ -13,6 +13,7 @@ import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.SpacingItemDecoration
import com.gh.gamecenter.core.iinterface.IScrollable
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.databinding.FragmentForumActivityBinding
import com.gh.gamecenter.databinding.LayoutForumActivityCategoryItemBinding
@ -20,7 +21,7 @@ import com.gh.gamecenter.entity.ForumActivityCategoryEntity
import com.gh.gamecenter.entity.ForumActivityEntity
import com.google.android.flexbox.FlexboxLayout
class ForumActivityFragment : LazyListFragment<ForumActivityEntity, ForumActivityViewModel>() {
class ForumActivityFragment : LazyListFragment<ForumActivityEntity, ForumActivityViewModel>(), IScrollable {
private var mBinding: FragmentForumActivityBinding? = null
private var mAdapter: ForumActivityAdapter? = null
@ -200,4 +201,12 @@ class ForumActivityFragment : LazyListFragment<ForumActivityEntity, ForumActivit
}
changeCategoryBg()
}
override fun scrollToTop() {
mBinding?.listRv?.let {
if (it.visibility == View.VISIBLE) {
it.scrollToPosition(0)
}
}
}
}

View File

@ -6,13 +6,18 @@ import android.graphics.Color
import android.graphics.drawable.GradientDrawable
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
import com.gh.common.view.ImageContainerView.Companion.toImageContainerData
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
@ -28,6 +33,7 @@ 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.forum.home.ArticleItemVideoView.Companion.toArticleVideoData
import com.gh.gamecenter.forum.search.CommunitySearchEventListener
import com.gh.gamecenter.forum.search.CommunitySearchEventListener.Companion.SEARCH_BUTTON_COMMENT
import com.gh.gamecenter.forum.search.CommunitySearchEventListener.Companion.SEARCH_BUTTON_DISLIKE
@ -42,6 +48,7 @@ import com.gh.gamecenter.qa.entity.QuestionsDetailEntity
import com.gh.gamecenter.qa.questions.invite.QuestionsInviteActivity
import com.gh.gamecenter.qa.questions.newdetail.NewQuestionDetailActivity
import com.gh.gamecenter.qa.video.detail.ForumVideoDetailActivity
import com.gh.gamecenter.video.detail.VideoDetailContainerViewModel
import com.shuyu.gsyvideoplayer.builder.GSYVideoOptionBuilder
import com.shuyu.gsyvideoplayer.listener.GSYSampleCallBack
import com.shuyu.gsyvideoplayer.utils.OrientationUtils
@ -148,15 +155,46 @@ class ForumArticleAskItemViewHolder(
}
}
binding.imageContainer.bindData(entity, entrance, path) {
clickListener?.onItemChildClick(
entity.id,
entity.type,
htmlToString(entity.questions.title),
position,
SEARCH_BUTTON_VIEW_IMAGE
)
}
binding.imageContainer.bindData(
entity.toImageContainerData(),
object : ImageContainerView.OnImageContainerEventListener {
override fun onImageClick(
images: List<String>,
position: Int,
imageViewList: ArrayList<SimpleDraweeView>,
) {
clickListener?.onItemChildClick(
entity.id,
entity.type,
htmlToString(entity.questions.title),
position,
SEARCH_BUTTON_VIEW_IMAGE,
)
if (entity.communityId.isNullOrEmpty()) {
entity.communityId = entity.bbs.id
}
val intent = ImageViewerActivity.getIntent(
itemView.context, entity.images as ArrayList<String>, position, imageViewList,
if (entity.type == "community_article") entity else null, entrance, true,
)
itemView.context.startActivity(intent)
}
override fun onVideoCLick(videoId: String) {
DirectUtils.directToVideoDetail(
itemView.context,
videoId,
VideoDetailContainerViewModel.Location.VIDEO_HOT.value,
showComment = false,
entrance = entrance,
path = path
)
}
},
)
bindVideoData(entity.transformForumVideoEntity())
val user = entity.user
@ -315,34 +353,13 @@ class ForumArticleAskItemViewHolder(
.setVideoAllCallBack(object : GSYSampleCallBack() {
override fun onQuitFullscreen(url: String?, vararg objects: Any) {
orientationUtils.backToProtVideo()
visibleView.uploadVideoStreamingPlaying("退出全屏")
}
})
.build(visibleView)
visibleView.run {
updateVideoData(entity)
updateThumb(entity.poster)
updateDurationTv(entity.duration)
setVideoStatus(entity.status)
fullscreenButton.setOnClickListener {
val horizontalVideoView =
startWindowFullscreen(itemView.context, true, true) as? ArticleItemVideoView
if (horizontalVideoView == null) {
toastInInternalRelease("全屏失败,请向技术人员提供具体的操作步骤")
return@setOnClickListener
}
orientationUtils.resolveByClick()
horizontalVideoView.uuid = uuid
horizontalVideoView.updateVideoData(entity)
horizontalVideoView.updateThumb(entity.poster)
horizontalVideoView.violenceUpdateMuteStatus()
horizontalVideoView.setFullViewStatus()
uploadVideoStreamingPlaying("开始播放")
uploadVideoStreamingPlaying("点击全屏")
}
}
visibleView.updateVideoData(
entity.toArticleVideoData(),
AnswerArticleVideoViewEventHelper(entity, orientationUtils)
)
}
}
}
@ -382,7 +399,13 @@ class ForumArticleAskItemViewHolder(
SEARCH_BUTTON_VIEW_FORUM_DETAIL
)
MtaHelper.onEvent(getEventId(entrance), getKey(entrance), entity.community.name)
itemView.context.startActivity(ForumDetailActivity.getIntent(itemView.context, entity.community.id, entrance))
itemView.context.startActivity(
ForumDetailActivity.getIntent(
itemView.context,
entity.community.id,
entrance
)
)
LogUtils.uploadAccessToBbs(entity.community.id, "文章外所属论坛")
}

View File

@ -22,6 +22,7 @@ import com.gh.gamecenter.common.base.fragment.LazyFragment
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.GridSpacingItemColorDecoration
import com.gh.gamecenter.core.iinterface.IScrollable
import com.gh.gamecenter.databinding.ForumBannerIndicatorItemBinding
import com.gh.gamecenter.databinding.FragmentForumBinding
import com.gh.gamecenter.entity.ForumBannerEntity
@ -34,7 +35,7 @@ import com.halo.assistant.HaloApp
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class ForumFragment : LazyFragment(), SwipeRefreshLayout.OnRefreshListener {
class ForumFragment : LazyFragment(), SwipeRefreshLayout.OnRefreshListener, IScrollable {
private var mBinding: FragmentForumBinding? = null
private var mViewModel: ForumViewModel? = null
@ -513,4 +514,12 @@ class ForumFragment : LazyFragment(), SwipeRefreshLayout.OnRefreshListener {
super.onDarkModeChanged()
mBinding?.hotForumRv?.adapter?.let { it.notifyItemRangeChanged(0, it.itemCount) }
}
override fun scrollToTop() {
mBinding?.contentContainer?.let {
if (it.visibility == View.VISIBLE) {
it.scrollTo(0, 0)
}
}
}
}

View File

@ -0,0 +1,31 @@
package com.gh.gamecenter.forum.home.follow
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.forum.home.follow.fragment.AllFollowedFragment
class AllFollowedActivity : BaseActivity() {
override fun getLayoutId(): Int = R.layout.activity_all_followed
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DisplayUtils.setLightStatusBar(this, true)
setStatusBarColor(Color.TRANSPARENT)
val containerFragment = supportFragmentManager.findFragmentByTag(AllFollowedFragment::class.java.name)
?: AllFollowedFragment().with(intent.extras)
// 若 placeholder 外层为 RelativeLayout 的话,会出现莫名的偏移
supportFragmentManager.beginTransaction()
.replace(R.id.layout_activity_content, containerFragment, AllFollowedFragment::class.java.name)
.commitAllowingStateLoss()
}
companion object {
fun getIntent(context: Context): Intent = Intent(context, AllFollowedActivity::class.java)
}
}

View File

@ -0,0 +1,50 @@
package com.gh.gamecenter.forum.home.follow
import com.gh.gamecenter.entity.FollowUserEntity
abstract class AllFollowedItem() {
abstract val itemType: Int
open fun areItemsTheSame(other: AllFollowedItem): Boolean {
return other.itemType == itemType && doAreItemsTheSame(other)
}
open fun areContentsTheSame(other: AllFollowedItem): Boolean {
return other.itemType == itemType && doAreContentsTheSame(other)
}
open fun doAreItemsTheSame(other: AllFollowedItem): Boolean = true
open fun doAreContentsTheSame(other: AllFollowedItem): Boolean = this == other
companion object {
const val ALL_FOLLOWED_ITEM_TYPE_NO_TOP = 0
const val ALL_FOLLOWED_ITEM_TYPE_NORMAL = 1
const val ALL_FOLLOWED_ITEM_TYPE_MY_FOLLOW_HEADER = 2
}
}
class AllFollowedNoTopItem : AllFollowedItem() {
override val itemType: Int
get() = ALL_FOLLOWED_ITEM_TYPE_NO_TOP
}
data class AllFollowedMyFollowedHeaderItem(val isTop: Boolean) : AllFollowedItem() {
override val itemType: Int
get() = ALL_FOLLOWED_ITEM_TYPE_MY_FOLLOW_HEADER
}
data class AllFollowedNormalItem(
val data: FollowUserEntity,
var isTop: Boolean
) : AllFollowedItem() {
override val itemType: Int
get() = ALL_FOLLOWED_ITEM_TYPE_NORMAL
override fun doAreItemsTheSame(other: AllFollowedItem): Boolean {
return other is AllFollowedNormalItem && data.id == other.data.id
}
}

View File

@ -0,0 +1,306 @@
package com.gh.gamecenter.forum.home.follow
import android.content.Context
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.ActivityResultRegistry
import androidx.activity.result.contract.ActivityResultContract
import androidx.activity.result.contract.ActivityResultContracts.GetContent
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.gh.common.util.SyncDataBetweenPageHelper
import com.gh.gamecenter.common.entity.CommunityEntity
import com.gh.gamecenter.entity.PersonalHistoryEntity
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.feature.entity.ArticleEntity
import com.gh.gamecenter.feature.entity.CommentEntity
import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.personalhome.home.UserHistoryFragment
import com.gh.gamecenter.qa.article.detail.ArticleDetailActivity
import com.gh.gamecenter.qa.entity.ArticleDetailEntity
import com.gh.gamecenter.qa.entity.QuestionsDetailEntity
import com.gh.gamecenter.qa.questions.newdetail.NewQuestionDetailActivity
import com.gh.gamecenter.qa.video.detail.ForumVideoDetailActivity
class FollowActivityResultLauncher(
private val registry: ActivityResultRegistry,
private val onResultListener: OnResultListener
) : DefaultLifecycleObserver {
private lateinit var articleDetailLauncher: ActivityResultLauncher<LauncherDestination>
private lateinit var commentEntityLauncher: ActivityResultLauncher<LauncherDestination>
private lateinit var questionsDetailLauncher: ActivityResultLauncher<LauncherDestination>
private lateinit var forumVideoLauncher: ActivityResultLauncher<LauncherDestination>
override fun onCreate(owner: LifecycleOwner) {
super.onCreate(owner)
articleDetailLauncher = registry.register(
KEY_ARTICLE_DETAIL_LAUNCHER,
owner,
object : ActivityResultContract<LauncherDestination, ArticleDetailResult?>() {
override fun createIntent(context: Context, input: LauncherDestination): Intent {
val intent =
if (input.historyEntity != null) {
ArticleDetailActivity.getIntent(
context,
input.historyEntity.community,
input.historyEntity.id,
"",
UserHistoryFragment.PATH_USER_QUESTION_ANSWER,
sourceEntrance = "关注-用户动态"
)
} else {
ArticleDetailActivity.getIntent(
context,
CommunityEntity(input.answerEntity?.bbs?.id ?: ""),
input.answerEntity?.id ?: "",
"",
"",
sourceEntrance = "关注-论坛动态",
)
}
intent.putExtra(SyncDataBetweenPageHelper.DATA_POSITION_TAG, input.position)
intent.putExtra(SyncDataBetweenPageHelper.REQUEST_CODE_TAG, 101)
return intent
}
override fun parseResult(resultCode: Int, intent: Intent?): ArticleDetailResult? {
val position = intent?.getIntExtra(SyncDataBetweenPageHelper.DATA_POSITION_TAG, -1)
val resultData =
intent?.getParcelableExtra<ArticleDetailEntity>(ArticleDetailEntity::class.java.simpleName)
if (position != null && resultData != null) {
return ArticleDetailResult(position, resultData)
} else {
return null
}
}
})
{
it?.run {
onResultListener.onArticleDetailResult(position, articleDetailEntity)
}
}
commentEntityLauncher = registry.register(
KEY_COMMENT_LAUNCHER,
owner,
object : ActivityResultContract<LauncherDestination, CommentResult?>() {
override fun createIntent(context: Context, input: LauncherDestination): Intent {
val intent = NewQuestionDetailActivity.getSpecifiedCommentIntent(
context,
input.historyEntity?.question?.id ?: "",
input.historyEntity?.id ?: "",
"",
UserHistoryFragment.PATH_USER_QUESTION_ANSWER,
sourceEntrance = "关注-动态"
)
intent.putExtra(SyncDataBetweenPageHelper.DATA_POSITION_TAG, input.position)
intent.putExtra(SyncDataBetweenPageHelper.REQUEST_CODE_TAG, 102)
return intent
}
override fun parseResult(resultCode: Int, intent: Intent?): CommentResult? {
val position = intent?.getIntExtra(SyncDataBetweenPageHelper.DATA_POSITION_TAG, -1)
val commentEntity = intent?.getParcelableExtra<CommentEntity>(CommentEntity::class.java.simpleName)
if (position != null && commentEntity != null) {
return CommentResult(position, commentEntity)
} else {
return null
}
}
})
{
it?.run {
onResultListener.onCommentResult(position, commentEntity)
}
}
questionsDetailLauncher = registry.register(
KEY_QUESTION_DETAIL_LAUNCHER,
owner,
object : ActivityResultContract<LauncherDestination, QuestionsDetailResult?>() {
override fun createIntent(context: Context, input: LauncherDestination): Intent {
val intent = if (input.historyEntity != null) {
NewQuestionDetailActivity.getIntent(
context,
input.historyEntity.id,
"",
UserHistoryFragment.PATH_USER_QUESTION_ANSWER
)
} else {
if (input.answerEntity?.type == "question") {
NewQuestionDetailActivity.getIntent(
context, input.answerEntity.id,
"",
"",
sourceEntrance = "关注-论坛动态"
)
} else {
NewQuestionDetailActivity.getCommentIntent(
context,
input.answerEntity?.questions?.id ?: "",
input.answerEntity?.answerId ?: "",
"",
""
)
}
}
intent.putExtra(SyncDataBetweenPageHelper.DATA_POSITION_TAG, input.position)
intent.putExtra(SyncDataBetweenPageHelper.REQUEST_CODE_TAG, 103)
return intent
}
override fun parseResult(resultCode: Int, intent: Intent?): QuestionsDetailResult? {
val position = intent?.getIntExtra(SyncDataBetweenPageHelper.DATA_POSITION_TAG, -1)
val questionsDetailEntity =
intent?.getParcelableExtra<QuestionsDetailEntity>(QuestionsDetailEntity::class.java.simpleName)
return if (position != null && questionsDetailEntity != null) {
QuestionsDetailResult(position, questionsDetailEntity)
} else {
null
}
}
}
) {
it?.run {
onResultListener.onQuestionsDetailResult(position, questionsDetailEntity)
}
}
forumVideoLauncher = registry.register(
KEY_FORUM_VIDEO_LAUNCHER,
owner,
object : ActivityResultContract<LauncherDestination, ForumVideoResult?>() {
override fun createIntent(context: Context, input: LauncherDestination): Intent {
val intent = if (input.historyEntity != null) {
ForumVideoDetailActivity.getIntent(
context,
input.historyEntity.id,
input.historyEntity.community.id,
sourceEntrance = "关注-个人动态"
)
} else {
ForumVideoDetailActivity.getIntent(
context,
input.answerEntity?.id ?: "",
input.answerEntity?.bbs?.id ?: "",
"关注-论坛动态"
)
}
intent.putExtra(SyncDataBetweenPageHelper.DATA_POSITION_TAG, input.position)
intent.putExtra(SyncDataBetweenPageHelper.REQUEST_CODE_TAG, 104)
return intent
}
override fun parseResult(resultCode: Int, intent: Intent?): ForumVideoResult? {
val position = intent?.getIntExtra(SyncDataBetweenPageHelper.DATA_POSITION_TAG, -1)
val forumVideoEntity =
intent?.getParcelableExtra<ForumVideoEntity>(ForumVideoEntity::class.java.simpleName)
return if (position != null && forumVideoEntity != null) {
ForumVideoResult(position, forumVideoEntity)
} else {
null
}
}
}
)
{
it?.run {
onResultListener.onForumVideoResult(position, forumVideoEntity)
}
}
}
fun onPersonItemClick(position: Int, entity: PersonalHistoryEntity) {
when {
entity.type == "community_article"
|| entity.type == "community_article_vote" -> {
articleDetailLauncher.launch(LauncherDestination(position, historyEntity = entity))
}
entity.type.contains("video") -> {
forumVideoLauncher.launch(LauncherDestination(position, historyEntity = entity))
}
entity.type.contains("question") -> {
questionsDetailLauncher.launch(LauncherDestination(position, historyEntity = entity))
}
else -> {
commentEntityLauncher.launch(LauncherDestination(position, historyEntity = entity))
}
}
}
fun onBbsItemClick(position: Int, answerEntity: AnswerEntity) {
when (answerEntity.type) {
"community_article" -> {
articleDetailLauncher.launch(LauncherDestination(position, answerEntity = answerEntity))
}
"video" -> {
forumVideoLauncher.launch(LauncherDestination(position, answerEntity = answerEntity))
}
"question" -> {
questionsDetailLauncher.launch(LauncherDestination(position, answerEntity = answerEntity))
}
"answer" -> {
questionsDetailLauncher.launch(LauncherDestination(position, answerEntity = answerEntity))
}
}
}
companion object {
private const val KEY_ARTICLE_DETAIL_LAUNCHER = "key_article_detail_launcher"
private const val KEY_COMMENT_LAUNCHER = "key_comment_launcher"
private const val KEY_QUESTION_DETAIL_LAUNCHER = "key_question_detail_launcher"
private const val KEY_FORUM_VIDEO_LAUNCHER = "KEY_forum_video_LAUNCHER"
}
private data class LauncherDestination(
val position: Int,
val historyEntity: PersonalHistoryEntity? = null,
val answerEntity: AnswerEntity? = null
)
private data class ArticleDetailResult(
val position: Int,
val articleDetailEntity: ArticleDetailEntity
)
private data class CommentResult(
val position: Int,
val commentEntity: CommentEntity
)
private data class QuestionsDetailResult(
val position: Int,
val questionsDetailEntity: QuestionsDetailEntity
)
private data class ForumVideoResult(
val position: Int,
val forumVideoEntity: ForumVideoEntity
)
interface OnResultListener {
fun onArticleDetailResult(position: Int, articleDetailEntity: ArticleDetailEntity)
fun onCommentResult(position: Int, commentEntity: CommentEntity)
fun onQuestionsDetailResult(position: Int, questionsDetailEntity: QuestionsDetailEntity)
fun onForumVideoResult(position: Int, forumVideoEntity: ForumVideoEntity)
}
}

View File

@ -0,0 +1,32 @@
package com.gh.gamecenter.forum.home.follow
import android.content.Context
import android.graphics.Canvas
import android.graphics.Path
import android.util.AttributeSet
import androidx.core.content.ContextCompat
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.core.utils.DisplayUtils
class FollowClippedView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : androidx.appcompat.widget.AppCompatImageView(context, attrs, defStyleAttr) {
override fun onDraw(canvas: Canvas) {
val path = Path()
path.addRect(
0f,
0f,
width.toFloat(),
(DisplayUtils.getStatusBarHeight(resources) + 52f.dip2px() + 56f.dip2px()).toFloat(),
Path.Direction.CW
)
canvas.clipPath(path)
canvas.drawColor(ContextCompat.getColor(context, R.color.ui_background))
super.onDraw(canvas)
}
}

View File

@ -0,0 +1,51 @@
package com.gh.gamecenter.forum.home.follow
import android.content.Context
import android.content.Intent
import android.os.Bundle
import androidx.activity.viewModels
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.utils.toArrayList
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.entity.FollowUserEntity
import com.gh.gamecenter.forum.home.follow.fragment.FollowDynamicFragment
import com.gh.gamecenter.forum.home.follow.viewmodel.FollowDynamicViewModel
class FollowDynamicActivity : BaseActivity() {
private val viewModel by viewModels<FollowDynamicViewModel>()
override fun getLayoutId(): Int = R.layout.activity_amway
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DisplayUtils.transparentStatusAndNavigation(this)
DisplayUtils.setLightStatusBar(this, true)
val containerFragment = supportFragmentManager.findFragmentByTag(FollowDynamicFragment::class.java.name)
?: FollowDynamicFragment().with(intent.extras)
// 若 placeholder 外层为 RelativeLayout 的话,会出现莫名的偏移
supportFragmentManager.beginTransaction()
.replace(R.id.placeholder, containerFragment, FollowDynamicFragment::class.java.name)
.commitAllowingStateLoss()
}
override fun handleBackPressed(): Boolean {
viewModel.finish()
return true
}
companion object {
const val KEY_SELECTED_POSITION = "key_selected_position"
const val KEY_USERS = "key_users"
fun start(context: Context, position: Int, followedList: List<FollowUserEntity>) {
val intent = Intent(context, FollowDynamicActivity::class.java)
intent.putParcelableArrayListExtra(KEY_USERS, followedList.toArrayList())
intent.putExtra(KEY_SELECTED_POSITION, position)
context.startActivity(intent)
}
}
}

View File

@ -0,0 +1,61 @@
package com.gh.gamecenter.forum.home.follow
import com.gh.gamecenter.entity.HistoryGameEntity
import com.gh.gamecenter.entity.PersonalHistoryEntity
import com.gh.gamecenter.feature.entity.AnswerEntity
abstract class FollowDynamicItem {
abstract val itemType: Int
fun areItemsTheSame(other: FollowDynamicItem) =
itemType == other.itemType && doAreItemsTheSame(other)
fun areContentsTheSame(other: FollowDynamicItem) =
itemType == other.itemType && doAreContentsTheSame(other)
protected open fun doAreItemsTheSame(other: FollowDynamicItem) = true
protected open fun doAreContentsTheSame(other: FollowDynamicItem) = this == other
companion object {
const val FOLLOW_DYNAMIC_ITEM_TYPE_FOOTER = -1
const val FOLLOW_DYNAMIC_ITEM_TYPE_PERSONAL = 0
const val FOLLOW_DYNAMIC_ITEM_TYPE_BBS = 1
}
}
data class FollowDynamicPersonalItem(
val data: PersonalHistoryEntity
) : FollowDynamicItem() {
override val itemType: Int
get() = FOLLOW_DYNAMIC_ITEM_TYPE_PERSONAL
override fun doAreItemsTheSame(other: FollowDynamicItem): Boolean {
return other is FollowDynamicPersonalItem
&& data.id == other.data.id
}
}
data class FollowDynamicBbsItem(
val data: AnswerEntity
) : FollowDynamicItem() {
override val itemType: Int
get() = FOLLOW_DYNAMIC_ITEM_TYPE_BBS
override fun doAreItemsTheSame(other: FollowDynamicItem): Boolean {
return other is FollowDynamicBbsItem
&& data.id == other.data.id
}
}
object FollowDynamicFooterItem : FollowDynamicItem() {
override val itemType: Int
get() = FOLLOW_DYNAMIC_ITEM_TYPE_FOOTER
}

View File

@ -0,0 +1,82 @@
package com.gh.gamecenter.forum.home.follow
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup.LayoutParams
import android.view.ViewGroup.MarginLayoutParams
import android.widget.CheckedTextView
import android.widget.PopupWindow
import androidx.core.view.children
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.databinding.LayoutForumActivityCategoryItemBinding
import com.gh.gamecenter.databinding.PopFollowFilterBinding
import com.google.android.flexbox.FlexboxLayout
class FollowHomeFilterPopWindow(
private val binding: PopFollowFilterBinding
) : PopupWindow(binding.root, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) {
private val data = arrayListOf("全部", "关注的人", "关注的游戏")
init {
isOutsideTouchable = true
isFocusable = true
val layoutParams = binding.ivTopBackground.layoutParams as MarginLayoutParams
layoutParams.topMargin = -52F.dip2px() - DisplayUtils.getStatusBarHeight(binding.root.context.resources)
binding.ivTopBackground.layoutParams = layoutParams
val inflater = LayoutInflater.from(binding.root.context)
data.forEachIndexed { index, entity ->
LayoutForumActivityCategoryItemBinding.inflate(inflater).apply {
val params =
FlexboxLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
params.setMargins(0, 8F.dip2px(), 8F.dip2px(), 0)
params.height = 24F.dip2px()
root.layoutParams = params
name.text = entity
name.isChecked = index == 0
root.setOnClickListener {
updateCategory(index)
}
binding.fblFilter.addView(root)
}
}
binding.vBackground.setOnClickListener {
dismiss()
}
}
private fun updateCategory(index: Int) {
filteredListener?.filterFollowed(index)
binding.fblFilter.children.forEachIndexed { position, child ->
(child as? CheckedTextView)?.isChecked = (position == index)
}
dismiss()
}
private var filteredListener: OnFilteredChangedListener? = null
fun setFilterListener(filteredListener: OnFilteredChangedListener) {
this.filteredListener = filteredListener
}
companion object {
const val ALL_POSITION = 0
const val PERSON_POSITION = 1
const val GAMES_POSITION = 2
fun create(context: Context): FollowHomeFilterPopWindow {
val inflater = LayoutInflater.from(context)
val binding = PopFollowFilterBinding.inflate(inflater, null, false)
return FollowHomeFilterPopWindow(binding)
}
}
interface OnFilteredChangedListener {
fun filterFollowed(position: Int)
}
}

View File

@ -0,0 +1,198 @@
package com.gh.gamecenter.forum.home.follow
import com.gh.gamecenter.entity.FollowUserEntity
import com.gh.gamecenter.entity.FollowDynamicEntity
import com.gh.gamecenter.feature.entity.*
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.model.CustomPageItem
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_BANNER
abstract class FollowItem {
abstract val itemType: Int
fun areItemsTheSame(other: FollowItem) =
itemType == other.itemType && doAreItemsTheSame(other)
fun areContentsTheSame(other: FollowItem) =
itemType == other.itemType && doAreContentsTheSame(other)
protected open fun doAreItemsTheSame(other: FollowItem) = true
protected open fun doAreContentsTheSame(other: FollowItem) = this == other
companion object {
const val FOLLOW_ITEM_TYPE_INVALID = -4
const val FOLLOW_ITEM_TYPE_EMPTY = -3
const val FOLLOW_ITEM_TYPE_FOOTER = -2
const val FOLLOW_ITEM_TYPE_HEADER = -1
const val FOLLOW_ITEM_TYPE_RECOMMEND_USER = 0
const val FOLLOW_ITEM_TYPE_COMMON_BANNER = 1
const val FOLLOW_ITEM_TYPE_COMMON_BANNER_WITH_CARDS = 2
const val FOLLOW_ITEM_TYPE_NAVIGATION = 3
const val FOLLOW_ITEM_TYPE_RECOMMEND = 4
const val FOLLOW_ITEM_TYPE_COMMON_HORIZONTAL_SLIDE = 5
const val FOLLOW_ITEM_TYPE_COMMON_VERTICAL_TWO_CARDS = 6
const val FOLLOW_ITEM_TYPE_POST_CARD = 7
const val FOLLOW_ITEM_TYPE_GIFT_PACK = 8
const val FOLLOW_ITEM_TYPE_ARTICLE = 9
// 通用内容合集 样式
val commonContentCollection: HashMap<Int, Int> = hashMapOf(
CustomPageItem.COMMON_CONTENT_COLLECTION_LAYOUT_NAVIGATION to FOLLOW_ITEM_TYPE_NAVIGATION,
CustomPageItem.COMMON_CONTENT_COLLECTION_LAYOUT_KING_KONG to FOLLOW_ITEM_TYPE_RECOMMEND,
CustomPageItem.COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_SLIDE_BANNER to FOLLOW_ITEM_TYPE_COMMON_HORIZONTAL_SLIDE,
CustomPageItem.COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_BANNER to FOLLOW_ITEM_TYPE_COMMON_VERTICAL_TWO_CARDS,
CustomPageItem.COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_VERTICAL_CARD to FOLLOW_ITEM_TYPE_COMMON_HORIZONTAL_SLIDE,
CustomPageItem.COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_VERTICAL_CARD to FOLLOW_ITEM_TYPE_COMMON_VERTICAL_TWO_CARDS,
CustomPageItem.COMMON_CONTENT_COLLECTION_LAYOUT_VERTICAL_IMAGE_TEXT to FOLLOW_ITEM_TYPE_COMMON_VERTICAL_TWO_CARDS,
CustomPageItem.COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_IMAGE_TEXT to FOLLOW_ITEM_TYPE_COMMON_HORIZONTAL_SLIDE
)
}
}
data class FollowRecommendUsersItem(
val data: List<UserEntity>
) : FollowItem() {
override val itemType: Int
get() = FOLLOW_ITEM_TYPE_RECOMMEND_USER
}
class FollowCommonCollectionItem(
val data: CustomPageData.CommonContentCollection
) : FollowItem() {
override val itemType: Int
get() = if (data.layout == COMMON_CONTENT_COLLECTION_LAYOUT_BANNER) {
// 轮播图右边卡片列表, 至少需要2个卡片才能显示
if (data.slides.subSlide.size < 2) {
FOLLOW_ITEM_TYPE_COMMON_BANNER
} else {
FOLLOW_ITEM_TYPE_COMMON_BANNER_WITH_CARDS
}
} else {
commonContentCollection[data.layout] ?: FOLLOW_ITEM_TYPE_INVALID
}
override fun doAreItemsTheSame(other: FollowItem): Boolean {
return other is FollowCommonCollectionItem
&& data.id == other.data.id
}
override fun doAreContentsTheSame(other: FollowItem): Boolean {
return other is FollowCommonCollectionItem
&& data == other.data
}
}
data class FollowSplitCommonContentCollectionItem(
val data: CustomPageData.CommonContentCollection,
val leftPosition: Int,
) : FollowItem() {
override val itemType
get() = commonContentCollection[data.layout] ?: FOLLOW_ITEM_TYPE_INVALID
override fun doAreItemsTheSame(other: FollowItem): Boolean {
return other is FollowSplitCommonContentCollectionItem
&& data.id == other.data.id
}
override fun doAreContentsTheSame(other: FollowItem): Boolean {
return other is FollowSplitCommonContentCollectionItem
&& data == other.data
&& data.data.getOrNull(leftPosition) == other.data.data.getOrNull(leftPosition)
&& data.data.getOrNull(leftPosition + 1) == other.data.data.getOrNull(leftPosition + 1)
}
}
data class FollowPostCardItem(
val data: ArticleEntity
) : FollowItem() {
override val itemType: Int
get() = FOLLOW_ITEM_TYPE_POST_CARD
override fun doAreItemsTheSame(other: FollowItem): Boolean {
return other is FollowPostCardItem
&& data.id == other.data.id
}
}
data class FollowArticleItem(
val data: FollowDynamicEntity.Article
) : FollowItem() {
override val itemType: Int
get() = FOLLOW_ITEM_TYPE_ARTICLE
override fun doAreItemsTheSame(other: FollowItem): Boolean {
return other is FollowArticleItem
&& data.id == other.data.id
}
}
data class FollowGiftPackItem(
val libao: LibaoEntity,
val time: Long
) : FollowItem() {
override val itemType: Int
get() = FOLLOW_ITEM_TYPE_GIFT_PACK
override fun doAreItemsTheSame(other: FollowItem): Boolean {
return other is FollowGiftPackItem
&& libao.id == other.libao.id
}
override fun doAreContentsTheSame(other: FollowItem): Boolean {
return other is FollowGiftPackItem
&& time == other.time
&& libao.name == other.libao.name
&& libao.des == other.libao.des
&& libao.content == other.libao.content
&& libao.code == other.libao.code
&& libao.game?.mIcon == other.libao.game?.mIcon
&& libao.game?.iconFloat == other.libao.game?.iconFloat
&& libao.game?.iconSubscript == other.libao.game?.iconSubscript
&& libao.game?.name == other.libao.game?.name
}
}
data class FollowUserItem(
val isLogin: Boolean,
val data: List<FollowUserEntity>
) : FollowItem() {
override val itemType: Int
get() = FOLLOW_ITEM_TYPE_HEADER
override fun doAreItemsTheSame(other: FollowItem): Boolean {
return true
}
override fun doAreContentsTheSame(other: FollowItem): Boolean {
return other is FollowUserItem
&& data == other.data
}
}
object FollowEmptyItem : FollowItem() {
override val itemType: Int
get() = FOLLOW_ITEM_TYPE_EMPTY
}
object FollowFooterItem : FollowItem() {
override val itemType: Int
get() = FOLLOW_ITEM_TYPE_FOOTER
}
object InvalidItem : FollowItem() {
override val itemType: Int
get() = FOLLOW_ITEM_TYPE_INVALID
}

View File

@ -0,0 +1,6 @@
package com.gh.gamecenter.forum.home.follow
class FollowPageConfiguration(
val entrance: String,
val path: String
)

View File

@ -0,0 +1,45 @@
package com.gh.gamecenter.forum.home.follow
import android.content.Context
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.utils.DarkModeUtils
class ResetDataChangeHelper<T>(
private val context: Context,
private val adapter: RecyclerView.Adapter<*>
) {
private var darkMode = DarkModeUtils.isDarkModeOn(context)
private val _dataList = arrayListOf<T>()
val dataList: List<T> = _dataList
private var _countAndKey: Pair<Int, String>? = null
fun submitList(data: List<T>?, getKey: (T) -> String) {
_dataList.clear()
data?.let(_dataList::addAll)
if (data.isNullOrEmpty()) {
_countAndKey = null
adapter.notifyDataSetChanged()
return
}
val countAndKey = _countAndKey ?: Pair(0, "")
val (oldSize, oldKeys) = countAndKey
val newSize = data.size
var newKeys = ""
data.forEach {
newKeys += getKey(it)
}
if (oldSize == newSize) {
if (oldKeys != newKeys || darkMode != DarkModeUtils.isDarkModeOn(context)) { // 数量不变,内容发生变化 || 切换浅色模式
adapter.notifyItemRangeChanged(0, adapter.itemCount, "")
}
} else {// 数量发生变化
adapter.notifyDataSetChanged()
}
_countAndKey = Pair(newSize, newKeys)
}
}

View File

@ -0,0 +1,204 @@
package com.gh.gamecenter.forum.home.follow.adapter
import android.annotation.SuppressLint
import android.view.MotionEvent
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.consume
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.databinding.RecyclerAllFollowMyFollowHeaderBinding
import com.gh.gamecenter.databinding.RecyclerAllFollowedBinding
import com.gh.gamecenter.databinding.RecyclerAllFollowedNoTopBinding
import com.gh.gamecenter.entity.FollowUserEntity
import com.gh.gamecenter.forum.home.follow.AllFollowedItem
import com.gh.gamecenter.forum.home.follow.AllFollowedItem.Companion.ALL_FOLLOWED_ITEM_TYPE_MY_FOLLOW_HEADER
import com.gh.gamecenter.forum.home.follow.AllFollowedItem.Companion.ALL_FOLLOWED_ITEM_TYPE_NORMAL
import com.gh.gamecenter.forum.home.follow.AllFollowedItem.Companion.ALL_FOLLOWED_ITEM_TYPE_NO_TOP
import com.gh.gamecenter.forum.home.follow.AllFollowedMyFollowedHeaderItem
import com.gh.gamecenter.forum.home.follow.AllFollowedNoTopItem
import com.gh.gamecenter.forum.home.follow.AllFollowedNormalItem
import com.gh.gamecenter.forum.home.follow.model.AllFollowRepository
import com.gh.gamecenter.forum.home.follow.viewholder.FollowInvalidViewHolder
import com.gh.gamecenter.forum.home.follow.viewmodel.AllFollowedViewModel
class AllFollowedAdapter(
private val viewModel: AllFollowedViewModel,
private val startDrag: (ViewHolder) -> Unit,
) : ListAdapter<AllFollowedItem, ViewHolder>(createDiffItemCallBack()) {
private var _isEdit = false
val isEdit: Boolean
get() = _isEdit
// 置顶需要移动到的位置
private var _topItemCount = 0
val topItemCount: Int
get() = _topItemCount
private val _dataList = arrayListOf<AllFollowedNormalItem>()
val dataList: List<AllFollowedNormalItem>
get() = _dataList
fun setData(data: List<AllFollowedItem>) {
_dataList.clear()
_dataList.addAll(data.filterIsInstance<AllFollowedNormalItem>())
_topItemCount = dataList.count(AllFollowedNormalItem::isTop)
val dataWithHeader = arrayListOf<AllFollowedItem>()
data.forEachIndexed { index, item ->
if (index == 0) {
dataWithHeader.add(AllFollowedMyFollowedHeaderItem(true))
}
if (topItemCount == 0 && index == 0) {
// 暂时没有置顶的关注,需要显示提示
dataWithHeader.add(AllFollowedNoTopItem())
dataWithHeader.add(AllFollowedMyFollowedHeaderItem(false))
} else if (topItemCount == index) {
dataWithHeader.add(AllFollowedMyFollowedHeaderItem(false))
}
dataWithHeader.add(item)
}
super.submitList(dataWithHeader)
}
public override fun getItem(position: Int): AllFollowedItem {
return super.getItem(position)
}
override fun getItemViewType(position: Int): Int = currentList[position].itemType
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
when (viewType) {
ALL_FOLLOWED_ITEM_TYPE_NO_TOP -> NoTopViewHolder(parent.toBinding())
ALL_FOLLOWED_ITEM_TYPE_NORMAL -> AllFollowedViewHolder(parent.toBinding())
ALL_FOLLOWED_ITEM_TYPE_MY_FOLLOW_HEADER -> MyFollowerHeaderViewHolder(parent.toBinding())
else -> FollowInvalidViewHolder(parent.toBinding())
}
@SuppressLint("ClickableViewAccessibility")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
if (holder is AllFollowedViewHolder) {
val itemBinding = holder.binding
val item = getItem(position) as AllFollowedNormalItem
val data = item.data
if (isEdit) {
itemBinding.ivUpOrDown.goneIf(false)
itemBinding.ivDrag.goneIf(!item.isTop)
itemBinding.vNewTips.goneIf(true)
} else {
itemBinding.ivUpOrDown.goneIf(true)
itemBinding.ivDrag.goneIf(true)
itemBinding.vNewTips.goneIf(!data.isShowTip)
}
val editResourceId = if (item.isTop) R.drawable.ic_all_followed_down else R.drawable.ic_all_followed_up
itemBinding.ivUpOrDown.setImageResource(editResourceId)
itemBinding.tvName.text = data.name
if (data.isUser) {
itemBinding.ivUserIcon.goneIf(false) {
itemBinding.ivUserIcon.displayGameIcon(data.icon, null)
}
itemBinding.ivGameIcon.goneIf(true)
} else {
itemBinding.ivUserIcon.goneIf(true)
itemBinding.ivGameIcon.goneIf(false) {
itemBinding.ivGameIcon.displayGameIcon(data.icon, null)
}
}
itemBinding.ivUpOrDown.setOnClickListener {
if (item.isTop) {
cancelToTop(data.id)
} else {
toTop(data.id)
}
}
itemBinding.ivDrag.setOnTouchListener { _, event ->
consume {
if (event.action == MotionEvent.ACTION_DOWN) {
startDrag(holder)
}
}
}
arrayOf(itemBinding.ivGameIcon, itemBinding.ivUserIcon, itemBinding.tvName)
.forEach {
it.setOnClickListener {
viewModel.navigateToDetailPage(item)
}
}
}
}
private fun toTop(id: String) {
if (topItemCount >= itemCount) {
return
}
val newList = arrayListOf<AllFollowedNormalItem>()
val targetItem = dataList.find { it.data.id == id }
dataList.forEach {
if (newList.size == topItemCount && targetItem != null) {
newList.add(targetItem.copy(isTop = true))
}
if (it.data.id != id) {
newList.add(AllFollowedNormalItem(it.data, it.isTop))
}
}
setData(newList)
}
private fun cancelToTop(id: String) {
if (topItemCount == 0) {
return
}
val newList = arrayListOf<AllFollowedNormalItem>()
val targetItem = dataList.find { it.data.id == id }
dataList.forEach {
if (topItemCount == newList.size && targetItem != null) {
newList.add(targetItem.copy(isTop = false))
}
if (it.data.id != id) {
newList.add(it.copy())
}
}
setData(newList)
}
fun changeToEditable() {
_isEdit = !isEdit
notifyItemRangeChanged(0, itemCount, "")
}
fun getTopItem() =
dataList.filter {
it.isTop
}.mapIndexed { index, item ->
AllFollowRepository.Top(item.data.id, index + 1)
}
companion object {
fun createDiffItemCallBack() = object : ItemCallback<AllFollowedItem>() {
override fun areItemsTheSame(oldItem: AllFollowedItem, newItem: AllFollowedItem): Boolean {
return oldItem.areItemsTheSame(newItem)
}
override fun areContentsTheSame(oldItem: AllFollowedItem, newItem: AllFollowedItem): Boolean {
return oldItem.areContentsTheSame(newItem)
}
}
}
class NoTopViewHolder(val binding: RecyclerAllFollowedNoTopBinding) : ViewHolder(binding.root)
class MyFollowerHeaderViewHolder(val binding: RecyclerAllFollowMyFollowHeaderBinding) : ViewHolder(binding.root)
class AllFollowedViewHolder(val binding: RecyclerAllFollowedBinding) : ViewHolder(binding.root)
}

View File

@ -0,0 +1,115 @@
package com.gh.gamecenter.forum.home.follow.adapter
import android.content.Context
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.CommonCollectionItemBinding
import com.gh.gamecenter.entity.CommonCollectionContentEntity
import com.gh.gamecenter.forum.home.follow.ResetDataChangeHelper
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_IMAGE_TEXT
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_SLIDE_BANNER
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_VERTICAL_CARD
class FollowCommonCollectionAdapter(
context: Context,
private val clickInvoke: (Int, CommonCollectionContentEntity) -> Unit,
) :
RecyclerView.Adapter<FollowCommonCollectionAdapter.FollowCommonCollectionItemViewHolder>() {
private val dataChangeHelper = ResetDataChangeHelper<CommonCollectionContentEntity>(context, this)
val dataList: List<CommonCollectionContentEntity>
get() = dataChangeHelper.dataList
private lateinit var _item: CustomPageData.CommonContentCollection
fun submitList(item: CustomPageData.CommonContentCollection) {
_item = item
dataChangeHelper.submitList(item.data) {
it.title
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FollowCommonCollectionItemViewHolder {
return FollowCommonCollectionItemViewHolder(parent.toBinding())
}
override fun onBindViewHolder(holder: FollowCommonCollectionItemViewHolder, position: Int) {
val layout = _item.layout
val item = dataList[position]
var width = 0
var height = 0
when (layout) {
COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_SLIDE_BANNER -> { // 横向滑动banner
width = 244F.dip2px()
height = 122F.dip2px()
}
COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_VERTICAL_CARD -> { // 横排竖式卡片
width = 160F.dip2px()
height = 213F.dip2px()
}
COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_IMAGE_TEXT -> { // 横排图文列表
width = 160F.dip2px()
height = 80F.dip2px()
}
}
holder.binding.apply {
ImageUtils.display(commonCollectionImage, item.image)
maskView.goneIf(layout == COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_IMAGE_TEXT || (item.title.isEmpty() && item.addedContent1.isNullOrEmpty()))
titleTv.goneIf(layout == COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_IMAGE_TEXT)
titleTv.text = item.title
desTv.goneIf(layout != COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_VERTICAL_CARD)
desTv.text = item.addedContent1
linkTitleTv.goneIf(layout != COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_IMAGE_TEXT)
linkTitleTv.text = item.title
linkDes1.goneIf(layout != COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_IMAGE_TEXT)
linkDes1.text = item.addedContent1
linkDes1.setTextColor(R.color.text_secondary.toColor(root.context))
linkDes2.goneIf(layout != COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_IMAGE_TEXT)
linkDes2.text = item.addedContent2
linkDes2.setTextColor(R.color.text_tertiary.toColor(root.context))
val maskHeight = if (layout == COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_SLIDE_BANNER) {
60f.dip2px()
} else {
38F.dip2px()
}
val maskParams = maskView.layoutParams as ConstraintLayout.LayoutParams
maskParams.height = maskHeight
maskView.layoutParams = maskParams
val imageParams = commonCollectionImage.layoutParams as ConstraintLayout.LayoutParams
imageParams.width = width
imageParams.height = height
commonCollectionImage.layoutParams = imageParams
val rootParams = root.layoutParams as RecyclerView.LayoutParams
rootParams.width = width
rootParams.leftMargin = if (position == 0) 16F.dip2px() else 0
rootParams.rightMargin = if (position == itemCount - 1) 16F.dip2px() else 0
root.layoutParams = rootParams
root.setOnClickListener {
clickInvoke(position, item)
}
}
}
override fun getItemCount(): Int {
return dataList.size
}
companion object {
const val spanCount = 2
}
class FollowCommonCollectionItemViewHolder(val binding: CommonCollectionItemBinding) :
BaseRecyclerViewHolder<Any>(binding.root)
}

View File

@ -0,0 +1,219 @@
package com.gh.gamecenter.forum.home.follow.adapter
import android.content.Context
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.databinding.CommunityAnswerItemBinding
import com.gh.gamecenter.entity.PersonalHistoryEntity
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.feature.entity.Questions
import com.gh.gamecenter.forum.home.ForumArticleAskItemViewHolder
import com.gh.gamecenter.forum.home.follow.FollowDynamicBbsItem
import com.gh.gamecenter.forum.home.follow.FollowDynamicFooterItem
import com.gh.gamecenter.forum.home.follow.FollowDynamicItem
import com.gh.gamecenter.forum.home.follow.FollowDynamicItem.Companion.FOLLOW_DYNAMIC_ITEM_TYPE_BBS
import com.gh.gamecenter.forum.home.follow.FollowDynamicItem.Companion.FOLLOW_DYNAMIC_ITEM_TYPE_FOOTER
import com.gh.gamecenter.forum.home.follow.FollowDynamicItem.Companion.FOLLOW_DYNAMIC_ITEM_TYPE_PERSONAL
import com.gh.gamecenter.forum.home.follow.FollowDynamicPersonalItem
import com.gh.gamecenter.forum.home.follow.viewholder.FollowFooterViewHolder
import com.gh.gamecenter.forum.home.follow.viewholder.FollowInvalidViewHolder
import com.gh.gamecenter.forum.home.follow.viewmodel.FollowDynamicListViewModel
import com.gh.gamecenter.forum.home.follow.viewmodel.FollowDynamicViewModel
import com.gh.gamecenter.forum.search.CommunitySearchEventListener
import com.gh.gamecenter.personalhome.PersonalItemViewHolder
class FollowDynamicAdapter(
private val context: Context,
private val viewModel: FollowDynamicListViewModel
) : ListAdapter<FollowDynamicItem, ViewHolder>(createDiffCallback()) {
private var _recyclerView: RecyclerView? = null
private var loadStatus: LoadStatus? = null
fun setLoadStatus(status: LoadStatus) {
loadStatus = status
notifyItemChanged(itemCount - 1)
}
override fun submitList(list: List<FollowDynamicItem>?) {
val dataWithFooter = if (list.isNullOrEmpty()) {
ArrayList(list)
} else {
list + FollowDynamicFooterItem
}
super.submitList(dataWithFooter)
}
override fun getItemViewType(position: Int): Int {
return currentList[position].itemType
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return when (viewType) {
FOLLOW_DYNAMIC_ITEM_TYPE_PERSONAL -> {
FollowDynamicPersonalViewHolder(context, parent.toBinding()) { history, position ->
viewModel.navigateToPersonDetailPage(history, position)
}
}
FOLLOW_DYNAMIC_ITEM_TYPE_BBS -> {
FollowDynamicBbSViewHolder(parent.toBinding(), viewModel.bbsId) { position, answer ->
viewModel.navigateToBbsDetailPage(position, answer)
}
}
FOLLOW_DYNAMIC_ITEM_TYPE_FOOTER ->
FollowFooterViewHolder(parent.toBinding())
else -> {
FollowInvalidViewHolder(parent.toBinding())
}
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
when (holder) {
is FollowDynamicPersonalViewHolder -> {
val item = getItem(position)
if (item is FollowDynamicPersonalItem) {
holder.onBind(item.data, "", position)
}
}
is FollowDynamicBbSViewHolder -> {
val item = getItem(position)
if (item is FollowDynamicBbsItem) {
holder.onBind(item.data, "", "", position)
}
}
is FollowFooterViewHolder -> {
holder.initFooterViewHolder(
loadStatus == LoadStatus.LIST_LOADING,
loadStatus == LoadStatus.LIST_FAILED,
loadStatus == LoadStatus.LIST_OVER,
R.string.load_over_with_click_hint
) {
if (loadStatus == LoadStatus.LIST_OVER) {
_recyclerView?.scrollToPosition(0)
} else if (loadStatus == LoadStatus.LIST_FAILED) {
viewModel.loadMore()
notifyItemChanged(itemCount - 1)
}
}
}
else -> Unit
}
}
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
recyclerView.addOnScrollListener(onLoadMoreListener)
_recyclerView = recyclerView
}
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
recyclerView.removeOnScrollListener(onLoadMoreListener)
_recyclerView = null
}
private val onLoadMoreListener = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
val layoutManger = recyclerView.layoutManager
if (layoutManger is LinearLayoutManager && itemCount != 0) {
val lastVisibleItemPosition = layoutManger.findLastVisibleItemPosition()
if (lastVisibleItemPosition == itemCount - 1 && loadStatus != LoadStatus.LIST_OVER && loadStatus != LoadStatus.LIST_LOADING) {
viewModel.loadMore()
}
}
}
}
}
companion object {
fun createDiffCallback() = object : ItemCallback<FollowDynamicItem>() {
override fun areItemsTheSame(oldItem: FollowDynamicItem, newItem: FollowDynamicItem): Boolean {
return oldItem.areItemsTheSame(newItem)
}
override fun areContentsTheSame(oldItem: FollowDynamicItem, newItem: FollowDynamicItem): Boolean {
return oldItem.areContentsTheSame(newItem)
}
}
}
class FollowDynamicPersonalViewHolder(
private val context: Context,
val binding: CommunityAnswerItemBinding,
private val itemClickCallback: (historyEntity: PersonalHistoryEntity, position: Int) -> Unit
) :
RecyclerView.ViewHolder(binding.root) {
// 复用原有 viewHolder
private val realViewHolder by lazy(LazyThreadSafetyMode.NONE) {
PersonalItemViewHolder(context, binding, itemClickCallback)
}
fun onBind(historyEntity: PersonalHistoryEntity, entrance: String, position: Int) {
realViewHolder.bindPersonalItem(historyEntity, entrance, position)
}
}
class FollowDynamicBbSViewHolder(
val binding: CommunityAnswerItemBinding,
private val bbsId: String,
private val onItemClick: (Int, AnswerEntity) -> Unit
) : RecyclerView.ViewHolder(binding.root) {
private val viewHolder by lazy(LazyThreadSafetyMode.NONE) {
ForumArticleAskItemViewHolder(binding)
}
fun onBind(answer: AnswerEntity, entrance: String, path: String, position: Int) {
val articlePosition = position - 1
if (answer.type != "answer") {
val questions = Questions()
questions.id = answer.id
questions.title = answer.title
questions.description = answer.description
questions.answerCount = answer.count.answer
answer.questions = questions
}
if (path == "精华" && answer.type != "answer" && answer.type != "video") answer.type = "community_article"
if (path == "问答") answer.type = "question"
if (path == "视频") answer.type = "video"
if (answer.bbs.id.isEmpty()) answer.bbs.id = bbsId
binding.forumNameLl.visibility = View.GONE
binding.topLine.goneIf(articlePosition == 0)
val params = binding.includeVoteAndComment.root.layoutParams as LinearLayout.LayoutParams
params.width = LinearLayout.LayoutParams.MATCH_PARENT
params.leftMargin = 0
params.rightMargin = 0
binding.includeVoteAndComment.root.layoutParams = params
// 这里默认已关注,不需要显示关注按钮
answer.me.isFollower = true
viewHolder.bindForumAnswerItem(answer, entrance, path, position)
viewHolder.itemView.setOnClickListener {
onItemClick(position, answer)
}
}
}
}

View File

@ -0,0 +1,488 @@
package com.gh.gamecenter.forum.home.follow.adapter
import android.view.ViewGroup
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.syncpage.ISyncAdapterHandler
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.entity.FollowUserEntity
import com.gh.gamecenter.eventbus.EBUserFollow
import com.gh.gamecenter.feature.entity.ConcernEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.LibaoEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.forum.home.follow.*
import com.gh.gamecenter.forum.home.follow.FollowItem.Companion.FOLLOW_ITEM_TYPE_ARTICLE
import com.gh.gamecenter.forum.home.follow.FollowItem.Companion.FOLLOW_ITEM_TYPE_COMMON_BANNER
import com.gh.gamecenter.forum.home.follow.FollowItem.Companion.FOLLOW_ITEM_TYPE_COMMON_BANNER_WITH_CARDS
import com.gh.gamecenter.forum.home.follow.FollowItem.Companion.FOLLOW_ITEM_TYPE_COMMON_HORIZONTAL_SLIDE
import com.gh.gamecenter.forum.home.follow.FollowItem.Companion.FOLLOW_ITEM_TYPE_COMMON_VERTICAL_TWO_CARDS
import com.gh.gamecenter.forum.home.follow.FollowItem.Companion.FOLLOW_ITEM_TYPE_EMPTY
import com.gh.gamecenter.forum.home.follow.FollowItem.Companion.FOLLOW_ITEM_TYPE_FOOTER
import com.gh.gamecenter.forum.home.follow.FollowItem.Companion.FOLLOW_ITEM_TYPE_GIFT_PACK
import com.gh.gamecenter.forum.home.follow.FollowItem.Companion.FOLLOW_ITEM_TYPE_HEADER
import com.gh.gamecenter.forum.home.follow.FollowItem.Companion.FOLLOW_ITEM_TYPE_NAVIGATION
import com.gh.gamecenter.forum.home.follow.FollowItem.Companion.FOLLOW_ITEM_TYPE_POST_CARD
import com.gh.gamecenter.forum.home.follow.FollowItem.Companion.FOLLOW_ITEM_TYPE_RECOMMEND
import com.gh.gamecenter.forum.home.follow.FollowItem.Companion.FOLLOW_ITEM_TYPE_RECOMMEND_USER
import com.gh.gamecenter.forum.home.follow.viewholder.*
import com.gh.gamecenter.forum.home.follow.viewmodel.FollowHomeViewModel
import com.gh.gamecenter.home.custom.OnViewHolderAttachListener
import com.gh.gamecenter.home.custom.model.CustomPageData
class FollowHomeAdapter(
private val lifecycleOwner: LifecycleOwner,
private val viewModel: FollowHomeViewModel,
private val pageConfiguration: FollowPageConfiguration
) : ListAdapter<FollowItem, ViewHolder>(createItemCallback()), ISyncAdapterHandler {
private var loadStatus: LoadStatus? = null
private var _recyclerView: RecyclerView? = null
override fun submitList(list: List<FollowItem>?) {
val dataWithFooter = if (list.isNullOrEmpty()) {
ArrayList(list)
} else {
list + FollowFooterItem
}
super.submitList(dataWithFooter)
}
fun setLoadStatus(status: LoadStatus) {
loadStatus = status
notifyItemChanged(itemCount - 1)
}
override fun getItemViewType(position: Int): Int {
return currentList[position].itemType
}
override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
var change: EBUserFollow? = null
var readStatusUpdate: ReadStatusUpdate? = null
payloads.forEach {
if (it is EBUserFollow) {
change = it
}
if (it is ReadStatusUpdate) {
readStatusUpdate = it
}
}
when {
change != null -> { // 局部刷新,关注/取消关注
(holder as? FollowRecommendListViewHolder)?.updateFollowed(change!!)
}
readStatusUpdate != null -> { // 局部刷新 关注用户是否有新消息
(holder as? FollowHomeHeaderViewHolder)?.updateReadStatus(readStatusUpdate!!.ids)
}
else -> {
super.onBindViewHolder(holder, position, payloads)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
when (viewType) {
FOLLOW_ITEM_TYPE_HEADER -> {
FollowHomeHeaderViewHolder(parent.toBinding(), object : FollowHomeHeaderViewHolder.OnEventListener {
override fun onItemClick(position: Int, data: List<FollowUserEntity>) {
viewModel.viewItemFollowed(position, data)
}
override fun onAllClick() {
viewModel.viewAllFollowed()
}
override fun onLoginClick() {
viewModel.login()
}
})
}
FOLLOW_ITEM_TYPE_RECOMMEND_USER -> {
FollowRecommendListViewHolder(parent.toBinding(),
object : FollowRecommendListAdapter.OnChildEventListener {
override fun onItemClick(childPosition: Int, userId: String) {
SensorsBridge.trackFollowPageRecommendedUserCardClick(userId, childPosition + 1)
viewModel.navigateToUserHomePage(userId)
}
override fun onFollowClick(childPosition: Int, userId: String, isFollow: Boolean) {
SensorsBridge.trackRecommendedUserFollowButtonClick(userId, childPosition + 1)
viewModel.followUser(userId, isFollow)
}
})
}
FOLLOW_ITEM_TYPE_COMMON_BANNER -> {
FollowHomeSlideListViewHolder(
parent.toBinding(),
lifecycleOwner,
object : FollowHomeSlideListViewHolder.OnChildEventListener {
override fun navigateToLinkPage(
position: Int,
link: LinkEntity,
data: CustomPageData.CommonContentCollection,
text: String,
exposureEvent: ExposureEvent?
) {
viewModel.navigateToLinkPageInCommonContent(position, link, data, text, exposureEvent)
}
override fun navigateToGameDetailPage(position: Int, game: GameEntity, text: String) {
viewModel.navigateToGameDetailPage(position, game, text)
}
})
}
FOLLOW_ITEM_TYPE_COMMON_BANNER_WITH_CARDS -> {
FollowHomeSlideWithCardsViewHolder(
parent.toBinding(),
viewModel.commonContentCollectionUseCase,
lifecycleOwner,
object : FollowHomeSlideWithCardsViewHolder.OnChildEventListener {
override fun navigateToGameDetailPage(
childPosition: Int,
gameEntity: GameEntity,
text: String
) {
viewModel.navigateToGameDetailPage(childPosition, gameEntity, text)
}
override fun navigateToLinkPage(
position: Int,
link: LinkEntity,
data: CustomPageData.CommonContentCollection,
text: String,
exposureEvent: ExposureEvent?
) {
viewModel.navigateToLinkPageInCommonContent(position, link, data, text, exposureEvent)
}
})
}
FOLLOW_ITEM_TYPE_NAVIGATION -> {
FollowNavigationViewHolder(parent.toBinding(),
object : FollowNavigationViewHolder.OnChildEventListener {
override fun navigateToLinkPage(
position: Int,
link: LinkEntity,
data: CustomPageData.CommonContentCollection,
text: String,
exposureEvent: ExposureEvent?
) {
viewModel.navigateToLinkPageInCommonContent(position, link, data, text, exposureEvent)
}
})
}
FOLLOW_ITEM_TYPE_RECOMMEND -> {
FollowHomeRecommendItemViewHolder(
parent.toBinding(),
object : FollowHomeRecommendItemViewHolder.OnChildEventListener {
override fun navigateToLinkPage(
position: Int,
link: LinkEntity,
data: CustomPageData.CommonContentCollection,
text: String,
exposureEvent: ExposureEvent?
) {
viewModel.navigateToLinkPageInCommonContent(position, link, data, text, exposureEvent)
}
})
}
FOLLOW_ITEM_TYPE_COMMON_HORIZONTAL_SLIDE ->
FollowCommonCollectionViewHolder(
parent.toBinding(),
object : FollowCommonCollectionViewHolder.OnChildEventListener {
override fun navigateToLinkPage(
position: Int,
link: LinkEntity,
data: CustomPageData.CommonContentCollection,
text: String,
exposureEvent: ExposureEvent?
) {
viewModel.navigateToLinkPageInCommonContent(position, link, data, text, exposureEvent)
}
override fun navigateToCommonCollectionDetailPage(data: CustomPageData.CommonContentCollection) {
viewModel.navigateToCommonCollectionDetailPage(data)
}
})
FOLLOW_ITEM_TYPE_COMMON_VERTICAL_TWO_CARDS ->
FollowCommonCollection12ViewHolder(
parent.toBinding(),
object : FollowCommonCollection12ViewHolder.OnChildEventListener {
override fun navigateToLinkPage(
position: Int,
link: LinkEntity,
data: CustomPageData.CommonContentCollection,
text: String,
exposureEvent: ExposureEvent?
) {
viewModel.navigateToLinkPageInCommonContent(position, link, data, text, exposureEvent)
}
override fun navigateToCommonCollectionDetailPage(data: CustomPageData.CommonContentCollection) {
viewModel.navigateToCommonCollectionDetailPage(data)
}
})
FOLLOW_ITEM_TYPE_POST_CARD -> {
FollowPostCardViewHolder(
parent.toBinding(),
pageConfiguration.entrance,
pageConfiguration.path
)
}
FOLLOW_ITEM_TYPE_GIFT_PACK -> {
GiftPackViewHolder(parent.toBinding(), object : GiftPackViewHolder.OnChildEventListener {
override fun onGameClick(game: GameEntity) {
viewModel.navigateToGameDetailPage(0, game, "礼包")
}
override fun onGiftPackageClick(libaoEntity: LibaoEntity) {
viewModel.navigateToLibaoDetailPage(libaoEntity)
}
override fun onCopy(code: String) {
viewModel.copyExchangeCode(code)
}
})
}
FOLLOW_ITEM_TYPE_ARTICLE -> {
GameArticleViewHolder(parent.toBinding(), object : GameArticleViewHolder.OnChildEventListener {
override fun onGameCLick(game: GameEntity) {
viewModel.navigateToGameDetailPage(0, game, "关注-文章")
}
override fun onArticleClick(articleId: String) {
viewModel.navigateToNewsDetailPage(articleId)
}
override fun onComment(articleId: String) {
viewModel.navigateToArticleCommentDetailPage(articleId)
}
override fun onShare(concernEntity: ConcernEntity) {
viewModel.onShareArticle(concernEntity)
}
})
}
FOLLOW_ITEM_TYPE_FOOTER -> FollowFooterViewHolder(parent.toBinding())
FOLLOW_ITEM_TYPE_EMPTY -> {
FollowHomeEmptyViewHolder(parent.toBinding())
}
else -> FollowInvalidViewHolder(parent.toBinding())
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
when (holder) {
is FollowHomeHeaderViewHolder -> {
getRealTypeItem<FollowUserItem>(position) {
holder.bind(it.isLogin, it.data)
}
}
is FollowRecommendListViewHolder -> {
getRealTypeItem<FollowRecommendUsersItem>(position) {
holder.bindView(it.data)
}
}
is FollowHomeSlideListViewHolder -> {
getRealTypeItem<FollowCommonCollectionItem>(position) {
holder.bindView(it.data)
}
}
is FollowHomeSlideWithCardsViewHolder -> {
getRealTypeItem<FollowCommonCollectionItem>(position) {
holder.bindView(it.data)
}
}
is FollowNavigationViewHolder -> {
getRealTypeItem<FollowCommonCollectionItem>(position) {
holder.bindView(it.data)
}
}
is FollowHomeRecommendItemViewHolder -> {
getRealTypeItem<FollowCommonCollectionItem>(position) {
holder.bindView(it.data)
}
}
is FollowCommonCollectionViewHolder -> {
getRealTypeItem<FollowCommonCollectionItem>(position) {
holder.bindView(it.data)
}
}
is FollowCommonCollection12ViewHolder -> {
getRealTypeItem<FollowSplitCommonContentCollectionItem>(position) {
holder.bind(it.data, it.leftPosition)
}
}
is FollowPostCardViewHolder -> {
getRealTypeItem<FollowPostCardItem>(position) {
holder.bind(it.data, position)
}
}
is GameArticleViewHolder -> {
getRealTypeItem<FollowArticleItem>(position) {
holder.bind(it.data)
}
}
is GiftPackViewHolder -> {
getRealTypeItem<FollowGiftPackItem>(position) {
holder.bind(it.libao, it.time)
}
}
is FollowFooterViewHolder -> {
holder.initFooterViewHolder(
loadStatus == LoadStatus.LIST_LOADING,
loadStatus == LoadStatus.LIST_FAILED,
loadStatus == LoadStatus.LIST_OVER,
R.string.load_over_with_click_hint
) {
if (loadStatus == LoadStatus.LIST_OVER) {
_recyclerView?.scrollToPosition(0)
} else if (loadStatus == LoadStatus.LIST_FAILED) {
viewModel.loadMore()
notifyItemChanged(itemCount - 1)
}
}
}
else -> Unit
}
}
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
_recyclerView = recyclerView
recyclerView.addOnScrollListener(onLoadMoreListener)
}
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
recyclerView.removeOnScrollListener(onLoadMoreListener)
_recyclerView = null
}
override fun onViewAttachedToWindow(holder: ViewHolder) {
if (holder is OnViewHolderAttachListener) {
holder.onViewAttach(_recyclerView)
}
}
override fun onViewDetachedFromWindow(holder: ViewHolder) {
if (holder is OnViewHolderAttachListener) {
holder.onViewDetach(_recyclerView)
}
}
override fun getSyncData(position: Int): Pair<String, Any>? {
if (position >= itemCount) return null
val item = getItem(position)
return when {
item is FollowPostCardItem -> {
Pair(item.data.id, item.data.count)
}
else -> null
}
}
private inline fun <T> getRealTypeItem(position: Int, block: (T) -> Unit) {
val item = getItem(position)
(item as? T)?.let(block)
}
fun notifyFollowUserChanged(change: EBUserFollow) {
val position = currentList.indexOfFirst { it is FollowRecommendUsersItem }
notifyItemChanged(position, change)
}
fun notifyFollowUserReadStatus(ids: List<String>) {
val position = currentList.indexOfFirst { it is FollowUserItem }
notifyItemChanged(position, ReadStatusUpdate(ids))
}
private val onLoadMoreListener = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
val layoutManger = recyclerView.layoutManager
if (layoutManger is LinearLayoutManager) {
val lastVisibleItemPosition = layoutManger.findLastVisibleItemPosition()
if (lastVisibleItemPosition == itemCount - 1 && loadStatus != LoadStatus.LIST_OVER && loadStatus != LoadStatus.LIST_LOADING) {
viewModel.loadMore()
}
}
}
}
}
companion object {
fun createItemCallback() = object : ItemCallback<FollowItem>() {
override fun areItemsTheSame(oldItem: FollowItem, newItem: FollowItem): Boolean {
return oldItem.areItemsTheSame(newItem)
}
override fun areContentsTheSame(oldItem: FollowItem, newItem: FollowItem): Boolean {
return oldItem.areContentsTheSame(newItem)
}
}
}
data class ReadStatusUpdate(
val ids: List<String>
)
}

View File

@ -0,0 +1,96 @@
package com.gh.gamecenter.forum.home.follow.adapter
import android.content.Context
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.debounceActionWithInterval
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.databinding.RecyclerFollowRecommendBinding
import com.gh.gamecenter.eventbus.EBUserFollow
import com.gh.gamecenter.feature.entity.UserEntity
import com.gh.gamecenter.forum.home.follow.ResetDataChangeHelper
class FollowRecommendListAdapter(
context: Context,
private val listener: OnChildEventListener
) : RecyclerView.Adapter<FollowRecommendListAdapter.RecommendViewHolder>() {
private val resetDataChangeHelper = ResetDataChangeHelper<UserEntity>(context, this)
fun submitList(data: List<UserEntity>) {
resetDataChangeHelper.submitList(data) {
it.id ?: ""
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecommendViewHolder {
return RecommendViewHolder(parent.toBinding())
}
override fun getItemCount(): Int =
resetDataChangeHelper.dataList.size
override fun onBindViewHolder(holder: RecommendViewHolder, position: Int) {
val item = resetDataChangeHelper.dataList[position]
val context = holder.itemView.context
holder.binding.ivIcon.display(item.border, item.icon, item.auth?.icon)
holder.binding.tvFollowerCount.text = if (item.count.fans > NUMBER_MAX) {
context.getString(R.string.fans_with_number_max)
} else {
context.getString(R.string.fans_with_number, item.count.fans)
}
val createCount = item.count.question + item.count.communityArticle + item.count.video
holder.binding.tvArticleCount.text = if (createCount > NUMBER_MAX) {
context.getString(R.string.creations_with_number_max)
} else {
context.getString(R.string.creations_with_number, createCount)
}
holder.binding.tvSign.text = if (item.introduce.isBlank()) {
context.getString(R.string.user_default_introduce)
} else {
item.introduce
}
holder.binding.tvFollow.text = if (item.isFollowed) {
context.getString(R.string.concerned)
} else {
context.getString(R.string.menu_concern)
}
holder.itemView.setOnClickListener {
listener.onItemClick(position, item.id ?: "")
}
holder.binding.tvFollow.setOnClickListener {
debounceActionWithInterval(it.id, 1000) {
listener.onFollowClick(position, item.id ?: "", !item.isFollowed)
}
}
}
fun updateFollowed(change: EBUserFollow) {
val position = resetDataChangeHelper.dataList.indexOfFirst { it.id == change.userId }
val item = resetDataChangeHelper.dataList[position]
item.isFollowed = change.isFollow
notifyItemChanged(position)
}
companion object {
private const val NUMBER_MAX = 99
}
class RecommendViewHolder(val binding: RecyclerFollowRecommendBinding) : ViewHolder(binding.root)
interface OnChildEventListener {
fun onItemClick(childPosition: Int, userId: String)
fun onFollowClick(childPosition: Int, userId: String, isFollow: Boolean)
}
}

View File

@ -0,0 +1,205 @@
package com.gh.gamecenter.forum.home.follow.adapter
import android.content.Context
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.databinding.RecyclerFollowedListAllBinding
import com.gh.gamecenter.databinding.RecyclerFollowedListBbsBinding
import com.gh.gamecenter.databinding.RecyclerFollowedListPersonBinding
import com.gh.gamecenter.entity.FollowUserEntity
import com.gh.gamecenter.forum.home.follow.model.FollowRepository.Companion.FOLLOW_UPDATE_TYPE_USER
class FollowedHeaderAdapter(
private val context: Context,
private val listener: OnChildEventListener
) :
ListAdapter<FollowUserEntity, ViewHolder>(createItemCallback()) {
/**
* 1.当 _selectedPosition 为 -1 时
* 不显示 viewAll 按钮
* 显示选中框
*
* 2. 当_selectedPosition 为 -2 时
* 不显示 viewAll 按钮
* 暂无选中框
*/
private var _selectedPosition = -1
private val showAll: Boolean
get() = _selectedPosition == -1
fun submitList(data: List<FollowUserEntity>, selectedPosition: Int = -1) {
_selectedPosition = selectedPosition
super.submitList(data)
}
override fun getItemCount(): Int = if (showAll) super.getItemCount() + 1 else super.getItemCount()
override fun getItemViewType(position: Int): Int {
return if (showAll) {
if (position < currentList.size) {
if (getItem(position).type == FOLLOW_UPDATE_TYPE_USER) {
VIEW_TYPE_BBS
} else {
VIEW_TYPE_GAME
}
} else {
VIEW_TYPE_VIEW_ALL
}
} else {
if (getItem(position).type == FOLLOW_UPDATE_TYPE_USER) {
VIEW_TYPE_BBS
} else {
VIEW_TYPE_GAME
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
when (viewType) {
VIEW_TYPE_GAME -> {
FollowedGameViewHolder(parent.toBinding(), ::onSelectedChanged)
}
VIEW_TYPE_BBS -> {
FollowedPersonViewHolder(parent.toBinding(), ::onSelectedChanged)
}
else -> FollowedViewAllViewHolder(parent.toBinding(), listener::onViewAll)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
when (holder) {
is FollowedGameViewHolder -> {
holder.bindView(getItem(position), position, _selectedPosition == position, showAll)
}
is FollowedPersonViewHolder -> {
holder.bindView(getItem(position), position, _selectedPosition == position, showAll)
}
}
}
private fun onSelectedChanged(position: Int) {
selectPosition(position)
listener.onViewItem(position)
}
fun selectPosition(position: Int) {
if (_selectedPosition != -1 && _selectedPosition != position) {
val lastSelectedPosition = _selectedPosition
_selectedPosition = position
notifyItemChanged(lastSelectedPosition)
notifyItemChanged(_selectedPosition)
_selectedPosition = position
}
}
fun clearSelectedPosition() {
val lastSelectedPosition = _selectedPosition
_selectedPosition = -2
if (lastSelectedPosition >= 0) {
notifyItemChanged(lastSelectedPosition)
}
}
companion object {
private const val VIEW_TYPE_GAME = 0
private const val VIEW_TYPE_BBS = 1
private const val VIEW_TYPE_VIEW_ALL = 2
private fun createItemCallback() = object : ItemCallback<FollowUserEntity>() {
override fun areItemsTheSame(oldItem: FollowUserEntity, newItem: FollowUserEntity): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: FollowUserEntity, newItem: FollowUserEntity): Boolean {
return oldItem == newItem
}
}
}
private class FollowedGameViewHolder(
val binding: RecyclerFollowedListBbsBinding,
private val itemClick: (Int) -> Unit,
) : ViewHolder(binding.root) {
fun bindView(item: FollowUserEntity, position: Int, isSelected: Boolean, showName: Boolean) {
if (showName) {
binding.tvName.goneIf(false)
binding.tvName.text = item.bbs.name
} else {
binding.tvName.goneIf(true)
}
binding.ivIcon.displayGameIcon(item.icon, null)
val resourceId = if (isSelected) {
R.drawable.background_shape_theme_radius_12
} else {
R.drawable.background_shape_white_radius_12
}
binding.gIndicator.goneIf(!item.isShowTip)
binding.vIconBackground.setBackgroundResource(resourceId)
itemView.setOnClickListener {
itemClick(position)
}
}
}
private class FollowedPersonViewHolder(
val binding: RecyclerFollowedListPersonBinding,
private val itemClick: (Int) -> Unit,
) : ViewHolder(binding.root) {
fun bindView(item: FollowUserEntity, position: Int, isSelected: Boolean, showName: Boolean) {
if (showName) {
binding.tvName.goneIf(false)
binding.tvName.text = item.user.name
} else {
binding.tvName.goneIf(true)
}
binding.ivIcon.displayGameIcon(item.icon, null)
val resourceId = if (isSelected) {
R.drawable.background_shape_theme_radius_999
} else {
R.drawable.background_shape_white_radius_999
}
binding.gIndicator.goneIf(!item.isShowTip)
binding.vIconBackground.setBackgroundResource(resourceId)
itemView.setOnClickListener {
itemClick(position)
}
}
}
private class FollowedViewAllViewHolder(
binding: RecyclerFollowedListAllBinding,
private val viewAll: () -> Unit,
) : ViewHolder(binding.root) {
init {
binding.root.setOnClickListener {
viewAll()
}
}
}
interface OnChildEventListener {
fun onViewItem(position: Int)
fun onViewAll()
}
}

View File

@ -0,0 +1,31 @@
package com.gh.gamecenter.forum.home.follow.adapter
import android.annotation.SuppressLint
import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.gh.gamecenter.entity.FollowUserEntity
import com.gh.gamecenter.forum.home.follow.fragment.FollowDynamicListFragment
class FollowedViewPagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
private val dataList = arrayListOf<FollowUserEntity>()
@SuppressLint("NotifyDataSetChanged")
fun addData(newData: List<FollowUserEntity>) {
if (dataList.isEmpty()) {
dataList.addAll(newData)
notifyDataSetChanged()
} else {
val start = dataList.size
dataList.addAll(newData)
notifyItemRangeChanged(start, newData.size)
}
}
override fun getItemCount(): Int = dataList.size
override fun createFragment(position: Int): Fragment {
val item = dataList[position]
return FollowDynamicListFragment.newInstance(item.user.id ?: "", item.bbs.id)
}
}

View File

@ -0,0 +1,47 @@
package com.gh.gamecenter.forum.home.follow.eventlistener
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.entity.FollowUserEntity
import com.gh.gamecenter.feature.entity.ArticleEntity
import com.gh.gamecenter.feature.entity.ConcernEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.LibaoEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.home.custom.model.CustomPageData
interface OnFollowHomeEventListener {
fun viewItemFollowed(position: Int, data: List<FollowUserEntity>)
fun viewAllFollowed()
fun login()
fun followUser(userId: String, isFollow: Boolean)
fun navigateToLinkPageInCommonContent(
position: Int,
link: LinkEntity,
data: CustomPageData.CommonContentCollection,
text: String,
exposureEvent: ExposureEvent?
)
fun navigateToGameDetailPage(position: Int, game: GameEntity, text: String)
fun navigateToCommonCollectionDetailPage(data: CustomPageData.CommonContentCollection)
fun navigateToUserHomePage(userId: String)
fun onPostCardClick(position: Int, article: ArticleEntity)
fun navigateToLibaoDetailPage(libaoEntity: LibaoEntity)
fun navigateToNewsDetailPage(articleId: String)
fun navigateToArticleCommentDetailPage(articleId: String)
fun onShareArticle(concernEntity: ConcernEntity)
fun copyExchangeCode(code: String)
}

View File

@ -0,0 +1,263 @@
package com.gh.gamecenter.forum.home.follow.fragment
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Rect
import android.view.View
import androidx.core.view.children
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ItemDecoration
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.LazyFragment
import com.gh.gamecenter.common.utils.DialogHelper
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.toArrayList
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.databinding.FragmentAllFollowedBinding
import com.gh.gamecenter.forum.detail.ForumDetailActivity
import com.gh.gamecenter.forum.home.follow.AllFollowedNoTopItem
import com.gh.gamecenter.forum.home.follow.AllFollowedNormalItem
import com.gh.gamecenter.forum.home.follow.adapter.AllFollowedAdapter
import com.gh.gamecenter.forum.home.follow.viewmodel.AllFollowedViewModel
import com.gh.gamecenter.livedata.EventObserver
import com.lightgame.listeners.OnBackPressedListener
import com.lightgame.utils.toast.ToastHelper
import java.util.*
import kotlin.math.abs
class AllFollowedFragment : LazyFragment(), OnBackPressedListener {
private lateinit var binding: FragmentAllFollowedBinding
private val viewModel by viewModels<AllFollowedViewModel>()
private val itemTouchHelper = ItemTouchHelper(MyItemTouchCallback())
private var originalTopList = listOf<AllFollowedNormalItem>()
private val adapter by lazy(LazyThreadSafetyMode.NONE) {
AllFollowedAdapter(viewModel) {
startDrag(it)
}
}
override fun getRealLayoutId(): Int = R.layout.fragment_all_followed
override fun onRealLayoutInflated(inflatedView: View) {
binding = FragmentAllFollowedBinding.bind(inflatedView)
}
override fun onFragmentFirstVisible() {
super.onFragmentFirstVisible()
binding.rvFollowed.layoutManager = LinearLayoutManager(requireContext())
binding.rvFollowed.adapter = adapter
binding.rvFollowed.addItemDecoration(MyItemDecoration(requireContext()))
itemTouchHelper.attachToRecyclerView(binding.rvFollowed)
with(viewModel) {
dataList.observe(viewLifecycleOwner) {
originalTopList = it.filter(AllFollowedNormalItem::isTop)
adapter.setData(it)
}
operateTopSuccessfully.observe(viewLifecycleOwner, EventObserver {
// 提交成功
ToastHelper.showToast(requireContext(), getString(R.string.save_successfully))
finish(true)
})
detailDestination.observe(viewLifecycleOwner, EventObserver {
if (it.isUser) {
DirectUtils.directToHomeActivity(requireContext(), it.user.id)
} else {
startActivity(ForumDetailActivity.getIntent(requireContext(), it.bbs.id, ""))
}
})
loadFirst()
}
binding.tvRight.setOnClickListener {
if (adapter.isEdit && isDataChanged()) {
viewModel.operateTop(adapter.getTopItem())
} else {
adapter.changeToEditable()
binding.tvRight.text = if (adapter.isEdit) "保存" else "完成"
val textResId = if (adapter.isEdit) R.string.finish else R.string.manager
binding.tvRight.setText(textResId)
}
}
binding.ivBack.setOnClickListener {
if (adapter.isEdit && isDataChanged()) {
showSaveTipsDialog()
} else {
finish()
}
}
}
private fun showSaveTipsDialog() {
DialogHelper.showDialog(
requireContext(),
getString(R.string.archive_dialog_title),
getString(R.string.whether_to_save_changes),
cancelText = getString(R.string.dialog_nickname_cancel),
confirmText = getString(R.string.servers_subscribed_game_unsubscribe_dialog_confirm),
confirmClickCallback = {
viewModel.operateTop(adapter.getTopItem())
},
extraConfig = DialogHelper.Config(showCloseIcon = false, centerTitle = true, centerContent = true)
)
}
private fun isDataChanged(): Boolean = originalTopList != adapter.dataList.filter(AllFollowedNormalItem::isTop)
private fun startDrag(viewHolder: RecyclerView.ViewHolder) {
itemTouchHelper.startDrag(viewHolder)
}
override fun onHandleBackPressed(): Boolean {
if (adapter.isEdit && isDataChanged()) {
showSaveTipsDialog()
return true
}
finish()
return true
}
private fun finish(isSorted: Boolean = false) {
if (viewModel.hasReadSetIds.isNotEmpty() || isSorted) {
val intent = Intent()
// 如果顺序发生变化,则直接通知外部重新加载列表,无需局部更新阅读状态
if (!isSorted) {
intent.putStringArrayListExtra(KEY_HAS_READ_ID, viewModel.hasReadSetIds.toList().toArrayList())
}
activity?.setResult(Activity.RESULT_OK, intent)
}
activity?.finish()
}
companion object {
const val KEY_HAS_READ_ID = "key_has_read_id"
}
private class MyItemDecoration(private val context: Context) : ItemDecoration() {
private val paint = Paint().apply {
color = R.color.text_primary.toColor(context)
textSize = 14F.dip2px().toFloat()
}
private val overPaint = Paint().apply {
color = R.color.ui_surface.toColor(context)
}
private val headerHeight by lazy(LazyThreadSafetyMode.NONE) {
HEADER_HEIGHT.dip2px()
}
private val screenWidth by lazy(LazyThreadSafetyMode.NONE) {
context.resources.displayMetrics.widthPixels
}
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
// 是否需要绘制 置顶 悬浮头部
val adapter = parent.adapter
if (adapter is AllFollowedAdapter) {
// 寻找最后一个 置顶 的itemView
val lastTopView = parent.children.lastOrNull {
val position = parent.getChildAdapterPosition(it)
val item = if (position != -1) {
adapter.getItem(position)
} else {
null
}
(item is AllFollowedNormalItem && item.isTop) || item is AllFollowedNoTopItem
}
if (lastTopView != null) {
// 绘制 置顶 悬浮条
val bottom = if (lastTopView.bottom >= headerHeight) {
headerHeight
} else {
lastTopView.bottom
}
c.drawRect(
Rect(0, 0, screenWidth, bottom),
overPaint
)
val text = "置顶(${adapter.topItemCount}/${adapter.itemCount})"
drawFollowedHeader(text, c, bottom)
} else {
// 绘制 “我的关注” 悬浮条
c.drawRect(
Rect(0, 0, screenWidth, headerHeight),
overPaint
)
drawFollowedHeader("我的关注", c, headerHeight)
}
}
}
private fun drawFollowedHeader(text: String, c: Canvas, bottom: Int) {
val fontMetrics = paint.fontMetrics
val centerYDistanceToBaseline =
(abs(fontMetrics.descent) + abs(fontMetrics.ascent) + abs(fontMetrics.leading)) / 2 - abs(
fontMetrics.descent
)
val baselineY = bottom - 25F.dip2px().toFloat() + centerYDistanceToBaseline
c.drawText(text, 0, text.length, 16F.dip2px().toFloat(), baselineY, paint)
}
companion object {
private const val HEADER_HEIGHT = 50F
}
}
private class MyItemTouchCallback : ItemTouchHelper.Callback() {
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
return makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0)
}
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
val adapter = recyclerView.adapter as? AllFollowedAdapter
if (adapter != null) {
val position = viewHolder.bindingAdapterPosition
val item = adapter.getItem(position)
if (item is AllFollowedNormalItem && item.isTop) {
val targetPosition = target.bindingAdapterPosition
val targetItem = adapter.getItem(targetPosition)
if (targetItem is AllFollowedNormalItem && targetItem.isTop) {
val newList = ArrayList(adapter.dataList)
val dataPosition = newList.indexOfFirst { it.data.id == item.data.id }
val targetDataPosition = newList.indexOfFirst { it.data.id == targetItem.data.id }
Collections.swap(newList, dataPosition, targetDataPosition)
adapter.setData(newList)
return true
}
}
}
return false
}
override fun isLongPressDragEnabled(): Boolean = false
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit
}
}

View File

@ -0,0 +1,270 @@
package com.gh.gamecenter.forum.home.follow.fragment
import android.animation.ValueAnimator
import android.graphics.PointF
import android.os.Bundle
import android.view.View
import android.view.animation.Interpolator
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.LinearSmoothScroller
import androidx.recyclerview.widget.OrientationHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.OnScrollListener
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.LazyFragment
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.visibleIf
import com.gh.gamecenter.databinding.FragmentFollowDynamicBinding
import com.gh.gamecenter.entity.FollowUserEntity
import com.gh.gamecenter.forum.home.follow.FollowDynamicActivity.Companion.KEY_SELECTED_POSITION
import com.gh.gamecenter.forum.home.follow.FollowDynamicActivity.Companion.KEY_USERS
import com.gh.gamecenter.forum.home.follow.adapter.FollowedHeaderAdapter
import com.gh.gamecenter.forum.home.follow.adapter.FollowedViewPagerAdapter
import com.gh.gamecenter.forum.home.follow.viewmodel.FollowDynamicViewModel
import com.gh.gamecenter.livedata.EventObserver
import kotlin.math.pow
class FollowDynamicFragment : LazyFragment() {
private lateinit var binding: FragmentFollowDynamicBinding
private val viewModel by activityViewModels<FollowDynamicViewModel>()
private var selectedPosition = 0
private val screenCenterX by lazy(LazyThreadSafetyMode.NONE) {
resources.displayMetrics.widthPixels / 2
}
private val offsetX by lazy(LazyThreadSafetyMode.NONE) {
screenCenterX - 32F.dip2px()
}
private val layoutManager by lazy(LazyThreadSafetyMode.NONE) {
LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL, false)
}
private val helper by lazy {
OrientationHelper.createHorizontalHelper(layoutManager)
}
private val followedAdapter by lazy {
FollowedHeaderAdapter(requireContext(), object : FollowedHeaderAdapter.OnChildEventListener {
override fun onViewItem(position: Int) {
selectedPosition = position
binding.viewPager.setCurrentItem(position, false)
alignIndicator()
}
override fun onViewAll() = Unit
})
}
private val dynamicListVpAdapter by lazy {
FollowedViewPagerAdapter(this)
}
override fun getRealLayoutId(): Int = R.layout.fragment_follow_dynamic
override fun onRealLayoutInflated(inflatedView: View) {
binding = FragmentFollowDynamicBinding.bind(inflatedView)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val list = arguments?.getParcelableArrayList<FollowUserEntity>(KEY_USERS)
if (list != null) {
viewModel.addFirstPageUsers(list)
}
selectedPosition = arguments?.getInt(KEY_SELECTED_POSITION, 0) ?: 0
}
override fun onFragmentFirstVisible() {
super.onFragmentFirstVisible()
with(viewModel) {
followedUsers.observe(viewLifecycleOwner, Observer { (isFirst, data) ->
followedAdapter.submitList(data, selectedPosition)
if (isFirst) {
scrollSelectedPositionToCenter(false)
startAnimation(true)
}
dynamicListVpAdapter.addData(data)
})
}
binding.rvFollowedHeader.layoutManager = layoutManager
binding.rvFollowedHeader.adapter = followedAdapter
binding.viewPager.adapter = dynamicListVpAdapter
binding.viewPager.offscreenPageLimit = 1
binding.viewPager.setCurrentItem(selectedPosition, false)
binding.viewPager.registerOnPageChangeCallback(object : OnPageChangeCallback() {
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) = Unit
override fun onPageScrollStateChanged(state: Int) {
super.onPageScrollStateChanged(state)
if (state != 0) {
binding.ivIndicator.visibleIf(false)
followedAdapter.clearSelectedPosition()
} else {
binding.ivIndicator.visibleIf(true)
followedAdapter.selectPosition(selectedPosition)
}
}
override fun onPageSelected(position: Int) {
if (selectedPosition != position) {
selectedPosition = position
followedAdapter.selectPosition(position)
scrollSelectedPositionToCenter(true)
}
}
})
with(viewModel) {
finishAction.observe(viewLifecycleOwner, EventObserver {
startAnimation(false)
requireActivity().finish()
requireActivity().overridePendingTransition(0, R.anim.no_anim)
})
}
}
private fun scrollSelectedPositionToCenter(isSmoothScroll: Boolean) {
binding.rvFollowedHeader.clearOnScrollListeners()
if (isSmoothScroll) {
// 1. 如果当前 itemView 在屏幕内,则只需要直接将其滚动到中间即可
if (!checkTargetViewIfExist()) {
// 2. 如果当前itemView不在屏幕内则需要先将 itemView 滚动到屏幕内,在将其滚到屏幕中间
binding.rvFollowedHeader.addOnScrollListener(object : OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
binding.rvFollowedHeader.clearOnScrollListeners()
checkTargetViewIfExist()
}
}
})
val vector = layoutManager.computeScrollVectorForPosition(selectedPosition) ?: PointF()
val linearSmoothScroller = object : LinearSmoothScroller(requireContext()) {
override fun getHorizontalSnapPreference(): Int {
super.getHorizontalSnapPreference()
return if (vector.x > 0) SNAP_TO_END else SNAP_TO_START
}
}
linearSmoothScroller.targetPosition = selectedPosition
layoutManager.startSmoothScroll(linearSmoothScroller)
}
} else {
layoutManager.scrollToPositionWithOffset(selectedPosition, offsetX)
}
binding.rvFollowedHeader.addOnScrollListener(object : OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
alignIndicator()
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
alignIndicator()
}
})
}
private fun alignIndicator() {
val targetView = layoutManager.findViewByPosition(selectedPosition)
if (targetView != null) {
binding.ivIndicator.translationX = distanceToStart(targetView).toFloat()
}
}
private fun checkTargetViewIfExist(): Boolean {
val targetView = layoutManager.findViewByPosition(selectedPosition)
if (targetView != null) {
val distanceToCenter = distanceToCenter(targetView)
if (distanceToCenter != 0) {
binding.rvFollowedHeader.smoothScrollBy(distanceToCenter, 0)
}
}
return targetView != null
}
private val easeOutInterpolator = Interpolator { input -> 1 - (1 - input).pow(2) }
private fun startAnimation(isStart: Boolean) {
val values = if (isStart) {
floatArrayOf(ANIMATION_START_VALUE, ANIMATION_END_VALUE)
} else {
floatArrayOf(ANIMATION_END_VALUE, ANIMATION_START_VALUE)
}
val valueAnimator = ValueAnimator.ofFloat(*values)
val followHeaderTranslationY =
requireContext().resources.getDimensionPixelSize(R.dimen.follow_detail_header_translation_y)
val dynamicListTranslationY =
requireContext().resources.getDimensionPixelSize(R.dimen.follow_detail_content_translation_y)
with(valueAnimator) {
duration = ANIMATION_DURATION
interpolator = easeOutInterpolator
addUpdateListener {
val value = it.animatedValue as Float
val alphaEnd = 5F / 8F
val alpha = if (value >= 0 && value <= alphaEnd) {
value / alphaEnd
} else {
1F
}
binding.ivTopBackground.alpha = alpha
binding.rvFollowedHeader.alpha = alpha
binding.viewPager.alpha = alpha
if (value >= 0.5F) {
binding.ivIndicator.alpha = (value - 0.5F) * 2
} else {
binding.ivIndicator.alpha = 0F
}
binding.ivTopBackground.translationY = followHeaderTranslationY * (1 - value)
binding.rvFollowedHeader.translationY = followHeaderTranslationY * (1 - value)
binding.viewPager.translationY = dynamicListTranslationY * (1 - value)
if (isStart && value == 1F) {
viewModel.startLoad()
}
}
start()
}
}
override fun onBackPressed(): Boolean {
return super.onBackPressed()
}
private fun distanceToCenter(targetView: View): Int {
val childCenter = helper.getDecoratedStart(targetView) + helper.getDecoratedMeasurement(targetView) / 2
val containerCenter = helper.startAfterPadding + helper.totalSpace / 2
return childCenter - containerCenter
}
private fun distanceToStart(targetView: View): Int = distanceToCenter(targetView) + screenCenterX
companion object {
private const val ANIMATION_DURATION = 400L
private const val ANIMATION_START_VALUE = 0F
private const val ANIMATION_END_VALUE = 1F
}
}

View File

@ -0,0 +1,264 @@
package com.gh.gamecenter.forum.home.follow.fragment
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.utils.clearHtmlFormatCompletely
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.removeInsertedContent
import com.gh.gamecenter.common.utils.removeVideoContent
import com.gh.gamecenter.databinding.FragmentFollowDynamicListBinding
import com.gh.gamecenter.feature.entity.CommentEntity
import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.forum.detail.ForumDetailActivity
import com.gh.gamecenter.forum.home.follow.FollowActivityResultLauncher
import com.gh.gamecenter.forum.home.follow.FollowDynamicBbsItem
import com.gh.gamecenter.forum.home.follow.FollowDynamicPersonalItem
import com.gh.gamecenter.forum.home.follow.adapter.FollowDynamicAdapter
import com.gh.gamecenter.forum.home.follow.viewmodel.FollowDynamicListViewModel
import com.gh.gamecenter.forum.home.follow.viewmodel.FollowDynamicViewModel
import com.gh.gamecenter.livedata.EventObserver
import com.gh.gamecenter.qa.entity.ArticleDetailEntity
import com.gh.gamecenter.qa.entity.QuestionsDetailEntity
class FollowDynamicListFragment : Fragment() {
private var userId: String = ""
private var bbsId: String = ""
private val viewModel by viewModels<FollowDynamicListViewModel>()
private val followDynamicViewModel by activityViewModels<FollowDynamicViewModel>()
private lateinit var binding: FragmentFollowDynamicListBinding
private val adapter by lazy {
FollowDynamicAdapter(requireContext(), viewModel)
}
private lateinit var launcher: FollowActivityResultLauncher
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
userId = arguments?.getString(KEY_USER_ID) ?: ""
bbsId = arguments?.getString(KEY_BBS_ID) ?: ""
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return FragmentFollowDynamicListBinding.inflate(inflater, container, false)
.also {
binding = it
}.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initView()
with(viewModel) {
loadStatus.observe(viewLifecycleOwner) { (loadStatus, isPullRefresh) ->
if (isPullRefresh) {
if (loadStatus != LoadStatus.INIT_LOADING) {
binding.srlRefresh.isRefreshing = false
}
} else {
binding.reuseLoading.root.goneIf(loadStatus != LoadStatus.INIT_LOADING)
binding.reuseNoData.root.goneIf(loadStatus != LoadStatus.INIT_EMPTY)
binding.reuseNoConnection.root.goneIf(loadStatus != LoadStatus.LIST_FAILED)
binding.srlRefresh.isEnabled =
!(loadStatus == LoadStatus.INIT_FAILED || loadStatus == LoadStatus.INIT_LOADING)
}
adapter.setLoadStatus(loadStatus)
}
dataList.observe(viewLifecycleOwner) {
adapter.submitList(it)
}
personDetailDestination.observe(viewLifecycleOwner, EventObserver { (personalEntity, position) ->
launcher.onPersonItemClick(position, personalEntity)
})
bbsDetailDestination.observe(viewLifecycleOwner, EventObserver { (position, answer) ->
launcher.onBbsItemClick(position, answer)
})
}
followDynamicViewModel.isStartLoadAction.observe(viewLifecycleOwner, Observer {
if (it) {
viewModel.loadFirst(userId, bbsId, false)
}
})
binding.tvTitle.text = if (userId.isNotBlank()) {
getString(R.string.follow_detail_person_updates)
} else {
getString(R.string.follow_detail_game_updates)
}
binding.tvRight.text = if (userId.isNotBlank()) {
getString(R.string.user_home_page)
} else {
getString(R.string.go_to_forum)
}
binding.tvRight.setOnClickListener {
if (userId.isNotBlank()) {
DirectUtils.directToHomeActivity(requireContext(), userId)
} else {
startActivity(ForumDetailActivity.getIntent(requireContext(), bbsId, ""))
}
}
}
private fun initView() {
launcher = FollowActivityResultLauncher(requireActivity().activityResultRegistry,
object : FollowActivityResultLauncher.OnResultListener {
override fun onArticleDetailResult(position: Int, articleDetailEntity: ArticleDetailEntity) {
val item = adapter.currentList[position]
if (item is FollowDynamicPersonalItem) {
item.data.apply {
val newCount = count
newCount.vote = articleDetailEntity.count.vote
newCount.comment = articleDetailEntity.count.comment
count = newCount
title = articleDetailEntity.title
brief = articleDetailEntity.content.removeVideoContent().removeInsertedContent()
.clearHtmlFormatCompletely()
me.isCommunityArticleVote = articleDetailEntity.me.isCommunityArticleVote
}
}
if (item is FollowDynamicBbsItem) {
item.data.apply {
val newCount = count
newCount.vote = articleDetailEntity.count.vote
newCount.comment = articleDetailEntity.count.comment
count = newCount
title = articleDetailEntity.title
brief = articleDetailEntity.content.removeVideoContent().removeInsertedContent()
.clearHtmlFormatCompletely()
me.isCommunityArticleVote = articleDetailEntity.me.isCommunityArticleVote
}
}
adapter.notifyItemChanged(position)
}
override fun onCommentResult(position: Int, commentEntity: CommentEntity) {
val item = adapter.currentList[position]
if (item is FollowDynamicPersonalItem) {
item.data.apply {
val newCount = count
newCount.vote = commentEntity.vote
newCount.comment = commentEntity.reply
count = newCount
brief = commentEntity.content ?: ""
me.isAnswerVoted = commentEntity.me?.isCommentVoted ?: false
}
}
if (item is FollowDynamicBbsItem) {
item.data.apply {
val newCount = count
newCount.vote = commentEntity.vote
newCount.comment = commentEntity.reply
count = newCount
brief = commentEntity.content ?: ""
me.isAnswerVoted = commentEntity.me?.isCommentVoted ?: false
}
}
adapter.notifyItemChanged(position)
}
override fun onQuestionsDetailResult(position: Int, questionsDetailEntity: QuestionsDetailEntity) {
val item = adapter.currentList[position]
if (item is FollowDynamicPersonalItem) {
item.data.apply {
val newCount = count
newCount.answer = questionsDetailEntity.count.answer - count.reply
count = newCount
title = questionsDetailEntity.title ?: ""
}
}
if (item is FollowDynamicBbsItem) {
item.data.apply {
val newCount = count
newCount.answer = questionsDetailEntity.count.answer - count.reply
count = newCount
title = questionsDetailEntity.title ?: ""
}
}
adapter.notifyItemChanged(position)
}
override fun onForumVideoResult(position: Int, forumVideoEntity: ForumVideoEntity) {
val item = adapter.currentList[position]
if (item is FollowDynamicPersonalItem) {
item.data.apply {
val newCount = count
newCount.vote = forumVideoEntity.count.vote
newCount.comment = forumVideoEntity.count.comment
count = newCount
des = forumVideoEntity.des
title = forumVideoEntity.title
me.isVoted = forumVideoEntity.me.isVoted
}
}
if (item is FollowDynamicBbsItem) {
item.data.apply {
val newCount = count
newCount.vote = forumVideoEntity.count.vote
newCount.comment = forumVideoEntity.count.comment
count = newCount
des = forumVideoEntity.des
title = forumVideoEntity.title
me.isVoted = forumVideoEntity.me.isVoted
}
}
adapter.notifyItemChanged(position)
}
})
viewLifecycleOwner.lifecycle.addObserver(launcher)
binding.rvDynamic.layoutManager = LinearLayoutManager(requireContext())
binding.rvDynamic.adapter = adapter
binding.srlRefresh.setOnRefreshListener {
viewModel.loadFirst(userId, bbsId, true)
}
binding.ivClose.setOnClickListener {
followDynamicViewModel.finish()
}
}
companion object {
private const val KEY_USER_ID = "key_user_id"
private const val KEY_BBS_ID = "key_bbs_id"
fun newInstance(userId: String, bbsId: String): FollowDynamicListFragment {
val fragment = FollowDynamicListFragment()
val bundle = Bundle().apply {
putString(KEY_USER_ID, userId)
putString(KEY_BBS_ID, bbsId)
}
fragment.arguments = bundle
return fragment
}
}
}

View File

@ -0,0 +1,477 @@
package com.gh.gamecenter.forum.home.follow.fragment
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.View
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContract
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.OnScrollListener
import com.alibaba.android.arouter.launcher.ARouter
import com.gh.common.util.CheckLoginUtils
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.ShareCardActivity
import com.gh.gamecenter.ShareCardPicActivity
import com.gh.gamecenter.common.base.fragment.LazyFragment
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.iinterface.IScrollable
import com.gh.gamecenter.core.provider.IVisitManagerProvider
import com.gh.gamecenter.core.utils.MD5Utils
import com.gh.gamecenter.databinding.FragmentFollowHomeBinding
import com.gh.gamecenter.entity.FollowUserEntity
import com.gh.gamecenter.eventbus.EBUserFollow
import com.gh.gamecenter.feature.provider.IMessageDetailProvider
import com.gh.gamecenter.forum.home.CommunityHomeViewModel
import com.gh.gamecenter.forum.home.ForumScrollCalculatorHelper
import com.gh.gamecenter.forum.home.follow.*
import com.gh.gamecenter.forum.home.follow.adapter.FollowHomeAdapter
import com.gh.gamecenter.forum.home.follow.fragment.AllFollowedFragment.Companion.KEY_HAS_READ_ID
import com.gh.gamecenter.forum.home.follow.model.FollowRepository.Companion.FOLLOW_UPDATE_TYPE_GAME
import com.gh.gamecenter.forum.home.follow.model.FollowRepository.Companion.FOLLOW_UPDATE_TYPE_USER
import com.gh.gamecenter.forum.home.follow.viewholder.FollowHomeHeaderViewHolder
import com.gh.gamecenter.forum.home.follow.viewmodel.FollowHomeViewModel
import com.gh.gamecenter.game.commoncollection.detail.CustomCommonCollectionDetailActivity
import com.gh.gamecenter.libao.LibaoDetailActivity
import com.gh.gamecenter.livedata.EventObserver
import com.gh.gamecenter.login.user.UserViewModel
import com.gh.gamecenter.message.view.concern.ConcernFragment
import com.shuyu.gsyvideoplayer.video.base.GSYVideoView
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class FollowHomeFragment : LazyFragment(), IScrollable {
override fun addSyncPageObserver(): Boolean {
return true
}
private val viewModel by viewModels<FollowHomeViewModel>()
private val communityHomeViewModel by viewModels<CommunityHomeViewModel>(
ownerProducer = { requireParentFragment() }
)
private lateinit var userViewModel: UserViewModel
private var userId: String? = null
private var dynamicType: String? = null
private var binding: FragmentFollowHomeBinding? = null
private var hasLoaded = false
private lateinit var pageConfiguration: FollowPageConfiguration
private var isFirstTimeEnter = true
private val handler = Handler(Looper.getMainLooper())
private val layoutManager by lazy {
LinearLayoutManager(context)
}
private val adapter: FollowHomeAdapter by lazy {
FollowHomeAdapter(viewLifecycleOwner, viewModel, pageConfiguration)
}
// 3s 以后才开始计算
private var enterStartTime = 0L
private lateinit var allFollowLauncher: ActivityResultLauncher<Unit>
private var mScrollCalculatorHelper: ForumScrollCalculatorHelper? = null
private val headerViewHolder by lazy(LazyThreadSafetyMode.NONE) {
binding?.let {
FollowHomeHeaderViewHolder(it.layoutHeader, object : FollowHomeHeaderViewHolder.OnEventListener {
override fun onItemClick(position: Int, data: List<FollowUserEntity>) {
viewModel.viewItemFollowed(position, data)
}
override fun onAllClick() {
viewModel.viewAllFollowed()
}
override fun onLoginClick() {
viewModel.login()
}
})
}
}
override fun provideSyncAdapter(): RecyclerView.Adapter<*> {
return adapter
}
override fun getRealLayoutId(): Int = R.layout.fragment_follow_home
override fun onRealLayoutInflated(inflatedView: View) {
binding = FragmentFollowHomeBinding.bind(inflatedView)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
pageConfiguration = FollowPageConfiguration("", "")
allFollowLauncher = requireActivity().activityResultRegistry.register(
KEY_LAUNCH_ALL_FOLLOW,
this,
object : ActivityResultContract<Unit, List<String>?>() {
override fun createIntent(context: Context, input: Unit?): Intent {
return AllFollowedActivity.getIntent(context)
}
override fun parseResult(resultCode: Int, intent: Intent?): List<String>? {
if (resultCode == Activity.RESULT_OK) {
val hasReadIds = intent?.getStringArrayListExtra(KEY_HAS_READ_ID)
return hasReadIds ?: listOf()
}
return null
}
}
) { value ->
value?.let {
if (it.isEmpty()) {
viewModel.loadFollowedUsers()
} else {
adapter.notifyFollowUserReadStatus(it)
}
}
}
viewModel.hasFollowedUser.observe(this, Observer {
communityHomeViewModel.updateHasFollowUser(it)
})
}
override fun initRealView() {
super.initRealView()
mScrollCalculatorHelper = ForumScrollCalculatorHelper(R.id.horizontalVideoView, R.id.verticalVideoView, 0)
binding?.recyclerView?.itemAnimator = null
userViewModel = viewModelProvider(UserViewModel.Factory(requireActivity().application))
userViewModel.loginObsUserinfo.observe(viewLifecycleOwner) {
/* 由于其它页面每次初始化 userViewModel时loginObsUserInfo都会被赋值一次为避免重复加载这里需要过滤重复的用户信息 */
if (hasLoaded && userId == it?.data?.userId) {
// 已经加载过数据并且userId不变不需要重复加载
return@observe
}
hasLoaded = true
if (it?.data == null) {
// 未登录,加载推荐关注列表
userId = null
viewModel.loadFirst(userId != null, isPullToRefresh = false)
} else {
// 已登录,加载已关注列表
userId = it.data.userId
if (userId != null) {
viewModel.loadFirst(userId != null, isPullToRefresh = false)
}
}
}
communityHomeViewModel.filterFollowedAction.observe(viewLifecycleOwner, EventObserver {
val newDynamicType = when (it) {
0 -> ""
1 -> FOLLOW_UPDATE_TYPE_USER
else -> FOLLOW_UPDATE_TYPE_GAME
}
if (dynamicType != newDynamicType) {
dynamicType = newDynamicType
viewModel.loadFirst(userId != null, dynamicType ?: "", false)
}
})
with(viewModel) {
loadStatus.observe(viewLifecycleOwner) { (loadStatus, isPullRefresh) ->
if (isPullRefresh) {
if (loadStatus != LoadStatus.INIT_LOADING) {
binding?.srlRefresh?.isRefreshing = false
}
} else {
binding?.layoutLoading?.root?.goneIf(loadStatus != LoadStatus.INIT_LOADING)
}
adapter.setLoadStatus(loadStatus)
if (loadStatus == LoadStatus.INIT_LOADED) {
AppExecutor.uiExecutor.executeWithDelay(Runnable {
tryCatchInRelease {
scroll()
mScrollCalculatorHelper?.onScrollStateChanged(
binding?.recyclerView!!,
RecyclerView.SCROLL_STATE_IDLE
)
}
}, 100)
}
}
dataList.observe(viewLifecycleOwner) {
if (it.size == 1) {
// 由于第一个是头部如果list长度为1则说明后面没有数据
binding?.llEmptyContainer?.goneIf(false)
binding?.recyclerView?.goneIf(true)
val headerItem = it.firstOrNull() as? FollowUserItem
if (headerItem != null) {
headerViewHolder?.bind(headerItem.isLogin, headerItem.data)
setEmptyLayout(headerItem)
}
} else {
binding?.llEmptyContainer?.goneIf(true)
binding?.recyclerView?.goneIf(false)
setDataList(it)
}
}
followedDetailDestination.observe(viewLifecycleOwner, EventObserver { (position, data) ->
FollowDynamicActivity.start(requireContext(), position, data)
activity?.overridePendingTransition(0, 0) // 取消进入和退出动画
})
myFollowedListDestination.observe(viewLifecycleOwner, EventObserver {
allFollowLauncher.launch(Unit)
})
loginDestination.observe(viewLifecycleOwner, EventObserver {
CheckLoginUtils.checkLogin(context, "关注", null)
})
gameDetailDestination.observe(viewLifecycleOwner, EventObserver {
GameDetailActivity.startGameDetailActivity(
requireContext(),
it.id,
"",
-1,
traceEvent = it.exposureEvent
)
})
linkDestination.observe(viewLifecycleOwner, EventObserver { (link, exposureEvent) ->
DirectUtils.directToLinkPage(requireContext(), link, "关注页面", "", exposureEvent)
})
commonCollectionDestination.observe(viewLifecycleOwner, EventObserver {
val intent = CustomCommonCollectionDetailActivity.getIntent(
requireContext(),
it.id,
it.layout,
"自定义页面",
)
startActivity(intent)
})
userHomeDestination.observe(viewLifecycleOwner, EventObserver {
DirectUtils.directToHomeActivity(requireContext(), it, 1, "", "")
})
libaoDetailDestination.observe(viewLifecycleOwner, EventObserver {
val intent = LibaoDetailActivity.getIntent(requireContext(), it, false, "$mEntrance+关注列表")
startActivity(intent)
})
newDetailDestination.observe(viewLifecycleOwner, EventObserver {
ARouter.getInstance().build(RouteConsts.activity.newsDetailActivity)
.withString(EntranceConsts.KEY_NEWSID, it)
.withString(EntranceConsts.KEY_ENTRANCE, "")
.navigation(requireActivity(), ConcernFragment.NEWS_MESSAGE_ARTICLE_REQUEST)
})
articleCommentDetailDestination.observe(viewLifecycleOwner, EventObserver {
val messageDetailProvider = ARouter.getInstance().build(RouteConsts.provider.messageDetail)
.navigation() as? IMessageDetailProvider
if (messageDetailProvider != null) {
val intent = messageDetailProvider.getIntentById(requireContext(), it, -1, false, "")
startActivityForResult(intent, ConcernFragment.NEWS_MESSAGE_ARTICLE_REQUEST)
}
})
shareArticleDestination.observe(viewLifecycleOwner, EventObserver { concernEntity ->
val imgs = concernEntity.img
if (!imgs.isNullOrEmpty()) {
ShareCardPicActivity.startShareCardPicActivity(requireContext(), concernEntity, mEntrance)
} else {
val shareContent =
if (concernEntity.brief != null) {
concernEntity.brief ?: ""
} else {
concernEntity.content ?: ""
}
startActivity(ShareCardActivity.getIntent(context, concernEntity, shareContent))
}
})
copyExchangeCodeAction.observe(viewLifecycleOwner, EventObserver {
it.copyTextAndToast()
})
updateOkhttpCacheAction.observe(viewLifecycleOwner, EventObserver {
// 更新okhttp缓存数据
val visitManagerProvider =
ARouter.getInstance()
.build(RouteConsts.provider.visitManager)
.navigation() as? IVisitManagerProvider
visitManagerProvider?.updateOkhttpCache(requireContext(), it)
})
}
binding?.srlRefresh?.setOnRefreshListener {
mScrollCalculatorHelper?.currentPlayer?.release()
mScrollCalculatorHelper?.reset()
val isLogin = userId != null
viewModel.loadFirst(isLogin, dynamicType ?: "", true)
}
binding?.recyclerView?.addOnScrollListener(object : OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
binding?.recyclerView?.let {
mScrollCalculatorHelper?.onScrollStateChanged(it, newState)
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (dy != 0) scroll()
}
})
}
private fun setEmptyLayout(headerItem: FollowUserItem) {
binding?.layoutEmpty?.run {
root.goneIf(false)
if (headerItem.isLogin) {
reuseNoneDataDescTv.goneIf(true)
} else {
reuseNoneDataDescTv.goneIf(false)
reuseNoneDataDescTv.setText(R.string.follow_home_no_login_tips)
}
}
}
private fun scroll() {
val firstVisibleItem = layoutManager.findFirstVisibleItemPosition()
val lastVisibleItem = layoutManager.findLastVisibleItemPosition()
mScrollCalculatorHelper?.onScroll(viewModel.videoList, firstVisibleItem, lastVisibleItem)
}
override fun onFragmentResume() {
resumeVideo()
super.onFragmentResume()
handler.postDelayed({
enterStartTime = System.currentTimeMillis()
SensorsBridge.trackFollowTabBrowse(isFirstTimeEnter)
}, TRACK_ENTER_TIME)
}
override fun onFragmentPause() {
super.onFragmentPause()
pauseVideo()
isFirstTimeEnter = false
handler.removeCallbacksAndMessages(null)
if (enterStartTime != 0L) {
val endTime = System.currentTimeMillis()
val stayLength = "${(endTime - enterStartTime) / MS_UNIT}"
SensorsBridge.trackFollowTabBrowseDuration(stayLength)
}
enterStartTime = 0L
}
private fun resumeVideo() {
mScrollCalculatorHelper?.run {
if (currentPlayer != null
&& currentPosition >= 0
&& currentPosition < viewModel.videoList.size
) {
val video = viewModel.videoList.safelyGetInRelease(currentPosition)
if (video != null) {
val position = ForumScrollCalculatorHelper.getPlaySchedule(MD5Utils.getContentMD5(video.url))
//这里必须要延迟操作,否则会白屏
mBaseHandler.postDelayed({
tryCatchInRelease {
if (position != 0L) {
if (currentPlayer?.currentState == GSYVideoView.CURRENT_STATE_PAUSE) {
currentPlayer?.startPlayLogic(true)
}
} else {
currentPlayer?.release()
}
}
}, 50)
}
}
}
}
private fun pauseVideo() {
mScrollCalculatorHelper?.run {
if (currentPlayer != null
&& currentPosition >= 0
&& currentPosition < viewModel.videoList.size
) {
currentPlayer?.onVideoPause()
val position = currentPlayer?.getCurrentPosition() ?: 0L
val video = viewModel.videoList.safelyGetInRelease(currentPosition)
if (video != null) {
ForumScrollCalculatorHelper.savePlaySchedule(MD5Utils.getContentMD5(video.url), position)
}
}
}
}
override fun onDestroy() {
super.onDestroy()
mScrollCalculatorHelper?.currentPlayer?.release()
}
private fun setDataList(dataList: List<FollowItem>) {
binding?.run {
if (recyclerView.adapter == null) {
recyclerView.layoutManager = layoutManager
recyclerView.adapter = adapter
}
adapter.submitList(dataList)
}
}
override fun scrollToTop() {
binding?.recyclerView?.scrollToPosition(0)
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onUserFollow(change: EBUserFollow) {
adapter.notifyFollowUserChanged(change)
}
companion object {
private const val KEY_LAUNCH_ALL_FOLLOW = "key_launch_all_follow"
const val LOCATION_FOLLOW_PAGE = "关注页面"
private const val TRACK_ENTER_TIME = 3000L
private const val MS_UNIT = 1000.0
}
}

View File

@ -0,0 +1,49 @@
package com.gh.gamecenter.forum.home.follow.model
import com.gh.common.util.PackageUtils
import com.gh.gamecenter.common.utils.toRequestBody
import com.gh.gamecenter.entity.FollowUserEntity
import com.gh.gamecenter.feature.entity.Auth
import com.gh.gamecenter.feature.entity.UserEntity
import com.gh.gamecenter.forum.home.follow.AllFollowedNormalItem
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.retrofit.service.ApiService
import com.halo.assistant.HaloApp
import io.reactivex.Single
import okhttp3.ResponseBody
class AllFollowRepository(private val apiService: ApiService) {
fun loadMyFollowedUser(pageNo: Int) =
apiService.getMyFollowedUsers(
UserManager.getInstance().userId,
PackageUtils.getGhVersionName(),
HaloApp.getInstance().channel,
pageNo,
true,
mapOf()
)
.map { list ->
list.map {
AllFollowedNormalItem(it, it.isTop)
}
}
fun operateTop(tops: List<Top>): Single<ResponseBody> {
val body = mapOf("top" to tops).toRequestBody()
return apiService.operateTop(
UserManager.getInstance().userId,
PackageUtils.getGhVersionName(),
HaloApp.getInstance().channel,
body
)
}
fun postRead(type: String, typeId: String) =
apiService.postRead(UserManager.getInstance().userId, type, typeId)
data class Top(
val _id: String,
val order: Int
)
}

View File

@ -0,0 +1,47 @@
package com.gh.gamecenter.forum.home.follow.model
import com.gh.common.util.PackageUtils
import com.gh.gamecenter.forum.home.follow.FollowDynamicBbsItem
import com.gh.gamecenter.forum.home.follow.FollowDynamicPersonalItem
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.retrofit.service.ApiService
import com.halo.assistant.HaloApp
class FollowDynamicRepository(
private val apiService: ApiService
) {
fun loadData(userId: String, bbsId: String, pageNo: Int) =
if (userId.isNotBlank()) {
loadPersonDynamic(userId, pageNo)
} else {
loadBbsDynamic(bbsId, pageNo)
}
private fun loadPersonDynamic(userId: String, pageNo: Int) =
apiService.getPersonalHistory(
userId,
pageNo,
HaloApp.getInstance().channel,
PERSON_DYNAMIC_FILTER
).map {
it.map(::FollowDynamicPersonalItem)
}
private fun loadBbsDynamic(bbsId: String, pageNo: Int) =
apiService.getAllForumList(bbsId, BBS_DYNAMIC_SORT, pageNo, mapOf())
.map {
it.map(::FollowDynamicBbsItem)
}
companion object {
private const val PERSON_DYNAMIC_FILTER = "scene:follow_personal,type:all"
private const val BBS_DYNAMIC_SORT = "time.reply:-1&type=community_article|question|video"
fun newInstance(): FollowDynamicRepository = FollowDynamicRepository(
RetrofitManager.getInstance().api
)
}
}

View File

@ -0,0 +1,233 @@
package com.gh.gamecenter.forum.home.follow.model
import com.gh.common.util.LibaoUtils
import com.gh.common.util.PackageUtils
import com.gh.gamecenter.core.utils.UrlFilterUtils
import com.gh.gamecenter.entity.FollowDynamicEntity
import com.gh.gamecenter.entity.FollowDynamicEntity.Companion.FOLLOW_UPDATE_TYPE_ARTICLE
import com.gh.gamecenter.entity.FollowDynamicEntity.Companion.FOLLOW_UPDATE_TYPE_LIBAO
import com.gh.gamecenter.entity.FollowDynamicEntity.Companion.FOLLOW_UPDATE_TYPE_LIBAO_EXCHANGE
import com.gh.gamecenter.entity.FollowDynamicEntity.Companion.FOLLOW_UPDATE_TYPE_USER_POST
import com.gh.gamecenter.entity.FollowUserEntity
import com.gh.gamecenter.feature.entity.LibaoEntity
import com.gh.gamecenter.forum.home.follow.*
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_BANNER
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_VERTICAL_CARD
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_VERTICAL_IMAGE_TEXT
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.retrofit.service.ApiService
import com.halo.assistant.HaloApp
import io.reactivex.Single
class FollowRepository(
private val apiService: ApiService,
private val newApiService: ApiService
) {
private var hasFollowUser = false
private val version: String
get() = PackageUtils.getGhVersionName()
private val channel: String
get() = HaloApp.getInstance().channel
fun loadHomeData(isLogin: Boolean, type: String, pageNo: Int) =
if (isLogin) {
loadFollowedUsers()
.flatMap { users ->
if (users.isEmpty()) {
hasFollowUser = false
// 默认推荐
loadFollowRecommend(pageNo)
.map {
listOf(FollowUserItem(true, listOf())) + it
}
} else {
// 关注动态
hasFollowUser = true
loadFollowUpdates(type, pageNo)
.map {
listOf(FollowUserItem(true, users)) + it
}
}
}
} else {
loadFollowRecommend(pageNo)
.map {
listOf(FollowUserItem(false, listOf())) + it
}
}
fun loadMore(type: String, pageNo: Int) =
if (hasFollowUser) {
loadFollowUpdates(type, pageNo)
} else {
loadFollowRecommend(pageNo)
}
fun loadFollowedUsers(): Single<List<FollowUserEntity>> {
return newApiService.getMyFollowedUsers(
UserManager.getInstance().userId,
PackageUtils.getGhVersionName(),
HaloApp.getInstance().channel,
1,
true,
mapOf("page_size" to 20)
).onErrorReturnItem(listOf())
}
private fun loadFollowUpdates(type: String, pageNo: Int): Single<List<FollowItem>> =
newApiService.getFollowUpdates(
UserManager.getInstance().userId,
type,
pageNo,
PackageUtils.getGhVersionName(),
HaloApp.getInstance().channel
)
.onErrorReturnItem(listOf())
.flatMap {
loadGiftPackStatusIfNeed(it)
}.map(::transformFollowUpdatesToItemData)
fun loadGiftPackStatusIfNeed(data: List<FollowDynamicEntity>): Single<List<FollowDynamicEntity>> {
val libaoList = arrayListOf<LibaoEntity>()
data.forEach {
if (it.libao != null) {
libaoList.add(it.libao)
}
if (it.libaoExchange != null) {
libaoList.add(it.libaoExchange)
}
}
val builder = StringBuilder()
var i = 0
val size = libaoList.size
while (i < size) {
builder.append(libaoList[i].id)
builder.append("-")
i++
}
if (builder.isEmpty()) {
return Single.just(data)
}
builder.deleteCharAt(builder.length - 1)
val ids = builder.toString()
return apiService.getLibaoStatus(UrlFilterUtils.getFilterQuery("libao_ids", ids))
.map { statusList ->
LibaoUtils.initLiBaoEntity(statusList, libaoList)
data
}.single(listOf())
}
fun loadFollowRecommend(pageNo: Int): Single<List<FollowItem>> =
if (pageNo == 1) {
loadRecommendUsers().zipWith(
loadFollowCommonCollection(pageNo)
) { user, commonCollections ->
val data = arrayListOf<FollowItem>()
if (user.data.isNotEmpty()) {
data.add(user)
}
data.addAll(commonCollections)
data
}
} else {
Single.just(listOf())
}
private fun loadRecommendUsers(): Single<FollowRecommendUsersItem> =
newApiService.getRecommendUser(version, channel)
.onErrorReturnItem(listOf())
.map(::FollowRecommendUsersItem)
private fun loadFollowCommonCollection(pageNo: Int): Single<List<FollowItem>> =
newApiService.getFollowCommonCollection(version, channel, pageNo)
.onErrorReturnItem(listOf())
.map { components ->
val data = arrayListOf<FollowItem>()
components.forEach {
val layout = it.linkCommonCollection?.layout
if (layout == COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_BANNER
|| layout == COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_VERTICAL_CARD
|| layout == COMMON_CONTENT_COLLECTION_LAYOUT_VERTICAL_IMAGE_TEXT
) {
data.addAll(convertSplitCommonContentCollection(it))
} else {
data.add(
FollowCommonCollectionItem(
it.linkCommonCollection ?: CustomPageData.CommonContentCollection()
)
)
}
}
data
}
private fun convertSplitCommonContentCollection(item: CustomPageData.CustomsComponent): List<FollowSplitCommonContentCollectionItem> {
val linkCommonCollection = item.linkCommonCollection ?: return emptyList()
val data = linkCommonCollection.data
val items = arrayListOf<FollowSplitCommonContentCollectionItem>()
(data.indices step 2).forEach { left ->
items.add(
FollowSplitCommonContentCollectionItem(
linkCommonCollection,
left
)
)
}
return items
}
fun followUser(userId: String, isFollow: Boolean) =
if (isFollow) {
apiService.postFollowing(userId)
} else {
apiService.deleteFollowing(userId)
}
fun postArticleVisit(articleId: String) =
apiService.postArticleVisit(articleId)
companion object {
const val FOLLOW_UPDATE_TYPE_USER = "user"
const val FOLLOW_UPDATE_TYPE_GAME = "game"
fun transformFollowUpdatesToItemData(updates: List<FollowDynamicEntity>): List<FollowItem> {
val data = arrayListOf<FollowItem>()
updates.forEach {
when (it.type) {
FOLLOW_UPDATE_TYPE_LIBAO -> {
if (it.libao != null) {
FollowGiftPackItem(it.libao, it.time)
} else {
null
}
}
FOLLOW_UPDATE_TYPE_LIBAO_EXCHANGE -> {
if (it.libaoExchange != null) {
FollowGiftPackItem(it.libaoExchange, it.time)
} else {
null
}
}
FOLLOW_UPDATE_TYPE_ARTICLE -> FollowArticleItem(it.article)
FOLLOW_UPDATE_TYPE_USER_POST -> FollowPostCardItem(it.userPost)
else -> null
}?.let(data::add)
}
return data
}
fun newInstance() = FollowRepository(RetrofitManager.getInstance().api, RetrofitManager.getInstance().newApi)
}
}

View File

@ -0,0 +1,85 @@
package com.gh.gamecenter.forum.home.follow.viewholder
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.databinding.CommonCollection12ItemCustomBinding
import com.gh.gamecenter.entity.CommonCollectionContentEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.ui.CommonContentCollection12Ui
/**
* 关注-双列banner 双列竖式卡片 竖排图文列表
* 通用链接合集(1-2样式)
*/
class FollowCommonCollection12ViewHolder(
val binding: CommonCollection12ItemCustomBinding,
private val listener: OnChildEventListener
) :
ViewHolder(binding.root) {
private lateinit var _data: CustomPageData.CommonContentCollection
private val collection12Ui by lazy {
CommonContentCollection12Ui(binding, object : CommonContentCollection12Ui.OnCollection12Listener {
override fun onChildItemClick(childPosition: Int, contentEntity: CommonCollectionContentEntity) {
val linkEntity = contentEntity.linkEntity
NewLogUtils.logCommonCollectionClick(
_data.id,
_data.name,
"",
"",
"关注列表",
"关注首页",
linkEntity.title ?: "",
contentEntity.addedContent1 ?: "",
contentEntity.addedContent2 ?: "",
linkEntity.type ?: "",
linkEntity.text ?: "",
childPosition + 1
)
NewLogUtils.logCommonCategoryHomeContentClick(
contentEntity.title,
linkEntity.type ?: "",
linkEntity.link ?: "",
linkEntity.text ?: "",
_data.name,
_data.id,
"关注",
""
)
listener.navigateToLinkPage(bindingAdapterPosition, linkEntity, _data, "内容卡片", null)
}
override fun navigateToCommonCollectionDetailPage(collection: CustomPageData.CommonContentCollection) {
listener.navigateToCommonCollectionDetailPage(collection)
}
})
}
fun bind(data: CustomPageData.CommonContentCollection, leftPosition: Int) {
_data = data
collection12Ui.bind(data, leftPosition)
itemView.setBackgroundColor(R.color.ui_surface.toColor(itemView.context))
}
interface OnChildEventListener {
fun navigateToLinkPage(
position: Int,
link: LinkEntity,
data: CustomPageData.CommonContentCollection,
text: String,
exposureEvent: ExposureEvent?
)
fun navigateToCommonCollectionDetailPage(data: CustomPageData.CommonContentCollection)
}
}

View File

@ -0,0 +1,104 @@
package com.gh.gamecenter.forum.home.follow.viewholder
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.common.exposure.ExposureManager
import com.gh.common.exposure.ExposureTraceUtils
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.databinding.CommonCollectionListCustomBinding
import com.gh.gamecenter.entity.CommonCollectionContentEntity
import com.gh.gamecenter.entity.ExposureLinkEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.ExposureType
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.ui.CommonContentHorizontalSlideListUi
/**
* 关注-通用内容合集-横排滑动Banner
* 关注-通用内容合集-横排竖式卡片
* 关注-通用内容合集-横排图文列表
*/
class FollowCommonCollectionViewHolder(
val binding: CommonCollectionListCustomBinding,
private val listener: OnChildEventListener
) : ViewHolder(binding.root) {
private lateinit var _data: CustomPageData.CommonContentCollection
private val horizontalSlideUi by lazy {
CommonContentHorizontalSlideListUi(binding,
object : CommonContentHorizontalSlideListUi.OnHorizontalSlideListListener {
override fun addExposureEvent(childPosition: Int, link: ExposureLinkEntity) = Unit
override fun onChildItemClick(childPosition: Int, entity: CommonCollectionContentEntity) {
val linkEntity = entity.linkEntity
NewLogUtils.logCommonCollectionClick(
_data.id,
_data.name,
"",
"",
"关注推荐列表",
"关注页",
linkEntity.title ?: "",
entity.addedContent1 ?: "",
entity.addedContent2 ?: "",
linkEntity.type ?: "",
linkEntity.text ?: "",
childPosition + 1
)
NewLogUtils.logCommonCategoryHomeContentClick(
entity.title,
linkEntity.type ?: "",
linkEntity.link ?: "",
linkEntity.text ?: "",
_data.name,
_data.id,
"",
""
)
linkEntity.exposureEvent?.let {
val clickEvent = ExposureEvent(
payload = it.payload,
source = it.source,
eTrace = ExposureTraceUtils.appendTrace(it),
event = ExposureType.CLICK
)
if (linkEntity.type != "game") {
ExposureManager.log(clickEvent)
}
}
listener.navigateToLinkPage(bindingAdapterPosition, linkEntity, _data, "内容卡片", null)
}
override fun navigateToCommonCollectionDetailPage(data: CustomPageData.CommonContentCollection) {
listener.navigateToCommonCollectionDetailPage(data)
}
})
}
fun bindView(data: CustomPageData.CommonContentCollection) {
_data = data
horizontalSlideUi.bind(_data)
itemView.setBackgroundColor(R.color.ui_surface.toColor(itemView.context))
}
interface OnChildEventListener {
fun navigateToLinkPage(
position: Int,
link: LinkEntity,
data: CustomPageData.CommonContentCollection,
text: String,
exposureEvent: ExposureEvent?
)
fun navigateToCommonCollectionDetailPage(data: CustomPageData.CommonContentCollection)
}
}

View File

@ -0,0 +1,149 @@
package com.gh.gamecenter.forum.home.follow.viewholder
import android.view.View
import android.widget.ProgressBar
import android.widget.TextView
import androidx.annotation.StringRes
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.R
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.baselist.ListViewModel
import com.gh.gamecenter.common.baselist.LoadType
import com.gh.gamecenter.common.databinding.RefreshFooterviewBinding
class FollowFooterViewHolder(val binding: RefreshFooterviewBinding) : RecyclerView.ViewHolder(binding.root) {
val loading: ProgressBar
val hint: TextView
init {
loading = itemView.findViewById(R.id.footerview_loading)
hint = itemView.findViewById(R.id.footerview_hint)
}
fun initItemPadding() {
itemView.setPadding(0, 0, 0, 0)
}
fun initFooterViewHolder(
isNetworkError: Boolean,
isOver: Boolean,
@StringRes loadOverHint: Int
) {
if (isNetworkError) {
loading.visibility = View.GONE
hint.setText(R.string.loading_failed_retry)
} else if (isOver) {
loading.visibility = View.GONE
hint.setText(loadOverHint)
} else {
loading.visibility = View.VISIBLE
hint.setText(R.string.loading)
}
}
fun initFooterViewHolder(
viewModel: ListViewModel<*, *>,
isLoading: Boolean,
isNetworkError: Boolean,
isOver: Boolean
) {
initFooterViewHolder(
isLoading,
isNetworkError,
isOver,
R.string.load_over_hint
) { v: View? -> if (isNetworkError) viewModel.load(LoadType.RETRY) }
}
fun bindFooterDefaultEmpty(
viewModel: ListViewModel<*, *>,
isLoading: Boolean,
isNetworkError: Boolean,
isOver: Boolean
) {
if (isNetworkError) {
loading.visibility = View.GONE
hint.visibility = View.VISIBLE
hint.setText(R.string.loading_failed_retry)
} else if (isOver) {
loading.visibility = View.GONE
hint.visibility = View.VISIBLE
hint.setText(R.string.load_over_hint)
} else if (isLoading) {
loading.visibility = View.VISIBLE
hint.visibility = View.VISIBLE
hint.setText(R.string.loading)
} else {
loading.visibility = View.GONE
hint.visibility = View.GONE
}
itemView.setOnClickListener { v: View? -> if (isNetworkError) viewModel.load(LoadType.RETRY) }
}
@JvmOverloads
fun initFooterViewHolder(
isLoading: Boolean,
isNetworkError: Boolean,
isOver: Boolean,
@StringRes loadOverHint: Int = R.string.load_over_hint
) {
BaseActivity.updateStaticView(itemView, ArrayList())
if (isNetworkError) {
loading.visibility = View.GONE
hint.setText(R.string.loading_failed_retry)
} else if (isOver) {
loading.visibility = View.GONE
hint.setText(loadOverHint)
} else if (isLoading) {
loading.visibility = View.VISIBLE
hint.setText(R.string.loading)
} else {
loading.visibility = View.GONE
hint.setText(R.string.loading_more_hint)
}
}
fun initFooterViewHolder(
isLoading: Boolean,
mIsNetworkError: Boolean,
mIsOver: Boolean,
onClickListener: View.OnClickListener?
) {
initFooterViewHolder(
isLoading,
mIsNetworkError,
mIsOver,
R.string.load_over_hint,
onClickListener
)
}
fun initFooterViewHolder(
isLoading: Boolean,
mIsNetworkError: Boolean,
mIsOver: Boolean,
@StringRes loadOverHint: Int,
onClickListener: View.OnClickListener?
) {
if (mIsNetworkError) {
loading.visibility = View.GONE
hint.setText(R.string.loading_failed_retry)
itemView.isClickable = true
itemView.setOnClickListener(onClickListener)
} else if (mIsOver) {
loading.visibility = View.GONE
hint.setText(loadOverHint)
itemView.isClickable = true
itemView.setOnClickListener(onClickListener)
} else if (isLoading) {
loading.visibility = View.VISIBLE
hint.setText(R.string.loading)
itemView.isClickable = false
} else {
loading.visibility = View.GONE
hint.setText(R.string.loading_more_hint)
itemView.isClickable = false
}
}
}

View File

@ -0,0 +1,8 @@
package com.gh.gamecenter.forum.home.follow.viewholder
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.databinding.RecyclerFollowHomeEmptyBinding
class FollowHomeEmptyViewHolder(val binding: RecyclerFollowHomeEmptyBinding) : ViewHolder(binding.root) {
}

View File

@ -0,0 +1,85 @@
package com.gh.gamecenter.forum.home.follow.viewholder
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.databinding.RecyclerFollowHomeHeaderBinding
import com.gh.gamecenter.entity.FollowUserEntity
import com.gh.gamecenter.forum.home.follow.adapter.FollowedHeaderAdapter
class FollowHomeHeaderViewHolder(
val binding: RecyclerFollowHomeHeaderBinding,
val listener: OnEventListener
) : ViewHolder(binding.root) {
private var data: List<FollowUserEntity> = listOf()
private val followedHeaderAdapter by lazy {
FollowedHeaderAdapter(itemView.context, object : FollowedHeaderAdapter.OnChildEventListener {
override fun onViewItem(position: Int) {
listener.onItemClick(position, data)
}
override fun onViewAll() {
listener.onAllClick()
}
})
}
fun bind(isLogin: Boolean, followedList: List<FollowUserEntity>) {
fun setViewsVisible(tipsGone: Boolean, recyclerGone: Boolean, loginGone: Boolean) {
binding.tvNoDataTips.goneIf(tipsGone)
binding.rvFollowed.goneIf(recyclerGone)
binding.gLogin.goneIf(loginGone)
}
when {
!isLogin -> {
setViewsVisible(tipsGone = true, recyclerGone = true, loginGone = false)
binding.tvLogin.setOnClickListener {
listener.onLoginClick()
}
}
followedList.isEmpty() -> {
setViewsVisible(tipsGone = false, recyclerGone = true, loginGone = true)
}
else -> {
setViewsVisible(tipsGone = true, recyclerGone = false, loginGone = true)
if (binding.rvFollowed.adapter == null) {
binding.rvFollowed.layoutManager =
LinearLayoutManager(itemView.context, RecyclerView.HORIZONTAL, false)
binding.rvFollowed.adapter = followedHeaderAdapter
}
data = followedList
followedHeaderAdapter.submitList(followedList)
}
}
}
fun updateReadStatus(ids: List<String>) {
val oldData = followedHeaderAdapter.currentList
val newData = oldData.toMutableList()
oldData.forEachIndexed { index, followUserEntity ->
if (ids.contains(followUserEntity.id)) {
val newItem = followUserEntity.copy(_isShowTip = 0)
newData[index] = newItem
}
}
data = newData
followedHeaderAdapter.submitList(newData)
}
interface OnEventListener {
fun onItemClick(position: Int, data: List<FollowUserEntity>)
fun onAllClick()
fun onLoginClick()
}
}

View File

@ -0,0 +1,55 @@
package com.gh.gamecenter.forum.home.follow.viewholder
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.databinding.ItemHomeRecommendListCustomBinding
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.ui.CommonContentRecommendUi
/**
* 通用内容合集-金刚区
* 首页推荐入口
*/
class FollowHomeRecommendItemViewHolder(
val binding: ItemHomeRecommendListCustomBinding,
private val listener: OnChildEventListener
) : ViewHolder(binding.root) {
private lateinit var data: CustomPageData.CommonContentCollection
private val recommendUi by lazy {
CommonContentRecommendUi(binding, object : CommonContentRecommendUi.OnRecommendListener {
override fun navigateToLinkPage(link: LinkEntity, text: String, exposureEvent: ExposureEvent?) {
listener.navigateToLinkPage(bindingAdapterPosition, link, data, text, exposureEvent)
}
})
}
fun bindView(data: CustomPageData.CommonContentCollection) {
this.data = data
val recommends = data.recommends
recommendUi.bind(recommends, listOf())
itemView.setBackgroundColor(R.color.ui_surface.toColor(itemView.context))
}
companion object {
//图标最多列数
private const val MAX_SPAN_COUNT = 5
}
interface OnChildEventListener {
fun navigateToLinkPage(
position: Int,
link: LinkEntity,
data: CustomPageData.CommonContentCollection,
text: String,
exposureEvent: ExposureEvent?
)
}
}

View File

@ -0,0 +1,79 @@
package com.gh.gamecenter.forum.home.follow.viewholder
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.databinding.HomeSlideListCustomBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.home.custom.OnViewHolderAttachListener
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.ui.CommonContentHomeSLideListUi
/**
* 关注-通用内容合集-轮播Banner
*/
class FollowHomeSlideListViewHolder(
val binding: HomeSlideListCustomBinding,
lifecycleOwner: LifecycleOwner,
listener: OnChildEventListener,
) :
ViewHolder(binding.root), OnViewHolderAttachListener {
private lateinit var data: CustomPageData.CommonContentCollection
private val slideListUi by lazy {
CommonContentHomeSLideListUi(
binding,
lifecycleOwner,
object : CommonContentHomeSLideListUi.OnCommonHomeSlideListEventListener {
override fun navigateToLinkPage(link: LinkEntity, text: String, exposureEvent: ExposureEvent?) {
listener.navigateToLinkPage(bindingAdapterPosition, link, data, text, exposureEvent)
}
override fun navigateToGameDetailPage(position: Int, game: GameEntity, text: String) {
listener.navigateToGameDetailPage(position, game, text)
}
override fun updateImmersiveColor(color: Int) = Unit
override fun createExposureEvent(childPosition: Int, game: GameEntity?): ExposureEvent? = null
})
}
fun bindView(data: CustomPageData.CommonContentCollection) {
this.data = data
val slideList = data.slides.slide
slideListUi.bind(slideList, bindingAdapterPosition + 1)
itemView.setBackgroundColor(R.color.ui_surface.toColor(itemView.context))
}
override fun onViewAttach(parent: RecyclerView?) {
slideListUi.onViewAttach(parent)
}
override fun onViewDetach(parent: RecyclerView?) {
slideListUi.onViewDetach(parent)
}
interface OnChildEventListener {
fun navigateToLinkPage(
position: Int,
link: LinkEntity,
data: CustomPageData.CommonContentCollection,
text: String,
exposureEvent: ExposureEvent?
)
fun navigateToGameDetailPage(position: Int, game: GameEntity, text: String)
}
}

View File

@ -0,0 +1,74 @@
package com.gh.gamecenter.forum.home.follow.viewholder
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.databinding.HomeSlideWithCardsCustomBinding
import com.gh.gamecenter.entity.HomeSubSlide
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.home.custom.CommonContentCollectionUseCase
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.ui.CommonContentHomeSlideWithCardsUi
/**
* 关注-通用内容合集-轮播Banner
*/
class FollowHomeSlideWithCardsViewHolder(
val binding: HomeSlideWithCardsCustomBinding,
private val useCase: CommonContentCollectionUseCase,
lifecycleOwner: LifecycleOwner,
private val listener: OnChildEventListener
) : ViewHolder(binding.root) {
private lateinit var data: CustomPageData.CommonContentCollection
private val slideWithCardsUi by lazy {
CommonContentHomeSlideWithCardsUi(
useCase,
lifecycleOwner,
binding,
object : CommonContentHomeSlideWithCardsUi.HomeSLideWithCardsEventListener {
override fun updateImmersiveColor(color: Int) = Unit
override fun createEventWithSourceConcat(game: GameEntity, subSlideId: String) = Unit
override fun createExposureEvent(actualPosition: Int, game: GameEntity?): ExposureEvent? = null
override fun addGameExposureEvent(position: Int, game: GameEntity, subSlideId: String) = Unit
override fun navigateToGameDetailPage(childPosition: Int, gameEntity: GameEntity, text: String) {
listener.navigateToGameDetailPage(childPosition, gameEntity, text)
}
override fun navigateToLinkPageInSubSlide(game: GameEntity, homeSubSlide: HomeSubSlide, text: String) {
listener.navigateToLinkPage(bindingAdapterPosition, homeSubSlide.toLinkEntity(), data, text, null)
}
override fun navigateToLinkPage(link: LinkEntity, text: String, exposureEvent: ExposureEvent?) {
listener.navigateToLinkPage(bindingAdapterPosition, link, data, text, exposureEvent)
}
})
}
fun bindView(data: CustomPageData.CommonContentCollection) {
this.data = data
slideWithCardsUi.bind(data, bindingAdapterPosition + 1)
itemView.setBackgroundColor(R.color.ui_surface.toColor(itemView.context))
}
interface OnChildEventListener {
fun navigateToGameDetailPage(childPosition: Int, gameEntity: GameEntity, text: String)
fun navigateToLinkPage(
position: Int,
link: LinkEntity,
data: CustomPageData.CommonContentCollection,
text: String,
exposureEvent: ExposureEvent?
)
}
}

View File

@ -0,0 +1,6 @@
package com.gh.gamecenter.forum.home.follow.viewholder
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.databinding.RecyclerInvalidBinding
class FollowInvalidViewHolder(val binding: RecyclerInvalidBinding) : ViewHolder(binding.root)

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