Compare commits

..

22 Commits

Author SHA1 Message Date
853aeffe1e fix: 【光环助手】我的光环页面改版优化-07/08测试-客户端 https://jira.shanqu.cc/browse/GHZSCY-8202 2025-07-08 16:31:40 +08:00
e562ea0d35 fix: 【光环助手】我的光环页面改版优化-07/08测试-客户端 https://jira.shanqu.cc/browse/GHZSCY-8202 2025-07-08 13:53:37 +08:00
ff519a0da1 feat: 我的光环页面改版优化—客户端(ci) https://jira.shanqu.cc/browse/GHZSCY-7835 2025-07-04 15:18:40 +08:00
40147ac09d feat: 我的光环页面改版优化—客户端 https://jira.shanqu.cc/browse/GHZSCY-7835 2025-07-04 15:13:19 +08:00
c47883f176 用户数据 2025-07-04 11:34:37 +08:00
d6771cf5ea 我的游戏列表 2025-07-04 10:58:11 +08:00
ea742707fe 会员入口卡片 2025-07-03 11:42:42 +08:00
287f412402 新手引导 2025-07-03 11:38:13 +08:00
14bd1a5153 用户数据 2025-07-03 10:46:38 +08:00
00eaeac888 常用功能 2025-07-03 10:16:33 +08:00
d370c33d12 埋点 2025-07-02 14:23:58 +08:00
35c1a3487b fix: 修改获取游戏最近打开时间的逻辑 2025-07-02 10:36:23 +08:00
9b480b89c7 新手引导 2025-07-02 09:47:35 +08:00
ab34c7b56a 新手引导 2025-07-01 17:34:16 +08:00
4bb3208d59 玩过的游戏 2025-07-01 16:52:31 +08:00
e875ff9c03 已安装 2025-07-01 16:52:31 +08:00
9eac3d64e2 我的游戏列表 2025-07-01 16:52:31 +08:00
8f4a7dfd16 玩过的游戏 2025-07-01 16:52:31 +08:00
b611138496 游戏时长数据 2025-07-01 16:52:31 +08:00
40eebbc93c 我的光环 2025-07-01 16:52:31 +08:00
94b9f554b4 设置 2025-07-01 16:52:31 +08:00
2bbf2dce3c 浏览记录 2025-07-01 16:52:30 +08:00
191 changed files with 11528 additions and 730 deletions

View File

@ -72,6 +72,7 @@ android_build:
only:
- dev
- release
- feat/GHZSCY-7835
# 代码检查
sonarqube_analysis:
@ -157,4 +158,5 @@ oss-upload&send-email:
only:
- dev
- release
- feat/GHZSCY-7835

View File

