Compare commits

..

39 Commits

Author SHA1 Message Date
5bd882b489 chore: 版本更新至 5.42.5 2025-07-09 10:34:02 +08:00
1c1d1ff9b9 Merge branch 'hotfix/v5.42.4-1194/GHZSCY-8199' into 'release'
fix: 鸿蒙版本识别错误 https://jira.shanqu.cc/browse/GHZSCY-8199

See merge request halo/android/assistant-android!2228
2025-07-09 09:34:06 +08:00
a4e806d536 fix: 鸿蒙版本识别错误 https://jira.shanqu.cc/browse/GHZSCY-8199 2025-07-08 17:36:11 +08:00
122a0b929f Merge branch 'hotfix/v5.42.4-1194/GHZS-8203' into 'release'
fix: 已安装游戏替换问题 https://jira.shanqu.cc/browse/GHZSCY-8203

See merge request halo/android/assistant-android!2227
2025-07-08 17:28:11 +08:00
2a069fb5e7 fix: 已安装游戏替换问题 https://jira.shanqu.cc/browse/GHZSCY-8203 2025-07-08 17:14:49 +08:00
115b6638a8 Merge branch 'hotfix/v5.42.4-1194/huawei-deadsystemexception' into 'release'
fix: 华为系 android 10 设备停用静默获取已安装应用列表功能

See merge request halo/android/assistant-android!2226
2025-07-08 15:42:42 +08:00
ecbdcdced7 fix: 华为系 android 10 设备停用静默获取已安装应用列表功能 2025-07-08 14:54:08 +08:00
01db3b07d4 Merge branch 'fix/GHZSCY-8198' into 'release'
fix:【光环助手】鸿蒙系统安装弹窗「前往设置」按钮点击无反应 https://jira.shanqu.cc/browse/GHZSCY-8198

See merge request halo/android/assistant-android!2225
2025-07-08 09:45:19 +08:00
081658873f fix:【光环助手】鸿蒙系统安装弹窗「前往设置」按钮点击无反应 https://jira.shanqu.cc/browse/GHZSCY-8198 2025-07-08 09:45:19 +08:00
8d7157095d Merge branch 'feat/GHZSCY-8150' into 'release'
feat: GameDetailPageTabSelect埋点事件优化-客户端 https://jira.shanqu.cc/browse/GHZSCY-8150

See merge request halo/android/assistant-android!2224
2025-07-07 15:29:25 +08:00
1eb84ca71a feat: GameDetailPageTabSelect埋点事件优化-客户端 https://jira.shanqu.cc/browse/GHZSCY-8150 2025-07-07 15:27:56 +08:00
9f4149358a Merge branch 'hotfix/v5.42.4-1194/simulator_crash' into 'release'
fix: 修复 mumu 模拟器上 loadDataWithBaseUrl 方法的空指针闪退问题

See merge request halo/android/assistant-android!2223
2025-07-07 14:33:46 +08:00
5451f244bc Merge branch 'hotfix/v5.42.4-1194/subject_tab_crash' into 'release'
fix: 修复进入专题合集页面后快速返回出现的闪退问题...

See merge request halo/android/assistant-android!2222
2025-07-07 11:31:14 +08:00
aca80aceea fix: 修复进入专题合集页面后快速返回出现的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/456018/?project=22 2025-07-07 11:29:16 +08:00
964b391eec fix: 修复 mumu 模拟器上 loadDataWithBaseUrl 方法的空指针闪退问题 2025-07-07 10:37:08 +08:00
7584da3734 Merge branch 'hotfix/v5.42.4-1194/exposure_crash' into 'release'
fix: 修复曝光获取当前页面名称时偶发的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/456204

See merge request halo/android/assistant-android!2221
2025-06-30 15:01:27 +08:00
62060a5bf0 fix: 修复曝光获取当前页面名称时偶发的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/456204 2025-06-30 14:59:46 +08:00
f49caa11c8 Merge branch 'hotfix/v5.42.4-1194/tab_wrapper_crash' into 'release'
fix: 修复二级多tab导航页显示引导时出现的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/447802

See merge request halo/android/assistant-android!2219
2025-06-30 14:58:41 +08:00
61c2f48a50 fix: 修复二级多tab导航页显示引导时出现的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/447802 2025-06-30 14:43:40 +08:00
b9e0585dc9 chore: 版本更新至 5.42.4 2025-06-16 17:31:54 +08:00
9f191832fd Merge branch 'hotfix/v5.42.3-1193/GHZSCY-8143' into 'release'
fix: 修复 dsp 广告曝光上报问题 https://jira.shanqu.cc/browse/GHZSCY-8143

See merge request halo/android/assistant-android!2215
2025-06-16 14:48:43 +08:00
d8ee461a0a fix: 修复 dsp 广告曝光上报问题 2025-06-16 13:58:49 +08:00
99094896b1 Merge branch 'fix/libao_crash' into 'release'
fix: 修复礼包列表偶发闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/455408

See merge request halo/android/assistant-android!2214
2025-06-09 11:10:34 +08:00
23203b0c5d fix: 修复礼包列表偶发闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/455408 2025-06-09 10:46:01 +08:00
4e054aa834 chore: 版本更新至 5.42.3 2025-06-03 11:07:01 +08:00
e36cc54083 Merge branch 'fix/202506/454319' into 'release'
fix:https://sentry.shanqu.cc/organizations/lightgame/issues/454319/?project=22

See merge request halo/android/assistant-android!2213
2025-06-03 11:01:58 +08:00
bc5935d4b4 Merge branch 'hotfix/v5.42.2-1192/filter_crash' into 'release'
fix: 修复游戏屏蔽功能偶发的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/453949

See merge request halo/android/assistant-android!2212
2025-06-03 10:56:07 +08:00
8fc3bb720d fix:https://sentry.shanqu.cc/organizations/lightgame/issues/454319/?project=22 2025-06-03 10:45:27 +08:00
e8b68f889d fix: 修复游戏屏蔽功能偶发的闪退问题 2025-06-03 10:42:36 +08:00
649af64e62 Merge branch 'fix/GHZSCY-8061' into 'release'
fix:【光环助手】自定义专题合集-自定义设置点击问题 https://jira.shanqu.cc/browse/GHZSCY-8061

See merge request halo/android/assistant-android!2211
2025-05-30 13:39:50 +08:00
46516fb9c0 fix:【光环助手】自定义专题合集-自定义设置点击问题 https://jira.shanqu.cc/browse/GHZSCY-8061 2025-05-30 13:39:50 +08:00
eecfdced32 chore: 版本更新至 5.42.2 2025-05-30 11:37:13 +08:00
74d9aaf664 Merge branch 'hotfix/v5.42.1-1191/exposure_crash' into 'release'
fix: 修复曝光收集时的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/454091

See merge request halo/android/assistant-android!2210
2025-05-30 11:35:17 +08:00
28bbb128dd fix: 修复曝光收集时的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/454091 2025-05-30 09:43:19 +08:00
7640812518 chore: 版本更新至 5.42.1 2025-05-29 11:03:59 +08:00
0a30e82e17 Merge branch 'hotfix/v5.42.0-1190/exposure_crash' into 'release'
fix: 修复曝光收集时的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/453915

See merge request halo/android/assistant-android!2208
2025-05-29 11:01:45 +08:00
559951ad82 fix: 修复曝光收集时的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/453915 2025-05-29 10:43:08 +08:00
4a4cba5445 Merge branch 'feat/GHZSCY-8049-merge' into 'release'
feat: 游戏详情UI优化-客户端 https://jira.shanqu.cc/browse/GHZSCY-8049

See merge request halo/android/assistant-android!2209
2025-05-29 10:22:59 +08:00
b6d6568ac9 feat: 游戏详情UI优化-客户端 https://jira.shanqu.cc/browse/GHZSCY-8049 2025-05-29 10:21:47 +08:00
62 changed files with 1020 additions and 936 deletions

View File

