Compare commits

..

1 Commits

Author SHA1 Message Date
cfde1e1e62 重构数据解析,ui组件刷新逻辑 2025-06-13 16:15:25 +08:00
228 changed files with 2946 additions and 13532 deletions

View File

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

View File

@ -824,14 +824,6 @@
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

Before

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@ -12,6 +12,7 @@ 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
@ -81,9 +82,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(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 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 selectedUrl =
when (contentText.substring(hypertextPositionPair.first, hypertextPositionPair.second)) {

View File

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

View File

@ -1,16 +0,0 @@
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

@ -1,46 +0,0 @@
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,10 +45,6 @@ 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,7 +10,4 @@ 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,7 +3,9 @@ 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
@ -13,5 +15,4 @@ 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,6 +4,7 @@ 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
@ -13,7 +14,6 @@ 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,10 +202,6 @@ 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,15 +1073,6 @@ 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,15 +3,12 @@ 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 {
@ -93,10 +90,6 @@ 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,21 +588,6 @@ 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,33 +52,9 @@ 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"))
}
}
}
}
/**
* 一般用于获取时间跨度较大的数据
*
@ -87,6 +63,10 @@ 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()
@ -115,7 +95,13 @@ object UsageStatsHelper {
*/
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP_MR1)
private fun getDataByUsageEvents(startTime: Long): JSONArray? {
val usageEvents = usageStatsManager.queryEvents(startTime, System.currentTimeMillis())
val mUsageStatsManager = HaloApp
.getInstance()
.application
.getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
val usageEvents = mUsageStatsManager.queryEvents(startTime, System.currentTimeMillis())
val allEvents = ArrayList<UsageEvents.Event>()
var currentEvent: UsageEvents.Event
@ -175,7 +161,7 @@ object UsageStatsHelper {
@SuppressLint("CheckResult")
@TargetApi(Build.VERSION_CODES.LOLLIPOP_MR1)
private fun postUsageStats(beginTime: Long, doOnEnd: (() -> Unit)? = null) {
private fun postUsageStats(beginTime: Long) {
debugOnly {
Utils.log("UsageStats->beginTime:$beginTime endTime:" + System.currentTimeMillis())
}
@ -190,7 +176,6 @@ object UsageStatsHelper {
debugOnly {
Utils.log("UsageStats: 没有可上传的数据")
}
doOnEnd?.invoke()
return
}
@ -201,7 +186,6 @@ object UsageStatsHelper {
debugOnly {
Utils.log("UsageStats: 数据上传成功")
}
doOnEnd?.invoke()
}
})
}
@ -223,7 +207,7 @@ object UsageStatsHelper {
@JvmStatic
@SuppressLint("CheckResult")
fun checkAndPostUsageStats(doOnSuccess: (() -> Unit)? = null) {
fun checkAndPostUsageStats() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) {
debugOnly {
Utils.log("UsageStats checkAndPostUsageStats Android 版本小于22")
@ -243,8 +227,6 @@ object UsageStatsHelper {
return
}
updateLastUsedTimeByUsageStats()
mApi.getUsageStatusUpdateTime(HaloApp.getInstance().gid)
.subscribe(object : BiResponse<ResponseBody>() {
override fun onSuccess(data: ResponseBody) {
@ -257,7 +239,7 @@ object UsageStatsHelper {
} else {
lastPostTime
}
postUsageStats(beginTime, doOnSuccess)
postUsageStats(beginTime)
} 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.MyHaloFragment
import com.gh.gamecenter.personal.HaloPersonalFragment
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 -> {
MyHaloFragment().setSuperiorChain(superiorChain).with(bundle)
HaloPersonalFragment().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_arrow_up)
sizeTv.setDrawableEnd(R.drawable.ic_auxiliary_arrow_up_primary_8)
val inflater = LayoutInflater.from(sizeTv.context)
val layout = inflater.inflate(R.layout.layout_filter_size, null)

View File

@ -1,47 +0,0 @@
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,21 +14,15 @@ 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 androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import com.gh.gamecenter.core.AppExecutor;
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,7 +83,10 @@ public class ArticleViewModel extends ListViewModel<NewsEntity, NewsEntity> {
if (ArticleFragment.COLLECTION.equals(type)) {
return Single.fromObservable(RetrofitManager.getInstance().getApi().getCollectionArticle(UserManager.getInstance().getUserId(), page));
} else {
return HistoryDatabase.Companion.getInstance().newsDao().getNewsWithOffset(20, (page - 1) * 20, TimeUtils.getTimestampMonthsAgo(3));
if (page > 5) {
return Single.create(emitter -> emitter.onSuccess(new ArrayList<>()));
}
return HistoryDatabase.Companion.getInstance().newsDao().getNewsWithOffset(20, (page - 1) * 20);
}
}

View File

@ -2,18 +2,17 @@ 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.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.login.user.UserManager
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.common.retrofit.BiResponse
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.retrofit.RetrofitManager
import com.lightgame.utils.Utils
import io.reactivex.Observable
@ -46,7 +45,11 @@ class CommunityArticleViewModel(application: Application) : ListViewModel<Articl
})
)
} else {
HistoryDatabase.instance.articleDao().getArticleWithOffset(20, (page - 1) * 20, TimeUtils.getTimestampMonthsAgo(3))
if (page > 5) {
Single.create { it.onSuccess(arrayListOf()) }
} else {
HistoryDatabase.instance.articleDao().getArticleWithOffset(20, (page - 1) * 20)
}
}
}

View File

@ -18,7 +18,6 @@ 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
@ -46,8 +45,11 @@ class GamesCollectionViewModel(
TYPE_COLLECT -> mApi.getFavoriteGameCollectionList(userId, page)
TYPE_HISTORY -> {
HistoryDatabase.instance.gamesCollectionDao()
.getGamesCollectionWithOffset(20, (page - 1) * 20, TimeUtils.getTimestampMonthsAgo(3))
if (page > 5) {
Single.create { it.onSuccess(arrayListOf()) }
} else {
HistoryDatabase.instance.gamesCollectionDao().getGamesCollectionWithOffset(20, (page - 1) * 20)
}
}
else -> {

View File

@ -1,14 +1,13 @@
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
@ -72,7 +71,12 @@ class VideoViewModel(application: Application) : ListViewModel<MyVideoEntity, My
override fun provideDataSingle(page: Int): Single<MutableList<MyVideoEntity>>? {
if (type == VideoFragment.VideoStyle.BROWSING_HISTORY.value) {
return HistoryDatabase.instance.videoHistoryDao().getVideoWithOffset(20, (page - 1) * 20, TimeUtils.getTimestampMonthsAgo(3))
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 null
}

View File

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

View File

@ -9,7 +9,7 @@ import io.reactivex.Single
class GameSubjectDSPRemoteDataSource(private val api: DspApiService = RetrofitManager.getInstance().dspApiService) {
fun getDspGames(count: Int): Single<List<GameEntity>> {
fun getDspGames(type: String, count: Int): Single<List<GameEntity>> {
val meta = MetaUtil.getMeta()
val request = mapOf(
"device" to mapOf(

View File

@ -1,26 +0,0 @@
package com.gh.gamecenter.entity
import com.gh.gamecenter.servers.gametest2.GameServerTestV2ViewModel
import com.google.gson.annotations.SerializedName
data class GameServerTestDisplaySetting(
@SerializedName("time_text_past")
val timeTextPast: String = RECENT,
@SerializedName("time_text_present")
val timeTextPresent: String = TODAY,
@SerializedName("time_text_future")
val timeTextFuture: String = FUTURE,
@SerializedName("game_category")
val gameCategory: List<String> = listOf(
GameServerTestV2ViewModel.GameCategory.Local.value,
GameServerTestV2ViewModel.GameCategory.Online.value,
GameServerTestV2ViewModel.GameCategory.Welfare.value,
GameServerTestV2ViewModel.GameCategory.Gjonline.value
),
) {
companion object {
const val RECENT = "近期"
const val TODAY = "今天"
const val FUTURE = "预约"
}
}

View File

@ -1,46 +0,0 @@
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

@ -1,33 +0,0 @@
package com.gh.gamecenter.entity
import com.gh.gamecenter.feature.entity.GameEntity
import com.google.gson.annotations.SerializedName
data class SearchGameUnionEntity(
@SerializedName("type")
private val _type: String? = null,
@SerializedName("link_game")
val linkGame: GameEntity? = null,
@SerializedName("link_wechat_game_cpm_column")
val linkWechatGameCpmColumn: SearchSubjectEntity? = null,
@SerializedName("link_dsp_game_column")
val linkDspGameColumn: SearchSubjectEntity? = null,
@SerializedName("link_wechat_game")
val linkWechatGame: GameEntity? = null,
@SerializedName("link_column")
val linkColum: SearchSubjectEntity? = null,
@SerializedName("link_ad_space")
val linkAdSpace: AdConfig? = null
) {
val type: String
get() = _type ?: ""
companion object {
const val TYPE_GAME = "game"
const val TYPE_WECHAT_GAME_CPM_COLUMN = "wechat_game_cpm_column"
const val TYPE_DSP_GAME_COLUMN = "dsp_game_column"
const val TYPE_WECHAT_GAME = "wechat_game"
const val TYPE_COLUMN = "column"
const val TYPE_AD_SPACE = "ad_space"
}
}

View File

@ -2,9 +2,8 @@ package com.gh.gamecenter.entity
import android.os.Parcelable
import com.gh.common.filter.RegionSettingHelper
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_DSP_GAME_COLUMN
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_WECHAT_GAME_CPM_COLUMN
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.personalhome.home.UserHistoryViewModel
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@ -19,6 +18,10 @@ data class SearchSubjectEntity(
val codeId: String = "", // 广告CODE_ID(本地字段),不为空时为广告专题
@SerializedName("ad_icon_active")
val adIconActive: Boolean = false,
// 本地字段标记是否为微信小游戏CPM专题
var isWGameSubjectCPM: Boolean = false,
// 本地字段标记是否为DSP专题
var isDspSubject: Boolean = false,
val type: String = "",
@SerializedName("column_type")
@ -28,11 +31,10 @@ data class SearchSubjectEntity(
val size: Int = -1, // 专题游戏数量
) : Parcelable {
val isWGameSubjectCPM: Boolean
get() = type == TYPE_WECHAT_GAME_CPM_COLUMN
val isDspSubject: Boolean
get() = type == TYPE_DSP_GAME_COLUMN
companion object {
const val TYPE_WECHAT_GAME_CPM_COLUMN = "wechat_game_cpm_column"
const val TYPE_DSP_GAME_COLUMN = "dsp_game_column"
}
fun getFilterGame() = RegionSettingHelper.filterGame(games)
}

View File

@ -815,10 +815,7 @@ class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
downloadStatus = gameEntity?.downloadStatusChinese ?: "",
gameType = gameEntity?.categoryChinese ?: "",
position = position,
tabContent = tabEntity.name,
linkType = tabEntity.link?.type ?: "",
linkId = tabEntity.link?.link ?: "",
linkText = tabEntity.link?.text ?: ""
tabContent = tabEntity.name
)
val entrance = if (mEntrance.contains("论坛详情")) "论坛" else "游戏"

View File

@ -361,7 +361,6 @@ 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,7 +5,6 @@ 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
@ -701,7 +700,7 @@ class GameDetailFragment : LazyFragment(), IScrollable {
val infoTagLocation = IntArray(2)
binding.infoTagContainer.getLocationInWindow(infoTagLocation)
val top = (infoTagLocation[1] - 12F.dip2px()).toFloat()
root.setTargetRect(RectF(8F.dip2px().toFloat(), top, (screenWidth - 8F.dip2px()).toFloat(), top + 53F.dip2px()))
root.targetRect.set(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,22 +8,20 @@ import android.widget.LinearLayout
import android.widget.PopupWindow
import androidx.appcompat.app.AppCompatActivity
import androidx.constraintlayout.widget.ConstraintLayout
import com.gh.common.util.DownloadItemUtils
import com.gh.gamecenter.common.constant.ItemViewType
import com.gh.common.util.*
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.common.viewholder.FooterViewHolder
import com.gh.gamecenter.core.utils.StringUtils
import com.gh.gamecenter.core.utils.*
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) {
@ -32,23 +30,6 @@ 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
@ -132,7 +113,7 @@ class HistoryGameListAdapter(context: Context, private val mViewModel: HistoryGa
"(浏览记录:游戏)",
StringUtils.buildString("浏览记录", ":", gameEntity.name)
)
DownloadItemUtils.updateItem(mContext, gameEntity, GameViewHolder(holder.binding), false)
DownloadItemUtils.updateItemWithViewOnlyStyle(GameViewHolder(holder.binding))
holder.itemView.setOnLongClickListener {
consume {
@ -203,28 +184,4 @@ 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,44 +4,20 @@ 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
@ -79,43 +55,4 @@ 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,86 +1,32 @@
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 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)
return if (page > 5) {
Single.create { it.onSuccess(arrayListOf()) }
} else {
emitter.onSuccess(gameList)
HistoryDatabase.instance.gameDao().getGamesWithOffset(20, (page - 1) * 20).map {
val gameEntityList = arrayListOf<GameEntity>()
for (history in it) {
gameEntityList.add(history.convertHistoryGameEntityToGameEntity())
}
gameEntityList
}
}
}

View File

@ -5,15 +5,12 @@ 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.common.utils.viewModelProvider
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.login.user.UserManager
class HistoryWrapperFragment : BaseFragment_TabLayout() {
@ -29,6 +26,7 @@ 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))
}
@ -81,6 +79,12 @@ 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

@ -246,10 +246,9 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable, IB
setNavigationTitle(it.title)
})
dataList.observe(viewLifecycleOwner) {
adapter.submitList(it) {
dataList.observe(viewLifecycleOwner) { (shouldScrollToTop, data) ->
adapter.submitList(data) {
if (shouldScrollToTop) {
shouldScrollToTop = false
binding.gameList.scrollToPosition(0)
}
}

View File

@ -29,7 +29,7 @@ interface OnCustomPageEventListener {
/**
* 换一批
*/
fun onChangeABatch(subjectEntity: SubjectEntity)
fun onChangeABatch(componentId: String, subjectEntity: SubjectEntity)
fun onChangeAppBarColor(color: Int)
@ -56,7 +56,10 @@ interface OnCustomPageEventListener {
/**
* 点击进入专题详情
*/
fun navigateSubjectDetailPage(item: CustomSubjectCollectionItem, subject: CustomPageData.LinkColumnCollection.CustomSubjectEntity)
fun navigateSubjectDetailPage(
item: CustomSubjectCollectionItem,
subject: CustomPageData.LinkColumnCollection.CustomSubjectEntity
)
/**

View File

@ -35,9 +35,11 @@ class SubjectEventHelper(viewModel: CustomPageViewModel) : CustomPageItemChildEv
gameEntity.tempDspLogMap = map.toMap() // Return an immutable copy
}
}
gameEntity.isMiniGame() -> {
tracker.trackMiniGameClick(_item, gameEntity)
}
else -> {
tracker.trackColumnClick(_item, gameEntity, "游戏")
}
@ -51,9 +53,11 @@ class SubjectEventHelper(viewModel: CustomPageViewModel) : CustomPageItemChildEv
gameEntity.isDspGame -> {
tracker.trackDspGameClick(_item, gameEntity, "按钮", "自定义页面")
}
gameEntity.isMiniGame() -> {
tracker.trackMiniGameClick(_item, gameEntity)
}
else -> {
tracker.trackColumnClick(_item, gameEntity, "按钮")
}
@ -67,9 +71,9 @@ class SubjectEventHelper(viewModel: CustomPageViewModel) : CustomPageItemChildEv
}
}
fun onChangeABatch(subject: SubjectEntity) {
fun onChangeABatch(componentId: String, subject: SubjectEntity) {
tracker.trackColumnClick(_item, null, "右上角", "换一批")
viewModel.onChangeABatch(subject)
viewModel.onChangeABatch(componentId,subject)
}
fun onMoreClick(link: LinkEntity) {

View File

@ -0,0 +1,185 @@
package com.gh.gamecenter.home.custom.model
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.entity.PKEntity
import com.gh.gamecenter.entity.AmwayCommentEntity
import com.gh.gamecenter.entity.DiscoveryCardEntity
import com.gh.gamecenter.entity.HomeItemTestV2Entity
import com.gh.gamecenter.entity.SubjectEntity
import com.gh.gamecenter.feature.entity.AcctRecord
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_LINK_TYPE_COLUMN_COLLECTION
import com.gh.vspace.VGameItemData
/**
* 接口返回的原始数据模型,最终需要转化成 CustomPageItem 在ui层呈现
*/
sealed class CustomItemDTO(
val componentId: String, // 当前组件的唯一标识,用于后续更新单个数据时,快速找到目标数据
val link: LinkEntity
)
// 游戏专题:展开大图样式专用
class GameItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
val data: GameEntity,
val linkColumn: SubjectEntity?,
) : CustomItemDTO(_componentId, _link)
//游戏专题
data class SubjectItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
var data: SubjectEntity
) : CustomItemDTO(_componentId, _link) {
val scrollState = ScrollState()
}
// 专题合集/游戏单合集
class CollectionItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
val data: CustomPageData.LinkColumnCollection
) : CustomItemDTO(_componentId, _link) {
val isSubjectCollection: Boolean // 是否是专题合集(还有可能是游戏单合集)
get() = link.type == CUSTOM_LINK_TYPE_COLUMN_COLLECTION
var showPage: Int = 0
var loadPage: Int = 1
var isLoadedEnd: Boolean = false
val uiState = UIState()
/**
* 保存ui层状态
*/
data class UIState(
var isBackToStart: Boolean = false
)
}
// 最近在玩
data class RecentGamesItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
var data: List<VGameItemData>
) : CustomItemDTO(_componentId, _link)
// 微信小游戏-最近在玩
data class RecentWechatMiniGamesItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
var data: List<GameEntity>
) : CustomItemDTO(_componentId, _link)
// qq小游戏-最近在玩
data class RecentQqMiniGamesItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
var data: List<GameEntity>,
val linkQqGameRecentlyPlayed: CustomPageData.LinkRecentlyPlayed
) : CustomItemDTO(_componentId, _link)
// 加速游戏最近在玩
data class RecentAcceleratorGamesItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
var data: List<AcctRecord>,
val moreLink: LinkEntity?
) : CustomItemDTO(_componentId, _link)
// qq小游戏专题
data class QqMiniGamesItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
val data: SubjectEntity
) : CustomItemDTO(_componentId, _link) {
val scrollState = ScrollState()
}
// 微信小游戏专题
data class WechatMiniGamesItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
val data: SubjectEntity
) : CustomItemDTO(_componentId, _link) {
val scrollState = ScrollState()
}
// 微信小游戏CPM专题组件
data class WeChatMiniGamesCPMSubjectItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
var data: SubjectEntity
) : CustomItemDTO(_componentId, _link){
val scrollState = ScrollState()
}
// DSP专题组件
data class DSPItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
var data: SubjectEntity
) : CustomItemDTO(_componentId, _link){
val scrollState = ScrollState()
}
// 插件化区域
data class PluginItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
var data: List<GameEntity>
) : CustomItemDTO(_componentId, _link)
// 新游开测
data class GameTestV2ItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
val data: HomeItemTestV2Entity
) : CustomItemDTO(_componentId, _link)
// 光环推荐
data class DiscoverCardItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
var data: DiscoveryCardEntity?
) : CustomItemDTO(_componentId, _link)
// 安利墙
data class AmwayItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
val data: List<AmwayCommentEntity>
) : CustomItemDTO(_componentId, _link)
// 内容卡片
data class ContentCardItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
val data: CustomPageData.ContentCard
) : CustomItemDTO(_componentId, _link)
// 通用内容合集
data class CommonContentCollectionItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
val data: CustomPageData.CommonContentCollection
) : CustomItemDTO(_componentId, _link)
// PK
data class PKItemDTO(
private val _componentId: String,
private val _link: LinkEntity,
var data: PKEntity?
) : CustomItemDTO(_componentId, _link)
class ScrollState(
var scrolledOffset: Int = 0
)