@ -824,6 +824,14 @@
android:name="com.halo.assistant.accelerator.MyAssetsActivity"
android:screenOrientation="portrait" />
<activity
android:name=".personal.playtime.PlayTimeActivity"
android:screenOrientation="portrait" />
<activity
android:name=".playedgame.PlayedGameActivity"
android:screenOrientation="portrait" />
<!-- <activity-->
<!-- android:name="${applicationId}.douyinapi.DouYinEntryActivity"-->
<!-- android:launchMode="singleTask"-->

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -12,7 +12,6 @@ import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import androidx.fragment.app.FragmentTransaction
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.BaseDialogFragment
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.view.CustomLinkMovementMethod
@ -82,9 +81,9 @@ class NewPrivacyPolicyDialogFragment : BaseDialogFragment() {
override fun onClick(widget: View) {
val privacyPolicyUrl = requireContext().getString(com.gh.gamecenter.common.R.string.privacy_policy_url)
val childrenPrivacyPolicyUrl = requireContext().getString(R.string.children_policy_url)
val thirdPartySdkUrl = requireContext().getString(R.string.sdk_list_url)
val permissionListUrl = requireContext().getString(R.string.permission_and_usage_url)
val childrenPrivacyPolicyUrl = requireContext().getString(com.gh.gamecenter.common.R.string.children_policy_url)
val thirdPartySdkUrl = requireContext().getString(com.gh.gamecenter.common.R.string.sdk_list_url)
val permissionListUrl = requireContext().getString(com.gh.gamecenter.common.R.string.permission_and_usage_url)
val selectedUrl =
when (contentText.substring(hypertextPositionPair.first, hypertextPositionPair.second)) {

View File

@ -0,0 +1,5 @@
package com.gh.common.iinterface
interface IBatchManage {
fun onManageClick()
}

View File

@ -0,0 +1,16 @@
package com.gh.common.iinterface
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.toResString
interface IInstalledSortType {
fun getCurrentSortType(): InstalledSortType
fun changeSortType(sortType: InstalledSortType)
}
enum class InstalledSortType(val des: String) {
RECENTLY_PLAYED(R.string.order_default.toResString()),
LATEST_UPDATED(R.string.order_latest_updated.toResString()),
MOST_PLAYED(R.string.order_most_played.toResString()),
LEAST_PLAYED(R.string.order_least_played.toResString())
}

View File

@ -0,0 +1,46 @@
package com.gh.common.prioritychain
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.personal.MyHaloFragment
import java.lang.ref.WeakReference
class MyHaloGuideHandler(priority: Int) : PriorityChainHandler(priority) {
private var myHaloFragmentWeakRef: WeakReference<MyHaloFragment>? = null
fun doPreProcess(fragment: MyHaloFragment) {
myHaloFragmentWeakRef = WeakReference(fragment)
val shouldShowGuide = SPUtils.getBoolean(Constants.SP_SHOW_MY_HALO_GUIDE, true)
if (getStatus() == STATUS_PENDING) {
if (shouldShowGuide) {
updateStatus(STATUS_VALID)
process()
} else {
processNext()
}
} else {
if (shouldShowGuide) {
updateStatus(STATUS_VALID)
} else {
updateStatus(STATUS_INVALID)
}
}
}
override fun onProcess() : Boolean {
when (getStatus()) {
STATUS_VALID -> {
myHaloFragmentWeakRef?.get()?.scrollToTop()
myHaloFragmentWeakRef?.get()?.showGuide()
return true
}
STATUS_INVALID -> {
processNext()
}
}
return false
}
}

View File

@ -45,6 +45,10 @@ class PackageUtilsProviderImpl : IPackageUtilsProvider {
return PackageUtils.getInstalledTime(context, packageName)
}
override fun getLastUpdateTime(context: Context, packageName: String): Long {
return PackageUtils.getLastUpdateTime(context, packageName)
}
override fun getVersionNameByPackageName(packageName: String): String {
return PackageUtils.getVersionNameByPackageName(packageName) ?: ""
}

View File

@ -10,4 +10,7 @@ import com.gh.gamecenter.core.provider.IShellProvider
class ShellProviderImpl : IShellProvider {
override fun getSwitchInstallMethodIntent(context: Context, extraParcelable: Parcelable?): Intent =
ShellActivity.getIntent(context, ShellActivity.Type.SWITCH_INSTALL_METHOD, extraParcelable)
override fun getRealNameInfoIntent(context: Context, extraParcelable: Parcelable?): Intent =
ShellActivity.getIntent(context, ShellActivity.Type.REAL_NAME_INFO, extraParcelable)
}

View File

@ -3,9 +3,7 @@ package com.gh.common.provider
import android.content.Context
import android.os.Build
import androidx.annotation.RequiresApi
import com.therouter.router.Route
import com.gh.common.util.UsageStatsHelper
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.core.provider.IUsageStatsHelperProvider
@com.therouter.inject.ServiceProvider
@ -15,4 +13,5 @@ class UsageStatsHelperProviderImpl : IUsageStatsHelperProvider {
override fun skipToUsageStats(context: Context, requestCode: Int) {
UsageStatsHelper.skipToUsageStats(context, requestCode)
}
override fun getLastUsedTimeByPackageName(packageName: String): Long = UsageStatsHelper.getLastUsedTimeByPackageName(packageName)
}

View File

@ -4,7 +4,6 @@ import android.annotation.SuppressLint
import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.database.sqlite.SQLiteDiskIOException
import android.database.sqlite.SQLiteException
import android.graphics.Bitmap
import android.net.Uri
@ -14,6 +13,7 @@ import com.gh.common.util.LogUtils
import com.gh.common.util.PackageUtils
import com.gh.download.DownloadManager
import com.gh.gamecenter.common.callback.BiCallback
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.json.json
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.retrofit.EmptyResponse
@ -202,6 +202,10 @@ object SimulatorGameManager {
intent.setClassName(gameEntity.simulator?.apk?.packageName ?: "", destActivity)
try {
AppManager.getInstance().recentActiveActivity?.startActivity(intent)
// 记录光环内启动时间
val packageLaunchTimeSp = HaloApp.getInstance().getSharedPreferences(Constants.SP_PACKAGE_LAUNCH_TIME, Context.MODE_PRIVATE)
SPUtils.setLong(packageLaunchTimeSp, gameEntity.id, System.currentTimeMillis())
} catch (e: ActivityNotFoundException) {
ToastUtils.showToast("模拟器安装错误")
}

View File

@ -1073,6 +1073,15 @@ object DirectUtils {
jumpActivity(context, bundle)
}
@JvmStatic
fun directToDownloadManagerUpdate(context: Context, entrance: String? = null) {
val bundle = Bundle()
bundle.putString(KEY_ENTRANCE, entrance ?: ENTRANCE_BROWSER)
bundle.putString(KEY_TO, DownloadManagerActivity.TAG)
bundle.putInt(BaseFragment_TabLayout.PAGE_INDEX, INDEX_UPDATE)
jumpActivityCompat(context, bundle)
}
@JvmStatic
fun directToDownloadManagerAndStartUpdate(
context: Context,

View File

@ -3,12 +3,15 @@ package com.gh.common.util
import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import com.gh.download.DownloadManager
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.isVGameDownloadInDualDownloadMode
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.GameInstall
import com.gh.gamecenter.manager.PackagesManager
import com.gh.vspace.VHelper
import com.halo.assistant.HaloApp
object PackageLauncher {
@ -90,6 +93,10 @@ object PackageLauncher {
val intent = context.applicationContext.packageManager.getLaunchIntentForPackage(packageName)
if (intent != null) {
context.startActivity(intent)
// 记录光环内启动时间
val packageLaunchTimeSp = HaloApp.getInstance().getSharedPreferences(Constants.SP_PACKAGE_LAUNCH_TIME, Context.MODE_PRIVATE)
SPUtils.setLong(packageLaunchTimeSp, packageName, System.currentTimeMillis())
} else {
ToastUtils.toast("启动失败")
}

View File

@ -588,6 +588,21 @@ public class PackageUtils {
return 0;
}
/*
* 获取应用最后更新的时间
*/
public static long getLastUpdateTime(Context context, String packageName) {
try {
if (context == null) return 0;
PackageInfo packageInfo = context.getApplicationContext().getPackageManager()
.getPackageInfo(packageName, 0);
return packageInfo.lastUpdateTime;
} catch (NameNotFoundException e) {
// do nothing
}
return 0;
}
/*
* 返回光环助手的版本信息
*/

View File

@ -22,19 +22,19 @@ import com.gh.common.util.UsageStatsHelper.getDataByUsageEvents
import com.gh.common.util.UsageStatsHelper.getDataByUsageStats
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.debugOnly
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.retrofit.RetrofitManager
import com.halo.assistant.HaloApp
import com.lightgame.utils.Utils
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.ResponseBody
import org.json.JSONArray
import org.json.JSONException
import org.json.JSONObject
import java.util.*
import java.util.concurrent.ConcurrentHashMap
/**
* 参考资料: https://developer.android.com/reference/android/app/usage/UsageStatsManager
@ -52,9 +52,33 @@ object UsageStatsHelper {
const val USAGE_STATUS_REQUEST_CODE = 233
const val USAGE_STATUS_SP_KEY = "usage_status_sp_key"
private val usageStatsManager by lazy {
HaloApp.getInstance().application.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
}
private val mApi by lazy { RetrofitManager.getInstance().api }
private val mPreference by lazy { PreferenceManager.getDefaultSharedPreferences(HaloApp.getInstance().application) }
private val lastUsedTimeMap = ConcurrentHashMap<String, Long>()
fun getLastUsedTimeByPackageName(packageName: String): Long = lastUsedTimeMap[packageName] ?: 0L
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
fun updateLastUsedTimeByUsageStats() {
val usageStatsMap = usageStatsManager.queryAndAggregateUsageStats(
getDefaultBeginTime(),
System.currentTimeMillis()
) ?: return
for (entry in usageStatsMap) {
if (entry.value.lastTimeUsed != 0L) {
lastUsedTimeMap[entry.key] = entry.value.lastTimeUsed
debugOnly {
Utils.log("UsageStats getLastUsedTimeByUsageStats ==>" + entry.key + ":" + TimeUtils.getFormatTime(entry.value.lastTimeUsed, "yyyy-MM-dd HH:mm:ss"))
}
}
}
}
/**
* 一般用于获取时间跨度较大的数据
*
@ -63,10 +87,6 @@ object UsageStatsHelper {
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
private fun getDataByUsageStats(lastPostTime: Long): JSONArray? {
val usageStatsManager = HaloApp
.getInstance().application
.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
val usageStatsMap = usageStatsManager.queryAndAggregateUsageStats(
lastPostTime,
System.currentTimeMillis()
@ -95,13 +115,7 @@ object UsageStatsHelper {
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
private fun getDataByUsageEvents(startTime: Long): JSONArray? {
val mUsageStatsManager = HaloApp
.getInstance()
.application
.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
val usageEvents = mUsageStatsManager.queryEvents(startTime, System.currentTimeMillis())
val usageEvents = usageStatsManager.queryEvents(startTime, System.currentTimeMillis())
val allEvents = ArrayList<UsageEvents.Event>()
var currentEvent: UsageEvents.Event
@ -161,7 +175,7 @@ object UsageStatsHelper {
@SuppressLint("CheckResult")
@TargetApi(Build.VERSION_CODES.LOLLIPOP_MR1)
private fun postUsageStats(beginTime: Long) {
private fun postUsageStats(beginTime: Long, doOnEnd: (() -> Unit)? = null) {
debugOnly {
Utils.log("UsageStats->beginTime:$beginTime endTime:" + System.currentTimeMillis())
}
@ -176,6 +190,7 @@ object UsageStatsHelper {
debugOnly {
Utils.log("UsageStats: 没有可上传的数据")
}
doOnEnd?.invoke()
return
}
@ -186,6 +201,7 @@ object UsageStatsHelper {
debugOnly {
Utils.log("UsageStats: 数据上传成功")
}
doOnEnd?.invoke()
}
})
}
@ -207,7 +223,7 @@ object UsageStatsHelper {
@JvmStatic
@SuppressLint("CheckResult")
fun checkAndPostUsageStats() {
fun checkAndPostUsageStats(doOnSuccess: (() -> Unit)? = null) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) {
debugOnly {
Utils.log("UsageStats checkAndPostUsageStats Android 版本小于22")
@ -227,6 +243,8 @@ object UsageStatsHelper {
return
}
updateLastUsedTimeByUsageStats()
mApi.getUsageStatusUpdateTime(HaloApp.getInstance().gid)
.subscribe(object : BiResponse<ResponseBody>() {
override fun onSuccess(data: ResponseBody) {
@ -239,7 +257,7 @@ object UsageStatsHelper {
} else {
lastPostTime
}
postUsageStats(beginTime)
postUsageStats(beginTime, doOnSuccess)
} catch (e: JSONException) {
Utils.log("UsageStats: 获取上次上传时间失败,错误信息:${e.message}")
}

View File

@ -22,7 +22,7 @@ import com.gh.gamecenter.info.InfoWrapperFragment
import com.gh.gamecenter.libao.LibaoDetailFragment
import com.gh.gamecenter.libao.LibaoFragment
import com.gh.gamecenter.newsdetail.NewsDetailFragment
import com.gh.gamecenter.personal.HaloPersonalFragment
import com.gh.gamecenter.personal.MyHaloFragment
import com.gh.gamecenter.qa.article.detail.ArticleDetailFragment
import com.gh.gamecenter.qa.questions.newdetail.NewQuestionDetailFragment
import com.gh.gamecenter.qa.subject.AskSubjectFragment
@ -91,7 +91,7 @@ object ViewPagerFragmentHelper {
}
// 我的光环
TYPE_MY_HALO -> {
HaloPersonalFragment().setSuperiorChain(superiorChain).with(bundle)
MyHaloFragment().setSuperiorChain(superiorChain).with(bundle)
}
// 社区首页
TYPE_COMMUNITY_HOME -> {

View File

@ -135,7 +135,7 @@ class ConfigFilterView @JvmOverloads constructor(
private fun showSelectionPopupWindow(containerView: View, sizeTv: TextView, sizeText: String) {
sizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(sizeTv.context))
sizeTv.setDrawableEnd(R.drawable.ic_auxiliary_arrow_up_primary_8)
sizeTv.setDrawableEnd(R.drawable.ic_arrow_up)
val inflater = LayoutInflater.from(sizeTv.context)
val layout = inflater.inflate(R.layout.layout_filter_size, null)

View File

@ -0,0 +1,47 @@
package com.gh.common.view
import android.content.Context
import android.util.AttributeSet
import android.view.View
import androidx.core.widget.NestedScrollView
import kotlin.math.max
/**
* 父容器优先滚动的NestedScrollView
*/
class ParentPriorityNestedScrollView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
NestedScrollView(context, attrs) {
override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray, type: Int) {
if (dy > 0) {
super.onNestedPreScroll(target, dx, dy, consumed, type)
val remainingDy = dy - consumed[1]
if (remainingDy > 0) {
val remainingScrollY = getVerticalScrollRange() - height - scrollY
val consumedDy = minOf(remainingDy, remainingScrollY)
scrollBy(0, consumedDy)
consumed[1] += consumedDy
}
}
}
private fun getVerticalScrollRange(): Int {
val count = childCount
val parentSpace = height - paddingBottom - paddingTop
if (count == 0) {
return parentSpace
}
val child = getChildAt(0)
val lp = child.layoutParams as LayoutParams
var scrollRange = child.bottom + lp.bottomMargin
val scrollY = scrollY
val overscrollBottom = max(0.0, (scrollRange - parentSpace).toDouble()).toInt()
if (scrollY < 0) {
scrollRange -= scrollY
} else if (scrollY > overscrollBottom) {
scrollRange += scrollY - overscrollBottom
}
return scrollRange
}
}

View File

@ -14,15 +14,21 @@ import androidx.annotation.NonNull;
import com.gh.common.util.MessageShareUtils;
import com.gh.common.util.QRCodeUtils;
import com.gh.gamecenter.common.base.activity.ToolBarActivity;
import com.gh.gamecenter.common.constant.RouteConsts;
import com.gh.gamecenter.common.utils.ExtensionsKt;
import com.gh.gamecenter.common.utils.ShareUtils;
import com.gh.gamecenter.core.utils.MtaHelper;
import com.tencent.tauth.Tencent;
import com.therouter.router.Route;
/**
* Created by khy on 2017/2/6.
* 分享光环
*/
@Route(
path = RouteConsts.activity.shareGhActivity,
description = "分享光环"
)
public class ShareGhActivity extends ToolBarActivity {
ImageView mGhQrcode;

View File

@ -2,24 +2,24 @@ package com.gh.gamecenter.collection;
import android.app.Application;
import com.gh.gamecenter.core.AppExecutor;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import com.gh.common.history.HistoryDatabase;
import com.gh.common.history.HistoryHelper;
import com.gh.gamecenter.core.utils.UrlFilterUtils;
import com.gh.gamecenter.common.baselist.ListViewModel;
import com.gh.gamecenter.common.baselist.LoadType;
import com.gh.gamecenter.core.AppExecutor;
import com.gh.gamecenter.core.utils.TimeUtils;
import com.gh.gamecenter.core.utils.UrlFilterUtils;
import com.gh.gamecenter.feature.entity.NewsEntity;
import com.gh.gamecenter.feature.entity.ViewsEntity;
import com.gh.gamecenter.info.NewsViewsRepository;
import com.gh.gamecenter.login.user.UserManager;
import com.gh.gamecenter.retrofit.RetrofitManager;
import java.util.ArrayList;
import java.util.List;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import io.reactivex.Observable;
import io.reactivex.Single;
@ -83,10 +83,7 @@ public class ArticleViewModel extends ListViewModel<NewsEntity, NewsEntity> {
if (ArticleFragment.COLLECTION.equals(type)) {
return Single.fromObservable(RetrofitManager.getInstance().getApi().getCollectionArticle(UserManager.getInstance().getUserId(), page));
} else {
if (page > 5) {
return Single.create(emitter -> emitter.onSuccess(new ArrayList<>()));
}
return HistoryDatabase.Companion.getInstance().newsDao().getNewsWithOffset(20, (page - 1) * 20);
return HistoryDatabase.Companion.getInstance().newsDao().getNewsWithOffset(20, (page - 1) * 20, TimeUtils.getTimestampMonthsAgo(3));
}
}

View File

@ -2,17 +2,18 @@ package com.gh.gamecenter.collection
import android.annotation.SuppressLint
import android.app.Application
import com.gh.gamecenter.core.AppExecutor
import com.gh.common.history.HistoryDatabase
import com.gh.common.history.HistoryHelper
import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.ListViewModel
import com.gh.gamecenter.common.baselist.LoadType
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.feature.entity.ArticleEntity
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.feature.entity.ArticleEntity
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.retrofit.RetrofitManager
import com.lightgame.utils.Utils
import io.reactivex.Observable
@ -45,11 +46,7 @@ class CommunityArticleViewModel(application: Application) : ListViewModel<Articl
})
)
} else {
if (page > 5) {
Single.create { it.onSuccess(arrayListOf()) }
} else {
HistoryDatabase.instance.articleDao().getArticleWithOffset(20, (page - 1) * 20)
}
HistoryDatabase.instance.articleDao().getArticleWithOffset(20, (page - 1) * 20, TimeUtils.getTimestampMonthsAgo(3))
}
}

View File

@ -18,6 +18,7 @@ import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.common.utils.observableToMain
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.entity.GamesCollectionEntity
import com.gh.gamecenter.retrofit.RetrofitManager
@ -45,11 +46,8 @@ class GamesCollectionViewModel(
TYPE_COLLECT -> mApi.getFavoriteGameCollectionList(userId, page)
TYPE_HISTORY -> {
if (page > 5) {
Single.create { it.onSuccess(arrayListOf()) }
} else {
HistoryDatabase.instance.gamesCollectionDao().getGamesCollectionWithOffset(20, (page - 1) * 20)
}
HistoryDatabase.instance.gamesCollectionDao()
.getGamesCollectionWithOffset(20, (page - 1) * 20, TimeUtils.getTimestampMonthsAgo(3))
}
else -> {

View File

@ -1,13 +1,14 @@
package com.gh.gamecenter.collection
import android.app.Application
import com.gh.gamecenter.core.AppExecutor
import com.gh.common.constant.Config
import com.gh.common.history.HistoryDatabase
import com.gh.common.history.HistoryHelper
import com.gh.gamecenter.core.utils.UrlFilterUtils
import com.gh.gamecenter.common.baselist.ListViewModel
import com.gh.gamecenter.common.baselist.LoadType
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.core.utils.UrlFilterUtils
import com.gh.gamecenter.entity.MyVideoEntity
import com.gh.gamecenter.entity.PersonalHistoryEntity
import com.gh.gamecenter.feature.entity.User
@ -71,12 +72,7 @@ class VideoViewModel(application: Application) : ListViewModel<MyVideoEntity, My
override fun provideDataSingle(page: Int): Single<MutableList<MyVideoEntity>>? {
if (type == VideoFragment.VideoStyle.BROWSING_HISTORY.value) {
if (page > 5) {
return Single.create { it.onSuccess(arrayListOf()) }
}
if (type == VideoFragment.VideoStyle.BROWSING_HISTORY.value) {
return HistoryDatabase.instance.videoHistoryDao().getVideoWithOffset(20, (page - 1) * 20)
}
return HistoryDatabase.instance.videoHistoryDao().getVideoWithOffset(20, (page - 1) * 20, TimeUtils.getTimestampMonthsAgo(3))
}
return null
}

View File

@ -48,6 +48,7 @@ class UpdatableGameViewModel(
private var mPackageUpdateList = ArrayList<PackageUpdate>() // 包名更新列表,包括了我的版本和其它版本 (如果存在的话)
private var mValidGameUpdateListLiveData = MutableLiveData<List<GameUpdateEntity>>()
private var mUpdatableListLiveData = MutableLiveData<ArrayList<UpdatableDataItem>>()
private var mHasLandPageAddressDialog = false // 是否存在由第三方提供的游戏下载
@ -65,6 +66,7 @@ class UpdatableGameViewModel(
private val mDownloadManager by lazy { DownloadManager.getInstance() }
var updatableData: LiveData<ArrayList<UpdatableDataItem>> = mUpdatableListLiveData
var validGameUpdateData: LiveData<List<GameUpdateEntity>> = mValidGameUpdateListLiveData
/**
* 更新可更新数据列表
@ -319,6 +321,8 @@ class UpdatableGameViewModel(
mHasLandPageAddressDialog = landPageAddressUpdateList.isNotEmpty()
mValidGameUpdateListLiveData.postValue(validPackageUpdateList.map { it.matchedVersionUpdate })
// 构建装饰好的页面列表
// 正常的我的版本
if (validPackageUpdateList.isNotEmpty()) {

View File

@ -0,0 +1,46 @@
package com.gh.gamecenter.entity
import com.gh.gamecenter.common.entity.LinkEntity
import com.google.gson.annotations.SerializedName
data class MyHaloContentCard(
@SerializedName("vip_card")
val vipCard: VipCard? = null,
val function: Function? = null,
) {
data class VipCard(
@SerializedName("vip_card_style")
val style: String = "", // 会员入口卡片-前端展示卡片样式(a_long_card一个长卡片、two_short_cards两个短卡片),仅当type=vip时才有此字段
val data: List<VipCardData> = listOf()
) {
data class VipCardData(
@SerializedName("vip_type")
val vipType: String = "",// 会员类型(accelerator加速器会员、cloud_game云游戏会员)
@SerializedName("user_type")
val userType: String = "",
val des: String = "",// 权益说明
val button: String = "",// 按钮文案off不显示按钮、输入内容自定义文案
) {
companion object {
const val TYPE_ACCELERATOR = "accelerator"
}
}
}
data class Function(
@SerializedName("compliance_status")
val complianceStatus: Boolean = false, // 合规内容-显示状态【true/false】
val data: List<FunctionData> = listOf(),
) {
data class FunctionData(
@SerializedName("_id")
val id: String = "",
val name: String = "",
val icon: String = "",
val category: String = "",
val link: LinkEntity? = null, // 跳转链接类型(resource资源类型(以往的通用链接处理)、function功能类型(链接的type和text都是中文名称功能名))
@SerializedName("remind_switch")
var remindSwitch: Boolean = false // 红点提醒开关开启true, 关闭false
)
}
}

View File

@ -361,6 +361,7 @@ class GameDetailAcceleratorUiHelper(private val binding: DetailDownloadItemBindi
const val SOURCE_ENTRANCE_MY_ASSETS = "我的资产"
const val SOURCE_ENTRANCE_SEARCH = "搜索页"
const val SOURCE_ENTRANCE_RECENTLY_PLAYED = "最近在玩"
const val SOURCE_ENTRANCE_MY_HALO_INSTALLED = "我的光环-已安装"
const val BUTTON_NAME_ENTER_GAME = "进入游戏"
const val BUTTON_NAME_STOP_ACCELERATOR = "停止加速"

View File

@ -5,6 +5,7 @@ import android.app.Activity
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Color
import android.graphics.RectF
import android.graphics.Typeface
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.TransitionDrawable
@ -700,7 +701,7 @@ class GameDetailFragment : LazyFragment(), IScrollable {
val infoTagLocation = IntArray(2)
binding.infoTagContainer.getLocationInWindow(infoTagLocation)
val top = (infoTagLocation[1] - 12F.dip2px()).toFloat()
root.targetRect.set(8F.dip2px().toFloat(), top, (screenWidth - 8F.dip2px()).toFloat(), top + 53F.dip2px())
root.setTargetRect(RectF(8F.dip2px().toFloat(), top, (screenWidth - 8F.dip2px()).toFloat(), top + 53F.dip2px()))
guideIv.translationY = top - 56F.dip2px()
containerView.translationY = infoTagLocation[1].toFloat()
accelerateView.goneIf(viewModel.infoTagLiveData.value?.requestSpeedStatus != "on") {

View File

@ -8,20 +8,22 @@ import android.widget.LinearLayout
import android.widget.PopupWindow
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import com.gh.gamecenter.common.constant.ItemViewType
import com.gh.common.util.*
import com.gh.common.util.DownloadItemUtils
import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.viewholder.FooterViewHolder
import com.gh.gamecenter.adapter.viewholder.GameViewHolder
import com.gh.gamecenter.common.baselist.ListAdapter
import com.gh.gamecenter.common.constant.ItemViewType
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.DrawableView
import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.common.viewholder.FooterViewHolder
import com.gh.gamecenter.core.utils.StringUtils
import com.gh.gamecenter.databinding.PopupHistoryOptionBinding
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.feature.databinding.GameItemBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.game.GameItemViewHolder
import com.lightgame.download.DownloadEntity
class HistoryGameListAdapter(context: Context, private val mViewModel: HistoryGameListViewModel) :
ListAdapter<GameEntity>(context) {
@ -30,6 +32,23 @@ class HistoryGameListAdapter(context: Context, private val mViewModel: HistoryGa
private var mPopWindow: PopupWindow? = null
private var mPopupBinding: PopupHistoryOptionBinding? = null
var selectItems = arrayListOf<String>()
val positionAndPackageMap = HashMap<String, Int>()
override fun setListData(updateData: MutableList<GameEntity>?) {
positionAndPackageMap.clear()
// 记录游戏位置
if (updateData != null) {
for (i in 0 until updateData.size) {
val gameEntity = updateData[i]
var packages = gameEntity.id
for (apkEntity in gameEntity.getApk()) {
packages += apkEntity.packageName
}
positionAndPackageMap[packages + i] = i
}
}
super.setListData(updateData)
}
override fun areItemsTheSame(oldItem: GameEntity?, newItem: GameEntity?): Boolean {
return oldItem?.id == newItem?.id
@ -113,7 +132,7 @@ class HistoryGameListAdapter(context: Context, private val mViewModel: HistoryGa
"(浏览记录:游戏)",
StringUtils.buildString("浏览记录", ":", gameEntity.name)
)
DownloadItemUtils.updateItemWithViewOnlyStyle(GameViewHolder(holder.binding))
DownloadItemUtils.updateItem(mContext, gameEntity, GameViewHolder(holder.binding), false)
holder.itemView.setOnLongClickListener {
consume {
@ -184,4 +203,28 @@ class HistoryGameListAdapter(context: Context, private val mViewModel: HistoryGa
checkAllCb.isChecked = selectItems.size == mEntityList.size
}
}
fun notifyItemByDownload(download: DownloadEntity) {
for (key in positionAndPackageMap.keys) {
if (key.contains(download.packageName) && key.contains(download.gameId)) {
val position = positionAndPackageMap[key]
if (position != null && mEntityList != null && position < mEntityList.size) {
mEntityList[position].getEntryMap()[download.platform] = download
notifyItemChanged(position)
}
}
}
}
fun notifyItemAndRemoveDownload(status: EBDownloadStatus) {
for (key in positionAndPackageMap.keys) {
if (key.contains(status.packageName) && key.contains(status.gameId)) {
val position = positionAndPackageMap[key]
if (position != null && mEntityList != null && position < mEntityList.size) {
mEntityList[position].getEntryMap().remove(status.platform)
notifyItemChanged(position)
}
}
}
}
}

View File

@ -4,20 +4,44 @@ import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.ethanhua.skeleton.Skeleton
import com.gh.common.util.DialogUtils
import com.gh.common.xapk.XapkInstaller
import com.gh.common.xapk.XapkUnzipStatus
import com.gh.download.DownloadManager
import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.ListFragment
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.toDrawable
import com.gh.gamecenter.common.utils.viewModelProvider
import com.gh.gamecenter.common.view.CustomDividerItemDecoration
import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.ListFragment
import com.gh.gamecenter.databinding.FragmentListBaseSkeletonBinding
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.feature.entity.GameEntity
import com.lightgame.download.DataWatcher
import com.lightgame.download.DownloadEntity
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
open class HistoryGameListFragment : ListFragment<GameEntity, HistoryGameListViewModel>(), IBatchDelete {
private var mAdapter: HistoryGameListAdapter? = null
private val mBinding by lazy { FragmentListBaseSkeletonBinding.inflate(layoutInflater) }
private val dataWatcher = object : DataWatcher() {
override fun onDataChanged(downloadEntity: DownloadEntity) {
mAdapter?.notifyItemByDownload(downloadEntity)
if (downloadEntity.meta[XapkInstaller.XAPK_UNZIP_STATUS] == XapkUnzipStatus.FAILURE.name) {
showUnzipFailureDialog(downloadEntity)
}
}
override fun onDataInit(downloadEntity: DownloadEntity) {
mAdapter?.notifyItemByDownload(downloadEntity)
}
}
override fun getLayoutId() = R.layout.fragment_list_base_skeleton
override fun getInflatedLayout() = mBinding.root
@ -55,4 +79,43 @@ open class HistoryGameListFragment : ListFragment<GameEntity, HistoryGameListVie
}
return mItemDecoration
}
override fun onResume() {
super.onResume()
DownloadManager.getInstance().addObserver(dataWatcher)
}
override fun onPause() {
super.onPause()
DownloadManager.getInstance().removeObserver(dataWatcher)
}
private fun showUnzipFailureDialog(downloadEntity: DownloadEntity) {
val data = mAdapter?.positionAndPackageMap ?: return
for (gameAndPosition in data) {
if (gameAndPosition.key.contains(downloadEntity.packageName)) {
val targetView = mLayoutManager.findViewByPosition(gameAndPosition.value)
if (targetView != null) {
DialogUtils.showUnzipFailureDialog(requireContext(), downloadEntity)
return
}
}
}
}
// 下载被删除事件
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(status: EBDownloadStatus) {
if ("delete" == status.status) {
mAdapter?.notifyItemAndRemoveDownload(status)
}
}
// 安装/卸载 事件
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(busFour: EBPackage) {
if (!busFour.fromInit && busFour.isInstalledOrUninstalled()) {
mAdapter?.notifyDataSetChanged()
}
}
}

View File

@ -1,32 +1,86 @@
package com.gh.gamecenter.history
import android.annotation.SuppressLint
import android.app.Application
import com.gh.gamecenter.core.AppExecutor
import com.gh.common.history.HistoryDatabase
import com.gh.common.history.HistoryHelper
import com.gh.gamecenter.common.baselist.ListViewModel
import com.gh.gamecenter.common.baselist.LoadType
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.entity.HistoryGameEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.utils.ApkActiveUtils
import com.gh.gamecenter.retrofit.RetrofitManager
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.SingleEmitter
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import retrofit2.HttpException
class HistoryGameListViewModel(application: Application) : ListViewModel<GameEntity, GameEntity>(application) {
private val api = RetrofitManager.getInstance().api
override fun provideDataObservable(page: Int): Observable<List<GameEntity>>? {
return null
}
@SuppressLint("CheckResult")
override fun provideDataSingle(page: Int): Single<List<GameEntity>> {
return if (page > 5) {
Single.create { it.onSuccess(arrayListOf()) }
} else {
HistoryDatabase.instance.gameDao().getGamesWithOffset(20, (page - 1) * 20).map {
val gameEntityList = arrayListOf<GameEntity>()
for (history in it) {
gameEntityList.add(history.convertHistoryGameEntityToGameEntity())
}
gameEntityList
return Single.create { emitter ->
HistoryDatabase.instance.gameDao().getGamesWithOffset(20, (page - 1) * 20, TimeUtils.getTimestampMonthsAgo(3))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
if (it.isEmpty()) {
emitter.onSuccess(emptyList())
} else {
val ids = it.map { historyGameEntity -> historyGameEntity.id }
val result = ArrayList<GameEntity>()
val sequences = ArrayList<Observable<GameEntity>>()
for (id in ids) {
sequences.add(api.getGameDigest(id))
}
Observable.mergeDelayError(sequences)
.map(ApkActiveUtils.filterMapper)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Response<GameEntity?>() {
override fun onComplete() {
processingData(emitter, it, result)
}
override fun onFailure(e: HttpException?) {
processingData(emitter, it, result)
}
override fun onNext(response: GameEntity) {
ApkActiveUtils.filterHideApk(response)
result.add(response)
}
})
}
}, {
emitter.onError(it)
})
}
}
private fun processingData(
emitter: SingleEmitter<List<GameEntity>>,
historyGameList: List<HistoryGameEntity>,
gameList: List<GameEntity>
) {
if (gameList.isEmpty()) {
val gameEntityList = arrayListOf<GameEntity>()
for (history in historyGameList) {
gameEntityList.add(history.convertHistoryGameEntityToGameEntity())
}
emitter.onSuccess(gameEntityList)
} else {
emitter.onSuccess(gameList)
}
}

View File

@ -5,12 +5,15 @@ import android.view.MenuItem
import android.view.View
import android.widget.TextView
import androidx.fragment.app.Fragment
import com.gh.gamecenter.R
import com.gh.gamecenter.collection.ArticleFragment
import com.gh.gamecenter.collection.CommunityArticleFragment
import com.gh.gamecenter.collection.GamesCollectionFragment
import com.gh.gamecenter.collection.VideoFragment
import com.gh.gamecenter.common.base.fragment.BaseFragment_TabLayout
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.common.utils.viewModelProvider
import com.gh.gamecenter.R
import com.gh.gamecenter.collection.*
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.login.user.UserManager
class HistoryWrapperFragment : BaseFragment_TabLayout() {
@ -26,7 +29,6 @@ class HistoryWrapperFragment : BaseFragment_TabLayout() {
tabTitleList.add(getString(R.string.main_game))
tabTitleList.add(getString(R.string.game_collection))
tabTitleList.add(getString(R.string.video))
tabTitleList.add(getString(R.string.answer))
tabTitleList.add(getString(R.string.collection_article))
tabTitleList.add(getString(R.string.collection_info))
}
@ -79,12 +81,6 @@ class HistoryWrapperFragment : BaseFragment_TabLayout() {
fragments.add(VideoFragment().with((arguments?.clone() as? Bundle)?.apply {
putString("videoStyle", VideoFragment.VideoStyle.BROWSING_HISTORY.value)
}))
fragments.add(AnswerFragment().with((arguments?.clone() as? Bundle)?.apply {
putString(
EntranceConsts.KEY_TYPE,
AnswerFragment.HISTORY
)
}))
fragments.add(CommunityArticleFragment().with((arguments?.clone() as? Bundle)?.apply {
putString(
EntranceConsts.KEY_TYPE,

View File

@ -3,23 +3,24 @@ package com.gh.gamecenter.message
import android.annotation.SuppressLint
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import com.gh.gamecenter.common.constant.Constants
import com.gh.common.util.CheckLoginUtils
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.core.utils.GsonUtils
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.common.utils.createRequestBody
import com.gh.gamecenter.db.GameTrendsDao
import com.gh.gamecenter.db.info.GameTrendsInfo
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.entity.*
import com.gh.gamecenter.feature.entity.MessageUnreadEntity
import com.gh.gamecenter.common.utils.createRequestBody
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.core.utils.GsonUtils
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.db.GameTrendsDao
import com.gh.gamecenter.db.info.GameTrendsInfo
import com.gh.gamecenter.entity.AddonsUnreadEntity
import com.gh.gamecenter.feature.entity.ConcernEntity
import com.gh.gamecenter.feature.entity.MessageDigestEntity
import com.gh.gamecenter.feature.entity.MessageUnreadCount
import com.gh.gamecenter.feature.entity.MessageUnreadEntity
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.retrofit.service.ApiService
import com.google.gson.reflect.TypeToken
@ -69,7 +70,7 @@ object MessageUnreadRepository {
@SuppressLint("CheckResult")
fun loadMessageUnreadTotal(isRecordData: Boolean = false) {
Observable.zip(
getMessageUnread(), getDiscoveryData(), getNewAddonsData(), getAddonsUnreadCount()
getMessageUnread(), getDiscoveryData(), getFunctionsUnreadCount(), getAddonsUnreadCount()
) { t1, t2, t3, t4 ->
zixunConcern.postValue(t2 > 0)
if (isRecordData) {
@ -163,41 +164,19 @@ object MessageUnreadRepository {
}
@SuppressLint("CheckResult")
private fun getNewAddonsData(): Observable<Int> {
var count = 0
return Observable.create {
mApi.newHaloAddons
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BiResponse<ArrayList<HaloAddonEntity>>() {
override fun onSuccess(data: ArrayList<HaloAddonEntity>) {
val funcHaveReadRecord: HashSet<String> =
SPUtils.getStringSet(Constants.SP_ADDONS_FUNCS_HAVE_READ) as HashSet<String>
val recommendHaveReadRecord: HashSet<String> =
SPUtils.getStringSet(Constants.SP_ADDONS_RECOMMEND_HAVE_READ) as HashSet<String>
data.forEach { group ->
if (group.groupType == "more_features") {
group.addons.forEach { addonLinkEntity ->
if (addonLinkEntity.remind && !funcHaveReadRecord.contains(addonLinkEntity.id)) {
count++
}
}
}
if (group.groupType == "recommend") {
group.addons.forEach { addonLinkEntity ->
if (addonLinkEntity.remind && !recommendHaveReadRecord.contains(addonLinkEntity.id)) {
count++
}
}
}
}
it.onNext(count)
it.onComplete()
}
})
}
private fun getFunctionsUnreadCount(): Observable<Int> {
return mNewApi.myHaloContentCard
.compose(singleToMain())
.toObservable()
.map { myHaloContentCard ->
val haveReadSet = SPUtils.getStringSet(Constants.SP_ADDONS_FUNCS_HAVE_READ)
myHaloContentCard.function?.data.orEmpty()
.count { it.remindSwitch && !haveReadSet.contains(it.id) }
}
.onErrorReturnItem(0)
}
@SuppressLint("CheckResult")
private fun getAddonsUnreadCount(): Observable<Int> {
return Observable.create {
if (CheckLoginUtils.isLogin()) {

View File

@ -1,41 +1,24 @@
package com.gh.gamecenter.mygame
import android.annotation.SuppressLint
import android.app.Application
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.therouter.TheRouter
import com.gh.common.repository.ReservationRepository
import com.gh.common.util.ReservationHelper.getReserveRequestBody
import com.gh.gamecenter.core.runOnUiThread
import com.gh.download.DownloadManager
import com.gh.gamecenter.common.baselist.ListViewModel
import com.gh.gamecenter.common.baselist.LoadType
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.core.runOnUiThread
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.common.utils.toRequestBody
import com.gh.gamecenter.core.provider.IPushProvider
import com.gh.gamecenter.livedata.Event
import com.gh.gamecenter.personal.reservation.ManageReservationUseCase
import com.gh.gamecenter.retrofit.RetrofitManager
import com.halo.assistant.HaloApp
import com.halo.assistant.fragment.reserve.ReserveReminderRepository
import com.lightgame.utils.Utils
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import okhttp3.ResponseBody
class MyReservationViewModel(application: Application) : ListViewModel<GameEntity, GameEntity>(application) {
var positionAndPackageMap = HashMap<String, Int>() // key: packageName + position, value: position
private val repository = ReserveReminderRepository.newInstance()
private val compositeDisposable = CompositeDisposable()
private val manageReservationUseCase = ManageReservationUseCase<GameEntity>()
override fun provideDataObservable(page: Int): Observable<List<GameEntity>>? {
return null
@ -68,126 +51,56 @@ class MyReservationViewModel(application: Application) : ListViewModel<GameEntit
}
fun cancelReservation(game: GameEntity) {
deleteOrCancelReservation(game, false)
}
manageReservationUseCase.deleteOrCancelReservation(game, false) {
// if (deleteReservation) {
//// MtaHelper.onEvent("预约游戏", "取消预约", game.name)
// } else {
//// MtaHelper.onEvent("预约游戏", "删除预约", game.name)
// }
@SuppressLint("CheckResult")
private fun deleteOrCancelReservation(game: GameEntity, deleteReservation: Boolean) {
val single = if (deleteReservation) {
RetrofitManager.getInstance().newApi
.deleteGameReservation(game.id, getReserveRequestBody(game, HaloApp.getInstance().application))
.subscribeOn(Schedulers.io())
} else {
RetrofitManager.getInstance().newApi
.cancelGameReservation(game.id, getReserveRequestBody(game, HaloApp.getInstance().application))
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
val list = mListLiveData.value
// 当数据量为1的时候不删了直接刷新
if (list?.size == 1) {
runOnUiThread { load(LoadType.REFRESH) }
} else {
list?.remove(list.find { it.id == game.id })
mListLiveData.postValue(list)
}
}
single.subscribe(object : BiResponse<ResponseBody>() {
override fun onSuccess(data: ResponseBody) {
ReservationRepository.refreshReservations()
if (deleteReservation) {
// MtaHelper.onEvent("预约游戏", "取消预约", game.name)
} else {
// MtaHelper.onEvent("预约游戏", "删除预约", game.name)
}
val list = mListLiveData.value
// 当数据量为1的时候不删了直接刷新
if (list?.size == 1) {
runOnUiThread { load(LoadType.REFRESH) }
} else {
list?.remove(list.find { it.id == game.id })
mListLiveData.postValue(list)
}
}
override fun onFailure(exception: Exception) {
Utils.toast(getApplication(), exception.message)
exception.printStackTrace()
}
})
}
private val _enableAutoDownloadSuccessfully = MutableLiveData<Event<Boolean>>()
val enableAutoDownloadSuccessfully: LiveData<Event<Boolean>> = _enableAutoDownloadSuccessfully
val enableAutoDownloadSuccessfully: LiveData<Event<Boolean>> = manageReservationUseCase.enableAutoDownloadSuccessfully
fun enableAutoDownload(enable: Boolean, games: Set<GameEntity>) {
if (enable) {
games.forEach {
SensorsBridge.trackOpenAppointmentAutomaticDownload(it.id, it.name ?: "", it.categoryChinese)
}
} else {
games.forEach {
SensorsBridge.trackCancelAppointmentAutomaticDownload(it.id, it.name ?: "", it.categoryChinese)
}
}
val params = createParamsInBatch(games).apply {
put("wifi_auto_download", enable)
}
repository.enableAutoDownloadInBatches(params.toRequestBody())
.map {
val oldList = mResultLiveData.value ?: emptyList()
val newList = oldList.map { item ->
if (games.any { it.id == item.id }) {
item.copy(_wifiAutoDownload = enable)
} else {
item
}
manageReservationUseCase.enableAutoDownload(enable, games, {
val oldList = mResultLiveData.value ?: emptyList()
val newList = oldList.map { item ->
if (games.any { it.id == item.id }) {
item.copy(_wifiAutoDownload = enable)
} else {
item
}
newList
}
.compose(singleToMain())
.subscribe(object : BiResponse<List<GameEntity>>() {
override fun onSuccess(data: List<GameEntity>) {
mResultLiveData.value = data
_enableAutoDownloadSuccessfully.value = Event(enable)
}
}).let(compositeDisposable::add)
newList
}) { data ->
mResultLiveData.value = data
}
}
fun cancelReserveInBatch(games: Set<GameEntity>) {
val params = createParamsInBatch(games).apply {
put("appointment", false)
}
repository.cancelReserveInBatch(params.toRequestBody())
.subscribeOn(Schedulers.io())
.subscribe(object : BiResponse<ResponseBody>() {
override fun onSuccess(data: ResponseBody) {
ReservationRepository.refreshReservations()
val oldList = mResultLiveData.value ?: emptyList()
val newList = oldList.toMutableList()
manageReservationUseCase.cancelReserveInBatch(games) {
val oldList = mResultLiveData.value ?: emptyList()
val newList = oldList.toMutableList()
newList.removeAll {
games.any { game -> game.id == it.id }
}
if (newList.isEmpty()) {
// 刷新页面
runOnUiThread { load(LoadType.REFRESH) }
} else {
mResultLiveData.postValue(newList)
}
}
}).let(compositeDisposable::add)
}
private fun createParamsInBatch(games: Set<GameEntity>): HashMap<String, Any> {
val jPushId =
(TheRouter.get(IPushProvider::class.java)
?.getRegistrationId(HaloApp.getInstance().application)) ?: ""
val idAndMirrorType = games.map {
var mirrorPosition = it.getMirrorPosition()
if (mirrorPosition == -1) {
mirrorPosition = 0
newList.removeAll {
games.any { game -> game.id == it.id }
}
if (newList.isEmpty()) {
// 刷新页面
runOnUiThread { load(LoadType.REFRESH) }
} else {
mResultLiveData.postValue(newList)
}
hashMapOf("_id" to it.id, "mirror_type" to mirrorPosition)
}
return hashMapOf("jpush_id" to jPushId, "games" to idAndMirrorType)
}
private val _changePageStateAction = MutableLiveData<Event<MyReservationFragment.ReservePageState>>()
@ -198,7 +111,7 @@ class MyReservationViewModel(application: Application) : ListViewModel<GameEntit
override fun onCleared() {
super.onCleared()
compositeDisposable.clear()
manageReservationUseCase.onCleared()
}
}

View File

@ -26,6 +26,10 @@ class DeliveryInfoActivity : ToolBarActivity() {
updateStatusBarColor(com.gh.gamecenter.common.R.color.ui_surface, com.gh.gamecenter.common.R.color.ui_surface)
}
override fun provideNormalIntent(): Intent {
return getTargetIntent(this, DeliveryInfoActivity::class.java, DeliveryInfoFragment::class.java)
}
companion object {
fun getIntent(context: Context?): Intent? {
return getTargetIntent(context, DeliveryInfoActivity::class.java, DeliveryInfoFragment::class.java)

View File

@ -4,7 +4,10 @@ import android.annotation.SuppressLint
import android.content.Intent
import android.database.sqlite.SQLiteException
import android.graphics.Typeface
import android.os.*
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.text.TextUtils
import android.view.MotionEvent
import android.view.View
@ -14,6 +17,7 @@ import androidx.appcompat.content.res.AppCompatResources
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.content.ContextCompat
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.PagerSnapHelper
@ -210,7 +214,7 @@ class HaloPersonalFragment : BaseLazyFragment() {
linkText = ""
)
NewLogUtils.logMessageInformBellClick(
mStubBinding.loginMessageHint.visibility == View.VISIBLE,
mStubBinding.loginMessageHint.isVisible,
"我的"
)
SensorsBridge.trackMessageCenterClick()
@ -725,7 +729,7 @@ class HaloPersonalFragment : BaseLazyFragment() {
mStubBinding.personalUserName.text = getString(R.string.login_immediately)
mStubBinding.userIdTv.text = getString(R.string.login_immediately_hint)
if (mStubBinding.loginMessageHint.visibility == View.VISIBLE) {
if (mStubBinding.loginMessageHint.isVisible) {
mStubBinding.loginMessageHint.visibility = View.GONE
EventBus.getDefault().post(EBReuse(MESSAGE_READ_OVER))
}
@ -735,7 +739,6 @@ class HaloPersonalFragment : BaseLazyFragment() {
private fun inflateRealView() {
mBinding.stub.inflateOrShow()
mStubBinding.statusBar.goneIf(Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
mStubBinding.darkModeIv.goneIf(!(Config.getNightModeSetting()?.icon ?: false))
mStubBinding.darkModeIv.setImageResource(if (mIsDarkModeOn) R.drawable.ic_personal_light_mode else R.drawable.ic_personal_dark_mode)
mStubBinding.loginMessageHint.typeface =
@ -755,13 +758,11 @@ class HaloPersonalFragment : BaseLazyFragment() {
mStubBinding.historyTv.setOnClickListener(this)
mStubBinding.myCollectionTv.setOnClickListener(this)
val statusBarHeight =
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) 0 else DisplayUtils.getStatusBarHeight(resources)
mStubBinding.motionLayout.minimumHeight = statusBarHeight + 48F.dip2px()
mStubBinding.motionLayout.minimumHeight = DisplayUtils.getStatusBarHeight(resources) + 48F.dip2px()
mStubBinding.appbar.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { _: AppBarLayout?, verticalOffset: Int ->
mStubBinding.appbar.addOnOffsetChangedListener { _: AppBarLayout?, verticalOffset: Int ->
mStubBinding.listRefresh.isEnabled = abs(verticalOffset) <= 2
})
}
mStubBinding.listRefresh.setColorSchemeColors(
ContextCompat.getColor(
@ -856,13 +857,13 @@ class HaloPersonalFragment : BaseLazyFragment() {
root.goneIf(EnvHelper.isGATApp)
}
mStubBinding.userProtocolItem.run {
titleTv.text = getString(R.string.setting_user_protocol)
titleTv.text = getString(com.gh.gamecenter.common.R.string.setting_user_protocol)
iconIv.setImageResource(R.drawable.ic_personal_user_protocol)
root.setOnClickListener {
NewFlatLogUtils.logHaloSelfClick("其他功能", getString(R.string.setting_user_protocol))
NewFlatLogUtils.logHaloSelfClick("其他功能", getString(com.gh.gamecenter.common.R.string.setting_user_protocol))
SensorsBridge.trackHaloSelfClick(
profile = "其他功能",
text = getString(R.string.setting_user_protocol),
text = getString(com.gh.gamecenter.common.R.string.setting_user_protocol),
linkType = "",
linkId = "",
linkText = ""
@ -877,15 +878,15 @@ class HaloPersonalFragment : BaseLazyFragment() {
}
}
mStubBinding.privacyPolicyItem.run {
titleTv.text = getString(R.string.setting_privacy_policy)
titleTv.text = getString(com.gh.gamecenter.common.R.string.setting_privacy_policy)
iconIv.setImageResource(R.drawable.ic_personal_privacy_policy)
//判断隐私政策是否有更新
mStubBinding.privacyPolicyItem.redDot.visibility = if (checkPrivacyIsSame()) View.GONE else View.VISIBLE
root.setOnClickListener {
NewFlatLogUtils.logHaloSelfClick("其他功能", getString(R.string.setting_privacy_policy))
NewFlatLogUtils.logHaloSelfClick("其他功能", getString(com.gh.gamecenter.common.R.string.setting_privacy_policy))
SensorsBridge.trackHaloSelfClick(
profile = "其他功能",
text = getString(R.string.setting_privacy_policy),
text = getString(com.gh.gamecenter.common.R.string.setting_privacy_policy),
linkType = "",
linkId = "",
linkText = ""
@ -905,13 +906,13 @@ class HaloPersonalFragment : BaseLazyFragment() {
}
}
mStubBinding.infoListItem.run {
titleTv.text = getString(R.string.setting_info_list)
titleTv.text = getString(com.gh.gamecenter.common.R.string.setting_info_list)
iconIv.setImageResource(R.drawable.ic_personal_info_list)
root.setOnClickListener {
NewFlatLogUtils.logHaloSelfClick("其他功能", getString(R.string.setting_info_list))
NewFlatLogUtils.logHaloSelfClick("其他功能", getString(com.gh.gamecenter.common.R.string.setting_info_list))
SensorsBridge.trackHaloSelfClick(
profile = "其他功能",
text = getString(R.string.setting_info_list),
text = getString(com.gh.gamecenter.common.R.string.setting_info_list),
linkType = "",
linkId = "",
linkText = ""
@ -919,20 +920,20 @@ class HaloPersonalFragment : BaseLazyFragment() {
startActivity(
WebActivity.getWebIntent(
requireContext(),
getString(R.string.setting_info_list),
requireContext().getString(R.string.info_list_url)
getString(com.gh.gamecenter.common.R.string.setting_info_list),
requireContext().getString(com.gh.gamecenter.common.R.string.info_list_url)
)
)
}
}
mStubBinding.sdkListItem.run {
titleTv.text = getString(R.string.setting_sdk_list)
titleTv.text = getString(com.gh.gamecenter.common.R.string.setting_sdk_list)
iconIv.setImageResource(R.drawable.ic_personal_sdk)
root.setOnClickListener {
NewFlatLogUtils.logHaloSelfClick("其他功能", getString(R.string.setting_sdk_list))
NewFlatLogUtils.logHaloSelfClick("其他功能", getString(com.gh.gamecenter.common.R.string.setting_sdk_list))
SensorsBridge.trackHaloSelfClick(
profile = "其他功能",
text = getString(R.string.setting_sdk_list),
text = getString(com.gh.gamecenter.common.R.string.setting_sdk_list),
linkType = "",
linkId = "",
linkText = ""
@ -940,20 +941,20 @@ class HaloPersonalFragment : BaseLazyFragment() {
startActivity(
WebActivity.getWebIntent(
requireContext(),
getString(R.string.setting_sdk_list),
requireContext().getString(R.string.sdk_list_url)
getString(com.gh.gamecenter.common.R.string.setting_sdk_list),
requireContext().getString(com.gh.gamecenter.common.R.string.sdk_list_url)
)
)
}
}
mStubBinding.permissionAndUsageItem.run {
titleTv.text = getString(R.string.setting_permission_and_usage)
titleTv.text = getString(com.gh.gamecenter.common.R.string.setting_permission_and_usage)
iconIv.setImageResource(R.drawable.ic_personal_permission)
root.setOnClickListener {
NewFlatLogUtils.logHaloSelfClick("其他功能", getString(R.string.setting_permission_and_usage))
NewFlatLogUtils.logHaloSelfClick("其他功能", getString(com.gh.gamecenter.common.R.string.setting_permission_and_usage))
SensorsBridge.trackHaloSelfClick(
profile = "其他功能",
text = getString(R.string.setting_permission_and_usage),
text = getString(com.gh.gamecenter.common.R.string.setting_permission_and_usage),
linkType = "",
linkId = "",
linkText = ""
@ -961,20 +962,20 @@ class HaloPersonalFragment : BaseLazyFragment() {
startActivity(
WebActivity.getWebIntent(
requireContext(),
getString(R.string.setting_permission_and_usage),
requireContext().getString(R.string.permission_and_usage_url)
getString(com.gh.gamecenter.common.R.string.setting_permission_and_usage),
requireContext().getString(com.gh.gamecenter.common.R.string.permission_and_usage_url)
)
)
}
}
mStubBinding.childrenPolicyItem.run {
titleTv.text = getString(R.string.setting_children_policy)
titleTv.text = getString(com.gh.gamecenter.common.R.string.setting_children_policy)
iconIv.setImageResource(R.drawable.ic_personal_children_policy)
root.setOnClickListener {
NewFlatLogUtils.logHaloSelfClick("其他功能", getString(R.string.setting_children_policy))
NewFlatLogUtils.logHaloSelfClick("其他功能", getString(com.gh.gamecenter.common.R.string.setting_children_policy))
SensorsBridge.trackHaloSelfClick(
profile = "其他功能",
text = getString(R.string.setting_children_policy),
text = getString(com.gh.gamecenter.common.R.string.setting_children_policy),
linkType = "",
linkId = "",
linkText = ""
@ -982,8 +983,8 @@ class HaloPersonalFragment : BaseLazyFragment() {
startActivity(
WebActivity.getWebIntent(
requireContext(),
getString(R.string.setting_children_policy),
requireContext().getString(R.string.children_policy_url)
getString(com.gh.gamecenter.common.R.string.setting_children_policy),
requireContext().getString(com.gh.gamecenter.common.R.string.children_policy_url)
)
)
}

View File

@ -0,0 +1,114 @@
package com.gh.gamecenter.personal
import android.view.View
import androidx.core.view.isVisible
import androidx.lifecycle.LiveData
import com.ethanhua.skeleton.Skeleton
import com.ethanhua.skeleton.ViewSkeletonScreen
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.LazyFragment
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.databinding.FragmentMyHaloListBaseBinding
import com.gh.gamecenter.core.iinterface.IScrollable
abstract class MyHaloBaseListFragment: LazyFragment(), IScrollable {
protected lateinit var binding: FragmentMyHaloListBaseBinding
protected var skeleton: ViewSkeletonScreen? = null
override fun getRealLayoutId(): Int = com.gh.gamecenter.common.R.layout.fragment_my_halo_list_base
override fun onRealLayoutInflated(inflatedView: View) {
super.onRealLayoutInflated(inflatedView)
binding = FragmentMyHaloListBaseBinding.bind(inflatedView)
}
override fun onFragmentFirstVisible() {
super.onFragmentFirstVisible()
skeleton = Skeleton.bind(binding.skeleton).shimmer(false)
.load(R.layout.activity_install_skeleton).show()
binding.reuseNoConnection.connectionReloadTv.setOnClickListener {
onLoadRefresh()
}
provideLoadStatusLiveData()?.observe(viewLifecycleOwner) {
when (it) {
LoadStatus.INIT_EMPTY -> onLoadEmpty()
LoadStatus.INIT_EXCEPTION -> onLoadException()
LoadStatus.INIT_FAILED -> onLoadFailed()
LoadStatus.INIT_LOADED, LoadStatus.INIT_OVER -> onLoadDone()
else -> {}
}
}
}
abstract fun provideLoadStatusLiveData(): LiveData<LoadStatus>?
protected open fun onLoadDone() {
skeleton?.hide()
binding.reuseNoneData.reuseNoneData.isVisible = false
binding.reuseNoConnection.reuseNoConnection.isVisible = false
binding.reuseDataException.reuseDataException.isVisible = false
binding.reuseGrantGetInstalledApps.reuseGrantGetInstalledApps.isVisible = false
binding.listRv.isVisible = true
}
protected open fun onLoadEmpty() {
showEmpty()
}
protected open fun showEmpty(isGranted: Boolean = true) {
skeleton?.hide()
binding.reuseNoneData.reuseNoneData.isVisible = isGranted
binding.reuseGrantGetInstalledApps.reuseGrantGetInstalledApps.isVisible = !isGranted
binding.reuseNoConnection.reuseNoConnection.isVisible = false
binding.reuseDataException.reuseDataException.isVisible = false
binding.listRv.isVisible = false
}
open fun onLoadRefresh() {
if (!isBindingInitialized()) return
skeleton?.show()
binding.reuseNoneData.reuseNoneData.isVisible = false
binding.reuseNoConnection.reuseNoConnection.isVisible = false
binding.reuseDataException.reuseDataException.isVisible = false
binding.reuseGrantGetInstalledApps.reuseGrantGetInstalledApps.isVisible = false
binding.listRv.isVisible = false
refresh()
}
protected abstract fun refresh()
protected open fun onLoadFailed() {
skeleton?.hide()
binding.reuseNoneData.reuseNoneData.isVisible = false
binding.reuseNoConnection.reuseNoConnection.isVisible = true
binding.reuseDataException.reuseDataException.isVisible = false
binding.reuseGrantGetInstalledApps.reuseGrantGetInstalledApps.isVisible = false
binding.listRv.isVisible = false
}
protected open fun onLoadException() {
skeleton?.hide()
binding.reuseNoneData.reuseNoneData.isVisible = false
binding.reuseNoConnection.reuseNoConnection.isVisible = false
binding.reuseDataException.reuseDataException.isVisible = true
binding.reuseGrantGetInstalledApps.reuseGrantGetInstalledApps.isVisible = false
binding.listRv.isVisible = false
}
protected fun isBindingInitialized() = ::binding.isInitialized
override fun scrollToTop() {
if (!isBindingInitialized()) return
binding.listRv.scrollToPosition(0)
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
if (isBindingInitialized()) {
binding.listRv.adapter?.run { notifyItemRangeChanged(0, itemCount) }
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,324 @@
package com.gh.gamecenter.personal
import android.content.Context
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.core.view.setPadding
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.CheckLoginUtils
import com.gh.common.util.DataCollectionUtils
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.*
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.databinding.ItemMyHaloFunctionBinding
import com.gh.gamecenter.databinding.ItemMyHaloFunctionFirstBinding
import com.gh.gamecenter.databinding.ItemMyHaloFunctionSecondBinding
import com.gh.gamecenter.entity.MyHaloContentCard.Function.FunctionData
import com.gh.gamecenter.game.upload.GameSubmissionActivity
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.message.MessageUnreadRepository
import com.gh.gamecenter.savegame.GameArchiveListActivity
import com.gh.gamecenter.setting.SettingBridge
import com.gh.gamecenter.simulatorgame.SimulatorGameActivity
import com.gh.gamecenter.teenagermode.TeenagerModeActivity
import com.gh.gamecenter.toolbox.ToolBoxActivity
import com.gh.gamecenter.video.videomanager.VideoManagerActivity
import com.lightgame.adapter.BaseRecyclerAdapter
class MyHaloFunctionAdapter(context: Context): BaseRecyclerAdapter<RecyclerView.ViewHolder>(context) {
private val firstPageList = mutableListOf<FunctionData>()
private val secondPageList = mutableListOf<FunctionData>()
private val secondPageItemWidth = (mContext.resources.displayMetrics.widthPixels - 40F.dip2px()) / 4F
private val secondPageItemHeight = 64F.dip2px()
fun submitData(firstPageDataList: List<FunctionData>, secondPageDataList: List<FunctionData>?) {
firstPageList.clear()
secondPageList.clear()
firstPageList.addAll(firstPageDataList)
secondPageDataList?.let { secondPageList.addAll(it) }
notifyDataSetChanged()
}
override fun getItemViewType(position: Int): Int {
return if (position == 0) ITEM_FIRST else ITEM_SECOND
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == ITEM_FIRST) {
FirstFunctionViewHolder(parent.toBinding())
} else {
SecondFunctionViewHolder(parent.toBinding())
}
}
override fun getItemCount(): Int = if (secondPageList.isEmpty()) 1 else 2
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is FirstFunctionViewHolder) {
holder.binding.root.run {
if (firstPageList == holder.dataList && holder.itemBindingList.isNotEmpty()) {
holder.itemBindingList.forEachIndexed { index, binding ->
val entity = firstPageList.getOrNull(index)
if (entity != null) {
bindFunctionItem(binding, entity)
}
}
} else {
holder.itemBindingList.clear()
removeAllViews()
val itemWidth = if (itemCount == 1) {
setPadding(4F.dip2px())
secondPageItemWidth
} else {
setPadding(4F.dip2px(), 4F.dip2px(), 0, 4F.dip2px())
(context.resources.displayMetrics.widthPixels - 36F.dip2px() - secondPageItemWidth / 2F) / 4F
}
firstPageList.forEachIndexed { _, entity ->
ItemMyHaloFunctionBinding.inflate(layoutInflater).run {
bindFunctionItem(this, entity)
holder.itemBindingList.add(this)
addView(root, itemWidth.toInt(), 64F.dip2px())
}
}
holder.dataList = firstPageList.toMutableList()
}
}
}
if (holder is SecondFunctionViewHolder) {
holder.binding.root.run {
val lineCount = (secondPageList.size + SECOND_PAGE_SPAN_COUNT - 1) / SECOND_PAGE_SPAN_COUNT
updateLayoutParams<ViewGroup.LayoutParams> { height = lineCount * secondPageItemHeight + 8F.dip2px() }
if (adapter is SecondFunctionAdapter) {
(adapter as SecondFunctionAdapter).let {
if (it.currentList == secondPageList) {
it.notifyItemRangeChanged(0, it.itemCount)
} else {
it.submitList(secondPageList.toMutableList())
}
}
} else {
layoutManager = GridLayoutManager(mContext, SECOND_PAGE_SPAN_COUNT)
adapter = SecondFunctionAdapter().apply {
submitList(secondPageList.toMutableList())
}
}
}
}
}
class FirstFunctionViewHolder(val binding: ItemMyHaloFunctionFirstBinding) :
RecyclerView.ViewHolder(binding.root) {
var itemBindingList = arrayListOf<ItemMyHaloFunctionBinding>()
var dataList: List<FunctionData>? = null
}
class SecondFunctionViewHolder(val binding: ItemMyHaloFunctionSecondBinding) :
RecyclerView.ViewHolder(binding.root)
class SecondFunctionAdapter: ListAdapter<FunctionData, SecondFunctionAdapter.FunctionItemViewHolder>(
createDiffItemCallBack()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FunctionItemViewHolder =
FunctionItemViewHolder(parent.toBinding())
override fun onBindViewHolder(holder: FunctionItemViewHolder, position: Int) {
bindFunctionItem(holder.binding, getItem(position))
}
class FunctionItemViewHolder(val binding: ItemMyHaloFunctionBinding): RecyclerView.ViewHolder(binding.root)
companion object {
fun createDiffItemCallBack() = object : ItemCallback<FunctionData>() {
override fun areItemsTheSame(oldItem: FunctionData, newItem: FunctionData): Boolean = true
override fun areContentsTheSame(oldItem: FunctionData, newItem: FunctionData): Boolean = oldItem == newItem
}
}
}
companion object {
const val ITEM_FIRST = 0
const val ITEM_SECOND = 1
const val SECOND_PAGE_SPAN_COUNT = 4
fun bindFunctionItem(binding: ItemMyHaloFunctionBinding, entity: FunctionData) {
binding.run {
nameTv.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(root.context))
redDotIv.background = R.drawable.oval_hint_red_bg.toDrawable(root.context)
iconIv.hierarchy.setActualImageColorFilter(
if (entity.icon.endsWith(".svg")) {
PorterDuffColorFilter(
com.gh.gamecenter.common.R.color.text_primary.toColor(root.context),
PorterDuff.Mode.SRC_ATOP
)
} else {
null
}
)
ImageUtils.display(iconIv, entity.icon)
nameTv.text = entity.name
redDotIv.isVisible = entity.remindSwitch
root.setOnClickListener {
directPage(root.context, entity)
redDotIv.isVisible = false
SensorsBridge.trackHaloSelfClick(
"功能服务",
entity.name,
entity.link?.type ?: "",
entity.link?.link ?: "",
entity.link?.text ?: ""
)
}
}
}
private fun directPage(context: Context, entity: FunctionData) {
if (entity.remindSwitch) {
val haveReadRecord= SPUtils.getStringSet(Constants.SP_ADDONS_FUNCS_HAVE_READ) as HashSet<String>
val newReadRecord = hashSetOf<String>()//这里必须重新创建HashSet对象否则重启app数据不能保存
newReadRecord.addAll(haveReadRecord)
newReadRecord.add(entity.id)
SPUtils.setStringSet(Constants.SP_ADDONS_FUNCS_HAVE_READ, newReadRecord)
MessageUnreadRepository.loadMessageUnreadTotal(true)
entity.remindSwitch = false
}
when (entity.link?.type) {
"视频投稿" -> {
if (UserManager.getInstance().isLoggedIn) {
if (BuildConfig.DEBUG) {
context.startActivity(VideoManagerActivity.getIntent(context, "", "我的光环-视频投稿"))
} else {
DialogHelper.showDialog(
context, "提示",
"抱歉,您当前系统版本过低,暂不支持视频功能", "我知道了", ""
)
}
} else {
CheckLoginUtils.checkLogin(context, "我的光环-视频投稿") {}
}
}
"账号安全" -> {
if (UserManager.getInstance().isLoggedIn) {
context.startActivity(SettingBridge.getSecurityIntent(context, "我的光环-账号安全"))
} else {
CheckLoginUtils.checkLogin(context, "我的光环-账号安全") {}
}
}
"模拟器游戏" -> {
context.startActivity(SimulatorGameActivity.getIntent(context))
}
"收货信息" -> {
if (UserManager.getInstance().isLoggedIn) {
context.startActivity(DeliveryInfoActivity.getIntent(context))
} else {
CheckLoginUtils.checkLogin(context, "我的光环-收货信息") {}
}
}
"实名认证" -> {
context.startActivity(ShellActivity.getIntent(context, ShellActivity.Type.REAL_NAME_INFO, null))
}
"微信提醒" -> {
if (UserManager.getInstance().isLoggedIn) {
context.startActivity(WebActivity.getBindWechatIntent(context))
SensorsBridge.trackEvent(
"AppointmenWechatRemindConfigPageShow",
"source_entrance",
"我的光环-微信提醒"
)
} else {
CheckLoginUtils.checkLogin(context, "我的光环-微信提醒") { }
}
}
"游戏动态" -> {
if (UserManager.getInstance().isLoggedIn) {
DataCollectionUtils.uploadClick(context, "游戏动态", "发现")
DirectUtils.directToConcernInfo(context, "我的光环-常用功能")
} else {
CheckLoginUtils.checkLogin(context, "我的光环-游戏动态") { }
}
}
"资讯中心" -> {
DataCollectionUtils.uploadClick(context, "资讯中心", "发现")
context.startActivity(InfoActivity.getIntent(context))
}
"礼包中心" -> {
DataCollectionUtils.uploadClick(context, "礼包中心", "发现")
DirectUtils.directToGift(context, "(发现:礼包)")
}
"工具箱" -> {
DataCollectionUtils.uploadClick(context, "工具箱", "发现")
context.startActivity(ToolBoxActivity.getIntent(context, "(发现:工具箱)"))
}
"安装包清理" -> {
DataCollectionUtils.uploadClick(context, "安装包清理", "发现")
PermissionHelper.checkManageAllFilesOrStoragePermissionBeforeAction(context) {
context.startActivity(CleanApkActivity.getIntent(context))
}
}
"个人中心" -> {
if (UserManager.getInstance().isLoggedIn) {
context.startActivity(UserInfoActivity.getIntent(context))
} else {
CheckLoginUtils.checkLogin(context, "我的光环-个人中心") {}
}
}
"游戏投稿" -> {
if (UserManager.getInstance().isLoggedIn) {
PermissionHelper.checkStoragePermissionBeforeAction(context) {
context.startActivity(
GameSubmissionActivity.getIntent(context, "(我的光环)", "")
)
}
} else {
CheckLoginUtils.checkLogin(context, "我的光环-游戏投稿") { }
}
}
"视频数据" -> {
if (UserManager.getInstance().isLoggedIn) {
DirectUtils.directVideoData(context, "我的光环-视频数据")
} else {
CheckLoginUtils.checkLogin(context, "我的光环-视频数据") {}
}
}
"青少年模式" -> {
context.startActivity(TeenagerModeActivity.getIntent(context))
}
"游戏存档" -> {
context.startActivity(GameArchiveListActivity.getIntent(context))
}
"开服管理" -> {// 开服管理
DirectUtils.directToServersCalendarManagement("我的光环-常用功能")
}
else -> entity.link?.let { DirectUtils.directToLinkPage(context, it, "我的光环", "常用功能") }
}
}
}
}

View File

@ -0,0 +1,210 @@
package com.gh.gamecenter.personal
import android.annotation.SuppressLint
import android.app.Application
import android.content.Context
import android.os.Build
import android.text.TextUtils
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import com.gh.common.history.HistoryDatabase
import com.gh.common.util.DirectUtils
import com.gh.common.util.PackageUtils
import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.entity.AppEntity
import com.gh.gamecenter.entity.MyHaloContentCard
import com.gh.gamecenter.entity.SignEntity
import com.gh.gamecenter.livedata.Event
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.newsdetail.NewsDetailActivity
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.subject.SubjectActivity
import com.halo.assistant.HaloApp
import com.lightgame.utils.Utils
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import okhttp3.ResponseBody
import retrofit2.HttpException
import java.text.SimpleDateFormat
import java.util.*
class MyHaloViewModel(application: Application) : AndroidViewModel(application) {
private val api = RetrofitManager.getInstance().api
private val newApi = RetrofitManager.getInstance().newApi
val appEntity = MutableLiveData<AppEntity>()
val vipCardLiveData = MutableLiveData<MyHaloContentCard.VipCard?>()
val functionLiveData = MutableLiveData<MyHaloContentCard.Function?>()
val sortTypeLiveData = MutableLiveData<Event<String>>()
val managementStateLiveData = MutableLiveData(false)
val historyGameCountFlow = HistoryDatabase.instance.gameDao().getGameCount()
private val compositeDisposable = CompositeDisposable()
init {
loadData()
}
fun changeManagementState() {
val state = managementStateLiveData.value ?: false
managementStateLiveData.postValue(!state)
}
fun loadData() {
checkUpdate()
getContentCard()
}
fun logout() {
vipCardLiveData.postValue(null)
getContentCard()
}
@SuppressLint("CheckResult")
fun getContentCard() {
newApi.myHaloContentCard
.compose(singleToMain())
.subscribe({
if (it != null) {
vipCardLiveData.postValue(it.vipCard)
val functionData = it.function?.apply {
val haveReadRecord: HashSet<String> =
SPUtils.getStringSet(Constants.SP_ADDONS_FUNCS_HAVE_READ) as HashSet<String>
data.forEach { functionData ->
if (functionData.remindSwitch && haveReadRecord.contains(functionData.id)) {
functionData.remindSwitch = false
}
}
}
functionLiveData.postValue(functionData)
}
}, {}).let(compositeDisposable::add)
}
private fun checkUpdate() {
api.getUpdate(
PackageUtils.getGhVersionName(),
PackageUtils.getGhVersionCode(),
HaloApp.getInstance().channel,
Build.VERSION.SDK_INT
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Response<AppEntity>() {
override fun onResponse(response: AppEntity?) {
super.onResponse(response)
response?.let { appEntity.postValue(it) }
}
})
}
fun sign(successCallback: (signEntity: SignEntity) -> Unit) {
val context = getApplication<Application>().applicationContext
api.postSign(UserManager.getInstance().userId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Response<SignEntity>() {
override fun onResponse(signEntity: SignEntity?) {
if (signEntity != null) {
successCallback.invoke(signEntity)
}
}
override fun onFailure(e: HttpException?) {
if (e == null || e.code() != 401) {
ToastUtils.toast(context.getString(com.gh.gamecenter.common.R.string.loading_network_error))
}
}
})
}
fun signSkip(context: Context, signEntity: SignEntity) {
val data = signEntity.data
val entrance = "(我的光环)+(签到)"
if (data == null || TextUtils.isEmpty(data.type)) {
DirectUtils.directToHomeDefaultTab(context)
return
}
when (data.type) {
"game" -> {
GameDetailActivity.startGameDetailActivity(context, data.link, entrance)
}
"news" -> {
context.startActivity(NewsDetailActivity.getIntentById(context, data.link, entrance))
}
"column" -> {
SubjectActivity.startSubjectActivity(
context,
data.link,
null,
false,
null,
entrance
)
}
else -> {
val linkEntity = LinkEntity()
linkEntity.type = data.type
linkEntity.link = data.link
linkEntity.text = data.text
linkEntity.community = data.community
linkEntity.display = data.display
DirectUtils.directToLinkPage(context, linkEntity, entrance, "")
}
}
}
fun isCanSign(time: Long): Boolean {
val context = getApplication<Application>().applicationContext
val formatDay = SimpleDateFormat("dd", Locale.CHINA)
val lastSignTime = time * 1000
val curTime = Utils.getTime(context) * 1000
val lastSignDay = formatDay.format(lastSignTime).toInt()
val curDay = formatDay.format(curTime).toInt()
return lastSignDay != curDay || curTime - lastSignTime > 24 * 60 * 60 * 1000
}
@SuppressLint("CheckResult")
fun applyOrReceiveBadge(
id: String,
successCallback: (data: ResponseBody) -> Unit,
failureCallback: () -> Unit
) {
api.applyOrReceiveBadge(id)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BiResponse<ResponseBody>() {
override fun onSuccess(data: ResponseBody) {
successCallback.invoke(data)
}
override fun onFailure(exception: java.lang.Exception) {
super.onFailure(exception)
failureCallback.invoke()
}
}).let(compositeDisposable::add)
}
fun getLoginEntranceByType(loginTag: String): String = when (loginTag) {
"qq" -> "我的光环-QQ"
"wechat" -> "我的光环-微信"
"weibo" -> "我的光环-新浪微博"
else -> ""
}
override fun onCleared() {
super.onCleared()
compositeDisposable.clear()
}
}

View File

@ -0,0 +1,572 @@
package com.gh.gamecenter.personal.installed
import android.content.res.ColorStateList
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.graphics.Typeface
import android.view.ViewGroup
import android.view.ViewGroup.LayoutParams
import android.widget.LinearLayout
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.facebook.imagepipeline.common.ImageDecodeOptions
import com.gh.common.util.CheckLoginUtils
import com.gh.common.util.DirectUtils
import com.gh.common.util.DownloadItemUtils
import com.gh.common.util.PackageUtils
import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.adapter.viewholder.GameViewHolder
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.image.CustomSvgImageDecoder
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.viewholder.FooterViewHolder
import com.gh.gamecenter.core.utils.NumberUtils
import com.gh.gamecenter.databinding.*
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.feature.entity.AcctGameInfo
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.MyHaloGameTag
import com.gh.gamecenter.feature.game.GameItemViewHolder
import com.gh.gamecenter.feature.provider.IBindingAdaptersProvider
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.DISTRICT_SERVER_EMPTY
import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.DISTRICT_SERVER_HAVA
import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.SCENE_TYPE_NO_GUIDE_LAYER
import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.SOURCE_ENTRANCE_MY_HALO_INSTALLED
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorZoneDialogFragment
import com.gh.gamecenter.gamedetail.entity.GameDetailTabEntity
import com.gh.gamecenter.search.SearchGameResultAdapter.Companion.startAccelerating
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
import com.lightgame.download.DownloadEntity
import com.therouter.TheRouter
class InstalledAdapter: ListAdapter<InstalledGameItemData, RecyclerView.ViewHolder>(createDiffCallback()) {
private val positionAndPackageMap = HashMap<String, Int>()
private var isShowUpdate = false
override fun submitList(list: List<InstalledGameItemData>?) {
positionAndPackageMap.clear()
// 记录游戏位置
list?.forEachIndexed { i, item ->
if (i == 0) {
isShowUpdate = item.isHead
}
item.gameEntity?.let { gameEntity ->
val key = buildString {
append(gameEntity.id)
gameEntity.getApk().forEach { apk ->
append(apk.packageName)
}
append(i)
}
positionAndPackageMap[key] = i
}
}
super.submitList(list)
}
override fun getItemViewType(position: Int): Int {
val itemData = currentList.getOrNull(position) ?: return ITEM_NORMAL
return if (itemData.isHead) ITEM_HEAD else if (itemData.isFooter) ITEM_FOOTER else ITEM_NORMAL
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
when (viewType) {
ITEM_HEAD -> {
InstalledGameUpdateItemViewHolder(parent.toBinding())
}
ITEM_FOOTER -> {
FooterViewHolder(parent.layoutInflater.inflate(com.gh.gamecenter.common.R.layout.refresh_footerview, parent, false))
}
else -> {
InstalledGameItemViewHolder(parent.toBinding())
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val itemData = currentList.getOrNull(position) ?: return
if (holder is InstalledGameUpdateItemViewHolder) {
holder.binding.run {
root.setCardBackgroundColor(com.gh.gamecenter.common.R.color.ui_surface.toColor(root.context))
container.background = com.gh.gamecenter.common.R.drawable.reuse_listview_item_style.toDrawable(root.context)
updateDataTv.typeface = Typeface.createFromAsset(root.context.assets, Constants.DIN_FONT_PATH)
updateDataTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(root.context))
updateTv.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(root.context))
arrowIv.setImageResource(R.drawable.ic_auxiliary_arrow_right_8)
updateDataTv.text = itemData.updatableGameCount.toString()
val gameIconList = listOf(gameIcon1, gameIcon2, gameIcon3)
gameIconList.forEach {
it.setBorderColor(com.gh.gamecenter.common.R.color.ui_surface)
}
itemData.updatableGameList?.forEachIndexed { index, game ->
gameIconList[index].displayGameIcon(game)
}
itemData.updatableGameList?.size?.let {
gameIcon1.goneIf(it == 0)
gameIcon2.goneIf(it < 2)
gameIcon3.goneIf(it < 3)
}
root.setOnClickListener {
DirectUtils.directToDownloadManagerUpdate(root.context)
SensorsBridge.trackHaloSelfGameInstalledClick(false, profile = "可更新入口", text = "可更新入口")
}
}
}
if (holder is InstalledGameItemViewHolder) {
val gameEntity = itemData.gameEntity ?: return
val isMultiVersion = gameEntity.getApk().size > 1
holder.binding.run {
root.setCardBackgroundColor(com.gh.gamecenter.common.R.color.ui_surface.toColor(root.context))
myHaloGame.run {
selectIv.isVisible = false
root.background = com.gh.gamecenter.common.R.drawable.reuse_listview_item_style.toDrawable(root.context)
gameInfo.run {
wifiAutoDownloadTv.isVisible = false
tagTv.isVisible = false
gameNameTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(root.context))
val provider = TheRouter.get(IBindingAdaptersProvider::class.java)
provider?.setGameName(gameNameTv, gameEntity, false)
gameIconView.displayGameIcon(gameEntity)
GameItemViewHolder.initGameSubtitleAndAdLabel(gameEntity, gameSubtitleTv)
downloadBtn.goneIf(isMultiVersion)
if (isMultiVersion) {
gameDesTv.text = "${gameEntity.getApk().size} 个版本"
} else if (gameEntity.getApk().size == 1) {
var gameDesText = gameEntity.getApk().first().getPlatformName()
if (gameEntity.playedTime != 0L) {
gameDesText += " 已玩${NumberUtils.transSimpleUsageTime(gameEntity.playedTime)}"
}
gameDesTv.text = gameDesText
DownloadItemUtils.setOnClickListener(
root.context,
downloadBtn,
gameEntity,
holder.bindingAdapterPosition,
this@InstalledAdapter,
"(我的光环-已安装)",
location = "我的光环-已安装" + ":" + gameEntity.name,
traceEvent = gameEntity.exposureEvent,
clickCallback = {
SensorsBridge.trackHaloSelfGameInstalledClick(
false,
gameEntity.id,
gameEntity.name ?: "",
gameEntity.categoryChinese,
if (isShowUpdate) holder.bindingAdapterPosition else holder.bindingAdapterPosition + 1,
profile = "按钮",
text = downloadBtn.text
)
}
)
DownloadItemUtils.updateItem(root.context, gameEntity, GameViewHolder(root).apply {
gameDownloadBtn = downloadBtn
}, listener = object : DownloadButton.OnUpdateListener {
override fun completion(text: String) {
// 如果下载按钮状态为启动,则需要判断是否需要显示加速按钮
showSpeedButton(
text,
gameEntity,
gameInfo,
if (isShowUpdate) holder.bindingAdapterPosition else holder.bindingAdapterPosition + 1
)
}
})
}
}
multiVersionContainer.goneIf(!isMultiVersion) {
multiVersionContainer.run {
removeAllViews()
gameEntity.getApk().forEach { apkEntity ->
ItemInstalledGameMultiVersionBinding.inflate(layoutInflater).apply {
divider.setBackgroundColor(gameEntity.iconThemeColor.hexStringToIntColor().modifyHSV(s = 0.5F, v = 0.5F))
platformNameTv.text = apkEntity.getPlatformName()
var gameDesText = "V${PackageUtils.getVersionNameByPackageName(apkEntity.packageName)}"
if (gameEntity.playedTime != 0L) {
gameDesText += " 已玩${NumberUtils.transSimpleUsageTime(apkEntity.playedTime)}"
}
desTv.text = gameDesText
val downloadGameEntity = gameEntity.copy()
downloadGameEntity.setApk(gameEntity.getApk().filter { it.packageName == apkEntity.packageName }.toArrayList())
downloadGameEntity.dropOtherApk()
DownloadItemUtils.setOnClickListener(
root.context,
downloadBtn,
downloadGameEntity,
holder.bindingAdapterPosition,
this@InstalledAdapter,
"(我的光环-已安装)",
location = "我的光环-已安装" + ":" + gameEntity.name,
traceEvent = gameEntity.exposureEvent
)
DownloadItemUtils.updateItem(root.context, downloadGameEntity, GameViewHolder(root).apply {
gameDownloadBtn = downloadBtn
})
addView(root, LinearLayout.LayoutParams.MATCH_PARENT, 48F.dip2px())
}
}
}
}
bindAnnouncement(TYPE_INSTALLED, this, gameEntity, if (isShowUpdate) holder.bindingAdapterPosition else holder.bindingAdapterPosition + 1)
bindService(TYPE_INSTALLED, this, gameEntity, if (isShowUpdate) holder.bindingAdapterPosition else holder.bindingAdapterPosition + 1, "我的光环-已安装")
}
root.setOnClickListener {
GameDetailActivity.startGameDetailActivity(
root.context,
gameEntity.id,
"(我的光环:我的游戏)",
gameEntity.exposureEvent
)
SensorsBridge.trackHaloSelfGameInstalledClick(
false,
gameEntity.id,
gameEntity.name ?: "",
gameEntity.categoryChinese,
if (isShowUpdate) holder.bindingAdapterPosition else holder.bindingAdapterPosition + 1,
profile = "游戏",
text = "游戏"
)
}
}
}
if (holder is FooterViewHolder) {
holder.initFooterViewHolder(false, false, true)
}
}
private fun showSpeedButton(
text: String,
gameEntity: GameEntity,
binding: LayoutMyHaloGameInfoBinding,
position: Int
) {
if (text == com.gh.gamecenter.feature.R.string.launch.toResString() && !gameEntity.isVGamePreferred() && gameEntity.canSpeed) {
val context = binding.root.context
binding.tvSpeed.goneIf(false)
binding.downloadBtn.goneIf(true)
val serviceArea = gameEntity.serviceArea
val hasMutualityZone = serviceArea.size > 1
val lastAcctGame = gameEntity.lastAcctGame
val isCurrentGameAccelerating = AcceleratorDataHolder.instance.isCurrentGameAccelerating(gameEntity.id)
if (isCurrentGameAccelerating) {
binding.arrowIv.goneIf(true)
binding.zoneDivider.goneIf(true)
binding.tvSpeed.updateLayoutParams<LayoutParams> { width = LayoutParams.WRAP_CONTENT }
binding.tvSpeed.setPadding(12F.dip2px(), 0, 12F.dip2px(), 0)
binding.tvSpeed.text = R.string.accelerating.toResString()
binding.tvSpeed.setBackgroundResource(com.gh.gamecenter.common.R.drawable.bg_common_button_light_fill_blue)
binding.tvSpeed.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
} else {
binding.arrowIv.goneIf(!hasMutualityZone)
binding.zoneDivider.goneIf(!hasMutualityZone)
binding.tvSpeed.updateLayoutParams<LayoutParams> { width = if (hasMutualityZone) 93F.dip2px() else LayoutParams.WRAP_CONTENT }
binding.tvSpeed.setPadding(12F.dip2px(), 0, if (hasMutualityZone) 32F.dip2px() else 12F.dip2px(), 0)
binding.tvSpeed.setBackgroundResource(com.gh.gamecenter.common.R.drawable.bg_common_button_fill_gradient_blue)
binding.tvSpeed.text = lastAcctGame?.zoneName ?: R.string.network_acceleration.toResString()
binding.tvSpeed.setTextColor(com.gh.gamecenter.common.R.color.text_aw_primary.toColor(context))
}
binding.tvSpeed.setOnClickListener {
val districtServer = when {
hasMutualityZone && lastAcctGame != null -> lastAcctGame.zoneName
hasMutualityZone -> DISTRICT_SERVER_EMPTY
else -> DISTRICT_SERVER_HAVA
}
SensorsBridge.trackNetworkAccelerationButtonClick(
gameEntity.getUniquePackageName() ?: "",
gameEntity.id,
gameEntity.name ?: "",
AcceleratorDataHolder.instance.memberType,
districtServer,
SCENE_TYPE_NO_GUIDE_LAYER,
SOURCE_ENTRANCE_MY_HALO_INSTALLED
)
SensorsBridge.trackHaloSelfGameInstalledClick(
false,
gameEntity.id,
gameEntity.name ?: "",
gameEntity.categoryChinese,
position,
profile = "按钮",
text = binding.tvSpeed.text.toString()
)
if (CheckLoginUtils.isLogin()) {
when {
!hasMutualityZone -> {
// 单区服,直接启动
val zoneInfo = gameEntity.serviceArea.firstOrNull() ?: AcctGameInfo.ZoneInfo(0)
startAccelerating(gameEntity, context, zoneInfo, false, SOURCE_ENTRANCE_MY_HALO_INSTALLED)
}
lastAcctGame != null -> {
// 多区服,有缓存的加速记录
startAccelerating(gameEntity, context, lastAcctGame.zoneInfo, true, SOURCE_ENTRANCE_MY_HALO_INSTALLED)
}
else -> {
// 多区服,没有缓存的加速记录
AcceleratorZoneDialogFragment.show(
context,
null,
gameEntity.serviceArea.toArrayList(),
gameEntity,
SOURCE_ENTRANCE_MY_HALO_INSTALLED
)
}
}
} else {
CheckLoginUtils.checkLogin(context, null, true, "") {}
}
}
binding.tvSpeed.isClickable = !isCurrentGameAccelerating
binding.arrowIv.setOnClickListener {
context.ifLogin("[网络加速]") {
AcceleratorZoneDialogFragment.show(
context,
lastAcctGame?.zoneInfo?.id,
gameEntity.serviceArea.toArrayList(),
gameEntity,
SOURCE_ENTRANCE_MY_HALO_INSTALLED
)
}
}
} else {
binding.tvSpeed.goneIf(true)
binding.arrowIv.goneIf(true)
binding.zoneDivider.goneIf(true)
}
}
fun notifyItemByDownload(download: DownloadEntity) {
for (key in positionAndPackageMap.keys) {
if (key.contains(download.packageName) && key.contains(download.gameId)) {
val position = positionAndPackageMap[key]
if (position != null && position < currentList.size) {
currentList[position].gameEntity?.getEntryMap()?.set(download.platform, download)
notifyItemChanged(position)
}
}
}
}
fun notifyByDownloadRemoved(status: EBDownloadStatus) {
for (key in positionAndPackageMap.keys) {
if (key.contains(status.packageName)) {
val position = positionAndPackageMap[key]
if (position != null && position < currentList.size) {
currentList[position].gameEntity?.getEntryMap()?.remove(status.platform)
notifyItemChanged(position)
}
}
}
}
class InstalledGameUpdateItemViewHolder(val binding: ItemInstalledGameUpdateBinding): RecyclerView.ViewHolder(binding.root)
class InstalledGameItemViewHolder(val binding: ItemInstalledGameBinding): RecyclerView.ViewHolder(binding.root)
companion object {
const val ITEM_HEAD = 0
const val ITEM_NORMAL = 1
const val ITEM_FOOTER = 2
const val TYPE_INSTALLED = "installed"
const val TYPE_V_GAME = "smooth"
const val TYPE_RESERVATION = "reservation"
fun createDiffCallback() = object : ItemCallback<InstalledGameItemData>() {
override fun areItemsTheSame(oldItem: InstalledGameItemData, newItem: InstalledGameItemData): Boolean {
return oldItem.areItemsTheSame(newItem)
}
override fun areContentsTheSame(oldItem: InstalledGameItemData, newItem: InstalledGameItemData): Boolean {
return oldItem.areContentsTheSame(newItem)
}
}
fun bindAnnouncement(type: String, binding: ItemMyHaloGameBinding, gameEntity: GameEntity, position: Int) {
binding.run {
announcementContainer.goneIf(gameEntity.announcementTag.isEmpty()) {
announcementContainer.removeAllViews()
val themeColor = gameEntity.iconThemeColor.hexStringToIntColor().modifyHSV(s = 0.5F, v = 0.5F)
val isDarkModeOn = DarkModeUtils.isDarkModeOn(root.context)
announcementContainer.setRoundedColorBackgroundByColorInt(
if (isDarkModeOn) com.gh.gamecenter.common.R.color.ui_container_1.toColor(root.context) else themeColor, 6F, if (isDarkModeOn) 1F else 0.05F
)
gameEntity.announcementTag.forEach {
ItemMyHaloGameAnnouncementBinding.inflate(root.layoutInflater).apply {
val decodeOptions = ImageDecodeOptions.newBuilder()
.setCustomImageDecoder(CustomSvgImageDecoder(themeColor))
.build()
if (it.icon.isEmpty() && it.defaultAnnouncementIconPath != null) {
ImageUtils.displayFromAssets(iconIv, it.defaultAnnouncementIconPath!!, decodeOptions)
} else {
ImageUtils.display(iconIv, it.icon, customImageDecodeOptions = if (it.icon.endsWith(".svg")) decodeOptions else null)
}
titleTv.setTextColor(if (isDarkModeOn) com.gh.gamecenter.common.R.color.text_secondary.toColor(root.context) else themeColor)
titleTv.text = it.title
arrowIv.alpha = if (isDarkModeOn) 1F else 0.25F
arrowIv.imageTintList = ColorStateList.valueOf(if (isDarkModeOn) com.gh.gamecenter.common.R.color.text_secondary.toColor(root.context) else themeColor)
root.setOnClickListener { _ ->
it.link?.let { link ->
DirectUtils.directToLinkPage(root.context, link, "", "")
if (type == TYPE_RESERVATION) {
SensorsBridge.trackHaloSelfGameAppointmentClick(
gameEntity.id,
gameEntity.name ?: "",
gameEntity.categoryChinese,
"游戏公告",
it.title,
link.link ?: "",
link.text ?: "",
link.type ?: "",
)
} else {
SensorsBridge.trackHaloSelfGameInstalledClick(
type == TYPE_V_GAME,
gameEntity.id,
gameEntity.name ?: "",
gameEntity.categoryChinese,
position,
"游戏公告",
it.title,
link.link ?: "",
link.text ?: "",
link.type ?: "",
)
}
}
}
announcementContainer.addView(root, LinearLayout.LayoutParams.MATCH_PARENT, 28F.dip2px())
}
}
}
}
}
fun bindService(type: String, binding: ItemMyHaloGameBinding, gameEntity: GameEntity, position: Int, entrance: String) {
binding.run {
serviceContainer.goneIf(gameEntity.serviceTag.isEmpty()) {
serviceContainer.removeAllViews()
serviceContainer.dividerDrawable = R.drawable.divider_installed_game_service.toDrawable(root.context)
gameEntity.serviceTag.forEach {
ItemMyHaloGameServiceBinding.inflate(root.layoutInflater).apply {
val textSecondaryColorFilter = PorterDuffColorFilter(com.gh.gamecenter.common.R.color.text_secondary.toColor(root.context), PorterDuff.Mode.SRC_ATOP)
if (it.icon.isEmpty() && it.defaultServiceIcon != null) {
iconIv.hierarchy.setPlaceholderImage(it.defaultServiceIcon!!.toDrawable(root.context)?.apply {
colorFilter = textSecondaryColorFilter
})
} else {
iconIv.hierarchy.setActualImageColorFilter(if (it.icon.endsWith(".svg")) textSecondaryColorFilter else null)
iconIv.hierarchy.setPlaceholderImage(com.gh.gamecenter.common.R.drawable.occupy)
ImageUtils.display(iconIv, it.icon)
}
nameTv.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(root.context))
nameTv.text = it.title
root.setOnClickListener { _ ->
if (it.link != null) {
DirectUtils.directToLinkPage(root.context, it.link!!, entrance, "")
if (type == TYPE_RESERVATION) {
SensorsBridge.trackHaloSelfGameAppointmentClick(
gameEntity.id,
gameEntity.name ?: "",
gameEntity.categoryChinese,
"服务标签",
it.title,
it.link?.link ?: "",
it.link?.text ?: "",
it.link?.type ?: "",
)
} else {
SensorsBridge.trackHaloSelfGameInstalledClick(
type == TYPE_V_GAME,
gameEntity.id,
gameEntity.name ?: "",
gameEntity.categoryChinese,
position,
"服务标签",
it.title,
it.link?.link ?: "",
it.link?.text ?: "",
it.link?.type ?: "",
)
}
} else {
when (it.type) {
MyHaloGameTag.TYPE_ARCHIVE -> {
GameDetailActivity.startGameDetailActivity(
binding.root.context,
gameEntity,
entrance,
defaultTab = GameDetailTabEntity.TYPE_ARCHIVE
)
}
MyHaloGameTag.TYPE_BBS -> {
GameDetailActivity.startGameDetailActivity(
binding.root.context,
gameEntity,
entrance,
defaultTab = GameDetailTabEntity.TYPE_BBS
)
}
MyHaloGameTag.TYPE_ZONE -> {
GameDetailActivity.startGameDetailActivity(
binding.root.context,
gameEntity,
entrance,
defaultTab = GameDetailTabEntity.TYPE_ZONE
)
}
MyHaloGameTag.TYPE_GIFT -> {
GameDetailActivity.startGameDetailActivity(
binding.root.context,
gameEntity,
entrance,
defaultTab = GameDetailTabEntity.TYPE_GIFT,
scrollToLibao = true
)
}
MyHaloGameTag.TYPE_SERVER -> {
GameDetailActivity.startGameDetailActivity(
binding.root.context,
gameEntity,
entrance,
scrollToServer = true
)
}
}
}
}
serviceContainer.addView(
root,
LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.MATCH_PARENT
).apply {
weight = 1F
})
}
}
}
}
}
}
}

View File

@ -0,0 +1,196 @@
package com.gh.gamecenter.personal.installed
import android.annotation.SuppressLint
import android.view.View
import android.view.ViewGroup.MarginLayoutParams
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.LiveData
import androidx.recyclerview.widget.LinearLayoutManager
import com.gh.common.iinterface.IInstalledSortType
import com.gh.common.iinterface.InstalledSortType
import com.gh.common.util.DirectUtils
import com.gh.common.util.PackageHelper
import com.gh.download.DownloadManager
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.eventbus.EBReuse
import com.gh.gamecenter.common.utils.PermissionHelper
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.isSimulatorGame
import com.gh.gamecenter.download.UpdatableGameViewModel
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.feature.entity.GameInstall
import com.gh.gamecenter.livedata.Event
import com.gh.gamecenter.manager.PackagesManager
import com.gh.gamecenter.packagehelper.PackageViewModel
import com.gh.gamecenter.personal.MyHaloBaseListFragment
import com.gh.gamecenter.personal.MyHaloViewModel
import com.lightgame.download.DataWatcher
import com.lightgame.download.DownloadEntity
import com.lightgame.download.DownloadStatus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class InstalledFragment: MyHaloBaseListFragment(), IInstalledSortType {
private val viewModel by viewModels<InstalledViewModel>()
private val myHaloViewModel by activityViewModels<MyHaloViewModel>()
private val packageViewModel by viewModels<PackageViewModel> { PackageViewModel.Factory() }
private val updatableGameViewModel by viewModels<UpdatableGameViewModel> { UpdatableGameViewModel.Factory("", mEntrance) }
private val installedAdapter by lazy { InstalledAdapter() }
private val dataWatcher = object : DataWatcher() {
override fun onDataChanged(downloadEntity: DownloadEntity) {
if (downloadEntity.status == DownloadStatus.done && downloadEntity.isSimulatorGame()) {
DownloadManager.getInstance().getEntryMap(downloadEntity.name)?.let {
it[downloadEntity.platform] = downloadEntity
}
packageViewModel.getGameInstalledLiveData().value?.let {
viewModel.initData(
PackagesManager.filterSameApk(
PackagesManager.filterDownloadBlackPackage(
it as MutableList<GameInstall>?
)
)
)
}
return
}
installedAdapter.notifyItemByDownload(downloadEntity)
}
}
override fun onFragmentFirstVisible() {
super.onFragmentFirstVisible()
if (packageViewModel.getGameUpdateIncludeCurrentVersion().value.isNullOrEmpty()) {
updatableGameViewModel.setUpdatableList(arrayListOf())
}
observeData()
binding.run {
binding.reuseNoneData.reuseResetLoadTv.updateLayoutParams<MarginLayoutParams> { width = 150F.dip2px() }
binding.reuseNoneData.reuseResetLoadTv.visibility = View.VISIBLE
binding.reuseNoneData.reuseNoneDataTv.text = getString(com.gh.gamecenter.common.R.string.game_no_data)
binding.reuseNoneData.reuseNoneDataDescTv.visibility = View.VISIBLE
binding.reuseNoneData.reuseNoneDataDescTv.text = getString(com.gh.gamecenter.common.R.string.game_no_data_desc)
binding.reuseNoneData.reuseResetLoadTv.text = "去首页看看"
binding.reuseNoneData.reuseResetLoadTv.setOnClickListener {
DirectUtils.directToHomeDefaultTab(requireContext())
}
listRv.run {
itemAnimator = null
layoutManager = LinearLayoutManager(requireContext())
adapter = installedAdapter
}
}
}
override fun provideLoadStatusLiveData(): LiveData<LoadStatus> = viewModel.loadStatusLiveData
override fun onFragmentResume() {
super.onFragmentResume()
DownloadManager.getInstance().addObserver(dataWatcher)
myHaloViewModel.sortTypeLiveData.postValue(Event(getCurrentSortType().des))
}
override fun onFragmentPause() {
super.onFragmentPause()
DownloadManager.getInstance().removeObserver(dataWatcher)
}
private fun observeData() {
packageViewModel.getGameInstalledLiveData().observe(viewLifecycleOwner) { gameInstalls ->
viewModel.initData(
PackagesManager.filterSameApk(
PackagesManager.filterDownloadBlackPackage(gameInstalls as MutableList<GameInstall>?)
)
)
}
packageViewModel.getGameUpdateIncludeCurrentVersion().observe(viewLifecycleOwner) { updatableList ->
updatableGameViewModel.setUpdatableList(updatableList)
}
updatableGameViewModel.validGameUpdateData.observe(viewLifecycleOwner) {
viewModel.updateGameUpdateData(it)
}
viewModel.itemDataListLiveData.observe(viewLifecycleOwner) {
installedAdapter.submitList(it)
}
}
override fun onLoadEmpty() {
updateNoDataView()
}
private fun updateNoDataView() {
binding.run {
val isGetInstalledListDisagreed = PackageHelper.isGetInstalledPackagesAgreedRequired()
&& !PackageHelper.isGetInstalledPackagesAgreed()
val isGetInstalledListPermissionDisabled = PermissionHelper.isGetInstalledListPermissionDisabled(requireContext())
if (isGetInstalledListDisagreed || isGetInstalledListPermissionDisabled) {
showEmpty(false)
reuseGrantGetInstalledApps.grantTv.setOnClickListener {
PackageHelper.showGetInstallAppsListDialogAndRequestPermissionIfNeeded(requireActivity()) { isGranted ->
if (isGranted) {
updateNoDataView()
}
}
}
} else {
showEmpty(true)
reuseNoneData.reuseResetLoadTv.setOnClickListener {
DirectUtils.directToHomeDefaultTab(requireContext())
}
}
}
}
override fun getCurrentSortType(): InstalledSortType = viewModel.sortedType
override fun changeSortType(sortType: InstalledSortType) {
viewModel.sortedType = sortType
}
override fun refresh() {
packageViewModel.getGameInstalledLiveData().value?.let {
viewModel.initData(PackagesManager.filterSameApk(
PackagesManager.filterDownloadBlackPackage(it as MutableList<GameInstall>?)
))
}
packageViewModel.getGameUpdateIncludeCurrentVersion().value?.let {
updatableGameViewModel.setUpdatableList(it)
}
}
// 打开下载按钮事件
@SuppressLint("NotifyDataSetChanged")
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(reuse: EBReuse) {
if (("Refresh" == reuse.type || "PlatformChanged" == reuse.type)) {
installedAdapter.notifyItemRangeChanged(0, installedAdapter.itemCount)
}
}
//下载被删除事件
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(status: EBDownloadStatus) {
if ("delete" == status.status) {
DownloadManager.getInstance()
.removePlatform(status.name, status.platform)
installedAdapter.notifyByDownloadRemoved(status)
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(packageEb: EBPackage) {
if (!packageEb.fromInit && packageEb.isInstalledOrUninstalled()) {
onLoadRefresh()
}
}
}

View File

@ -0,0 +1,20 @@
package com.gh.gamecenter.personal.installed
import com.gh.gamecenter.feature.entity.GameEntity
data class InstalledGameItemData(
var gameEntity: GameEntity? = null,
var updatableGameCount: Int = 0,
var updatableGameList: List<GameEntity>? = null,
var isHead: Boolean = false,
var isFooter: Boolean = false
) {
private val isGame
get() = gameEntity != null
fun areItemsTheSame(other: InstalledGameItemData) =
isHead == other.isHead && isFooter == other.isFooter && isGame == other.isGame
fun areContentsTheSame(other: InstalledGameItemData) =
updatableGameList == other.updatableGameList && gameEntity == other.gameEntity
}

View File

@ -0,0 +1,234 @@
package com.gh.gamecenter.personal.installed
import android.annotation.SuppressLint
import android.app.Application
import android.content.Context
import android.text.TextUtils
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import com.gh.common.filter.RegionSettingHelper.shouldThisGameBeFiltered
import com.gh.common.iinterface.InstalledSortType
import com.gh.common.util.GameUtils
import com.gh.common.util.PackageUtils
import com.gh.download.DownloadManager
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.common.utils.toArrayList
import com.gh.gamecenter.common.utils.toRequestBody
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.core.utils.ThirdPartyPackageHelper.getGameId
import com.gh.gamecenter.entity.GameUpdateEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.GameInstall
import com.gh.gamecenter.feature.utils.ApkActiveUtils
import com.gh.gamecenter.manager.PackagesManager
import com.gh.gamecenter.retrofit.RetrofitManager
import com.halo.assistant.HaloApp
import io.reactivex.disposables.Disposable
class InstalledViewModel(application: Application): AndroidViewModel(application) {
private val newApi = RetrofitManager.getInstance().newApi
private var packageUpdateList: List<GameUpdateEntity>? = null
var sortedType = InstalledSortType.valueOf(
SPUtils.getString(
Constants.SP_MY_HALO_INSTALLED_ORDER,
InstalledSortType.RECENTLY_PLAYED.name
) ?: InstalledSortType.RECENTLY_PLAYED.name
)
set(value) {
if (field != value) {
field = value
changeSortedType()
SPUtils.setString(Constants.SP_MY_HALO_INSTALLED_ORDER, value.name)
}
}
private var disposable: Disposable? = null
val itemDataListLiveData = MutableLiveData<List<InstalledGameItemData>>()
val loadStatusLiveData = MutableLiveData<LoadStatus>()
private val allGameInstallList: ArrayList<GameInstall> = ArrayList()
private val sortedItemDataList = ArrayList<InstalledGameItemData>()
private fun changeSortedType() {
if (sortedItemDataList.isNotEmpty()) {
when (sortedType) {
InstalledSortType.RECENTLY_PLAYED -> {
sortedItemDataList.forEach { it.gameEntity?.updateRecentlyPlayedTime() }
sortedItemDataList.sortByDescending { it.gameEntity?.recentlyPlayedTime }
}
InstalledSortType.LATEST_UPDATED -> sortedItemDataList.sortByDescending { it.gameEntity?.updateTime }
InstalledSortType.MOST_PLAYED -> sortedItemDataList.sortWith(compareByDescending<InstalledGameItemData> { it.gameEntity?.playedTime }.thenByDescending { it.gameEntity?.packageInstallTime })
InstalledSortType.LEAST_PLAYED -> sortedItemDataList.sortWith(compareBy<InstalledGameItemData> { it.gameEntity?.playedTime }.thenByDescending { it.gameEntity?.packageInstallTime })
}
decorateListAndPost()
}
}
fun initData(list: ArrayList<GameInstall>) {
val packageLaunchTimeSp = HaloApp.getInstance().getSharedPreferences(Constants.SP_PACKAGE_LAUNCH_TIME, Context.MODE_PRIVATE)
val simulatorGameInstallList = DownloadManager.getInstance().allSimulatorDownloadEntity.asSequence()
.mapNotNull { entity ->
entity.gameId.takeUnless { shouldThisGameBeFiltered(it) }?.let { gameId ->
GameInstall(
id = gameId,
isSimulatorGame = true,
lastUsedTime = SPUtils.getLong(packageLaunchTimeSp, gameId, 0L)
)
}
}
.toList()
val normalGameInstallList = list.asSequence()
.filterNot { it.isVGame || shouldThisGameBeFiltered(it.id) }
.onEach { gameInstall ->
val ghId = PackageUtils.getMetaData(getApplication(), gameInstall.packageName, "gh_id")
if (ghId != null && ghId != gameInstall.id) {
gameInstall.id = ghId.toString()
} else {
val gameId = getGameId(gameInstall.packageName)
if (!TextUtils.isEmpty(gameId)) {
gameInstall.id = gameId
}
}
GameInstall.updateRecentlyPlayedTime(gameInstall)
}
.toList()
allGameInstallList.clear()
allGameInstallList.addAll(simulatorGameInstallList)
allGameInstallList.addAll(normalGameInstallList)
if (allGameInstallList.isNotEmpty()) {
loadGamesData(allGameInstallList.mapNotNull { it.id }.toSet())
} else {
itemDataListLiveData.postValue(emptyList())
}
}
@SuppressLint("CheckResult")
private fun loadGamesData(ids: Set<String>) {
val paramsMap = mapOf(
"game_id" to ids
)
disposable = newApi.postMyHaloGame(TAB_INSTALLED, 1, ids.size, paramsMap.toRequestBody())
.map(ApkActiveUtils.filterMapperList)
.compose(singleToMain())
.subscribe({
processingData(it)
}, {
loadStatusLiveData.postValue(LoadStatus.INIT_FAILED)
})
}
private fun processingData(gameList: List<GameEntity>) {
val sortedGameList = mutableListOf<GameEntity>()
for (entity in gameList) {
val newEntity = entity.copy()
// 下载管理不显示镜像游戏,不然会有奇怪的问题
if (entity.shouldUseMirrorInfo()) {
continue
}
val gameInstallList = allGameInstallList.filter { it.id == entity.id }
if (gameInstallList.isEmpty()) {
continue
}
if (gameInstallList.size > 1) {
val gameInstall = gameInstallList.maxByOrNull { it.recentlyPlayedTime }
newEntity.packageInstallTime = gameInstall?.installTime ?: 0L
newEntity.packageLastUpdateTime = gameInstall?.lastUpdateTime ?: 0L
newEntity.lastUsedTime = gameInstall?.lastUsedTime ?: 0L
} else {
newEntity.packageInstallTime = gameInstallList.first().installTime
newEntity.packageLastUpdateTime = gameInstallList.first().lastUpdateTime
newEntity.lastUsedTime = gameInstallList.first().lastUsedTime
}
if (entity.getApk().size > 1) {
val packageNameSet = gameInstallList.map { it.packageName }.toSet()
newEntity.setApk(entity.getApk().filter { packageNameSet.contains(it.packageName) }.toArrayList())
newEntity.dropOtherApk()
newEntity.getApk().forEach { apkEntity ->
if (newEntity.pluggableCollection == null && PackageUtils.isCanPluggable(apkEntity)) {
val pluggableCollection =
GameUtils.getPluggableCollectionFromGameEntity(entity, apkEntity.packageName)
if (pluggableCollection != null) {
newEntity.pluggableCollection = pluggableCollection
}
}
}
newEntity.playedTime = newEntity.getApk().maxOfOrNull { it.playedTime } ?: 0L
} else if (entity.getApk().size == 1) {
newEntity.playedTime = newEntity.getApk().firstOrNull()?.playedTime ?: 0L
}
if (newEntity.getApk().size == 1 && (newEntity.isDualBtnModeEnabled() || newEntity.isVGamePreferred())) {
// 双下载按钮或畅玩优先的游戏,仅本地已安装才显示
if (!PackagesManager.isInstalled(newEntity.getUniquePackageName())) {
continue
}
newEntity.forceToBeDownloadModeOnly()
}
if (newEntity.getApk().isEmpty()) continue
sortedGameList.add(newEntity)
}
when (sortedType) {
InstalledSortType.RECENTLY_PLAYED -> sortedGameList.sortByDescending { it.recentlyPlayedTime }
InstalledSortType.LATEST_UPDATED -> sortedGameList.sortByDescending { it.updateTime }
InstalledSortType.MOST_PLAYED -> sortedGameList.sortWith(compareByDescending<GameEntity> { it.playedTime }.thenByDescending { it.packageInstallTime })
InstalledSortType.LEAST_PLAYED -> sortedGameList.sortWith(compareBy<GameEntity> { it.playedTime }.thenByDescending { it.packageInstallTime })
}
for (entity in sortedGameList) {
entity.setEntryMap(DownloadManager.getInstance().getEntryMap(entity.name))
}
sortedItemDataList.clear()
sortedItemDataList.addAll(sortedGameList.map { InstalledGameItemData(gameEntity = it) })
decorateListAndPost()
}
private fun decorateListAndPost() {
val itemDataList = mutableListOf<InstalledGameItemData>()
if (!packageUpdateList.isNullOrEmpty()) {
itemDataList.add(
InstalledGameItemData(
isHead = true,
updatableGameCount = packageUpdateList!!.size,
updatableGameList = packageUpdateList!!.take(3)
.map { it.transformGameEntity() }
)
)
}
itemDataList.addAll(sortedItemDataList)
itemDataList.add(InstalledGameItemData(isFooter = true))
itemDataListLiveData.postValue(itemDataList)
loadStatusLiveData.postValue(if (itemDataList.isEmpty()) LoadStatus.INIT_EMPTY else LoadStatus.INIT_LOADED)
}
fun updateGameUpdateData(dataList: List<GameUpdateEntity>) {
packageUpdateList = dataList
val itemDataList = itemDataListLiveData.value?.toMutableList() ?: arrayListOf()
if (itemDataList.isNotEmpty() && dataList.isNotEmpty()) {
if (itemDataList.first().isHead) itemDataList.removeAt(0)
decorateListAndPost()
}
}
override fun onCleared() {
super.onCleared()
disposable?.dispose()
}
companion object {
const val TAB_INSTALLED = "installed"
}
}

View File

@ -0,0 +1,29 @@
package com.gh.gamecenter.personal.playtime
import android.os.Bundle
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.core.utils.DisplayUtils
/**
* 游戏时长数据
*/
class PlayTimeActivity : BaseActivity() {
override fun getLayoutId(): Int {
return R.layout.activity_amway
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DisplayUtils.transparentStatusBar(this)
val containerFragment = supportFragmentManager.findFragmentByTag(
PlayTimeFragment::class.java.name
)
?: PlayTimeFragment().with(intent.extras)
supportFragmentManager.beginTransaction()
.replace(R.id.placeholder, containerFragment, PlayTimeFragment::class.java.name)
.commitAllowingStateLoss()
}
}

View File

@ -0,0 +1,88 @@
package com.gh.gamecenter.personal.playtime
import android.content.Context
import android.graphics.Typeface
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import androidx.core.view.updateLayoutParams
import androidx.core.view.updateMargins
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.common.baselist.ListAdapter
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.ItemViewType
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.viewholder.FooterViewHolder
import com.gh.gamecenter.core.utils.NumberUtils
import com.gh.gamecenter.databinding.ItemPlayTimeBinding
import com.gh.gamecenter.feature.entity.GameEntity
class PlayTimeAdapter(context: Context, private val viewModel: PlayTimeViewModel): ListAdapter<GameEntity>(context) {
private var maxWidth = context.resources.displayMetrics.widthPixels - 80F.dip2px()
private var minWidth = 32F.dip2px()
private var maxPlayTime = 0L
override fun getItemViewType(position: Int): Int = if (position == itemCount - 1) {
ItemViewType.ITEM_FOOTER
} else {
ItemViewType.GAME_NORMAL
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
when (viewType) {
ItemViewType.GAME_NORMAL -> {
PlayTimeItemViewHolder(parent.toBinding())
}
else -> {
FooterViewHolder(mLayoutInflater.inflate(com.gh.gamecenter.common.R.layout.refresh_footerview, parent, false))
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is PlayTimeItemViewHolder) {
val gameEntity = mEntityList.getOrNull(position) ?: return
holder.binding.run {
gameNameTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(mContext))
playTimeTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(mContext))
root.updateLayoutParams<MarginLayoutParams> { updateMargins(top = if (position == 0) 10F.dip2px() else 0) }
rankTv.typeface = Typeface.createFromAsset(mContext.assets, Constants.DIN_FONT_PATH)
rankTv.text = (position + 1).toString()
rankTv.setTextColor(
when (position) {
0 -> com.gh.gamecenter.common.R.color.number_1.toColor(mContext)
1 -> com.gh.gamecenter.common.R.color.number_2.toColor(mContext)
2 -> com.gh.gamecenter.common.R.color.number_3.toColor(mContext)
else -> com.gh.gamecenter.common.R.color.text_tertiary.toColor(mContext)
}
)
gameIconView.displayGameIcon(gameEntity)
gameNameTv.text = gameEntity.name
val playTime = NumberUtils.getPlayTimeFormatTime(gameEntity.playedTime)
if (position == 0) {
val playTimeTextWidth = playTimeTv.paint.measureText(playTime)
maxWidth = (mContext.resources.displayMetrics.widthPixels - 80F.dip2px() - playTimeTextWidth).toInt()
maxPlayTime = gameEntity.playedTime
}
playTimeTv.text = playTime
progressBar.updateLayoutParams<MarginLayoutParams> {
width = if (position == 0) maxWidth else (minWidth + (maxWidth - minWidth) * (gameEntity.playedTime / maxPlayTime.toFloat())).toInt()
}
progressBar.setRoundedColorBackgroundByColorInt(gameEntity.iconThemeColor.hexStringToIntColor().modifyHSV(s = 0.8F, v = 0.8F), 8F, 0.1F)
gameContainer.setOnClickListener {
GameDetailActivity.startGameDetailActivity(mContext, gameEntity.id, "游戏时长数据", null)
}
}
}
if (holder is FooterViewHolder) {
holder.initItemPadding()
holder.initFooterViewHolder(viewModel, mIsLoading, mIsNetworkError, mIsOver)
holder.hint.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(mContext))
}
}
override fun getItemCount(): Int = if (mEntityList.isEmpty()) 0 else mEntityList.size + 1
class PlayTimeItemViewHolder(var binding: ItemPlayTimeBinding) : RecyclerView.ViewHolder(binding.root)
}

View File

@ -0,0 +1,202 @@
package com.gh.gamecenter.personal.playtime
import android.content.Intent
import android.graphics.Typeface
import android.os.Build
import android.os.Bundle
import android.view.View
import android.view.ViewGroup.LayoutParams
import android.view.ViewGroup.MarginLayoutParams
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.DialogUtils
import com.gh.common.util.NewFlatLogUtils
import com.gh.common.util.UsageStatsHelper
import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.LazyListFragment
import com.gh.gamecenter.common.baselist.ListAdapter
import com.gh.gamecenter.common.callback.CancelListener
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.setSwitchAnimation
import com.gh.gamecenter.common.utils.viewModelProvider
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.utils.ClickUtils
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.core.utils.NumberUtils
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.databinding.FragmentPlayTimeBinding
import com.gh.gamecenter.feature.entity.GameEntity
import kotlin.math.abs
class PlayTimeFragment: LazyListFragment<GameEntity, PlayTimeViewModel>() {
private lateinit var binding: FragmentPlayTimeBinding
private val viewModel: PlayTimeViewModel by lazy { viewModelProvider() }
private val playTimeAdapter by lazy { PlayTimeAdapter(requireContext(), viewModel) }
private var dismissGameTimeLongDialogFlag = false
private var isCollapsed = false
override fun getStubLayoutId() = com.gh.gamecenter.common.R.layout.fragment_stub
override fun getRealLayoutId() = R.layout.fragment_play_time
override fun provideListViewModel(): PlayTimeViewModel = viewModel
override fun provideListAdapter(): ListAdapter<*> = playTimeAdapter
override fun getItemDecoration(): RecyclerView.ItemDecoration? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 提交用户使用数据
runOnIoThread {
UsageStatsHelper.checkAndPostUsageStats()
}
}
override fun onRealLayoutInflated(inflatedView: View) {
super.onRealLayoutInflated(inflatedView)
binding = FragmentPlayTimeBinding.bind(inflatedView)
}
override fun inflateRealView() {
super.inflateRealView()
binding.run {
hourDataTv.typeface = Typeface.createFromAsset(requireContext().assets, Constants.DIN_FONT_PATH)
minuteDataTv.typeface = Typeface.createFromAsset(requireContext().assets, Constants.DIN_FONT_PATH)
updatePlayTimeSetting()
// toolbar 消费 fitsSystemWindows 避免在 collapsingToolbar 下面出现多出来的 padding
// [https://stackoverflow.com/questions/48137666/viewgroup-inside-collapsingtoolbarlayout-show-extra-bottom-padding-when-set-fits]
ViewCompat.setOnApplyWindowInsetsListener(appbar) { _, insets ->
(toolbar.layoutParams as MarginLayoutParams).topMargin = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top
WindowInsetsCompat.CONSUMED
}
val statusBarHeight = DisplayUtils.getStatusBarHeight(context?.resources)
val collapsingTrigger = 66F.dip2px() + statusBarHeight
toolbar.setNavigationOnClickListener { requireActivity().finish() }
collapsingToolbar.scrimVisibleHeightTrigger = collapsingTrigger
collapsingToolbar.scrimShownAction = {
isCollapsed = it
titleTv.isVisible = it
}
titleTv.setOnClickListener {
if (ClickUtils.isFastDoubleClick(titleTv.id, 300)) {
scrollToTop()
}
}
appbar.addOnOffsetChangedListener { _, verticalOffset ->
val absOffset = abs(verticalOffset)
mListRefresh?.isEnabled = absOffset <= 2
}
}
}
override fun onFragmentResume() {
super.onFragmentResume()
DisplayUtils.setLightStatusBar(requireActivity(), !mIsDarkModeOn)
}
override fun onFragmentFirstVisible() {
super.onFragmentFirstVisible()
showUsageStatsDialogIfNeeded()
viewModel.totalPlayTimeLiveData.observe(viewLifecycleOwner) {
val hourAndMinute = NumberUtils.splitToHourAndMinute(it)
binding.hourDataTv.goneIf(hourAndMinute.first == 0L) {
binding.hourDataTv.text = hourAndMinute.first.toString()
}
binding.hourTv.goneIf(hourAndMinute.first == 0L)
binding.minuteDataTv.isVisible = true
binding.minuteTv.isVisible = true
binding.minuteDataTv.text = hourAndMinute.second.toString()
}
}
private fun showUsageStatsDialogIfNeeded() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1 &&
!UsageStatsHelper.checkForPermission() &&
SPUtils.getBoolean(Constants.SP_SHOW_USAGE_STATS_DIALOG, true)) {
showUsageStatsDialog()
SPUtils.setBoolean(Constants.SP_SHOW_USAGE_STATS_DIALOG, false)
}
}
private fun updatePlayTimeSetting() {
binding.run {
val showUsageStats = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1 && !UsageStatsHelper.checkForPermission()
playTimeSettingContainer.goneIf(!showUsageStats) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
lottieView.setSwitchAnimation(UsageStatsHelper.checkForPermission())
lottieView.setOnClickListener {
dismissGameTimeLongDialogFlag = false
if (!UsageStatsHelper.checkForPermission()) {
showUsageStatsDialog()
}
}
}
}
}
}
private fun showUsageStatsDialog() {
DialogUtils.showUsageStatsDialog(
requireContext(), {
NewFlatLogUtils.logGameTimeLongDialogClick("去开启")
dismissGameTimeLongDialogFlag = true
UsageStatsHelper.skipToUsageStats(
requireContext(),
UsageStatsHelper.USAGE_STATUS_REQUEST_CODE
)
}, object : CancelListener {
override fun onCancel() {
if (!dismissGameTimeLongDialogFlag) {
NewFlatLogUtils.logGameTimeLongDialogClick("关闭弹窗")
dismissGameTimeLongDialogFlag = true
}
}
}
)
}
private fun scrollToTop() {
val firstItemPosition = mLayoutManager.findFirstVisibleItemPosition()
if (firstItemPosition >= 10) {
mListRv?.scrollToPosition(6)
}
mListRv?.smoothScrollToPosition(0)
binding.appbar.setExpanded(true)
}
override fun onLoadDone() {
super.onLoadDone()
binding.container.updateLayoutParams<LayoutParams> { height = LayoutParams.WRAP_CONTENT }
}
override fun onLoadRefresh() {
super.onLoadRefresh()
binding.container.updateLayoutParams<LayoutParams> { height = LayoutParams.MATCH_PARENT }
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == UsageStatsHelper.USAGE_STATUS_REQUEST_CODE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
updatePlayTimeSetting()
SPUtils.setBoolean(UsageStatsHelper.USAGE_STATUS_SP_KEY, UsageStatsHelper.checkForPermission())
}
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
DisplayUtils.setLightStatusBar(requireActivity(), !mIsDarkModeOn)
}
}