@ -18,6 +18,7 @@ import com.therouter.TheRouter
import com.facebook.drawee.controller.BaseControllerListener
import com.facebook.drawee.view.SimpleDraweeView
import com.facebook.imagepipeline.image.ImageInfo
import com.g00fy2.versioncompare.Version
import com.gh.common.exposure.ExposureManager
import com.gh.common.util.DirectUtils.directToLinkPage
import com.gh.common.util.LogUtils
@ -186,11 +187,11 @@ object AdDelegateHelper {
"halo_launch" -> {
config.ownerAd?.startAd?.let { it.id = config.ownerAd.id }
// HarmonyOS 2.2.0 版本不展示第三方开屏广告 (因为会引起奇怪的闪退)
if (MetaUtil.getRom().name == "HarmonyOS"
&& MetaUtil.getRom().versionName == "2.2.0"
&& config.displayRule.adSource == AD_TYPE_SDK) {
// HarmonyOS 4.2.0 版本不展示第三方开屏广告 (因为会引起奇怪的闪退)
if (MetaUtil.getRom().romName == "HarmonyOS"
&& Version(MetaUtil.getRom().romVersion).isLowerThan(Version("4.2.0"))
&& config.displayRule.adSource == AD_TYPE_SDK
) {
return
}

View File

@ -390,7 +390,7 @@ public class Config {
"manufacturer", Build.MANUFACTURER,
"model", Build.MODEL,
"android_sdk_version", String.valueOf(Build.VERSION.SDK_INT),
"rom", MetaUtil.INSTANCE.getRom().name() + " " + MetaUtil.INSTANCE.getRom().getVersionName()
"rom", MetaUtil.INSTANCE.getRom().getRomName() + " " + MetaUtil.INSTANCE.getRom().getRomVersion()
);
RetrofitManager.getInstance()

View File

@ -16,6 +16,7 @@ class DefaultExposureStateChangeListener : IExposureStateChangeListener {
val exposureStatus = if (inExposure) "曝光中" else "结束曝光"
val isCPMExposureEvent = exposureEvent.payload.miniGameType == Constants.WECHAT_MINI_GAME_CPM
val isDSPExposureEvent = exposureEvent.payload.miniGameType == Constants.DSP_GAME
Utils.log(
RecyclerViewExposureHelper.TAG,
@ -29,6 +30,12 @@ class DefaultExposureStateChangeListener : IExposureStateChangeListener {
"上报 CPM 曝光 ${exposureEvent.payload.gameName} ${exposureEvent.id}"
)
ExposureManager.logCPM(exposureEvent)
} else if (isDSPExposureEvent && inExposure) {
Utils.log(
RecyclerViewExposureHelper.TAG,
"上报 DSP 曝光 ${exposureEvent.payload.gameName} ${exposureEvent.id}"
)
ExposureManager.logDSP(exposureEvent)
}
if (!inExposure

View File

@ -111,6 +111,12 @@ object ExposureManager {
}
}
fun logDSP(event: ExposureEvent) {
AppExecutor.logExecutor.execute {
DspReportHelper.report(event.payload.showUrl)
}
}
/**
* @param forcedUpload Ignore all restrictions.
*/

View File

@ -73,7 +73,7 @@ object RegionSettingHelper {
if (list is ArrayList) return list
}
val listCopy: ArrayList<GameEntity> = if (list is ArrayList) list else ArrayList(list)
val listCopy: ArrayList<GameEntity> = ArrayList(list)
listCopy.removeAll { mFilterGameIdSet?.contains(it.id) ?: false }
return listCopy
}

View File

@ -108,21 +108,32 @@ object GameSubstituteRepositoryHelper {
var thisPositionNeedToBeReplaced = false
// 检查是否已安装该游戏里同包名的 APK
for (apk in game.getApk()) {
if (PackageHelper.validLocalPackageNameSet.contains(apk.packageName)) {
if (game.getApk().size == 1) {
val apk = game.getApk().firstOrNull()
// 若该游戏只有一个 APK且该 APK 的包名在本地已安装的包名列表中
if (PackageHelper.localPackageNameSet.contains(apk?.packageName)) {
// 将该位置的游戏标记为需要替换
positionOfGameToBeReplacedList.add(index)
thisPositionNeedToBeReplaced = true
break
}
} else {
// 检查是否已安装该游戏里同包名的 APK
for (apk in game.getApk()) {
if (PackageHelper.validLocalPackageNameSet.contains(apk.packageName)) {
// 将该位置的游戏标记为需要替换
positionOfGameToBeReplacedList.add(index)
thisPositionNeedToBeReplaced = true
break
}
}
}
// 检查是否已安装该游戏 id
if (PackagesManager.getInstalledDataByGameId(game.id) != null) {
if (!thisPositionNeedToBeReplaced && PackagesManager.getInstalledDataByGameId(game.id) != null) {
// 将该位置的游戏标记为需要替换
positionOfGameToBeReplacedList.add(index)
thisPositionNeedToBeReplaced = true
break
continue
}
// 若此游戏所包含的 apk 没有已安装,那么再检查是否已安装有预设相关包名
@ -152,11 +163,19 @@ object GameSubstituteRepositoryHelper {
if (mGameCollectionList.isNullOrEmpty()) return
// 临时的游戏 ID 列表(包含 gameList 的游戏),避免重复替换
val tempDisplayingGameIdSet = HashSet(displayingGameIdSet)
gameList.forEach {
tempDisplayingGameIdSet.add(it.id)
}
for (position in positionOfGameToBeReplacedList) {
val validGame = getValidGame(relatedCollectionId, displayingGameIdSet)
val validGame = getValidGame(relatedCollectionId, tempDisplayingGameIdSet)
validGame?.let {
gameList[position] = it
displayingGameIdSet.add(it.id)
tempDisplayingGameIdSet.add(it.id)
}
}
}

View File

@ -245,6 +245,7 @@ public class LogUtils {
object.put("jnfj", MetaUtil.getBase64EncodedIMEI());
object.put("G_ID", UserManager.getInstance().getDeviceId());
object.put("oaid", HaloApp.getInstance().getOAID());
object.put("rom", MetaUtil.getMeta().getRom());
} catch (JSONException e) {
e.printStackTrace();
}
@ -264,6 +265,7 @@ public class LogUtils {
object.put("channel", HaloApp.getInstance().getChannel());
object.put("dia", MetaUtil.getBase64EncodedAndroidId());
object.put("oaid", MetaUtil.INSTANCE.getMeta().getOaid());
object.put("rom", MetaUtil.getMeta().getRom());
object.put("time", Utils.getTime(context));
object.put("network", DeviceUtils.getNetwork(context));
object.put("user_id", UserManager.getInstance().getUserId());
@ -289,6 +291,7 @@ public class LogUtils {
metaObject.put("channel", meta.getChannel());
metaObject.put("gid", meta.getGid());
metaObject.put("oaid", meta.getOaid());
metaObject.put("rom", MetaUtil.getMeta().getRom());
metaObject.put("jnfj", MetaUtil.getBase64EncodedIMEI());
metaObject.put("mac", meta.getMac());
metaObject.put("manufacturer", meta.getManufacturer());
@ -495,6 +498,7 @@ public class LogUtils {
metaObject.put("os", meta.getOs());
metaObject.put("userId", meta.getUserId());
metaObject.put("oaid", HaloApp.getInstance().getOAID());
metaObject.put("rom", MetaUtil.getMeta().getRom());
} catch (JSONException e) {
e.printStackTrace();

View File

@ -33,6 +33,7 @@ import com.lightgame.utils.Utils
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.*
import java.util.HashMap
import java.util.Locale
import java.util.concurrent.Executors
import kotlin.collections.ArrayList
import kotlin.collections.HashSet
@ -288,12 +289,22 @@ object PackageHelper {
* 用户是否已经使用另类方式获取已安装应用列表
*/
private fun isUseAlternativeWayToGetInstalledPackages(): Boolean {
if (isBuggyHuaweiDevice()) {
useAlternativeWayToGetInstalledPackages = false
return false
}
return useAlternativeWayToGetInstalledPackages
|| (SPUtils.getBoolean(SP_GET_INSTALLED_PACKAGES_BY_ALTERNATIVE_API)
.also { useAlternativeWayToGetInstalledPackages = it })
}
private fun updateUseAlternativeWayToGetInstalledPackages() {
if (isBuggyHuaweiDevice()) {
useAlternativeWayToGetInstalledPackages = false
return
}
// 启用另类获取已安装应用列表的 API
useAlternativeWayToGetInstalledPackages = true
SPUtils.setBoolean(SP_GET_INSTALLED_PACKAGES_BY_ALTERNATIVE_API, true)
@ -478,7 +489,12 @@ object PackageHelper {
/**
* 是否支持动态获取已安装应用列表权限
*/
fun isSupportGetInstalledAppsPermission(context: Context): Boolean {
private fun isSupportGetInstalledAppsPermission(context: Context): Boolean {
if (isBuggyHuaweiDevice()) {
// 华为系 Android 10 设备存在 bug调用过多可能会触发 DeadSystemException
return false
}
if (isUseAlternativeWayToGetInstalledPackages()) {
// 已经使用另类获取已安装应用列表形式,强制判定为不支持动态获取已安装应用列表权限
return false
@ -789,4 +805,13 @@ object PackageHelper {
return packageList
}
private fun isBuggyHuaweiDevice(): Boolean {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q) {
val manufacturer = Build.MANUFACTURER.lowercase(Locale.CHINA) ?: ""
return manufacturer == "huawei" || manufacturer == "honor"
} else {
return false
}
}
}

View File

@ -37,7 +37,7 @@ public class PostCommentUtils {
device.put("model", Build.MODEL);
device.put("manufacturer", Build.MANUFACTURER);
device.put("android_version", android.os.Build.VERSION.RELEASE);
device.put("rom", MetaUtil.INSTANCE.getRom().name() + " " + MetaUtil.INSTANCE.getRom().getVersionName());
device.put("rom", MetaUtil.INSTANCE.getRom().getRomName() + " " + MetaUtil.INSTANCE.getRom().getRomVersion());
content.put("device", device);
} catch (Exception e) {
e.printStackTrace();

View File

@ -9,15 +9,14 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.gh.common.constant.Config
import com.gh.gamecenter.common.utils.DialogHelper
import com.gh.gamecenter.entity.NewApiSettingsEntity
import com.lightgame.utils.Utils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.DialogHelper
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.enlargeTouchArea
import com.gh.gamecenter.common.utils.setDrawableEnd
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.entity.NewApiSettingsEntity
import com.halo.assistant.HaloApp
import com.lightgame.utils.Utils
/**
* 处理厂商纯净/安全模式的辅助类
@ -313,9 +312,11 @@ object PureModeHelper {
intent.setPackage("com.huawei.security.privacycenter")
intent.setAction("com.huawei.securitycenter.PURE_MODE_ACTIVITY")
intent.putExtra("intent_from_settings", true)
context.startActivity(intent)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
HaloApp.getInstance().startActivity(intent)
} catch (_: Exception) {
toSystemSettings(context)
}
}

View File

@ -9,7 +9,11 @@ import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.exposure.meta.MetaUtil
import com.gh.gamecenter.common.exposure.meta.MetaUtil.getMeta
import com.gh.gamecenter.common.loghub.LoghubUtils
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.utils.DeviceUtils
import com.gh.gamecenter.common.utils.asVGame
import com.gh.gamecenter.common.utils.getExtension
import com.gh.gamecenter.common.utils.getMetaExtra
import com.gh.gamecenter.common.utils.isSimulatorGame
import com.gh.ndownload.NDataChanger
import com.gh.ndownload.NDownloadBridge
import com.halo.assistant.HaloApp
@ -427,6 +431,7 @@ object DownloadDataHelper {
metaObject.put("channel", meta.channel)
metaObject.put("gid", meta.gid)
metaObject.put("oaid", meta.oaid)
metaObject.put("rom", meta.rom)
metaObject.put("manufacturer", meta.manufacturer)
metaObject.put("model", meta.model)
metaObject.put("network", DeviceUtils.getNetwork(context))

View File

@ -3,9 +3,6 @@ package com.gh.download
import android.annotation.SuppressLint
import android.text.TextUtils
import com.gh.common.util.*
import com.gh.common.util.DataCollectionUtils
import com.gh.common.util.PackageInstaller
import com.gh.common.util.PackageUtils
import com.gh.common.xapk.XapkInstaller
import com.gh.download.server.BrowserInstallHelper
import com.gh.gamecenter.common.constant.Constants
@ -33,9 +30,7 @@ import com.lightgame.download.DownloadEntity
import com.lightgame.utils.Utils
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONException
import org.json.JSONObject

View File

@ -7,6 +7,7 @@ import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.common.DefaultUrlHandler
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.loadDataCompat
import com.gh.gamecenter.databinding.DownloadDialogInstructionItemBinding
class DownloadDialogInstructionItemViewHolder(val binding: DownloadDialogInstructionItemBinding) :
@ -14,11 +15,8 @@ class DownloadDialogInstructionItemViewHolder(val binding: DownloadDialogInstruc
fun bindItem(listData: List<DownloadDialogItemData>, position: Int, entrance: String) {
val instruction = listData[position].instruction
binding.webView.loadDataWithBaseURL(
null,
binding.webView.loadDataCompat(
"<body style='margin:0;padding:0;color:#666666;font-size:12px;line-height:18px;'>$instruction</body>",
"text/html",
"utf-8", null
)
binding.webView.settings
binding.webView.setBackgroundColor(Color.TRANSPARENT)

View File

@ -9,6 +9,7 @@ import androidx.fragment.app.FragmentActivity
import com.gh.gamecenter.common.base.fragment.BaseDialogFragment
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.common.utils.loadDataCompat
import com.gh.gamecenter.databinding.DialogDownloadLinkBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.halo.assistant.HaloApp
@ -27,10 +28,7 @@ class DownloadLinkDialog : BaseDialogFragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val binding: DialogDownloadLinkBinding = DialogDownloadLinkBinding.inflate(layoutInflater, container, false)
binding.title.text = mLinkEntity?.title
binding.webView.loadDataWithBaseURL(
null,
mLinkEntity?.content ?: "", "text/html", "utf-8", null
)
binding.webView.loadDataCompat(mLinkEntity?.content ?: "")
binding.confirm.setOnClickListener {
dismissAllowingStateLoss()

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

@ -886,7 +886,7 @@ class GameDetailViewModel(
params["source"] = HaloApp.getInstance().application.getString(R.string.app_name)
params["jnfj"] = MetaUtil.getBase64EncodedIMEI()
params["manufacturer"] = Build.MANUFACTURER
params["rom"] = MetaUtil.getRom().name + " " + MetaUtil.getRom().versionName
params["rom"] = MetaUtil.getRom().romName + " " + MetaUtil.getRom().romVersion
params["suggestion_type"] = "游戏求更新"
params["game_id"] = game?.id ?: ""

View File

@ -7,7 +7,7 @@ import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.widget.LinearLayout
import androidx.core.view.children
import androidx.core.view.forEach
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.RecyclerView
@ -117,7 +117,7 @@ class GameLibaoAdapter(
binding.horizontalScrollView.goneIf(libaoEntity.materials.isEmpty()) {
if (binding.imagesContainer.tag == libaoEntity.id) {
binding.imagesContainer.children.forEach { view ->
binding.imagesContainer.forEach { view ->
if (view is SimpleDraweeView) {
view.hierarchy.roundingParams = RoundingParams().apply {
setCornersRadius(4F.dip2px().toFloat())

View File

@ -541,7 +541,7 @@ class RatingEditActivity : ToolBarActivity(), KeyboardHeightObserver {
jsonObject.put("game_version", gameVersion)
jsonObject.put("source", if (mFromAmway) "anliwall" else "game_detail")
jsonObject.put("plugin_version", PackageUtils.getMetaData(this, mInstallPackageName, "gh_version"))
jsonObject.put("rom", MetaUtil.getRom().name + " " + MetaUtil.getRom().versionName)
jsonObject.put("rom", MetaUtil.getRom().romName + " " + MetaUtil.getRom().romVersion)
jsonObject.put("again", again)
val body = jsonObject.toString().toRequestBody("application/json".toMediaTypeOrNull())

View File

@ -384,7 +384,7 @@ class CustomPageRepository private constructor(
pageInfo.componentPosition
)
)
dspSubjectUseCase.getDspGames(subject.size.index)
dspSubjectUseCase.getDspGames(subject.columnType, subject.size.index)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BiResponse<List<GameEntity>>() {

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

@ -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;
@ -78,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;
@ -328,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微信小游戏
*/
@ -3106,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);
/**
* 新游开测-详情列表
*/

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

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

View File

@ -1,40 +1,20 @@
package com.gh.gamecenter.search
import com.gh.gamecenter.entity.AdConfig
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_AD_SPACE
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.entity.SearchSubjectEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
data class SearchItemData(
class SearchItemData(
val game: GameEntity? = null,
val subject: SearchSubjectEntity? = null,
val ad: AdConfig.ThirdPartyAd? = null,
val adConfig: AdConfig? = null,
var gamePosition: Int = -1,
val gamePosition: Int = -1,
var exposureEventList: ArrayList<ExposureEvent>? = null,
private val isFirst: Boolean = false,
val type: String,
var placeHolderId: String? = null // 如果当前itemBeam还未填充实际数据仅做占位作用placeHolderId会被赋值作为实际数据填充的唯一标识当实际数据填充后placeHolderId置为空
) {
val isShowFirstSetting: Boolean
get() = isFirst && game?.firstSetting != null && !game.firstSetting?.firstPosition.isNullOrBlank()
val isPlaceHolder: Boolean
get() = placeHolderId != null
fun matchPlaceHolder(other: SearchItemData) =
if (type == other.type) {
when (type) {
TYPE_WECHAT_GAME_CPM_COLUMN, TYPE_DSP_GAME_COLUMN -> placeHolderId == other.subject?.columnId
TYPE_AD_SPACE -> placeHolderId == SearchGameResultRepository.getAdPlaceHolderId(other.adConfig)
else -> false
}
} else {
false
}
}

View File

@ -1,255 +0,0 @@
package com.gh.gamecenter.search.viewmodel
import android.app.Application
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
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.retrofit.Response
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.observableToMain
import com.gh.gamecenter.core.provider.IHandleGameResponseProvider
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_GAME
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_WECHAT_GAME
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.livedata.Event
import com.gh.gamecenter.livedata.EventObserver
import com.gh.gamecenter.minigame.MiniGameSearchResultRepository
import com.gh.gamecenter.search.ISearchGameResultRepository
import com.gh.gamecenter.search.SearchGameResultRepository
import com.gh.gamecenter.search.SearchItemData
import com.therouter.TheRouter.get
import io.reactivex.Observable
import retrofit2.HttpException
class SearchGameResultViewModel(
application: Application,
private var mSearchKey: String?,
private val repository: ISearchGameResultRepository,
private var mSearchType: String,
val sourceEntrance: String
) : ListViewModel<SearchItemData, SearchItemData>(application) {
private var mPage = 0
private var gameList = arrayListOf<GameEntity>()
private val dataUpdateObserver = EventObserver<SearchItemData> {
notifyItemChanged(it)
}
private var originalDataList = arrayListOf<SearchItemData>()
init {
if (repository is SearchGameResultRepository) {
repository.dataUpdateEvent.observeForever(dataUpdateObserver)
}
}
fun updateSearchKeyWithType(searchKey: String, searchType: String) {
mSearchKey = searchKey
mSearchType = searchType
}
override fun loadData() {
if (mCurLoadParams == null) initLoadParams()
val loadParams = if (mRetryParams != null) mRetryParams else mCurLoadParams
val curStatus = mLoadStatusLiveData.value
if (curStatus != null && curStatus != LoadStatus.INIT_LOADED && curStatus != LoadStatus.LIST_LOADED && curStatus != LoadStatus.INIT) return
if (mCurLoadParams.loadOffset == LoadParams.DEFAULT_OFFSET) {
mLoadStatusLiveData.setValue(LoadStatus.INIT_LOADING)
} else {
mLoadStatusLiveData.setValue(LoadStatus.LIST_LOADING)
}
mPage = loadParams.loadOffset
repository.getSearchGame(mSearchKey, loadParams.loadOffset)
.compose(observableToMain())
.subscribe(object : Response<List<SearchItemData>>() {
override fun onResponse(response: List<SearchItemData>?) {
handleSuccess(response ?: emptyList(), curStatus)
}
override fun onFailure(e: HttpException?) {
e?.let(::handleFailure)
}
})
}
private fun handleSuccess(response: List<SearchItemData>, curStatus: LoadStatus?) {
if (mCurLoadParams.loadOffset == LoadParams.DEFAULT_OFFSET || curStatus == LoadStatus.INIT) {
originalDataList.clear()
}
// 针对游戏的一些操作(过滤隐藏游戏过滤隐藏APK吗增加下载数据)
val handleGameResponse = get(IHandleGameResponseProvider::class.java)
val gameList = handleGameResponse?.handleGameResponse(response, mEntrance) ?: response
originalDataList.addAll(gameList as List<SearchItemData>)
mListLiveData.postValue(originalDataList)
loadStatusControl(response.size)
}
private fun handleFailure(exception: Exception) {
if (exception is HttpException && exception.code() == 404) {
loadStatusControl(0)
mLoadStatusLiveData.value = LoadStatus.INIT_EXCEPTION
mLoadExceptionLiveData.postValue(exception)
} else {
loadStatusControl(REQUEST_FAILURE_SIZE)
if (exception is HttpException) {
mLoadExceptionLiveData.postValue(exception)
}
}
}
override fun mergeResultLiveData() {
mResultLiveData.addSource(mListLiveData) { list ->
val data = arrayListOf<SearchItemData>()
var gamePosition = 0
gameList.clear()
originalDataList.forEach {
if (it.type == TYPE_GAME || it.type == TYPE_WECHAT_GAME) {
it.gamePosition = gamePosition
gamePosition++
it.game?.let(gameList::add)
}
if (!it.isPlaceHolder) {
data.add(it)
}
}
refreshWrongInstallStatus()
if (data.isEmpty()) {
handleLoadStatusWhenGameListIsEmpty(data, list)
} else {
mResultLiveData.postValue(data)
}
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 notifyItemChanged(newItem: SearchItemData) {
val positionList = arrayListOf<Int>()
originalDataList.forEachIndexed { index, item ->
if (item.matchPlaceHolder(newItem)) {
positionList.add(index)
}
}
positionList.forEach {
originalDataList[it] = newItem
}
val displayItems = originalDataList.filterNot { it.isPlaceHolder }
mResultLiveData.postValue(displayItems)
}
fun refreshWrongInstallStatus() {
if (gameList.isNotEmpty()) {
PackageHelper.refreshWrongInstallStatus(gameList)
}
}
private fun handleLoadStatusWhenGameListIsEmpty(list: List<SearchItemData>, itemDataList: List<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<SearchItemData>>? = null
private val _refreshVipLoading = MutableLiveData<Event<Boolean>>()
val refreshVipLoading: LiveData<Event<Boolean>> = _refreshVipLoading
fun showRefreshVipLoading(show: Boolean) {
_refreshVipLoading.value = Event(show)
}
override fun onCleared() {
super.onCleared()
if (repository is SearchGameResultRepository) {
repository.dataUpdateEvent.removeObserver(dataUpdateObserver)
}
}
class Factory(
private val mApplication: Application,
private val mSearchKey: String?,
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,
repository,
mSearchType,
mSourceEntrance
) as T
}
}
}

View File

@ -32,7 +32,6 @@ class GameServerTestV2Fragment : LazyFragment() {
private var mSettingBinding: LayoutGameServerTestV2SettingBinding? = null
private var mCurrentFragment: GameServerTestV2ListFragment? = null
private var mLinkText = ""
private var mLinkId = ""
private var mPageLocation: PageLocation? = null
private var mIsSettingAnimating = false
@ -40,7 +39,6 @@ class GameServerTestV2Fragment : LazyFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mLinkId = arguments?.getString(EntranceConsts.KEY_ID, "none") ?: "none"
mLinkText = requireArguments().getString(EntranceConsts.KEY_NAME, "")
val topTabIndex = requireArguments().getInt(EntranceConsts.KEY_TAB_INDEX, -1)
val bottomTabIndex = requireArguments().getInt(EntranceConsts.KEY_BOTTOM_TAB_INDEX, -1)
@ -66,40 +64,22 @@ class GameServerTestV2Fragment : LazyFragment() {
}
override fun onFragmentFirstVisible() {
mViewModel = viewModelProviderFromParent(GameServerTestV2ViewModel.Factory(mLinkId))
mViewModel = viewModelProviderFromParent()
super.onFragmentFirstVisible()
mViewModel?.run {
displaySettingLiveData.observe(viewLifecycleOwner) {
mBinding?.filterView?.setItemList(timeFilterList, DEFAULT_TIME_FILTER_INDEX)
mViewModel?.selectedTimeFilterLiveData?.value = mViewModel?.timeFilterList?.getOrNull(DEFAULT_TIME_FILTER_INDEX) ?: ""
mBinding?.filterView?.isVisible = true
mBinding?.optionIv?.setOnClickListener {
showSettingView()
SensorsBridge.trackColumnTestClick(
location = "详情页",
recommendType = mLinkText,
text = "设置",
bottomTab = mPageLocation?.bottomTab,
multiTabName = mPageLocation?.severalTabPageName,
multiTabId = mPageLocation?.severalTabPageId,
position = mPageLocation?.tabPosition,
tabContent = mPageLocation?.tabContent
)
}
mCurrentFragment?.loadData()
if (SPUtils.getBoolean(Constants.SP_SHOW_GAME_SERVER_TEST_V2_SETTING_GUIDE, true)) {
mBaseHandler.post {
showGuide()
}
SPUtils.setBoolean(Constants.SP_SHOW_GAME_SERVER_TEST_V2_SETTING_GUIDE, false)
}
}
selectedTimeFilterLiveData.observe(viewLifecycleOwner) {
mBinding?.filterView?.setItemList(timeFilterList, DEFAULT_TIME_FILTER_INDEX)
selectedTimeFilterLiveData.observe(this@GameServerTestV2Fragment) {
mBinding?.filterView?.setCurrentItem(timeFilterList.indexOf(it))
}
}
changeFragment()
if (SPUtils.getBoolean(Constants.SP_SHOW_GAME_SERVER_TEST_V2_SETTING_GUIDE, true)) {
mBaseHandler.post {
showGuide()
}
SPUtils.setBoolean(Constants.SP_SHOW_GAME_SERVER_TEST_V2_SETTING_GUIDE, false)
}
}
override fun initRealView() {
@ -109,7 +89,7 @@ class GameServerTestV2Fragment : LazyFragment() {
filterContainer.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_background.toColor(requireContext()))
filterView.onCheckedAction = { index ->
mViewModel?.let {
it.setSelectedTimeFilter(it.timeFilterList.getOrNull(index))
it.setSelectedTimeFilter(it.timeFilterList[index])
mCurrentFragment?.changeTimeFilter()
SensorsBridge.trackColumnTestClick(
location = "详情页",
@ -120,7 +100,7 @@ class GameServerTestV2Fragment : LazyFragment() {
multiTabId = mPageLocation?.severalTabPageId,
position = mPageLocation?.tabPosition,
tabContent = mPageLocation?.tabContent,
buttonType = it.timeFilterList.getOrNull(index) ?: ""
buttonType = it.timeFilterList[index]
)
}
}
@ -144,6 +124,19 @@ class GameServerTestV2Fragment : LazyFragment() {
)
}
}
optionIv.setOnClickListener {
showSettingView()
SensorsBridge.trackColumnTestClick(
location = "详情页",
recommendType = mLinkText,
text = "设置",
bottomTab = mPageLocation?.bottomTab,
multiTabName = mPageLocation?.severalTabPageName,
multiTabId = mPageLocation?.severalTabPageId,
position = mPageLocation?.tabPosition,
tabContent = mPageLocation?.tabContent
)
}
}
}
@ -197,7 +190,7 @@ class GameServerTestV2Fragment : LazyFragment() {
val isCategoryFilterChanged = selectedCategoryFilterSet != tempCategoryFilterSet
selectedCategoryFilterSet = HashSet(tempCategoryFilterSet)
SPUtils.setStringSet(Constants.SP_GAME_SERVER_TEST_V2_CATEGORY_SET, selectedCategoryFilterSet)
setSelectedTimeFilter(timeFilterList.getOrNull(1))
setSelectedTimeFilter(GameServerTestV2ViewModel.TODAY_GAME)
if (isCategoryFilterChanged) {
mCurrentFragment?.changeCategoryFilter()
}
@ -219,8 +212,8 @@ class GameServerTestV2Fragment : LazyFragment() {
}
val itemList = arrayListOf<TextView>()
val allItem = getItemTextView(GameServerTestV2ViewModel.GameCategory.All.displayName)
allItem.tag = GameServerTestV2ViewModel.GameCategory.All.displayName
val allItem = getItemTextView("全部")
allItem.tag = "全部"
binding.flexbox.addView(allItem)
toggleHighlightedTextView(allItem, mViewModel?.tempCategoryFilterSet.isNullOrEmpty())
allItem.setOnClickListener {
@ -345,6 +338,7 @@ class GameServerTestV2Fragment : LazyFragment() {
}
private fun changeFragment() {
mViewModel?.selectedTimeFilterLiveData?.value = mViewModel?.timeFilterList?.get(DEFAULT_TIME_FILTER_INDEX) ?: ""
mCurrentFragment =
childFragmentManager.findFragmentByTag(GameServerTestV2ViewModel::class.java.name) as? GameServerTestV2ListFragment
?: GameServerTestV2ListFragment()

View File

@ -18,7 +18,6 @@ import com.gh.download.DownloadManager
import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.LazyListFragment
import com.gh.gamecenter.common.baselist.ListAdapter
import com.gh.gamecenter.common.baselist.LoadType
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.exposure.ExposureSource
@ -77,12 +76,6 @@ class GameServerTestV2ListFragment :
)
}
override fun isAutomaticLoad(): Boolean = false
fun loadData() {
mListViewModel.load(LoadType.NORMAL)
}
override fun onCreate(savedInstanceState: Bundle?) {
mGameServerTestV2ViewModel = viewModelProviderFromParent()
super.onCreate(savedInstanceState)
@ -363,7 +356,7 @@ class GameServerTestV2ListFragment :
override fun onChanged(ts: MutableList<GameServerTestV2ListViewModel.ItemData>?) {
super.onChanged(ts)
if (!ts.isNullOrEmpty() && mListViewModel.isLoadFirstPage) {
scrollToTime(GameServerTestV2ViewModel.TimeFilter.Today.value)
scrollToTime(GameServerTestV2ViewModel.TimeFilter.TODAY.value)
mListRv?.postDelayed({
scroll()
mScrollCalculatorHelper.onScrollStateChanged(RecyclerView.SCROLL_STATE_IDLE)

View File

@ -55,9 +55,14 @@ class GameServerTestV2ListViewModel(
mListLiveData.value = null
mLoadStatusLiveData.postValue(LoadStatus.INIT_LOADING)
val categoryFilter = mGameServerTestV2ViewModel?.selectedCategoryFilterSet?.joinToString("-") {
GameServerTestV2ViewModel.GameCategory.fromDisplayName(it).value
} ?: "" //如果选择全部类型,则传空值
var categoryFilter = "" //如果选择全部类型,则传空值
mGameServerTestV2ViewModel?.selectedCategoryFilterSet?.forEachIndexed { index, category ->
if (index == 0) {
categoryFilter = mGameServerTestV2ViewModel.getCategoryFilterValue(category).value
} else {
categoryFilter += "-${mGameServerTestV2ViewModel.getCategoryFilterValue(category).value}"
}
}
Observable.zip(provideDetailObservable(categoryFilter), provideTopGamesObservable()) { t1, t2 ->
mTopGameList = ArrayList(t2).apply {
@ -146,11 +151,11 @@ class GameServerTestV2ListViewModel(
// 置顶游戏固定显示在今天上方,如果今天没有数据则展示在未来上方;如果今天和未来都没有数据,则显示在近期上方
val topGamesReadableDaysOffset = if (mTodayCount != 0) {
GameServerTestV2ViewModel.TimeFilter.Today.value
GameServerTestV2ViewModel.TimeFilter.TODAY.value
} else if (mFutureCount != 0) {
GameServerTestV2ViewModel.TimeFilter.Future.value
GameServerTestV2ViewModel.TimeFilter.UPCOMING_DAY.value
} else {
GameServerTestV2ViewModel.TimeFilter.Recent.value
GameServerTestV2ViewModel.TimeFilter.PAST_DAY.value
}
var isTopGamesAdded = false
@ -174,7 +179,11 @@ class GameServerTestV2ListViewModel(
}
}
val readableDaysOffset = slice.timeType
val readableDaysOffset = when (slice.timeType) {
"recent" -> GameServerTestV2ViewModel.TimeFilter.PAST_DAY.value
"future" -> GameServerTestV2ViewModel.TimeFilter.UPCOMING_DAY.value
else -> GameServerTestV2ViewModel.TimeFilter.TODAY.value
}
if (!mTopGameList.isNullOrEmpty() && readableDaysOffset == topGamesReadableDaysOffset && !isTopGamesAdded) {
subList.add(
@ -326,31 +335,31 @@ class GameServerTestV2ListViewModel(
if (position >= 0) return position
val recentCount =
itemDataList.count { it.readableDaysOffset == GameServerTestV2ViewModel.TimeFilter.Recent.value }
itemDataList.count { it.readableDaysOffset == GameServerTestV2ViewModel.TimeFilter.PAST_DAY.value }
val todayCount =
itemDataList.count { it.readableDaysOffset == GameServerTestV2ViewModel.TimeFilter.Today.value }
itemDataList.count { it.readableDaysOffset == GameServerTestV2ViewModel.TimeFilter.TODAY.value }
when (timeFilter) {
GameServerTestV2ViewModel.TimeFilter.Recent.value -> {
position = mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.Today.value, -1)
GameServerTestV2ViewModel.TimeFilter.PAST_DAY.value -> {
position = mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.TODAY.value, -1)
if (position >= 0) return position
position =
mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.Future.value, -1)
mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.UPCOMING_DAY.value, -1)
if (position >= 0) return position
}
GameServerTestV2ViewModel.TimeFilter.Today.value -> {
GameServerTestV2ViewModel.TimeFilter.TODAY.value -> {
position =
mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.Future.value, -1)
mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.UPCOMING_DAY.value, -1)
if (position >= 0) return position
position =
mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.Recent.value, -1)
mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.PAST_DAY.value, -1)
if (position >= 0) return recentCount - 1
}
GameServerTestV2ViewModel.TimeFilter.Future.value -> {
position = mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.Today.value, -1)
GameServerTestV2ViewModel.TimeFilter.UPCOMING_DAY.value -> {
position = mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.TODAY.value, -1)
if (position >= 0) return recentCount + todayCount - 1
position =
mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.Recent.value, -1)
mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.PAST_DAY.value, -1)
if (position >= 0) recentCount - 1
}
}

View File

@ -1,6 +1,5 @@
package com.gh.gamecenter.servers.gametest2
import android.annotation.SuppressLint
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
@ -8,129 +7,89 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.gh.common.constant.Config
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.entity.GameServerTestDisplaySetting
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.servers.gametest2.GameServerTestV2ViewModel.GameCategory.*
import com.halo.assistant.HaloApp
class GameServerTestV2ViewModel(application: Application, private val linkId: String = "none") : AndroidViewModel(application) {
class GameServerTestV2ViewModel(application: Application) : AndroidViewModel(application) {
val timeFilterList = arrayListOf(
GameServerTestDisplaySetting.RECENT,
GameServerTestDisplaySetting.TODAY,
GameServerTestDisplaySetting.FUTURE
)
val categoryFilterList = arrayListOf(
Local.displayName,
Online.displayName,
Welfare.displayName,
Gjonline.displayName
)
val timeFilterList = arrayListOf(RECENT_GAME, TODAY_GAME, FUTURE_GAME)
val categoryFilterList = arrayListOf("单机游戏", "网络游戏", "福利游戏", "国际服游戏")
var selectedCategoryFilterSet = SPUtils.getStringSet(Constants.SP_GAME_SERVER_TEST_V2_CATEGORY_SET).toHashSet()
var tempCategoryFilterSet = SPUtils.getStringSet(Constants.SP_GAME_SERVER_TEST_V2_CATEGORY_SET).toHashSet()
var selectedTimeFilterLiveData = MutableLiveData<String?>()
var selectedTimeFilterLiveData = MutableLiveData(timeFilterList[GameServerTestV2Fragment.DEFAULT_TIME_FILTER_INDEX])
var bigImageMode = SPUtils.getBoolean(
Constants.SP_GAME_SERVER_TEST_V2_BIG_IMAGE_MODE,
Config.getSettings() == null || Config.getSettings()?.columnTestV2Setting?.defaultView == "on"
)
val displaySettingLiveData = MutableLiveData<GameServerTestDisplaySetting>()
init {
loadDisplaySetting()
fun getCategoryFilterString(): String {
var selectedCategoryFilter = "全部类型"
selectedCategoryFilterSet.forEachIndexed { index, category ->
if (index == 0) {
selectedCategoryFilter = category
} else {
selectedCategoryFilter += "+$category"
}
}
return selectedCategoryFilter
}
@SuppressLint("CheckResult")
fun loadDisplaySetting() {
RetrofitManager.getInstance().newApi.getGameServerTestDisplaySetting(linkId)
.compose(singleToMain())
.subscribe({ setting ->
timeFilterList.clear()
timeFilterList.addAll(listOf(setting.timeTextPast, setting.timeTextPresent, setting.timeTextFuture))
TimeFilter.Recent.displayName = setting.timeTextPast
TimeFilter.Today.displayName = setting.timeTextPresent
TimeFilter.Future.displayName = setting.timeTextFuture
categoryFilterList.clear()
categoryFilterList.addAll(setting.gameCategory.map { GameCategory.fromValue(it).displayName })
selectedCategoryFilterSet.removeAll { !categoryFilterList.contains(it) }
tempCategoryFilterSet.removeAll { !categoryFilterList.contains(it) }
displaySettingLiveData.postValue(setting)
}, {
it.printStackTrace()
displaySettingLiveData.postValue(GameServerTestDisplaySetting())
})
fun getCategoryFilterValue(category: String): GameCategory {
return when (category) {
"单机游戏" -> GameCategory.LOCAL
"网络游戏" -> GameCategory.ONLINE
"福利游戏" -> GameCategory.WELFARE
"国际服游戏" -> GameCategory.GJONLINE
else -> GameCategory.ALL
}
}
fun getCategoryFilterString() = selectedCategoryFilterSet.joinToString("+").ifEmpty { "全部类型" }
fun getSelectedTimeFilterString(dayFilter: String): String {
return when (dayFilter) {
TimeFilter.PAST_DAY.value -> RECENT_GAME
TimeFilter.TODAY.value -> TODAY_GAME
TimeFilter.UPCOMING_DAY.value -> FUTURE_GAME
else -> TODAY_GAME
}
}
fun getSelectedTimeFilterString(dayFilter: String) = TimeFilter.fromValue(dayFilter).displayName
fun getCurrentSelectedTimeFilter() = TimeFilter.fromDisplayName(selectedTimeFilterLiveData.value ?: "").value
fun getCurrentSelectedTimeFilter(): String {
return when (selectedTimeFilterLiveData.value) {
RECENT_GAME -> TimeFilter.PAST_DAY.value
TODAY_GAME -> TimeFilter.TODAY.value
FUTURE_GAME -> TimeFilter.UPCOMING_DAY.value
else -> TimeFilter.TODAY.value
}
}
fun setSelectedTimeFilter(filter: String?) {
if (filter.isNullOrEmpty()) return
selectedTimeFilterLiveData.value = filter
}
sealed class TimeFilter(val value: String, var displayName: String) {
data object Recent : TimeFilter("recent", GameServerTestDisplaySetting.RECENT)
data object Today : TimeFilter("today", GameServerTestDisplaySetting.TODAY)
data object Future : TimeFilter("future", GameServerTestDisplaySetting.FUTURE)
companion object {
fun fromDisplayName(displayName: String): TimeFilter {
return when (displayName) {
Recent.displayName -> Recent
Future.displayName -> Future
else -> Today
}
}
fun fromValue(value: String): TimeFilter {
return when (value) {
Recent.value -> Recent
Future.value -> Future
else -> Today
}
}
}
enum class TimeFilter(val value: String) {
PAST_DAY("recent"),
TODAY("today"),
UPCOMING_DAY("future")
}
sealed class GameCategory(val value: String, val displayName: String) {
data object All : GameCategory("all", "全部")
data object Local : GameCategory("local", "单机游戏")
data object Online : GameCategory("online", "网络游戏")
data object Welfare : GameCategory("welfare", "福利游戏")
data object Gjonline : GameCategory("gjonline", "国际服游戏")
companion object {
fun fromDisplayName(displayName: String): GameCategory {
return when (displayName) {
Local.displayName -> Local
Online.displayName -> Online
Welfare.displayName -> Welfare
Gjonline.displayName -> Gjonline
else -> All
}
}
fun fromValue(value: String): GameCategory {
return when (value) {
Local.value -> Local
Online.value -> Online
Welfare.value -> Welfare
Gjonline.value -> Gjonline
else -> All
}
}
}
enum class GameCategory(val value: String) {
ALL("all"),
LOCAL("local"),
ONLINE("online"),
WELFARE("welfare"),
GJONLINE("gjonline"),
}
class Factory(private val linkId: String = "none") :
companion object {
const val RECENT_GAME = "近期"
const val TODAY_GAME = "今天"
const val FUTURE_GAME = "预约"
}
class Factory(private val mApplication: Application) :
ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return GameServerTestV2ViewModel(HaloApp.getInstance(), linkId) as T
return GameServerTestV2ViewModel(mApplication) as T
}
}
}

View File

@ -6,6 +6,7 @@ import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.debounceActionWithInterval
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.view.Chips
import com.gh.gamecenter.databinding.ItemColumnCollectionCustomTabBinding
@ -30,8 +31,10 @@ class CustomTabFollowAdapter(private val viewModel: ColumnCollectionTabViewModel
endIcon.setImageResource(R.drawable.ic_basic_x_8_secondary)
endIcon.imageTintList = null
setOnClickListener {
if (!isForbidden) {
viewModel?.addMore(subjectData)
debounceActionWithInterval(500) {
if (!isForbidden) {
viewModel?.addMore(subjectData)
}
}
}
}

View File

@ -6,6 +6,7 @@ import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.debounceActionWithInterval
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.view.Chips
import com.gh.gamecenter.databinding.ItemColumnCollectionCustomTabBinding
@ -26,7 +27,9 @@ class CustomTabMoreAdapter(private val viewModel: ColumnCollectionTabViewModel?)
startIcon.setImageResource(R.drawable.ic_auxiliary_plus_secondary_8)
startIcon.imageTintList = null
setOnClickListener {
viewModel?.addFollow(subjectData)
debounceActionWithInterval(500) {
viewModel?.addFollow(subjectData)
}
}
}
}

View File

@ -812,6 +812,8 @@ class SubjectTabFragment : ToolbarFragment() {
}
override fun onBackPressed(): Boolean {
if (!isAdded) return super.onBackPressed()
if (binding.settingGuideContainer.isVisible) {
binding.settingGuideContainer.performClick()
return true

View File

@ -118,6 +118,8 @@ class TabWrapperFragment: BaseTabWrapperFragment(), ISmartRefresh, ISmartRefresh
if (shouldShow && guide != null && guidePosition != -1) {
tabLayout?.post {
if (activity == null) return@post
val tabView = tabLayout?.getTabAt(guidePosition)?.view
if (tabView != null) {
if (!isDefaultCustomPageFragment() && priorityChain.isHandlerQueueEmpty()) {
@ -133,7 +135,7 @@ class TabWrapperFragment: BaseTabWrapperFragment(), ISmartRefresh, ISmartRefresh
} else {
multiTabGuideHandler.doPreProcess(
true,
requireActivity(),
activity,
mBaseHandler,
guide,
tabLayout,

View File

@ -243,7 +243,7 @@ class VFeedbackDialogFragment : BaseDialogFragment() {
"source" to HaloApp.getInstance().application.getString(R.string.app_name)
"jnfj" to MetaUtil.getBase64EncodedIMEI()
"manufacturer" to Build.MANUFACTURER
"rom" to MetaUtil.getRom().name + " " + MetaUtil.getRom().versionName
"rom" to MetaUtil.getRom().romName + " " + MetaUtil.getRom().romVersion
"suggestion_type" to "游戏问题"
"game_id" to game.id

View File

@ -152,7 +152,7 @@ class AccelerationUseCase {
"from" to "",
"game_id" to game.id,
"manufacturer" to Build.MANUFACTURER,
"rom" to MetaUtil.getRom().name + " " + MetaUtil.getRom().versionName,
"rom" to MetaUtil.getRom().romName + " " + MetaUtil.getRom().romVersion,
"suggestion_type" to "加速器失败",
"message" to "加速失败, 游戏ID: ${game.id}, 游戏名: ${game.name ?: ""}",
"log" to it

View File

@ -693,7 +693,7 @@ class WebFragment : LazyFragment(), IScrollable {
webview.loadUrl(mWebUrl)
}
} else {
webview.loadDataWithBaseURL(null, mWebUrl, "text/html", "utf-8", null)
webview.loadDataCompat(mWebUrl)
}
}
if (mIsHorizontalDispatcherEnabled) {

View File

@ -56,10 +56,9 @@
<com.gh.gamecenter.common.view.SegmentedIconFilterView
android:id="@+id/filterView"
android:layout_width="wrap_content"
android:layout_width="130dp"
android:layout_height="28dp"
android:layout_marginStart="16dp"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />

View File

@ -15,9 +15,14 @@ import com.tencent.vasdolly.helper.ChannelReaderUtil
class FlavorProviderImp : IFlavorProvider {
private var isInit = false
private val pendingEvent = arrayListOf<String>()
override fun init(application: Application, activity: Activity, activateRatio: Int) {
SMHelper.init(application, getChannelStr(application))
isInit = true
if (HaloApp.getInstance().isBrandNewInstall) {
logEvent("EVENT_ACTIVE")
SPUtils.setLong("TIME_OF_BRAND_NEW_INSTALL", System.currentTimeMillis() / 1000)
@ -31,6 +36,8 @@ class FlavorProviderImp : IFlavorProvider {
SPUtils.setBoolean("SHOULD_SEND_RETENTION_EVENT", false)
}
}
pendingEvent.forEach(SMHelper::onEvent)
pendingEvent.clear()
}
override fun getChannelStr(application: Application): String {
@ -43,7 +50,11 @@ class FlavorProviderImp : IFlavorProvider {
}
override fun logEvent(content: String) {
SMHelper.onEvent(content)
if (isInit) {
SMHelper.onEvent(content)
} else {
pendingEvent.add(content)
}
}
override fun logCoreEvent() {

View File

@ -7,8 +7,8 @@ ext {
targetSdkVersion = 30 // 升级targetSdkVersion到 34 时需要根据官方文档补全前台服务的权限类型。比如 NDownloadServiceKeepAliveService
// application info (每个大版本之间的 versionCode 增加 20)
versionCode = 1210
versionName = "5.43.0"
versionCode = 1195
versionName = "5.42.5"
applicationId = "com.gh.gamecenter"
applicationIdGat = "com.gh.gamecenter.intl"
@ -99,7 +99,7 @@ ext {
verifier = "1.0.6"
skeleton = "1.1.5"
mta = "6.8.0"
romChecker = "1.0.3"
romChecker = "2.0.0"
oss = "2.8.8"
desugarJdkLibs = "1.1.5"
toolargetool = "0.3.0"
@ -143,7 +143,7 @@ ext {
acloudPush = "3.8.8.1"
jpushVersion = "5.4.0"
jverifiationVersion = "3.4.2"
jverifiationVersion = "3.2.5"
honorPushVersion = "7.0.61.303"
volcTlsVersion = "1.1.4"

View File

@ -39,7 +39,7 @@ class QaFeedbackViewModel(application: Application, private val contentId: Strin
map["source"] = HaloApp.getInstance().getString(R.string.app_name)
map["jnfj"] = getBase64EncodedIMEI()
map["manufacturer"] = Build.MANUFACTURER
map["rom"] = MetaUtil.getRom().name + " " + MetaUtil.getRom().versionName
map["rom"] = MetaUtil.getRom().romName + " " + MetaUtil.getRom().romVersion
val requestBody = map.toRequestBody()
RetrofitManager.getInstance().api

View File

@ -209,7 +209,7 @@ class SuggestViewModel(
"source" to getApplication<Application>().getString(R.string.app_name)
"jnfj" to getBase64EncodedIMEI()
"manufacturer" to Build.MANUFACTURER
"rom" to MetaUtil.getRom().name + " " + MetaUtil.getRom().versionName
"rom" to MetaUtil.getRom().romName + " " + MetaUtil.getRom().romVersion
"suggestion_type" to suggestType
"from" to contact
"contact_func" to contactType.value
@ -318,7 +318,7 @@ class SuggestViewModel(
"source" to getApplication<Application>().getString(R.string.app_name)
"jnfj" to getBase64EncodedIMEI()
"manufacturer" to Build.MANUFACTURER
"rom" to MetaUtil.getRom().name + " " + MetaUtil.getRom().versionName
"rom" to MetaUtil.getRom().romName + " " + MetaUtil.getRom().romVersion
"suggestion_type" to mSuggestType
"app_name" to appName
"owner_type" to copyrightIdentity

View File

@ -63,7 +63,7 @@ dependencies {
kapt "com.google.auto.service:auto-service:${autoServiceVersion}"
api "com.louiscad.splitties:splitties-views-dsl:${splitties}"
api "com.louiscad.splitties:splitties-views-dsl-constraintlayout:${splitties}"
api "com.github.nichbar:AndroidRomChecker:${romChecker}"
api "com.lg:romchecker:${romChecker}"
api "androidx.cardview:cardview:${cardView}"
api "com.google.android.material:material:${material}"
api "com.scwang.smartrefresh:SmartRefreshLayout:${smartRefreshLayout}"

View File

@ -33,7 +33,6 @@ object MetaUtil {
private var rom: Rom? = null
fun refreshMeta() {
val appProvider = TheRouter.get(IAppProvider::class.java)
val buildConfigProvider = TheRouter.get(IBuildConfigProvider::class.java)
val userManagerProvider = TheRouter.get(IUserManagerProvider::class.java)
@ -55,7 +54,7 @@ object MetaUtil {
exposureVersion = buildConfigProvider?.getExposureVersion(),
session_id = Tracker.sessionId,
launch_id = Tracker.launchId,
rom = ""
rom = getRom().romName + " " + getRom().romVersion
)
}
@ -68,7 +67,13 @@ object MetaUtil {
}
fun getRom(): Rom {
if (rom == null) {
val appProvider = TheRouter.get(IAppProvider::class.java)
if (appProvider?.isUserAcceptPrivacyPolicy(HaloApp.getInstance()) == false) {
// 如果用户没有同意隐私政策,则返回一个默认的 Rom 对象
return Rom.create("unknown", "unknown", "unknown")
}
if (rom == null || rom?.romName == "unknown") {
rom = RomIdentifier.getRom()
}
return rom!!

View File

@ -167,20 +167,22 @@ object TrackerLogger {
}
private fun getMeta(): JSONObject {
val (_, _, model, manufacturer, _, _, android_version, network, _, gid, oaid, channel, appVersion, userId) = MetaUtil.getMeta()
val meta = MetaUtil.getMeta()
val metaObject = JSONObject()
try {
metaObject.put("dia", getBase64EncodedAndroidId())
metaObject.put("android_version", android_version)
metaObject.put("app_version", appVersion)
metaObject.put("channel", channel)
metaObject.put("gid", gid)
metaObject.put("oaid", oaid)
metaObject.put("android_version", meta.android_version)
metaObject.put("app_version", meta.appVersion)
metaObject.put("channel", meta.channel)
metaObject.put("gid", meta.gid)
metaObject.put("oaid", meta.oaid)
metaObject.put("rom", meta.rom)
metaObject.put("jnfj", getBase64EncodedIMEI())
metaObject.put("manufacturer", manufacturer)
metaObject.put("model", model)
metaObject.put("network", network)
metaObject.put("user_id", userId)
metaObject.put("manufacturer", meta.manufacturer)
metaObject.put("model", meta.model)
metaObject.put("network", meta.network)
metaObject.put("user_id", meta.userId)
} catch (e: JSONException) {
e.printStackTrace()
}

View File

@ -1494,6 +1494,26 @@ fun WebView.enableForceDark(isDarkModeOn: Boolean) {
}
}
fun WebView.loadDataCompat(data: String, mimeType: String = "text/html", encoding: String = "utf-8") {
try {
this.loadDataWithBaseURL(
null,
data,
mimeType,
encoding,
null
)
} catch (e: NullPointerException) {
this.loadDataWithBaseURL(
"localhost",
data,
"text/html",
"utf-8",
null
)
}
}
/**
* WebView是否启用强制深色模式
*/

View File

@ -52,7 +52,7 @@ public class LogUtils {
metaObject.put("os", meta.getOs());
metaObject.put("userId", meta.getUserId());
metaObject.put("oaid", appProvider.getOaid());
metaObject.put("rom", meta.getRom());
} catch (JSONException e) {
e.printStackTrace();
}
@ -71,6 +71,7 @@ public class LogUtils {
metaObject.put("channel", meta.getChannel());
metaObject.put("gid", meta.getGid());
metaObject.put("oaid", meta.getOaid());
metaObject.put("rom", meta.getRom());
metaObject.put("jnfj", MetaUtil.getBase64EncodedIMEI());
metaObject.put("mac", meta.getMac());
metaObject.put("manufacturer", meta.getManufacturer());
@ -79,7 +80,7 @@ public class LogUtils {
metaObject.put("os", meta.getOs());
metaObject.put("user_id", meta.getUserId());
metaObject.put("oaid", appProvider.getOaid());
metaObject.put("rom", meta.getRom());
} catch (JSONException e) {
e.printStackTrace();
}
@ -132,7 +133,7 @@ public class LogUtils {
metaObject.put("os", meta.getOs());
metaObject.put("userId", meta.getUserId());
metaObject.put("oaid", meta.getOaid());
metaObject.put("rom", meta.getRom());
object.put("event", "SHARE");
object.put("meta", metaObject);
object.put("timestamp", System.currentTimeMillis() / 1000);

View File

@ -5,12 +5,10 @@ import android.graphics.drawable.Drawable
import android.util.AttributeSet
import android.view.LayoutInflater
import android.widget.FrameLayout
import androidx.core.view.setPadding
import com.gh.gamecenter.common.R
import com.gh.gamecenter.common.databinding.ViewSegmentedIconFilterBinding
import com.gh.gamecenter.common.utils.DarkModeUtils
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.toDrawable
class SegmentedIconFilterView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null): FrameLayout(context, attrs) {
private var textSize = 12F
@ -25,12 +23,15 @@ class SegmentedIconFilterView @JvmOverloads constructor(context: Context, attrs:
var onCheckedAction: ((Int) -> Unit)? = null
init {
val view = LayoutInflater.from(context).inflate(R.layout.view_segmented_icon_filter, this, false)
val view = LayoutInflater.from(context).inflate(R.layout.view_segmented_icon_filter, this, true)
binding = ViewSegmentedIconFilterBinding.bind(view)
addView(view)
initView(attrs)
}
fun setContainerBackground(drawable: Drawable?) {
binding.motionLayout.background = drawable
}
private fun initView(attrs: AttributeSet?) {
if (attrs != null) {
val ta = context.obtainStyledAttributes(attrs, R.styleable.SegmentedFilterView)
@ -46,8 +47,12 @@ class SegmentedIconFilterView @JvmOverloads constructor(context: Context, attrs:
ta.recycle()
}
background = containerBackground ?: R.drawable.bg_shape_ui_container_2_radius_999.toDrawable(context)
setPadding(containerPadding)
binding.motionLayout.run {
setPadding(containerPadding, containerPadding, containerPadding, containerPadding)
containerBackground?.let {
background = it
}
}
indicatorBackground?.let {
binding.indicator.background = it
}
@ -124,7 +129,6 @@ class SegmentedIconFilterView @JvmOverloads constructor(context: Context, attrs:
}
fun updateView(isDarkModeOn: Boolean) {
background = containerBackground ?: R.drawable.bg_shape_ui_container_2_radius_999.toDrawable(context)
binding.motionLayout.run {
when (currentIndex) {
0 -> jumpToState(if (isDarkModeOn) R.id.filterDark1 else R.id.filter1)

View File

@ -1,60 +1,66 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.motion.widget.MotionLayout
xmlns:android="http://schemas.android.com/apk/res/android"
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/motionLayout"
android:layout_width="wrap_content"
android:layout_height="24dp"
app:layoutDescription="@xml/view_segmented_icon_filter_scene">
android:layout_height="wrap_content">
<View
android:id="@+id/indicator"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="@drawable/background_shape_white_radius_999" />
<androidx.constraintlayout.motion.widget.MotionLayout
android:id="@+id/motionLayout"
android:layout_width="130dp"
android:layout_height="28dp"
android:background="@drawable/bg_shape_ui_container_2_radius_999"
android:padding="2dp"
app:layoutDescription="@xml/view_segmented_icon_filter_scene">
<ImageView
android:id="@+id/icon1Iv"
android:layout_width="12dp"
android:layout_height="12dp"
app:srcCompat="@drawable/ic_segmented_icon_filter_dot" />
<View
android:id="@+id/indicator"
android:layout_width="50dp"
android:layout_height="0dp"
android:background="@drawable/background_shape_white_radius_999" />
<TextView
android:id="@+id/filter1Tv"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:gravity="center"
android:includeFontPadding="false"
android:text="近期"
android:textSize="@dimen/secondary_size" />
<ImageView
android:id="@+id/icon1Iv"
android:layout_width="12dp"
android:layout_height="12dp"
app:srcCompat="@drawable/ic_segmented_icon_filter_dot" />
<ImageView
android:id="@+id/icon2Iv"
android:layout_width="12dp"
android:layout_height="12dp"
app:srcCompat="@drawable/ic_segmented_icon_filter_dot" />
<TextView
android:id="@+id/filter1Tv"
android:layout_width="wrap_content"
android:layout_height="28dp"
android:gravity="center"
android:includeFontPadding="false"
android:text="近期"
android:textSize="@dimen/secondary_size" />
<TextView
android:id="@+id/filter2Tv"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:gravity="center"
android:includeFontPadding="false"
android:text="今天"
android:textSize="@dimen/secondary_size" />
<ImageView
android:id="@+id/icon2Iv"
android:layout_width="12dp"
android:layout_height="12dp"
app:srcCompat="@drawable/ic_segmented_icon_filter_dot" />
<ImageView
android:id="@+id/icon3Iv"
android:layout_width="12dp"
android:layout_height="12dp"
app:srcCompat="@drawable/ic_segmented_icon_filter_dot" />
<TextView
android:id="@+id/filter2Tv"
android:layout_width="wrap_content"
android:layout_height="28dp"
android:gravity="center"
android:includeFontPadding="false"
android:text="今天"
android:textSize="@dimen/secondary_size" />
<TextView
android:id="@+id/filter3Tv"
android:layout_width="wrap_content"
android:layout_height="0dp"
android:gravity="center"
android:includeFontPadding="false"
android:text="未来"
android:textSize="@dimen/secondary_size" />
</androidx.constraintlayout.motion.widget.MotionLayout>
<ImageView
android:id="@+id/icon3Iv"
android:layout_width="12dp"
android:layout_height="12dp"
app:srcCompat="@drawable/ic_segmented_icon_filter_dot" />
<TextView
android:id="@+id/filter3Tv"
android:layout_width="wrap_content"
android:layout_height="28dp"
android:gravity="center"
android:includeFontPadding="false"
android:text="未来"
android:textSize="@dimen/secondary_size" />
</androidx.constraintlayout.motion.widget.MotionLayout>
</FrameLayout>

View File

@ -31,11 +31,9 @@
<ConstraintSet android:id="@+id/filter1">
<Constraint
android:id="@+id/indicator"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginEnd="6dp"
android:layout_width="50dp"
android:layout_height="24dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toStartOf="@+id/filter2Tv"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
@ -66,18 +64,18 @@
android:id="@+id/icon2Iv"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginStart="6dp"
android:alpha="0"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="@+id/filter2Tv"
motion:layout_constraintStart_toEndOf="@+id/indicator"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/filter2Tv"
android:layout_width="wrap_content"
android:layout_width="40dp"
android:layout_height="0dp"
android:layout_marginStart="14dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
motion:layout_constraintStart_toEndOf="@+id/indicator"
motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute
motion:attributeName="textColor"
@ -88,19 +86,18 @@
android:id="@+id/icon3Iv"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginStart="6dp"
android:alpha="0"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="@+id/filter3Tv"
motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/filter3Tv"
android:layout_width="wrap_content"
android:layout_width="40dp"
android:layout_height="0dp"
android:layout_marginStart="14dp"
android:layout_marginEnd="6dp"
android:layout_marginStart="-2dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute
@ -112,12 +109,9 @@
<ConstraintSet android:id="@+id/filter2">
<Constraint
android:id="@+id/indicator"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_width="50dp"
android:layout_height="24dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toStartOf="@+id/filter3Tv"
motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
motion:layout_constraintTop_toTopOf="parent" />
@ -125,16 +119,17 @@
android:id="@+id/icon1Iv"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginStart="6dp"
android:alpha="0"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="@+id/filter1Tv"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/filter1Tv"
android:layout_width="wrap_content"
android:layout_width="40dp"
android:layout_height="0dp"
android:layout_marginStart="6dp"
android:layout_marginStart="-2dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent">
@ -147,7 +142,7 @@
android:id="@+id/icon2Iv"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginStart="14dp"
android:layout_marginStart="6dp"
android:alpha="1"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
@ -178,13 +173,11 @@
<Constraint
android:id="@+id/filter3Tv"
android:layout_width="wrap_content"
android:layout_width="40dp"
android:layout_height="0dp"
android:layout_marginStart="14dp"
android:layout_marginEnd="6dp"
android:layout_marginEnd="-2dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute
motion:attributeName="textColor"
@ -195,28 +188,27 @@
<ConstraintSet android:id="@+id/filter3">
<Constraint
android:id="@+id/indicator"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_width="50dp"
android:layout_height="24dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/icon1Iv"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginStart="6dp"
android:alpha="0"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="@+id/filter1Tv"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/filter1Tv"
android:layout_width="wrap_content"
android:layout_width="40dp"
android:layout_height="0dp"
android:layout_marginStart="6dp"
android:layout_marginStart="-2dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent">
@ -229,16 +221,17 @@
android:id="@+id/icon2Iv"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginStart="6dp"
android:alpha="0"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="@+id/filter2Tv"
motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/filter2Tv"
android:layout_width="wrap_content"
android:layout_width="40dp"
android:layout_height="0dp"
android:layout_marginStart="14dp"
android:layout_marginStart="-2dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
motion:layout_constraintTop_toTopOf="parent">
@ -251,7 +244,7 @@
android:id="@+id/icon3Iv"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginStart="14dp"
android:layout_marginStart="6dp"
android:alpha="1"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
@ -262,9 +255,7 @@
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginStart="2dp"
android:layout_marginEnd="6dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toEndOf="@+id/icon3Iv"
motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute
@ -276,11 +267,9 @@
<ConstraintSet android:id="@+id/filterDark1">
<Constraint
android:id="@+id/indicator"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginEnd="8dp"
android:layout_width="50dp"
android:layout_height="24dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toStartOf="@+id/filter2Tv"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
@ -311,18 +300,18 @@
android:id="@+id/icon2Iv"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginStart="6dp"
android:alpha="0"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="@+id/filter2Tv"
motion:layout_constraintStart_toEndOf="@+id/indicator"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/filter2Tv"
android:layout_width="wrap_content"
android:layout_width="40dp"
android:layout_height="0dp"
android:layout_marginStart="14dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
motion:layout_constraintStart_toEndOf="@+id/indicator"
motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute
motion:attributeName="textColor"
@ -333,19 +322,18 @@
android:id="@+id/icon3Iv"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginStart="6dp"
android:alpha="0"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="@+id/filter3Tv"
motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/filter3Tv"
android:layout_width="wrap_content"
android:layout_width="40dp"
android:layout_height="0dp"
android:layout_marginStart="14dp"
android:layout_marginEnd="6dp"
android:layout_marginStart="-2dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute
@ -357,12 +345,9 @@
<ConstraintSet android:id="@+id/filterDark2">
<Constraint
android:id="@+id/indicator"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_width="50dp"
android:layout_height="24dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toStartOf="@+id/filter3Tv"
motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
motion:layout_constraintTop_toTopOf="parent" />
@ -370,16 +355,17 @@
android:id="@+id/icon1Iv"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginStart="6dp"
android:alpha="0"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="@+id/filter1Tv"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/filter1Tv"
android:layout_width="wrap_content"
android:layout_width="40dp"
android:layout_height="0dp"
android:layout_marginStart="6dp"
android:layout_marginStart="-2dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent">
@ -392,7 +378,7 @@
android:id="@+id/icon2Iv"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginStart="14dp"
android:layout_marginStart="6dp"
android:alpha="1"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
@ -415,6 +401,7 @@
android:id="@+id/icon3Iv"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginStart="6dp"
android:alpha="0"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="@+id/filter3Tv"
@ -422,13 +409,11 @@
<Constraint
android:id="@+id/filter3Tv"
android:layout_width="wrap_content"
android:layout_width="40dp"
android:layout_height="0dp"
android:layout_marginStart="14dp"
android:layout_marginEnd="6dp"
android:layout_marginEnd="-2dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute
motion:attributeName="textColor"
@ -439,28 +424,27 @@
<ConstraintSet android:id="@+id/filterDark3">
<Constraint
android:id="@+id/indicator"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginStart="8dp"
android:layout_width="50dp"
android:layout_height="24dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/icon1Iv"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginStart="6dp"
android:alpha="0"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="@+id/filter1Tv"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/filter1Tv"
android:layout_width="wrap_content"
android:layout_width="40dp"
android:layout_height="0dp"
android:layout_marginStart="6dp"
android:layout_marginStart="-2dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="parent"
motion:layout_constraintTop_toTopOf="parent">
@ -473,16 +457,17 @@
android:id="@+id/icon2Iv"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginStart="6dp"
android:alpha="0"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toStartOf="@+id/filter2Tv"
motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
motion:layout_constraintTop_toTopOf="parent" />
<Constraint
android:id="@+id/filter2Tv"
android:layout_width="wrap_content"
android:layout_width="40dp"
android:layout_height="0dp"
android:layout_marginStart="14dp"
android:layout_marginStart="-2dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
motion:layout_constraintTop_toTopOf="parent">
@ -495,7 +480,7 @@
android:id="@+id/icon3Iv"
android:layout_width="12dp"
android:layout_height="12dp"
android:layout_marginStart="14dp"
android:layout_marginStart="6dp"
android:alpha="1"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
@ -506,9 +491,7 @@
android:layout_width="wrap_content"
android:layout_height="0dp"
android:layout_marginStart="2dp"
android:layout_marginEnd="6dp"
motion:layout_constraintBottom_toBottomOf="parent"
motion:layout_constraintEnd_toEndOf="parent"
motion:layout_constraintStart_toEndOf="@+id/icon3Iv"
motion:layout_constraintTop_toTopOf="parent">
<CustomAttribute

View File

@ -65,6 +65,7 @@ object AppExecutor {
val cachedScheduler by lazy { Schedulers.from(ioExecutor) }
val computationScheduler by lazy { Schedulers.from(heavyWeightIoExecutor) }
val logScheduler by lazy { Schedulers.from(logExecutor) }
class MainThreadExecutor : Executor {
private val mainThreadHandler = Handler(Looper.getMainLooper())

View File

@ -49,7 +49,12 @@ data class ExposureEvent(
eTrace: List<ExposureEvent>? = null,
event: ExposureType = ExposureType.EXPOSURE
) {
val currentActivity = CurrentActivityHolder.getCurrentActivity()
val currentActivity = try {
CurrentActivityHolder.getCurrentActivity()
} catch (e: Exception) {
// 捕获异常,避免在获取当前 Activity 时出现问题
null
}
val currentActivitySimpleName = if (currentActivity != null) currentActivity::class.simpleName else "unknown"
val businessId = if (currentActivity is IBusiness) currentActivity.getBusinessId() else null

View File

@ -72,7 +72,7 @@ class RecyclerViewExposureHelper(
init {
val disposable = collectExposureSubject
.throttleLatest(SAMPLE_RATE, TimeUnit.MILLISECONDS)
.subscribeOn(AndroidSchedulers.mainThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
collectExposureData()
}, {

View File

@ -169,7 +169,7 @@ class Va : IVa {
LogConstants.KEY_MANUFACTURER to Build.MANUFACTURER,
LogConstants.KEY_MODEL to Build.MODEL,
LogConstants.KEY_NETWORK to (MetaUtil.getMeta().network ?: ""),
LogConstants.KEY_ROM to RomIdentifier.getRom().name + " " + RomIdentifier.getRom().versionName,
LogConstants.KEY_ROM to RomIdentifier.getRom().romName + " " + RomIdentifier.getRom().romVersion,
LogConstants.KEY_HOST_VERSION to hostVersion,
LogConstants.KEY_HOST_CHANNEL to hostChannel,
LogConstants.KEY_OAID to oaid,

2
vasdk

Submodule vasdk updated: 02d9241f8d...16201868dd