View File

@ -0,0 +1,42 @@
package com.gh.gamecenter.home.custom.model
import android.util.LruCache
import com.gh.gamecenter.feature.entity.GameEntity
import io.reactivex.Observable
import io.reactivex.Single
class CustomPageCacheDataSource {
// 计算内存缓存大小这里取应用最大可用内存的1/8单位KB
private fun calculateMemoryCacheSize(): Int {
val maxMemory = (Runtime.getRuntime().maxMemory() / 1024).toInt()
return maxMemory / 8
}
private val memoryCache: LruCache<String, List<GameEntity>> =
object : LruCache<String, List<GameEntity>>(calculateMemoryCacheSize()) {
override fun sizeOf(key: String, value: List<GameEntity>): Int {
return value.toTypedArray().size / 1024 // 单位KB
}
}
fun getGameList(subjectId: String) = Observable.create {
val gameList = memoryCache.get(subjectId)
if (gameList.isNullOrEmpty()) {
it.onComplete()
} else {
it.onNext(gameList)
}
}
fun putGameList(subjectId: String?, data: List<GameEntity>) {
if (!subjectId.isNullOrBlank()) {
memoryCache.put(subjectId, data)
}
}
fun onClear() {
memoryCache.evictAll()
}
}

View File

@ -15,10 +15,10 @@ import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.vspace.VGameItemData
abstract class CustomPageItem(
// 当前模块在数据列表中的位置注意非ui页面的位置而是数据列表中的位置因为某条数据可能会拆分成多个item
val link: LinkEntity,
var position: Int,
val componentPosition: Int
var position: Int,// 当前模块在数据列表中的位置注意非ui页面的位置而是数据列表中的位置因为某条数据可能会拆分成多个item
val componentPosition: Int,
val componentId: String,
) {
abstract val itemType: Int
@ -362,9 +362,11 @@ abstract class CustomPageItem(
data class CustomSubjectItem(
private val _link: LinkEntity,
val data: SubjectEntity,
private val scrollState: ScrollState,
private val _position: Int,
private val _componentPosition: Int
) : CustomPageItem(_link, _position, _componentPosition) {
private val _componentPosition: Int,
private val _componentId: String,
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int
get() = subjectTypeMap[data.type] ?: CUSTOM_PAGE_ITEM_TYPE_INVALID
@ -379,7 +381,11 @@ data class CustomSubjectItem(
get() = data.data ?: emptyList()
// 临时变量,记录横向列表滚动的距离
var scrolledOffset: Int = 0
var scrolledOffset: Int
get() = scrollState.scrolledOffset
set(value) {
scrollState.scrolledOffset = value
}
val exposureSource: List<ExposureSource>
get() = listOf(ExposureSource("专题", "${data.name ?: ""}+$componentStyle+${data.id}"))
@ -420,7 +426,7 @@ data class CustomSubjectItem(
if (latestDayBeforeTodayPair.first >= testDayOffset) {
latestDayBeforeTodayPair = Pair(testDayOffset, index)
}
} else if (testDayOffset > 0) {
} else {
if (testDayOffset <= latestDayAfterTodayPair.first) {
latestDayAfterTodayPair = Pair(testDayOffset, index)
}
@ -440,11 +446,12 @@ data class CustomSubjectItem(
data class CustomSplitSubjectItem(
private val _link: LinkEntity,
var data: SubjectEntity,
val step: Int,
val startChildPosition: Int,
private val _position: Int,
private val _componentPosition: Int,
val step: Int,
val startChildPosition: Int
) : CustomPageItem(_link, _position, _componentPosition) {
private val _componentId: String,
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
var splitItemCount: Int = -1 // 游戏专题拆分出来的子项数量
@ -509,8 +516,9 @@ data class CustomGameItem(
val linkColumn: SubjectEntity?,
val childPosition: Int,
private val _position: Int, // 当前 item 在 ui 中的位置
private val _componentPosition: Int // 当前组件的位置
) : CustomPageItem(_link, _position, _componentPosition) {
private val _componentPosition: Int, // 当前组件的位置
private val _componentId: String
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_GAME_HOME_GAME_ITEM
@ -546,9 +554,11 @@ data class CustomGameItem(
data class CustomSubjectCollectionItem(
private val _link: LinkEntity,
val data: CustomPageData.LinkColumnCollection,
val uiState: CollectionItemDTO.UIState,
private val _position: Int,
private val _componentPosition: Int
) : CustomPageItem(_link, _position, _componentPosition) {
private val _componentPosition: Int,
private val _componentId: String
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
val isSubjectCollection: Boolean // 是否是专题合集(还有可能是游戏单合集)
get() = link.type == CUSTOM_LINK_TYPE_COLUMN_COLLECTION
@ -572,19 +582,14 @@ data class CustomSubjectCollectionItem(
emptyList()
}
// 临时变量:记录合集中,轮换刷新时当前显示的页码
//当前显示的页码
var showPage: Int = 0
// 临时变量:记录合集中,轮换刷新时当前加载的页码
var loadPage: Int = 1
// 临时变量记录点击轮换刷新时是否正在loading
var isLoading: Boolean = false
// 临时变量,数据是否已全部加载完毕
var isLoadedEnd: Boolean = false
var isBackToStart = true
var isBackToStart: Boolean
get() = uiState.isBackToStart
set(value) {
uiState.isBackToStart = value
}
val showSubject: CustomPageData.LinkColumnCollection.CustomSubjectEntity?
get() = data.data.getOrNull(showPage % data.data.size)
@ -592,14 +597,12 @@ data class CustomSubjectCollectionItem(
override fun doAreItemsTheSame(other: CustomPageItem): Boolean {
return other is CustomSubjectCollectionItem
&& data.id == other.data.id
}
override fun doAreContentsTheSames(other: CustomPageItem): Boolean {
return other is CustomSubjectCollectionItem
&& showPage == other.showPage
&& isLoading == other.isLoading
&& isLoadedEnd == other.isLoadedEnd
&& showSubject == other.showSubject
}
}
@ -608,8 +611,9 @@ data class CustomRecentGamesItem(
private val _link: LinkEntity,
val data: List<VGameItemData>,
private val _position: Int,
private val _componentPosition: Int
) : CustomPageItem(_link, _position, _componentPosition) {
private val _componentPosition: Int,
private val _componentId: String
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_RECENT_PLAY
@ -628,8 +632,9 @@ data class CustomRecentWeChatMiniGamesItem(
private val _link: LinkEntity,
val data: List<GameEntity>,
private val _position: Int,
private val _componentPosition: Int
) : CustomPageItem(_link, _position, _componentPosition) {
private val _componentPosition: Int,
private val _componentId: String
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_MINI_GAME_RECENT_PLAY
@ -647,8 +652,9 @@ data class CustomWeChatMiniGamesCPMSubjectItem(
private val _link: LinkEntity,
val data: SubjectEntity,
private val _position: Int,
private val _componentPosition: Int
) : CustomPageItem(_link, _position, _componentPosition) {
private val _componentPosition: Int,
private val _componentId: String
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_MINI_GAME_RECENT_PLAY
@ -667,8 +673,9 @@ data class CustomRecentQqMiniGamesItem(
val data: List<GameEntity>,
val linkQqGameRecentlyPlayed: CustomPageData.LinkRecentlyPlayed,
private val _position: Int,
private val _componentPosition: Int
) : CustomPageItem(_link, _position, _componentPosition) {
private val _componentPosition: Int,
private val _componentId: String
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_MINI_GAME_RECENT_PLAY
@ -690,8 +697,9 @@ data class CustomRecentAcceleratorItem(
val data: List<AcctRecord>,
val moreLink: LinkEntity?,
private val _position: Int,
private val _componentPosition: Int
) : CustomPageItem(_link, _position, _componentPosition) {
private val _componentPosition: Int,
private val _componentId: String
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_ACCELERATOR_RECENT_PLAYED
@ -713,8 +721,9 @@ data class CustomPluginItem(
private val _link: LinkEntity,
val data: List<GameEntity>,
private val _position: Int,
private val _componentPosition: Int
) : CustomPageItem(_link, _position, _componentPosition) {
private val _componentPosition: Int,
private val _componentId: String
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_PLUGIN
@ -735,8 +744,9 @@ data class CustomGameTestV2Item(
private val _link: LinkEntity,
val data: HomeItemTestV2Entity,
private val _position: Int,
private val _componentPosition: Int
) : CustomPageItem(_link, _position, _componentPosition) {
private val _componentPosition: Int,
private val _componentId: String
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_NEW_GAME_TEST
@ -754,8 +764,9 @@ data class CustomDiscoverCardItem(
private val _link: LinkEntity,
val data: DiscoveryCardEntity?,
private val _position: Int,
private val _componentPosition: Int
) : CustomPageItem(_link, _position, _componentPosition) {
private val _componentPosition: Int,
private val _componentId: String
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
val spanCount = 3
@ -781,8 +792,9 @@ data class CustomAmwayItem(
private val _link: LinkEntity,
val data: List<AmwayCommentEntity>,
private val _position: Int,
private val _componentPosition: Int
) : CustomPageItem(_link, _position, _componentPosition) {
private val _componentPosition: Int,
private val _componentId: String
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_AMWAY_WALL
@ -800,8 +812,9 @@ data class CustomContentCardItem(
private val _link: LinkEntity,
val data: CustomPageData.ContentCard,
private val _position: Int,
private val _componentPosition: Int
) : CustomPageItem(_link, _position, _componentPosition) {
private val _componentPosition: Int,
private val _componentId: String
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_CONTENT_CARD
@ -822,8 +835,9 @@ data class CustomCommonContentCollectionItem(
private val _link: LinkEntity,
val data: CustomPageData.CommonContentCollection,
private val _position: Int,
private val _componentPosition: Int
) : CustomPageItem(_link, _position, _componentPosition) {
private val _componentPosition: Int,
private val _componentId: String
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int
get() = if (data.layout == COMMON_CONTENT_COLLECTION_LAYOUT_BANNER) {
@ -861,10 +875,11 @@ data class CustomCommonContentCollectionItem(
data class CustomSplitCommonContentCollectionItem(
private val _link: LinkEntity,
val data: CustomPageData.CommonContentCollection,
val leftPosition: Int,
private val _position: Int,
private val _componentPosition: Int,
val leftPosition: Int,
) : CustomPageItem(_link, _position, _componentPosition) {
private val _componentId: String
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType
get() = commonContentCollection[data.layout] ?: CUSTOM_PAGE_ITEM_TYPE_INVALID
@ -897,8 +912,9 @@ data class CustomPKItem(
private val _link: LinkEntity,
val data: PKEntity?,
private val _position: Int,
private val _componentPosition: Int
) : CustomPageItem(_link, _position, _componentPosition) {
private val _componentPosition: Int,
private val _componentId: String
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_PK
@ -916,8 +932,9 @@ data class CustomDspPlaceholderItem(
private val _link: LinkEntity,
val data: SubjectEntity,
private val _position: Int,
private val _componentPosition: Int
) : CustomPageItem(_link, _position, _componentPosition) {
private val _componentPosition: Int,
private val _componentId: String
) : CustomPageItem(_link, _position, _componentPosition, _componentId) {
override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_MINI_GAME_RECENT_PLAY
@ -930,14 +947,9 @@ data class CustomDspPlaceholderItem(
}
}
object CustomFooterItem : CustomPageItem(LinkEntity(), -1, -1) {
object CustomFooterItem : CustomPageItem(LinkEntity(), -1, -1, "") {
override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_FOOTER
}
object CustomInvalidItem : CustomPageItem(LinkEntity(), -2, -2) {
override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_INVALID
}

View File

@ -1,8 +1,10 @@
package com.gh.gamecenter.home.custom.model
import android.content.Context
import android.util.LruCache
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.feature.entity.FloatingWindowEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.halo.assistant.HaloApp
import java.text.SimpleDateFormat
import java.util.*

View File

@ -56,12 +56,17 @@ class CustomPageRemoteDataSource(
}
}
fun loadCollectionContent(item: CustomSubjectCollectionItem): Single<List<CustomPageData.LinkColumnCollection.CustomSubjectEntity>> =
if (item.isSubjectCollection) {
newApi.getColumnsCollectionContents(item.data.id, "component", item.loadPage + 1, 1)
} else {
newApi.getGameCollectionContents(item.data.id, "component", item.loadPage + 1, 1)
}
fun loadColumnsCollectionContents(
collectionId: String,
page: Int
): Single<MutableList<CustomPageData.LinkColumnCollection.CustomSubjectEntity>> =
newApi.getColumnsCollectionContents(collectionId, "component", page, 1)
fun loadGameCollectionContents(
collectionId: String,
page: Int
): Single<MutableList<CustomPageData.LinkColumnCollection.CustomSubjectEntity>> =
newApi.getGameCollectionContents(collectionId, "component", page, 1)
fun loadChangeSubjectGame(subjectEntity: SubjectEntity): Observable<List<GameEntity>> =
if (subjectEntity.isQQColumn) {

View File

@ -2,10 +2,13 @@ package com.gh.gamecenter.home.custom.model
import androidx.annotation.MainThread
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.asLiveData
import com.gh.common.filter.RegionSettingHelper
import com.gh.common.util.HomePluggableHelper
import com.gh.common.util.PackageUtils
import com.gh.download.DownloadManager
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.Constants.SP_HIDDEN_NOTIFICATIONS
import com.gh.gamecenter.common.utils.observableToMain
@ -17,7 +20,9 @@ import com.gh.gamecenter.entity.DiscoveryGameCardEntity
import com.gh.gamecenter.entity.DiscoveryGameCardLabel
import com.gh.gamecenter.feature.entity.AcctRecord
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.PluginLocation
import com.gh.gamecenter.gamedetail.accelerator.AccelerationDataBase
import com.gh.gamecenter.packagehelper.PackageRepository
import com.gh.gamecenter.retrofit.RetrofitManager
import com.google.gson.Gson
import com.google.gson.reflect.TypeToken
@ -34,6 +39,30 @@ class CustomPageShareRepository private constructor() {
private val compositeDisposable = CompositeDisposable()
private val _pluginGames = MediatorLiveData<List<GameEntity>>().apply {
addSource(PackageRepository.gameUpdateLiveData) { updateList ->
val gameList = arrayListOf<GameEntity>()
updateList.forEach {
if (it.isPluggable && it.indexPlugin == "open" && it.isShowPlugin(PluginLocation.only_index)) {
val game = it.transformGameEntity()
if (HomePluggableHelper.showHomePluggable(game)) {
game.setEntryMap(DownloadManager.getInstance().getEntryMap(game.name))
gameList.add(game)
}
}
}
// 下载七天排序
gameList.sortWith { o1, o2 -> o2.download - o1.download }
value = gameList
}
}
val pluginGames: LiveData<List<GameEntity>> = _pluginGames
fun temporarilyBlockGameInPluginItem(gameId: String) {
val gameList = _pluginGames.value ?: return
_pluginGames.value = gameList.filterNot { it.id == gameId }
}
private val _discoverData = MutableLiveData<DiscoveryCardEntity>()
val discoverData: LiveData<DiscoveryCardEntity> = _discoverData

View File

@ -26,10 +26,7 @@ import com.gh.gamecenter.home.custom.createExposureEvent
import com.gh.gamecenter.home.custom.eventlistener.CustomPageItemChildEventHelper
import com.gh.gamecenter.home.custom.eventlistener.GameSubjectCollectionEventHelper
import com.gh.gamecenter.home.custom.eventlistener.SubjectEventHelper
import com.gh.gamecenter.home.custom.model.CustomPageItem
import com.gh.gamecenter.home.custom.model.CustomSplitSubjectItem
import com.gh.gamecenter.home.custom.model.CustomSubjectCollectionItem
import com.gh.gamecenter.home.custom.model.CustomSubjectItem
import com.gh.gamecenter.home.custom.model.*
import com.lightgame.download.DownloadEntity
abstract class BaseCustomViewHolder(
@ -98,7 +95,14 @@ abstract class BaseCustomViewHolder(
titleBinding.root.goneIf(false)
setSubjectTitle(
titleBinding,
CustomSubjectItem(item.link, item.data, item.position, item.componentPosition),
CustomSubjectItem(
item.link,
item.data,
ScrollState(),
item.position,
item.componentPosition,
item.componentId
),
eventHelper,
titleType
)
@ -189,7 +193,7 @@ abstract class BaseCustomViewHolder(
when (subject.home) {
HOME_CHANGE -> {
// 点击换一批
eventHelper.onChangeABatch(subject)
eventHelper.onChangeABatch(item.componentId, subject)
}
HOME_MORE -> {

View File

@ -88,7 +88,7 @@ class CustomRefreshIconViewHolder(
} else {
_subjectEntity = subjectEntity
}
println("kayn -->isBackToStart:${item.isBackToStart}")
// 如果游戏全部被屏蔽,自动加载下一页
if (subjectEntity.games.isEmpty()) {
childEventHelper.onRotateRefresh()

View File

@ -3,24 +3,23 @@ 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.common.constant.Constants
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.retrofit.BiResponse
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.common.utils.SensorsBridge
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.entity.*
import com.gh.gamecenter.feature.entity.MessageUnreadEntity
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
@ -70,7 +69,7 @@ object MessageUnreadRepository {
@SuppressLint("CheckResult")
fun loadMessageUnreadTotal(isRecordData: Boolean = false) {
Observable.zip(
getMessageUnread(), getDiscoveryData(), getFunctionsUnreadCount(), getAddonsUnreadCount()
getMessageUnread(), getDiscoveryData(), getNewAddonsData(), getAddonsUnreadCount()
) { t1, t2, t3, t4 ->
zixunConcern.postValue(t2 > 0)
if (isRecordData) {
@ -164,19 +163,41 @@ object MessageUnreadRepository {
}
@SuppressLint("CheckResult")
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)
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()
}
})
}
}
@SuppressLint("CheckResult")
private fun getAddonsUnreadCount(): Observable<Int> {
return Observable.create {
if (CheckLoginUtils.isLogin()) {

View File

@ -38,19 +38,12 @@ object MiniGameRecentlyPlayUseCase {
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BiResponse<List<GameEntity>>() {
override fun onSuccess(data: List<GameEntity>) {
if (gameType == Constants.QQ_MINI_GAME) {
_qqRecentGamesItemLiveData.value = data
} else {
_wechatRecentGamesItemLiveData.value = data
}
}
override fun onFailure(exception: Exception) {
super.onFailure(exception)
if (gameType == Constants.QQ_MINI_GAME) {
_qqRecentGamesItemLiveData.value = emptyList()
} else {
_wechatRecentGamesItemLiveData.value = emptyList()
if (data.isNotEmpty()) {
if (gameType == Constants.QQ_MINI_GAME) {
_qqRecentGamesItemLiveData.value = data
} else {
_wechatRecentGamesItemLiveData.value = data
}
}
}
})
@ -90,10 +83,7 @@ object MiniGameRecentlyPlayUseCase {
_qqRecentGamesItemLiveData.value
} else {
_wechatRecentGamesItemLiveData.value
} ?: let {
loadRecentlyPlayedMiniGameList(gameType)// 最近在玩数据为空时,则调用接口获取数据
emptyList()
}
} ?: emptyList()
fun clearRecentlyPlayedMiniGameList(gameType: String = "") {
when (gameType) {

View File

@ -4,7 +4,7 @@ import com.gh.gamecenter.SearchActivity.Companion.TRACK_SEARCH_TYPE_INPUT
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.viewModelProvider
import com.gh.gamecenter.search.SearchGameResultFragment
import com.gh.gamecenter.search.viewmodel.SearchGameResultViewModel
import com.gh.gamecenter.search.SearchGameResultViewModel
import com.halo.assistant.HaloApp
/**
@ -19,6 +19,7 @@ class MiniGameSearchResultFragment : SearchGameResultFragment() {
val factory = SearchGameResultViewModel.Factory(
HaloApp.getInstance().application,
mKey,
true,
MiniGameSearchResultRepository(),
TRACK_SEARCH_TYPE_INPUT,
activity?.intent?.getStringExtra(EntranceConsts.KEY_SOURCE_ENTRANCE) ?: ""

View File

@ -1,25 +1,45 @@
package com.gh.gamecenter.minigame
import com.gh.gamecenter.dsp.data.GameSubjectDSPRemoteDataSource
import com.gh.gamecenter.entity.SearchSubjectEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.minigame.wechat.WGameSubjectCPMRemoteDataSource
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.retrofit.service.ApiService
import com.gh.gamecenter.search.ISearchGameResultRepository
import com.gh.gamecenter.search.SearchItemData
import io.reactivex.Observable
import io.reactivex.Single
class MiniGameSearchResultRepository(
private val api: ApiService = RetrofitManager.getInstance().newApi
private val api: ApiService = RetrofitManager.getInstance().newApi,
private val mWGameSubjectCPMDataSource: WGameSubjectCPMRemoteDataSource = WGameSubjectCPMRemoteDataSource(),
private val mGameSubjectDSPDataSource: GameSubjectDSPRemoteDataSource = GameSubjectDSPRemoteDataSource(RetrofitManager.getInstance().dspApiService)
) : ISearchGameResultRepository {
override fun getSearchGame(
key: String?,
page: Int
): Observable<List<SearchItemData>> {
): Observable<List<GameEntity>> {
return api.getSearchMiniGameList(key, page, 20)
.map { data ->
data.map {
SearchItemData(it, type = "")
}
}
}
override fun getSearchMiniGameCPM(key: String?): Observable<List<GameEntity>> {
return Observable.just(emptyList())
}
override fun getSearchSubject(key: String?, page: Int): Observable<List<SearchSubjectEntity>> {
return Observable.just(emptyList())
}
override fun getWGameCPMGameList(size: Int): Single<MutableList<GameEntity>> {
return mWGameSubjectCPMDataSource.getUserRecommendCPMList(pageSize = size)
}
override fun getDspGameList(
columnType: String,
showDownload: Boolean,
size: Int,
): Single<List<GameEntity>> {
return mGameSubjectDSPDataSource.getDspGames(columnType, size)
}
}

View File

@ -6,6 +6,7 @@ import com.gh.gamecenter.common.livedata.NonStickyMutableLiveData
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.feature.entity.GameEntity
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
/**
@ -14,11 +15,7 @@ import io.reactivex.schedulers.Schedulers
class WGameSubjectCPMListUseCase(
private val repository: WGameSubjectCPMListRepository = WGameSubjectCPMListRepository()
) {
/**
* 微信专题CPM请求记录用于避免重复请求以专题ID作为Key
*/
private val requestKeyList = mutableListOf<String>()
private val compositeDisposable = CompositeDisposable()
/**
* 微信小游戏CPM列表这里的LiveData充当类似于EventBus的角色因此使用NonStickyMutableLiveData
@ -26,21 +23,18 @@ class WGameSubjectCPMListUseCase(
private val _wechatMiniGameCPMListLiveData = NonStickyMutableLiveData<Pair<String, List<GameEntity>>>()
val wechatMiniGameCPMListLiveData: LiveData<Pair<String, List<GameEntity>>> = _wechatMiniGameCPMListLiveData
@SuppressLint("CheckResult")
fun getWechatMiniGameCPMList(subjectId: String?, minimumSize: Int, onlyFee: Boolean) {
if (subjectId.isNullOrEmpty() || requestKeyList.contains(subjectId)) {
return
}
requestKeyList.add(subjectId)
fun getWechatMiniGameCPMList(componentId: String, minimumSize: Int, onlyFee: Boolean) {
repository.getColumn(null, 1, null, null, null, null, minimumSize, onlyFee)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BiResponse<List<GameEntity>>() {
override fun onSuccess(data: List<GameEntity>) {
requestKeyList.remove(subjectId)
_wechatMiniGameCPMListLiveData.value = subjectId to data
_wechatMiniGameCPMListLiveData.value = componentId to data
}
})
}).let(compositeDisposable::add)
}
fun onClear() {
compositeDisposable.clear()
}
}

View File

@ -1,24 +1,41 @@
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.core.runOnUiThread
import com.gh.gamecenter.common.constant.RouteConsts
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 manageReservationUseCase = ManageReservationUseCase<GameEntity>()
private val repository = ReserveReminderRepository.newInstance()
private val compositeDisposable = CompositeDisposable()
override fun provideDataObservable(page: Int): Observable<List<GameEntity>>? {
return null
@ -51,56 +68,126 @@ class MyReservationViewModel(application: Application) : ListViewModel<GameEntit
}
fun cancelReservation(game: GameEntity) {
manageReservationUseCase.deleteOrCancelReservation(game, false) {
// 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)
}
}
deleteOrCancelReservation(game, false)
}
val enableAutoDownloadSuccessfully: LiveData<Event<Boolean>> = manageReservationUseCase.enableAutoDownloadSuccessfully
fun enableAutoDownload(enable: Boolean, games: Set<GameEntity>) {
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)
@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())
}
single.subscribe(object : BiResponse<ResponseBody>() {
override fun onSuccess(data: ResponseBody) {
ReservationRepository.refreshReservations()
if (deleteReservation) {
// MtaHelper.onEvent("预约游戏", "取消预约", game.name)
} else {
item
// 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)
}
}
newList
}) { data ->
mResultLiveData.value = data
override fun onFailure(exception: Exception) {
Utils.toast(getApplication(), exception.message)
exception.printStackTrace()
}
})
}
private val _enableAutoDownloadSuccessfully = MutableLiveData<Event<Boolean>>()
val enableAutoDownloadSuccessfully: LiveData<Event<Boolean>> = _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
}
}
newList
}
.compose(singleToMain())
.subscribe(object : BiResponse<List<GameEntity>>() {
override fun onSuccess(data: List<GameEntity>) {
mResultLiveData.value = data
_enableAutoDownloadSuccessfully.value = Event(enable)
}
}).let(compositeDisposable::add)
}
fun cancelReserveInBatch(games: Set<GameEntity>) {
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)
}
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()
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
}
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>>()
@ -111,7 +198,7 @@ class MyReservationViewModel(application: Application) : ListViewModel<GameEntit
override fun onCleared() {
super.onCleared()
manageReservationUseCase.onCleared()
compositeDisposable.clear()
}
}

View File

@ -26,10 +26,6 @@ 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,10 +4,7 @@ import android.annotation.SuppressLint
import android.content.Intent
import android.database.sqlite.SQLiteException
import android.graphics.Typeface
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.os.*
import android.text.TextUtils
import android.view.MotionEvent
import android.view.View
@ -17,7 +14,6 @@ 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
@ -214,7 +210,7 @@ class HaloPersonalFragment : BaseLazyFragment() {
linkText = ""
)
NewLogUtils.logMessageInformBellClick(
mStubBinding.loginMessageHint.isVisible,
mStubBinding.loginMessageHint.visibility == View.VISIBLE,
"我的"
)
SensorsBridge.trackMessageCenterClick()
@ -729,7 +725,7 @@ class HaloPersonalFragment : BaseLazyFragment() {
mStubBinding.personalUserName.text = getString(R.string.login_immediately)
mStubBinding.userIdTv.text = getString(R.string.login_immediately_hint)
if (mStubBinding.loginMessageHint.isVisible) {
if (mStubBinding.loginMessageHint.visibility == View.VISIBLE) {
mStubBinding.loginMessageHint.visibility = View.GONE
EventBus.getDefault().post(EBReuse(MESSAGE_READ_OVER))
}
@ -739,6 +735,7 @@ 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 =
@ -758,11 +755,13 @@ class HaloPersonalFragment : BaseLazyFragment() {
mStubBinding.historyTv.setOnClickListener(this)
mStubBinding.myCollectionTv.setOnClickListener(this)
mStubBinding.motionLayout.minimumHeight = DisplayUtils.getStatusBarHeight(resources) + 48F.dip2px()
val statusBarHeight =
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) 0 else DisplayUtils.getStatusBarHeight(resources)
mStubBinding.motionLayout.minimumHeight = statusBarHeight + 48F.dip2px()
mStubBinding.appbar.addOnOffsetChangedListener { _: AppBarLayout?, verticalOffset: Int ->
mStubBinding.appbar.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { _: AppBarLayout?, verticalOffset: Int ->
mStubBinding.listRefresh.isEnabled = abs(verticalOffset) <= 2
}
})
mStubBinding.listRefresh.setColorSchemeColors(
ContextCompat.getColor(
@ -857,13 +856,13 @@ class HaloPersonalFragment : BaseLazyFragment() {
root.goneIf(EnvHelper.isGATApp)
}
mStubBinding.userProtocolItem.run {
titleTv.text = getString(com.gh.gamecenter.common.R.string.setting_user_protocol)
titleTv.text = getString(R.string.setting_user_protocol)
iconIv.setImageResource(R.drawable.ic_personal_user_protocol)
root.setOnClickListener {
NewFlatLogUtils.logHaloSelfClick("其他功能", getString(com.gh.gamecenter.common.R.string.setting_user_protocol))
NewFlatLogUtils.logHaloSelfClick("其他功能", getString(R.string.setting_user_protocol))
SensorsBridge.trackHaloSelfClick(
profile = "其他功能",
text = getString(com.gh.gamecenter.common.R.string.setting_user_protocol),
text = getString(R.string.setting_user_protocol),
linkType = "",
linkId = "",
linkText = ""
@ -878,15 +877,15 @@ class HaloPersonalFragment : BaseLazyFragment() {
}
}
mStubBinding.privacyPolicyItem.run {
titleTv.text = getString(com.gh.gamecenter.common.R.string.setting_privacy_policy)
titleTv.text = getString(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(com.gh.gamecenter.common.R.string.setting_privacy_policy))
NewFlatLogUtils.logHaloSelfClick("其他功能", getString(R.string.setting_privacy_policy))
SensorsBridge.trackHaloSelfClick(
profile = "其他功能",
text = getString(com.gh.gamecenter.common.R.string.setting_privacy_policy),
text = getString(R.string.setting_privacy_policy),
linkType = "",
linkId = "",
linkText = ""
@ -906,13 +905,13 @@ class HaloPersonalFragment : BaseLazyFragment() {
}
}
mStubBinding.infoListItem.run {
titleTv.text = getString(com.gh.gamecenter.common.R.string.setting_info_list)
titleTv.text = getString(R.string.setting_info_list)
iconIv.setImageResource(R.drawable.ic_personal_info_list)
root.setOnClickListener {
NewFlatLogUtils.logHaloSelfClick("其他功能", getString(com.gh.gamecenter.common.R.string.setting_info_list))
NewFlatLogUtils.logHaloSelfClick("其他功能", getString(R.string.setting_info_list))
SensorsBridge.trackHaloSelfClick(
profile = "其他功能",
text = getString(com.gh.gamecenter.common.R.string.setting_info_list),
text = getString(R.string.setting_info_list),
linkType = "",
linkId = "",
linkText = ""
@ -920,20 +919,20 @@ class HaloPersonalFragment : BaseLazyFragment() {
startActivity(
WebActivity.getWebIntent(
requireContext(),
getString(com.gh.gamecenter.common.R.string.setting_info_list),
requireContext().getString(com.gh.gamecenter.common.R.string.info_list_url)
getString(R.string.setting_info_list),
requireContext().getString(R.string.info_list_url)
)
)
}
}
mStubBinding.sdkListItem.run {
titleTv.text = getString(com.gh.gamecenter.common.R.string.setting_sdk_list)
titleTv.text = getString(R.string.setting_sdk_list)
iconIv.setImageResource(R.drawable.ic_personal_sdk)
root.setOnClickListener {
NewFlatLogUtils.logHaloSelfClick("其他功能", getString(com.gh.gamecenter.common.R.string.setting_sdk_list))
NewFlatLogUtils.logHaloSelfClick("其他功能", getString(R.string.setting_sdk_list))
SensorsBridge.trackHaloSelfClick(
profile = "其他功能",
text = getString(com.gh.gamecenter.common.R.string.setting_sdk_list),
text = getString(R.string.setting_sdk_list),
linkType = "",
linkId = "",
linkText = ""
@ -941,20 +940,20 @@ class HaloPersonalFragment : BaseLazyFragment() {
startActivity(
WebActivity.getWebIntent(
requireContext(),
getString(com.gh.gamecenter.common.R.string.setting_sdk_list),
requireContext().getString(com.gh.gamecenter.common.R.string.sdk_list_url)
getString(R.string.setting_sdk_list),
requireContext().getString(R.string.sdk_list_url)
)
)
}
}
mStubBinding.permissionAndUsageItem.run {
titleTv.text = getString(com.gh.gamecenter.common.R.string.setting_permission_and_usage)
titleTv.text = getString(R.string.setting_permission_and_usage)
iconIv.setImageResource(R.drawable.ic_personal_permission)
root.setOnClickListener {
NewFlatLogUtils.logHaloSelfClick("其他功能", getString(com.gh.gamecenter.common.R.string.setting_permission_and_usage))
NewFlatLogUtils.logHaloSelfClick("其他功能", getString(R.string.setting_permission_and_usage))
SensorsBridge.trackHaloSelfClick(
profile = "其他功能",
text = getString(com.gh.gamecenter.common.R.string.setting_permission_and_usage),
text = getString(R.string.setting_permission_and_usage),
linkType = "",
linkId = "",
linkText = ""
@ -962,20 +961,20 @@ class HaloPersonalFragment : BaseLazyFragment() {
startActivity(
WebActivity.getWebIntent(
requireContext(),
getString(com.gh.gamecenter.common.R.string.setting_permission_and_usage),
requireContext().getString(com.gh.gamecenter.common.R.string.permission_and_usage_url)
getString(R.string.setting_permission_and_usage),
requireContext().getString(R.string.permission_and_usage_url)
)
)
}
}
mStubBinding.childrenPolicyItem.run {
titleTv.text = getString(com.gh.gamecenter.common.R.string.setting_children_policy)
titleTv.text = getString(R.string.setting_children_policy)
iconIv.setImageResource(R.drawable.ic_personal_children_policy)
root.setOnClickListener {
NewFlatLogUtils.logHaloSelfClick("其他功能", getString(com.gh.gamecenter.common.R.string.setting_children_policy))
NewFlatLogUtils.logHaloSelfClick("其他功能", getString(R.string.setting_children_policy))
SensorsBridge.trackHaloSelfClick(
profile = "其他功能",
text = getString(com.gh.gamecenter.common.R.string.setting_children_policy),
text = getString(R.string.setting_children_policy),
linkType = "",
linkId = "",
linkText = ""
@ -983,8 +982,8 @@ class HaloPersonalFragment : BaseLazyFragment() {
startActivity(
WebActivity.getWebIntent(
requireContext(),
getString(com.gh.gamecenter.common.R.string.setting_children_policy),
requireContext().getString(com.gh.gamecenter.common.R.string.children_policy_url)
getString(R.string.setting_children_policy),
requireContext().getString(R.string.children_policy_url)
)
)
}

View File

@ -1,114 +0,0 @@
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

@ -1,324 +0,0 @@
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

@ -1,210 +0,0 @@
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

@ -1,572 +0,0 @@
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

@ -1,196 +0,0 @@
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

@ -1,20 +0,0 @@
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

@ -1,234 +0,0 @@
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

@ -1,29 +0,0 @@
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

@ -1,88 +0,0 @@
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

@ -1,202 +0,0 @@
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

@ -1,34 +0,0 @@
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

@ -1,107 +0,0 @@
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

@ -1,496 +0,0 @@
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

@ -1,48 +0,0 @@
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

@ -1,167 +0,0 @@
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

@ -1,26 +0,0 @@
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

@ -1,71 +0,0 @@
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

@ -1,43 +0,0 @@
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

@ -1,307 +0,0 @@
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

@ -1,531 +0,0 @@
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

@ -1,110 +0,0 @@
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

@ -1,71 +0,0 @@
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

@ -1,240 +0,0 @@
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

@ -1,32 +0,0 @@
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

@ -1,230 +0,0 @@
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

@ -1,231 +0,0 @@
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

@ -1,57 +0,0 @@
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

@ -1,102 +0,0 @@
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

@ -1,107 +0,0 @@
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

@ -50,7 +50,6 @@ import com.gh.gamecenter.entity.GameColumnCollection;
import com.gh.gamecenter.entity.GameData;
import com.gh.gamecenter.entity.GameDigestEntity;
import com.gh.gamecenter.entity.GameGuidePopupEntity;
import com.gh.gamecenter.entity.GameServerTestDisplaySetting;
import com.gh.gamecenter.entity.GameServerTestTopGame;
import com.gh.gamecenter.entity.GameServerTestV2Entity;
import com.gh.gamecenter.entity.GameVideoInfo;
@ -63,7 +62,6 @@ 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;
@ -79,7 +77,6 @@ import com.gh.gamecenter.entity.RatingReplyEntity;
import com.gh.gamecenter.entity.RecommendPopupEntity;
import com.gh.gamecenter.entity.ReserveModifyEntity;
import com.gh.gamecenter.entity.ReserveReminderEntity;
import com.gh.gamecenter.entity.SearchGameUnionEntity;
import com.gh.gamecenter.entity.SearchSubjectEntity;
import com.gh.gamecenter.entity.ServerPublishEntity;
import com.gh.gamecenter.entity.ServerSubscriptionEntity;
@ -329,18 +326,6 @@ public interface ApiService {
@GET
Observable<List<GameEntity>> getSearchGame(@Url String url);
/**
* 搜索游戏(新:整合了 cpm dsp 广告位置排序问题)
*/
@GET("./games:search?view=union")
Observable<List<SearchGameUnionEntity>> getSearchGameUnionData(
@Query("keyword") String keyword,
@Query("page") int page,
@Query("version") String version,
@Query("channel") String channel,
@Query("AD") String ad
);
/**
* 搜索CPM微信小游戏
*/
@ -1793,9 +1778,6 @@ 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);
/**
* 获取开服过滤标签
*/
@ -3110,12 +3092,6 @@ public interface ApiService {
@GET("columns/tests/v2")
Observable<GameServerTestV2Entity> getServerTestV2(@Query("filter") String filter);
/**
* 新游开测-显示配置
*/
@GET("app/column_test_v2/{link_id}/display_setting")
Single<GameServerTestDisplaySetting> getGameServerTestDisplaySetting(@Path("link_id") String linkId);
/**
* 新游开测-详情列表
*/
@ -3552,27 +3528,4 @@ 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 where orderTag >= :startTimestamp order by orderTag desc limit :pageSize offset :offset ")
fun getArticleWithOffset(pageSize: Int, offset: Int, startTimestamp: Long): Single<List<ArticleEntity>>
@Query("select * from ArticleEntity order by orderTag desc limit :pageSize offset :offset ")
fun getArticleWithOffset(pageSize: Int, offset: Int): Single<List<ArticleEntity>>
@Delete
fun deleteArticle(article: ArticleEntity)

View File

@ -3,7 +3,6 @@ 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 {
@ -11,11 +10,8 @@ interface GameDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun addGame(game: 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>
@Query("select * from HistoryGameEntity order by orderTag desc limit :pageSize offset :offset ")
fun getGamesWithOffset(pageSize: Int, offset: Int): Single<List<HistoryGameEntity>>
@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 where orderTag >= :startTimestamp order by orderTag desc limit :pageSize offset :offset")
fun getGamesCollectionWithOffset(pageSize: Int, offset: Int, startTimestamp: Long): Single<MutableList<GamesCollectionEntity>>
@Query("select * from GamesCollectionEntity order by orderTag desc limit :pageSize offset :offset")
fun getGamesCollectionWithOffset(pageSize: Int, offset: Int): 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 where orderTag >= :startTimestamp order by orderTag desc limit :pageSize offset :offset ")
fun getNewsWithOffset(pageSize: Int, offset: Int, startTimestamp: Long): Single<List<NewsEntity>>
@Query("select * from NewsEntity order by orderTag desc limit :pageSize offset :offset ")
fun getNewsWithOffset(pageSize: Int, offset: Int): 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!='' and time >= :startTimestamp order by time desc limit :pageSize offset :offset")
fun getVideoWithOffset(pageSize: Int, offset: Int, startTimestamp: Long): Single<MutableList<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 id from MyVideoEntity where videoStreamRecord=2 order by time desc")
fun getAttentionVideoRecord(): Single<MutableList<String>>

View File

@ -1,7 +1,18 @@
package com.gh.gamecenter.search
import com.gh.gamecenter.entity.SearchSubjectEntity
import com.gh.gamecenter.feature.entity.GameEntity
import io.reactivex.Observable
import io.reactivex.Single
interface ISearchGameResultRepository {
fun getSearchGame(key: String?, page: Int): Observable<List<SearchItemData>>
fun getSearchGame(key: String?, page: Int): Observable<List<GameEntity>>
fun getSearchMiniGameCPM(key: String?): Observable<List<GameEntity>>
fun getSearchSubject(key: String?, page: Int): Observable<List<SearchSubjectEntity>>
fun getWGameCPMGameList(size: Int): Single<MutableList<GameEntity>>
fun getDspGameList(columnType: String, showDownload: Boolean, size: Int): Single<List<GameEntity>>
}

View File

@ -31,7 +31,6 @@ import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.SettingsEntity
import com.gh.gamecenter.feature.exposure.addExposureHelper
import com.gh.gamecenter.help.HelpAndFeedbackBridge
import com.gh.gamecenter.search.viewmodel.SearchGameResultViewModel
import com.halo.assistant.HaloApp
import com.lightgame.download.DataWatcher
import com.lightgame.download.DownloadEntity
@ -68,6 +67,7 @@ class SearchGameIndexFragment : ListFragment<GameEntity, SearchGameResultViewMod
SearchGameResultViewModel.Factory(
HaloApp.getInstance(),
mKey,
false,
SearchGameResultRepository(),
mType,
activity?.intent?.getStringExtra(EntranceConsts.KEY_SOURCE_ENTRANCE) ?: ""
@ -198,6 +198,7 @@ class SearchGameIndexFragment : ListFragment<GameEntity, SearchGameResultViewMod
this.mType = type
mAdapter?.key = key
mListViewModel?.updateSearchKeyWithType(key, type)
mListViewModel?.clearSearchSubjects()
mListViewModel?.load(LoadType.REFRESH)
}

View File

@ -1,6 +1,7 @@
package com.gh.gamecenter.search
import android.content.Context
import android.util.SparseArray
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@ -59,7 +60,6 @@ import com.gh.gamecenter.gamedetail.accelerator.dialog.StartingAcceleratorDialog
import com.gh.gamecenter.gamedetail.entity.GameDetailTabEntity
import com.gh.gamecenter.help.HelpAndFeedbackBridge
import com.gh.gamecenter.minigame.MiniGameSearchResultFragment
import com.gh.gamecenter.search.viewmodel.SearchGameResultViewModel
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
import com.lightgame.download.DownloadEntity
import com.lightgame.utils.Util_System_Keyboard
@ -1119,12 +1119,12 @@ class SearchGameResultAdapter(
!hasMutualityZone -> {
// 单区服,直接启动
val zoneInfo = gameEntity.serviceArea.firstOrNull() ?: AcctGameInfo.ZoneInfo(0)
startAccelerating(gameEntity, context, zoneInfo, false, SOURCE_ENTRANCE_SEARCH)
startAccelerating(gameEntity, context, zoneInfo, false)
}
lastAcctGame != null -> {
// 多区服,有缓存的加速记录
startAccelerating(gameEntity, context, lastAcctGame.zoneInfo, true, SOURCE_ENTRANCE_SEARCH)
startAccelerating(gameEntity, context, lastAcctGame.zoneInfo, true)
}
else -> {
@ -1167,16 +1167,15 @@ class SearchGameResultAdapter(
}
}
fun startAccelerating(
private fun startAccelerating(
gameEntity: GameEntity,
context: Context,
zoneInfo: AcctGameInfo.ZoneInfo,
hasMultiZone: Boolean,
sourceEntrance: String
hasMultiZone: Boolean
) {
val request = AcceleratorValidator.Request(
gameEntity,
sourceEntrance
SOURCE_ENTRANCE_SEARCH
)
AcceleratorClient.newInstance()
.execute(context, request, object : AcceleratorValidator.ValidateListener {
@ -1187,7 +1186,7 @@ class SearchGameResultAdapter(
gameEntity,
true,
hasMultiZone,
sourceEntrance
SOURCE_ENTRANCE_SEARCH
)
}
})

View File

@ -13,6 +13,7 @@ import android.view.animation.TranslateAnimation
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.exposure.DefaultExposureStateChangeListener
import com.gh.common.util.*
@ -40,10 +41,10 @@ import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.eventbus.EBStartupAcceleration
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.addExposureHelper
import com.gh.gamecenter.feature.entity.VipEntity
import com.gh.gamecenter.help.HelpAndFeedbackBridge
import com.gh.gamecenter.livedata.EventObserver
import com.gh.gamecenter.minigame.MiniGameSearchResultFragment
import com.gh.gamecenter.search.viewmodel.SearchGameResultViewModel
import com.gh.gamecenter.search.viewmodel.SearchTabViewModel
import com.halo.assistant.HaloApp
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
@ -125,6 +126,7 @@ open class SearchGameResultFragment : ListFragment<GameEntity, SearchGameResultV
SearchGameResultViewModel.Factory(
HaloApp.getInstance(),
mKey,
true,
SearchGameResultRepository(),
mType,
activity?.intent?.getStringExtra(EntranceConsts.KEY_SOURCE_ENTRANCE) ?: "",
@ -442,6 +444,7 @@ open class SearchGameResultFragment : ListFragment<GameEntity, SearchGameResultV
this.mKey = key
mAdapter?.key = key
mAdapter?.clearAdIdSet()
mListViewModel?.clearSearchSubjects()
mListViewModel?.load(LoadType.REFRESH)
}

View File

@ -1,218 +1,88 @@
package com.gh.gamecenter.search
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.gh.ad.AdDelegateHelper
import com.gh.common.filter.RegionSettingHelper
import com.gh.common.constant.Config
import com.gh.common.util.AdHelper
import com.gh.common.util.PackageUtils
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.safelyGetInRelease
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.dsp.data.GameSubjectDSPRemoteDataSource
import com.gh.gamecenter.entity.AdConfig
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_AD_SPACE
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_COLUMN
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_DSP_GAME_COLUMN
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_GAME
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_WECHAT_GAME
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_WECHAT_GAME_CPM_COLUMN
import com.gh.gamecenter.entity.SearchSubjectEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.livedata.Event
import com.gh.gamecenter.gamedetail.accelerator.AccelerationDataBase
import com.gh.gamecenter.minigame.wechat.WGameSubjectCPMRemoteDataSource
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.retrofit.service.ApiService
import com.halo.assistant.HaloApp
import io.reactivex.Observable
import io.reactivex.disposables.CompositeDisposable
import java.util.*
import java.util.concurrent.ConcurrentHashMap
import kotlin.random.Random
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import java.net.URLEncoder
import java.util.concurrent.TimeUnit
class SearchGameResultRepository(
private val mApi: ApiService = RetrofitManager.getInstance().api,
private val mNewApi: ApiService = RetrofitManager.getInstance().newApi,
private val mWGameSubjectCPMDataSource: WGameSubjectCPMRemoteDataSource = WGameSubjectCPMRemoteDataSource(),
private val mGameSubjectDSPDataSource: GameSubjectDSPRemoteDataSource = GameSubjectDSPRemoteDataSource(
RetrofitManager.getInstance().dspApiService
)
private val mGameSubjectDSPDataSource: GameSubjectDSPRemoteDataSource = GameSubjectDSPRemoteDataSource(RetrofitManager.getInstance().dspApiService)
) : ISearchGameResultRepository {
private val compositeDisposable = CompositeDisposable()
private var currentSearchKey: String? = null
private val _dataUpdateEvent = MutableLiveData<Event<SearchItemData>>()
val dataUpdateEvent: LiveData<Event<SearchItemData>> = _dataUpdateEvent
private var currentMiniGameCPMSearchList: MutableList<GameEntity>? = null
private val adGameOneIdSet = Collections.newSetFromMap(ConcurrentHashMap<String, Boolean>())
override fun getSearchGame(
key: String?,
page: Int
): Observable<List<GameEntity>> {
// 可能会有特殊字符,需要 encode 处理
val encodedKey = URLEncoder.encode(key, "utf-8")
return mApi.getSearchGame(
Config.API_HOST
+ "games:search?keyword=" + encodedKey
+ "&view=digest&page=" + page
+ "&channel=" + HaloApp.getInstance().channel
+ "&version=" + BuildConfig.VERSION_NAME
+ "&AD=" + AdHelper.getIdfaString()
)
.map { games ->
// 获取已加速成功过的区服
val gameCanSpeed = games.filter { it.canSpeed }
val gameIds = gameCanSpeed
.map { it.id }
val acctGameList = AccelerationDataBase.instance.accelerationDao().queryAcctGameInfoByGameId(gameIds)
gameCanSpeed.forEach { game ->
game.lastAcctGame = acctGameList.find { game.id == it.gameId }
}
games
}
}
override fun getSearchGame(key: String?, page: Int): Observable<List<SearchItemData>> {
if (page == 1) {
adGameOneIdSet.clear()
override fun getSearchMiniGameCPM(key: String?): Observable<List<GameEntity>> {
val currentMiniGameCPMSearchList = currentMiniGameCPMSearchList
if (key == currentSearchKey && currentMiniGameCPMSearchList != null) {
return Observable.just(currentMiniGameCPMSearchList)
}
val version = PackageUtils.getGhVersionName()
val channel = HaloApp.getInstance().channel
return mApi.getSearchGameUnionData(key, page, version, channel, AdHelper.getIdfaString())
.map { data ->
data.map {
when (it.type) {
TYPE_GAME -> SearchItemData(it.linkGame, type = it.type)
TYPE_WECHAT_GAME_CPM_COLUMN -> {
it.linkWechatGameCpmColumn?.let(::loadThirdPartyData)
SearchItemData(type = it.type, placeHolderId = it.linkWechatGameCpmColumn?.columnId)
}
TYPE_DSP_GAME_COLUMN -> {
it.linkDspGameColumn?.let(::loadThirdPartyData)
SearchItemData(type = it.type, placeHolderId = it.linkDspGameColumn?.columnId)
}
TYPE_WECHAT_GAME -> SearchItemData(game = it.linkWechatGame, type = it.type)
TYPE_COLUMN -> SearchItemData(subject = it.linkColum, type = it.type)
TYPE_AD_SPACE -> {
val adConfig = it.linkAdSpace
if (adConfig != null) {
val shouldShowAd = AdDelegateHelper.shouldShowGameSearchAd(adConfig)
when {
shouldShowAd && showThirdPartyAd(adConfig) -> {
SearchItemData(
ad = adConfig.thirdPartyAd,
adConfig = it.linkAdSpace,
type = TYPE_AD_SPACE
)
}
shouldShowAd && showOwnerAd(adConfig) -> {
loadOwnerAdData(adConfig)
SearchItemData(type = it.type, placeHolderId = getAdPlaceHolderId(adConfig))
}
else -> SearchItemData(type = it.type, placeHolderId = INVALID_PLACEHOLDER_ID)
}
} else {
SearchItemData(type = it.type, placeHolderId = INVALID_PLACEHOLDER_ID)
}
}
else -> {
// 无效数据
SearchItemData(type = it.type, placeHolderId = INVALID_PLACEHOLDER_ID)
}
}
}
return mNewApi.getSearchWechatMiniCPMGame(key)
.timeout(5, TimeUnit.SECONDS)
.onErrorReturnItem(emptyList())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext {
this.currentSearchKey = key
this.currentMiniGameCPMSearchList = it
}
}
private fun loadThirdPartyData(subject: SearchSubjectEntity) {
if (subject.isWGameSubjectCPM) {
mWGameSubjectCPMDataSource.getUserRecommendCPMList(pageSize = subject.size)
} else {
mGameSubjectDSPDataSource.getDspGames(subject.size)
}.compose(singleToMain())
.subscribe(object : BiResponse<List<GameEntity>>() {
override fun onSuccess(data: List<GameEntity>) {
subject.games = data
_dataUpdateEvent.value = Event(SearchItemData(type = subject.type, subject = subject))
}
}).let(compositeDisposable::add)
override fun getSearchSubject(key: String?, page: Int): Observable<List<SearchSubjectEntity>> {
return mApi.getSearchSubject(key, page)
}
private fun loadOwnerAdData(adConfig: AdConfig?) {
adConfig ?: return
val pageSize = adConfig.ownerAd?.adSource?.gamesIds?.size ?: 20
val codeId = adConfig.ownerAd?.id
val paramsMap = mapOf("page" to "1,$pageSize", "code_id" to codeId)
mNewApi.getAdGames(adConfig.id, paramsMap)
.map { data ->
data.forEach {
it.run {
adIconActive = adConfig.ownerAd?.adSource?.adIconActive ?: false
adSpaceId = adConfig.id
gameAdSourceId = adConfig.ownerAd?.id ?: ""
}
}
data
}
.compose(singleToMain())
.subscribe(object : BiResponse<List<GameEntity>>() {
override fun onSuccess(data: List<GameEntity>) {
val filterGameList = RegionSettingHelper.filterGame(data)
handleOwnerAdGames(adConfig, filterGameList)
}
override fun onFailure(exception: Exception) {
super.onFailure(exception)
handleOwnerAdGames(adConfig, emptyList())
}
}).let(compositeDisposable::add)
override fun getWGameCPMGameList(size: Int,): Single<MutableList<GameEntity>> {
return mWGameSubjectCPMDataSource.getUserRecommendCPMList(pageSize = size)
}
private fun handleOwnerAdGames(adConfig: AdConfig, games: List<GameEntity>) {
val showAdColumn = adConfig.ownerAd?.adSource?.displayStyle == "game_zone"
val showOnFailed = adConfig.displayRule.onFailedAction == "show"
when {
games.isNotEmpty() && showAdColumn -> {
SearchItemData(
subject = SearchSubjectEntity(
name = adConfig.ownerAd?.adSource?.title ?: "",
games = games.take(AD_SUBJECT_GAME_MAX_COUNT),
adId = adConfig.id,
codeId = adConfig.ownerAd?.id ?: "",
adIconActive = adConfig.ownerAd?.adSource?.adIconActive ?: false
),
adConfig = adConfig,
type = TYPE_AD_SPACE
)
}
games.isNotEmpty() -> {
var randomGame = games.safelyGetInRelease(Random.nextInt(games.size))
if (games.size > 1 && adGameOneIdSet.contains(randomGame?.id)) {
// 存在重复游戏时重新获取随机游戏
randomGame = games.safelyGetInRelease(Random.nextInt(games.size))
}
randomGame?.id?.let(adGameOneIdSet::add)
SearchItemData(game = randomGame, adConfig = adConfig, type = TYPE_AD_SPACE)
}
showOnFailed && adConfig.thirdPartyAd != null -> {
// 自有广告为空时,显示第三方广告
SearchItemData(ad = adConfig.thirdPartyAd, adConfig = adConfig, type = TYPE_AD_SPACE)
}
else -> null
}?.let {
_dataUpdateEvent.value = Event(it)
}
}
fun clear() {
compositeDisposable.clear()
}
companion object {
private const val AD_SUBJECT_GAME_MAX_COUNT = 8
private const val INVALID_PLACEHOLDER_ID = "Invalid"
fun getAdPlaceHolderId(adConfig: AdConfig?) = "${adConfig?.id}-${adConfig?.position}"
fun showThirdPartyAd(adConfig: AdConfig?) =
(adConfig?.displayRule?.adSource == AdDelegateHelper.AD_TYPE_SDK && adConfig.thirdPartyAd != null) ||
(adConfig?.displayRule?.adSource == AdDelegateHelper.AD_TYPE_OWNER &&
adConfig.ownerAd == null &&
adConfig.thirdPartyAd != null &&
adConfig.displayRule.onFailedAction == "show")
fun showOwnerAd(adConfig: AdConfig?) =
(adConfig?.displayRule?.adSource == AdDelegateHelper.AD_TYPE_OWNER &&
adConfig.ownerAd != null) ||
(adConfig?.displayRule?.adSource == AdDelegateHelper.AD_TYPE_SDK &&
adConfig.thirdPartyAd == null &&
adConfig.ownerAd != null &&
adConfig.displayRule.onFailedAction == "show")
override fun getDspGameList(
columnType: String,
showDownload: Boolean,
size: Int,
): Single<List<GameEntity>> {
return mGameSubjectDSPDataSource.getDspGames(columnType, size)
}
}

View File

@ -0,0 +1,469 @@
package com.gh.gamecenter.search
import android.annotation.SuppressLint
import android.app.Application
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.gh.ad.AdDelegateHelper
import com.gh.common.filter.RegionSettingHelper
import com.gh.common.util.PackageHelper
import com.gh.gamecenter.SearchActivity
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.baselist.ListViewModel
import com.gh.gamecenter.common.baselist.LoadParams
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.safelyGetInRelease
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.common.utils.toArrayList
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.entity.AdConfig
import com.gh.gamecenter.entity.SearchSubjectEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.livedata.Event
import com.gh.gamecenter.minigame.MiniGameSearchResultRepository
import com.gh.gamecenter.retrofit.RetrofitManager
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import java.util.concurrent.ConcurrentHashMap
import kotlin.random.Random
class SearchGameResultViewModel(
application: Application,
private var mSearchKey: String?,
private var mIsManuallySearch: Boolean,
private val repository: ISearchGameResultRepository,
private var mSearchType: String,
val sourceEntrance: String
) : ListViewModel<GameEntity, SearchItemData>(application) {
private var mPage = 0
private var mSearchSubjects = arrayListOf<SearchSubjectEntity>()
// 游戏广告列表,需要先本地按插入 position 排序,避免后续问题
private var mGameSearchAdList: List<AdConfig>? = null
private var mAdPositionSet: HashSet<Int>? = null
private val mAdGameMap = ConcurrentHashMap<String, List<GameEntity>>()
private var mGameEntityList = arrayListOf<GameEntity>()
fun updateSearchKeyWithType(searchKey: String, searchType: String) {
mSearchKey = searchKey
mSearchType = searchType
}
fun clearSearchSubjects() {
mSearchSubjects.clear()
}
override fun mergeResultLiveData() {
mResultLiveData.addSource(mListLiveData) { list ->
mGameEntityList = ArrayList(list)
decorateListAndPost(list)
}
}
@SuppressLint("CheckResult")
private fun decorateListAndPost(list: MutableList<GameEntity>) {
val itemDataList = ArrayList<SearchItemData>()
val combineGameList = list.toMutableList()
refreshWrongInstallStatus()
repository.getSearchMiniGameCPM(mSearchKey)
.zipWith(repository.getSearchSubject(mSearchKey, mPage)) { cpmGameList, subjectList -> // CPM游戏搜索结果列表合并
for (cpmGame in cpmGameList) {
if (cpmGame.location <= 0 || cpmGame.location > list.size) {
combineGameList.add(cpmGame)
} else {
combineGameList.add(cpmGame.location - 1, cpmGame)
}
}
itemDataList.addAll(
combineGameList.mapIndexed { index, game ->
SearchItemData(game = game, gamePosition = index, isFirst = index == 0)
}
)
subjectList
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ mutableList ->
// 数据源来自于第三方的专题列表,包括 CPM 专题和 DSP 专题
val thirdPartySearchSubjectList = mutableListOf<SearchSubjectEntity>()
for (item in mutableList) {
// 避免同一个位置重复的专题
if (!mSearchSubjects.any {
it.location == item.location && it.columnId == item.columnId
}) {
mSearchSubjects.add(item)
}
}
mSearchSubjects.forEach {
if (it.type == SearchSubjectEntity.TYPE_WECHAT_GAME_CPM_COLUMN) {
thirdPartySearchSubjectList.add(it.apply { isWGameSubjectCPM = true })
} else if (it.type == SearchSubjectEntity.TYPE_DSP_GAME_COLUMN) {
thirdPartySearchSubjectList.add(it.apply { isDspSubject = true })
}
val item = SearchItemData(subject = it)
if (it.location <= 0 || it.location > itemDataList.size) {
itemDataList.add(item)
} else {
itemDataList.add(it.location - 1, item)
}
}
// 处理初始化列表且游戏列表size为0的情况
handleLoadStatusWhenGameListIsEmpty(list, itemDataList)
if (mIsManuallySearch) {
if (mSearchKey == AdDelegateHelper.gameSearchKeyword) {
updateAdConfigAndDecorateList(itemDataList, list)
} else {
AdDelegateHelper.requestAdConfig(false, mSearchKey ?: "") {
updateAdConfigAndDecorateList(itemDataList, list)
}
}
} else {
postResultList(itemDataList, list)
}
if (thirdPartySearchSubjectList.isNotEmpty()) {
decorateListWithThirdPartyData(thirdPartySearchSubjectList, itemDataList, list)
}
}, {
it.printStackTrace()
handleLoadStatusWhenGameListIsEmpty(list, itemDataList)
mResultLiveData.postValue(itemDataList)
})
}
/**
* 请求第三方接口数据来填充专题游戏数据,包含 DSP 专题和 CPM 专题
*/
@SuppressLint("CheckResult")
private fun decorateListWithThirdPartyData(
subjects: List<SearchSubjectEntity>,
itemDataList: ArrayList<SearchItemData>,
list: MutableList<GameEntity>
) {
val subjectList = subjects.filterNot { it.games.isNotEmpty() }
if (subjectList.isEmpty()) return
val subjectSingleList = subjectList.map { subject ->
if (subject.isWGameSubjectCPM) {
repository.getWGameCPMGameList(subject.size)
.onErrorReturnItem(mutableListOf())
.map { subject.columnId to it }
} else {
repository.getDspGameList(subject.columnType, subject.showDownload, subject.size)
.onErrorReturnItem(mutableListOf())
.map { subject.columnId to it }
}
}
Single.zip(subjectSingleList) { it.map { item -> item as Pair<String, MutableList<GameEntity>> } }
.map { dataList ->
for (index in itemDataList.indices) {
val itemData = itemDataList[index]
val subject = itemData.subject ?: continue
if (subject.games.isNotEmpty()) continue
val pair = dataList.firstOrNull { data ->
data.first == subject.columnId
} ?: continue
val newItemData = SearchItemData(
subject = subject.copy(games = pair.second)
)
itemDataList[index] = newItemData
}
itemDataList
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ dataList -> postResultList(dataList, list) }, {})
}
@SuppressLint("CheckResult")
private fun updateAdConfigAndDecorateList(itemDataList: ArrayList<SearchItemData>, list: MutableList<GameEntity>) {
mGameSearchAdList =
AdDelegateHelper.getGameSearchAdList().filter { AdDelegateHelper.shouldShowGameSearchAd(it) }.toArrayList()
.apply { sortBy { it.position } }
val adPositionSet = hashSetOf<Int>()
if (!mGameSearchAdList.isNullOrEmpty()) {
for (config in mGameSearchAdList!!) {
adPositionSet.add(config.position)
}
}
mAdPositionSet = adPositionSet
if (adPositionSet.isNotEmpty()) {
val ownerAdList = arrayListOf<AdConfig>()
val thirdPartyAdList = arrayListOf<AdConfig>()
// 位置已经排过序,若插入位置大于列表的 size 那么直接 break 放弃
for ((index, position) in adPositionSet.withIndex()) {
if (position < itemDataList.size + index + 1) {
val adConfig = mGameSearchAdList!!.safelyGetInRelease(index)
val showThirdPartyAd = adConfig?.displayRule?.adSource == AdDelegateHelper.AD_TYPE_SDK
val showOwnerAd = adConfig?.displayRule?.adSource == AdDelegateHelper.AD_TYPE_OWNER
val showOnFailed = adConfig?.displayRule?.onFailedAction == "show"
if ((showThirdPartyAd && adConfig?.thirdPartyAd != null)
|| (showOwnerAd && adConfig?.ownerAd == null && adConfig?.thirdPartyAd != null && showOnFailed)
) {
thirdPartyAdList.add(adConfig)
} else if ((showOwnerAd && adConfig?.ownerAd != null)
|| (showThirdPartyAd && adConfig?.ownerAd != null && showOnFailed)
) {
ownerAdList.add(adConfig)
}
} else {
break
}
}
if (ownerAdList.isNotEmpty()) {
// 存在自有广告时获取对应广告ID的游戏
val requestSingleList = arrayListOf<Single<List<GameEntity>>>()
ownerAdList.forEach {
// 没有对应广告ID的游戏数据时才添加到请求列表
if (mAdGameMap[it.id] == null) {
requestSingleList.add(createAdGameListSingle(it))
}
}
if (requestSingleList.isNotEmpty()) {
Single.zip(requestSingleList) {}
.compose(singleToMain())
.subscribe({
decorateListWithAd(itemDataList, list)
}, {
decorateListWithAd(itemDataList, list)
})
} else {
decorateListWithAd(itemDataList, list)
}
} else {
decorateListWithThirdPartyAdOnly(itemDataList, thirdPartyAdList, list)
}
} else {
postResultList(itemDataList, list)
}
}
fun refreshWrongInstallStatus() {
if (mGameEntityList.isNotEmpty()) {
PackageHelper.refreshWrongInstallStatus(mGameEntityList)
}
}
private fun decorateListWithThirdPartyAdOnly(
itemDataList: ArrayList<SearchItemData>,
thirdPartyAdList: List<AdConfig>,
list: List<GameEntity>
) {
thirdPartyAdList.forEach {
itemDataList.add(it.position - 1, SearchItemData(ad = it.thirdPartyAd, adConfig = it))
SPUtils.setLong(Constants.SP_LAST_GAME_SEARCH_AD_SHOW_TIME + it.position, System.currentTimeMillis())
}
postResultList(itemDataList, list)
}
private fun decorateListWithAd(
itemDataList: ArrayList<SearchItemData>,
list: List<GameEntity>
) {
val adGameOneIdSet = HashSet<String>() // 展示样式为单个游戏时记录游戏ID避免重复
val decoratedItemDataSize = itemDataList.size
for ((index, position) in mAdPositionSet!!.withIndex()) {
if (position < decoratedItemDataSize + index + 1) {
val adConfig = mGameSearchAdList!!.safelyGetInRelease(index)
val showThirdPartyAd = adConfig?.displayRule?.adSource == AdDelegateHelper.AD_TYPE_SDK
val showOwnerAd = adConfig?.displayRule?.adSource == AdDelegateHelper.AD_TYPE_OWNER
val showOnFailed = adConfig?.displayRule?.onFailedAction == "show"
if ((showThirdPartyAd && adConfig?.thirdPartyAd != null)
|| (showOwnerAd && adConfig?.ownerAd == null && adConfig?.thirdPartyAd != null && showOnFailed)
) {
itemDataList.add(position - 1, SearchItemData(ad = adConfig.thirdPartyAd, adConfig = adConfig))
SPUtils.setLong(
Constants.SP_LAST_GAME_SEARCH_AD_SHOW_TIME + adConfig.position,
System.currentTimeMillis()
)
} else if ((showOwnerAd && adConfig?.ownerAd != null)
|| (showThirdPartyAd && adConfig?.ownerAd != null && showOnFailed)
) {
val gameList = mAdGameMap[adConfig.id]
if (!gameList.isNullOrEmpty()) {
if (adConfig.ownerAd.adSource?.displayStyle == "game_zone") {
// 游戏专题
itemDataList.add(
position - 1,
SearchItemData(
subject = SearchSubjectEntity(
name = adConfig.ownerAd.adSource?.title ?: "",
games = gameList.take(AD_SUBJECT_GAME_MAX_COUNT),
adId = adConfig.id,
codeId = adConfig.ownerAd.id,
adIconActive = adConfig.ownerAd.adSource?.adIconActive ?: false
),
adConfig = adConfig
)
)
} else {
// 单个游戏
var randomGameEntity = gameList.safelyGetInRelease(Random.nextInt(gameList.size))
if (gameList.size != 1 && adGameOneIdSet.contains(randomGameEntity?.id)) {
// 存在重复游戏时重新获取随机游戏
randomGameEntity = gameList.safelyGetInRelease(Random.nextInt(gameList.size))
}
randomGameEntity?.id?.let { adGameOneIdSet.add(it) }
itemDataList.add(
position - 1,
SearchItemData(game = randomGameEntity, adConfig = adConfig)
)
}
SPUtils.setLong(
Constants.SP_LAST_GAME_SEARCH_AD_SHOW_TIME + adConfig.position,
System.currentTimeMillis()
)
} else if (showOnFailed && adConfig.thirdPartyAd != null) {
// 自有广告为空时,显示第三方广告
itemDataList.add(position - 1, SearchItemData(ad = adConfig.thirdPartyAd, adConfig = adConfig))
SPUtils.setLong(
Constants.SP_LAST_GAME_SEARCH_AD_SHOW_TIME + adConfig.position,
System.currentTimeMillis()
)
}
}
} else {
break
}
}
postResultList(itemDataList, list)
}
@SuppressLint("CheckResult")
private fun createAdGameListSingle(adConfig: AdConfig) = Single.create { emitter ->
val pageSize = adConfig.ownerAd?.adSource?.gamesIds?.size ?: 20
val codeId = adConfig.ownerAd?.id
val paramsMap = mapOf("page" to "1,$pageSize", "code_id" to codeId)
RetrofitManager.getInstance().newApi.getAdGames(adConfig.id, paramsMap)
.compose(singleToMain())
.subscribe({ data ->
val filterGameList = RegionSettingHelper.filterGame(data)
filterGameList.forEach {
it.run {
adIconActive = adConfig.ownerAd?.adSource?.adIconActive ?: false
adSpaceId = adConfig.id
gameAdSourceId = adConfig.ownerAd?.id ?: ""
}
}
mAdGameMap[adConfig.id] = filterGameList
emitter.onSuccess(filterGameList)
}, {
mAdGameMap[adConfig.id] = emptyList()
emitter.onSuccess(emptyList<GameEntity>())
})
}
private fun postResultList(resultList: ArrayList<SearchItemData>, list: List<GameEntity>) {
mResultLiveData.postValue(resultList.toMutableList())
if (mPage == 1) {
if (repository is MiniGameSearchResultRepository) {
SensorsBridge.trackMiniGameSearchResultReturn(
GlobalActivityManager.getCurrentPageEntity().pageId,
GlobalActivityManager.getCurrentPageEntity().pageName,
sourceEntrance,
mSearchKey ?: "",
SearchActivity.toTrackSearchType(mSearchType),
list.isNotEmpty()
)
} else {
SensorsBridge.trackGameSearchResultReturn(
GlobalActivityManager.getCurrentPageEntity().pageId,
GlobalActivityManager.getCurrentPageEntity().pageName,
sourceEntrance,
mSearchKey ?: "",
SearchActivity.toTrackSearchType(mSearchType),
list.isNotEmpty()
)
}
}
}
private fun handleLoadStatusWhenGameListIsEmpty(list: List<GameEntity>, itemDataList: ArrayList<SearchItemData>) {
if (mPage == 1 && list.isEmpty()) {
mLoadStatusLiveData.value = if (itemDataList.isEmpty()) LoadStatus.INIT_EMPTY else LoadStatus.INIT_OVER
}
}
override fun loadStatusControl(size: Int) {
if (mCurLoadParams.loadOffset == LoadParams.DEFAULT_OFFSET) { // 初始化列表
if (size == 0) {
// 初始化列表size为0情况放到mergeResultLiveData方法处理
} else if (size == REQUEST_FAILURE_SIZE) {
mLoadStatusLiveData.setValue(LoadStatus.INIT_FAILED)
} else if (size < mOverLimitSize) { // 避免一个屏幕出现两次分页
mLoadStatusLiveData.setValue(LoadStatus.INIT_OVER)
} else {
mLoadStatusLiveData.setValue(LoadStatus.INIT_LOADED)
}
} else {
if (size == REQUEST_FAILURE_SIZE) {
mLoadStatusLiveData.setValue(LoadStatus.LIST_FAILED)
} else if (size == 0) {
mLoadStatusLiveData.setValue(LoadStatus.LIST_OVER)
} else {
mLoadStatusLiveData.setValue(LoadStatus.LIST_LOADED)
}
}
if (size == REQUEST_FAILURE_SIZE) {
mRetryParams = mCurLoadParams
} else {
mRetryParams = null
mCurLoadParams.loadOffset = mCurLoadParams.loadOffset + 1 // 页数 + 1
}
}
override fun provideDataObservable(page: Int): Observable<List<GameEntity>> {
mPage = page
return repository.getSearchGame(mSearchKey, page)
}
private val _refreshVipLoading = MutableLiveData<Event<Boolean>>()
val refreshVipLoading: LiveData<Event<Boolean>> = _refreshVipLoading
fun showRefreshVipLoading(show: Boolean) {
_refreshVipLoading.value = Event(show)
}
class Factory(
private val mApplication: Application,
private val mSearchKey: String?,
private val mIsManuallySearch: Boolean,
private val repository: ISearchGameResultRepository,
private val mSearchType: String,
private val mSourceEntrance: String
) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return SearchGameResultViewModel(
mApplication,
mSearchKey,
mIsManuallySearch,
repository,
mSearchType,
mSourceEntrance
) as T
}
}
companion object {
const val AD_SUBJECT_GAME_MAX_COUNT = 8
}
}

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