View File

@ -0,0 +1,34 @@
package com.gh.gamecenter.personal.playtime
import android.app.Application
import androidx.lifecycle.MutableLiveData
import com.gh.gamecenter.common.baselist.ListViewModel
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.retrofit.RetrofitManager
import com.lightgame.utils.Utils
import io.reactivex.Observable
import io.reactivex.Single
class PlayTimeViewModel(application: Application) :
ListViewModel<GameEntity, GameEntity>(application) {
private val api = RetrofitManager.getInstance().api
val totalPlayTimeLiveData = MutableLiveData<Long>()
override fun provideDataSingle(page: Int): Single<List<GameEntity>>? {
return api.getMostPlayedGames(UserManager.getInstance().userId, Utils.getTime(getApplication()))
.map {
totalPlayTimeLiveData.postValue(it.headers()["TotalTime"]?.toLong() ?: 0L)
it.body()
}
}
override fun mergeResultLiveData() {
mResultLiveData.addSource(mListLiveData) {
mResultLiveData.postValue(it)
}
}
override fun provideDataObservable(page: Int): Observable<MutableList<GameEntity>>? = null
}

View File

@ -0,0 +1,107 @@
package com.gh.gamecenter.personal.reservation
import android.annotation.SuppressLint
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.common.utils.toRequestBody
import com.gh.gamecenter.core.provider.IPushProvider
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.livedata.Event
import com.halo.assistant.HaloApp
import com.lightgame.utils.Utils
import com.therouter.TheRouter
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import okhttp3.ResponseBody
class ManageReservationUseCase<T> {
private val repository = ReservationRepository()
private val compositeDisposable = CompositeDisposable()
@SuppressLint("CheckResult")
fun deleteOrCancelReservation(game: GameEntity, deleteReservation: Boolean, onSuccess: () -> Unit) {
val single = if (deleteReservation) {
repository.deleteReservation(game.id, game).subscribeOn(Schedulers.io())
} else {
repository.cancelReservation(game.id, game)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
single.subscribe(object : BiResponse<ResponseBody>() {
override fun onSuccess(data: ResponseBody) {
repository.refreshReservations()
onSuccess()
}
override fun onFailure(exception: Exception) {
Utils.toast(HaloApp.getInstance().application, exception.message)
exception.printStackTrace()
}
})
}
private val _enableAutoDownloadSuccessfully = MutableLiveData<Event<Boolean>>()
val enableAutoDownloadSuccessfully: LiveData<Event<Boolean>> = _enableAutoDownloadSuccessfully
fun enableAutoDownload(enable: Boolean, games: Set<GameEntity>, mapper: (ResponseBody) -> List<T>, onSuccess: (List<T>) -> Unit) {
if (enable) {
games.forEach {
SensorsBridge.trackOpenAppointmentAutomaticDownload(it.id, it.name ?: "", it.categoryChinese)
}
} else {
games.forEach {
SensorsBridge.trackCancelAppointmentAutomaticDownload(it.id, it.name ?: "", it.categoryChinese)
}
}
val params = createParamsInBatch(games).apply {
put("wifi_auto_download", enable)
}
repository.enableAutoDownloadInBatches(params.toRequestBody())
.map(mapper)
.compose(singleToMain())
.subscribe(object : BiResponse<List<T>>() {
override fun onSuccess(data: List<T>) {
onSuccess.invoke(data)
_enableAutoDownloadSuccessfully.value = Event(enable)
}
}).let(compositeDisposable::add)
}
fun cancelReserveInBatch(games: Set<GameEntity>, onSuccess: () -> Unit) {
val params = createParamsInBatch(games).apply {
put("appointment", false)
}
repository.cancelReserveInBatch(params.toRequestBody())
.subscribeOn(Schedulers.io())
.subscribe(object : BiResponse<ResponseBody>() {
override fun onSuccess(data: ResponseBody) {
repository.refreshReservations()
onSuccess()
}
}).let(compositeDisposable::add)
}
private fun createParamsInBatch(games: Set<GameEntity>): HashMap<String, Any> {
val jPushId =
(TheRouter.get(IPushProvider::class.java)
?.getRegistrationId(HaloApp.getInstance().application)) ?: ""
val idAndMirrorType = games.map {
var mirrorPosition = it.getMirrorPosition()
if (mirrorPosition == -1) {
mirrorPosition = 0
}
hashMapOf("_id" to it.id, "mirror_type" to mirrorPosition)
}
return hashMapOf("jpush_id" to jPushId, "games" to idAndMirrorType)
}
fun onCleared() {
compositeDisposable.clear()
}
}

View File

@ -0,0 +1,496 @@
package com.gh.gamecenter.personal.reservation
import android.content.Context
import android.graphics.Typeface
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
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.OnScrollListener
import com.gh.common.pop.ReserveAllSelectPop
import com.gh.common.util.CommentUtils
import com.gh.common.util.DownloadItemUtils
import com.gh.common.util.NewFlatLogUtils
import com.gh.common.util.ReservationHelper
import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.adapter.viewholder.GameViewHolder
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.callback.CancelListener
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.DrawableView
import com.gh.gamecenter.common.viewholder.FooterViewHolder
import com.gh.gamecenter.core.utils.StringUtils
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.databinding.ItemMyHaloGameBinding
import com.gh.gamecenter.databinding.ItemMyHaloReservationHeadBinding
import com.gh.gamecenter.databinding.ItemMyHaloReservationMoreBinding
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.game.GameItemViewHolder
import com.gh.gamecenter.feature.provider.IBindingAdaptersProvider
import com.gh.gamecenter.game.GameAndPosition
import com.gh.gamecenter.mygame.MyReservationFragment
import com.gh.gamecenter.personal.installed.InstalledAdapter
import com.lightgame.view.CheckableImageView
import com.therouter.TheRouter
class ReservationAdapter(private val context: Context, private val viewModel: ReservationViewModel) :
ListAdapter<ReservationGameItemData, RecyclerView.ViewHolder>(createDiffCallback()) {
private var pageState: MyReservationFragment.ReservePageState = MyReservationFragment.ReservePageState.Normal
private val isEditing: Boolean
get() = pageState != MyReservationFragment.ReservePageState.Normal
fun changeStatus(status: MyReservationFragment.ReservePageState) {
if (pageState != status) {
selectedGames.clear()
pageState = status
notifyItemRangeChanged(0, itemCount)
if (status == MyReservationFragment.ReservePageState.Normal) {
allSelectPop.dismiss()
} else {
allSelectPop.show(status, context)
}
}
}
fun notifyFooterItemOnLoadStatusChanged(isDownloadable: Boolean, loadStatus: LoadStatus) {
if (loadStatus != LoadStatus.LIST_LOADED) {
currentList.indexOfFirst { it.isFooter && it.isDownloadable == isDownloadable }.let {
if (it != -1) {
notifyItemChanged(it)
}
}
}
}
private val selectedGames = mutableSetOf<GameEntity>()
private val allSelectPop by lazy {
ReserveAllSelectPop.create(context, object : ReserveAllSelectPop.OnReserveAllSelectListener {
override fun selectAll(isChecked: Boolean): Int {
selectedGames.clear()
if (isChecked) {
if (pageState == MyReservationFragment.ReservePageState.EnableAutoDownload) {
currentList
.mapNotNull { it.gameEntity }
.filter {
it.wifiAutoDownloadEnable && !it.wifiAutoDownload
}
.let(selectedGames::addAll)
} else {
currentList.mapNotNull { it.gameEntity }.let(selectedGames::addAll)
}
}
notifyItemRangeChanged(0, currentList.size)
return selectedGames.size
}
override fun submit() {
doSubmit()
}
})
}
private fun doSubmit() {
if (pageState == MyReservationFragment.ReservePageState.EnableAutoDownload) {
allSelectPop.dismiss()
viewModel.enableAutoDownload(true, selectedGames.toSet())
viewModel.changeReserveStatus(MyReservationFragment.ReservePageState.Normal)
} else {
showCancelReservationInBatchDialog(selectedGames.toSet())
}
}
private fun showCancelReservationInBatchDialog(games: Set<GameEntity>) {
// 批量删除,埋点在外部上报
games.forEach {
SensorsBridge.trackCancelAppointmentDialogShow(it.id, it.name ?: "", it.categoryChinese)
}
ReservationHelper.showCancelReservationDialog(context, null, {
games.forEach {
SensorsBridge.trackCancelAppointmentDialogClick(it.id, it.name ?: "", it.categoryChinese, "确定取消")
}
viewModel.cancelReserveInBatch(games)
if (allSelectPop.isShowing) {
allSelectPop.dismiss()
}
viewModel.changeReserveStatus(MyReservationFragment.ReservePageState.Normal)
}, object : CancelListener {
override fun onCancel() {
games.forEach {
SensorsBridge.trackCancelAppointmentDialogClick(
it.id,
it.name ?: "",
it.categoryChinese,
"暂不取消"
)
}
}
}, dialogCancelCallback = {
games.forEach {
SensorsBridge.trackCancelAppointmentDialogClick(it.id, it.name ?: "", it.categoryChinese, "关闭弹窗")
}
})
}
private fun showCancelReservationDialog(game: GameEntity) {
NewFlatLogUtils.logMyGameReserveTabGameCardClick("按钮", game.id, game.name ?: "")
ReservationHelper.showCancelReservationDialog(context, game, {
NewFlatLogUtils.logMyGameCancelReserveDialogClick("确定取消", game.id, game.name ?: "")
viewModel.cancelReservation(game)
}, object : CancelListener {
override fun onCancel() {
NewFlatLogUtils.logMyGameCancelReserveDialogClick("关闭弹窗", game.id, game.name ?: "")
}
})
}
override fun getItemViewType(position: Int): Int {
val itemData = currentList.getOrNull(position) ?: return ITEM_NORMAL
return when {
itemData.isHead -> ITEM_HEAD
itemData.isMore -> ITEM_MORE
itemData.isFooter -> ITEM_FOOTER
else -> ITEM_NORMAL
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
when (viewType) {
ITEM_HEAD -> {
ReservationHeadItemViewHolder(parent.toBinding())
}
ITEM_MORE -> {
ReservationMoreItemViewHolder(parent.toBinding())
}
ITEM_FOOTER -> {
FooterViewHolder(
parent.layoutInflater.inflate(
com.gh.gamecenter.common.R.layout.refresh_footerview,
parent,
false
)
)
}
else -> {
ReservationGameItemViewHolder(parent.toBinding())
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val itemData = currentList.getOrNull(position) ?: return
if (holder is ReservationHeadItemViewHolder) {
holder.binding.run {
root.background =
com.gh.gamecenter.common.R.drawable.background_shape_white_radius_12_top_only.toDrawable(context)
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
countTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
countTv.typeface = Typeface.createFromAsset(context.assets, Constants.DIN_FONT_PATH)
titleTv.text = itemData.title
countTv.text = itemData.gameCount.toString()
}
}
if (holder is ReservationMoreItemViewHolder) {
holder.binding.run {
root.background =
com.gh.gamecenter.common.R.drawable.ui_surface_radius_12_bottom_only_item_style.toDrawable(context)
divider.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_divider.toColor(context))
moreTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
moreTv.setDrawableEnd(if (viewModel.isDownloadableExpand) R.drawable.ic_auxiliary_arrow_up_8 else R.drawable.ic_auxiliary_arrow_down_8)
moreTv.text = if (viewModel.isDownloadableExpand) "收起" else "查看全部"
root.setOnClickListener {
viewModel.onMoreClick()
}
}
}
if (holder is ReservationGameItemViewHolder) {
val gameEntity = itemData.gameEntity ?: return
holder.binding.run {
multiVersionContainer.isVisible = false
root.updateLayoutParams<MarginLayoutParams> {
leftMargin = 8F.dip2px()
rightMargin = 8F.dip2px()
}
root.background = if (itemData.roundBottomOnly) {
com.gh.gamecenter.common.R.drawable.ui_surface_radius_12_bottom_only_item_style.toDrawable(context)
} else {
com.gh.gamecenter.common.R.drawable.reuse_listview_item_style.toDrawable(root.context)
}
gameInfo.run {
tvSpeed.isVisible = false
arrowIv.isVisible = false
zoneDivider.isVisible = false
gameNameTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(root.context))
val provider = TheRouter.get(IBindingAdaptersProvider::class.java)
provider?.setGameName(gameNameTv, gameEntity, false)
gameIconView.displayGameIcon(gameEntity)
GameItemViewHolder.initGameSubtitleAndAdLabel(gameEntity, gameSubtitleTv)
gameDesTv.text = "${CommentUtils.getCommentTime(gameEntity.reserveTime)} 预约"
wifiAutoDownloadTv.goneIf(!gameEntity.wifiAutoDownload)
tagTv.goneIf(gameEntity.wifiAutoDownload) {
tagTv.text = gameEntity.tagStyle.take(3).joinToString("·") { it.name }
}
selectIv.isVisible = isEditing
updateSelectedStatus(selectIv, gameEntity)
downloadBtn.isVisible = !isEditing
moreIv.isVisible = !isEditing
moreIv.setOnClickListener {
ReservationManageDialogFragment.show(
context,
gameEntity,
onWifiAutoDownloadClickListener = {
viewModel.enableAutoDownload(it, setOf(gameEntity))
},
onCancelReservationClickListener = {
showCancelReservationDialog(gameEntity)
})
}
if (!isEditing) {
DownloadItemUtils.updateItemWithReserveStatus(
GameViewHolder(root).also {
it.gameDownloadBtn = downloadBtn
it.gameDownloadTips = downloadTipsLottie
it.multiVersionDownloadTv = multiVersionDownloadTv
},
gameEntity
)
}
if ("appointment" == gameEntity.reserveStatus) {
downloadBtn.setOnClickListener {
showCancelReservationDialog(gameEntity)
}
} else {
DownloadItemUtils.setOnClickListener(
context,
downloadBtn,
gameEntity,
holder.bindingAdapterPosition,
this@ReservationAdapter,
"我的光环-预约",
location = StringUtils.buildString("我的光环-预约", ":", gameEntity.name),
traceEvent = null,
clickCallback = {
SensorsBridge.trackHaloSelfGameAppointmentClick(
gameEntity.id,
gameEntity.name ?: "",
gameEntity.categoryChinese,
"按钮",
downloadBtn.text,
)
}
)
}
}
InstalledAdapter.bindAnnouncement(InstalledAdapter.TYPE_RESERVATION, this, gameEntity, -1)
InstalledAdapter.bindService(InstalledAdapter.TYPE_RESERVATION, this, gameEntity, -1, "我的光环-预约")
root.setOnClickListener {
if (pageState == MyReservationFragment.ReservePageState.Normal) {
GameDetailActivity.startGameDetailActivity(context, gameEntity.id, "我的光环-预约", null)
SensorsBridge.trackHaloSelfGameAppointmentClick(
gameEntity.id,
gameEntity.name ?: "",
gameEntity.categoryChinese,
"游戏",
"游戏",
)
} else {
val isEnableSelect = pageState == MyReservationFragment.ReservePageState.CancelReserve
|| (gameEntity.wifiAutoDownloadEnable && !gameEntity.wifiAutoDownload)
if (isEnableSelect) { // 可勾选状态
if (selectIv.isEnabled) {
selectIv.toggle()
if (selectIv.isChecked) {
selectedGames.add(gameEntity)
} else {
selectedGames.remove(gameEntity)
}
updateSelectedStatus(selectIv, gameEntity)
allSelectPop.updateSelectedCount(selectedGames.size)
}
} else { // 不可勾选状态,弹出相应的提示
val toastResId = if (gameEntity.wifiAutoDownload) {
// 已经开启wifi自动下载
R.string.has_enabled_for_auto_download_in_wifi
} else {
R.string.not_support_auto_download_in_wifi
}
ToastUtils.showToast(toastResId.toResString())
}
}
}
}
holder.itemView.tag = gameEntity.id
}
if (holder is FooterViewHolder) {
val loadStatus =
if (itemData.isDownloadable) viewModel.downloadableLoadStatus.value else viewModel.reservableLoadStatus.value
holder.initItemPadding()
holder.initFooterViewHolder(
loadStatus == LoadStatus.LIST_LOADING,
loadStatus == LoadStatus.LIST_FAILED,
loadStatus == LoadStatus.LIST_OVER
) {
if (itemData.isDownloadable && viewModel.downloadableLoadStatus.value == LoadStatus.LIST_FAILED) {
viewModel.onDownloadableLoadMore()
} else if (viewModel.reservableLoadStatus.value == LoadStatus.LIST_FAILED) {
viewModel.onReservableLoadMore()
}
}
}
}
private fun updateSelectedStatus(selectIv: CheckableImageView, gameEntity: GameEntity) {
if (pageState == MyReservationFragment.ReservePageState.CancelReserve) {
selectIv.isChecked = selectedGames.any { gameEntity.id == it.id }
val isChecked = selectedGames.any { gameEntity.id == it.id }
if (isChecked) {
selectIv.setImageResource(com.gh.gamecenter.common.R.drawable.ic_selector_selected)
} else {
selectIv.setImageResource(com.gh.gamecenter.common.R.drawable.ic_selector_default)
}
} else { // 批量开启wifi自动下载
when {
!gameEntity.wifiAutoDownloadEnable -> { // 此游戏不支持wifi自动下载不可勾选状态
selectIv.setImageResource(com.gh.gamecenter.common.R.drawable.ic_selector_unable)
}
gameEntity.wifiAutoDownload -> {// 已经开启了wifi自动下载勾选-不可编辑状态
selectIv.setImageResource(com.gh.gamecenter.common.R.drawable.ic_selector_selected_unable)
}
else -> { // 默认状态,可勾选
selectIv.setImageDrawable(DrawableView.getCheckSelectorDrawable(context))
selectIv.isChecked = selectedGames.any { gameEntity.id == it.id }
}
}
}
}
private val onLoadMoreListener = object : OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
autoLoadMore(recyclerView)
}
}
private fun autoLoadMore(recyclerView: RecyclerView) {
val layoutManger = recyclerView.layoutManager
if (layoutManger is LinearLayoutManager) {
val lastVisibleItemPosition = layoutManger.findLastVisibleItemPosition()
val lastVisibleItemData = currentList.getOrNull(lastVisibleItemPosition)
if (lastVisibleItemData?.isFooter == true) {
val loadStatus =
if (lastVisibleItemData.isDownloadable) viewModel.downloadableLoadStatus.value else viewModel.reservableLoadStatus.value
if (lastVisibleItemData.isDownloadable &&
loadStatus != LoadStatus.LIST_OVER &&
loadStatus != LoadStatus.LIST_LOADING
) {
viewModel.onDownloadableLoadMore()
} else if (loadStatus != LoadStatus.LIST_OVER &&
loadStatus != LoadStatus.LIST_LOADING
) {
viewModel.onReservableLoadMore()
}
}
if (lastVisibleItemData?.isDownloadable == false) {
val loadStatus = viewModel.downloadableLoadStatus.value
if (loadStatus != LoadStatus.INIT_EMPTY &&
loadStatus != LoadStatus.LIST_FAILED &&
loadStatus != LoadStatus.LIST_OVER &&
loadStatus != LoadStatus.LIST_LOADING
) {
viewModel.onDownloadableLoadMore()
}
}
}
}
fun submitList(recyclerView: RecyclerView, list: List<ReservationGameItemData>) {
submitList(list) {
autoLoadMore(recyclerView)
}
}
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
recyclerView.addOnScrollListener(onLoadMoreListener)
}
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
super.onDetachedFromRecyclerView(recyclerView)
recyclerView.removeOnScrollListener(onLoadMoreListener)
}
fun notifyItemAndRemoveDownload(status: EBDownloadStatus) {
val data = getGameEntityByPackage(status.packageName)
for (gameAndPosition in data) {
if (gameAndPosition.entity != null && gameAndPosition.entity.name == status.name) {
gameAndPosition.entity.getEntryMap().remove(status.platform)
}
notifyItemChanged(gameAndPosition.position)
}
}
fun getGameEntityByPackage(packageName: String): List<GameAndPosition> {
val positionList = ArrayList<GameAndPosition>()
val positionMap = viewModel.positionAndPackageMap
for (key in positionMap.keys) {
if (key.contains(packageName)) {
val position = positionMap[key]!!
val game = currentList.getOrNull(position)?.gameEntity
if (game != null) {
positionList.add(GameAndPosition(game, position))
}
}
}
return positionList
}
class ReservationHeadItemViewHolder(val binding: ItemMyHaloReservationHeadBinding) :
RecyclerView.ViewHolder(binding.root)
class ReservationGameItemViewHolder(val binding: ItemMyHaloGameBinding) : RecyclerView.ViewHolder(binding.root)
class ReservationMoreItemViewHolder(val binding: ItemMyHaloReservationMoreBinding) :
RecyclerView.ViewHolder(binding.root)
companion object {
const val ITEM_HEAD = 0
const val ITEM_NORMAL = 1
const val ITEM_MORE = 2
const val ITEM_FOOTER = 3
fun createDiffCallback() = object : ItemCallback<ReservationGameItemData>() {
override fun areItemsTheSame(oldItem: ReservationGameItemData, newItem: ReservationGameItemData): Boolean {
return oldItem.areItemsTheSame(newItem)
}
override fun areContentsTheSame(
oldItem: ReservationGameItemData,
newItem: ReservationGameItemData
): Boolean {
return oldItem.areContentsTheSame(newItem)
}
}
}
}

View File

@ -0,0 +1,48 @@
package com.gh.gamecenter.personal.reservation
import android.content.Context
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.gh.gamecenter.common.base.fragment.BaseBottomDialogFragment
import com.gh.gamecenter.core.utils.CurrentActivityHolder
import com.gh.gamecenter.databinding.DialogReservationBatchManageBinding
class ReservationBatchManageDialogFragment : BaseBottomDialogFragment<DialogReservationBatchManageBinding>() {
private var onEnableWifiAutoDownloadClickListener: (() -> Unit)? = null
private var onCancelReservationClickListener: (() -> Unit)? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mBinding.enableWifiAutoDownloadTv.setOnClickListener {
dismissAllowingStateLoss()
onEnableWifiAutoDownloadClickListener?.invoke()
}
mBinding.cancelReservationTv.setOnClickListener {
dismissAllowingStateLoss()
onCancelReservationClickListener?.invoke()
}
mBinding.cancelTv.setOnClickListener {
dismissAllowingStateLoss()
}
}
companion object {
fun show(
context: Context,
onEnableWifiAutoDownloadClickListener: () -> Unit,
onCancelReservationClickListener: () -> Unit,
) {
if (context is AppCompatActivity) {
context.supportFragmentManager
} else {
(CurrentActivityHolder.getCurrentActivity() as? AppCompatActivity)?.supportFragmentManager
}?.let {
val fragment = ReservationBatchManageDialogFragment()
fragment.onEnableWifiAutoDownloadClickListener = onEnableWifiAutoDownloadClickListener
fragment.onCancelReservationClickListener = onCancelReservationClickListener
fragment.show(it, fragment::class.java.simpleName)
}
}
}
}

View File

@ -0,0 +1,167 @@
package com.gh.gamecenter.personal.reservation
import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.LiveData
import androidx.recyclerview.widget.LinearLayoutManager
import com.gh.common.iinterface.IBatchManage
import com.gh.common.util.CheckLoginUtils
import com.gh.download.DownloadManager
import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.eventbus.EBReuse
import com.gh.gamecenter.common.utils.toResString
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.livedata.EventObserver
import com.gh.gamecenter.mygame.MyReservationFragment
import com.gh.gamecenter.personal.MyHaloBaseListFragment
import com.gh.gamecenter.personal.MyHaloViewModel
import com.lightgame.download.DataWatcher
import com.lightgame.download.DownloadEntity
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class ReservationFragment: MyHaloBaseListFragment(), IBatchManage {
private val viewModel by viewModels<ReservationViewModel>()
private val myHaloViewModel by activityViewModels<MyHaloViewModel>()
private val reservationAdapter by lazy { ReservationAdapter(requireContext(), viewModel) }
private val dataWatcher = object : DataWatcher() {
override fun onDataChanged(downloadEntity: DownloadEntity) {
val data = reservationAdapter.getGameEntityByPackage(downloadEntity.packageName)
for (gameAndPosition in data) {
if (gameAndPosition.entity != null && gameAndPosition.entity.name == downloadEntity.name) {
val entryMap = gameAndPosition.entity.getEntryMap()
entryMap[downloadEntity.platform] = downloadEntity
}
reservationAdapter.notifyItemChanged(gameAndPosition.position)
}
}
}
override fun onFragmentFirstVisible() {
super.onFragmentFirstVisible()
onLoadRefresh()
observeData()
binding.listRv.run {
itemAnimator = null
layoutManager = LinearLayoutManager(requireContext())
adapter = reservationAdapter
}
}
override fun onFragmentResume() {
super.onFragmentResume()
DownloadManager.getInstance().addObserver(dataWatcher)
}
override fun onFragmentPause() {
super.onFragmentPause()
DownloadManager.getInstance().removeObserver(dataWatcher)
}
override fun provideLoadStatusLiveData(): LiveData<LoadStatus> = viewModel.loadStatus
override fun onLoadEmpty() {
super.onLoadEmpty()
updateNoDataView()
}
private fun updateNoDataView() {
binding.reuseNoneData.run {
if (!CheckLoginUtils.isLogin()) {
reuseNoneDataTv.text = "登录后可查看预约游戏"
reuseResetLoadTv.isVisible = true
reuseResetLoadTv.text = "去登录"
reuseResetLoadTv.setOnClickListener {
CheckLoginUtils.checkLogin(requireContext(), "我的光环-预约") {}
}
} else {
reuseNoneDataTv.text = com.gh.gamecenter.common.R.string.game_empty.toResString()
reuseResetLoadTv.isVisible = false
}
}
}
private fun observeData() {
viewModel.itemDataListLiveData.observe(viewLifecycleOwner) {
reservationAdapter.submitList(binding.listRv, it)
}
viewModel.downloadableLoadStatus.observe(viewLifecycleOwner) {
reservationAdapter.notifyFooterItemOnLoadStatusChanged(true, it)
}
viewModel.reservableLoadStatus.observe(viewLifecycleOwner) {
reservationAdapter.notifyFooterItemOnLoadStatusChanged(false, it)
}
myHaloViewModel.managementStateLiveData.observe(viewLifecycleOwner) {
if (isSupportVisible && !it) {
reservationAdapter.changeStatus(MyReservationFragment.ReservePageState.Normal)
}
}
viewModel.enableAutoDownloadSuccessfully.observe(viewLifecycleOwner, EventObserver {
if (it) {
ToastUtils.showToast(R.string.has_enabled_for_auto_download_in_wifi.toResString())
} else {
ToastUtils.showToast(R.string.has_unable_for_auto_download_in_wifi.toResString())
}
})
viewModel.changePageStateAction.observe(viewLifecycleOwner, EventObserver {
if (it == MyReservationFragment.ReservePageState.Normal && myHaloViewModel.managementStateLiveData.value == true) {
myHaloViewModel.changeManagementState()
}
})
}
override fun refresh() {
if (CheckLoginUtils.isLogin()) {
viewModel.initData()
} else {
onLoadEmpty()
}
}
override fun onManageClick() {
ReservationBatchManageDialogFragment.show(requireContext(), {
reservationAdapter.changeStatus(MyReservationFragment.ReservePageState.EnableAutoDownload)
binding.listRv.requestLayout()
myHaloViewModel.changeManagementState()
}, {
reservationAdapter.changeStatus(MyReservationFragment.ReservePageState.CancelReserve)
binding.listRv.requestLayout()
myHaloViewModel.changeManagementState()
})
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(reuse: EBReuse) {
if (reuse.type == Constants.LOGIN_TAG || reuse.type == Constants.LOGOUT_TAG) {
onLoadRefresh()
}
}
//安装、卸载事件
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(busFour: EBPackage) {
if (busFour.isInstalledOrUninstalled()) {
reservationAdapter.notifyItemRangeChanged(0, reservationAdapter.itemCount)
}
}
//下载被删除事件
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(status: EBDownloadStatus) {
if ("delete" == status.status) {
reservationAdapter.notifyItemAndRemoveDownload(status)
}
}
}

View File

@ -0,0 +1,26 @@
package com.gh.gamecenter.personal.reservation
import com.gh.gamecenter.feature.entity.GameEntity
data class ReservationGameItemData(
var gameEntity: GameEntity? = null,
var isHead: Boolean = false,
var title: String = "",
var gameCount: Int = 0,
var isMore: Boolean = false,
var isFooter: Boolean = false,
var isDownloadable: Boolean = false,
var roundBottomOnly: Boolean = false,
) {
private val isGame
get() = gameEntity != null
fun areItemsTheSame(other: ReservationGameItemData) =
isHead == other.isHead && isFooter == other.isFooter && isMore == other.isMore && isGame == other.isGame
fun areContentsTheSame(other: ReservationGameItemData) = when {
isHead -> title == other.title && gameCount == other.gameCount
isGame -> gameEntity == other.gameEntity && roundBottomOnly == other.roundBottomOnly
else -> false
}
}

View File

@ -0,0 +1,71 @@
package com.gh.gamecenter.personal.reservation
import android.content.Context
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf
import androidx.core.view.isVisible
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.BaseBottomDialogFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.toResString
import com.gh.gamecenter.core.utils.CurrentActivityHolder
import com.gh.gamecenter.databinding.DialogReservationManageBinding
import com.gh.gamecenter.feature.entity.GameEntity
class ReservationManageDialogFragment : BaseBottomDialogFragment<DialogReservationManageBinding>() {
private var gameEntity: GameEntity? = null
private var onWifiAutoDownloadClickListener: ((Boolean) -> Unit)? = null
private var onCancelReservationClickListener: (() -> Unit)? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
gameEntity = arguments?.getParcelable(EntranceConsts.KEY_GAME_ENTITY)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
gameEntity?.let { mBinding.gameIconView.displayGameIcon(it) }
mBinding.gameNameTv.text = gameEntity?.name
mBinding.wifiAutoDownloadTv.isVisible = gameEntity?.wifiAutoDownloadEnable == true
mBinding.wifiAutoDownloadTv.text =
if (gameEntity?.wifiAutoDownload == true) R.string.cancel_auto_download_with_wifi.toResString() else R.string.enable_automatic_downloading_with_wifi.toResString()
mBinding.wifiAutoDownloadTv.setOnClickListener {
dismissAllowingStateLoss()
onWifiAutoDownloadClickListener?.invoke(gameEntity?.wifiAutoDownload == false)
}
mBinding.cancelReservationTv.setOnClickListener {
dismissAllowingStateLoss()
onCancelReservationClickListener?.invoke()
}
mBinding.cancelTv.setOnClickListener {
dismissAllowingStateLoss()
}
}
companion object {
fun show(
context: Context,
gameEntity: GameEntity,
onWifiAutoDownloadClickListener: (Boolean) -> Unit,
onCancelReservationClickListener: () -> Unit,
) {
if (context is AppCompatActivity) {
context.supportFragmentManager
} else {
(CurrentActivityHolder.getCurrentActivity() as? AppCompatActivity)?.supportFragmentManager
}?.let {
val fragment = ReservationManageDialogFragment().apply {
arguments = bundleOf(
EntranceConsts.KEY_GAME_ENTITY to gameEntity
)
}
fragment.onWifiAutoDownloadClickListener = onWifiAutoDownloadClickListener
fragment.onCancelReservationClickListener = onCancelReservationClickListener
fragment.show(it, fragment::class.java.simpleName)
}
}
}
}

View File

@ -0,0 +1,43 @@
package com.gh.gamecenter.personal.reservation
import com.gh.common.repository.ReservationRepository
import com.gh.common.util.ReservationHelper
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.retrofit.RetrofitManager
import com.halo.assistant.HaloApp
import com.halo.assistant.fragment.reserve.ReserveReminderRepository
import io.reactivex.Single
import okhttp3.RequestBody
import okhttp3.ResponseBody
class ReservationRepository {
private val newApi = RetrofitManager.getInstance().newApi
private val reminderRepository by lazy { ReserveReminderRepository.newInstance() }
fun deleteReservation(gameId: String, game: GameEntity): Single<ResponseBody> {
return newApi.deleteGameReservation(
gameId,
ReservationHelper.getReserveRequestBody(game, HaloApp.getInstance().application)
)
}
fun cancelReservation(gameId: String, game: GameEntity): Single<ResponseBody> {
return newApi.cancelGameReservation(
gameId,
ReservationHelper.getReserveRequestBody(game, HaloApp.getInstance().application)
)
}
fun enableAutoDownloadInBatches(params: RequestBody): Single<ResponseBody> {
return reminderRepository.enableAutoDownloadInBatches(params)
}
fun cancelReserveInBatch(params: RequestBody): Single<ResponseBody> {
return reminderRepository.cancelReserveInBatch(params)
}
fun refreshReservations() {
ReservationRepository.refreshReservations()
}
}

View File

@ -0,0 +1,307 @@
package com.gh.gamecenter.personal.reservation
import android.annotation.SuppressLint
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import com.gh.common.filter.RegionSettingHelper
import com.gh.download.DownloadManager
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.utils.ApkActiveUtils
import com.gh.gamecenter.livedata.Event
import com.gh.gamecenter.mygame.MyReservationFragment
import com.gh.gamecenter.retrofit.RetrofitManager
import com.lightgame.utils.Utils
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import java.util.concurrent.atomic.AtomicInteger
class ReservationViewModel(application: Application): AndroidViewModel(application) {
private val api = RetrofitManager.getInstance().newApi
var isDownloadableExpand = false
private val downloadableTotal = AtomicInteger(0)
private val reservableTotal = AtomicInteger(0)
private var downloadablePage = 1
private var reservablePage = 1
// 记录地区屏蔽游戏数量
private val downloadableRemovedGameCount = AtomicInteger(0)
private val reservableRemovedGameCount = AtomicInteger(0)
private val isDownloadableFirstPage
get() = downloadablePage == 1
private val isReservableFirstPage
get() = reservablePage == 1
val itemDataListLiveData = MutableLiveData<List<ReservationGameItemData>>()
private val downloadableItemDataList = mutableListOf<ReservationGameItemData>()
private val reservableItemDataList = mutableListOf<ReservationGameItemData>()
val downloadableLoadStatus = MutableLiveData<LoadStatus>()
val reservableLoadStatus = MutableLiveData<LoadStatus>()
val loadStatus = MediatorLiveData<LoadStatus>().apply {
fun determineLoadStatus(source: LoadStatus?, other: LoadStatus?) = when {
source == LoadStatus.INIT_FAILED && other == LoadStatus.INIT_FAILED -> LoadStatus.INIT_FAILED
source == LoadStatus.INIT_EMPTY && other == LoadStatus.INIT_EMPTY -> LoadStatus.INIT_EMPTY
else -> LoadStatus.INIT_LOADED
}
addSource(downloadableLoadStatus) {
val finalLoadStatus = determineLoadStatus(it, reservableLoadStatus.value)
if (finalLoadStatus != value) value = finalLoadStatus
}
addSource(reservableLoadStatus) {
val finalLoadStatus = determineLoadStatus(it, downloadableLoadStatus.value)
if (finalLoadStatus != value) value = finalLoadStatus
}
}
var positionAndPackageMap = HashMap<String, Int>() // key: packageName + position, value: position
private val compositeDisposable = CompositeDisposable()
private val manageReservationUseCase = ManageReservationUseCase<ReservationGameItemData>()
fun initData() {
loadStatus.value = LoadStatus.INIT
resetPageData()
loadFirst()
}
private fun resetPageData() {
isDownloadableExpand = false
downloadableTotal.set(0)
reservableTotal.set(0)
downloadablePage = 1
reservablePage = 1
downloadableRemovedGameCount.set(0)
reservableRemovedGameCount.set(0)
downloadableItemDataList.clear()
reservableItemDataList.clear()
}
private fun createRequest(
status: String,
page: Int,
total: AtomicInteger,
removedCount: AtomicInteger,
itemDataList: MutableList<ReservationGameItemData>
): Single<List<GameEntity>> = api.getMyHaloReservation(status, page, PAGE_SIZE, Utils.getTime(getApplication()))
.subscribeOn(Schedulers.io())
.map {
total.set(it.headers()["Total"]?.toInt() ?: 0)
it.body()
}
.map {
val filterGameList = RegionSettingHelper.filterGame(it)
removedCount.addAndGet(it.size - filterGameList.size)
total.addAndGet(-removedCount.get())
filterGameList
}
.map(ApkActiveUtils.filterMapperList)
.observeOn(AndroidSchedulers.mainThread())
.doOnSuccess { gameList ->
itemDataList.addAll(gameList.map {
ReservationGameItemData(
gameEntity = it,
isDownloadable = status == STATUS_DOWNLOAD
)
})
updateLoadStatus(status, gameList.size)
}
.doOnError {
updateLoadStatus(status, FAILURE_SIZE)
}
.onErrorReturn { emptyList() }
private val downloadableRequest
get() = createRequest(STATUS_DOWNLOAD, downloadablePage, downloadableTotal, downloadableRemovedGameCount, downloadableItemDataList)
private val reservableRequest
get() = createRequest(STATUS_RESERVATION, reservablePage, reservableTotal, reservableRemovedGameCount, reservableItemDataList)
@SuppressLint("CheckResult")
fun loadFirst() {
Single.zip(downloadableRequest, reservableRequest) { _, _ -> }
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
decorateListAndPost()
}, {
it.printStackTrace()
}).let(compositeDisposable::add)
}
@SuppressLint("CheckResult")
private fun loadDownloadableGame() {
downloadableRequest
.subscribe({
decorateListAndPost()
}, {
it.printStackTrace()
}).let(compositeDisposable::add)
}
@SuppressLint("CheckResult")
private fun loadReservableGame() {
reservableRequest
.subscribe({
decorateListAndPost()
}, {
it.printStackTrace()
}).let(compositeDisposable::add)
}
fun onDownloadableLoadMore() {
if (isDownloadableExpand && downloadableLoadStatus.value != LoadStatus.LIST_OVER) {
downloadableLoadStatus.value = LoadStatus.LIST_LOADING
loadDownloadableGame()
}
}
fun onReservableLoadMore() {
if (reservableLoadStatus.value != LoadStatus.LIST_OVER) {
reservableLoadStatus.value = LoadStatus.LIST_LOADING
loadReservableGame()
}
}
fun onMoreClick() {
isDownloadableExpand = !isDownloadableExpand
decorateListAndPost()
}
private fun updateLoadStatus(status: String, size: Int) {
val loadStatusLiveData = if (status == STATUS_DOWNLOAD) downloadableLoadStatus else reservableLoadStatus
val isFirstPage = if (status == STATUS_DOWNLOAD) isDownloadableFirstPage else isReservableFirstPage
if (size == 0) {
loadStatusLiveData.value = if (isFirstPage) LoadStatus.INIT_EMPTY else LoadStatus.LIST_OVER
} else if (size == FAILURE_SIZE) {
loadStatusLiveData.value = if (isFirstPage) LoadStatus.INIT_FAILED else LoadStatus.LIST_FAILED
} else {
loadStatusLiveData.value = if (isFirstPage) LoadStatus.INIT_LOADED else LoadStatus.LIST_LOADED
if (status == STATUS_DOWNLOAD) {
downloadablePage++
} else {
reservablePage++
}
}
}
private fun decorateListAndPost() {
val itemDataList = mutableListOf<ReservationGameItemData>()
if (downloadableItemDataList.isNotEmpty()) {
itemDataList.add(ReservationGameItemData(isHead = true, title = DOWNLOADABLE_TITLE, gameCount = downloadableTotal.get(), isDownloadable = true))
if (isDownloadableExpand) {
itemDataList.addAll(downloadableItemDataList)
itemDataList.add(ReservationGameItemData(isMore = true, roundBottomOnly = true))
if (downloadableLoadStatus.value != LoadStatus.LIST_OVER || reservableItemDataList.isEmpty()) {
itemDataList.add(ReservationGameItemData(isFooter = true, isDownloadable = true))
}
} else {
itemDataList.add(downloadableItemDataList.first())
itemDataList.add(ReservationGameItemData(isMore = true, roundBottomOnly = true))
}
}
if (reservableItemDataList.isNotEmpty()) {
itemDataList.add(ReservationGameItemData(isHead = true, title = RESERVABLE_TITLE, gameCount = reservableTotal.get(), isDownloadable = false))
itemDataList.addAll(reservableItemDataList.onEachIndexed { index, itemData ->
itemData.roundBottomOnly = index == reservableItemDataList.size - 1
})
itemDataList.add(ReservationGameItemData(isFooter = true, isDownloadable = false))
}
positionAndPackageMap.clear()
itemDataList.forEachIndexed { index, itemData ->
itemData.gameEntity?.let {
addGamePositionAndPackage(it, index)
}
}
itemDataListLiveData.postValue(itemDataList)
}
private fun addGamePositionAndPackage(game: GameEntity, position: Int) {
var packages = ""
for (apkEntity in game.getApk()) {
packages += apkEntity.packageName
}
packages += game.simulator?.apk?.packageName
positionAndPackageMap[packages + position] = position
game.setEntryMap(DownloadManager.getInstance().getEntryMap(game.name))
}
fun cancelReservation(game: GameEntity) {
manageReservationUseCase.deleteOrCancelReservation(game, false) {
downloadableItemDataList.removeAll { game.id == it.gameEntity?.id }
reservableItemDataList.removeAll { game.id == it.gameEntity?.id }
decorateListAndPost()
}
}
val enableAutoDownloadSuccessfully: LiveData<Event<Boolean>> = manageReservationUseCase.enableAutoDownloadSuccessfully
fun enableAutoDownload(enable: Boolean, games: Set<GameEntity>) {
manageReservationUseCase.enableAutoDownload(enable, games, {
val gameIds = games.map { it.id }.toSet()
val updateFn: (ReservationGameItemData) -> (ReservationGameItemData) = { item ->
item.gameEntity?.takeIf { it.id in gameIds }?.let { game ->
item.copy(gameEntity = game.copy(_wifiAutoDownload = enable))
} ?: item
}
updateListItems(downloadableItemDataList, updateFn)
updateListItems(reservableItemDataList, updateFn)
itemDataListLiveData.value?.map(updateFn) ?: emptyList()
}) { data ->
itemDataListLiveData.postValue(data)
}
}
private fun updateListItems(
list: MutableList<ReservationGameItemData>,
updateFn: (ReservationGameItemData) -> ReservationGameItemData
) {
val iterator = list.listIterator()
while (iterator.hasNext()) {
val item = iterator.next()
iterator.set(updateFn(item))
}
}
fun cancelReserveInBatch(games: Set<GameEntity>) {
manageReservationUseCase.cancelReserveInBatch(games) {
downloadableItemDataList.removeAll { games.any { game -> game.id == it.gameEntity?.id } }
reservableItemDataList.removeAll { games.any { game -> game.id == it.gameEntity?.id } }
decorateListAndPost()
}
}
private val _changePageStateAction = MutableLiveData<Event<MyReservationFragment.ReservePageState>>()
val changePageStateAction: LiveData<Event<MyReservationFragment.ReservePageState>> = _changePageStateAction
fun changeReserveStatus(state: MyReservationFragment.ReservePageState) {
_changePageStateAction.value = Event(state)
}
override fun onCleared() {
super.onCleared()
compositeDisposable.clear()
manageReservationUseCase.onCleared()
}
companion object {
const val PAGE_SIZE = 20
const val FAILURE_SIZE = -1
const val STATUS_DOWNLOAD = "download"
const val STATUS_RESERVATION = "appointment"
const val DOWNLOADABLE_TITLE = "可下载"
const val RESERVABLE_TITLE = "即将上线"
}
}

View File

@ -0,0 +1,531 @@
package com.gh.gamecenter.personal.vgame
import android.content.Context
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.PopupWindow
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.NewFlatLogUtils
import com.gh.download.DownloadManager
import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.DrawableView
import com.gh.gamecenter.common.viewholder.FooterViewHolder
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.utils.CurrentActivityHolder
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.core.utils.SpeedUtils
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.databinding.ItemInstalledVgameSquareBinding
import com.gh.gamecenter.databinding.PopupHistoryOptionBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.game.GameItemViewHolder
import com.gh.gamecenter.feature.provider.IBindingAdaptersProvider
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.history.ManageOption
import com.gh.gamecenter.manager.PackagesManager
import com.gh.gamecenter.personal.installed.InstalledAdapter
import com.gh.gamecenter.personal.installed.InstalledAdapter.InstalledGameItemViewHolder
import com.gh.gamecenter.personal.installed.InstalledGameItemData
import com.gh.vspace.VHelper
import com.gh.vspace.shortcut.OnCreateShortcutResult
import com.gh.vspace.shortcut.ShortcutManager
import com.gh.vspace.shortcut.ShortcutPermissionDialog
import com.lightgame.download.DownloadEntity
import com.lightgame.download.DownloadStatus
import com.lightgame.utils.Utils
import com.muugi.shortcut.core.Executor
import com.therouter.TheRouter
import org.json.JSONArray
class InstalledVGameAdapter(private val viewModel: InstalledVGameViewModel): ListAdapter<InstalledGameItemData, RecyclerView.ViewHolder>(createDiffCallback()) {
private var selectItems = arrayListOf<String>()
private var popWindow: PopupWindow? = null
private var currentOption = ManageOption.OPTION_MANAGER
private var popupBinding: PopupHistoryOptionBinding? = null
private val positionAndPackageMap = HashMap<String, Int>()
override fun submitList(list: List<InstalledGameItemData>?) {
positionAndPackageMap.clear()
// 记录游戏位置
list?.forEachIndexed { i, item ->
item.gameEntity?.let { gameEntity ->
val key = buildString {
append(gameEntity.id)
gameEntity.getApk().forEach { apk ->
append(apk.packageName)
}
append(i)
}
positionAndPackageMap[key] = i
}
}
super.submitList(list)
}
override fun getItemViewType(position: Int): Int {
val itemData = currentList.getOrNull(position) ?: return ITEM_NORMAL
return if (itemData.isHead) ITEM_HEAD else if (itemData.isFooter) ITEM_FOOTER else ITEM_NORMAL
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
when (viewType) {
ITEM_HEAD -> {
InstalledVGameHeadItemViewHolder(parent.toBinding())
}
ITEM_FOOTER -> {
FooterViewHolder(parent.layoutInflater.inflate(com.gh.gamecenter.common.R.layout.refresh_footerview, parent, false))
}
else -> {
InstalledGameItemViewHolder(parent.toBinding())
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val itemData = currentList.getOrNull(position) ?: return
if (holder is InstalledVGameHeadItemViewHolder) {
holder.binding.run {
root.setCardBackgroundColor(com.gh.gamecenter.common.R.color.ui_surface.toColor(root.context))
container.background = com.gh.gamecenter.common.R.drawable.reuse_listview_item_style.toDrawable(root.context)
root.setOnClickListener {
VHelper.startVSpaceSquare(root.context)
}
}
}
if (holder is InstalledGameItemViewHolder) {
val gameEntity = itemData.gameEntity ?: return
holder.binding.run {
root.setCardBackgroundColor(com.gh.gamecenter.common.R.color.ui_surface.toColor(root.context))
myHaloGame.run {
multiVersionContainer.isVisible = false
selectIv.setImageDrawable(DrawableView.getCheckSelectorDrawable(root.context))
root.background = com.gh.gamecenter.common.R.drawable.reuse_listview_item_style.toDrawable(root.context)
selectIv.goneIf(currentOption == ManageOption.OPTION_MANAGER)
selectIv.isChecked = selectItems.contains(gameEntity.id)
gameInfo.run {
wifiAutoDownloadTv.isVisible = false
tvSpeed.isVisible = false
arrowIv.isVisible = false
zoneDivider.isVisible = false
tagTv.isVisible = false
gameNameTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(root.context))
val provider = TheRouter.get(IBindingAdaptersProvider::class.java)
provider?.setGameName(gameNameTv, gameEntity, false)
gameIconView.displayGameIcon(gameEntity)
GameItemViewHolder.initGameSubtitleAndAdLabel(gameEntity, gameSubtitleTv)
moreIv.isVisible = currentOption == ManageOption.OPTION_MANAGER
moreIv.setOnClickListener {
InstalledVGameMoreDialogFragment.show(
root.context,
gameEntity,
onAddIconToLauncherClickListener = {
addShortcutToLauncher(root.context, gameEntity)
}, onClearGameDataClickListener = {
showClearGameDataDialog(root.context, gameEntity)
}, onDeleteGameClickListener = {
showDelGameDialog(root.context, gameEntity)
})
}
gameDesTv.text = gameEntity.des
val entityFromDownloadManager = DownloadManager.getInstance().getDownloadEntitySnapshot(gameEntity)
val entityFromVGame = VHelper.getVDownloadEntitySnapshot(gameEntity.id, gameEntity.getUniquePackageName())
val downloadEntity =
if (entityFromDownloadManager != null && entityFromDownloadManager.status != DownloadStatus.cancel) {
entityFromDownloadManager
} else {
entityFromVGame
}
updateDownloadBtnAndDes(root.context, downloadEntity, downloadBtn, gameDesTv, gameEntity) {
SensorsBridge.trackHaloSelfGameInstalledClick(
true,
gameEntity.id,
gameEntity.name ?: "",
gameEntity.categoryChinese,
holder.bindingAdapterPosition,
profile = "按钮",
text = downloadBtn.text
)
}
}
InstalledAdapter.bindAnnouncement(InstalledAdapter.TYPE_V_GAME, this, gameEntity, holder.bindingAdapterPosition)
InstalledAdapter.bindService(InstalledAdapter.TYPE_V_GAME, this, gameEntity, holder.bindingAdapterPosition, "我的光环-畅玩")
}
root.setOnClickListener {
if (currentOption == ManageOption.OPTION_MANAGER) {
GameDetailActivity.startGameDetailActivity(root.context, gameEntity.id, "我的光环-畅玩")
SensorsBridge.trackHaloSelfGameInstalledClick(
true,
gameEntity.id,
gameEntity.name ?: "",
gameEntity.categoryChinese,
holder.bindingAdapterPosition,
profile = "游戏",
text = "游戏"
)
} else {
if (selectItems.contains(gameEntity.id)) {
selectItems.remove(gameEntity.id)
} else {
selectItems.add(gameEntity.id)
}
checkSelectItems()
notifyItemChanged(position)
}
}
}
}
if (holder is FooterViewHolder) {
holder.initFooterViewHolder(false, false, true)
}
}
private fun updateDownloadBtnAndDes(
context: Context,
downloadEntity: DownloadEntity?,
downloadBtn: DownloadButton,
downloadDes: TextView,
gameEntity: GameEntity,
clickCallback: () -> Unit
) {
// 青少年模式显示查看
if (SPUtils.getBoolean(Constants.SP_TEENAGER_MODE)) {
downloadBtn.text = "查看"
downloadBtn.buttonStyle = DownloadButton.ButtonStyle.TEENAGER_MODE
return
}
downloadBtn.goneIf(currentOption != ManageOption.OPTION_MANAGER)
if (downloadEntity != null) {
val status = downloadEntity.status
var btnText = context.getText(com.gh.gamecenter.feature.R.string.downloading)
var backgroundType = DownloadButton.ButtonStyle.NORMAL
downloadBtn.apply {
when (status) {
DownloadStatus.downloading -> {
btnText = "${downloadEntity.percent}%"
downloadDes.visibility = View.VISIBLE
val speedText = SpeedUtils.getSpeed(downloadEntity.speed)
val remainTimeText = SpeedUtils.getRemainTime(
downloadEntity.size,
downloadEntity.progress,
downloadEntity.speed * 1024
)
downloadDes.text = "$speedText(剩$remainTimeText)"
downloadDes.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
backgroundType = DownloadButton.ButtonStyle.DOWNLOADING_NORMAL
progress = (downloadEntity.percent * 10).toInt()
setOnClickListener {
clickCallback.invoke()
DownloadManager.getInstance().pause(downloadEntity.url)
}
}
DownloadStatus.waiting -> {
btnText = context.getString(com.gh.gamecenter.feature.R.string.waiting)
backgroundType = DownloadButton.ButtonStyle.WAITING
downloadDes.visibility = View.VISIBLE
downloadDes.text = context.getString(com.gh.gamecenter.feature.R.string.waiting)
downloadDes.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
setOnClickListener {
clickCallback.invoke()
Utils.toast(context, "最多只能同时下载三个任务,请稍等")
}
}
DownloadStatus.pause,
DownloadStatus.timeout,
DownloadStatus.neterror,
DownloadStatus.subscribe,
DownloadStatus.diskioerror,
DownloadStatus.diskisfull,
DownloadStatus.overflow -> {
btnText = context.getString(com.gh.gamecenter.feature.R.string.resume)
downloadDes.visibility = View.VISIBLE
downloadDes.text = if (status == DownloadStatus.subscribe) "等待WIFI" else "已暂停"
downloadDes.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
setOnClickListener {
clickCallback.invoke()
DownloadManager.getInstance().resume(downloadEntity, false)
}
}
DownloadStatus.done -> {
if (PackagesManager.isCanUpdate(
gameId = gameEntity.id,
packageName = gameEntity.getApk().firstOrNull()?.packageName,
asVGame = true
)
) {
btnText = context.getString(com.gh.gamecenter.feature.R.string.update)
setOnClickListener {
clickCallback.invoke()
PackagesManager.getUpdateList()
.firstOrNull { it.id == downloadEntity.gameId }?.let {
VHelper.updateOrReDownload(downloadEntity, it)
}
}
} else {
btnText = context.getString(com.gh.gamecenter.feature.R.string.launch)
setOnClickListener {
clickCallback.invoke()
CurrentActivityHolder.getCurrentActivity()?.let {
VHelper.installOrLaunch(it, downloadEntity, "畅玩管理")
}
}
}
}
else -> {
// do nothing
}
}
text = btnText.toString()
buttonStyle = backgroundType
}
} else {
downloadBtn.buttonStyle = DownloadButton.ButtonStyle.NORMAL
downloadBtn.text = "下载"
downloadBtn.setOnClickListener {
clickCallback.invoke()
ToastUtils.toast("不应该出现状态为'下载'的按钮")
}
}
}
fun changeOption(context: Context, option: ManageOption) {
currentOption = option
when (currentOption) {
ManageOption.OPTION_MANAGER -> {
selectItems.clear()
popWindow?.dismiss()
popWindow = null
}
else -> {
if (popWindow == null || popWindow?.isShowing == false) {
showOptionWindow(context)
}
}
}
notifyItemRangeChanged(0, currentList.size)
}
/**
* 显示添加图标到桌面的对话框
*/
private fun addShortcutToLauncher(context: Context, gameEntity: GameEntity) {
ShortcutManager.getInstance()
.tryCreateShortCut(
context, gameEntity,
object : OnCreateShortcutResult {
override fun showPermissionDialog(executor: Executor?) {
NewFlatLogUtils.logHaloFunShortcutPermissionDialogShow(
gameEntity.id,
gameEntity.name ?: ""
)
ShortcutPermissionDialog(context, gameEntity, executor).show()
}
override fun exist() {
ToastUtils.showToast(context.getString(R.string.shortcut_exist))
}
override fun success() {
ToastUtils.showToast(context.getString(R.string.shortcut_create_success))
}
override fun failed() {
ToastUtils.showToast(context.getString(R.string.shortcut_create_failed))
}
})
}
/**
* 显示清除游戏数据对话框
*/
private fun showClearGameDataDialog(context: Context, gameEntity: GameEntity) {
NewFlatLogUtils.logHaloFunCleanDataDialogShow(
gameEntity.id,
gameEntity.name ?: ""
)
DialogHelper.showDialog(
context,
"提示",
"清除后游戏的所有记录都将被清空,无法恢复!您确定要清除吗?",
"确定",
"取消",
confirmClickCallback = { //清除游戏数据
viewModel.cleanVGameData(gameEntity.getUniquePackageName())
NewFlatLogUtils.logHaloFunCleanDataDialogButtonClick(
gameEntity.id,
gameEntity.name ?: "",
"确定"
)
},
cancelClickCallback = {
NewFlatLogUtils.logHaloFunCleanDataDialogButtonClick(
gameEntity.id,
gameEntity.name ?: "",
"取消"
)
},
extraConfig = DialogHelper.Config(centerTitle = true)
)
}
/**
* 显示删除游戏的对话框
*/
private fun showDelGameDialog(
context: Context,
gameEntity: GameEntity
) {
DialogHelper.showDialog(
context,
"删除游戏",
"单机类游戏被删除将可能导致本地存档、充值数据丢失,请确认后操作(网游类游戏删除不会影响游戏存档和充值数据)",
"再等等",
"删除",
{},
{
//删除对应的桌面快捷方式
ShortcutManager.getInstance().removeShortcut(context, gameEntity)
runOnIoThread {
val apk = gameEntity.getApk().firstOrNull()
viewModel.deleteVGame(apk?.url, apk?.packageName)
AppExecutor.uiExecutor.executeWithDelay({
viewModel.refresh()
}, 200)
}
},
uiModificationCallback = {
it.cancelTv.setTextColor(com.gh.gamecenter.common.R.color.secondary_red.toColor(it.root.context))
it.confirmTv.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(it.root.context))
},
extraConfig = DialogHelper.Config(centerTitle = true)
)
}
private fun showOptionWindow(context: Context) {
popupBinding = PopupHistoryOptionBinding.inflate(LayoutInflater.from(context))
popupBinding?.root?.isFocusable = true
popupBinding?.root?.isFocusableInTouchMode = true
popWindow =
PopupWindow(popupBinding?.root, LinearLayout.LayoutParams.MATCH_PARENT, 56F.dip2px())
popWindow?.showAtLocation(
(context as AppCompatActivity).window.decorView, Gravity.BOTTOM, 0, 0
)
popupBinding?.itemDelete?.setOnClickListener {
NewFlatLogUtils.logHaloFunEvent("halo_fun_manage_game_delete_dialog_show")
DialogHelper.showDialog(
context,
"删除游戏",
"单机类游戏被删除将可能导致本地存档、充值数据丢失,请确认后操作(网游类游戏删除不会影响游戏存档和充值数据)",
"再等等",
"删除",
{
NewFlatLogUtils.logHaloFunManageGameDeleteDialogClick("再看看", JSONArray(selectItems))
},
{
NewFlatLogUtils.logHaloFunManageGameDeleteDialogClick("删除", JSONArray(selectItems))
val selectItemCopy = ArrayList(selectItems)
selectItems.clear()
checkSelectItems()
changeOption(context, ManageOption.OPTION_MANAGER)
runOnIoThread {
viewModel.deleteVGames(selectItemCopy)
AppExecutor.uiExecutor.executeWithDelay({
viewModel.refresh()
}, 200)
}
},
uiModificationCallback = {
it.cancelTv.setTextColor(com.gh.gamecenter.common.R.color.secondary_red.toColor(it.root.context))
it.confirmTv.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(it.root.context))
},
extraConfig = DialogHelper.Config(centerTitle = true)
)
}
popupBinding?.checkAllCb?.setCompoundDrawablesWithIntrinsicBounds(
DrawableView.getCheckSelectorDrawable(
context
), null, null, null
)
popupBinding?.checkAllCb?.setOnClickListener {
if (popupBinding?.checkAllCb?.isChecked == true) {
selectItems.clear()
selectItems.addAll(currentList.mapNotNull { it.gameEntity?.id }.toList())
} else {
selectItems.clear()
}
checkSelectItems()
notifyItemRangeChanged(0, currentList.size)
}
checkSelectItems()
}
private fun checkSelectItems() {
popupBinding?.run {
selectNumTv.text = if (selectItems.isEmpty()) "" else "(${selectItems.size})"
itemDelete.background = if (selectItems.isEmpty()) com.gh.gamecenter.feature.R.drawable.button_round_gray_light.toDrawable(root.context) else com.gh.gamecenter.common.R.drawable.download_button_normal_style.toDrawable(root.context)
itemDelete.setTextColor(if (selectItems.isEmpty()) com.gh.gamecenter.common.R.color.text_secondary.toColor(root.context) else com.gh.gamecenter.common.R.color.white.toColor(root.context))
itemDelete.isEnabled = selectItems.isNotEmpty()
checkAllCb.isChecked = selectItems.isNotEmpty() && selectItems.size == currentList.size - 2
}
}
fun notifyItemByDownload(download: DownloadEntity) {
for (key in positionAndPackageMap.keys) {
if (key.contains(download.packageName) && key.contains(download.gameId)) {
val position = positionAndPackageMap[key]
if (position != null && position < currentList.size) {
currentList[position].gameEntity?.getEntryMap()?.set(download.platform, download)
notifyItemChanged(position)
}
}
}
}
class InstalledVGameHeadItemViewHolder(val binding: ItemInstalledVgameSquareBinding): RecyclerView.ViewHolder(binding.root)
companion object {
const val ITEM_HEAD = 0
const val ITEM_NORMAL = 1
const val ITEM_FOOTER = 2
fun createDiffCallback() = object : ItemCallback<InstalledGameItemData>() {
override fun areItemsTheSame(oldItem: InstalledGameItemData, newItem: InstalledGameItemData): Boolean {
return oldItem.areItemsTheSame(newItem)
}
override fun areContentsTheSame(oldItem: InstalledGameItemData, newItem: InstalledGameItemData): Boolean {
return oldItem.areContentsTheSame(newItem)
}
}
}
}

View File

@ -0,0 +1,110 @@
package com.gh.gamecenter.personal.vgame
import androidx.core.view.isVisible
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LiveData
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.recyclerview.widget.LinearLayoutManager
import com.gh.common.iinterface.IBatchManage
import com.gh.common.iinterface.IInstalledSortType
import com.gh.common.iinterface.InstalledSortType
import com.gh.download.DownloadManager
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.history.ManageOption
import com.gh.gamecenter.livedata.Event
import com.gh.gamecenter.personal.MyHaloBaseListFragment
import com.gh.gamecenter.personal.MyHaloViewModel
import com.lightgame.download.DataWatcher
import com.lightgame.download.DownloadEntity
import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
class InstalledVGameFragment: MyHaloBaseListFragment(), IInstalledSortType, IBatchManage {
private val viewModel by viewModels<InstalledVGameViewModel>()
private val myHaloViewModel by activityViewModels<MyHaloViewModel>()
private val installedVGameAdapter by lazy { InstalledVGameAdapter(viewModel) }
private val dataWatcher: DataWatcher = object : DataWatcher() {
override fun onDataChanged(downloadEntity: DownloadEntity) {
installedVGameAdapter.notifyItemByDownload(downloadEntity)
}
override fun onDataInit(downloadEntity: DownloadEntity) {
installedVGameAdapter.notifyItemByDownload(downloadEntity)
}
}
override fun onFragmentFirstVisible() {
super.onFragmentFirstVisible()
binding.run {
listRv.run {
itemAnimator = null
layoutManager = LinearLayoutManager(requireContext())
adapter = installedVGameAdapter
}
}
observeData()
viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.vGames
.catch {
ToastUtils.showToast(it.message ?: "")
}
.collectLatest {
viewModel.loadData(it)
}
}
}
}
override fun provideLoadStatusLiveData(): LiveData<LoadStatus> = viewModel.loadStatusLiveData
private fun observeData() {
myHaloViewModel.managementStateLiveData.observe(viewLifecycleOwner) {
if (isSupportVisible) {
installedVGameAdapter.changeOption(requireContext(), if (it) ManageOption.OPTION_CANCEL_SELECT else ManageOption.OPTION_MANAGER)
}
}
viewModel.itemDataListLiveData.observe(viewLifecycleOwner) {
installedVGameAdapter.submitList(it)
}
}
override fun onFragmentResume() {
super.onFragmentResume()
DownloadManager.getInstance().addObserver(dataWatcher)
myHaloViewModel.sortTypeLiveData.postValue(Event(getCurrentSortType().des))
}
override fun onFragmentPause() {
super.onFragmentPause()
DownloadManager.getInstance().removeObserver(dataWatcher)
}
override fun getCurrentSortType(): InstalledSortType = viewModel.sortedType
override fun changeSortType(sortType: InstalledSortType) {
viewModel.sortedType = sortType
}
override fun onManageClick() {
myHaloViewModel.changeManagementState()
}
override fun refresh() {
viewModel.refresh()
}
override fun showEmpty(isGranted: Boolean) {
super.showEmpty(isGranted)
binding.listRv.isVisible = true
}
}

View File

@ -0,0 +1,71 @@
package com.gh.gamecenter.personal.vgame
import android.content.Context
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf
import com.gh.gamecenter.common.base.fragment.BaseBottomDialogFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.core.utils.CurrentActivityHolder
import com.gh.gamecenter.databinding.DialogInstalledVgameMoreBinding
import com.gh.gamecenter.feature.entity.GameEntity
class InstalledVGameMoreDialogFragment : BaseBottomDialogFragment<DialogInstalledVgameMoreBinding>() {
private var gameEntity: GameEntity? = null
private var onAddIconToLauncherClickListener: (() -> Unit)? = null
private var onClearGameDataClickListener: (() -> Unit)? = null
private var onDeleteGameClickListener: (() -> Unit)? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
gameEntity = arguments?.getParcelable(EntranceConsts.KEY_GAME_ENTITY)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
gameEntity?.let { mBinding.gameIconView.displayGameIcon(it) }
mBinding.gameNameTv.text = gameEntity?.name
mBinding.addIconToDesktopTv.setOnClickListener {
dismissAllowingStateLoss()
onAddIconToLauncherClickListener?.invoke()
}
mBinding.cleanGameDataTv.setOnClickListener {
dismissAllowingStateLoss()
onClearGameDataClickListener?.invoke()
}
mBinding.deleteGameTv.setOnClickListener {
dismissAllowingStateLoss()
onDeleteGameClickListener?.invoke()
}
mBinding.cancelTv.setOnClickListener {
dismissAllowingStateLoss()
}
}
companion object {
fun show(
context: Context,
gameEntity: GameEntity,
onAddIconToLauncherClickListener: () -> Unit,
onClearGameDataClickListener: () -> Unit,
onDeleteGameClickListener: () -> Unit,
) {
if (context is AppCompatActivity) {
context.supportFragmentManager
} else {
(CurrentActivityHolder.getCurrentActivity() as? AppCompatActivity)?.supportFragmentManager
}?.let {
val fragment = InstalledVGameMoreDialogFragment().apply {
arguments = bundleOf(
EntranceConsts.KEY_GAME_ENTITY to gameEntity
)
}
fragment.onAddIconToLauncherClickListener = onAddIconToLauncherClickListener
fragment.onClearGameDataClickListener = onClearGameDataClickListener
fragment.onDeleteGameClickListener = onDeleteGameClickListener
fragment.show(it, fragment::class.java.simpleName)
}
}
}
}

View File

@ -0,0 +1,240 @@
package com.gh.gamecenter.personal.vgame
import android.annotation.SuppressLint
import android.app.Application
import androidx.annotation.WorkerThread
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import com.gh.common.iinterface.InstalledSortType
import com.gh.download.DownloadManager
import com.gh.download.PackageObserver
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.common.utils.toProperReadableSize
import com.gh.gamecenter.common.utils.toRequestBody
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.runOnUiThread
import com.gh.gamecenter.core.utils.NumberUtils
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.feature.entity.ApkEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.utils.ApkActiveUtils
import com.gh.gamecenter.personal.installed.InstalledGameItemData
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.vspace.VHelper
import com.gh.vspace.db.VGameEntity
import com.lightgame.download.DownloadStatus
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.subjects.PublishSubject
import org.greenrobot.eventbus.EventBus
import java.util.concurrent.TimeUnit
class InstalledVGameViewModel(application: Application): AndroidViewModel(application) {
private val newApi = RetrofitManager.getInstance().newApi
val itemDataListLiveData = MutableLiveData<List<InstalledGameItemData>>()
val loadStatusLiveData = MutableLiveData<LoadStatus>()
var sortedType = InstalledSortType.valueOf(
SPUtils.getString(
Constants.SP_MY_HALO_INSTALLED_V_GAME_ORDER,
InstalledSortType.RECENTLY_PLAYED.name
) ?: InstalledSortType.RECENTLY_PLAYED.name
)
set(value) {
if (field != value) {
field = value
changeSortedType()
SPUtils.setString(Constants.SP_MY_HALO_INSTALLED_V_GAME_ORDER, value.name)
}
}
private val sortedItemDataList = ArrayList<InstalledGameItemData>()
val vGames = VHelper.vGameDao.getAllGames()
private val compositeDisposable = CompositeDisposable()
private var loadPublishSubject: PublishSubject<List<VGameEntity>> = PublishSubject.create()
init {
loadPublishSubject.debounce(300, TimeUnit.MILLISECONDS)
.distinctUntilChanged { p1, p2 -> p1.map { it.packageName }.sorted() == p2.map { it.packageName }.sorted() }
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
initData(it)
}.let(compositeDisposable::add)
}
private fun changeSortedType() {
if (sortedItemDataList.isNotEmpty()) {
when (sortedType) {
InstalledSortType.RECENTLY_PLAYED -> sortedItemDataList.sortByDescending { it.gameEntity?.lastPlayedTime }
InstalledSortType.LATEST_UPDATED -> sortedItemDataList.sortByDescending { it.gameEntity?.updateTime }
InstalledSortType.MOST_PLAYED -> sortedItemDataList.sortWith(compareByDescending<InstalledGameItemData> { it.gameEntity?.playedTime }.thenByDescending { it.gameEntity?.packageInstallTime })
InstalledSortType.LEAST_PLAYED -> sortedItemDataList.sortWith(compareBy<InstalledGameItemData> { it.gameEntity?.playedTime }.thenByDescending { it.gameEntity?.packageInstallTime })
}
decorateListAndPost()
}
}
fun loadData(vGameList: List<VGameEntity>) {
loadPublishSubject.onNext(vGameList)
}
fun refresh() {
runOnIoThread {
initData(VHelper.getAllVGame())
}
}
private fun initData(vGameList: List<VGameEntity>) {
val installedVGameList = vGameList.filterNot { vGame ->
DownloadManager.getInstance().allVDownloadTaskSnapshots.any {
it.gameId == vGame.downloadEntity.gameId && it.status != DownloadStatus.done
}
}
if (installedVGameList.isEmpty()) {
decorateListAndPost()
} else {
loadGamesData(installedVGameList)
}
}
@SuppressLint("CheckResult")
private fun loadGamesData(vGameList: List<VGameEntity>) {
val vGameIdList = vGameList.map { it.downloadEntity.gameId }
val paramsMap = mapOf(
"game_id" to vGameIdList
)
newApi.postMyHaloGame(TAB_V_GAME, 1, vGameIdList.size, paramsMap.toRequestBody())
.map(ApkActiveUtils.filterMapperList)
.compose(singleToMain())
.subscribe({
processingData(it, vGameList)
}, {
loadStatusLiveData.postValue(LoadStatus.INIT_FAILED)
}).let(compositeDisposable::add)
}
private fun processingData(gameList: List<GameEntity>, vGameList: List<VGameEntity>) {
val sortedGameList = mutableListOf<GameEntity>()
for (entity in gameList) {
val newEntity = entity.copy()
val vGameEntity = vGameList.find { it.downloadEntity.gameId == entity.id } ?: continue
val gameEntity = VHelper.toGameEntity(vGameEntity.downloadEntity)
newEntity.lastPlayedTime = gameEntity.lastPlayedTime
if (gameEntity.playedTime != 0L) {
newEntity.playedTime = gameEntity.playedTime
newEntity.des =
"已畅玩${NumberUtils.transSimpleUsageTime(gameEntity.playedTime / 1000)}"
} else {
var occupiedSpace =
VHelper.getAppOccupiedSpace(vGameEntity.downloadEntity.packageName)
if (occupiedSpace == 0L) {
occupiedSpace = vGameEntity.downloadEntity.size
}
newEntity.des = "已占用 ${occupiedSpace.toProperReadableSize()}"
}
sortedGameList.add(newEntity)
}
when (sortedType) {
InstalledSortType.RECENTLY_PLAYED -> sortedGameList.sortByDescending { it.lastPlayedTime }
InstalledSortType.LATEST_UPDATED -> sortedGameList.sortByDescending { it.updateTime }
InstalledSortType.MOST_PLAYED -> sortedGameList.sortWith(compareByDescending<GameEntity> { it.playedTime }.thenByDescending { it.packageInstallTime })
InstalledSortType.LEAST_PLAYED -> sortedGameList.sortWith(compareBy<GameEntity> { it.playedTime }.thenByDescending { it.packageInstallTime })
}
sortedItemDataList.clear()
sortedItemDataList.addAll(sortedGameList.map { InstalledGameItemData(gameEntity = it) })
decorateListAndPost()
}
private fun decorateListAndPost() {
val itemDataList = mutableListOf<InstalledGameItemData>()
itemDataList.add(InstalledGameItemData(isHead = true))
itemDataList.addAll(sortedItemDataList)
if (sortedItemDataList.isNotEmpty()) {
itemDataList.add(InstalledGameItemData(isFooter = true))
}
itemDataListLiveData.postValue(itemDataList)
loadStatusLiveData.postValue(if (sortedItemDataList.isEmpty()) LoadStatus.INIT_EMPTY else LoadStatus.INIT_LOADED)
}
@WorkerThread
fun deleteVGame(url: String?, packageName: String?) {
DownloadManager.getInstance().pause(url)
DownloadManager.getInstance().cancel(url)
VHelper.uninstall(packageName ?: "")
runOnUiThread {
val event = EBPackage("卸载", packageName ?: "", "unknown").also {
it.isVGame = true
}
EventBus.getDefault().post(event)
PackageObserver.onPackageChanged(event)
ToastUtils.toast("已删除 1 款游戏")
}
}
@WorkerThread
fun deleteVGames(idList: ArrayList<String>) {
val packageList = arrayListOf<String>()
val apkList = arrayListOf<ApkEntity>()
for (id in idList) {
val apkEntity =
sortedItemDataList.firstOrNull { id == it.gameEntity?.id }?.gameEntity?.getApk()?.firstOrNull() ?: continue
apkList.add(apkEntity)
}
for (apkEntity in apkList) {
DownloadManager.getInstance().pause(apkEntity.url)
DownloadManager.getInstance().cancel(apkEntity.url)
packageList.add(apkEntity.packageName)
VHelper.uninstall(apkEntity.packageName)
}
runOnUiThread {
for (packageName in packageList) {
val event = EBPackage("卸载", packageName, "unknown").also {
it.isVGame = true
}
EventBus.getDefault().post(event)
PackageObserver.onPackageChanged(event)
}
ToastUtils.toast("已删除 ${packageList.size} 款游戏")
}
}
fun cleanVGameData(packageName: String?) {
runOnIoThread {
VHelper.cleanGameData(packageName)
}
}
override fun onCleared() {
super.onCleared()
compositeDisposable.clear()
}
companion object {
const val TAB_V_GAME = "smooth"
}
}

View File

@ -0,0 +1,32 @@
package com.gh.gamecenter.playedgame
import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.gh.gamecenter.common.base.activity.ToolBarActivity
import com.gh.gamecenter.common.utils.updateStatusBarColor
/**
* 玩过的游戏
*/
class PlayedGameActivity: ToolBarActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
updateStatusBarColor(com.gh.gamecenter.common.R.color.ui_surface, com.gh.gamecenter.common.R.color.ui_surface)
}
override fun provideNormalIntent(): Intent {
return getTargetIntent(this, PlayedGameActivity::class.java, PlayedGameFragment::class.java)
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
updateStatusBarColor(com.gh.gamecenter.common.R.color.ui_surface, com.gh.gamecenter.common.R.color.ui_surface)
}
companion object {
fun getIntent(context: Context): Intent {
return getTargetIntent(context, PlayedGameActivity::class.java, PlayedGameFragment::class.java)
}
}
}

View File

@ -0,0 +1,230 @@
package com.gh.gamecenter.playedgame
import android.content.Context
import android.util.TypedValue
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.view.updateLayoutParams
import androidx.core.view.updateMargins
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.databind.BindingAdapters
import com.gh.common.util.DownloadItemUtils
import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.adapter.viewholder.GameViewHolder
import com.gh.gamecenter.common.baselist.ListAdapter
import com.gh.gamecenter.common.constant.ItemViewType
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.viewholder.FooterViewHolder
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.utils.NumberUtils
import com.gh.gamecenter.core.utils.StringUtils
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.game.GameItemViewHolder
import com.gh.gamecenter.feature.utils.PlatformUtils
import com.gh.gamecenter.mygame.PlayedGameAdapter.MyPlayedGameViewHolder
import com.lightgame.download.DownloadEntity
class PlayedGameAdapter(
context: Context,
private val viewModel: PlayedGameViewModel
): ListAdapter<GameEntity>(context) {
private val downloadText by lazy { mContext.getString(com.gh.gamecenter.feature.R.string.download) }
private val tryText by lazy { mContext.getString(com.gh.gamecenter.feature.R.string.attempt) }
private val positionAndPackageMap = HashMap<String, Int>()
override fun setListData(updateData: MutableList<GameEntity>?) {
// 记录游戏位置
if (updateData != null) {
for (i in 0 until updateData.size) {
val gameEntity = updateData[i]
// 一些数据置换
val apk = gameEntity.getApk()
if (apk.size > 0 && !apk[0].ghVersion.isNullOrEmpty()) {
gameEntity.brief = PlatformUtils.getInstance(mContext).getPlatformName(apk[0].getPlatform())
}
var packages = gameEntity.id
for (apkEntity in apk) {
packages += apkEntity.packageName
}
positionAndPackageMap[packages + i] = i
}
}
super.setListData(updateData)
}
override fun getItemViewType(position: Int): Int = if (position == itemCount - 1) {
ItemViewType.ITEM_FOOTER
} else {
ItemViewType.GAME_NORMAL
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
when (viewType) {
ItemViewType.GAME_NORMAL -> {
MyPlayedGameViewHolder(parent.toBinding())
}
else -> {
FooterViewHolder(mLayoutInflater.inflate(com.gh.gamecenter.common.R.layout.refresh_footerview, parent, false))
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is MyPlayedGameViewHolder) {
val gameEntity = mEntityList.getOrNull(position) ?: return
holder.binding.run {
uninstalledTv.updateLayoutParams<MarginLayoutParams> {
updateMargins(top = 8F.dip2px())
}
holder.initServerType(gameEntity)
gameItemIncluded.run {
root.setPadding(16F.dip2px(), 8F.dip2px(), 16F.dip2px(), 8F.dip2px())
root.background = com.gh.gamecenter.common.R.drawable.reuse_listview_item_style.toDrawable(root.context)
gameName.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(root.context))
gameDes.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(root.context))
recommendStarPref.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(root.context))
downloadBtn.layoutParams = downloadBtn.layoutParams.apply {
width = 56F.dip2px()
height = 28F.dip2px()
}
gameIconView.displayGameIcon(gameEntity)
gameRating.textSize = if (gameEntity.commentCount > 3) 12F else 10F
BindingAdapters.setGameName(gameName, gameEntity, false)
BindingAdapters.setGameTags(labelList, gameEntity, "")
gameRating.setDrawableStart(if (gameEntity.commentCount > 3) com.gh.gamecenter.feature.R.drawable.game_horizontal_rating.toDrawable() else null)
gameRating.setPadding(0, 0, if (gameEntity.commentCount > 3) 8F.dip2px() else 0, 0)
gameRating.text = if (gameEntity.commentCount > 3) {
if (gameEntity.star == 10.0F) "10" else gameEntity.star.toString()
} else ""
gameRating.setTextColor(
if (gameEntity.commentCount > 3) com.gh.gamecenter.common.R.color.text_theme.toColor(mContext) else com.gh.gamecenter.common.R.color.primary_theme.toColor(
mContext
)
)
gameDes.text = gameEntity.decoratedDes
recommendStar.rating = gameEntity.recommendStar.toFloat()
GameItemViewHolder.initGameSubtitleAndAdLabel(gameEntity, gameSubtitleTv)
gameDes.updateLayoutParams<MarginLayoutParams> {
topMargin = 8F.dip2px()
}
ConstraintSet().apply {
clone(root)
if (gameEntity.playedTime == 0L) {
connect(gameDes.id, ConstraintSet.BOTTOM, gameDesSpace.id, ConstraintSet.BOTTOM)
} else {
clear(gameDes.id, ConstraintSet.BOTTOM)
}
}.applyTo(root)
if (gameEntity.playedTime == 0L) {
gameDes.post {
gameDesSpace.updateLayoutParams<MarginLayoutParams> {
height = gameDes.height + 8F.dip2px()
}
}
labelList.visibility = View.GONE
} else {
gameDesSpace.updateLayoutParams<MarginLayoutParams> {
height = 28F.dip2px()
}
labelList.visibility = View.VISIBLE
labelList.removeAllViews()
val runTimeView = TextView(mContext)
runTimeView.isSingleLine = true
runTimeView.setTextSize(TypedValue.COMPLEX_UNIT_SP, 11F)
runTimeView.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(mContext))
runTimeView.text = "游戏时长 ${NumberUtils.transSimpleUsageTime(gameEntity.playedTime)}"
labelList.addView(runTimeView)
}
}
DownloadItemUtils.setOnClickListener(
mContext,
gameItemIncluded.downloadBtn,
gameEntity,
position,
this@PlayedGameAdapter,
"玩过的游戏",
location = StringUtils.buildString("玩过的游戏", ":", gameEntity.name)
)
DownloadItemUtils.updateItem(
mContext,
gameEntity,
GameViewHolder(gameItemIncluded),
true
)
gameItemIncluded.downloadBtn.putWidgetBusinessName("玩过的游戏")
root.setOnClickListener {
GameDetailActivity.startGameDetailActivity(mContext, gameEntity.id, "玩过的游戏", null)
}
root.setOnLongClickListener {
val adapterPosition = holder.bindingAdapterPosition
if (adapterPosition != RecyclerView.NO_POSITION) {
val game = mEntityList.getOrNull(adapterPosition) ?: return@setOnLongClickListener true
PlayedGameMoreDialogFragment.show(mContext, game) {
viewModel.deletePlayedGame(game)
}
}
true
}
// 很丑陋,但因为上面的下载按钮文案和状态是异步获取更新的,所以这里也只能异步更新了
AppExecutor.lightWeightIoExecutor.execute {
AppExecutor.uiExecutor.execute {
tryWithDefaultCatch {
val downloadBtnText = gameItemIncluded.downloadBtn.text
val isDownloadable =
downloadBtnText.contains(downloadText) || downloadBtnText.contains(tryText)
uninstalledTv.visibleIf(isDownloadable)
}
}
}
}
}
if (holder is FooterViewHolder) {
holder.initItemPadding()
holder.initFooterViewHolder(viewModel, mIsLoading, mIsNetworkError, mIsOver)
holder.hint.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(mContext))
}
}
override fun getItemCount(): Int = if (mEntityList.isNullOrEmpty()) 0 else mEntityList.size + 1
fun notifyItemByDownload(download: DownloadEntity) {
for (key in positionAndPackageMap.keys) {
if (key.contains(download.packageName) && key.contains(download.gameId)) {
val position = positionAndPackageMap[key]
if (position != null && mEntityList != null && position < mEntityList.size) {
mEntityList[position].getEntryMap()[download.platform] = download
notifyItemChanged(position)
}
}
}
}
fun notifyItemAndRemoveDownload(status: EBDownloadStatus) {
for (key in positionAndPackageMap.keys) {
if (key.contains(status.packageName) && key.contains(status.gameId)) {
val position = positionAndPackageMap[key]
if (position != null && mEntityList != null && position < mEntityList.size) {
mEntityList[position].getEntryMap().remove(status.platform)
notifyItemChanged(position)
}
}
}
}
fun clearPositionAndPackageMap() {
positionAndPackageMap.clear()
}
}

View File

@ -0,0 +1,231 @@
package com.gh.gamecenter.playedgame
import android.content.Intent
import android.graphics.Typeface
import android.os.Build
import android.view.View
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.DialogUtils
import com.gh.common.util.UsageStatsHelper
import com.gh.download.DownloadManager
import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.LazyListFragment
import com.gh.gamecenter.common.baselist.ListAdapter
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.viewModelProvider
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.databinding.FragmentPlayedGameBinding
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.GameInstall
import com.gh.gamecenter.manager.PackagesManager
import com.gh.gamecenter.personalhome.InstalledGameDialog
import com.lightgame.download.DataWatcher
import com.lightgame.download.DownloadEntity
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import kotlin.math.abs
class PlayedGameFragment: LazyListFragment<GameEntity, PlayedGameViewModel>() {
private lateinit var binding: FragmentPlayedGameBinding
private val viewModel by lazy { viewModelProvider<PlayedGameViewModel>() }
private val playedGameAdapter by lazy { PlayedGameAdapter(requireContext(), viewModel) }
private val dataWatcher = object : DataWatcher() {
override fun onDataChanged(downloadEntity: DownloadEntity) {
playedGameAdapter.notifyItemByDownload(downloadEntity)
}
override fun onDataInit(downloadEntity: DownloadEntity) {
onDataChanged(downloadEntity)
}
}
override fun getRealLayoutId(): Int = R.layout.fragment_played_game
override fun provideListAdapter(): ListAdapter<*> = playedGameAdapter
override fun getItemDecoration(): RecyclerView.ItemDecoration? = null
override fun onRealLayoutInflated(inflatedView: View) {
super.onRealLayoutInflated(inflatedView)
binding = FragmentPlayedGameBinding.bind(inflatedView)
binding.run {
appbar.addOnOffsetChangedListener { _, verticalOffset ->
listRefresh.isEnabled = abs(verticalOffset) < 2
}
playedGameDataTv.typeface = Typeface.createFromAsset(requireContext().assets, Constants.DIN_FONT_PATH)
mostPlayedContainer.run {
titleTv.text = "玩的最多"
enableUsageStatsView.setOnClickListener {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1 && !UsageStatsHelper.checkForPermission()) {
DialogUtils.showUsageStatsDialog(
requireContext(), {
UsageStatsHelper.skipToUsageStats(
requireContext(),
UsageStatsHelper.USAGE_STATUS_REQUEST_CODE
)
}, null
)
}
}
}
recentlyPlayedContainer.run {
titleTv.text = "最近在玩"
}
}
}
override fun onFragmentFirstVisible() {
super.onFragmentFirstVisible()
setNavigationTitle("玩过的游戏")
// 提交用户使用数据
runOnIoThread {
UsageStatsHelper.checkAndPostUsageStats()
}
viewModel.totalGameLiveData.observe(viewLifecycleOwner) {
binding.playedGameDataTv.text = it
}
viewModel.mostPlayedGameListLiveData.observe(viewLifecycleOwner) {
updateMostPlayedGame(it)
}
viewModel.recentlyPlayedGameListLiveData.observe(viewLifecycleOwner) {
updateRecentlyPlayedGame(it)
}
}
private fun updateMostPlayedGame(gameList: List<GameEntity>) {
val isUsageStatsEnabled = Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1 || UsageStatsHelper.checkForPermission()
binding.mostPlayedContainer.run {
if (isUsageStatsEnabled) {
emptyTv.isVisible = gameList.isEmpty()
enableUsageStatsTv.isVisible = false
enableUsageStatsView.isVisible = false
} else {
emptyTv.isVisible = false
enableUsageStatsTv.isVisible = gameList.isEmpty()
enableUsageStatsView.isVisible = gameList.isEmpty()
}
val gameIconList = listOf(gameIcon1, gameIcon2, gameIcon3)
gameList.forEachIndexed { index, game ->
gameIconList[index].run {
displayGameIcon(game)
setOnClickListener {
GameDetailActivity.startGameDetailActivity(requireContext(), game.id, "玩过的游戏-玩的最多", null)
}
}
}
gameList.size.let {
gameIcon1.goneIf(it == 0)
rank1Iv.goneIf(it == 0)
gameIcon2.goneIf(it < 2)
rank2Iv.goneIf(it < 2)
gameIcon3.goneIf(it < 3)
rank3Iv.goneIf(it < 3)
}
}
}
private fun updateRecentlyPlayedGame(gameList: List<GameEntity>) {
binding.recentlyPlayedContainer.run {
if (gameList.isEmpty()) {
emptyTv.isVisible = true
} else {
emptyTv.isVisible = false
val gameIconList = listOf(gameIcon1, gameIcon2, gameIcon3)
gameList.forEachIndexed { index, game ->
gameIconList[index].run {
displayGameIcon(game)
setOnClickListener {
GameDetailActivity.startGameDetailActivity(requireContext(),
game.id, "玩过的游戏-玩的最多", null)
}
}
}
gameList.size.let {
gameIcon1.goneIf(it == 0)
gameIcon2.goneIf(it < 2)
gameIcon3.goneIf(it < 3)
}
}
}
}
override fun onFragmentPause() {
super.onFragmentPause()
DownloadManager.getInstance().removeObserver(dataWatcher)
}
override fun onFragmentResume() {
super.onFragmentResume()
DownloadManager.getInstance().addObserver(dataWatcher)
}
override fun onRefresh() {
playedGameAdapter.clearPositionAndPackageMap()
super.onRefresh()
}
override fun onChanged(ts: MutableList<GameEntity>?) {
super.onChanged(ts)
val installedList =
PackagesManager.filterSameApk(PackagesManager.filterDownloadBlackPackage(PackagesManager.getInstalledList()))
val simulatorDownloadEntityList = DownloadManager.getInstance().allSimulatorDownloadEntity
simulatorDownloadEntityList.forEach { entity ->
installedList.add(
GameInstall(
entity.gameId,
packageName = entity.packageName,
name = entity.name,
icon = entity.icon
)
)
}
// val count = SPUtils.getInt(Constants.SP_MARK_INSTALLED_GAME, 0)
val isCancel = SPUtils.getBoolean(Constants.SP_MARK_INSTALLED_GAME_MY_GAME, false)
if (!isCancel && ts.isNullOrEmpty() && installedList.isNotEmpty()) {
val dialog = InstalledGameDialog(requireContext(), installedList, "我的光环_新", "我的游戏-标记玩过弹窗")
dialog.show()
dialog.onConfirmClickListener = {
onLoadRefresh()
}
}
}
// 下载被删除事件
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(status: EBDownloadStatus) {
if ("delete" == status.status) {
playedGameAdapter.notifyItemAndRemoveDownload(status)
}
}
// 安装/卸载 事件
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(busFour: EBPackage) {
if (busFour.isInstalledOrUninstalled()) {
playedGameAdapter.notifyDataSetChanged()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == UsageStatsHelper.USAGE_STATUS_REQUEST_CODE && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
viewModel.mostPlayedGameListLiveData.value?.let {
updateMostPlayedGame(it)
}
SPUtils.setBoolean(UsageStatsHelper.USAGE_STATUS_SP_KEY, UsageStatsHelper.checkForPermission())
}
}
}

View File

@ -0,0 +1,57 @@
package com.gh.gamecenter.playedgame
import android.content.Context
import android.os.Bundle
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf
import com.gh.gamecenter.common.base.fragment.BaseBottomDialogFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.core.utils.CurrentActivityHolder
import com.gh.gamecenter.databinding.DialogPlayedGameMoreBinding
import com.gh.gamecenter.feature.entity.GameEntity
class PlayedGameMoreDialogFragment : BaseBottomDialogFragment<DialogPlayedGameMoreBinding>() {
private var gameEntity: GameEntity? = null
private var onClearClickListener: (() -> Unit)? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
gameEntity = arguments?.getParcelable(EntranceConsts.KEY_GAME_ENTITY)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
gameEntity?.let { mBinding.gameIconView.displayGameIcon(it) }
mBinding.gameNameTv.text = gameEntity?.name
mBinding.clearTv.setOnClickListener {
dismissAllowingStateLoss()
onClearClickListener?.invoke()
}
mBinding.cancelTv.setOnClickListener {
dismissAllowingStateLoss()
}
}
companion object {
fun show(
context: Context,
gameEntity: GameEntity,
onClearClickListener: () -> Unit,
) {
if (context is AppCompatActivity) {
context.supportFragmentManager
} else {
(CurrentActivityHolder.getCurrentActivity() as? AppCompatActivity)?.supportFragmentManager
}?.let {
val fragment = PlayedGameMoreDialogFragment().apply {
arguments = bundleOf(
EntranceConsts.KEY_GAME_ENTITY to gameEntity
)
}
fragment.onClearClickListener = onClearClickListener
fragment.show(it, fragment::class.java.simpleName)
}
}
}
}

View File

@ -0,0 +1,102 @@
package com.gh.gamecenter.playedgame
import android.annotation.SuppressLint
import android.app.Application
import androidx.lifecycle.MutableLiveData
import com.gh.gamecenter.common.baselist.ListViewModel
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.baselist.LoadType
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.retrofit.RetrofitManager
import com.lightgame.utils.Utils
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import okhttp3.ResponseBody
class PlayedGameViewModel(application: Application) : ListViewModel<GameEntity, GameEntity>(application) {
private val api = RetrofitManager.getInstance().api
private val recentlyPlayedGameRepository = RecentlyPlayedGameRepository()
private val compositeDisposable = CompositeDisposable()
val totalGameLiveData = MutableLiveData<String>()
val mostPlayedGameListLiveData = MutableLiveData<List<GameEntity>>()
val recentlyPlayedGameListLiveData = recentlyPlayedGameRepository.recentlyPlayedGameList
override fun load(loadType: LoadType?) {
if (mLoadStatusLiveData.value == LoadStatus.INIT || loadType == LoadType.REFRESH) {
getMostPlayedGame()
recentlyPlayedGameRepository.refreshRecentlyPlayedGame()
}
super.load(loadType)
}
override fun provideDataObservable(page: Int): Observable<MutableList<GameEntity>>? = null
@SuppressLint("CheckResult")
override fun provideDataSingle(page: Int): Single<MutableList<GameEntity>> =
api.getPlayedGamesWithResponse(UserManager.getInstance().userId, page, Utils.getTime(getApplication()), mapOf())
.map {
totalGameLiveData.postValue(it.headers()["Total"] ?: "0")
it.body()
}
override fun mergeResultLiveData() {
mResultLiveData.addSource(mListLiveData) {
it.forEach { game ->
game.hideSizeInsideDes = true
game.tagStyle.clear()
}
mResultLiveData.postValue(it)
}
}
@SuppressLint("CheckResult")
private fun getMostPlayedGame() {
api.getMostPlayedGames(UserManager.getInstance().userId, Utils.getTime(getApplication()))
.map { it.body() }
.compose(singleToMain())
.subscribe({
mostPlayedGameListLiveData.postValue(it.take(3))
}, {
it.printStackTrace()
mostPlayedGameListLiveData.postValue(emptyList())
}).let(compositeDisposable::add)
}
@SuppressLint("CheckResult")
fun deletePlayedGame(gameEntity: GameEntity) {
api.deletePlayedGame(UserManager.getInstance().userId, gameEntity.playedGameId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BiResponse<ResponseBody>() {
override fun onSuccess(data: ResponseBody) {
mListLiveData.value?.let {
for (game in it) {
if (gameEntity.id == game.id) {
it.remove(game)
mListLiveData.postValue(it)
break
}
}
}
}
override fun onFailure(exception: Exception) {
super.onFailure(exception)
Utils.toast(getApplication(), exception.localizedMessage)
}
}).let(compositeDisposable::add)
}
override fun onCleared() {
super.onCleared()
compositeDisposable.clear()
recentlyPlayedGameRepository.onClear()
}
}

View File

@ -0,0 +1,107 @@
package com.gh.gamecenter.playedgame
import android.annotation.SuppressLint
import androidx.lifecycle.MutableLiveData
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.retrofit.RetrofitManager
import com.halo.assistant.HaloApp
import com.lightgame.utils.Utils
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import java.util.concurrent.atomic.AtomicInteger
class RecentlyPlayedGameRepository {
private var disposable: Disposable? = null
private val api = RetrofitManager.getInstance().api
val recentlyPlayedGameList = MutableLiveData<List<GameEntity>>()
fun refreshRecentlyPlayedGame() {
onClear()
loadData()
}
@SuppressLint("CheckResult")
private fun loadData() {
disposable = loadAllPlayedGames()
.map { allGames ->
val heap = java.util.PriorityQueue<GameEntity>(RECENTLY_PLAYED_GAME_COUNT, compareBy { it.recentlyPlayedTime })
allGames.forEach { game ->
if (heap.size < RECENTLY_PLAYED_GAME_COUNT) {
heap.offer(game)
} else if (game.recentlyPlayedTime > (heap.peek()?.recentlyPlayedTime ?: 0L)) {
heap.poll()
heap.offer(game)
}
}
heap.sortedByDescending { it.recentlyPlayedTime }
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ topThree ->
recentlyPlayedGameList.postValue(topThree)
}, {
it.printStackTrace()
recentlyPlayedGameList.postValue(emptyList())
})
}
private fun loadAllPlayedGames(): Single<List<GameEntity>> {
return Single.create { emitter ->
val allGames = mutableListOf<GameEntity>()
val currentPage = AtomicInteger(1)
var hasMore = true
while (hasMore && !emitter.isDisposed) {
try {
val page = currentPage.getAndIncrement()
val games = loadPlayedGame(page).blockingGet()
if (games.isEmpty()) {
hasMore = false
} else {
allGames.addAll(games)
}
} catch (e: Exception) {
if (!emitter.isDisposed) {
emitter.onError(e)
}
return@create
}
}
if (!emitter.isDisposed) {
emitter.onSuccess(allGames)
}
}
}
private fun loadPlayedGame(page: Int): Single<List<GameEntity>> {
val paramsMap = mapOf("page_size" to 50)
return api.getPlayedGames(
UserManager.getInstance().userId,
page,
Utils.getTime(HaloApp.getInstance()),
paramsMap
)
.doOnSuccess { list ->
list.forEach { it.updateRecentlyPlayedTime() }
}
.onErrorReturn { emptyList() }
.subscribeOn(Schedulers.io())
}
fun onClear() {
disposable?.dispose()
}
companion object {
const val RECENTLY_PLAYED_GAME_COUNT = 3
}
}

View File

@ -63,6 +63,7 @@ import com.gh.gamecenter.entity.ImageInfoEntity;
import com.gh.gamecenter.entity.InterestedGameEntity;
import com.gh.gamecenter.entity.LibaoDetailEntity;
import com.gh.gamecenter.entity.MultiTabNav;
import com.gh.gamecenter.entity.MyHaloContentCard;
import com.gh.gamecenter.entity.MyVideoEntity;
import com.gh.gamecenter.entity.NewApiSettingsEntity;
import com.gh.gamecenter.entity.NewSettingsEntity;
@ -1792,6 +1793,9 @@ public interface ApiService {
@GET("users/{user_id}/played_games")
Single<List<GameEntity>> getPlayedGames(@Path("user_id") String userId, @Query("page") int page, @Query("timestamp") long timestamp, @QueryMap Map<String, Object> params);
@GET("users/{user_id}/played_games")
Single<Response<List<GameEntity>>> getPlayedGamesWithResponse(@Path("user_id") String userId, @Query("page") int page, @Query("timestamp") long timestamp, @QueryMap Map<String, Object> params);
/**
* 获取开服过滤标签
*/
@ -3548,4 +3552,27 @@ public interface ApiService {
@POST("/devices/{device_id}/installed_games")
Single<ResponseBody> postInstalledPackages(@Path("device_id") String deviceId, @Body RequestBody body);
/**
* 我的光环-会员卡片+常用功能+合规开关
*/
@GET("my_halo/content_card")
Single<MyHaloContentCard> getMyHaloContentCard();
/**
* 我的光环-获取游戏列表
*/
@POST("my_halo/game")
Single<List<GameEntity>> postMyHaloGame(@Query("tab_type") String tabType, @Query("page") int page, @Query("page_size") int pageSize, @Body RequestBody body);
/**
* 我的光环-游戏预约
*/
@GET("users/appointments")
Single<Response<List<GameEntity>>> getMyHaloReservation(@Query("status") String status, @Query("page") int page, @Query("page_size") int pageSize, @Query("timestamp") long timestamp);
/**
* 我的光环-用户玩得最多的游戏(时长从高到低)
*/
@GET("users/{user_id}/played_games_more")
Single<Response<List<GameEntity>>> getMostPlayedGames(@Path("user_id") String userId, @Query("timestamp") long timestamp);
}

View File

@ -10,8 +10,8 @@ interface ArticleHistoryDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun addArticle(answer: ArticleEntity)
@Query("select * from ArticleEntity order by orderTag desc limit :pageSize offset :offset ")
fun getArticleWithOffset(pageSize: Int, offset: Int): Single<List<ArticleEntity>>
@Query("select * from ArticleEntity where orderTag >= :startTimestamp order by orderTag desc limit :pageSize offset :offset ")
fun getArticleWithOffset(pageSize: Int, offset: Int, startTimestamp: Long): Single<List<ArticleEntity>>
@Delete
fun deleteArticle(article: ArticleEntity)

View File

@ -3,6 +3,7 @@ package com.gh.gamecenter.room.dao
import androidx.room.*
import com.gh.gamecenter.entity.HistoryGameEntity
import io.reactivex.Single
import kotlinx.coroutines.flow.Flow
@Dao
interface GameDao {
@ -10,8 +11,11 @@ interface GameDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun addGame(game: HistoryGameEntity)
@Query("select * from HistoryGameEntity order by orderTag desc limit :pageSize offset :offset ")
fun getGamesWithOffset(pageSize: Int, offset: Int): Single<List<HistoryGameEntity>>
@Query("select * from HistoryGameEntity where orderTag >= :startTimestamp order by orderTag desc limit :pageSize offset :offset ")
fun getGamesWithOffset(pageSize: Int, offset: Int, startTimestamp: Long): Single<List<HistoryGameEntity>>
@Query("SELECT COUNT(*) FROM HistoryGameEntity")
fun getGameCount(): Flow<Int>
@Delete
fun deleteGame(game: HistoryGameEntity)

View File

@ -10,8 +10,8 @@ interface GamesCollectionDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun addGamesCollection(gamesCollection: GamesCollectionEntity)
@Query("select * from GamesCollectionEntity order by orderTag desc limit :pageSize offset :offset")
fun getGamesCollectionWithOffset(pageSize: Int, offset: Int): Single<MutableList<GamesCollectionEntity>>
@Query("select * from GamesCollectionEntity where orderTag >= :startTimestamp order by orderTag desc limit :pageSize offset :offset")
fun getGamesCollectionWithOffset(pageSize: Int, offset: Int, startTimestamp: Long): Single<MutableList<GamesCollectionEntity>>
@Delete
fun deleteGamesCollection(gamesCollection: GamesCollectionEntity)

View File

@ -10,8 +10,8 @@ interface NewsHistoryDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun addNews(news: NewsEntity)
@Query("select * from NewsEntity order by orderTag desc limit :pageSize offset :offset ")
fun getNewsWithOffset(pageSize: Int, offset: Int): Single<List<NewsEntity>>
@Query("select * from NewsEntity where orderTag >= :startTimestamp order by orderTag desc limit :pageSize offset :offset ")
fun getNewsWithOffset(pageSize: Int, offset: Int, startTimestamp: Long): Single<List<NewsEntity>>
@Delete
fun deleteNews(news: NewsEntity)

View File

@ -9,8 +9,8 @@ interface VideoHistoryDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun addVideo(video: MyVideoEntity)
@Query("select * from MyVideoEntity where user!='' order by time desc limit :pageSize offset :offset")
fun getVideoWithOffset(pageSize: Int, offset: Int): Single<MutableList<MyVideoEntity>>
@Query("select * from MyVideoEntity where user!='' and time >= :startTimestamp order by time desc limit :pageSize offset :offset")
fun getVideoWithOffset(pageSize: Int, offset: Int, startTimestamp: Long): Single<MutableList<MyVideoEntity>>
@Query("select id from MyVideoEntity where videoStreamRecord=2 order by time desc")
fun getAttentionVideoRecord(): Single<MutableList<String>>

View File

@ -1119,12 +1119,12 @@ class SearchGameResultAdapter(
!hasMutualityZone -> {
// 单区服,直接启动
val zoneInfo = gameEntity.serviceArea.firstOrNull() ?: AcctGameInfo.ZoneInfo(0)
startAccelerating(gameEntity, context, zoneInfo, false)
startAccelerating(gameEntity, context, zoneInfo, false, SOURCE_ENTRANCE_SEARCH)
}
lastAcctGame != null -> {
// 多区服,有缓存的加速记录
startAccelerating(gameEntity, context, lastAcctGame.zoneInfo, true)
startAccelerating(gameEntity, context, lastAcctGame.zoneInfo, true, SOURCE_ENTRANCE_SEARCH)
}
else -> {
@ -1167,15 +1167,16 @@ class SearchGameResultAdapter(
}
}
private fun startAccelerating(
fun startAccelerating(
gameEntity: GameEntity,
context: Context,
zoneInfo: AcctGameInfo.ZoneInfo,
hasMultiZone: Boolean
hasMultiZone: Boolean,
sourceEntrance: String
) {
val request = AcceleratorValidator.Request(
gameEntity,
SOURCE_ENTRANCE_SEARCH
sourceEntrance
)
AcceleratorClient.newInstance()
.execute(context, request, object : AcceleratorValidator.ValidateListener {
@ -1186,7 +1187,7 @@ class SearchGameResultAdapter(
gameEntity,
true,
hasMultiZone,
SOURCE_ENTRANCE_SEARCH
sourceEntrance
)
}
})

View File

@ -17,9 +17,7 @@ import androidx.lifecycle.ProcessLifecycleOwner;
import androidx.multidex.MultiDexApplication;
import androidx.webkit.WebViewCompat;
import com.gh.common.util.AdHelper;
import com.gh.gamecenter.login.interceptor.LoginInterceptor;
import com.therouter.TheRouter;
import com.facebook.drawee.backends.pipeline.DraweeConfig;
import com.facebook.imageformat.DefaultImageFormats;
import com.facebook.imagepipeline.core.ImagePipelineConfig;
import com.facebook.imagepipeline.core.ImagePipelineFactory;
@ -33,6 +31,7 @@ import com.gh.common.filter.RegionSettingHelper;
import com.gh.common.interceptor.MainInterceptor;
import com.gh.common.prioritychain.GlobalPriorityChainHelper;
import com.gh.common.util.ActivationHelper;
import com.gh.common.util.AdHelper;
import com.gh.common.util.DataUtils;
import com.gh.common.util.DeviceTokenUtils;
import com.gh.common.util.DownloadNotificationHelper;
@ -53,6 +52,7 @@ import com.gh.gamecenter.common.constant.Constants;
import com.gh.gamecenter.common.entity.LinkEntity;
import com.gh.gamecenter.common.exposure.meta.MetaUtil;
import com.gh.gamecenter.common.image.EmptyDecoder;
import com.gh.gamecenter.common.image.SvgDecoder;
import com.gh.gamecenter.common.tracker.Tracker;
import com.gh.gamecenter.common.utils.DarkModeUtils;
import com.gh.gamecenter.common.utils.DeviceUtils;
@ -602,6 +602,12 @@ public class HaloApp extends MultiDexApplication {
// 所以这里尝试在 5.0 & 5.1 设备上关闭 fresco 的 native 解码
ImagePipelineConfig.Builder pipelineConfigBuilder = ImagePipelineConfig.newBuilder(this);
ImageDecoderConfig.Builder decodeConfigBuilder = ImageDecoderConfig.newBuilder();
// 添加SVG支持
decodeConfigBuilder.addDecodingCapability(SvgDecoder.SVG_FORMAT, new SvgDecoder.SvgFormatChecker(), new SvgDecoder.SvgImageDecoder());
DraweeConfig.Builder draweeConfigBuilder = DraweeConfig.newBuilder();
draweeConfigBuilder.addCustomDrawableFactory(new SvgDecoder.SvgDrawableFactory());
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP
|| Build.VERSION.SDK_INT == Build.VERSION_CODES.LOLLIPOP_MR1) {
pipelineConfigBuilder.setMemoryChunkType(MemoryChunkType.BUFFER_MEMORY)
@ -621,10 +627,12 @@ public class HaloApp extends MultiDexApplication {
.setImageDecoderConfig(decodeConfigBuilder.build())
.experiment()
.setNativeCodeDisabled(true);
} else {
pipelineConfigBuilder.setImageDecoderConfig(decodeConfigBuilder.build());
}
try {
BigImageViewer.initialize(FrescoImageLoader.with(this, pipelineConfigBuilder.build()));
BigImageViewer.initialize(FrescoImageLoader.with(this, pipelineConfigBuilder.build(), draweeConfigBuilder.build()));
} catch (Throwable e) {
e.printStackTrace();
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 178 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 51 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 157 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 194 KiB

After

Width:  |  Height:  |  Size: 280 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 73 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 287 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/white_alpha_10" />
<stroke
android:width="1dp"
android:color="@color/white_alpha_10" />
<corners android:radius="999dp" />
</shape>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<gradient
android:angle="90"
android:endColor="@color/ui_surface_55"
android:startColor="@color/ui_surface_0" />
</shape>

View File

@ -5,6 +5,6 @@
<gradient
android:angle="90"
android:endColor="@color/ui_surface"
android:startColor="@color/ui_background" />
android:startColor="@color/ui_surface_55" />
</shape>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="999dp" />
<stroke
android:width="1dp"
android:color="@color/ui_divider_2" />
</shape>

View File

@ -1,9 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="999dp" />
<solid android:color="#6640577A" />
<stroke
android:width="1dp"
android:color="#1A5A74CC" />
<gradient
android:endColor="#1AA6B9F5"
android:startColor="#33A6B9F5" />
</shape>

View File

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:bottom="8dp"
android:top="8dp">
<shape android:shape="rectangle">
<solid android:color="@color/ui_divider" />
<size android:width="1dp" />
<corners android:radius="999dp" />
</shape>
</item>
</layer-list>

View File

@ -8,6 +8,6 @@
android:pathData="M7,4.5L4,1.5L1,4.5"
android:strokeLineJoin="round"
android:fillColor="#00000000"
android:strokeColor="@color/text_primary"
android:strokeColor="@color/text_neutral"
android:strokeLineCap="round"/>
</vector>

View File

@ -0,0 +1,16 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<group>
<clip-path
android:pathData="M0,0h20v20h-20z"/>
<path
android:pathData="M5,2.5C3.619,2.5 2.5,3.619 2.5,5V14.167C2.5,15.547 3.619,16.667 5,16.667H9.167C9.627,16.667 10,16.294 10,15.833C10,15.373 9.627,15 9.167,15H5C4.54,15 4.167,14.627 4.167,14.167V5C4.167,4.54 4.54,4.167 5,4.167H14.167C14.627,4.167 15,4.54 15,5V9.167C15,9.627 15.373,10 15.833,10C16.294,10 16.667,9.627 16.667,9.167V5C16.667,3.619 15.547,2.5 14.167,2.5H5Z"
android:fillColor="@color/text_secondary"/>
<path
android:pathData="M15.833,12.5C15.833,12.04 15.46,11.667 15,11.667C14.54,11.667 14.167,12.04 14.167,12.5V14.167H12.5C12.04,14.167 11.667,14.54 11.667,15C11.667,15.46 12.04,15.833 12.5,15.833H14.167V17.5C14.167,17.96 14.54,18.333 15,18.333C15.46,18.333 15.833,17.96 15.833,17.5V15.833H17.5C17.96,15.833 18.333,15.46 18.333,15C18.333,14.54 17.96,14.167 17.5,14.167H15.833V12.5Z"
android:fillColor="@color/text_secondary"/>
</group>
</vector>

View File

@ -0,0 +1,14 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="20"
android:viewportHeight="20">
<group>
<clip-path
android:pathData="M0,0h20v20h-20z"/>
<path
android:pathData="M7.917,3.333C7.457,3.333 7.083,3.706 7.083,4.167V5H12.917V4.167C12.917,3.706 12.544,3.333 12.083,3.333H7.917ZM14.583,5L17.083,5C17.544,5 17.917,5.373 17.917,5.833C17.917,6.294 17.544,6.667 17.083,6.667H2.917C2.457,6.667 2.083,6.294 2.083,5.833C2.083,5.373 2.457,5 2.917,5L5.417,5V4.167C5.417,2.786 6.536,1.667 7.917,1.667H12.083C13.464,1.667 14.583,2.786 14.583,4.167V5ZM4.167,8.333C4.627,8.333 5,8.706 5,9.167V15.833C5,16.294 5.373,16.667 5.833,16.667H14.167C14.627,16.667 15,16.294 15,15.833V9.167C15,8.706 15.373,8.333 15.833,8.333C16.294,8.333 16.667,8.706 16.667,9.167V15.833C16.667,17.214 15.547,18.333 14.167,18.333H5.833C4.453,18.333 3.333,17.214 3.333,15.833V9.167C3.333,8.706 3.707,8.333 4.167,8.333ZM8.958,9.167C8.958,8.706 8.585,8.333 8.125,8.333C7.665,8.333 7.292,8.706 7.292,9.167V12.5C7.292,12.96 7.665,13.333 8.125,13.333C8.585,13.333 8.958,12.96 8.958,12.5V9.167ZM11.875,8.333C12.335,8.333 12.708,8.706 12.708,9.167V12.5C12.708,12.96 12.335,13.333 11.875,13.333C11.415,13.333 11.042,12.96 11.042,12.5V9.167C11.042,8.706 11.415,8.333 11.875,8.333Z"
android:fillColor="@color/text_secondary"
android:fillType="evenOdd"/>
</group>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="16dp"
android:height="16dp"
android:viewportWidth="16"
android:viewportHeight="16">
<path
android:fillColor="@color/text_neutral"
android:fillType="evenOdd"
android:pathData="M8,11.333C8.736,11.333 9.333,11.93 9.333,12.667C9.333,13.403 8.736,14 8,14C7.263,14 6.667,13.403 6.667,12.667C6.667,11.93 7.263,11.333 8,11.333ZM8,6.667C8.736,6.667 9.333,7.264 9.333,8C9.333,8.736 8.736,9.333 8,9.333C7.263,9.333 6.667,8.736 6.667,8C6.667,7.264 7.263,6.667 8,6.667ZM8,2C8.736,2 9.333,2.597 9.333,3.333C9.333,4.07 8.736,4.667 8,4.667C7.263,4.667 6.667,4.07 6.667,3.333C6.667,2.597 7.263,2 8,2Z" />
</vector>

View File

@ -0,0 +1,24 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="14dp"
android:height="14dp"
android:viewportWidth="14"
android:viewportHeight="14">
<path
android:pathData="M5.662,1.921C5.435,1.693 5.065,1.693 4.838,1.921L3.5,3.258L2.746,2.504C2.518,2.276 2.149,2.276 1.921,2.504C1.693,2.732 1.693,3.101 1.921,3.329L3.088,4.496C3.315,4.724 3.685,4.724 3.912,4.496L5.662,2.746C5.89,2.518 5.89,2.149 5.662,1.921Z"
android:fillColor="@color/text_tertiary"/>
<path
android:pathData="M7.292,3.208C7.292,2.886 7.553,2.625 7.875,2.625H11.667C11.989,2.625 12.25,2.886 12.25,3.208C12.25,3.53 11.989,3.792 11.667,3.792H7.875C7.553,3.792 7.292,3.53 7.292,3.208Z"
android:fillColor="@color/text_tertiary"/>
<path
android:pathData="M4.958,7C4.958,6.678 5.22,6.417 5.542,6.417H11.667C11.989,6.417 12.25,6.678 12.25,7C12.25,7.322 11.989,7.583 11.667,7.583H5.542C5.22,7.583 4.958,7.322 4.958,7Z"
android:fillColor="@color/text_tertiary"/>
<path
android:pathData="M4.958,10.792C4.958,10.469 5.22,10.208 5.542,10.208H11.667C11.989,10.208 12.25,10.469 12.25,10.792C12.25,11.114 11.989,11.375 11.667,11.375H5.542C5.22,11.375 4.958,11.114 4.958,10.792Z"
android:fillColor="@color/text_tertiary"/>
<path
android:pathData="M2.917,7.875C3.4,7.875 3.792,7.483 3.792,7C3.792,6.517 3.4,6.125 2.917,6.125C2.433,6.125 2.042,6.517 2.042,7C2.042,7.483 2.433,7.875 2.917,7.875Z"
android:fillColor="@color/text_tertiary"/>
<path
android:pathData="M2.917,11.667C3.4,11.667 3.792,11.275 3.792,10.792C3.792,10.308 3.4,9.917 2.917,9.917C2.433,9.917 2.042,10.308 2.042,10.792C2.042,11.275 2.433,11.667 2.917,11.667Z"
android:fillColor="@color/text_tertiary"/>
</vector>

View File

@ -0,0 +1,24 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="20dp"
android:height="20dp"
android:viewportWidth="14"
android:viewportHeight="14">
<path
android:pathData="M5.662,1.921C5.435,1.693 5.065,1.693 4.838,1.921L3.5,3.258L2.746,2.504C2.518,2.276 2.149,2.276 1.921,2.504C1.693,2.732 1.693,3.101 1.921,3.329L3.088,4.496C3.315,4.724 3.685,4.724 3.912,4.496L5.662,2.746C5.89,2.518 5.89,2.149 5.662,1.921Z"
android:fillColor="@color/text_secondary"/>
<path
android:pathData="M7.292,3.208C7.292,2.886 7.553,2.625 7.875,2.625H11.667C11.989,2.625 12.25,2.886 12.25,3.208C12.25,3.53 11.989,3.792 11.667,3.792H7.875C7.553,3.792 7.292,3.53 7.292,3.208Z"
android:fillColor="@color/text_secondary"/>
<path
android:pathData="M4.958,7C4.958,6.678 5.22,6.417 5.542,6.417H11.667C11.989,6.417 12.25,6.678 12.25,7C12.25,7.322 11.989,7.583 11.667,7.583H5.542C5.22,7.583 4.958,7.322 4.958,7Z"
android:fillColor="@color/text_secondary"/>
<path
android:pathData="M4.958,10.792C4.958,10.469 5.22,10.208 5.542,10.208H11.667C11.989,10.208 12.25,10.469 12.25,10.792C12.25,11.114 11.989,11.375 11.667,11.375H5.542C5.22,11.375 4.958,11.114 4.958,10.792Z"
android:fillColor="@color/text_secondary"/>
<path
android:pathData="M2.917,7.875C3.4,7.875 3.792,7.483 3.792,7C3.792,6.517 3.4,6.125 2.917,6.125C2.433,6.125 2.042,6.517 2.042,7C2.042,7.483 2.433,7.875 2.917,7.875Z"
android:fillColor="@color/text_secondary"/>
<path
android:pathData="M2.917,11.667C3.4,11.667 3.792,11.275 3.792,10.792C3.792,10.308 3.4,9.917 2.917,9.917C2.433,9.917 2.042,10.308 2.042,10.792C2.042,11.275 2.433,11.667 2.917,11.667Z"
android:fillColor="@color/text_secondary"/>
</vector>

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