Compare commits

..

1 Commits

Author SHA1 Message Date
718ed2d209 feat: 简单对接云游戏 SDK 2025-04-22 15:21:47 +08:00
292 changed files with 3820 additions and 8466 deletions

View File

@ -113,7 +113,6 @@ android {
buildConfigField "String", "WECHAT_SECRET", "\"${WECHAT_SECRET}\""
buildConfigField "String", "TENCENT_APPID", "\"${TENCENT_APPID}\""
buildConfigField "String", "WEIBO_APPKEY", "\"${WEIBO_APPKEY}\""
buildConfigField "String", "DSP_API_HOST","\"${DSP_API_HOST}\""
// 一体包的32位畅玩游戏助手包名
buildConfigField "String", "EXT_PACKAGE_NAME", "\"${rootProject.ext.EXT_PACKAGE_NAME}\""
}
@ -540,7 +539,9 @@ dependencies {
implementation(project(":feature:wechat_pay"))
}
implementation "me.xdrop:fuzzywuzzy:${fuzzyVersion}"
if(!gradle.ext.excludeOptionalModules || gradle.ext.enableCloudGame){
implementation(project(":feature:cloud_game"))
}
}
File propFile = file('sign.properties')

View File

@ -3,14 +3,9 @@ package com.gh.vspace.installexternalgames
import android.Manifest
import android.app.Dialog
import android.content.ComponentName
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.os.Environment
import android.provider.Settings
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.LinearLayoutManager
import com.gh.common.util.DialogUtils
@ -42,9 +37,6 @@ class InstallExternalGameFragment : ToolbarFragment(), OnItemClickListener {
private var uninstallDisposable: Disposable? = null
private var shouldScan = false
override fun getLayoutId() = 0
override fun getInflatedLayout() =
@ -82,24 +74,7 @@ class InstallExternalGameFragment : ToolbarFragment(), OnItemClickListener {
adapter.notifyDataSetChanged()
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
if (!Environment.isExternalStorageManager()) {
shouldScan = true
AlertDialog.Builder(requireContext()).setMessage("请在设置页面允许光环助手")
.setNegativeButton("") { dialog, which ->
val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
}.show()
} else {
mViewModel.scanPaths()
}
} else {
requestStoragePermission()
}
requestStoragePermission()
}
private fun requestStoragePermission() {
@ -121,12 +96,6 @@ class InstallExternalGameFragment : ToolbarFragment(), OnItemClickListener {
}
}
override fun onStart() {
super.onStart()
if (shouldScan) {
mViewModel.scanPaths()
}
}
private fun initView() {
dialog = DialogUtils.showWaitDialog(requireContext(), "")
@ -183,7 +152,7 @@ class InstallExternalGameFragment : ToolbarFragment(), OnItemClickListener {
}, true)
VHelper.newCwValidateVspaceBeforeAction(
requireContext(), null,
requireContext(),null,
) {
dialog.show()
}

File diff suppressed because one or more lines are too long

View File

@ -180,12 +180,12 @@ object AdDelegateHelper {
config.ownerAd?.startAd?.let { it.id = config.ownerAd.id }
// HarmonyOS 2.2.0 版本不展示第三方开屏广告 (因为会引起奇怪的闪退)
if (MetaUtil.getRom().name == "HarmonyOS"
&& MetaUtil.getRom().versionName == "2.2.0"
&& config.displayRule.adSource == "third_party_ads") {
return
}
// if (MetaUtil.getRom().name == "HarmonyOS"
// && MetaUtil.getRom().versionName == "2.2.0"
// && config.displayRule.adSource == "third_party_ads") {
//
// return
// }
mSplashAd = config
}

View File

@ -18,6 +18,7 @@ import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.provider.IHelpAndFeedbackProvider
import com.gh.gamecenter.common.utils.PackageFlavorHelper
import com.gh.gamecenter.core.provider.IPushProvider
import com.gh.gamecenter.feature.cloudgame.CloudGameHelper
import com.gh.gamecenter.login.utils.QuickLoginHelper
import com.gh.gamecenter.login.view.LoginActivity
import com.gh.gamecenter.va.VCore
@ -106,6 +107,8 @@ class GlobalActivityLifecycleObserver : Application.ActivityLifecycleCallbacks {
if (PackageFlavorHelper.IS_TEST_FLAVOR && activity is AppCompatActivity) {
DarkModeSwitchHelper.showDarkModeSwitchFloatingView(activity)
CloudGameHelper.showCloudGameFloat(activity)
}
if (activity is AppCompatActivity

View File

@ -39,7 +39,6 @@ import com.gh.gamecenter.common.utils.SensorsBridge.EVENT_NAME
import com.gh.gamecenter.common.utils.SensorsBridge.KEY_IS_FIRST_TIME
import com.gh.gamecenter.common.view.dsbridge.CompletionHandler
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.provider.IAcceleratorProvider
import com.gh.gamecenter.core.provider.IPushProvider
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.runOnUiThread
@ -801,14 +800,6 @@ class DefaultJsApi(
listener?.onStopGameAccelerate()
}
@JavascriptInterface
fun getDurationRemainingTime(msg: Any, handler: CompletionHandler<Any>) {
TheRouter.get(IAcceleratorProvider::class.java)?.loadQyUserPermissionData {
val durationExpiredMinute = AcceleratorDataHolder.instance.vipEntity?.durationExpiredTime ?: 0L
handler.complete(durationExpiredMinute)
}
}
@JavascriptInterface
fun refreshToken(token: Any, handler: CompletionHandler<Any>) {
val accessToken = token.toString()

View File

@ -66,8 +66,6 @@ public class Config {
public static final String WGAME_CPM_BUSIAPPID = BuildConfig.WGAME_CPM_BUSIAPPID;
public static final String WGAME_CPM_API_HOST = EnvHelper.getWGameCPMHost();
public static final String DSP_API_HOST = EnvHelper.getDspHost();
// Third-Party confs
public static final String WECHAT_APPID = BuildConfig.WECHAT_APPID;
public static final String WECHAT_SECRET = BuildConfig.WECHAT_SECRET;

View File

@ -13,14 +13,11 @@ import android.text.SpannableString;
import android.text.TextUtils;
import android.text.style.ForegroundColorSpan;
import android.view.View;
import android.view.ViewParent;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
import com.gh.common.chain.BrowserInstallHandler;
import com.gh.common.chain.CheckDownloadHandler;
@ -274,9 +271,7 @@ public class BindingAdapters {
SensorsBridge.trackInstallGameClick(
gameEntity.getId(),
gameEntity.getName() != null ? gameEntity.getName() : "",
"主动安装",
gameEntity.isDspGame(),
gameEntity.getDspAdId()
"主动安装"
);
DownloadEntity downloadEntity = DownloadManager.getInstance().getDownloadEntitySnapshot(gameEntity);
@ -536,14 +531,19 @@ public class BindingAdapters {
binding.tvSellingPoints.setVisibility(View.GONE);
}
Context context = layout.getContext();
binding.gtcvTags.removeAllViews();
ArrayList<TagStyleEntity> tagStyle = gameEntity.getTagStyle();
StringBuilder tagText = new StringBuilder();
for (int i = 0; i < tagStyle.size(); i++) {
if (i < 3) {
tagText.append(i == 0 ? "" : "·").append(tagStyle.get(i).getName());
TextView textView = new TextView(layout.getContext());
textView.setTextColor(ExtensionsKt.toColor(com.gh.gamecenter.common.R.color.text_tertiary, context));
textView.setTextSize(10);
textView.setText((i == 0 ? "" : "·") + tagStyle.get(i).getName());
binding.gtcvTags.addView(textView);
}
}
binding.gtcvTags.setText(tagText);
} else {
layout.setVisibility(View.VISIBLE);
binding.getRoot().setVisibility(View.GONE);
@ -570,20 +570,6 @@ public class BindingAdapters {
break;
}
}
ViewParent parent = binding.getRoot().getParent();
if (parent instanceof ConstraintLayout) {
ConstraintLayout constraintLayout = (ConstraintLayout) parent;
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(constraintLayout);
constraintSet.clear(R.id.gameDesSpace, ConstraintSet.BOTTOM);
if (subjectTag.equals(SUBJECT_TAG_SELLING_POINT)) {
constraintSet.connect(R.id.gameDesSpace, ConstraintSet.BOTTOM, R.id.layout_selling_points, ConstraintSet.TOP);
} else {
constraintSet.connect(R.id.gameDesSpace, ConstraintSet.BOTTOM, R.id.label_list, ConstraintSet.TOP);
}
constraintSet.applyTo(constraintLayout);
}
}
public static void setVideoDetailGameTags(LinearLayout layout, GameEntity gameEntity) {

View File

@ -1,294 +0,0 @@
package com.gh.common.dialog
import android.content.Context
import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.HaloApp
import com.gh.gamecenter.common.base.fragment.BaseDialogFragment
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.core.utils.CurrentActivityHolder
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.databinding.DialogFragmentAccelerateExporationBinding
import kotlinx.parcelize.Parcelize
public class AccelerateExpirationDialogFragment : BaseDialogFragment() {
private lateinit var binding: DialogFragmentAccelerateExporationBinding
private var _reminder: ExpirationReminder? = null
override fun onBack(): Boolean {
return true
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
_reminder = arguments?.getParcelable(KEY_EXPIRATION_REMINDER)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return DialogFragmentAccelerateExporationBinding.inflate(inflater, container, false)
.also {
binding = it
}.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val reminder = _reminder ?: return
val (title, content) = when (reminder) {
is ExpirationReminder.UserExpired -> {
SensorsBridge.trackMonthlyPackageExpiresDialogShow(
reminder.gameId, reminder.gameName, reminder.pkgName, reminder.sourceEntrance
)
getString(
R.string.membership_expiration_reminder_title,
"${reminder.days}"
) to getString(R.string.membership_expiration_reminder_content)
}
is ExpirationReminder.InsufficientDuration -> {
SensorsBridge.trackInsufficientDurationDialogShow(
reminder.gameId,
reminder.gameName,
reminder.pkgName,
reminder.sourceEntrance
)
getString(
R.string.duration_time_out_reminder_title,
"${reminder.hours}"
) to getString(R.string.duration_time_out_reminder_content)
}
is ExpirationReminder.DurationExpired -> {
SensorsBridge.trackTimedPackageExpiresDialogShow(
reminder.gameId,
reminder.gameName,
reminder.pkgName,
reminder.sourceEntrance
)
getString(
R.string.duration_expiration_reminder_title,
"${reminder.days}"
) to getString(R.string.duration_expiration_reminder_content)
}
is ExpirationReminder.TimeRunsOut -> {
SensorsBridge.trackDurationExhaustedDialogShow()
getString(R.string.accelerator_member_expired_title) to getString(R.string.accelerator_member_expired_content)
}
}
binding.tvTitle.text = title
binding.tvContent.text = content
binding.tvCancel.setOnClickListener {
when (reminder) {
is ExpirationReminder.UserExpired -> {
SensorsBridge.trackMonthlyPackageExpiresDialogClick(
reminder.gameId, reminder.gameName, reminder.pkgName, reminder.sourceEntrance, BUTTON_NAME_KNOW
)
}
is ExpirationReminder.InsufficientDuration -> {
SensorsBridge.trackInsufficientDurationDialogClick(
reminder.gameId, reminder.gameName, reminder.pkgName, reminder.sourceEntrance, BUTTON_NAME_KNOW
)
}
is ExpirationReminder.DurationExpired -> {
SensorsBridge.trackTimedPackageExpiresDialogClick(
reminder.gameId, reminder.gameName, reminder.pkgName, reminder.sourceEntrance, BUTTON_NAME_KNOW
)
}
is ExpirationReminder.TimeRunsOut -> {
SensorsBridge.trackDurationExhaustedDialogClick(BUTTON_NAME_KNOW)
}
}
dismissAllowingStateLoss()
}
binding.tvSubmit.setOnClickListener {
when (reminder) {
is ExpirationReminder.UserExpired -> {
SensorsBridge.trackMonthlyPackageExpiresDialogClick(
reminder.gameId, reminder.gameName, reminder.pkgName, reminder.sourceEntrance, BUTTON_NAME_BUY
)
}
is ExpirationReminder.InsufficientDuration -> {
SensorsBridge.trackInsufficientDurationDialogClick(
reminder.gameId, reminder.gameName, reminder.pkgName, reminder.sourceEntrance, BUTTON_NAME_BUY
)
}
is ExpirationReminder.DurationExpired -> {
SensorsBridge.trackTimedPackageExpiresDialogClick(
reminder.gameId, reminder.gameName, reminder.pkgName, reminder.sourceEntrance, BUTTON_NAME_BUY
)
}
is ExpirationReminder.TimeRunsOut -> {
SensorsBridge.trackDurationExhaustedDialogClick(BUTTON_NAME_BUY)
}
}
SensorsBridge.trackMyAssetsPageShow(
reminder.pkgName,
reminder.gameId,
reminder.gameName,
reminder.dialogName
)
dismissAllowingStateLoss()
DirectUtils.navigateToMyAssetsPage(requireContext(), reminder.sourceEntrance)
}
}
override fun onStart() {
super.onStart()
val width = HaloApp.getInstance().resources.displayMetrics.widthPixels - 60F.dip2px()
val height = dialog?.window?.attributes?.height ?: ViewGroup.LayoutParams.WRAP_CONTENT
dialog?.window?.setLayout(width, height)
}
companion object {
const val KEY_EXPIRATION_REMINDER = "key_expiration_reminder"
private const val BUTTON_NAME_KNOW = "知道了"
private const val BUTTON_NAME_BUY = "前往购买"
fun checkDialogShown(context: Context, reminder: ExpirationReminder, callback: ((Boolean) -> Unit)? = null) {
when (reminder) {
is ExpirationReminder.UserExpired -> {
val days = SPUtils.getLong(Constants.SP_ACCELERATOR_USER_REMAINING_REMIND)
if (reminder.days != days) {
SPUtils.setLong(Constants.SP_ACCELERATOR_USER_REMAINING_REMIND, reminder.days)
show(context, reminder)
callback?.invoke(true)
} else {
callback?.invoke(false)
}
}
is ExpirationReminder.InsufficientDuration -> {
val hours = SPUtils.getLong(Constants.SP_ACCELERATOR_DURATION_REMAINING_HOURS)
if (reminder.hours != hours) {
SPUtils.setLong(Constants.SP_ACCELERATOR_DURATION_REMAINING_HOURS, reminder.hours)
show(context, reminder)
callback?.invoke(true)
} else {
callback?.invoke(false)
}
}
is ExpirationReminder.DurationExpired -> {
val days = SPUtils.getLong(Constants.SP_ACCELERATOR_DURATION_REMAINING_DAYS)
if (reminder.days != days) {
SPUtils.setLong(Constants.SP_ACCELERATOR_DURATION_REMAINING_DAYS, reminder.days)
show(context, reminder)
callback?.invoke(true)
} else {
callback?.invoke(false)
}
}
is ExpirationReminder.TimeRunsOut -> {
val hasShow = SPUtils.getBoolean(Constants.SP_ACCELERATOR_MEMBERSHIP_EXPIRED)
if (!hasShow) {
SPUtils.setBoolean(Constants.SP_ACCELERATOR_MEMBERSHIP_EXPIRED, true)
show(context, reminder)
callback?.invoke(true)
} else {
callback?.invoke(false)
}
}
}
}
fun show(context: Context, reminder: ExpirationReminder) {
if (context is AppCompatActivity) {
context.supportFragmentManager
} else {
(CurrentActivityHolder.getCurrentActivity() as? AppCompatActivity)?.supportFragmentManager
}?.let {
val fragment = AccelerateExpirationDialogFragment().apply {
arguments = Bundle().apply {
putParcelable(KEY_EXPIRATION_REMINDER, reminder)
}
}
fragment.show(it, AcceleratorPermissionGuideDialogFragment::class.java.name)
}
}
}
sealed class ExpirationReminder(
val gameId: String,
val gameName: String,
val pkgName: String,
val sourceEntrance: String
) :
Parcelable {
abstract val dialogName: String
@Parcelize
data class InsufficientDuration(
val hours: Long,
private val _gameId: String,
private val _gameName: String,
private val _pkgName: String,
private val _sourceEntrance: String,
) :
ExpirationReminder(_gameId, _gameName, _pkgName, _sourceEntrance) {
override val dialogName: String
get() = "时长不足提示弹窗"
}
@Parcelize
data class DurationExpired(
val days: Long,
private val _gameId: String,
private val _gameName: String,
private val _pkgName: String,
private val _sourceEntrance: String,
) :
ExpirationReminder(_gameId, _gameName, _pkgName, _sourceEntrance) {
override val dialogName: String
get() = "计时套餐临期提示弹窗"
}
@Parcelize
data class UserExpired(
val days: Long,
private val _gameId: String,
private val _gameName: String,
private val _pkgName: String,
private val _sourceEntrance: String,
) :
ExpirationReminder(_gameId, _gameName, _pkgName, _sourceEntrance) {
override val dialogName: String
get() = "包月套餐临期提示弹窗"
}
@Parcelize
data class TimeRunsOut(
private val _sourceEntrance: String,
) : ExpirationReminder("", "", "", _sourceEntrance) {
override val dialogName: String
get() = "时长耗尽提示弹窗"
}
}
}

View File

@ -1,68 +0,0 @@
package com.gh.common.dialog
import android.content.Context
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import com.gh.gamecenter.common.HaloApp
import com.gh.gamecenter.common.base.fragment.BaseDialogFragment
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.core.utils.CurrentActivityHolder
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.databinding.DialogFragmentAcceleratorPermissionGuideBinding
class AcceleratorPermissionGuideDialogFragment : BaseDialogFragment() {
private lateinit var binding: DialogFragmentAcceleratorPermissionGuideBinding
private var callback: (() -> Unit)? = null
override fun onBack(): Boolean {
return true
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return DialogFragmentAcceleratorPermissionGuideBinding.inflate(inflater, container, false)
.also {
binding = it
}.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
SPUtils.setBoolean(Constants.SP_ACCELERATOR_PERMISSION_GUIDE_SHOW, true)
binding.tvSubmit.setOnClickListener {
dismiss()
callback?.invoke()
}
}
fun setOnCallback(block: () -> Unit) {
this.callback = block
}
override fun onStart() {
super.onStart()
val width = HaloApp.getInstance().resources.displayMetrics.widthPixels - 60F.dip2px()
val height = dialog?.window?.attributes?.height ?: ViewGroup.LayoutParams.WRAP_CONTENT
dialog?.window?.setLayout(width, height)
}
companion object {
fun show(context: Context, callback: () -> Unit) {
if (context is AppCompatActivity) {
context.supportFragmentManager
} else {
(CurrentActivityHolder.getCurrentActivity() as? AppCompatActivity)?.supportFragmentManager
}?.let {
val fragment = AcceleratorPermissionGuideDialogFragment()
fragment.setOnCallback(callback)
fragment.show(it, AcceleratorPermissionGuideDialogFragment::class.java.name)
}
}
}
}

View File

@ -19,7 +19,7 @@ class ExposureListener(var fragment: Fragment, var exposable: IExposable) : Recy
ExposureThrottleBus(
{ commitExposure(it) },
Consumer(Throwable::printStackTrace),
{ commitExternalExposure(it) }
{ commitWXCPMExposure(it) }
)
}
var layoutManager: LayoutManager? = null
@ -31,7 +31,7 @@ class ExposureListener(var fragment: Fragment, var exposable: IExposable) : Recy
override fun onFragmentPaused(fm: FragmentManager, f: Fragment) {
if (fragment == f) {
visibleState?.let { commitExposure(it) }
visibleState?.let { commitExternalExposure(it) }
visibleState?.let { commitWXCPMExposure(it) }
throttleBus.clear()
}
}
@ -97,33 +97,23 @@ class ExposureListener(var fragment: Fragment, var exposable: IExposable) : Recy
}
/**
* 提交第三方的曝光事件 微信小游戏CPM 和 DSP 游戏)
* 由于普通曝光事件的上报通道存在节流特性不符合CPM接口对于数据上报的实时性和准确性的要求所以使用额外的通道进行上报
* 微信小游戏CPM曝光事件接口上报由于普通曝光事件的上报通道存在节流特性不符合CPM接口对于数据上报的实时性和准确性的要求所以使用额外的通道进行上报
*/
private fun commitExternalExposure(visibleState: ExposureThrottleBus.VisibleState) {
private fun commitWXCPMExposure(visibleState: ExposureThrottleBus.VisibleState) {
val cpmEventList = arrayListOf<ExposureEvent>()
val dspEventList = arrayListOf<ExposureEvent>()
val eventList = arrayListOf<ExposureEvent>()
for (pos in visibleState.firstVisiblePosition..visibleState.lastVisiblePosition) {
try {
exposable.getEventByPosition(pos)?.let {
when (it.payload.miniGameType) {
Constants.WECHAT_MINI_GAME_CPM -> cpmEventList.add(it)
Constants.DSP_GAME -> dspEventList.add(it)
else -> {
// do nothing
}
if (it.payload.miniGameType == Constants.WECHAT_MINI_GAME_CPM) {
eventList.add(it)
}
}
exposable.getEventListByPosition(pos)?.let { list ->
list.forEach {
when (it.payload.miniGameType) {
Constants.WECHAT_MINI_GAME_CPM -> cpmEventList.add(it)
Constants.DSP_GAME -> dspEventList.add(it)
else -> {
// do nothing
}
if (it.payload.miniGameType == Constants.WECHAT_MINI_GAME_CPM) {
eventList.add(it)
}
}
}
@ -131,8 +121,7 @@ class ExposureListener(var fragment: Fragment, var exposable: IExposable) : Recy
// Just ignore the error.
}
}
ExposureManager.logExternal(cpmEventList, dspEventList)
ExposureManager.logCPM(eventList)
}
}

View File

@ -6,9 +6,7 @@ import com.gh.gamecenter.common.loghub.TLogHubHelper
import com.gh.gamecenter.common.utils.FixedSizeLinkedHashSet
import com.gh.gamecenter.common.utils.toJson
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.dsp.DspReportHelper
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.ExposureType
import com.gh.gamecenter.feature.minigame.wechat.WGameSubjectCPMListReportHelper
import com.lightgame.utils.Utils
import com.volcengine.model.tls.LogItem
@ -38,10 +36,6 @@ object ExposureManager {
AppExecutor.logExecutor.execute {
if (event.payload.miniGameType == Constants.WECHAT_MINI_GAME_CPM) {
WGameSubjectCPMListReportHelper.reportExposure(event)
} else if (event.payload.miniGameType == Constants.DSP_GAME) {
if (event.event == ExposureType.DOWNLOAD_COMPLETE) {
DspReportHelper.report(event.payload.downloadUrl)
}
}
if (!exposureCache.contains(event.id)) {
exposureSet.add(event)
@ -70,18 +64,12 @@ object ExposureManager {
}
/**
* Log a collection of exposure event for external use.
* Log a wechat mini game cpm collection of exposure event.
*/
fun logExternal(cpmEventList: List<ExposureEvent>, dspEventList: ArrayList<ExposureEvent>) {
fun logCPM(eventList: List<ExposureEvent>) {
AppExecutor.logExecutor.execute {
if (cpmEventList.isNotEmpty()) {
WGameSubjectCPMListReportHelper.reportExposure(cpmEventList.toSet())
}
if (dspEventList.isNotEmpty()) {
for (event in dspEventList) {
DspReportHelper.report(event.payload.showUrl)
}
if (eventList.isNotEmpty()) {
WGameSubjectCPMListReportHelper.reportExposure(eventList.toSet())
}
}
}

View File

@ -89,7 +89,6 @@ object ExposureUtils {
exposureEvent.payload.path = path
exposureEvent.payload.speed = speed
exposureEvent.payload.redirectedUrlList = redirectedUrlList
exposureEvent.payload.downloadUrl = gameEntity.downloadUrl
ExposureManager.log(exposureEvent)
ExposureManager.commitSavedExposureEvents(forcedUpload = true)

View File

@ -59,7 +59,7 @@ class CustomFloatingWindowHandler(priority: Int) : PriorityChainHandler(priority
override fun onProcess(): Boolean {
when (getStatus()) {
STATUS_VALID -> {
_showFloatingAction.postValue(Event(data))
_showFloatingAction.value = Event(data)
return true
// floatingWindowProvider.showFloatingWindowOnly(
// mFragment!!,

View File

@ -64,8 +64,7 @@ object GlobalPriorityChainHelper : ISuperiorChain {
* 预启动所有的优先级弹窗管理链
*/
fun preStart(withSpecialDelay: Boolean) {
val launchRedirectHandler = LaunchRedirectHandler(-102)
val membershipExpiredHandler = MembershipExpiredHandler(-101)
val launchRedirectHandler = LaunchRedirectHandler(-101)
val updateDialogHandler = UpdateDialogHandler(-100)
val privacyPolicyDialogHandler = PrivacyPolicyDialogHandler(-99)
val notificationPermissionDialogHandler = NotificationPermissionDialogHandler(0)
@ -75,7 +74,6 @@ object GlobalPriorityChainHelper : ISuperiorChain {
val resumeDownloadHandler = ResumeDownloadHudHandler(4)
mainChain.addHandler(launchRedirectHandler)
mainChain.addHandler(membershipExpiredHandler)
mainChain.addHandler(updateDialogHandler)
mainChain.addHandler(privacyPolicyDialogHandler)
mainChain.addHandler(welcomeDialogHandler)
@ -85,7 +83,6 @@ object GlobalPriorityChainHelper : ISuperiorChain {
mainChain.addHandler(resumeDownloadHandler)
launchRedirectHandler.doPreProcess()
membershipExpiredHandler.doPreProcess()
updateDialogHandler.doPreProcess()
// 首次启动延迟 300ms保证请求首次启动时已经获取到了 GID 、 OAID 等标记

View File

@ -1,80 +0,0 @@
package com.gh.common.prioritychain
import android.os.Bundle
import androidx.fragment.app.FragmentActivity
import com.gh.common.dialog.AccelerateExpirationDialogFragment
import com.gh.gamecenter.common.constant.Constants.SP_ACCELERATOR_MEMBERSHIP_EXPIRED
import com.gh.gamecenter.core.utils.CurrentActivityHolder
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.login.user.UserRepository
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
class MembershipExpiredHandler(priority: Int) : PriorityChainHandler(priority) {
fun doPreProcess() {
UserRepository.getInstance().setAutoLoginListener {
val hasShow = SPUtils.getBoolean(SP_ACCELERATOR_MEMBERSHIP_EXPIRED)
val vipEntity = AcceleratorDataHolder.instance.vipEntity
if (getStatus() == STATUS_PENDING) {
if (vipEntity == null ||
vipEntity.vipStatus ||
vipEntity.isNewUser ||
vipEntity.isVipRefundUser ||
vipEntity.isDurationRefundUser ||
hasShow
) {
processNext()
} else {
updateStatus(STATUS_VALID)
process()
}
} else {
if (vipEntity == null ||
vipEntity.vipStatus ||
vipEntity.isNewUser ||
vipEntity.isVipRefundUser ||
vipEntity.isDurationRefundUser ||
hasShow
) {
updateStatus(STATUS_INVALID)
} else {
updateStatus(STATUS_VALID)
}
}
}
}
override fun onProcess(): Boolean {
val currentActivity = CurrentActivityHolder.getCurrentActivity()
if (GlobalPriorityChainHelper.isThisActivityValid(currentActivity)) {
when (getStatus()) {
STATUS_VALID -> {
val reminder = AccelerateExpirationDialogFragment.ExpirationReminder.TimeRunsOut("")
SPUtils.setBoolean(SP_ACCELERATOR_MEMBERSHIP_EXPIRED, true)
val dialogFragment = AccelerateExpirationDialogFragment().apply {
arguments = Bundle().apply {
putParcelable(
AccelerateExpirationDialogFragment.KEY_EXPIRATION_REMINDER, reminder
)
}
}
dialogFragment.dialog?.setOnDismissListener {
processNext()
}
dialogFragment.show(
(currentActivity as FragmentActivity).supportFragmentManager,
AccelerateExpirationDialogFragment::class.java.name
)
return true
}
STATUS_INVALID -> {
processNext()
}
}
}
return false
}
}

View File

@ -31,6 +31,4 @@ class BuildConfigImpl : IBuildConfigProvider {
override fun getWGameCPMBusiAppId(): String = BuildConfig.WGAME_CPM_BUSIAPPID
override fun getLogProducerProject(): String = BuildConfig.LOG_HUB_PROJECT
override fun getDspApiHost(): String = BuildConfig.DSP_API_HOST
}

View File

@ -119,10 +119,8 @@ class DownloadButtonClickedProviderImpl : IDownloadButtonClickedProvider {
}
// 小游戏的启动不需要上报下载点击事件
// @see https://jira.shanqu.cc/browse/GHZSCY-7013 & https://jira.shanqu.cc/browse/GHZSCY-7918
if (boundedObject is GameEntity
&& (boundedObject.isMiniGame() || boundedObject.isDspGame)
) {
// @see https://jira.shanqu.cc/browse/GHZSCY-7013
if (boundedObject is GameEntity && boundedObject.isMiniGame()) {
return
}

View File

@ -1,57 +1,24 @@
package com.gh.common.provider
import com.gh.common.util.PackageUtils
import android.annotation.SuppressLint
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.core.provider.IAcceleratorDataHolderProvider
import com.gh.gamecenter.feature.entity.BaseEntity
import com.gh.gamecenter.feature.entity.VipEntity
import com.gh.gamecenter.login.retrofit.RetrofitManager
import com.gh.gamecenter.login.user.UserManager
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
@com.therouter.inject.ServiceProvider
class IAcceleratorDataHolderProviderImpl : IAcceleratorDataHolderProvider {
override fun isPaidVip(): Boolean =
AcceleratorDataHolder.instance.isPaidVip
override fun getGhVersionName(): String {
return PackageUtils.getGhVersionName()
}
/**
* 请注意,由于光环后端无法获取 奇游时长套餐 加速时长,这里的 vipStatus不准
* 这里只做参考具体的状态需要奇游sdk提供如果奇游sdk返回失败则以这条接口结果为准
*/
@SuppressLint("CheckResult")
override fun loadVipEntityFromGh(userId: String, callBack: (Any?) -> Unit) {
RetrofitManager.getInstance().newApi.getVipStatus(
userId.ifBlank { UserManager.getInstance().userId },
"gjonline_vip",
true
)
.compose(singleToMain())
.subscribe(object : BiResponse<BaseEntity<VipEntity>>() {
override fun onSuccess(data: BaseEntity<VipEntity>) {
callBack(data.data)
}
override fun onFailure(exception: Exception) {
super.onFailure(exception)
callBack(null)
}
})
}
override fun updateVipEntity(data: Any) {
if (data is VipEntity) {
AcceleratorDataHolder.instance.setVipEntity(data)
override fun setVipEntity(vip: Any) {
if (vip is VipEntity) {
AcceleratorDataHolder.instance.setVipEntity(vip)
}
}
override fun addInitFunResult(result: String) {
AcceleratorDataHolder.instance.addInitFunResult(result)
}
override fun getInitFunResults(): List<String> {
return AcceleratorDataHolder.instance.initResults
}
override fun clear() {
AcceleratorDataHolder.instance.clear()

View File

@ -5,6 +5,7 @@ import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.core.provider.IWechatPayResultProvider
import com.gh.gamecenter.feature.entity.OrderEntity
import com.gh.gamecenter.feature.entity.VipEntity
import com.gh.gamecenter.feature.eventbus.EBPayState
import com.halo.assistant.accelerator.repository.AccelerationRepository
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
@ -24,8 +25,14 @@ class WechatPayResultProviderImpl : IWechatPayResultProvider {
// 支付成功
EventBus.getDefault().post(EBPayState.PaySuccess)
// 先刷新本地状态,支付成功
AcceleratorDataHolder.instance.handleUserRechargeSuccess(orderEntity)
// 先刷新本地状态,支付成功,肯定是付费会员
AcceleratorDataHolder.instance.setVipEntity(
VipEntity(
_vipStatus = true,
_isNewUser = false,
_isTryVip = false
)
)
SensorsBridge.trackMemberRechargeResult(
AccelerationRepository.PAYMENT_TYPE_WECHAT,

View File

@ -55,8 +55,6 @@ public class DetailDownloadUtils {
}
viewHolder.setSpeedViewsVisible(false);
// 默认为显示状态
viewHolder.getDownloadPb().setVisibility(View.VISIBLE);
// 根据预置的配置更新 ViewHolder 的状态 (譬如青少年模式、下载内容为空等)
if (updateViewHolderWithPredefinedConfig(viewHolder, gameEntity)) {
@ -102,9 +100,9 @@ public class DetailDownloadUtils {
viewHolder.getLocalDownloadContainer().setVisibility(View.VISIBLE);
}
// 1.畅玩未安装/当前游戏配置了加速,且当前下载的默认类型为"普通下载",且用户未安装时,禁用双按钮,禁用畅玩
if ((!VHelper.isInstalled(gameEntity.getUniquePackageName())
&& !PackagesManager.isInstalled(gameEntity.getUniquePackageName()) || gameEntity.getCanSpeed())
// 畅玩未安装,且当前下载的默认类型为"普通下载",且用户未安装时,禁用双按钮,禁用畅玩
if (!VHelper.isInstalled(gameEntity.getUniquePackageName())
&& !PackagesManager.isInstalled(gameEntity.getUniquePackageName())
&& downloadEntity != null
&& ExtensionsKt.isLocalDownloadInDualDownloadMode(downloadEntity)
) {
@ -112,22 +110,8 @@ public class DetailDownloadUtils {
if (viewHolder.getOverlayTv() != null) {
viewHolder.getOverlayTv().setVisibility(View.GONE);
}
// 此时需要将 本地占位按钮样式改成单条样式
if (viewHolder.getLocalDownloadContainer() != null) {
viewHolder.getLocalDownloadContainer().setBackgroundResource(com.gh.gamecenter.common.R.drawable.bg_common_button_fill_gradient_blue);
}
if (viewHolder.getLocalDownloadTitleTv() != null) {
viewHolder.getLocalDownloadTitleTv().setTextColor(ExtensionsKt.toColor(com.gh.gamecenter.common.R.color.text_aw_primary, viewHolder.getContext()));
}
} else {
viewHolder.getDownloadPb().setVisibility(View.VISIBLE);
// 将占位按钮样式恢复成左半边样式
if (viewHolder.getLocalDownloadContainer() != null) {
viewHolder.getLocalDownloadContainer().setBackgroundResource(com.gh.gamecenter.common.R.drawable.bg_common_button_light_fill_blue);
}
if (viewHolder.getLocalDownloadTitleTv() != null) {
viewHolder.getLocalDownloadTitleTv().setTextColor(ExtensionsKt.toColor(com.gh.gamecenter.common.R.color.text_theme, viewHolder.getContext()));
}
}
}
@ -154,11 +138,12 @@ public class DetailDownloadUtils {
showDualDownloadButton,
downloadEntity
);
if (downloadEntity != null && downloadEntity.getStatus() != DownloadStatus.done) {
// 如果存在未完成的任务,则不显示加速按钮
} else {
viewHolder.checkIfShowSpeedUi(showVGame, showDualDownloadButton);
if(!showVGame){
String rawBtnText = GameUtils.getDownloadBtnText(viewHolder.getContext(), gameEntity, false, showVGame, PluginLocation.only_game);
viewHolder.checkIfShowSpeedUi(rawBtnText);
}
} else {
// 游戏包含多 APK 的情况
viewHolder.getMultiVersionDownloadTv().setText("选择下载你的版本" + (TextUtils.isEmpty(downloadAddWord) ? "" : "-" + downloadAddWord));
@ -625,7 +610,7 @@ public class DetailDownloadUtils {
return (int) Math.ceil(downloadEntity.getPercent());
}
public static String convertSizeString(String sizeString) {
private static String convertSizeString(String sizeString) {
String numberPart;
String indicator;

View File

@ -1130,15 +1130,9 @@ object DirectUtils {
@JvmStatic
fun directToWebView(context: Context, url: String, entrance: String? = null) {
directToWebView(context, url, entrance, null)
}
@JvmStatic
fun directToWebView(context: Context, url: String, entrance: String? = null, title: String? = null) {
if (url.isEmpty()) return
val bundle = Bundle()
bundle.putString(KEY_ENTRANCE, entrance ?: ENTRANCE_BROWSER)
bundle.putString(EntranceConsts.KEY_GAMENAME, title)
if (url.contains("android_page_type=singleton")) {
bundle.putString(KEY_TO, SingletonWebActivity::class.java.simpleName)
} else {
@ -1534,8 +1528,8 @@ object DirectUtils {
if (categoryId.isEmpty()) return
val bundle = Bundle()
bundle.putString(KEY_TO, CategoryV2Activity::class.java.name)
bundle.putString(KEY_PAGE_ID, categoryId)
bundle.putString(KEY_PAGE_NAME, categoryTitle)
bundle.putString(KEY_CATEGORY_ID, categoryId)
bundle.putString(KEY_CATEGORY_TITLE, categoryTitle)
bundle.putString(KEY_ENTRANCE, BaseActivity.mergeEntranceAndPath(entrance, path))
if (exposureEvent != null) bundle.putParcelableArrayList(
KEY_EXPOSURE_SOURCE_LIST,

View File

@ -2,9 +2,10 @@ package com.gh.common.util
import android.content.Context
import android.os.Build
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.replaceLineBreakWithBr
import com.gh.gamecenter.common.utils.toResString
import com.gh.gamecenter.core.utils.EmptyCallback
import com.gh.gamecenter.feature.entity.ApkEntity
import com.gh.gamecenter.feature.entity.GameEntity
@ -31,7 +32,7 @@ object DownloadDialogHelper {
DialogHelper.showDialogWithHtmlContent(
context,
dialog.title,
dialog.content.replaceLineBreakWithBr(),
dialog.content,
"继续下载",
"取消",
confirmClickCallback = {
@ -58,8 +59,7 @@ object DownloadDialogHelper {
gameName = gameEntity.name ?: "",
gameType = gameEntity.categoryChinese
)
},
extraConfig = DialogHelper.Config(centerTitle = true)
}
)
} else {
callback.onCallback()

View File

@ -61,7 +61,7 @@ object DownloadItemUtils {
gameEntity: GameEntity,
downloadEntity: DownloadEntity,
adapter: RecyclerView.Adapter<out RecyclerView.ViewHolder?>?,
index: Int,
index: Int
) {
if (gameEntity.id != downloadEntity.gameId) {
adapter?.notifyItemChanged(index)
@ -139,7 +139,7 @@ object DownloadItemUtils {
context: Context,
gameEntity: GameEntity,
holder: GameViewHolder,
hideDownloadBtnIfNoAvailableContent: Boolean,
hideDownloadBtnIfNoAvailableContent: Boolean
) {
updateItem(
context = context,
@ -155,7 +155,7 @@ object DownloadItemUtils {
context: Context,
gameEntity: GameEntity,
holder: GameViewHolder,
briefStyle: String?,
briefStyle: String?
) {
updateItem(
context = context,
@ -177,7 +177,7 @@ object DownloadItemUtils {
hideDownloadBtnIfNoAvailableContent: Boolean = false,
briefStyle: String? = null,
isShowRecommendStar: Boolean = false,
listener: DownloadButton.OnUpdateListener? = null,
listener: DownloadButton.OnUpdateListener? = null
) {
holder.gameDownloadBtn.putObject(gameEntity)
@ -223,7 +223,7 @@ object DownloadItemUtils {
gameEntity: GameEntity,
hideDownloadBtnIfNoAvailableContent: Boolean = false,
pluginLocation: PluginLocation? = PluginLocation.only_game,
listener: DownloadButton.OnUpdateListener?,
listener: DownloadButton.OnUpdateListener?
) {
// 控制是否显示下载按钮
downloadBtn.goneIf(context.getString(R.string.app_name) == gameEntity.name)
@ -452,8 +452,7 @@ object DownloadItemUtils {
DownloadStatus.subscribe,
DownloadStatus.diskisfull,
DownloadStatus.diskioerror,
DownloadStatus.overflow,
-> {
DownloadStatus.overflow -> {
buttonStyle = DownloadButton.ButtonStyle.NORMAL
setText(com.gh.gamecenter.feature.R.string.resume)
listener?.completion(downloadBtn.text)
@ -483,7 +482,7 @@ object DownloadItemUtils {
holder: GameViewHolder,
gameEntity: GameEntity,
briefStyle: String?,
isShowRecommendStar: Boolean = false,
isShowRecommendStar: Boolean = false
) {
updateItemViewStatus(holder, briefStyle, gameEntity.columnRecommend, isShowRecommendStar)
val downloadEntity = DownloadManager.getInstance().getDownloadEntitySnapshot(gameEntity)
@ -508,7 +507,7 @@ object DownloadItemUtils {
holder: GameViewHolder,
gameEntity: GameEntity,
briefStyle: String?,
isShowRecommendStar: Boolean = false,
isShowRecommendStar: Boolean = false
) {
val entryMap = gameEntity.getEntryMap()
var downloadEntity: DownloadEntity? = null
@ -536,12 +535,11 @@ object DownloadItemUtils {
context: Context,
holder: GameViewHolder,
downloadEntity: DownloadEntity,
isMultiVersion: Boolean = false,
isMultiVersion: Boolean = false
) {
when (downloadEntity.status) {
DownloadStatus.redirected,
DownloadStatus.downloading,
-> {
DownloadStatus.downloading -> {
if (isMultiVersion) {
holder.gameDownloadBtn.buttonStyle = DownloadButton.ButtonStyle.NORMAL
val darkMode =
@ -558,8 +556,7 @@ object DownloadItemUtils {
)
} else {
holder.gameDownloadTips?.visibility = View.GONE
holder.gameDownloadBtn.buttonStyle =
DownloadButton.ButtonStyle.DOWNLOADING_NORMAL
holder.gameDownloadBtn.buttonStyle = DownloadButton.ButtonStyle.DOWNLOADING_NORMAL
holder.gameDownloadBtn.progress = (downloadEntity.percent * 10).toInt()
holder.gameDownloadBtn.text = downloadEntity.percent.toString() + "%"
}
@ -580,15 +577,13 @@ object DownloadItemUtils {
DownloadStatus.diskioerror,
DownloadStatus.diskisfull,
DownloadStatus.subscribe,
DownloadStatus.overflow,
-> {
DownloadStatus.overflow -> {
if (isMultiVersion) {
holder.gameDownloadTips?.visibility = View.VISIBLE
holder.gameDownloadTips?.setDownloadTipsAnimation(false)
}
holder.gameDownloadBtn.buttonStyle = DownloadButton.ButtonStyle.NORMAL
holder.gameDownloadBtn.text =
context.getString(com.gh.gamecenter.feature.R.string.resume)
holder.gameDownloadBtn.text = context.getString(com.gh.gamecenter.feature.R.string.resume)
}
DownloadStatus.done -> {
@ -621,7 +616,7 @@ object DownloadItemUtils {
holder: GameViewHolder,
briefStyle: String?,
recommendStyle: LinkEntity?,
isShowRecommendStar: Boolean = false,
isShowRecommendStar: Boolean = false
) {
holder.gameDownloadTips?.visibility = View.GONE
// 推荐指数优先,现暂时为游戏单详情列表游戏使用
@ -680,7 +675,7 @@ object DownloadItemUtils {
adapter: RecyclerView.Adapter<out RecyclerView.ViewHolder?>?,
entrance: String,
location: String,
sourceEntrance: String = "其他",
sourceEntrance: String = "其他"
) {
setOnClickListener(
context,
@ -769,7 +764,7 @@ object DownloadItemUtils {
traceEvent: ExposureEvent?,
clickCallback: EmptyCallback?,
refreshCallback: EmptyCallback?,
allStateClickCallback: EmptyCallback?,
allStateClickCallback: EmptyCallback?
) {
// 为 downloadButton 添加游戏实体,供点击的时候上报用
downloadBtn.putObject(gameEntity)
@ -1005,7 +1000,7 @@ object DownloadItemUtils {
entrance: String,
location: String,
traceEvent: ExposureEvent? = null,
refreshCallback: EmptyCallback? = null,
refreshCallback: EmptyCallback? = null
) {
val str =
if (downloadBtn is DownloadButton) downloadBtn.text else context.getString(com.gh.gamecenter.feature.R.string.download)
@ -1114,17 +1109,15 @@ object DownloadItemUtils {
val downloadEntity = SimulatorGameManager.findDownloadEntityByUrl(apk.url)
com.gh.gamecenter.common.utils.NewFlatLogUtils.logGameInstall(
gameId = gameEntity.id,
gameName = gameEntity.name ?: "",
gameId = downloadEntity?.gameId ?: "",
gameName = downloadEntity?.name ?: "",
trigger = "主动安装"
)
SensorsBridge.trackInstallGameClick(
gameId = gameEntity.id,
gameName = gameEntity.name ?: "",
action = "主动安装",
isDspGame = gameEntity.isDspGame,
dspAdId = gameEntity.dspAdId
gameId = downloadEntity?.gameId ?: "",
gameName = downloadEntity?.name ?: "",
action = "主动安装"
)
if (gameEntity.simulator != null) {
@ -1250,7 +1243,7 @@ object DownloadItemUtils {
location: String,
asVGame: Boolean,
isSubscribe: Boolean,
traceEvent: ExposureEvent?,
traceEvent: ExposureEvent?
) {
if (gameEntity.getApk().isEmpty()) return
@ -1294,7 +1287,7 @@ object DownloadItemUtils {
entrance: String,
location: String,
isSubscribe: Boolean,
traceEvent: ExposureEvent?,
traceEvent: ExposureEvent?
) {
val msg = FileUtils.isCanDownload(context, gameEntity.getApk().firstOrNull()?.size ?: "")
if (TextUtils.isEmpty(msg)) {
@ -1321,7 +1314,7 @@ object DownloadItemUtils {
gameEntity: GameEntity,
position: Int,
adapter: RecyclerView.Adapter<out RecyclerView.ViewHolder?>?,
refreshCallback: EmptyCallback?,
refreshCallback: EmptyCallback?
) {
val apkEntity = gameEntity.getApk().firstOrNull()
val downloadEntity = DownloadManager.getInstance().getDownloadEntitySnapshot(gameEntity)
@ -1364,7 +1357,7 @@ object DownloadItemUtils {
location: String,
asVGame: Boolean,
isSubscribe: Boolean,
traceEvent: ExposureEvent?,
traceEvent: ExposureEvent?
) {
// 执行更新操作前,先清理历史下载任务,避免冲突

View File

@ -21,9 +21,9 @@ import com.gh.gamecenter.common.eventbus.EBShowDialog
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.utils.NewFlatLogUtils
import com.gh.gamecenter.core.utils.GsonUtils
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.dsp.DspReportHelper
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.feature.entity.CustomPageTrackData
import com.gh.gamecenter.feature.entity.GameEntity
@ -282,10 +282,11 @@ object DownloadObserver {
private fun performDownloadCompleteAction(
downloadEntity: DownloadEntity,
downloadManager: DownloadManager,
downloadManager: DownloadManager
) {
if (downloadEntity.name.contains(mApplication.getString(R.string.app_name))) {
statDoneEvent(downloadEntity)
MtaHelper.onEvent("软件更新", "下载完成")
// 会有 ActivityNotFoundException 异常catch 掉不管了
tryWithDefaultCatch {
if (Constants.SILENT_UPDATE != downloadEntity.getMetaExtra(Constants.EXTRA_DOWNLOAD_TYPE)) {
@ -298,9 +299,7 @@ object DownloadObserver {
SensorsBridge.trackInstallGameClick(
gameName = downloadEntity.name,
gameId = downloadEntity.gameId,
action = "自动安装",
isDspGame = downloadEntity.getMetaExtra(Constants.DSP_GAME) == "true",
dspAdId = downloadEntity.getMetaExtra(Constants.DSP_AD_ID)
action = "自动安装"
)
// TODO 在 Android 11 上没有授权安装未知应用的权限前第一次调用这个方法系统会杀掉我们的进程...
@ -397,9 +396,7 @@ object DownloadObserver {
SensorsBridge.trackInstallGameClick(
gameId = downloadEntity.gameId,
gameName = downloadEntity.name,
action = "自动安装",
isDspGame = downloadEntity.getMetaExtra(Constants.DSP_GAME) == "true",
dspAdId = downloadEntity.getMetaExtra(Constants.DSP_AD_ID)
action = "自动安装"
)
PackageInstaller.install(mApplication, downloadEntity, false, ignoreAsVGame = false)
}
@ -508,8 +505,7 @@ object DownloadObserver {
gameVersion = downloadEntity.versionName ?: "",
isPlatformRecommend = isPlatformRecommend,
adIconActive = downloadEntity.meta[Constants.AD_ICON_ACTIVE].toBoolean(),
isAdData = downloadEntity.meta[Constants.IS_AD_DATA].toBoolean(),
downloadUrl = downloadEntity.meta[Constants.DOWNLOAD_URL]
isAdData = downloadEntity.meta[Constants.IS_AD_DATA].toBoolean()
),
downloadEntity.platform,
downloadEntity.exposureTrace,
@ -565,77 +561,27 @@ object DownloadObserver {
val pushMessageId = (HaloApp.get(Constants.PUSH_MESSAGE_ID, false) as? String) ?: ""
val pushLinkId = (HaloApp.get(Constants.PUSH_LINK_ENTITY, false) as? LinkEntity)?.link ?: ""
val isFromPush = pushMessageId.isNotEmpty()
if (downloadEntity.getMetaExtra(Constants.DSP_GAME) != "true") {
SensorsBridge.trackEventWithExposureSource(
"DownloadProcessFinish",
exposureEvent?.source,
"game_id",
downloadEntity.gameId,
"game_name",
downloadEntity.meta[Constants.GAME_NAME] ?: "",
"game_type",
downloadEntity.meta[Constants.GAME_CATEGORY_IN_CHINESE] ?: "",
"game_label",
downloadEntity.tags.joinToString(","),
"game_schema_type",
if (downloadEntity.getMetaExtra(Constants.KEY_BIT) == "32") "32位" else "64位",
"page_name",
getCurrentPageEntity().pageName,
"page_id",
getCurrentPageEntity().pageId,
"page_business_id",
getCurrentPageEntity().pageBusinessId,
"last_page_name",
getLastPageEntity().pageName,
"last_page_id",
getLastPageEntity().pageId,
"last_page_business_id",
getLastPageEntity().pageBusinessId,
"download_status",
downloadEntity.meta[Constants.DOWNLOAD_STATUS_IN_CHINESE] ?: "",
"download_type",
if (downloadEntity.asVGame()) "畅玩下载" else "本地下载",
"is_from_push_notifications",
isFromPush,
"message_id",
pushMessageId,
"link_id",
pushLinkId,
*kvs
)
} else {
val searchContent = downloadEntity.getMetaExtra(Constants.SEARCH_KEY)
SensorsBridge.trackEventWithExposureSource(
"DspAdDownloadFinish",
exposureEvent?.source,
"ad_id", downloadEntity.getMetaExtra(Constants.DSP_AD_ID),
"search_content", searchContent,
"game_column_name", downloadEntity.getMetaExtra(Constants.SUBJECT_NAME),
"game_column_id", downloadEntity.getMetaExtra(Constants.SUBJECT_ID),
"game_id", downloadEntity.gameId,
"game_name", downloadEntity.meta[Constants.GAME_NAME] ?: "",
"game_type", downloadEntity.meta[Constants.GAME_CATEGORY_IN_CHINESE] ?: "",
"game_label", downloadEntity.tags.joinToString(","),
"game_schema_type", if (downloadEntity.getMetaExtra(Constants.KEY_BIT) == "32") "32位" else "64位",
"page_name", getCurrentPageEntity().pageName,
"page_id", getCurrentPageEntity().pageId,
"page_business_id", getCurrentPageEntity().pageBusinessId,
"last_page_name", getLastPageEntity().pageName,
"last_page_id", getLastPageEntity().pageId,
"last_page_business_id", getLastPageEntity().pageBusinessId,
"download_status", downloadEntity.meta[Constants.DOWNLOAD_STATUS_IN_CHINESE] ?: "",
"download_type", if (downloadEntity.asVGame()) "畅玩下载" else "本地下载",
"is_from_push_notifications", isFromPush,
"message_id", pushMessageId,
"link_id", pushLinkId,
"location", if (searchContent.isEmpty()) "自定义页面" else "游戏搜索结果列表",
*kvs
)
DspReportHelper.report(downloadEntity.getMetaExtra(Constants.DOWNLOAD_URL))
}
SensorsBridge.trackEventWithExposureSource(
"DownloadProcessFinish",
exposureEvent?.source,
"game_id", downloadEntity.gameId,
"game_name", downloadEntity.meta[Constants.GAME_NAME] ?: "",
"game_type", downloadEntity.meta[Constants.GAME_CATEGORY_IN_CHINESE] ?: "",
"game_label", downloadEntity.tags.joinToString(","),
"game_schema_type", if (downloadEntity.getMetaExtra(Constants.KEY_BIT) == "32") "32位" else "64位",
"page_name", getCurrentPageEntity().pageName,
"page_id", getCurrentPageEntity().pageId,
"page_business_id", getCurrentPageEntity().pageBusinessId,
"last_page_name", getLastPageEntity().pageName,
"last_page_id", getLastPageEntity().pageId,
"last_page_business_id", getLastPageEntity().pageBusinessId,
"download_status", downloadEntity.meta[Constants.DOWNLOAD_STATUS_IN_CHINESE] ?: "",
"download_type", if (downloadEntity.asVGame()) "畅玩下载" else "本地下载",
"is_from_push_notifications", isFromPush,
"message_id", pushMessageId,
"link_id", pushLinkId,
*kvs
)
}
DataCollectionUtils.uploadDownload(mApplication, downloadEntity, "完成")

View File

@ -111,12 +111,7 @@ object GameUtils {
var gh_id: Any?
apkFor@ for (apkEntity in gameEntity.getApk()) {
val isInstalledLocally =
if (gameEntity.isDspGame) {
PackageHelper.validLocalPackageNameSet.contains(apkEntity.packageName)
} else {
PackagesManager.isInstalled(apkEntity.packageName)
}
val isInstalledLocally = PackagesManager.isInstalled(apkEntity.packageName)
// filter by packageName
val settings = Config.getSettings()

View File

@ -2,29 +2,21 @@ package com.gh.common.util
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.gh.common.dialog.AccelerateExpirationDialogFragment
import com.gh.download.DownloadManager
import com.gh.download.PackageObserver
import com.gh.gamecenter.DownloadManagerActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.NewFlatLogUtils
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.core.provider.IAcceleratorProvider
import com.gh.gamecenter.core.utils.CurrentActivityHolder
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.hud.HeadUpDisplayHelper
import com.gh.gamecenter.hud.HeadUpDisplayLogHelper
import com.gh.gamecenter.manager.PackagesManager
import com.gh.gamecenter.packagehelper.PackageRepository
import com.halo.assistant.HaloApp
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.getMetaExtra
import com.gh.gamecenter.dsp.DspReportHelper
import com.gh.gamecenter.R
import com.gh.gamecenter.hud.HeadUpDisplayLogHelper
import com.lightgame.utils.Utils
import com.therouter.TheRouter
import org.greenrobot.eventbus.EventBus
object PackageChangeHelper : DefaultLifecycleObserver {
@ -35,13 +27,12 @@ object PackageChangeHelper : DefaultLifecycleObserver {
private const val UNINSTALL_PENDING = 2
private const val UPDATE_PENDING = 3
// <包名pending 类型,应用版本光环ID> 的 pending 集合
private var pendingPackageMap: HashMap<String, PackageEntity> = hashMapOf()
// <包名pending 类型,应用版本> Triple
private var pendingPackageTriple: Triple<String, Int, String>? = null
private var pendingGhId: String? = null
private var pendingHUDShowed: Boolean = false
private var isLaunching = true
/**
* 添加一个等待中,待确定是否已成功安装的应用
*/
@ -52,21 +43,24 @@ object PackageChangeHelper : DefaultLifecycleObserver {
Utils.log(TAG, "添加了: $packageName 包名等待安装成功")
pendingHUDShowed = false
pendingPackageMap[packageName] = PackageEntity(packageName, INSTALL_PENDING, "")
pendingPackageTriple = Triple(packageName, INSTALL_PENDING, "")
} else {
Utils.log(TAG, "添加了: $packageName 包名等待安装更新成功")
val ghId = PackageUtils.getGhId(packageName)
// 记录光环插件相关信息,用于安装成功后的处理
val ghId = PackageUtils.getGhId(packageName)?.toString() ?: ""
if (ghId != null) {
pendingGhId = ghId.toString()
}
pendingHUDShowed = false
pendingPackageMap[packageName] =
PackageEntity(packageName, UPDATE_PENDING, installData.version, ghId)
pendingPackageTriple = Triple(packageName, UPDATE_PENDING, installData.version)
}
}
fun isInstallPendingPackage(packageName: String): Boolean {
return pendingPackageMap[packageName]?.packageName == packageName
return pendingPackageTriple?.first == packageName
}
/**
@ -74,159 +68,87 @@ object PackageChangeHelper : DefaultLifecycleObserver {
*/
fun addUninstallPendingPackage(packageName: String) {
Utils.log(TAG, "添加了: $packageName 包名等待卸载成功")
pendingPackageMap[packageName] = PackageEntity(packageName, UNINSTALL_PENDING, "")
}
override fun onCreate(owner: LifecycleOwner) {
super.onCreate(owner)
isLaunching = true
pendingPackageTriple = Triple(packageName, UNINSTALL_PENDING, "")
}
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
if (pendingPackageMap.size > 0) {
calculateAndDispatchPackageChanges()
}
if (pendingPackageTriple != null) {
val packageName = pendingPackageTriple?.first ?: return
val isInstallPending = pendingPackageTriple?.second == INSTALL_PENDING
val isUninstallPending = pendingPackageTriple?.second == UNINSTALL_PENDING
val isUpdatePending = pendingPackageTriple?.second == UPDATE_PENDING
refreshVipStatus()
}
/**
* 应用从后台切到前台如果是登录状态需要刷新用户的vip状态
*/
private fun refreshVipStatus() {
val iAcceleratorProvider = TheRouter.get(IAcceleratorProvider::class.java)
if (iAcceleratorProvider?.hasToken() == true &&
AcceleratorDataHolder.instance.enableRefresh &&
AcceleratorDataHolder.instance.vipEntity != null &&
!isLaunching
) {
iAcceleratorProvider.loadQyUserPermissionData {
val vip = AcceleratorDataHolder.instance.vipEntity
if (vip != null && !vip.vipStatus && !vip.isNewUser && !vip.isVipRefundUser && !vip.isDurationRefundUser) {
// 获取当前
val currentActivity = CurrentActivityHolder.getCurrentActivity()
val reminder = AccelerateExpirationDialogFragment.ExpirationReminder.TimeRunsOut("")
if (currentActivity != null && HaloApp.getInstance().isRunningForeground) {
AccelerateExpirationDialogFragment.checkDialogShown(currentActivity, reminder)
}
}
}
}
}
/**
* 计算并分发包名的安装状态
*/
private fun calculateAndDispatchPackageChanges() {
val completeList = arrayListOf<PackageEntity>()
val pendingHudPackageList = arrayListOf<String>()
for (packageEntity in pendingPackageMap.values) {
val packageName = packageEntity.packageName
val pendingVersion = packageEntity.version
val pendingGhId = packageEntity.ghId
val pendingVersion = pendingPackageTriple?.third ?: ""
val installedVersionName = PackageUtils.getVersionNameByPackageName(packageName)
val isInstalled = installedVersionName != null
if (packageEntity.isInstallPending()) {
if (isInstallPending) {
if (isInstalled) {
completeList += packageEntity
pendingPackageTriple = null
pendingGhId = null
PackageRepository.addInstalledGame(
PackageRepository.packageFilterManager,
packageName
)
PackageRepository.addInstalledGame(PackageRepository.packageFilterManager, packageName)
// 添加到额外的包名白名单,下次启动同时查询此包的安装情况
PackageHelper.updateAdditionalWhiteListPackageName(
packageName = packageName,
isAdd = true
)
PackageHelper.updateAdditionalWhiteListPackageName(packageName = packageName, isAdd = true)
performInstallSuccessAction(packageName)
} else {
// 任务不存在了,将等待更新安装结果的 packageEntity 置为 null
if (DownloadManager.getInstance()
.getDownloadEntitySnapshotByPackageName(packageName) == null
) {
completeList += packageEntity
// 任务不存在了,将等待更新安装结果的 triple 置为 null
if (DownloadManager.getInstance().getDownloadEntitySnapshotByPackageName(packageName) == null) {
pendingPackageTriple = null
} else {
pendingHudPackageList.add(packageName)
showHUDIfNeeded(packageName)
}
}
} else if (packageEntity.isUninstallPending() && !isInstalled) {
completeList += packageEntity
} else if (isUninstallPending && !isInstalled) {
pendingPackageTriple = null
pendingGhId = null
// 从额外的包名白名单移除,下次启动时不再查询此包的安装情况
PackageHelper.updateAdditionalWhiteListPackageName(
packageName = packageName,
isAdd = false
)
PackageHelper.updateAdditionalWhiteListPackageName(packageName = packageName, isAdd = false)
performUninstallSuccessAction(packageName)
} else if (packageEntity.isUpdatePending()) {
} else if (isUpdatePending) {
val isUpdateValid = if (installedVersionName != pendingVersion) {
true
} else {
pendingGhId.isNotEmpty()
&& pendingGhId != PackageUtils.getGhId(packageName).toString()
!pendingGhId.isNullOrEmpty() && pendingGhId != PackageUtils.getGhId(packageName).toString()
}
pendingPackageTriple = null
pendingGhId = null
if (isUpdateValid) {
performUninstallSuccessAction(packageName)
performInstallSuccessAction(packageName)
completeList += packageEntity
} else {
// 任务不存在了,将等待更新安装结果的 packageEntity 置为 null
if (DownloadManager.getInstance()
.getDownloadEntitySnapshotByPackageName(packageName) == null
) {
completeList += packageEntity
// 任务不存在了,将等待更新安装结果的 triple 置为 null
if (DownloadManager.getInstance().getDownloadEntitySnapshotByPackageName(packageName) == null) {
pendingPackageTriple = null
} else {
pendingHudPackageList.add(packageName)
showHUDIfNeeded(packageName)
}
}
// 添加到额外的包名白名单,下次启动同时查询此包的安装情况
PackageHelper.updateAdditionalWhiteListPackageName(
packageName = packageName,
isAdd = true
)
PackageHelper.updateAdditionalWhiteListPackageName(packageName = packageName, isAdd = true)
}
}
for (invalidPackage in completeList) {
pendingPackageMap.remove(invalidPackage.packageName)
}
if (pendingHudPackageList.isNotEmpty()) {
showHUDIfNeeded(pendingHudPackageList.last())
}
}
override fun onStop(owner: LifecycleOwner) {
super.onStop(owner)
isLaunching = false
}
private fun showHUDIfNeeded(packageName: String) {
if (pendingHUDShowed) return
val downloadEntity =
DownloadManager.getInstance().getDownloadEntitySnapshotByPackageName(packageName)
?: return
if (downloadEntity.gameId == Constants.GHZS_GAME_ID) return
val downloadEntity = DownloadManager.getInstance().getDownloadEntitySnapshotByPackageName(packageName) ?: return
val activity = CurrentActivityHolder.getCurrentActivity() ?: return
if (activity is DownloadManagerActivity) return
pendingHUDShowed = true
HeadUpDisplayHelper.showHUD(
activity,
HeadUpDisplayHelper.showHUD(activity,
downloadEntity.icon ?: "",
activity.getString(R.string.hud_has_pending_install_game),
activity.getString(R.string.hud_head_to_download_manager),
@ -267,26 +189,16 @@ object PackageChangeHelper : DefaultLifecycleObserver {
private fun performInstallSuccessAction(
packageName: String,
cachedGameEntity: GameEntity? = null,
withLog: Boolean = true,
withLog: Boolean = true
) {
Utils.log(TAG, "安装了: $packageName 包名的程序")
val downloadEntity =
DownloadManager.getInstance().getDownloadEntitySnapshotByPackageName(packageName)
val gameId = downloadEntity?.gameId ?: ""
val gameName = downloadEntity?.name ?: ""
val downloadEntity = DownloadManager.getInstance().getDownloadEntitySnapshotByPackageName(packageName)
val gameId = if (downloadEntity != null && downloadEntity.gameId != null) downloadEntity.gameId else ""
val gameName = if (downloadEntity != null && downloadEntity.name != null) downloadEntity.name else ""
if (withLog && gameId.isNotEmpty()) {
if (withLog) {
NewFlatLogUtils.logGameInstallComplete(gameId, gameName)
SensorsBridge.trackInstallGameFinish(
gameId,
gameName,
downloadEntity?.getMetaExtra(Constants.DSP_GAME) == "true",
downloadEntity?.getMetaExtra(Constants.DSP_AD_ID) ?: ""
)
if (downloadEntity?.getMetaExtra(Constants.DSP_GAME) == "true") {
DspReportHelper.report(downloadEntity.getMetaExtra(Constants.INSTALL_URL))
}
SensorsBridge.trackInstallGameFinish(gameId, gameName)
}
InstallUtils.getInstance().removeInstall(packageName)
@ -325,23 +237,4 @@ object PackageChangeHelper : DefaultLifecycleObserver {
EventBus.getDefault().post(uninstallEb)
}
private class PackageEntity(
val packageName: String,
val type: Int,
val version: String,
val ghId: String = "",
) {
fun isInstallPending(): Boolean {
return type == INSTALL_PENDING
}
fun isUninstallPending(): Boolean {
return type == UNINSTALL_PENDING
}
fun isUpdatePending(): Boolean {
return type == UPDATE_PENDING
}
}
}

View File

@ -2,10 +2,12 @@ package com.gh.common.util
import android.os.Bundle
import androidx.fragment.app.Fragment
import com.therouter.TheRouter
import com.gh.common.iinterface.ISuperiorChain
import com.gh.gamecenter.amway.AmwayFragment
import com.gh.gamecenter.category2.CategoryV2Fragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.provider.IHelpAndFeedbackProvider
import com.gh.gamecenter.discovery.DiscoveryFragment
@ -36,7 +38,6 @@ import com.gh.gamecenter.toolbox.ToolboxFragment
import com.gh.gamecenter.video.detail.HomeVideoFragment
import com.gh.gamecenter.wrapper.ToolbarWrapperFragment
import com.halo.assistant.fragment.WebFragment
import com.therouter.TheRouter
/**
* 通用跳转Fragment方法
@ -147,15 +148,6 @@ object ViewPagerFragmentHelper {
bundle.putString(EntranceConsts.KEY_QUESTIONS_ID, linkEntity.link)
NewQuestionDetailFragment().with(bundle)
}
// 专题合集详情页
TYPE_COLUMN_COLLECTION -> {
bundle.putString(EntranceConsts.KEY_COLLECTION_ID, linkEntity.link)
bundle.putInt(EntranceConsts.KEY_POSITION, 0)
bundle.putString(EntranceConsts.KEY_COLUMNNAME, linkEntity.text)
bundle.putBoolean(EntranceConsts.KEY_IS_COLUMN_COLLECTION, true)
bundle.putString(EntranceConsts.KEY_SUBJECT_TYPE, "tab")
ColumnCollectionDetailFragment().with(bundle)
}
// 其他原来带Toolbar的Fragment
else -> createToolbarWrapperFragment(bundle, linkEntity, isTabWrapper)
}
@ -185,6 +177,15 @@ object ViewPagerFragmentHelper {
bundle.putString(EntranceConsts.KEY_SUBJECT_TYPE, "detail")
bundle.putBoolean(EntranceConsts.KEY_SHOW_DOWNLOAD_MENU, !isTabWrapper)
}
// 专题合集详情页
TYPE_COLUMN_COLLECTION -> {
className = ColumnCollectionDetailFragment::class.java.name
bundle.putString(EntranceConsts.KEY_COLLECTION_ID, entity.link)
bundle.putInt(EntranceConsts.KEY_POSITION, 0)
bundle.putString(EntranceConsts.KEY_COLUMNNAME, entity.text)
bundle.putBoolean(EntranceConsts.KEY_IS_COLUMN_COLLECTION, true)
bundle.putString(EntranceConsts.KEY_SUBJECT_TYPE, "tab")
}
// 开服表
TYPE_SERVER -> {
className = GameServersPublishFragment::class.java.name
@ -199,8 +200,8 @@ object ViewPagerFragmentHelper {
// 分类2.0
TYPE_CATEGORY_V2 -> {
className = CategoryV2Fragment::class.java.name
bundle.putString(EntranceConsts.KEY_PAGE_ID, entity.link)
bundle.putString(EntranceConsts.KEY_PAGE_NAME, entity.text)
bundle.putString(EntranceConsts.KEY_CATEGORY_ID, entity.link)
bundle.putString(EntranceConsts.KEY_CATEGORY_TITLE, entity.text)
bundle.putBoolean(EntranceConsts.KEY_SHOW_DOWNLOAD_MENU, !isTabWrapper)
}
// 通用内容合集详情页

View File

@ -1,48 +1,38 @@
package com.gh.common.view
import android.annotation.SuppressLint
import android.content.Context
import android.content.res.ColorStateList
import android.graphics.Typeface
import android.graphics.Color
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CompoundButton
import android.widget.LinearLayout
import android.widget.PopupWindow
import android.widget.RadioButton
import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.forEach
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.core.content.ContextCompat
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.LayoutCategoryFilterBinding
import com.gh.gamecenter.databinding.LayoutCategoryFilterSizeBinding
import com.gh.gamecenter.databinding.PopCategoryFilterSizeBinding
import com.gh.gamecenter.databinding.PopCategoryFilterTypeBinding
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.setDrawableEnd
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.google.android.flexbox.FlexboxLayout
class CategoryFilterView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr) {
) : LinearLayout(context, attrs, defStyleAttr) {
private val binding: LayoutCategoryFilterBinding
private var mTypeTv: TextView
private var mCatalogTv: TextView
private var mSizeTv: TextView
private var mTypeContainer: View
private var mCatalogContainer: View
private var mSizeContainer: View
private val mTypeFilterArray = arrayOf(SortType.RECOMMENDED, SortType.NEWEST, SortType.RATING)
private val sizeFilterArray by lazy {
listOf(
SubjectSettingEntity.Size(min = -1, max = -1, text = "全部大小"),
SubjectSettingEntity.Size(min = -1, max = 100, text = "100M以下"),
SubjectSettingEntity.Size(min = 100, max = 300, text = "100-300M"),
SubjectSettingEntity.Size(min = 300, max = 500, text = "300-500M"),
SubjectSettingEntity.Size(min = 500, max = 1000, text = "500M-1G"),
SubjectSettingEntity.Size(min = 1000, max = -1, text = "1G以上")
)
}
private var mTypeFilterArray = arrayOf(SortType.RECOMMENDED, SortType.NEWEST, SortType.RATING)
private var sizeFilterArray: ArrayList<SubjectSettingEntity.Size>? = null
private var mOnCategoryFilterSetupListener: OnCategoryFilterSetupListener? = null
private var mOnFilterClickListener: OnFilterClickListener? = null
@ -51,28 +41,29 @@ class CategoryFilterView @JvmOverloads constructor(
private var mSizePopupWindow: PopupWindow? = null
init {
View.inflate(context, R.layout.layout_category_filter, this)
val inflater = LayoutInflater.from(context)
binding = LayoutCategoryFilterBinding.inflate(inflater, this, true)
mTypeTv = findViewById(R.id.type_tv)
mCatalogTv = findViewById(R.id.catalog_tv)
mSizeTv = findViewById(R.id.size_tv)
mTypeContainer = findViewById(R.id.container_type)
mCatalogContainer = findViewById(R.id.container_category)
mSizeContainer = findViewById(R.id.container_size)
mTypeTv.text = mTypeFilterArray[0].value
binding.ivTypeArrow.setImageResource(R.drawable.ic_arrow_down)
binding.ivTypeArrow.imageTintList =
ColorStateList.valueOf(com.gh.gamecenter.common.R.color.text_neutral.toColor(context))
binding.ivSizeArrow.setImageResource(R.drawable.ic_arrow_down)
binding.ivSizeArrow.imageTintList =
ColorStateList.valueOf(com.gh.gamecenter.common.R.color.text_neutral.toColor(context))
binding.tvType.text = mTypeFilterArray[0].value
binding.llTypeContainer.setOnClickListener {
mTypeContainer.setOnClickListener {
mOnFilterClickListener?.onTypeClick()
showSelectTypePopupWindow()
showSelectTypePopupWindow(this, mTypeTv, mTypeTv.text.toString())
}
binding.llSizeContainer.setOnClickListener {
mCatalogContainer.setOnClickListener {
mOnFilterClickListener?.onCategoryClick()
mOnCategoryFilterSetupListener?.onSetupSortCategory()
}
mSizeContainer.setOnClickListener {
mOnFilterClickListener?.onSizeClick()
showSelectSizePopupWindow()
showSelectSizePopupWindow(this, mSizeTv, mSizeTv.text.toString())
}
}
@ -85,133 +76,154 @@ class CategoryFilterView @JvmOverloads constructor(
}
fun resetSortSize() {
binding.tvType.text = R.string.size.toResString()
mSizeTv.text = "全部大小"
}
private fun showSelectTypePopupWindow() {
binding.tvType.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
private fun toggleHighlightedTextView(targetTextView: TextView, highlightIt: Boolean) {
if (highlightIt) {
targetTextView.background = ContextCompat.getDrawable(targetTextView.context, R.drawable.bg_tag_text)
targetTextView.setTextColor(Color.WHITE)
} else {
targetTextView.background = null
targetTextView.setTextColor(ContextCompat.getColor(targetTextView.context, com.gh.gamecenter.common.R.color.text_757575))
}
}
binding.ivTypeArrow.setImageResource(R.drawable.ic_arrow_up)
binding.ivTypeArrow.imageTintList =
ColorStateList.valueOf(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
private fun showSelectTypePopupWindow(containerView: View, typeTv: TextView, typeText: String) {
typeTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
typeTv.setDrawableEnd(R.drawable.ic_filter_arrow_up)
val inflater = LayoutInflater.from(context)
val typeBinding = PopCategoryFilterTypeBinding.inflate(inflater, null, false)
val windowWidth = resources.displayMetrics.widthPixels - 80F.dip2px()
val windowHeight = mOnCategoryFilterSetupListener?.getPopHeight() ?: 0
val inflater = LayoutInflater.from(typeTv.context)
val layout = inflater.inflate(R.layout.layout_filter_size, null)
val windowWidth = typeTv.context.resources.displayMetrics.widthPixels - 80F.dip2px()
val popupWindow = PopupWindow(
typeBinding.root,
layout,
windowWidth,
if (windowHeight == 0) ViewGroup.LayoutParams.WRAP_CONTENT else (windowHeight - height)
LayoutParams.WRAP_CONTENT
).apply { mTypePopupWindow = this }
val flexboxLayout = layout.findViewById<FlexboxLayout>(R.id.flexbox)
val backgroundView = layout.findViewById<View>(R.id.background)
typeBinding.root.setOnClickListener {
backgroundView.setOnClickListener {
popupWindow.dismiss()
}
val checkedId = when (binding.tvType.text.toString()) {
"最新" -> R.id.rb_newest
"评分" -> R.id.rb_score
else -> R.id.rb_popular
}
for (type in mTypeFilterArray) {
val item = inflater.inflate(R.layout.item_filter_size, flexboxLayout, false)
val checkButton = typeBinding.root.findViewById<RadioButton>(checkedId)
checkButton.setTypeface(checkButton.typeface, Typeface.BOLD)
checkButton.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
checkButton.setDrawableEnd(R.drawable.ic_basic_checkmark_circle_fill)
// 单列 3 个,强行设置宽度为屏幕的 1/3
val width = windowWidth / 3
val height = item.layoutParams.height
val onCheckedChangeListener = CompoundButton.OnCheckedChangeListener { button, isChecked ->
if (isChecked) {
button.setTypeface(button.typeface, Typeface.BOLD)
button.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
button.setDrawableEnd(R.drawable.ic_basic_checkmark_circle_fill)
item.layoutParams = ViewGroup.LayoutParams(width, height)
flexboxLayout.addView(item)
val type = when (button.id) {
R.id.rb_newest -> SortType.NEWEST
R.id.rb_score -> SortType.RATING
else -> SortType.RECOMMENDED
}
binding.tvType.text = type.value
mOnCategoryFilterSetupListener?.onSetupSortType(type)
val tv = item.findViewById<TextView>(R.id.size_tv)
tv.text = type.value
toggleHighlightedTextView(tv, typeText == type.value)
tv.tag = type.value
item.setOnClickListener {
toggleHighlightedTextView(tv, true)
popupWindow.dismiss()
} else {
button.setTypeface(button.typeface, Typeface.NORMAL)
button.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
button.setDrawableEnd(null)
}
}
typeBinding.rgFilter.forEach {
if (it is RadioButton) {
it.setOnCheckedChangeListener(onCheckedChangeListener)
typeTv.text = type.value
mOnCategoryFilterSetupListener?.onSetupSortType(type)
}
}
popupWindow.setOnDismissListener {
binding.tvType.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
binding.ivTypeArrow.setImageResource(R.drawable.ic_arrow_down)
binding.ivTypeArrow.imageTintList =
ColorStateList.valueOf(com.gh.gamecenter.common.R.color.text_neutral.toColor(context))
typeTv.setTextColor(com.gh.gamecenter.common.R.color.text_757575.toColor(context))
typeTv.setDrawableEnd(R.drawable.ic_filter_arrow_down)
mTypePopupWindow = null
}
popupWindow.isTouchable = true
popupWindow.isFocusable = true
popupWindow.animationStyle = 0
popupWindow.showAsDropDown(this, 0, 0)
popupWindow.showAsDropDown(containerView, 0, 0)
}
private fun showSelectSizePopupWindow() {
binding.tvSize.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
binding.ivSizeArrow.setImageResource(R.drawable.ic_arrow_up)
binding.ivSizeArrow.imageTintList =
ColorStateList.valueOf(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
private fun showSelectSizePopupWindow(containerView: View, sizeTv: TextView, sizeText: String) {
sizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
sizeTv.setDrawableEnd(R.drawable.ic_filter_arrow_up)
val inflater = LayoutInflater.from(context)
val sizeBinding = PopCategoryFilterSizeBinding.inflate(inflater, null, false)
val windowWidth = context.resources.displayMetrics.widthPixels - 80F.dip2px()
val windowHeight = mOnCategoryFilterSetupListener?.getPopHeight() ?: 0
val inflater = LayoutInflater.from(sizeTv.context)
val layout = inflater.inflate(R.layout.layout_filter_size, null)
val windowWidth = sizeTv.context.resources.displayMetrics.widthPixels - 80F.dip2px()
val popupWindow = PopupWindow(
sizeBinding.root,
layout,
windowWidth,
if (windowHeight == 0) ViewGroup.LayoutParams.WRAP_CONTENT else (windowHeight - height)
LayoutParams.WRAP_CONTENT
).apply { mSizePopupWindow = this }
sizeBinding.root.setOnClickListener {
popupWindow.dismiss()
}
sizeBinding.rvSize.setOnClickListener {
// do nothing
}
val flexboxLayout = layout.findViewById<FlexboxLayout>(R.id.flexbox)
val backgroundView = layout.findViewById<View>(R.id.background)
val sizeAdapter = CategoryFilterSizeAdapter {
if (it.min == INVALID_SIZE && it.max == INVALID_SIZE) {
binding.tvSize.text = R.string.size.toResString()
} else {
binding.tvSize.text = it.text
sizeFilterArray = if (sizeFilterArray == null) {
getDefaultSizeFilterArray()
} else {
sizeFilterArray?.apply {
if (firstOrNull()?.text != "全部大小") {
add(0, SubjectSettingEntity.Size(min = -1, max = -1, text = "全部大小"))
}
}
popupWindow.dismiss()
mOnCategoryFilterSetupListener?.onSetupSortSize(it)
}
sizeBinding.rvSize.layoutManager = GridLayoutManager(context, 3, RecyclerView.VERTICAL, false)
sizeBinding.rvSize.adapter = sizeAdapter
val selectedPosition =
sizeFilterArray.indexOfFirst { it.text?.contains(binding.tvSize.text.toString()) == true }
sizeAdapter.setData(sizeFilterArray, selectedPosition)
backgroundView.setOnClickListener {
popupWindow.dismiss()
}
for (size in sizeFilterArray!!) {
val item = inflater.inflate(R.layout.item_filter_size, flexboxLayout, false)
// 单列 3 个,强行设置宽度为屏幕的 1/3
val width = windowWidth / 3
val height = item.layoutParams.height
item.layoutParams = ViewGroup.LayoutParams(width, height)
flexboxLayout.addView(item)
val tv = item.findViewById<TextView>(R.id.size_tv)
tv.text = size.text
toggleHighlightedTextView(tv, sizeText == size.text)
tv.tag = size.text
item.setOnClickListener {
toggleHighlightedTextView(tv, true)
popupWindow.dismiss()
sizeTv.text = size.text
mOnCategoryFilterSetupListener?.onSetupSortSize(size)
}
}
popupWindow.setOnDismissListener {
binding.tvSize.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
binding.ivSizeArrow.setImageResource(R.drawable.ic_arrow_down)
binding.ivSizeArrow.imageTintList =
ColorStateList.valueOf(com.gh.gamecenter.common.R.color.text_neutral.toColor(context))
sizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_757575.toColor(context))
sizeTv.setDrawableEnd(R.drawable.ic_filter_arrow_down)
mSizePopupWindow = null
}
popupWindow.isTouchable = true
popupWindow.isFocusable = true
popupWindow.animationStyle = 0
popupWindow.showAsDropDown(this, 0, 0)
popupWindow.showAsDropDown(containerView, 0, 0)
}
private fun getDefaultSizeFilterArray(): ArrayList<SubjectSettingEntity.Size> {
return arrayListOf<SubjectSettingEntity.Size>().apply {
add(SubjectSettingEntity.Size(min = -1, max = -1, text = "全部大小"))
add(SubjectSettingEntity.Size(min = -1, max = 100, text = "100M以下"))
add(SubjectSettingEntity.Size(min = 100, max = 300, text = "100-300M"))
add(SubjectSettingEntity.Size(min = 300, max = 500, text = "300-500M"))
add(SubjectSettingEntity.Size(min = 500, max = 1000, text = "500M-1G"))
add(SubjectSettingEntity.Size(min = 1000, max = -1, text = "1G以上"))
}
}
fun setRootBackgroundColor(@ColorInt color: Int) {
@ -219,23 +231,21 @@ class CategoryFilterView @JvmOverloads constructor(
}
fun setItemTextColor(@ColorInt color: Int) {
val colorInt = com.gh.gamecenter.common.R.color.text_neutral.toColor(context)
binding.tvType.setTextColor(color)
binding.tvSize.setTextColor(color)
binding.ivTypeArrow.imageTintList = ColorStateList.valueOf(colorInt)
binding.ivSizeArrow.imageTintList = ColorStateList.valueOf(colorInt)
mTypeTv.setTextColor(color)
mCatalogTv.setTextColor(color)
mSizeTv.setTextColor(color)
}
fun updatePopupWindow() {
when {
mTypePopupWindow != null && mTypePopupWindow!!.isShowing -> {
mTypePopupWindow?.dismiss()
showSelectTypePopupWindow()
showSelectTypePopupWindow(this, mTypeTv, mTypeTv.text.toString())
}
mSizePopupWindow != null && mSizePopupWindow!!.isShowing -> {
mSizePopupWindow?.dismiss()
showSelectSizePopupWindow()
showSelectSizePopupWindow(this, mSizeTv, mSizeTv.text.toString())
}
}
}
@ -243,69 +253,18 @@ class CategoryFilterView @JvmOverloads constructor(
interface OnCategoryFilterSetupListener {
fun onSetupSortSize(sortSize: SubjectSettingEntity.Size)
fun onSetupSortType(sortType: SortType)
fun getPopHeight(): Int
fun onSetupSortCategory()
}
interface OnFilterClickListener {
fun onCategoryClick()
fun onTypeClick()
fun onSizeClick()
}
enum class SortType(val value: String) {
RECOMMENDED("热门"),
NEWEST("最新"),
RATING("评分")
}
class CategoryFilterSizeAdapter(private val itemClick: (SubjectSettingEntity.Size) -> Unit) :
RecyclerView.Adapter<CategoryFilterSizeAdapter.SizeViewHolder>() {
private val dataList = arrayListOf<SubjectSettingEntity.Size>()
private var selectedPosition = 0
@SuppressLint("NotifyDataSetChanged")
fun setData(data: List<SubjectSettingEntity.Size>, position: Int) {
dataList.clear()
dataList.addAll(data)
selectedPosition = position
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SizeViewHolder {
return SizeViewHolder(parent.toBinding())
}
override fun getItemCount(): Int {
return dataList.size
}
override fun onBindViewHolder(holder: SizeViewHolder, position: Int) {
val item = dataList[position]
val context = holder.binding.root.context
if (selectedPosition == position) {
holder.binding.root.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
holder.binding.root.setBackgroundResource(R.drawable.shape_category_filter_size_item_selected)
} else {
holder.binding.root.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
holder.binding.root.setBackgroundResource(R.drawable.shape_category_filter_size_item_normal)
}
holder.binding.root.text = item.text
holder.binding.root.setOnClickListener {
if (selectedPosition == holder.bindingAdapterPosition) {
return@setOnClickListener
}
val lastPosition = selectedPosition
selectedPosition = holder.bindingAdapterPosition
notifyItemChanged(lastPosition)
notifyItemChanged(selectedPosition)
itemClick(item)
}
}
class SizeViewHolder(val binding: LayoutCategoryFilterSizeBinding) : RecyclerView.ViewHolder(binding.root)
}
companion object {
private const val INVALID_SIZE = -1
RECOMMENDED("热门推荐"),
NEWEST("最新上线"),
RATING("最高评分")
}
}

View File

@ -1,23 +1,18 @@
package com.gh.common.view
import android.content.Context
import android.graphics.Typeface
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.CheckedTextView
import android.widget.LinearLayout
import android.widget.PopupWindow
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.forEach
import androidx.core.view.setPadding
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.setDrawableEnd
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.toDrawable
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.google.android.flexbox.FlexboxLayout
@ -35,15 +30,11 @@ class ConfigFilterView @JvmOverloads constructor(
var recommendedTv: TextView
var updateTv: TextView //更新
var container: View
private var dot1: View
private var dot2: View
private var dot3: View
private var sizeFilterArray: ArrayList<SubjectSettingEntity.Size>? = null
var sizeFilterArray: ArrayList<SubjectSettingEntity.Size>? = null
private var mOnConfigFilterSetupListener: OnConfigFilterSetupListener? = null
private var mSelectionTvList: ArrayList<TextView>
private var highlightedSortedType: SortType? = null
init {
View.inflate(context, R.layout.layout_config_filter, this)
@ -54,9 +45,6 @@ class ConfigFilterView @JvmOverloads constructor(
updateTv = findViewById(R.id.updateTv)
recommendedTv = findViewById(R.id.recommended_tv)
container = findViewById(R.id.config_controller)
dot1 = findViewById(R.id.dot1)
dot2 = findViewById(R.id.dot2)
dot3 = findViewById(R.id.dot3)
mSelectionTvList = arrayListOf(newestTv, ratingTv, updateTv, recommendedTv)
@ -66,30 +54,24 @@ class ConfigFilterView @JvmOverloads constructor(
}
ratingTv.setOnClickListener {
highlightedSortedType = SortType.RATING
mOnConfigFilterSetupListener?.onSetupSortType(SortType.RATING)
updateHighlightedTextView(ratingTv)
}
updateTv.setOnClickListener {
highlightedSortedType = SortType.UPDATE
mOnConfigFilterSetupListener?.onSetupSortType(SortType.UPDATE)
updateHighlightedTextView(updateTv)
}
newestTv.setOnClickListener {
highlightedSortedType = SortType.NEWEST
mOnConfigFilterSetupListener?.onSetupSortType(SortType.NEWEST)
updateHighlightedTextView(newestTv)
}
recommendedTv.setOnClickListener {
highlightedSortedType = SortType.RECOMMENDED
mOnConfigFilterSetupListener?.onSetupSortType(SortType.RECOMMENDED)
updateHighlightedTextView(recommendedTv)
}
updateAllTextView(SortType.UPDATE)
}
private fun updateHighlightedTextView(highlightedTv: TextView) {
@ -98,17 +80,14 @@ class ConfigFilterView @JvmOverloads constructor(
}
}
fun updateAllTextView(sortType: SortType? = highlightedSortedType) {
highlightedSortedType = sortType
sortType?.let {
when (it) {
SortType.RECOMMENDED -> updateHighlightedTextView(recommendedTv)
SortType.NEWEST -> updateHighlightedTextView(newestTv)
SortType.RATING -> updateHighlightedTextView(ratingTv)
SortType.UPDATE -> updateHighlightedTextView(updateTv)
}
mSizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
fun updateAllTextView(sortType: SortType) {
when (sortType) {
SortType.RECOMMENDED -> updateHighlightedTextView(recommendedTv)
SortType.NEWEST -> updateHighlightedTextView(newestTv)
SortType.RATING -> updateHighlightedTextView(ratingTv)
SortType.UPDATE -> updateHighlightedTextView(updateTv)
}
mSizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_757575.toColor(context))
}
fun setOnConfigSetupListener(onConfigFilterSetupListener: OnConfigFilterSetupListener) {
@ -117,11 +96,11 @@ class ConfigFilterView @JvmOverloads constructor(
private fun toggleHighlightedTextView(targetTextView: TextView, highlightIt: Boolean) {
if (highlightIt) {
targetTextView.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
targetTextView.setTypeface(Typeface.DEFAULT, Typeface.BOLD)
targetTextView.background = R.drawable.bg_tag_text.toDrawable()
targetTextView.setTextColor(com.gh.gamecenter.common.R.color.text_white.toColor(context))
} else {
targetTextView.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
targetTextView.setTypeface(Typeface.DEFAULT, Typeface.NORMAL)
targetTextView.background = null
targetTextView.setTextColor(com.gh.gamecenter.common.R.color.text_757575.toColor(context))
}
}
@ -133,8 +112,8 @@ class ConfigFilterView @JvmOverloads constructor(
}
private fun showSelectionPopupWindow(containerView: View, sizeTv: TextView, sizeText: String) {
sizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(sizeTv.context))
sizeTv.setDrawableEnd(R.drawable.ic_auxiliary_arrow_up_primary_8)
sizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(sizeTv.context))
sizeTv.setDrawableEnd(R.drawable.ic_filter_arrow_up)
val inflater = LayoutInflater.from(sizeTv.context)
val layout = inflater.inflate(R.layout.layout_filter_size, null)
@ -157,39 +136,43 @@ class ConfigFilterView @JvmOverloads constructor(
}
}
flexboxLayout.setOnClickListener { }
backgroundView.setOnClickListener {
popupWindow.dismiss()
}
for (size in sizeFilterArray!!) {
val item = inflater.inflate(R.layout.item_config_filter_size, flexboxLayout, false)
val item = inflater.inflate(R.layout.item_filter_size, flexboxLayout, false)
// 单列 4 个,强行设置宽度为屏幕的 1/4
val width = (sizeTv.context.resources.displayMetrics.widthPixels - 56F.dip2px()) / 4
val width = sizeTv.context.resources.displayMetrics.widthPixels / 4
val height = item.layoutParams.height
item.layoutParams = ViewGroup.LayoutParams(width, height)
flexboxLayout.addView(item)
val tv = item.findViewById<CheckedTextView>(R.id.size_tv)
val tv = item.findViewById<TextView>(R.id.size_tv)
tv.text = size.text
tv.isChecked = sizeText == size.text
if (sizeText == size.text) {
toggleHighlightedTextView(tv, true)
} else {
toggleHighlightedTextView(tv, false)
}
tv.tag = size.text
tv.setOnClickListener {
flexboxLayout.forEach { checkedTv ->
(checkedTv as CheckedTextView).isChecked = checkedTv == tv
}
item.setOnClickListener {
toggleHighlightedTextView(tv, true)
popupWindow.dismiss()
sizeTv.text = size.text
mOnConfigFilterSetupListener?.onSetupSortSize(size)
}
}
popupWindow.setOnDismissListener {
sizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(sizeTv.context))
sizeTv.setDrawableEnd(R.drawable.ic_auxiliary_arrow_down_8)
sizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_757575.toColor(sizeTv.context))
sizeTv.setDrawableEnd(R.drawable.ic_filter_arrow_down)
mPopupWindow = null
}
@ -210,49 +193,6 @@ class ConfigFilterView @JvmOverloads constructor(
}
}
fun initSubjectFilterView(subjectSetting: SubjectSettingEntity) {
ratingTv.visibility = View.VISIBLE
if (subjectSetting.filterOptions.size > 1) {
// 重排序
subjectSetting.filterOptions.forEachIndexed { index, s ->
when (index) {
0 -> updateTv.text = s
1 -> recommendedTv.text = s
2 -> newestTv.text = s
3 -> ratingTv.text = s
}
}
} else {
updateTv.setPadding(0)
updateTv.setTypeface(Typeface.DEFAULT, Typeface.NORMAL)
updateTv.isClickable = false
updateTv.text = when (subjectSetting.filterOptions.first()) {
"推荐" -> "根据光环推荐排序"
"最新" -> "根据游戏上新排序"
"评分" -> "根据游戏评分排序"
"更新" -> "根据更新时间排序"
else -> subjectSetting.filterOptions.first()
}
}
// 隐藏相关选项
updateTv.goneIf(subjectSetting.filterOptions.isEmpty())
recommendedTv.goneIf(subjectSetting.filterOptions.size <= 1)
dot1.goneIf(subjectSetting.filterOptions.size <= 1)
newestTv.goneIf(subjectSetting.filterOptions.size <= 2)
dot2.goneIf(subjectSetting.filterOptions.size <= 2)
ratingTv.goneIf(subjectSetting.filterOptions.size <= 3)
dot3.goneIf(subjectSetting.filterOptions.size <= 3)
sizeFilterArray = subjectSetting.filterSizes
if (subjectSetting.filterOptions.size == 1) {
updateTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
updateTv.setTypeface(Typeface.DEFAULT, Typeface.NORMAL)
highlightedSortedType = null
}
}
interface OnConfigFilterSetupListener {
fun onShowSortSize()
fun onSetupSortSize(sortSize: SubjectSettingEntity.Size)

View File

@ -306,14 +306,21 @@ object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
DownloadNotificationHelper.addOrUpdateDownloadNotification(downloadEntity)
NDataChanger.notifyDataChanged(downloadEntity)
DownloadManager.getInstance().updateDownloadEntity(downloadEntity)
// 仅官网渠道上报 XAPK 异常信息
if (HaloApp.getInstance().channel == "GH_206") {
SentryHelper.onEvent(
"XAPK_UNZIP_ERROR",
"gameName", downloadEntity.name,
"errorDigest", exception.localizedMessage
)
}
DownloadDataHelper.uploadDownloadStatusEvent(downloadEntity, "xapk解压失败")
SensorsBridge.trackGameDecompressionFailed(
downloadEntity.gameId,
downloadEntity.name,
downloadEntity.categoryChinese,
exception.localizedMessage ?: "unknown error"
downloadEntity.categoryChinese
)
}

View File

@ -361,17 +361,6 @@ public class DownloadManager implements DownloadStatusListener {
ExtensionsKt.addMetaExtra(downloadEntity, VHelper.KEY_REQUIRED_G_APPS, gameEntity.getGAppsSwitch());
}
if (gameEntity.isDspGame()) {
ExtensionsKt.addMetaExtra(downloadEntity, Constants.DSP_GAME, "true");
ExtensionsKt.addMetaExtra(downloadEntity, Constants.SEARCH_KEY, gameEntity.getSearchKey());
ExtensionsKt.addMetaExtra(downloadEntity, Constants.SUBJECT_NAME, gameEntity.getSubjectName());
ExtensionsKt.addMetaExtra(downloadEntity, Constants.SUBJECT_ID, gameEntity.getSubjectId());
ExtensionsKt.addMetaExtra(downloadEntity, Constants.INSTALL_URL, gameEntity.getInstallUrl());
ExtensionsKt.addMetaExtra(downloadEntity, Constants.DOWNLOAD_URL, gameEntity.getDownloadUrl());
ExtensionsKt.addMetaExtra(downloadEntity, Constants.DSP_AD_ID, gameEntity.getDspAdId());
}
ExtensionsKt.addMetaExtra(downloadEntity, Constants.KEY_BIT, apkEntity.getBit());
// 记录是否为双下载按钮模式
@ -491,42 +480,13 @@ public class DownloadManager implements DownloadStatusListener {
};
List<Object> kvs = new ArrayList<>(Arrays.asList(arrayKv));
if (customPageTrackData != null) {
kvs.addAll(Arrays.asList(customPageTrackData.toKV()));
}
if (!gameEntity.isDspGame()) {
SensorsBridge.trackEventWithExposureSource(
"DownloadProcessBegin",
downloadExposureEvent.getSource(), kvs.toArray(new Object[0])
);
} else {
String searchKey = gameEntity.getSearchKey();
String loc;
if (TextUtils.isEmpty(searchKey)) {
loc = "自定义页面";
} else {
loc = "游戏搜索结果列表";
}
kvs.add("ad_id");
kvs.add(gameEntity.getDspAdId());
kvs.add("search_content");
kvs.add(searchKey);
kvs.add("game_column_name");
kvs.add(gameEntity.getSubjectName());
kvs.add("game_column_id");
kvs.add(gameEntity.getSubjectId());
kvs.add("location");
kvs.add(loc);
SensorsBridge.trackEventWithExposureSource(
"DspAdDownloadBegin",
downloadExposureEvent.getSource(), kvs.toArray(new Object[0])
);
}
SensorsBridge.trackEventWithExposureSource("DownloadProcessBegin",
downloadExposureEvent.getSource(), kvs.toArray(new Object[0])
);
//TODO remove
DownloadManager.getInstance().putStatus(downloadEntity.getUrl(), DownloadStatus.downloading);

View File

@ -175,9 +175,7 @@ object PackageObserver {
SensorsBridge.trackInstallGameClick(
gameId = mDownloadEntity.gameId,
gameName = mDownloadEntity.name,
action = "自动安装",
isDspGame = mDownloadEntity.getMetaExtra(Constants.DSP_GAME) == "true",
dspAdId = mDownloadEntity.getMetaExtra(Constants.DSP_AD_ID)
action = "自动安装"
)
PackageInstaller.install(application, mDownloadEntity)
}

View File

@ -944,9 +944,7 @@ public class MainActivity extends BaseActivity {
SensorsBridge.trackInstallGameClick(
finalDownloadEntity.getGameId(),
finalDownloadEntity.getName(),
"主动安装",
"true".equals(ExtensionsKt.getMetaExtra(finalDownloadEntity, Constants.DSP_GAME)),
ExtensionsKt.getMetaExtra(finalDownloadEntity, Constants.DSP_AD_ID)
"主动安装"
);
PackageInstaller.install(MainActivity.this, finalDownloadEntity);
}, 200);
@ -1067,9 +1065,7 @@ public class MainActivity extends BaseActivity {
SensorsBridge.trackInstallGameClick(
downloadEntity.getGameId(),
downloadEntity.getName(),
"自动安装",
"true".equals(ExtensionsKt.getMetaExtra(downloadEntity, Constants.DSP_GAME)),
ExtensionsKt.getMetaExtra(downloadEntity, Constants.DSP_AD_ID)
"自动安装"
);
PackageInstaller.install(this, downloadEntity, false, false);
}

View File

@ -23,7 +23,6 @@ import com.gh.common.simulator.NewSimulatorGameManager
import com.gh.common.simulator.SimulatorDownloadManager
import com.gh.common.simulator.SimulatorGameManager
import com.gh.common.util.*
import com.gh.common.util.GameUtils.getDownloadBtnText
import com.gh.common.util.LogUtils
import com.gh.common.xapk.XapkInstaller
import com.gh.common.xapk.XapkUnzipStatus
@ -45,7 +44,6 @@ import com.gh.gamecenter.core.utils.StringUtils
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.eventbus.EBScroll
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.PluginLocation
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.feature.view.DownloadButton.ButtonStyle
@ -104,13 +102,7 @@ class DetailViewHolder(
val speedContainer: ConstraintLayout?
private val ivFreeVipTag: ImageView?
private val gMoreZone: Group?
private val gUpdate: Group?
private val ivUpdate: ImageView?
private val tvSize: TextView?
private val vUpdate: View?
private val tvUpdate: TextView?
private var isShowVaUpdate = true
// 注意 View 的命名
init {
@ -129,14 +121,8 @@ class DetailViewHolder(
speedContainer = view.findViewById(R.id.cl_speed_container)
ivFreeVipTag = view.findViewById(R.id.iv_free_vip_tag)
gMoreZone = view.findViewById(R.id.g_more_zone)
gUpdate = view.findViewById(R.id.g_update)
ivUpdate = view.findViewById(R.id.iv_update)
tvSize = view.findViewById(R.id.tv_size)
vUpdate = view.findViewById(R.id.v_update)
tvUpdate = view.findViewById(R.id.tv_update)
context = view.context
com.gh.gamecenter.common.R.color.text_aw_primary.toColor()
var gameDownloadMode = gameEntity.getGameDownloadButtonMode()
@ -195,19 +181,8 @@ class DetailViewHolder(
com.gh.gamecenter.feature.R.string.download_local
)
)
vUpdate?.setOnClickListener {
if (isShowVaUpdate) {
vGameDownloadListener.onClick(vUpdate)
} else {
localDownloadListener.onClick(vUpdate)
}
}
}
}
downloadPb.putWidgetBusinessName("游戏详情页")
// "DownLoadbuttonClick" 埋点需要上报traceEvent信息
@ -217,90 +192,32 @@ class DetailViewHolder(
}
private fun restoreDialogFragment() {
val gamePermissionDialogFragment = (context.getActivity() as? AppCompatActivity)?.supportFragmentManager?.findFragmentByTag(
val gamePermissionDialogFragment = (context as AppCompatActivity).supportFragmentManager.findFragmentByTag(
GamePermissionDialogFragment::class.java.name
) as DialogFragment?
gamePermissionDialogFragment?.dismissAllowingStateLoss()
}
fun checkIfShowSpeedUi(showVGame: Boolean, showDualDownloadButton: Boolean) {
fun checkIfShowSpeedUi(rawBtnText: String) {
acceleratorUiHelper?.let {
val showSpeedUi = when {
gameEntity.canSpeed && showDualDownloadButton -> { // 双按钮
val localText = getDownloadBtnText(context, gameEntity, false, false, PluginLocation.only_game);
val downloadText =
getDownloadBtnText(context, gameEntity, false, true, PluginLocation.only_game)
when {
localText.contains(com.gh.gamecenter.feature.R.string.update.toResString()) -> { // 本地游戏需要更新
localDownloadContainer?.goneIf(true)
downloadPb.goneIf(true)
overlayTv?.goneIf(true)
gUpdate?.visibility = View.VISIBLE
ivUpdate?.goneIf(true)
tvSize?.goneIf(false)
tvSize?.text =
DetailDownloadUtils.convertSizeString(gameEntity.getApk().firstOrNull()?.size ?: "")
tvUpdate?.setText(R.string.update)
isShowVaUpdate = false
true
}
localText.contains(com.gh.gamecenter.feature.R.string.launch.toResString()) && downloadText == "更新" -> { // 畅玩游戏需要更新:显示 加速/更新
localDownloadContainer?.goneIf(true)
downloadPb.goneIf(true)
overlayTv?.goneIf(true)
gUpdate?.visibility = View.VISIBLE
ivUpdate?.goneIf(false)
tvSize?.goneIf(true)
tvUpdate?.setText(R.string.update)
isShowVaUpdate = true
true
}
localText.contains(com.gh.gamecenter.feature.R.string.launch.toResString()) -> { // 本地游戏为启动状态:显示 加速/畅玩
localDownloadContainer?.goneIf(true)
downloadPb.goneIf(true)
overlayTv?.goneIf(true)
gUpdate?.visibility = View.VISIBLE
tvUpdate?.setText(R.string.v_play)
tvSize?.goneIf(true)
isShowVaUpdate = true
true
}
else -> {
gUpdate?.visibility = View.GONE
tvSize?.goneIf(true)
false
}
}
when {
rawBtnText == "启动" && gameEntity.canSpeed -> {
downloadPb.goneIf(true)
localDownloadButton?.goneIf(true)
it.checkIfShowSpeedUi(true)
}
gameEntity.canSpeed && !showVGame -> {
gUpdate?.visibility = View.GONE
val downloadText = getDownloadBtnText(context, gameEntity, false, false, PluginLocation.only_game)
when {
downloadText.contains(com.gh.gamecenter.feature.R.string.launch.toResString()) -> {
localDownloadContainer?.goneIf(true)
downloadPb.goneIf(true)
true
}
downloadText.contains(R.string.update.toResString()) -> true
else -> false
}
rawBtnText == "更新" && gameEntity.canSpeed -> {
it.checkIfShowSpeedUi(true)
}
else -> false
else -> {
it.checkIfShowSpeedUi(false)
}
}
it.checkIfShowSpeedUi(showSpeedUi, showDualDownloadButton)
}
}
fun setSpeedViewsVisible(isVisible: Boolean) {
@ -394,9 +311,7 @@ class DetailViewHolder(
SensorsBridge.trackInstallGameClick(
mGameEntity.id,
mGameEntity.name ?: "",
"主动安装",
mGameEntity.isDspGame,
mGameEntity.dspAdId
"主动安装"
)
PackageInstaller.install(mViewHolder.context, mDownloadEntity)
}
@ -521,9 +436,7 @@ class DetailViewHolder(
SensorsBridge.trackInstallGameClick(
gameId = mGameEntity.id,
gameName = mGameEntity.name ?: "",
action = "主动安装",
isDspGame = mGameEntity.isDspGame,
dspAdId = mGameEntity.dspAdId
action = "主动安装"
)
val url = mGameEntity.getApk().firstOrNull()?.url
val downloadEntity = SimulatorGameManager.findDownloadEntityByUrl(url)

View File

@ -1,89 +1,82 @@
package com.gh.gamecenter.category2
import android.annotation.SuppressLint
import android.content.Context
import android.view.ViewGroup
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.view.GridSpacingItemColorDecoration
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.databinding.CategoryDirectoryItemBinding
import com.gh.gamecenter.entity.CategoryEntity
import com.lightgame.adapter.BaseRecyclerAdapter
class CategoryDirectoryAdapter(
private val listener: SearchCategoryPop.OnSearchCategoryListener
) : RecyclerView.Adapter<CategoryDirectoryAdapter.CategoryDirectoryItemViewHolder>() {
context: Context,
private val mViewModel: CategoryV2ViewModel,
private var mList: List<CategoryEntity>
) : BaseRecyclerAdapter<CategoryDirectoryAdapter.CategoryDirectoryItemViewHolder>(context) {
private val data = arrayListOf<CategoryEntity>()
val width = mContext.resources.displayMetrics.widthPixels * 260 / 360
@SuppressLint("NotifyDataSetChanged")
fun setListData(newData: List<CategoryEntity>) {
data.clear()
data.addAll(newData)
fun setListData(list: List<CategoryEntity>) {
mList = list
notifyDataSetChanged()
}
override fun getItemCount() = data.size
override fun getItemCount() = mList.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
CategoryDirectoryItemViewHolder(listener, parent.toBinding())
CategoryDirectoryItemViewHolder(CategoryDirectoryItemBinding.inflate(mLayoutInflater))
override fun onBindViewHolder(holder: CategoryDirectoryItemViewHolder, position: Int) {
holder.onBind(position, data[position])
}
holder.binding.run {
root.layoutParams = root.layoutParams?.apply {
width = mContext.resources.displayMetrics.widthPixels * 260 / 360
} ?: RecyclerView.LayoutParams(width, RecyclerView.LayoutParams.WRAP_CONTENT)
override fun onBindViewHolder(holder: CategoryDirectoryItemViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.isEmpty()) {
super.onBindViewHolder(holder, position, payloads)
} else {
holder.notifyItemSelectedChanged()
}
val padTop = if (position == 0) 16F.dip2px() else 24F.dip2px()
root.setPadding(16F.dip2px(), padTop, 16F.dip2px(), 0)
}
val entity = mList[position]
title.text = entity.name
title.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(mContext))
fun notifyItemSelectedChanged(parentId: String) {
val position = data.indexOfFirst { it.id == parentId }
if (position != -1) {
notifyItemChanged(position, "")
}
}
class CategoryDirectoryItemViewHolder(
private val listener: SearchCategoryPop.OnSearchCategoryListener,
val binding: CategoryDirectoryItemBinding
) :
ViewHolder(binding.root) {
private val childAdapter by lazy {
SubCategoryAdapter(listener)
}
fun onBind(position: Int, item: CategoryEntity) {
val context = binding.root.context
binding.title.text = item.name
if (binding.subCategoryRv.adapter == null) {
binding.subCategoryRv.layoutManager = object : GridLayoutManager(context, 4) {
override fun canScrollVertically(): Boolean {
return false
subCategoryRv.run {
if (adapter is SubCategoryAdapter) {
layoutManager = GridLayoutManager(mContext, 3)
adapter = entity.data?.let {
SubCategoryAdapter(
mContext,
mViewModel,
it,
position
)
}
}
binding.subCategoryRv.adapter = childAdapter
binding.subCategoryRv.addItemDecoration(
GridSpacingItemColorDecoration(
context,
8,
8,
com.gh.gamecenter.common.R.color.transparent
} else {
layoutManager = GridLayoutManager(mContext, 3)
adapter = entity.data?.let {
SubCategoryAdapter(
mContext,
mViewModel,
it,
position
)
}
addItemDecoration(
GridSpacingItemColorDecoration(
mContext,
6,
6,
com.gh.gamecenter.common.R.color.transparent
)
)
)
}
}
childAdapter.setData(position, item)
}
fun notifyItemSelectedChanged() {
childAdapter.notifyItemRangeChanged(0, childAdapter.itemCount, "")
}
}
class CategoryDirectoryItemViewHolder(val binding: CategoryDirectoryItemBinding) :
BaseRecyclerViewHolder<Any>(binding.root)
}

View File

@ -37,8 +37,8 @@ class CategoryV2Activity : DownloadToolbarActivity() {
companion object {
fun getIntent(context: Context, catalogId: String, catalogTitle: String, entrance: String): Intent {
val bundle = Bundle()
bundle.putString(EntranceConsts.KEY_PAGE_ID, catalogId)
bundle.putString(EntranceConsts.KEY_PAGE_NAME, catalogTitle)
bundle.putString(EntranceConsts.KEY_CATEGORY_ID, catalogId)
bundle.putString(EntranceConsts.KEY_CATEGORY_TITLE, catalogTitle)
bundle.putString(EntranceConsts.KEY_ENTRANCE, entrance)
return getTargetIntent(context, CategoryV2Activity::class.java, CategoryV2Fragment::class.java, bundle)
}

View File

@ -1,7 +1,6 @@
package com.gh.gamecenter.category2
import android.content.Context
import android.graphics.Typeface
import android.view.View
import android.view.ViewGroup
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
@ -14,13 +13,11 @@ import com.lightgame.adapter.BaseRecyclerAdapter
class CategoryV2Adapter(
context: Context,
private val mFragment: CategoryV2Fragment,
private val mViewModel: CategoryV2ViewModel,
private val mList: List<SidebarsEntity.SidebarEntity>
) : BaseRecyclerAdapter<CategoryV2Adapter.CategoryV2ItemViewHolder>(context) {
var selectedPosition = 0
private set
override fun getItemCount() = mList.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
@ -31,35 +28,25 @@ class CategoryV2Adapter(
val catalogEntity = mList[position]
catalogName.text = catalogEntity.name
recommendTag.goneIf(!catalogEntity.recommended)
if (position == selectedPosition) {
if (catalogEntity.name == mViewModel.selectedCategoryName) {
selectedTag.visibility = View.VISIBLE
catalogName.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(mContext))
catalogName.setTypeface(null, Typeface.BOLD)
catalogName.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(mContext))
root.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_surface.toColor(mContext))
} else {
selectedTag.visibility = View.GONE
catalogName.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(mContext))
catalogName.setTypeface(null, Typeface.NORMAL)
catalogName.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(mContext))
root.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_background.toColor(mContext))
}
root.setOnClickListener {
if (position != selectedPosition) {
if (catalogEntity.name != mViewModel.selectedCategoryName) {
mViewModel.selectedCategoryName = catalogEntity.name
mFragment.changeCategory(position)
mViewModel.logClickSide()
mViewModel.selectSidebarsPosition(position)
notifyDataSetChanged()
}
}
}
}
fun selectPosition(newPosition: Int) {
if (selectedPosition == newPosition) {
return
}
val oldSelection = selectedPosition
selectedPosition = newPosition
notifyItemChanged(oldSelection)
notifyItemChanged(newPosition)
}
class CategoryV2ItemViewHolder(val binding: CategoryV2ItemBinding) : BaseRecyclerViewHolder<Any>(binding.root)
}

View File

@ -1,14 +1,14 @@
package com.gh.gamecenter.category2
import android.content.res.ColorStateList
import android.graphics.Typeface
import android.os.Bundle
import android.view.Gravity
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.core.os.bundleOf
import androidx.core.widget.TextViewCompat
import androidx.fragment.app.viewModels
import androidx.core.view.GravityCompat
import androidx.drawerlayout.widget.DrawerLayout
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import com.gh.common.util.LogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.SearchActivity
@ -22,49 +22,35 @@ import com.gh.gamecenter.common.view.FixLinearLayoutManager
import com.gh.gamecenter.core.utils.PageSwitchDataHelper
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.databinding.FragmentCategoryBinding
import com.gh.gamecenter.entity.CategoryEntity
import com.gh.gamecenter.entity.SidebarsEntity
import com.gh.gamecenter.livedata.EventObserver
import com.gh.gamecenter.wrapper.SearchToolbarTabWrapperViewModel
class CategoryV2Fragment : LazyFragment() {
private var mBinding: FragmentCategoryBinding? = null
private val viewModel by viewModels<CategoryV2ViewModel>()
private var mViewModel: CategoryV2ViewModel? = null
private var mHomeViewModel: SearchToolbarTabWrapperViewModel? = null
private var mEntity: SidebarsEntity? = null
private var mSpecialCatalogFragment: SpecialCatalogFragment? = null
private var mCategoryV2ListFragment: CategoryV2ListFragment? = null
private var mLastPageDataMap: HashMap<String, String>? = null
private var pageId: String = ""
private var pageName: String = ""
private var mCategoryId: String = ""
private var mCategoryTitle: String = ""
private var mLastSelectedPosition = -1
private var searchCategoryPop: SearchCategoryPop? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
pageId = arguments?.getString(EntranceConsts.KEY_PAGE_ID) ?: ""
pageName = arguments?.getString(EntranceConsts.KEY_PAGE_NAME) ?: ""
mLastPageDataMap = PageSwitchDataHelper.popLastPageData()
savedInstanceState?.run {
mLastSelectedPosition = getInt(EntranceConsts.KEY_LAST_SELECTED_POSITION)
}
// 除了这里以外,下面还有一个判断是否为首页 tab 栏的赋值
var entrance = if (mEntrance.contains("首页")) "首页" else "板块"
val multiTabNavId = arguments?.getString(EntranceConsts.KEY_MULTI_TAB_NAV_ID, "") ?: ""
if (arguments?.getBoolean(EntranceConsts.KEY_IS_HOME) == true && multiTabNavId.isNotEmpty()) {
mHomeViewModel =
viewModelProviderFromParent(SearchToolbarTabWrapperViewModel.Factory(multiTabNavId, ""), multiTabNavId)
entrance = "首页Tab栏"
}
viewModel.init(entrance)
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putInt(EntranceConsts.KEY_LAST_SELECTED_POSITION, viewModel.selectedSidebarsPosition.value ?: 0)
mViewModel?.run {
outState.putInt(EntranceConsts.KEY_LAST_SELECTED_POSITION, selectedCategoryPosition)
}
super.onSaveInstanceState(outState)
}
@ -75,40 +61,100 @@ class CategoryV2Fragment : LazyFragment() {
}
override fun onFragmentFirstVisible() {
mCategoryId = arguments?.getString(EntranceConsts.KEY_CATEGORY_ID) ?: ""
mCategoryTitle = arguments?.getString(EntranceConsts.KEY_CATEGORY_TITLE) ?: ""
mLastPageDataMap = PageSwitchDataHelper.popLastPageData()
mViewModel = viewModelProviderFromParent(CategoryV2ViewModel.Factory(mCategoryId, mCategoryTitle), mCategoryId)
viewModel.logAppearance()
// 除了这里以外,下面还有一个判断是否为首页 tab 栏的赋值
mViewModel?.entrance = if (mEntrance.contains("首页")) "首页" else "板块"
val multiTabNavId = arguments?.getString(EntranceConsts.KEY_MULTI_TAB_NAV_ID, "") ?: ""
if (arguments?.getBoolean(EntranceConsts.KEY_IS_HOME) == true && multiTabNavId.isNotEmpty()) {
mHomeViewModel = viewModelProviderFromParent(SearchToolbarTabWrapperViewModel.Factory(multiTabNavId, ""), multiTabNavId)
mViewModel?.entrance = "首页Tab栏"
}
mViewModel?.logAppearance()
super.onFragmentFirstVisible()
viewModel.loadData(pageId, pageName)
}
override fun initRealView() {
super.initRealView()
initMenu(R.menu.menu_search)
setNavigationTitle(pageName)
setNavigationTitle(mCategoryTitle)
mBinding?.run {
tvMoreCategory.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()))
tvTagNumber.typeface =
Typeface.createFromAsset(requireContext().assets, Constants.DIN_FONT_PATH)
val width = resources.displayMetrics.widthPixels * 260 / 360
drawerLayout.setScrimColor(com.gh.gamecenter.common.R.color.black_alpha_30.toColor())
// 关闭手势滑动
drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED)
drawerLayout.addDrawerListener(object : DrawerLayout.DrawerListener {
override fun onDrawerStateChanged(newState: Int) {
}
override fun onDrawerSlide(drawerView: View, slideOffset: Float) {
}
override fun onDrawerClosed(drawerView: View) {
showGuide()
}
override fun onDrawerOpened(drawerView: View) {
}
})
directoryContainer.layoutParams.width = width
directoryRv.layoutParams.width = width
// 嵌入在首页时特殊处理
if (arguments?.getBoolean(EntranceConsts.KEY_IS_HOME) == true) {
root.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_surface.toColor(requireContext()))
root.setPadding(0, 8F.dip2px(), 0, 0)
directoryRv.isNestedScrollingEnabled = false
categoryRv.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_background.toColor(requireContext()))
}
}
viewModel.sidebarsLiveData.observe(viewLifecycleOwner) { sidebars ->
mViewModel?.directoriesLiveData?.observeNonNull(viewLifecycleOwner) {
initDirectoryView(it)
}
mViewModel?.selectedCountLiveData?.observeNonNull(viewLifecycleOwner) {
mBinding?.run {
if (it == 0) {
confirmTv.text = "确定"
} else {
mViewModel?.run {
if (selectedCategoryName != "全部") {
selectedCategoryName = "全部"
selectedCategoryPosition = if (mEntity?.hasSpecial == true) 1 else 0
categoryRv.adapter?.notifyDataSetChanged()
mCategoryV2ListFragment?.updateSubCategoryId("all")
}
}
confirmTv.text = "确定(已选${it})"
}
}
}
mViewModel?.sidebarsLiveData?.observe(viewLifecycleOwner, Observer {
mBinding?.run {
reuseLoading.root.visibility = View.GONE
if (sidebars != null) {
if (it != null) {
reuseNoConnection.root.visibility = View.GONE
categoryContainer.visibility = View.VISIBLE
reuseNoneData.root.visibility = View.GONE
mEntity = sidebars
(sidebars.sidebars as ArrayList).run {
mEntity = it
(mEntity!!.sidebars as ArrayList).run {
val allEntity = SidebarsEntity.SidebarEntity(name = "全部", categoryId = "all")
add(0, allEntity)
if (sidebars.hasSpecial) {
if (mEntity!!.hasSpecial) {
val specialEntity = SidebarsEntity.SidebarEntity(name = "精选")
add(1, specialEntity)
add(0, specialEntity)
add(1, allEntity)
} else {
add(0, allEntity)
}
}
initView()
@ -119,70 +165,12 @@ class CategoryV2Fragment : LazyFragment() {
reuseNoConnection.root.setOnClickListener {
reuseNoConnection.root.visibility = View.GONE
reuseLoading.root.visibility = View.VISIBLE
viewModel.loadData(pageId, pageName)
mViewModel?.getSidebars()
mViewModel?.getCategoryDirectories()
}
}
}
}
viewModel.directoriesLiveData.observe(viewLifecycleOwner) {
searchCategoryPop?.setData(it)
}
viewModel.selectedSubCategories.observe(viewLifecycleOwner) { selectedList ->
searchCategoryPop?.updateCategorySelected(selectedList)
updateMoreCategory(selectedList.size)
}
viewModel.selectedSidebarsPosition.observe(viewLifecycleOwner) {
mLastSelectedPosition = it
onSelectedPositionChanged(it)
val adapter = mBinding?.categoryRv?.adapter
if (adapter is CategoryV2Adapter) {
adapter.selectPosition(it)
}
}
viewModel.notifySubCategorySelected.observe(
viewLifecycleOwner,
EventObserver {
searchCategoryPop?.notifyItemSelectedChanged(it)
})
}
private fun createSearchPop(isAutoRequestFocus: Boolean): SearchCategoryPop {
val height = mBinding!!.root.height
return SearchCategoryPop.newInstance(requireContext(), height, isAutoRequestFocus, pageId, pageName).apply {
val data = viewModel.directoriesLiveData.value ?: listOf()
setData(data)
val selectedList = viewModel.selectedSubCategories.value
updateCategorySelected(selectedList)
setOnSearchCategoryListener(object : SearchCategoryPop.OnSearchCategoryListener {
override fun isEnableSelected(): Boolean {
val size = viewModel.selectedSubCategories.value?.size ?: 0
return size < 5
}
override fun onItemSelected(selected: CategoryV2ViewModel.SelectedTags) {
viewModel.addSubCategorySelected(selected)
}
override fun onItemRemoved(parentId: String, subCategoryId: String) {
viewModel.removeSubCategorySelected(parentId, subCategoryId, "全部游戏")
}
override fun onResetSelected() {
viewModel.clearSelectedTag()
viewModel.updateGameFiltered()
}
override fun onSubmit() {
viewModel.logClickDetermine()
}
})
}
})
}
fun removeGuide() {
@ -193,7 +181,15 @@ class CategoryV2Fragment : LazyFragment() {
}
private fun showGuide() {
if (!isAdded) return
mBinding?.run {
val isShow = SPUtils.getBoolean(Constants.SP_SHOW_CATEGORY_GUIDE)
if (isShow) return
guideContainer.layoutParams = (guideContainer.layoutParams as ViewGroup.MarginLayoutParams).apply {
val screenWidth = resources.displayMetrics.widthPixels
leftMargin = screenWidth * 66F.dip2px() / 360F.dip2px()
}
guideContainer.visibility = View.VISIBLE
postDelayedRunnable({
@ -208,7 +204,7 @@ class CategoryV2Fragment : LazyFragment() {
override fun onMenuItemClick(menuItem: MenuItem?) {
menuItem?.run {
if (itemId == R.id.menu_search) {
LogUtils.uploadSearchGame("access_to_search", pageName, "", "")
LogUtils.uploadSearchGame("access_to_search", mCategoryTitle, "", "")
val intent = SearchActivity.getIntent(
requireContext(),
false,
@ -221,64 +217,63 @@ class CategoryV2Fragment : LazyFragment() {
}
}
private fun initView() {
if (mEntity?.sidebars.isNullOrEmpty()) return
initSelectedCategory()
initCategoryRv()
private fun initDirectoryView(list: List<CategoryEntity>) {
mBinding?.run {
vSearchCategory.setOnClickListener {
SensorsBridge.logClassificationSearch(pageId, pageName)
removeGuide()
showSearchPop(true)
}
vMoreCategory.setOnClickListener {
removeGuide()
showSearchPop(false)
}
}
val isShow = SPUtils.getBoolean(Constants.SP_SHOW_CATEGORY_GUIDE)
if (!isShow) {
postDelayedRunnable({
showSearchPop(false)
searchCategoryPop?.setOnDismissListener {
showGuide()
mViewModel?.run {
if (directoryRv.adapter != null) {
(directoryRv.adapter as? CategoryDirectoryAdapter)?.setListData(list)
} else {
directoryRv.layoutManager = FixLinearLayoutManager(requireContext())
directoryRv.adapter = CategoryDirectoryAdapter(
requireContext(),
this,
list
)
}
}, 200)
}
}
resetTv.setOnClickListener {
mViewModel?.logClickReset("全部类别")
confirmTv.text = "确定"
mViewModel?.resetDirectoryList()
mCategoryV2ListFragment?.changeCategoryTab()
}
confirmTv.setOnClickListener {
mViewModel?.logClickDetermine()
drawerLayout.closeDrawer(GravityCompat.START)
}
}
}
private fun showSearchPop(isAutoRequestFocus: Boolean) {
mBinding?.run {
val location = IntArray(2)
vSearchCategory.getLocationOnScreen(location)
val popTop = location[1] - 8F.dip2px()
searchCategoryPop = createSearchPop(isAutoRequestFocus)
searchCategoryPop?.showAtLocation(vSearchCategory, Gravity.TOP, 0, popTop)
}
private fun initView() {
if (mEntity?.sidebars?.isNullOrEmpty() == true || mViewModel == null) return
initSelectedCategory()
initCategoryRv()
initContentFragment()
}
private fun initSelectedCategory() {
if (mLastSelectedPosition != -1) {
viewModel.selectSidebarsPosition(mLastSelectedPosition)
} else {
// 默认选中第 0 个 位置
viewModel.selectSidebarsPosition(0)
mEntity?.run {
mViewModel?.run {
if (mLastSelectedPosition != -1) {
selectedCategoryPosition = mLastSelectedPosition
selectedCategoryName = sidebars[mLastSelectedPosition].name
} else {
selectedCategoryPosition = 0
selectedCategoryName = sidebars[0].name
}
}
}
}
private fun initCategoryRv() {
mEntity?.run {
viewModel.run {
mViewModel?.run {
mBinding?.categoryRv?.layoutManager = FixLinearLayoutManager(requireContext())
mBinding?.categoryRv?.adapter = CategoryV2Adapter(
requireContext(),
this@CategoryV2Fragment,
this,
sidebars
)
@ -286,93 +281,226 @@ class CategoryV2Fragment : LazyFragment() {
}
}
private fun onSelectedPositionChanged(position: Int) {
mEntity?.run {
viewModel.run {
clearSelectedTag()
childFragmentManager.fragments.find { it.isAdded }
val targetFragment =
if (hasSpecial && position == 1) {
val fragment = childFragmentManager.findFragmentByTag(SpecialCatalogFragment::class.java.name)
?: SpecialCatalogFragment()
fragment.arguments = bundleOf(
EntranceConsts.KEY_IS_CATEGORY_V2 to true,
EntranceConsts.KEY_CATALOG_ID to pageId,
EntranceConsts.KEY_CATALOG_TITLE to pageName,
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST to arguments?.getParcelableArrayList<ExposureSource>(
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST
),
EntranceConsts.KEY_LAST_PAGE_DATA to mLastPageDataMap
)
fragment
private fun initContentFragment() {
mEntity?.apply {
mViewModel?.apply {
if (hasSpecial && selectedCategoryPosition == 0) {
initSpecialCatalogFragment()
} else {
initCategoryV2ListFragment()
} else {
val fragment = (childFragmentManager.findFragmentByTag(CategoryV2ListFragment::class.java.name)
?: CategoryV2ListFragment())
fragment.arguments = bundleOf(
EntranceConsts.KEY_PAGE_ID to id,
EntranceConsts.KEY_PAGE_NAME to pageName,
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST to arguments?.getParcelableArrayList<ExposureSource>(
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST
),
EntranceConsts.KEY_LAST_PAGE_DATA to mLastPageDataMap
)
fragment
// 第一次点"全部"tab展开全部类别选择框
// 加延迟是为了防止卡顿
if (SPUtils.getBoolean(Constants.SP_FIRST_ENTER_CATEGORY_V2, true)) {
SPUtils.setBoolean(Constants.SP_FIRST_ENTER_CATEGORY_V2, false)
mBinding?.drawerLayout?.postDelayed({
tryCatchInRelease { openDrawer() }
}, 500L)
}
}
}
}
}
private fun initSpecialCatalogFragment() {
mEntity?.run {
mSpecialCatalogFragment = childFragmentManager
.findFragmentByTag(SpecialCatalogFragment::class.java.name)
as? SpecialCatalogFragment ?: SpecialCatalogFragment()
mSpecialCatalogFragment?.arguments = bundleOf(
EntranceConsts.KEY_IS_CATEGORY_V2 to true,
EntranceConsts.KEY_CATALOG_ID to id,
EntranceConsts.KEY_CATALOG_TITLE to mCategoryTitle,
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST to arguments?.getParcelableArrayList<ExposureSource>(
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST
),
EntranceConsts.KEY_LAST_PAGE_DATA to mLastPageDataMap
)
childFragmentManager
.beginTransaction()
.replace(
R.id.gamesContainer,
mSpecialCatalogFragment!!,
SpecialCatalogFragment::class.java.name
)
.commitAllowingStateLoss()
}
}
private fun initCategoryV2ListFragment() {
mEntity?.run {
mViewModel?.run {
mCategoryV2ListFragment = childFragmentManager
.findFragmentByTag(CategoryV2ListFragment::class.java.name)
as? CategoryV2ListFragment ?: CategoryV2ListFragment()
mCategoryV2ListFragment?.arguments = bundleOf(
EntranceConsts.KEY_CATEGORY_ID to id,
EntranceConsts.KEY_SUB_CATEGORY_ID to sidebars[selectedCategoryPosition].categoryId,
EntranceConsts.KEY_CATEGORY_TITLE to mCategoryTitle,
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST to arguments?.getParcelableArrayList<ExposureSource>(
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST
),
EntranceConsts.KEY_LAST_PAGE_DATA to mLastPageDataMap
)
childFragmentManager
.beginTransaction()
.replace(R.id.gamesContainer, targetFragment, targetFragment::class.java.name)
.replace(
R.id.gamesContainer,
mCategoryV2ListFragment!!,
CategoryV2ListFragment::class.java.name
)
.commitAllowingStateLoss()
}
}
}
private fun updateMoreCategory(size: Int) {
mBinding?.run {
if (size > 0) {
vMoreCategory.setBackgroundResource(R.drawable.bg_more_category_filtered)
TextViewCompat.setCompoundDrawableTintList(
tvMoreCategory,
ColorStateList.valueOf(com.gh.gamecenter.common.R.color.text_theme.toColor(requireContext()))
)
tvMoreCategory.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(requireContext()))
tvTagNumber.goneIf(false) {
tvTagNumber.text = "$size"
fun changeCategory(position: Int) {
mEntity?.run {
mViewModel?.run {
resetDirectoryList()
if (hasSpecial) {
if (selectedCategoryPosition == 0) {
mCategoryV2ListFragment = childFragmentManager
.findFragmentByTag(CategoryV2ListFragment::class.java.name)
as? CategoryV2ListFragment ?: CategoryV2ListFragment()
mCategoryV2ListFragment?.arguments = bundleOf(
EntranceConsts.KEY_CATEGORY_ID to id,
EntranceConsts.KEY_SUB_CATEGORY_ID to sidebars[position].categoryId,
EntranceConsts.KEY_CATALOG_TITLE to mCategoryTitle,
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST to arguments?.getParcelableArrayList<ExposureSource>(
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST
),
EntranceConsts.KEY_LAST_PAGE_DATA to mLastPageDataMap
)
childFragmentManager
.beginTransaction()
.replace(
R.id.gamesContainer,
mCategoryV2ListFragment!!,
CategoryV2ListFragment::class.java.name
)
.commitAllowingStateLoss()
} else {
if (position == 0) {
removeGuide()
mSpecialCatalogFragment = childFragmentManager
.findFragmentByTag(SpecialCatalogFragment::class.java.name)
as? SpecialCatalogFragment ?: SpecialCatalogFragment()
mSpecialCatalogFragment?.arguments = bundleOf(
EntranceConsts.KEY_IS_CATEGORY_V2 to true,
EntranceConsts.KEY_CATALOG_ID to id,
EntranceConsts.KEY_CATALOG_TITLE to mCategoryTitle,
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST to arguments?.getParcelableArrayList<ExposureSource>(
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST
),
EntranceConsts.KEY_LAST_PAGE_DATA to mLastPageDataMap
)
childFragmentManager
.beginTransaction()
.replace(
R.id.gamesContainer,
mSpecialCatalogFragment!!,
SpecialCatalogFragment::class.java.name
)
.commitAllowingStateLoss()
} else {
if (mCategoryV2ListFragment?.isStateSaved == false) {
mCategoryV2ListFragment?.arguments = bundleOf(
EntranceConsts.KEY_CATEGORY_ID to id,
EntranceConsts.KEY_SUB_CATEGORY_ID to sidebars[position].categoryId,
EntranceConsts.KEY_CATALOG_TITLE to mCategoryTitle,
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST to arguments?.getParcelableArrayList<ExposureSource>(
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST
),
EntranceConsts.KEY_LAST_PAGE_DATA to mLastPageDataMap
)
}
mCategoryV2ListFragment?.changeCategoryTab(sidebars[position].categoryId)
}
}
// 第一次点"全部"tab展开全部类别选择框
// 加延迟是为了防止卡顿
if (position == 1 && SPUtils.getBoolean(Constants.SP_FIRST_ENTER_CATEGORY_V2, true)) {
SPUtils.setBoolean(Constants.SP_FIRST_ENTER_CATEGORY_V2, false)
mBinding?.drawerLayout?.postDelayed({
tryCatchInRelease { openDrawer() }
}, 200L)
}
} else {
if (mCategoryV2ListFragment?.isStateSaved == false) {
mCategoryV2ListFragment?.arguments = bundleOf(
EntranceConsts.KEY_CATEGORY_ID to id,
EntranceConsts.KEY_SUB_CATEGORY_ID to sidebars[position].categoryId,
EntranceConsts.KEY_CATALOG_TITLE to mCategoryTitle,
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST to arguments?.getParcelableArrayList<ExposureSource>(
EntranceConsts.KEY_EXPOSURE_SOURCE_LIST
),
EntranceConsts.KEY_LAST_PAGE_DATA to mLastPageDataMap
)
}
mCategoryV2ListFragment?.changeCategoryTab(sidebars[position].categoryId)
}
} else {
vMoreCategory.setBackgroundResource(R.drawable.bg_more_category_default)
TextViewCompat.setCompoundDrawableTintList(
tvMoreCategory,
ColorStateList.valueOf(com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()))
)
tvMoreCategory.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()))
tvTagNumber.goneIf(true)
selectedCategoryPosition = position
}
}
}
fun openDirectoryLayout() {
mBinding?.run {
var i = 0
mViewModel?.run {
mEntity?.run {
val sidebar = sidebars[selectedCategoryPosition]
if (sidebar.name != "全部") {
directories.forEachIndexed { index, entity ->
if (sidebar.type == "level_one") {
if (sidebar.categoryId == entity.id) {
i = index
return@run
}
} else {
if (sidebar.parentId == entity.id) {
i = index
return@run
}
}
}
} else if (sidebar.name == "全部" && selectedCategoryList.isNotEmpty()) {
i = selectedCategoryList[0].primaryIndex
}
}
}
openDrawer()
(directoryRv.layoutManager as? LinearLayoutManager)?.scrollToPositionWithOffset(i, 0)
}
}
private fun openDrawer() {
mBinding?.drawerLayout?.openDrawer(GravityCompat.START)
mHomeViewModel?.let {
mBinding?.directoryContainer?.setPadding(
0,
0,
0,
requireContext().resources.getDimension(com.gh.gamecenter.common.R.dimen.main_bottom_tab_height).toInt() - it.appBarOffset
)
}
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
getItemMenu(R.id.menu_search)?.setIcon(R.drawable.ic_column_search)
mBinding?.run {
categoryRv.adapter?.run {
categoryRv.recycledViewPool.clear()
notifyItemRangeChanged(0, itemCount, "")
}
val selectedTagsSize = viewModel.selectedSubCategories.value?.size ?: 0
updateMoreCategory(selectedTagsSize)
context?.let {
ivSearchCategory.imageTintList =
ColorStateList.valueOf(com.gh.gamecenter.common.R.color.text_instance.toColor(it))
}
mBinding?.categoryRv?.adapter?.run {
mBinding?.categoryRv?.recycledViewPool?.clear()
notifyItemRangeChanged(0, itemCount)
}
mBinding?.directoryRv?.adapter?.run {
mBinding?.directoryRv?.recycledViewPool?.clear()
notifyItemRangeChanged(0, itemCount)
}
}
companion object {
private const val SPECIAL_CATEGORY_POSITION = 1
}
}

View File

@ -76,15 +76,8 @@ class CategoryV2ListAdapter(
ItemViewType.GAME_NORMAL -> {
CategoryGameItemViewHolder(parent.toBinding())
}
else -> {
FooterViewHolder(
mLayoutInflater.inflate(
com.gh.gamecenter.common.R.layout.refresh_footerview,
parent,
false
)
)
FooterViewHolder(mLayoutInflater.inflate(com.gh.gamecenter.common.R.layout.refresh_footerview, parent, false))
}
}
}
@ -101,12 +94,20 @@ class CategoryV2ListAdapter(
holder.bindGameItem(gameEntity)
holder.initServerType(gameEntity)
val categoryTitle = mCategoryViewModel.pageName
val selectedCategoryName = mCategoryViewModel.selectedSidebarsName
val selectedSubCatalogName =
mCategoryViewModel.selectedSubCategories.value?.joinToString("-") { it.category.name ?: "" }
val sortType = mViewModel.gameFiltered.sortType.value
val sortSize = mViewModel.gameFiltered.size.text
val categoryTitle = mCategoryViewModel.categoryTitle
val selectedCategoryName = mCategoryViewModel.selectedCategoryName
val builder = StringBuilder()
mCategoryViewModel.selectedCategoryList.run {
forEachIndexed { index, entity ->
builder.append(entity.name)
if (index != size - 1) {
builder.append("_")
}
}
}
val selectedSubCatalogName = builder.toString()
val sortType = mViewModel.sortType.value
val sortSize = mViewModel.sortSize.text
val exposureSources = ArrayList<ExposureSource>()
if (!mViewModel.exposureSourceList.isNullOrEmpty()) {
@ -171,13 +172,8 @@ class CategoryV2ListAdapter(
) {
val trackEvent = JSONObject()
try {
trackEvent.put("navigation_bar_name", mCategoryViewModel.selectedSidebarsName)
trackEvent.put(
"game_tag",
(mCategoryViewModel.selectedSubCategories.value ?: listOf())
.map {
it.category.name
})
trackEvent.put("navigation_bar_name", mCategoryViewModel.selectedCategoryName)
trackEvent.put("game_tag", mCategoryViewModel.selectedCategoryList.map { it.name })
trackEvent.put("game_status", gameEntity.category)
trackEvent.put(
"inclusion_size",
@ -270,24 +266,18 @@ class CategoryV2ListAdapter(
binding.gameKaifuType.visibility = View.GONE
binding.gameKaifuType.text = ""
}
serverLabel != null -> {
binding.gameKaifuType.visibility = View.VISIBLE
binding.gameKaifuType.text = serverLabel.value
if (gameEntity.isUseDefaultServerStyle()) {
binding.gameKaifuType.background =
com.gh.gamecenter.feature.R.drawable.server_label_default_bg.toDrawable(binding.root.context)
binding.gameKaifuType.setTextColor(
com.gh.gamecenter.common.R.color.text_secondary.toColor(
binding.root.context
)
)
binding.gameKaifuType.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(binding.root.context))
} else {
binding.gameKaifuType.background = DrawableView.getServerDrawable(serverLabel.color)
binding.gameKaifuType.setTextColor(com.gh.gamecenter.common.R.color.white.toColor(binding.root.context))
}
}
else -> binding.gameKaifuType.visibility = View.GONE
}

View File

@ -2,10 +2,9 @@ package com.gh.gamecenter.category2
import android.os.Bundle
import android.view.View
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import android.view.ViewGroup
import com.ethanhua.skeleton.Skeleton
import com.gh.gamecenter.common.constant.Constants
import com.gh.common.exposure.ExposureListener
import com.gh.common.util.DialogUtils
import com.gh.common.view.CategoryFilterView
@ -14,17 +13,16 @@ import com.gh.common.xapk.XapkUnzipStatus
import com.gh.download.DownloadManager
import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.ListFragment
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.observeNonNull
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.viewModelProvider
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.FragmentCategoryListBinding
import com.gh.gamecenter.databinding.LayoutSelectedCategoryBinding
import com.gh.gamecenter.entity.CategoryEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.feature.entity.GameEntity
import com.google.android.flexbox.FlexboxLayout
import com.lightgame.download.DataWatcher
import com.lightgame.download.DownloadEntity
import org.greenrobot.eventbus.Subscribe
@ -32,18 +30,13 @@ import org.greenrobot.eventbus.ThreadMode
class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>() {
private var pageId: String = ""
private var pageName: String = ""
private val parentViewModel by viewModels<CategoryV2ViewModel>(
ownerProducer = { parentFragment ?: this }
)
override fun isAutomaticLoad(): Boolean {
return false
}
private var mCategoryId: String = ""
private var mSubCategoryId: String = ""
private var mCategoryTitle: String = ""
private var mAdapter: CategoryV2ListAdapter? = null
private var mSelectedViewList = ArrayList<View>()
private var mBinding: FragmentCategoryListBinding? = null
private var mCategoryViewModel: CategoryV2ViewModel? = null
private var mLastPageDataMap: HashMap<String, String>? = null
private val mDataWatcher = object : DataWatcher() {
override fun onDataChanged(downloadEntity: DownloadEntity) {
@ -59,12 +52,6 @@ class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>
}
}
private val selectedTagsAdapter by lazy {
SelectedTagsAdapter {
parentViewModel.removeSubCategorySelected(it.parentId, it.category.id, "游戏列表")
}
}
override fun getLayoutId() = 0
override fun getInflatedLayout() =
@ -73,7 +60,8 @@ class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>
override fun provideListViewModel() =
viewModelProvider<CategoryV2ListViewModel>(
CategoryV2ListViewModel.Factory(
pageId,
mCategoryId,
mSubCategoryId,
arguments?.getParcelableArrayList(EntranceConsts.KEY_EXPOSURE_SOURCE_LIST)
)
)
@ -82,7 +70,12 @@ class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>
?: CategoryV2ListAdapter(
requireContext(),
mListViewModel ?: provideListViewModel(),
parentViewModel,
mCategoryViewModel ?: viewModelProviderFromParent(
CategoryV2ViewModel.Factory(
mCategoryId,
mCategoryTitle
), mCategoryId
),
mEntrance,
mLastPageDataMap
).apply { mAdapter = this }
@ -90,10 +83,12 @@ class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>
override fun getItemDecoration() = null
override fun onCreate(savedInstanceState: Bundle?) {
pageId = arguments?.getString(EntranceConsts.KEY_PAGE_ID) ?: ""
pageName = arguments?.getString(EntranceConsts.KEY_PAGE_NAME) ?: ""
mCategoryId = arguments?.getString(EntranceConsts.KEY_CATEGORY_ID) ?: ""
mSubCategoryId = arguments?.getString(EntranceConsts.KEY_SUB_CATEGORY_ID) ?: ""
mCategoryTitle = arguments?.getString(EntranceConsts.KEY_CATEGORY_TITLE) ?: ""
mLastPageDataMap = arguments?.getSerializable(EntranceConsts.KEY_LAST_PAGE_DATA) as? HashMap<String, String>
mCategoryViewModel =
viewModelProviderFromParent(CategoryV2ViewModel.Factory(mCategoryId, mCategoryTitle), mCategoryId)
mEntrance = arguments?.getString(EntranceConsts.KEY_ENTRANCE) ?: Constants.ENTRANCE_UNKNOWN
super.onCreate(savedInstanceState)
@ -109,8 +104,19 @@ class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>
initFilterView()
mListViewModel?.refresh?.observeNonNull(viewLifecycleOwner) {
onRefresh()
mListViewModel?.refresh?.observeNonNull(viewLifecycleOwner) { onRefresh() }
mCategoryViewModel?.run {
categoryPositionLiveData.observeNonNull(viewLifecycleOwner) {
directories[it.first].data?.get(it.second)?.run {
mBinding?.selectedCategoryContainer?.visibility = View.VISIBLE
if (selected) {
addCategory(this)
} else {
removeCategory(this)
}
}
}
}
mListRv.addOnScrollListener(ExposureListener(this, provideListAdapter()))
@ -126,40 +132,19 @@ class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>
.show()
mBinding?.reuseNoneData?.reuseResetLoadTv?.setOnClickListener {
// 重试时,
// 清空所有筛选条件
with(parentViewModel) {
mCategoryViewModel?.run {
logClickReset("游戏列表")
clearSelectedTag()
// 移除大小限制
mBinding?.filterContainer?.resetSortSize()
val size = SubjectSettingEntity.Size()
updateGameFiltered(size)
}
}
with(parentViewModel) {
gameFiltered.observe(viewLifecycleOwner) {
mListViewModel.updateSortConfig(it)
}
selectedSubCategories.observe(viewLifecycleOwner) {
updateSelectedTags(it)
resetDirectoryList()
}
resetSortSize()
changeCategoryTab()
// openDirectoryLayout()
}
}
private fun updateSelectedTags(selectedTags: List<CategoryV2ViewModel.SelectedTags>) {
mBinding?.run {
flTagsContainer.goneIf(selectedTags.isEmpty()) {
if (rvTags.adapter == null) {
rvTags.layoutManager = LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL, false)
rvTags.adapter = selectedTagsAdapter
}
selectedTagsAdapter.submitList(selectedTags)
}
}
private fun resetSortSize() {
mBinding?.filterContainer?.resetSortSize()
mListViewModel?.sortSize = SubjectSettingEntity.Size(min = -1, max = -1, text = "全部大小")
}
@ -169,19 +154,22 @@ class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>
setOnConfigSetupListener(object : CategoryFilterView.OnCategoryFilterSetupListener {
override fun onSetupSortSize(sortSize: SubjectSettingEntity.Size) {
parentViewModel.updateGameFiltered(size = sortSize)
mListViewModel?.updateSortConfig(sortSize = sortSize)
}
override fun onSetupSortType(sortType: CategoryFilterView.SortType) {
parentViewModel.updateGameFiltered(sortType = sortType)
mListViewModel?.updateSortConfig(sortType = sortType)
}
override fun getPopHeight(): Int {
return (mBinding?.root?.height ?: 0) - (mBinding?.flTagsContainer?.height ?: 0)
override fun onSetupSortCategory() {
openDirectoryLayout()
}
})
setOnFilterClickListener(object : CategoryFilterView.OnFilterClickListener {
override fun onCategoryClick() {
removeGuide()
}
override fun onTypeClick() {
removeGuide()
@ -195,6 +183,101 @@ class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>
}
}
fun updateSubCategoryId(id: String) {
mSubCategoryId = id
}
fun changeCategoryTab(categoryId: String? = null) {
mSelectedViewList.clear()
mBinding?.selectedCategoryContainer?.run {
removeAllViews()
visibility = View.GONE
}
if (categoryId != null) mSubCategoryId = categoryId
mListViewModel?.updateSortConfig(categoryIds = mSubCategoryId)
}
private fun addCategory(entity: CategoryEntity) {
addCategoryView(entity)
mCategoryViewModel?.selectedCategoryList?.add(entity)
updateCategoryGame()
}
private fun removeCategory(entity: CategoryEntity) {
mCategoryViewModel?.selectedCategoryList?.run {
if (isEmpty()) return
removeCategoryView(entity.name ?: "")
remove(entity)
if (size == 0) {
mBinding?.selectedCategoryContainer?.visibility = View.GONE
mListViewModel?.updateSortConfig(categoryIds = mSubCategoryId)
} else {
updateCategoryGame()
}
}
}
private fun addCategoryView(entity: CategoryEntity) {
val binding = LayoutSelectedCategoryBinding.inflate(layoutInflater).apply {
val params =
FlexboxLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT)
params.setMargins(0, 8F.dip2px(), 8F.dip2px(), 0)
params.height = 24F.dip2px()
root.layoutParams = params
name.text = entity.name
root.setOnClickListener {
removeCategoryAndNotify(entity)
}
}
mSelectedViewList.add(binding.root)
mBinding?.selectedCategoryContainer?.addView(binding.root)
}
private fun removeCategoryView(categoryName: String) {
mCategoryViewModel?.selectedCategoryList?.run {
var i = 0
forEachIndexed { index, categoryEntity ->
if (categoryName == categoryEntity.name) {
i = index
}
}
if (i < mSelectedViewList.size) {
mBinding?.selectedCategoryContainer?.removeView(mSelectedViewList[i])
mSelectedViewList.removeAt(i)
}
}
}
private fun removeCategoryAndNotify(entity: CategoryEntity) {
removeCategory(entity)
entity.selected = false
mCategoryViewModel?.run {
if (selectedCount > 0) {
selectedCount--
postSelectedCount()
postCategoryDirectoryList()
logClickClassificationDelete(entity.primaryIndex, entity.name ?: "", "游戏列表")
}
}
}
private fun updateCategoryGame() {
mCategoryViewModel?.selectedCategoryList?.run {
val categoryIds = StringBuilder()
forEachIndexed { index, s ->
categoryIds.append(s.id)
if (index != size - 1) {
categoryIds.append("-")
}
}
mListViewModel?.updateSortConfig(categoryIds = categoryIds.toString())
}
}
private fun removeGuide() {
(parentFragment as? CategoryV2Fragment)?.removeGuide()
}
@ -245,6 +328,10 @@ class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>
}
}
fun openDirectoryLayout() {
(parentFragment as? CategoryV2Fragment)?.openDirectoryLayout()
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
mBinding?.filterContainer?.run {

View File

@ -4,13 +4,14 @@ import android.app.Application
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.gh.gamecenter.common.entity.ExposureEntity
import com.gh.common.exposure.ExposureUtils
import com.gh.gamecenter.core.utils.UrlFilterUtils
import com.gh.common.view.CategoryFilterView
import com.gh.gamecenter.common.baselist.ListViewModel
import com.gh.gamecenter.common.entity.ExposureEntity
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.core.utils.UrlFilterUtils
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.gh.gamecenter.retrofit.RetrofitManager
import com.halo.assistant.HaloApp
import io.reactivex.Observable
@ -19,13 +20,14 @@ import io.reactivex.Single
class CategoryV2ListViewModel(
application: Application,
val categoryId: String,
var categoryIds: String,
var exposureSourceList: List<ExposureSource>?
) : ListViewModel<GameEntity, GameEntity>(application) {
val refresh = MutableLiveData<Boolean>()
var gameFiltered = CategoryV2ViewModel.GameFiltered()
private set
var sortType = CategoryFilterView.SortType.RECOMMENDED
var sortSize = SubjectSettingEntity.Size()
override fun provideDataObservable(page: Int): Observable<List<GameEntity>>? = null
@ -49,36 +51,52 @@ class CategoryV2ListViewModel(
}
fun updateSortConfig(
gameFiltered: CategoryV2ViewModel.GameFiltered
sortSize: SubjectSettingEntity.Size? = null,
sortType: CategoryFilterView.SortType? = null,
categoryIds: String? = null
) {
this.gameFiltered = gameFiltered
refresh.postValue(true)
when {
sortSize != null && sortSize != this.sortSize -> {
this.sortSize = sortSize
refresh.postValue(true)
}
sortType != null && sortType != this.sortType -> {
this.sortType = sortType
refresh.postValue(true)
}
categoryIds != null && categoryIds != this.categoryIds -> {
this.categoryIds = categoryIds
refresh.postValue(true)
}
}
}
private fun getFilter(): String? {
return UrlFilterUtils.getFilterQuery(
"category_ids", gameFiltered.categoryIds.ifBlank { gameFiltered.sidebarCategoryId },
"min_size", gameFiltered.size.min.toString(),
"max_size", gameFiltered.size.max.toString()
"category_ids", categoryIds,
"min_size", sortSize.min.toString(),
"max_size", sortSize.max.toString()
)
}
private fun getSortType(): String? {
return when (gameFiltered.sortType) {
return when (sortType) {
CategoryFilterView.SortType.RECOMMENDED -> "download:-1"
CategoryFilterView.SortType.NEWEST -> "publish:-1"
CategoryFilterView.SortType.RATING -> "star:-1"
}
}
class Factory(val categoryId: String, val exposureSourceList: List<ExposureSource>?) :
class Factory(val categoryId: String, val categoryIds: String, val exposureSourceList: List<ExposureSource>?) :
ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return CategoryV2ListViewModel(
HaloApp.getInstance().application,
categoryId,
categoryIds,
exposureSourceList
) as T
}

View File

@ -1,214 +1,160 @@
package com.gh.gamecenter.category2
import androidx.lifecycle.LiveData
import android.annotation.SuppressLint
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.gh.common.util.LogUtils
import com.gh.common.view.CategoryFilterView
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.entity.CategoryEntity
import com.gh.gamecenter.entity.SidebarsEntity
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.gh.gamecenter.livedata.Event
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.retrofit.RetrofitManager
import io.reactivex.disposables.CompositeDisposable
import com.halo.assistant.HaloApp
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
class CategoryV2ViewModel : ViewModel() {
class CategoryV2ViewModel(
application: Application,
private val mCategoryId: String,
val categoryTitle: String
) : AndroidViewModel(application) {
private val compositeDisposable = CompositeDisposable()
private val api = RetrofitManager.getInstance().api
var sidebarsLiveData = MutableLiveData<SidebarsEntity?>()
var sidebarsLiveData = MutableLiveData<SidebarsEntity>()
var directories = ArrayList<CategoryEntity>()
var directoriesLiveData = MutableLiveData<List<CategoryEntity>>()
var selectedCount = 0
var selectedCountLiveData = MutableLiveData<Int>()
var categoryPositionLiveData = MutableLiveData<Pair<Int, Int>>()
var selectedCategoryName: String = ""
var selectedCategoryPosition: Int = 0
var selectedCategoryList = ArrayList<CategoryEntity>()
var entrance: String = ""
private var pageId = ""
var pageName = ""
private set
fun init(entrance: String) {
this.entrance = entrance
init {
getSidebars()
getCategoryDirectories()
}
fun loadData(pageId: String, pageName: String) {
this.pageId = pageId
this.pageName = pageName
val sidebarsObservable = api.getSidebars(pageId)
val directoriesObservable = api.getCategoryDirectories(pageId)
.onErrorReturnItem(listOf())
sidebarsObservable.zipWith(directoriesObservable) { t1, t2 ->
t1 to t2
}.compose(singleToMain())
.subscribe(object : BiResponse<Pair<SidebarsEntity, List<CategoryEntity>>>() {
override fun onSuccess(data: Pair<SidebarsEntity, List<CategoryEntity>>) {
val (sidebarsData, directories) = data
directoriesLiveData.value = directories
sidebarsLiveData.value = sidebarsData
@SuppressLint("CheckResult")
fun getSidebars() {
api.getSidebars(mCategoryId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BiResponse<SidebarsEntity>() {
override fun onSuccess(data: SidebarsEntity) {
sidebarsLiveData.postValue(data)
}
override fun onFailure(exception: Exception) {
super.onFailure(exception)
sidebarsLiveData.postValue(null)
}
}).let(compositeDisposable::add)
})
}
fun clearSelectedTag() {
logClickReset("全部类别")
val filteredList = _selectedSubCategories.value
if (!filteredList.isNullOrEmpty()) {
_selectedSubCategories.value = mutableListOf()
val directories = directoriesLiveData.value ?: return
directories.forEach {
it.data?.forEach { child ->
child.selected = false
@SuppressLint("CheckResult")
fun getCategoryDirectories() {
api.getCategoryDirectories(mCategoryId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BiResponse<List<CategoryEntity>>() {
override fun onSuccess(data: List<CategoryEntity>) {
directories = ArrayList(data)
postCategoryDirectoryList()
}
})
}
fun postSelectedCount() {
selectedCountLiveData.postValue(selectedCount)
}
fun postCategoryPosition(primaryIndex: Int, subIndex: Int) {
categoryPositionLiveData.postValue(Pair(primaryIndex, subIndex))
}
fun postCategoryDirectoryList() {
directoriesLiveData.postValue(directories)
}
fun resetDirectoryList() {
selectedCount = 0
selectedCategoryList.clear()
directories.forEach {
it.data?.forEach { entity ->
entity.selected = false
}
directoriesLiveData.value = directories
}
postSelectedCount()
postCategoryDirectoryList()
}
fun logAppearance() {
LogUtils.logCategoryV2AppearanceEvent(entrance, pageName)
LogUtils.logCategoryV2AppearanceEvent(entrance, categoryTitle)
}
fun logClickSide() {
val sidebars = sidebarsLiveData.value?.sidebars
val selectedPosition = selectedSidebarsPosition.value ?: 0
val selectedCategoryName = sidebars?.getOrNull(selectedPosition)?.name ?: ""
LogUtils.logCategoryV2ClickSideEvent(entrance, pageName, selectedCategoryName, selectedPosition)
LogUtils.logCategoryV2ClickSideEvent(entrance, categoryTitle, selectedCategoryName, selectedCategoryPosition)
}
private fun logClickClassification(selected: SelectedTags) {
fun logClickClassification(primaryIndex: Int, categoryName: String, position: Int) {
LogUtils.logCategoryV2ClickClassificationEvent(
entrance,
pageName,
selectedSidebarsName,
selected.parentName,
selected.category.name ?: "",
selected.parentPosition,
selected.position
categoryTitle,
selectedCategoryName,
directories[primaryIndex].name,
categoryName,
primaryIndex,
position
)
}
private fun logClickClassificationDelete(directoryName: String, categoryName: String, location: String) {
fun logClickClassificationDelete(primaryIndex: Int, categoryName: String, location: String) {
LogUtils.logCategoryV2ClickClassificationDeleteEvent(
entrance,
pageName,
directoryName,
categoryTitle,
directories[primaryIndex].name,
categoryName,
location
)
}
fun logClickDetermine() {
val categoryNames = selectedSubCategories.value?.joinToString("+") { it.category.name ?: "" }
LogUtils.logCategoryV2ClickDetermineEvent(entrance, pageName, categoryNames ?: "")
val categoryName = StringBuilder()
selectedCategoryList.forEachIndexed { index, s ->
categoryName.append(s.name)
if (index != selectedCategoryList.size - 1) {
categoryName.append("+")
}
}
LogUtils.logCategoryV2ClickDetermineEvent(entrance, categoryTitle, categoryName.toString())
}
fun logClickReset(location: String) {
val categoryName = selectedSubCategories.value?.joinToString("+") { it.category.name ?: "" }
LogUtils.logCategoryV2ClickResetEvent(entrance, pageName, categoryName ?: "", location)
}
private val _notifySubCategorySelected = MutableLiveData<Event<String>>()
val notifySubCategorySelected: LiveData<Event<String>> = _notifySubCategorySelected
private val _selectedSubCategories = MutableLiveData<List<SelectedTags>>()
val selectedSubCategories: LiveData<List<SelectedTags>> = _selectedSubCategories
fun addSubCategorySelected(selected: SelectedTags) {
val list = _selectedSubCategories.value
val newData = if (list == null) {
mutableListOf(selected)
} else {
list + selected
}
selected.category.selected = true
_selectedSubCategories.value = newData
// 当搜索条件发生变化时,侧边栏默认选中 “全部”
selectSidebarsPosition(0, false)
updateGameFiltered()
_notifySubCategorySelected.value = Event(selected.parentId)
logClickClassification(selected)
}
fun removeSubCategorySelected(parentId: String, categoryId: String, location: String) {
val list = _selectedSubCategories.value ?: return
val position = list.indexOfFirst {
it.parentId == parentId && categoryId == it.category.id
}
if (position != -1) {
val item = list[position]
item.category.selected = false
_selectedSubCategories.value = list - item
updateGameFiltered()
_notifySubCategorySelected.value = Event(parentId)
logClickClassificationDelete(item.parentName, item.category.name ?: "", location)
}
}
private val _selectedSidebarsPosition = MutableLiveData<Int>()
val selectedSidebarsPosition: LiveData<Int> = _selectedSidebarsPosition
val selectedSidebarsName: String
get() = sidebarsLiveData.value?.sidebars?.getOrNull(selectedSidebarsPosition.value ?: 0)?.name ?: ""
fun selectSidebarsPosition(position: Int, triggerSearch: Boolean = true) {
val oldPosition = _selectedSidebarsPosition.value ?: INVALID_POSITION
if (position != oldPosition) {
_selectedSidebarsPosition.value = position
// 如果是点击搜索而被动切换到 “全部” tab则这里不需要更新筛选条件
if (triggerSearch && position != 1) {
updateGameFiltered()
val categoryName = StringBuilder()
selectedCategoryList.forEachIndexed { index, s ->
categoryName.append(s.name)
if (index != selectedCategoryList.size - 1) {
categoryName.append("+")
}
}
LogUtils.logCategoryV2ClickResetEvent(entrance, categoryTitle, categoryName.toString(), location)
}
private val _gameFiltered = MutableLiveData<GameFiltered>()
val gameFiltered: LiveData<GameFiltered> = _gameFiltered
fun updateGameFiltered(
size: SubjectSettingEntity.Size? = null,
sortType: CategoryFilterView.SortType? = null
) {
val oldFiltered = _gameFiltered.value
val newSize = size ?: oldFiltered?.size ?: SubjectSettingEntity.Size()
val newSortType = sortType ?: oldFiltered?.sortType ?: CategoryFilterView.SortType.RECOMMENDED
val selectedSidebarPosition = selectedSidebarsPosition.value ?: 0
val categoryIds = selectedSubCategories.value?.joinToString("-") { it.category.id } ?: ""
val sidebarCategoryId =
sidebarsLiveData.value?.sidebars?.getOrNull(selectedSidebarPosition)?.categoryId ?: "all"
_gameFiltered.value = GameFiltered(newSize, newSortType, categoryIds, sidebarCategoryId)
class Factory(
private val categoryId: String,
private val categoryTitle: String
) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return CategoryV2ViewModel(
HaloApp.getInstance().application,
categoryId,
categoryTitle
) as T
}
}
override fun onCleared() {
super.onCleared()
compositeDisposable.clear()
}
companion object {
private const val INVALID_POSITION = -1
}
data class SelectedTags(
val parentId: String,
val parentName: String,
val parentPosition: Int,
val category: CategoryEntity,
val position: Int
)
data class GameFiltered(
val size: SubjectSettingEntity.Size = SubjectSettingEntity.Size(),
val sortType: CategoryFilterView.SortType = CategoryFilterView.SortType.RECOMMENDED,
val categoryIds: String = "",
val sidebarCategoryId: String = "全部"
)
}

View File

@ -1,292 +0,0 @@
package com.gh.gamecenter.category2
import android.content.Context
import android.graphics.Color
import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.blankj.utilcode.util.KeyboardUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.common.utils.toResString
import com.gh.gamecenter.common.view.BugFixedPopupWindow
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.databinding.PopSearchCategoryBinding
import com.gh.gamecenter.entity.CategoryEntity
import io.reactivex.Single
import io.reactivex.disposables.CompositeDisposable
import me.xdrop.fuzzywuzzy.FuzzySearch
class SearchCategoryPop(
height: Int,
private val isAutoRequestFocus: Boolean,
private val pageId: String,
private val pageName: String,
private val binding: PopSearchCategoryBinding
) : BugFixedPopupWindow(binding.root, ViewGroup.LayoutParams.MATCH_PARENT, height) {
private var listener: OnSearchCategoryListener? = null
private val handler = Handler(Looper.getMainLooper())
private var searchDataList: List<CategoryV2ViewModel.SelectedTags>? = null
private val compositeDisposable = CompositeDisposable()
private var isSearching = false
private val adapter by lazy {
CategoryDirectoryAdapter(object : OnSearchCategoryListener {
override fun isEnableSelected(): Boolean {
return listener?.isEnableSelected() ?: false
}
override fun onItemSelected(selected: CategoryV2ViewModel.SelectedTags) {
listener?.onItemSelected(selected)
}
override fun onItemRemoved(parentId: String, subCategoryId: String) {
listener?.onItemRemoved(parentId, subCategoryId)
}
override fun onResetSelected() {
listener?.onResetSelected()
}
override fun onSubmit() {
listener?.onSubmit()
}
})
}
private val resultAdapter by lazy {
SearchCategoryResultsAdapter {
SensorsBridge.logClassificationSearchReturnClick(
pageId,
pageName,
binding.searchView.searchKey,
it.category.name ?: ""
)
if (listener?.isEnableSelected() == true) {
clearSearchKey()
listener?.onItemSelected(it)
} else {
ToastUtils.toast(R.string.selected_category_tags_max_toast.toResString())
}
}
}
private val selectedTagAdapter by lazy {
SelectedTagsAdapter {
listener?.onItemRemoved(it.parentId, it.category.id)
}
}
init {
isOutsideTouchable = true
setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
isFocusable = true
inputMethodMode = INPUT_METHOD_NEEDED
softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING
initView()
}
private fun initView() {
binding.rvCategory.layoutManager = LinearLayoutManager(binding.root.context)
binding.rvCategory.adapter = adapter
binding.tvSelectedNumber.typeface =
Typeface.createFromAsset(binding.root.context.assets, Constants.DIN_FONT_PATH)
binding.root.setOnClickListener {
dismiss()
}
binding.clContent.setOnClickListener {
// 不需要具体实现,只是为了拦截 root 的点击事件
}
binding.tvReset.setOnClickListener {
listener?.onResetSelected()
}
binding.vSubmit.setOnClickListener {
dismiss()
listener?.onSubmit()
}
binding.searchView.addTextChangedListener {
handler.removeCallbacksAndMessages(null)
val key = it?.toString() ?: ""
if (key.isEmpty()) {
changeToSearching(false)
} else {
handler.postDelayed({
search(it?.toString() ?: "")
}, SEARCH_DELAY_DURATION)
}
}
if (!isAutoRequestFocus) {
binding.searchView.setEditTextOnFocusChangeListener { _, hasFocus ->
if (hasFocus) {
SensorsBridge.logClassificationSearch(pageId, pageName)
}
}
}
}
fun setData(data: List<CategoryEntity>) {
searchDataList = data
.asSequence()
.mapIndexed { index, parent ->
parent.data?.mapIndexed { childIndex, child ->
CategoryV2ViewModel.SelectedTags(parent.id, parent.name ?: "", index, child, childIndex)
} ?: listOf()
}
.flatten()
.toList()
adapter.setListData(data)
}
fun updateCategorySelected(selectedList: List<CategoryV2ViewModel.SelectedTags>?) {
val size = selectedList?.size ?: 0
binding.tvSelectedNumber.goneIf(size == 0) {
binding.tvSelectedNumber.text = "$size"
}
if (binding.rvSelected.adapter == null) {
binding.rvSelected.layoutManager =
LinearLayoutManager(binding.root.context, RecyclerView.HORIZONTAL, false)
binding.rvSelected.adapter = selectedTagAdapter
}
selectedTagAdapter.submitList(selectedList)
binding.rvSelected.goneIf(selectedList.isNullOrEmpty())
if (isSearching) {
search(binding.searchView.searchKey)
}
}
private fun search(key: String) {
Single.create {
val data = searchDataList?.filterNot { item -> item.category.selected } ?: emptyList()
val resultList = FuzzySearch.extractSorted(key, data, { item -> item.category.name ?: "" }, 1)
.map { item -> item.referent }
it.onSuccess(resultList)
}.compose(singleToMain())
.subscribe(object : BiResponse<List<CategoryV2ViewModel.SelectedTags>>() {
override fun onSuccess(data: List<CategoryV2ViewModel.SelectedTags>) {
val hasResult = data.isNotEmpty()
SensorsBridge.logClassificationSearchReturn(pageId, pageName, key, hasResult)
changeToSearching(true, key, data)
}
override fun onFailure(exception: Exception) {
SensorsBridge.logClassificationSearchReturn(pageId, pageName, key, false)
changeToSearching(true, key)
}
}).let(compositeDisposable::add)
}
private fun clearSearchKey() {
binding.searchView.clear()
}
private fun changeToSearching(
isSearching: Boolean,
key: String = "",
results: List<CategoryV2ViewModel.SelectedTags>? = null
) {
this.isSearching = isSearching
binding.rvCategory.goneIf(isSearching)
binding.rvResults.goneIf(results.isNullOrEmpty()) {
if (binding.rvResults.adapter == null) {
binding.rvResults.layoutManager = LinearLayoutManager(binding.rvResults.context)
binding.rvResults.adapter = resultAdapter
}
}
resultAdapter.setData(results ?: listOf(), key)
binding.reuseNoConnection.root.goneIf(!isSearching || !results.isNullOrEmpty()) {
binding.reuseNoConnection.reuseNoneDataTv.text = R.string.no_relevant_content_found.toResString()
binding.reuseNoConnection.reuseNoneDataDescTv.goneIf(false)
binding.reuseNoConnection.reuseNoneDataDescTv.text = R.string.try_a_different_search_term.toResString()
}
}
fun setOnSearchCategoryListener(listener: OnSearchCategoryListener) {
this.listener = listener
}
fun notifyItemSelectedChanged(parentId: String) {
adapter.notifyItemSelectedChanged(parentId)
}
override fun showAtLocation(parent: View?, gravity: Int, x: Int, y: Int) {
super.showAtLocation(parent, gravity, x, y)
if (isAutoRequestFocus) {
binding.searchView.requestFocus()
handler.removeCallbacksAndMessages(null)
// 在某些机型上延时一下才能弹起软键盘
handler.postDelayed({
KeyboardUtils.showSoftInput()
}, SHOW_SOFT_INPUT_DELAY)
}
}
override fun dismiss() {
super.dismiss()
clearSearchKey()
handler.removeCallbacksAndMessages(null)
compositeDisposable.clear()
}
companion object {
private const val SEARCH_DELAY_DURATION = 300L
private const val SHOW_SOFT_INPUT_DELAY = 200L
fun newInstance(
context: Context,
height: Int,
isAutoRequestFocus: Boolean,
pageId: String,
pageName: String
): SearchCategoryPop {
val inflater = LayoutInflater.from(context)
val binding = PopSearchCategoryBinding.inflate(inflater)
return SearchCategoryPop(height, isAutoRequestFocus, pageId, pageName, binding)
}
}
interface OnSearchCategoryListener {
fun isEnableSelected(): Boolean
fun onItemSelected(selected: CategoryV2ViewModel.SelectedTags)
fun onItemRemoved(parentId: String, subCategoryId: String)
fun onResetSelected()
fun onSubmit()
}
}

View File

@ -1,57 +0,0 @@
package com.gh.gamecenter.category2
import android.annotation.SuppressLint
import android.text.Spannable
import android.text.SpannableString
import android.text.style.ForegroundColorSpan
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.databinding.RecyclerSearchCategoryResultBinding
class SearchCategoryResultsAdapter(val clickListener: (CategoryV2ViewModel.SelectedTags) -> Unit) :
RecyclerView.Adapter<SearchCategoryResultsAdapter.ResultViewHolder>() {
private val dataList = arrayListOf<CategoryV2ViewModel.SelectedTags>()
private var key = ""
@SuppressLint("NotifyDataSetChanged")
fun setData(data: List<CategoryV2ViewModel.SelectedTags>, newKey: String) {
key = newKey
dataList.clear()
dataList.addAll(data)
notifyDataSetChanged()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ResultViewHolder {
return ResultViewHolder(parent.toBinding())
}
override fun getItemCount() = dataList.size
override fun onBindViewHolder(holder: ResultViewHolder, position: Int) {
val item = dataList[position]
val text = item.category.name ?: ""
val spannableString = SpannableString(text)
val highlightColor = com.gh.gamecenter.common.R.color.text_theme.toColor(holder.itemView.context)
text.forEachIndexed { index, char ->
if (key.contains(char)) {
// 需要高亮
spannableString.setSpan(
ForegroundColorSpan(highlightColor),
index,
index + 1,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
)
}
}
holder.binding.root.text = spannableString
holder.itemView.setOnClickListener {
clickListener(item)
}
}
class ResultViewHolder(val binding: RecyclerSearchCategoryResultBinding) : RecyclerView.ViewHolder(binding.root) {
}
}

View File

@ -1,48 +0,0 @@
package com.gh.gamecenter.category2
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.databinding.RecyclerCategorySelectedTagBinding
class SelectedTagsAdapter(val click: (CategoryV2ViewModel.SelectedTags) -> Unit) :
ListAdapter<CategoryV2ViewModel.SelectedTags, SelectedTagsAdapter.TagsViewHolder>(
createDiffUtil()
) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TagsViewHolder {
return TagsViewHolder(parent.toBinding())
}
override fun onBindViewHolder(holder: TagsViewHolder, position: Int) {
val item = getItem(position)
holder.binding.root.setText(item.category.name ?: "")
holder.itemView.setOnClickListener {
click(item)
}
}
companion object {
private fun createDiffUtil() = object : DiffUtil.ItemCallback<CategoryV2ViewModel.SelectedTags>() {
override fun areItemsTheSame(
oldItem: CategoryV2ViewModel.SelectedTags,
newItem: CategoryV2ViewModel.SelectedTags
): Boolean {
return oldItem.category.id == newItem.category.id
}
override fun areContentsTheSame(
oldItem: CategoryV2ViewModel.SelectedTags,
newItem: CategoryV2ViewModel.SelectedTags
): Boolean {
return oldItem == newItem
}
}
}
class TagsViewHolder(val binding: RecyclerCategorySelectedTagBinding) : RecyclerView.ViewHolder(binding.root)
}

View File

@ -1,94 +1,84 @@
package com.gh.gamecenter.category2
import android.annotation.SuppressLint
import android.content.Context
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.toDrawable
import com.gh.gamecenter.databinding.SubCategoryItemBinding
import com.gh.gamecenter.entity.CategoryEntity
import com.lightgame.adapter.BaseRecyclerAdapter
class SubCategoryAdapter(
private val listener: SearchCategoryPop.OnSearchCategoryListener,
) : RecyclerView.Adapter<SubCategoryAdapter.SubCategoryItemViewHolder>() {
context: Context,
private val mViewModel: CategoryV2ViewModel,
private val mList: List<CategoryEntity>,
private val mPrimaryIndex: Int
) : BaseRecyclerAdapter<SubCategoryAdapter.SubCategoryItemViewHolder>(context) {
private lateinit var itemData: CategoryEntity
private val data: List<CategoryEntity>
get() = itemData.data ?: emptyList()
private var directoryPosition = 0
@SuppressLint("NotifyDataSetChanged")
fun setData(directoryPosition: Int, newItem: CategoryEntity) {
this.directoryPosition = directoryPosition
itemData = newItem
notifyDataSetChanged()
}
override fun getItemCount() = data.size
override fun getItemCount() = mList.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
SubCategoryItemViewHolder(parent.toBinding())
override fun onBindViewHolder(holder: SubCategoryItemViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.isEmpty()) {
super.onBindViewHolder(holder, position, payloads)
} else {
val item = data[position]
updateSelectedState(item.selected, holder.binding)
}
}
SubCategoryItemViewHolder(SubCategoryItemBinding.inflate(mLayoutInflater))
override fun onBindViewHolder(holder: SubCategoryItemViewHolder, position: Int) {
holder.binding.run {
val categoryEntity = data[position]
tvName.text = categoryEntity.name
val categoryEntity = mList[position]
name.text = categoryEntity.name
recommendIv.goneIf(categoryEntity.recommend == false)
updateSelectedState(categoryEntity.selected, this)
if (categoryEntity.selected) {
selectedIv.visibility = View.VISIBLE
container.background = R.drawable.bg_category_selected.toDrawable(mContext)
name.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(mContext))
} else {
selectedIv.visibility = View.GONE
container.background = com.gh.gamecenter.common.R.drawable.bg_shape_space_radius_8.toDrawable(mContext)
name.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(mContext))
}
root.setOnClickListener {
when {
!categoryEntity.selected && !listener.isEnableSelected() -> {
ToastUtils.toast(R.string.selected_category_tags_max_toast.toResString())
}
mViewModel.selectedCount >= 5 && !categoryEntity.selected -> ToastUtils.toast("最多只能选择5个类别")
categoryEntity.selected -> {
listener.onItemRemoved(itemData.id, categoryEntity.id)
categoryEntity.selected = false
selectedIv.visibility = View.GONE
container.background = com.gh.gamecenter.common.R.drawable.bg_shape_space_radius_8.toDrawable(mContext)
name.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(mContext))
mViewModel.run {
if (selectedCount > 0) {
selectedCount--
postSelectedCount()
postCategoryPosition(mPrimaryIndex, position)
logClickClassificationDelete(mPrimaryIndex, categoryEntity.name ?: "", "全部类别")
}
}
}
!categoryEntity.selected -> {
listener.onItemSelected(
CategoryV2ViewModel.SelectedTags(
itemData.id,
itemData.name ?: "",
directoryPosition,
categoryEntity,
position
)
)
categoryEntity.selected = true
categoryEntity.primaryIndex = mPrimaryIndex
selectedIv.visibility = View.VISIBLE
container.background = R.drawable.bg_category_selected.toDrawable(mContext)
name.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(mContext))
mViewModel.run {
if (selectedCount < 5) {
selectedCount++
logClickClassification(mPrimaryIndex, categoryEntity.name ?: "", position)
postSelectedCount()
postCategoryPosition(mPrimaryIndex, position)
}
}
}
}
}
}
}
private fun updateSelectedState(isSelected: Boolean, binding: SubCategoryItemBinding) {
with(binding) {
val context = root.context
if (isSelected) {
container.background = R.drawable.bg_category_selected.toDrawable(context)
tvName.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
} else {
container.background = com.gh.gamecenter.common.R.drawable.bg_shape_space_radius_8.toDrawable(context)
tvName.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
}
}
}
class SubCategoryItemViewHolder(val binding: SubCategoryItemBinding) : ViewHolder(binding.root)
class SubCategoryItemViewHolder(val binding: SubCategoryItemBinding) : BaseRecyclerViewHolder<Any>(binding.root)
}

View File

@ -318,9 +318,7 @@ class GameDownloadFragmentAdapter extends BaseRecyclerAdapter<ViewHolder> {
SensorsBridge.trackInstallGameClick(
downloadEntity.getGameId(),
downloadEntity.getName(),
"主动安装",
false,
""
"主动安装"
);
PackageInstaller.install(mContext, downloadEntity);
}
@ -399,9 +397,7 @@ class GameDownloadFragmentAdapter extends BaseRecyclerAdapter<ViewHolder> {
SensorsBridge.trackInstallGameClick(
downloadEntity.getGameId(),
downloadEntity.getName(),
"主动安装",
"true".equals(ExtensionsKt.getMetaExtra(downloadEntity, Constants.DSP_GAME)),
ExtensionsKt.getMetaExtra(downloadEntity, Constants.DSP_AD_ID)
"主动安装"
);
PackageInstaller.install(mContext, downloadEntity);
}

View File

@ -143,8 +143,6 @@ class UpdatableGameViewModel(
// 按包名分
for (update in updatableList) {
if (update.id == Constants.GHZS_GAME_ID) continue
var list = packageNameAndUpdateListMap[update.packageName]
if (list == null) {
list = arrayListOf()

View File

@ -1,46 +0,0 @@
package com.gh.gamecenter.dsp
import com.gh.gamecenter.common.utils.FixedSizeLinkedHashSet
import com.gh.gamecenter.core.AppExecutor
import com.lightgame.utils.Utils
import okhttp3.OkHttpClient
import okhttp3.Request
object DspReportHelper {
private const val TAG = "DspReportHelper"
private val reportCache by lazy { FixedSizeLinkedHashSet<String>(100) }
fun report(url: String?) {
url ?: return
AppExecutor.logExecutor.execute {
if (reportCache.contains(url)) {
Utils.log(TAG, "遇到重复上报,自动过滤 $url")
return@execute
}
try {
val client = OkHttpClient()
val request = Request.Builder()
.url(url)
.build()
Utils.log(TAG, "Report is executing: $url")
val response = client.newCall(request).execute()
if (response.isSuccessful) {
// Add the URL to the cache to prevent duplicate reports
reportCache.add(url)
Utils.log(TAG, "Report successful: ${response.body?.string()}")
} else {
Utils.log(TAG, "Request failed with code: ${response.code}")
}
} catch (e: Exception) {
Utils.log(TAG, "Request failed: ${e.message}")
}
}
}
}

View File

@ -1,36 +0,0 @@
package com.gh.gamecenter.dsp.data
import com.gh.gamecenter.common.exposure.meta.MetaUtil
import com.gh.gamecenter.common.utils.toRequestBody
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.retrofit.service.DspApiService
import io.reactivex.Single
class GameSubjectDSPRemoteDataSource(private val api: DspApiService = RetrofitManager.getInstance().dspApiService) {
fun getDspGames(type: String, count: Int): Single<List<GameEntity>> {
val meta = MetaUtil.getMeta()
val request = mapOf(
"device" to mapOf(
"oaid" to (meta.oaid ?: ""),
"brand" to (meta.manufacturer ?: ""),
"model" to (meta.model ?: ""),
"osv" to (MetaUtil.getAndroidVersion()),
),
"count" to count,
"scene" to 3,
"busid" to "guanghuan1205"
)
return api.fetch(request.toRequestBody())
.map {
val gameEntityList = mutableListOf<GameEntity>()
for (bidEntity in it.bidList) {
val gameEntity = bidEntity.toGameEntity()
gameEntityList.add(gameEntity)
}
gameEntityList
}
}
}

View File

@ -2,22 +2,16 @@ package com.gh.gamecenter.entity
import android.os.Parcelable
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
@Parcelize
data class CategoryEntity(
@SerializedName("_id")
private var _id: String? = null,
var id: String? = "",
var icon: String? = "",
var name: String? = "",
var recommend: Boolean? = false,
var data: List<CategoryEntity>? = null
) : Parcelable {
val id: String
get() = _id ?: ""
@IgnoredOnParcel
var selected: Boolean = false
}
var data: List<CategoryEntity>? = null,
var selected: Boolean = false,
var primaryIndex: Int = -1
) : Parcelable

View File

@ -7,8 +7,5 @@ class GameColumnCollection(
val id: String = "",
val name: String = "",
// 取值为 "1-1" 或 "1-2" 或 "top" 相应地代表 1行1个 或 1行2个 或 排行榜
val style: String = "",
val custom: Boolean = false, // 自定义设置
@SerializedName("custom_size")
val customSize: Int = 0 // 默认显示前X个专题
val style: String = ""
)

View File

@ -3,7 +3,6 @@ package com.gh.gamecenter.entity
import android.os.Parcelable
import com.gh.common.filter.RegionSettingHelper
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.personalhome.home.UserHistoryViewModel
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@ -20,20 +19,11 @@ data class SearchSubjectEntity(
val adIconActive: Boolean = false,
// 本地字段标记是否为微信小游戏CPM专题
var isWGameSubjectCPM: Boolean = false,
// 本地字段标记是否为DSP专题
var isDspSubject: Boolean = false,
val type: String = "",
@SerializedName("column_type")
val columnType: String = "", // DSP 专题类型 (DSP专题类型apk下载应用、mini_game微信小程序/小游戏)
@SerializedName("show_download") // 下载/秒玩按钮true显示、false不显示专题类型为apk则为下载按钮专题类型为mini_game则为秒完按钮
val showDownload: Boolean = false,
val size: Int = -1, // 专题游戏数量
val type: String = ""
) : Parcelable {
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

@ -7,7 +7,7 @@ import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
@Parcelize
data class SubjectData(
class SubjectData(
// 入口必填
var subjectId: String?,
var subjectName: String?,
@ -21,28 +21,13 @@ data class SubjectData(
var subjectStyle: String = "",
var showDetailSubtitle: Boolean = false,
var showDetailIconSubscript: Boolean = false,
var customLimit: String = "", // unlimited无限制、forbidden禁止移出
var requireUpdateSetting: Boolean = false, // 多专题页面需要专题页面自行获取专题配置
var isAdData: Boolean = false,
var adId: String = "", // 广告ID(本地字段),不为空时为广告专题
var codeId: String = "", // 广告CODE_ID(本地字段),不为空时为广告专题
var tag: String = "" // 分类标签,埋点用
var codeId: String = "" // 广告CODE_ID(本地字段),不为空时为广告专题
) : Parcelable, Cloneable {
@IgnoredOnParcel
val isForbidden
get() = customLimit == "forbidden"
@IgnoredOnParcel
val sortChinese
get() = when {
sort.contains("publish") -> "最新"
sort.contains("star") -> "评分"
sort.contains("update") -> "更新"
else -> "推荐"
}
@IgnoredOnParcel
val subjectStyleChinese: String
get() = CustomPageItem.subjectTypeToComponentStyle[subjectStyle] ?: ""

View File

@ -117,12 +117,7 @@ data class SubjectEntity(
@SerializedName("show_index_icon_subscript")
val showIndexIconSubscript: Boolean = false,
@SerializedName("welfare_info")
val welfareInfo: WelfareInfo? = null,
@SerializedName("column_type")
private val _columnType: String? = null,
@SerializedName("size")
private val _size: Size? = null
val welfareInfo: WelfareInfo? = null
) : Parcelable {
@IgnoredOnParcel
@ -155,27 +150,10 @@ data class SubjectEntity(
val list: Int
get() = max(min(_list ?: 0, data?.size ?: 0), 1)
val columnType: String
get() = _columnType ?: ""
val size: Size
get() = _size ?: Size()
var isDspSubject: Boolean = false
companion object {
const val SUBJECT_TAG_UPDATE = "update" // 更新时间
const val SUBJECT_TAG_TYPE = "type" // 游戏标签
const val SUBJECT_TAG_TEST = "test" // 开测时间
const val SUBJECT_TAG_SELLING_POINT = "selling_points&type" // 卖点文案+游戏标签
}
@Parcelize
data class Size(
@SerializedName("index")
private val _index: Int? = null
) : Parcelable {
val index: Int
get() = _index ?: 0
}
}

View File

@ -13,7 +13,7 @@ class SubjectSettingEntity(
@SerializedName("type")
var typeEntity: TypeEntity = TypeEntity(),
var tag: String = "",
var filter: String = "", // off/on
var filter: String = "", // rows: off/on
var order: Boolean = false, // 是否显示序号
@SerializedName("brief_style")
@ -34,9 +34,6 @@ class SubjectSettingEntity(
private val _showDetailIconSubscript: Boolean? = null
) : Parcelable {
val isFilterEnabled: Boolean
get() = filter == "on"
val showDetailSubtitle: Boolean
get() = _showDetailSubtitle ?: false

View File

@ -23,6 +23,7 @@ import com.gh.gamecenter.eventbus.EBUserFollow
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.forum.home.ForumScrollCalculatorHelper
import com.gh.gamecenter.video.detail.CustomManager
import com.shuyu.gsyvideoplayer.video.base.GSYVideoView
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
@ -124,6 +125,10 @@ class ForumArticleAskListFragment : LazyListFragment<AnswerEntity, ForumArticleA
override fun onFragmentPause() {
super.onFragmentPause()
pauseVideo()
}
override fun onDestroy() {
super.onDestroy()
mScrollCalculatorHelper?.currentPlayer?.release()
}
@ -258,7 +263,9 @@ class ForumArticleAskListFragment : LazyListFragment<AnswerEntity, ForumArticleA
mBaseHandler.postDelayed({
tryCatchInRelease {
if (position != 0L) {
currentPlayer?.startPlayLogic(true)
if (currentPlayer?.currentState == GSYVideoView.CURRENT_STATE_PAUSE) {
currentPlayer?.startPlayLogic(true)
}
} else {
currentPlayer?.release()
}

View File

@ -160,15 +160,12 @@ class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: At
}
//监控播放错误
override fun onError(what: Int, extra: Int) {
super.onError(what, extra)
// override fun onError(what: Int, extra: Int) {
// super.onError(what, extra)
// Utils.toast(context, "网络错误,视频播放失败")
// setViewShowState(mStartButton, View.INVISIBLE)
// errorContainer.visibility = View.VISIBLE
// 部分设备由于MediaCodec实例达到上限导致 CodecException: Error 0xfffffff4在此处尝试释放所有视频
CustomManager.clearAllVideo()
}
//// errorContainer.visibility = View.VISIBLE
// }
override fun releaseVideos() {
CustomManager.releaseAllVideos(getKey())

View File

@ -2,7 +2,7 @@ package com.gh.gamecenter.game.columncollection.detail
import android.os.Bundle
import android.view.View
import androidx.core.view.isVisible
import androidx.lifecycle.ViewModelProviders
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.baselist.LazyListFragment
@ -14,8 +14,6 @@ import com.gh.gamecenter.common.json.json
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.observeNonNull
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.viewModelProviderFromParent
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.databinding.FragmentColumnCollectionDetailBinding
import com.gh.gamecenter.entity.GameColumnCollection
import com.gh.gamecenter.entity.SubjectData
@ -25,7 +23,6 @@ class ColumnCollectionDetailFragment : LazyListFragment<LinkEntity, ColumnCollec
private var mAdapter: ColumnCollectionDetailAdapter? = null
private var mBinding: FragmentColumnCollectionDetailBinding? = null
private var mFragment: SubjectTabFragment? = null
private var mTabIndex = -1
private var mBasicExposureSourceList: ArrayList<ExposureSource>? = null
@ -59,11 +56,6 @@ class ColumnCollectionDetailFragment : LazyListFragment<LinkEntity, ColumnCollec
override fun onFragmentFirstVisible() {
super.onFragmentFirstVisible()
if (mIsFromMainWrapper) {
DisplayUtils.setLightStatusBar(requireActivity(), !mIsDarkModeOn)
mBinding?.statusBar?.isVisible = true
}
mListViewModel.getGameColumnCollection()
mListViewModel.columnCollection.observeNonNull(this) {
setNavigationTitle(it.name)
@ -99,7 +91,7 @@ class ColumnCollectionDetailFragment : LazyListFragment<LinkEntity, ColumnCollec
}
override fun onChanged(list: MutableList<LinkEntity>?) {
if (!list.isNullOrEmpty()) {
if (list != null && list.isNotEmpty()) {
showSubjectTab(list)
}
}
@ -123,28 +115,25 @@ class ColumnCollectionDetailFragment : LazyListFragment<LinkEntity, ColumnCollec
isOrder = false,
requireUpdateSetting = true,
filter = "type:全部", // 默认显示大图
subjectType = subjectType,
customLimit = link.customLimit
subjectType = subjectType
)
)
}
mFragment = childFragmentManager.findFragmentByTag(SubjectTabFragment::class.java.name) as? SubjectTabFragment
val fragment = childFragmentManager.findFragmentByTag(SubjectTabFragment::class.java.name)
?: SubjectTabFragment()
val bundle = arguments
mListViewModel.columnCollection.value?.let {
bundle?.putString(EntranceConsts.KEY_COLUMN_COLLECTION_ID, it.id)
bundle?.putString(EntranceConsts.KEY_COLUMN_COLLECTION_NAME, it.name)
bundle?.putString(EntranceConsts.KEY_COLUMN_COLLECTION_STYLE, it.style)
bundle?.putBoolean(EntranceConsts.KEY_COLUMN_COLLECTION_CUSTOM, it.custom)
bundle?.putInt(EntranceConsts.KEY_COLUMN_COLLECTION_CUSTOM_SIZE, it.customSize)
}
bundle?.putParcelableArrayList(EntranceConsts.KEY_DATA, subjectDataList)
bundle?.putBoolean(EntranceConsts.KEY_IS_COLUMN_COLLECTION, true)
mFragment!!.arguments = bundle
fragment.arguments = bundle
mBinding?.placeholder?.visibility = View.VISIBLE
childFragmentManager.beginTransaction().replace(R.id.placeholder, mFragment!!, SubjectTabFragment::class.java.name)
childFragmentManager.beginTransaction().replace(R.id.placeholder, fragment, SubjectTabFragment::class.java.name)
.commitAllowingStateLoss()
}
@ -178,18 +167,9 @@ class ColumnCollectionDetailFragment : LazyListFragment<LinkEntity, ColumnCollec
arguments?.getString(EntranceConsts.KEY_COLLECTION_ID)
?: ""
)
return viewModelProviderFromParent(factory, arguments?.getString(EntranceConsts.KEY_COLLECTION_ID) ?: "")
return ViewModelProviders.of(this, factory).get(ColumnCollectionDetailViewModel::class.java)
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
if (mIsFromMainWrapper) {
DisplayUtils.setLightStatusBar(requireActivity(), !mIsDarkModeOn)
}
}
override fun onBackPressed(): Boolean = mFragment?.onBackPressed() ?: false
companion object {
const val TYPE_QQ_MINI_GAME_COLUMN = "qq_mini_game_column"
const val TYPE_WECHAT_MINI_GAME_COLUMN = "wechat_mini_game_column"

View File

@ -15,10 +15,10 @@ class GameHorizontalSimpleItemViewHolder(val binding: GameHorizontalSimpleItemBi
view.text = name
view.viewTreeObserver.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
view.viewTreeObserver.removeOnGlobalLayoutListener(this)
val newText = autoSplitText(view)
view.viewTreeObserver.removeOnGlobalLayoutListener(this);
val newText = autoSplitText(view);
if (!TextUtils.isEmpty(newText)) {
view.text = newText
view.text = newText;
}
}
})

View File

@ -137,6 +137,7 @@ class GameHorizontalSlideAdapter(
}
}
// notifyDataSetChanged 会出现页面抖动情况
fun checkResetData(subjectEntity: SubjectEntity) {
var dataIds = ""

View File

@ -37,6 +37,7 @@ import com.gh.gamecenter.gamedetail.detail.viewholder.GameDetailContentRecommend
import com.gh.gamecenter.gamedetail.entity.*
import com.gh.gamecenter.livedata.Event
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.login.user.UserRepository
import com.gh.gamecenter.manager.PackagesManager
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.vspace.VHelper
@ -104,7 +105,7 @@ class GameDetailViewModel(
private var relatedGameList = arrayListOf<GameEntity>()
// 内容卡片相关
private var contentCardList: List<ContentCardEntity>? = null
private var contentCardList: List<ContentCardEntity>?= null
private val contentCardSp by lazy {
HaloApp.getInstance().getSharedPreferences(SP_CONTENT_CARD, Context.MODE_PRIVATE)
}
@ -133,9 +134,9 @@ class GameDetailViewModel(
private var isGameUpdatable = false
private val compositeDisposable = CompositeDisposable()
private var userRelatedInfoReceivedCallback: (() -> Unit)? = null
init {
loadData()
}
@ -192,6 +193,13 @@ class GameDetailViewModel(
game = response
gameLiveData.postValue(Resource.success(game))
loadGameDetailData()
if (game?.canSpeed == true) {
// 如果当前游戏支持加速则刷新vip状态
val userId = UserManager.getInstance().userId
if (userId.isNotBlank()) {
UserRepository.getInstance().refreshVipStatus(userId, true)
}
}
}
override fun onFailure(e: HttpException?) {
@ -1116,14 +1124,6 @@ class GameDetailViewModel(
compositeDisposable.clear()
}
fun markContentCardRedDot(contentCardEntity: ContentCardEntity) {
SPUtils.setString(contentCardSp, RED_DOT_PREFIX + (gameId ?: "") + contentCardEntity.link.type, contentCardEntity.redDot.toString())
}
fun shouldShowContentCardRedDot(contentCardEntity: ContentCardEntity): Boolean {
return SPUtils.getString(contentCardSp, RED_DOT_PREFIX + (gameId ?: "") + contentCardEntity.link.type) != contentCardEntity.redDot.toString()
}
class Factory(
private val application: Application,
private val gameId: String?,
@ -1140,7 +1140,6 @@ class GameDetailViewModel(
companion object {
const val SP_CONTENT_CARD = "content_card"
const val TAG = "GameDetailViewModel"
const val RED_DOT_PREFIX = "red_dot_"
//[已领取/已淘号/再领/再淘]的礼包置底显示
fun sortLibaoList(libaoList: ArrayList<LibaoEntity>?) {

View File

@ -45,7 +45,6 @@ import com.gh.gamecenter.common.base.fragment.BaseLazyFragment
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.eventbus.EBReuse
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.json.json
import com.gh.gamecenter.common.mvvm.Status
@ -635,7 +634,7 @@ class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
DialogHelper.showDialogWithHtmlContent(
requireContext(),
dialog.title,
dialog.content.replaceLineBreakWithBr(),
dialog.content,
dialog.confirmButton.text.toString(),
dialog.closeButtonText,
{
@ -669,8 +668,7 @@ class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
"game_type", gameEntity?.categoryChinese ?: "",
"button_name", "关闭弹窗"
)
},
extraConfig = DialogHelper.Config(centerTitle = true)
}
)
}
@ -1113,13 +1111,6 @@ class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
return true
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(reuse: EBReuse) {
if ("download" == reuse.type) {
downloadBinding.detailProgressbar.performClick()
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(status: EBDownloadStatus) {
updateDownloadCountHint(packageViewModel.filterSameUpdateLiveData.value)
@ -1450,7 +1441,6 @@ class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
* 3. 当前游戏 APK 不为 1 个
* 4. 当前游戏类型不为畅玩
* 5. 当前游戏不是双下载时使用本地下载进行下载
* 6. 当前游戏支持加速
*/
private fun isSpecialDownloadDialogAvailable(downloadEntity: DownloadEntity? = null): Boolean {
if (Config.getNewApiSettingsEntity()?.install?.questionTip?.linkEntity == null) return false
@ -1459,7 +1449,7 @@ class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
if (downloadEntity?.asVGame() == true) return false
if (downloadEntity?.isSimulatorGame() == true) return false
if (downloadEntity?.isLocalDownloadInDualDownloadMode() == true) return false
if (gameEntity?.canSpeed == true) return false
return true
}

View File

@ -3,11 +3,8 @@ package com.gh.gamecenter.gamedetail
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.gh.gamecenter.common.constant.Constants.SP_ACCELERATOR_MEMBERSHIP_EXPIRED
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.core.provider.IAcceleratorProvider
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.feature.entity.BaseEntity
import com.gh.gamecenter.feature.entity.TrialEntity
import com.gh.gamecenter.feature.entity.VipEntity
@ -16,7 +13,6 @@ import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.login.user.UserRepository
import com.halo.assistant.accelerator.AccelerationUseCase
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
import com.therouter.TheRouter
import io.reactivex.disposables.CompositeDisposable
class StartingAcceleratorViewModel : ViewModel() {
@ -25,14 +21,13 @@ class StartingAcceleratorViewModel : ViewModel() {
val useCase = AccelerationUseCase()
private val _restartingAcceleratorAction = MutableLiveData<Event<Unit>>()
val restartingAcceleratorAction: LiveData<Event<Unit>> = _restartingAcceleratorAction
private val _restartingAcceleratorAction = MutableLiveData<Event<Boolean>>()
val restartingAcceleratorAction: LiveData<Event<Boolean>> = _restartingAcceleratorAction
fun loadAcceleratorToken() {
val userId = UserManager.getInstance().userId
if (userId.isNotBlank()) {
UserRepository.getInstance().setAcceleratorToken(userId) {
// 这里就算setToken失败也要调用启动加速失败会走正常的日志上报
_restartingAcceleratorAction.value = Event(Unit)
_restartingAcceleratorAction.value = Event(it)
}
}
}
@ -46,15 +41,16 @@ class StartingAcceleratorViewModel : ViewModel() {
.subscribe(object : BiResponse<BaseEntity<TrialEntity>>() {
override fun onSuccess(data: BaseEntity<TrialEntity>) {
if (data.data?.result == true) {
// 试用三小时下单成功
val permissionEntity = VipEntity(
_vipStatus = true,
_isNewUser = false,
_isTryVip = true,
// 刷新vip状态
// 这里先刷新内存数据
AcceleratorDataHolder.instance.setVipEntity(
VipEntity(
_vipStatus = true,
_isNewUser = false,
_isTryVip = true
)
)
AcceleratorDataHolder.instance.setVipEntity(permissionEntity)
TheRouter.get(IAcceleratorProvider::class.java)?.loadQyUserPermissionData()
SPUtils.setBoolean(SP_ACCELERATOR_MEMBERSHIP_EXPIRED, false)
_rechargeTrailResult.value = Event(true)
} else {
_rechargeTrailResult.value = Event(false)
@ -68,33 +64,6 @@ class StartingAcceleratorViewModel : ViewModel() {
}).let(compositeDisposable::add)
}
private val _vipEntity = MutableLiveData<Event<VipEntity?>>()
val vipEntity: LiveData<Event<VipEntity?>> = _vipEntity
fun loadVipStatus() {
val enableRefreshVipStatus = AcceleratorDataHolder.instance.enableRefresh
if (enableRefreshVipStatus) {
useCase.loadVipStatus()
.compose(singleToMain())
.subscribe(object : BiResponse<BaseEntity<VipEntity>>() {
override fun onSuccess(data: BaseEntity<VipEntity>) {
val vip = data.data
if (vip != null) {
AcceleratorDataHolder.instance.setVipEntity(vip)
}
_vipEntity.value = Event(vip)
}
override fun onFailure(exception: Exception) {
super.onFailure(exception)
_vipEntity.value = Event(null)
}
}).let(compositeDisposable::add)
} else {
_vipEntity.value = Event(AcceleratorDataHolder.instance.vipEntity)
}
}
override fun onCleared() {
super.onCleared()
compositeDisposable.clear()

View File

@ -15,6 +15,7 @@ import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.databinding.DetailDownloadItemBinding
import com.gh.gamecenter.feature.entity.AcctGameInfo
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.VipEntity
import com.gh.gamecenter.gamedetail.accelerator.chain.AcceleratorClient
import com.gh.gamecenter.gamedetail.accelerator.chain.AcceleratorValidator
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorDialogFragment
@ -55,8 +56,6 @@ class GameDetailAcceleratorUiHelper(private val binding: DetailDownloadItemBindi
private var hasAnyAcctRecord = false
private var showDualDownloadButton: Boolean = false
private val accelerationListener = object : OnAccelerateListener {
override fun onStateChanged(state: AccelerateState) {
when (state) {
@ -78,7 +77,7 @@ class GameDetailAcceleratorUiHelper(private val binding: DetailDownloadItemBindi
}
private val onDataHolderListener = object : AcceleratorDataHolder.OnDataHolderListener {
override fun onVipStateChanged() {
override fun onVipStateChanged(vip: VipEntity) {
showFreeTag()
}
}
@ -133,17 +132,13 @@ class GameDetailAcceleratorUiHelper(private val binding: DetailDownloadItemBindi
val isCurrentGameAccelerating = AcceleratorDataHolder.instance.isCurrentGameAccelerating(game.id)
if (!showDualDownloadButton) {
when {
isCurrentGameAccelerating -> {// 如果当前游戏正处于加速状态,则需要隐藏当前下载按钮
binding.detailProgressbar.goneIf(true)
}
when {
isCurrentGameAccelerating -> {// 如果当前游戏正处于加速状态,则需要隐藏当前下载按钮
binding.detailProgressbar.goneIf(true)
}
binding.detailProgressbar.text == "更新" -> { // 游戏没有处于加速状态,如果 下载按钮为 “更新” 状态,则需要显示出来
binding.detailProgressbar.setBackgroundResource(com.gh.gamecenter.common.R.drawable.bg_common_button_light_fill_blue)
binding.detailProgressbar.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
binding.detailProgressbar.goneIf(false)
}
binding.detailProgressbar.text == "更新" -> { // 游戏没有处于加速状态,如果 下载按钮为 “更新” 状态,则需要显示出来
binding.detailProgressbar.goneIf(false)
}
}
@ -158,13 +153,14 @@ class GameDetailAcceleratorUiHelper(private val binding: DetailDownloadItemBindi
} ?: R.string.network_acceleration.toResString()
}
fun checkIfShowSpeedUi(show: Boolean, showDualDownloadButton: Boolean) {
this.showDualDownloadButton = showDualDownloadButton
fun checkIfShowSpeedUi(show: Boolean) {
if (!isInit) {
return
}
showSpeedUi = show
binding.clSpeedContainer.goneIf(!showSpeedUi) {
binding.detailProgressbar.setBackgroundResource(com.gh.gamecenter.common.R.drawable.bg_common_button_light_fill_blue)
binding.detailProgressbar.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
// 是否需要展示弹窗
showGuideLayerIfNeed()
updateSpeedUi()
@ -228,7 +224,7 @@ class GameDetailAcceleratorUiHelper(private val binding: DetailDownloadItemBindi
game.id,
game.name ?: ""
)
guideView?.show(binding.clSpeed, context, uiListener::onStartAccelerator)
guideView?.show(binding.clSpeedContainer, context, uiListener::onStartAccelerator)
}
}
@ -248,7 +244,6 @@ class GameDetailAcceleratorUiHelper(private val binding: DetailDownloadItemBindi
if (isInvalidClick()) {
return
}
val lastAcctGameInfo = last
val memberType = AcceleratorDataHolder.instance.memberType
@ -283,7 +278,6 @@ class GameDetailAcceleratorUiHelper(private val binding: DetailDownloadItemBindi
doStartAccelerating(context, game, gameInfo)
}
}
}
private fun showZoneList(context: Context, game: GameEntity) {
@ -296,14 +290,13 @@ class GameDetailAcceleratorUiHelper(private val binding: DetailDownloadItemBindi
SOURCE_ENTRANCE_GAME_DETAIL
)
}
}
private var clickTime = 0L
private fun doStartAccelerating(context: Context, game: GameEntity, zoneInfo: AcctGameInfo.ZoneInfo) {
val request = AcceleratorValidator.Request(game, SOURCE_ENTRANCE_GAME_DETAIL)
val request = AcceleratorValidator.Request(isVip, isNewUser, game, SOURCE_ENTRANCE_GAME_DETAIL)
AcceleratorClient.newInstance()
.execute(context, request, object : AcceleratorValidator.ValidateListener {
override fun finished(context: Context) {

View File

@ -22,7 +22,7 @@ class AcceleratorClient(
AcceleratorLoginInterceptor(),
AcceleratorPackageCheckInterceptor(),
AcceleratorRealNameInterceptor(),
AcceleratorPermissionGuideInterceptor(),
AcceleratorVipInterceptor(),
AcceleratorStateInterceptor()
)
}

View File

@ -1,26 +0,0 @@
package com.gh.gamecenter.gamedetail.accelerator.chain
import android.content.Context
import com.gh.common.dialog.AcceleratorPermissionGuideDialogFragment
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.core.utils.SPUtils
class AcceleratorPermissionGuideInterceptor : AcceleratorValidator.Interceptor {
override fun intercept(
context: Context,
chain: AcceleratorValidator.Chain,
listener: AcceleratorValidator.ValidateListener?
) {
val hasShow = SPUtils.getBoolean(Constants.SP_ACCELERATOR_PERMISSION_GUIDE_SHOW)
val request = chain.request
if (hasShow) {
chain.proceed(context, request, listener)
} else {
AcceleratorPermissionGuideDialogFragment.show(context) {
if (chain.isValidContext(context)) {
chain.proceed(context, request, listener)
}
}
}
}
}

View File

@ -31,9 +31,11 @@ class AcceleratorValidator {
@Parcelize
data class Request(
val isVip: Boolean,
val isNewUser: Boolean,
val game: GameEntity,
val sourceEntrance: String
) : Parcelable
):Parcelable
}

View File

@ -1,14 +1,8 @@
package com.gh.gamecenter.gamedetail.accelerator.chain
import android.content.Context
import com.gh.common.dialog.AccelerateExpirationDialogFragment
import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.toResString
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorDialogFragment
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorDialogFragment.Companion.SPEED_ENABLE_VIP
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
class AcceleratorVipInterceptor : AcceleratorValidator.Interceptor {
@ -18,24 +12,20 @@ class AcceleratorVipInterceptor : AcceleratorValidator.Interceptor {
listener: AcceleratorValidator.ValidateListener?
) {
val request = chain.request
val vip = AcceleratorDataHolder.instance.vipEntity
if (vip?.vipStatus == true || vip?.isNewUser == true) {
val isVip = request.isVip
val isNewUser = request.isNewUser
if (isVip || isNewUser) {
chain.proceed(context, request, listener)
} else {
val reminder = AccelerateExpirationDialogFragment.ExpirationReminder.TimeRunsOut(request.sourceEntrance)
AccelerateExpirationDialogFragment.checkDialogShown(context, reminder) {
if (!it) {
val game = request.game
AcceleratorDialogFragment.show(
SPEED_ENABLE_VIP,
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
request.sourceEntrance,
context
)
}
}
val game = request.game
AcceleratorDialogFragment.show(
SPEED_ENABLE_VIP,
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
request.sourceEntrance,
context
)
}
}
}

View File

@ -86,7 +86,9 @@ class AcceleratorZoneDialogFragment : BaseBottomDialogFragment<DialogFragmentAcc
sourceEntrance
)
val request = AcceleratorValidator.Request(game, sourceEntrance)
val isVip = acceleratorDataHolder.isVip
val isNewUser = acceleratorDataHolder.isNewUser
val request = AcceleratorValidator.Request(isVip, isNewUser, game, sourceEntrance)
AcceleratorClient.newInstance()
.execute(it, request, object : AcceleratorValidator.ValidateListener {
override fun finished(context: Context) {

View File

@ -10,35 +10,30 @@ import android.view.View
import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.viewModels
import com.gh.common.dialog.AccelerateExpirationDialogFragment
import com.gh.common.util.PackageLauncher
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.BaseDialogFragment
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.core.callback.AccelerateState
import com.gh.gamecenter.core.callback.OnAccelerateListener
import com.gh.gamecenter.core.provider.IAcceleratorProvider
import com.gh.gamecenter.core.utils.CurrentActivityHolder
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.databinding.DialogFragmentStartingAcceleratorBinding
import com.gh.gamecenter.feature.entity.AcctGameInfo
import com.gh.gamecenter.feature.entity.AcctRecord
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.VipEntity
import com.gh.gamecenter.gamedetail.StartingAcceleratorViewModel
import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.DISTRICT_SERVER_HAVA
import com.gh.gamecenter.gamedetail.accelerator.GameDetailAcceleratorUiHelper.Companion.SOURCE_ENTRANCE_GAME_DETAIL
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorDialogFragment.Companion.SPEED_ENABLE_VIP
import com.gh.gamecenter.gamedetail.accelerator.dialog.AcceleratorDialogFragment.Companion.SPEED_START_FAILURE
import com.gh.gamecenter.livedata.EventObserver
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.login.user.UserRepository
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
import com.therouter.TheRouter
import io.reactivex.disposables.CompositeDisposable
import java.util.*
import kotlin.math.ceil
import kotlin.math.max
class StartingAcceleratorDialogFragment : BaseDialogFragment() {
@ -96,30 +91,14 @@ class StartingAcceleratorDialogFragment : BaseDialogFragment() {
// 加速失败上传奇游加速器log
unloadAcceleratorErrorLog(game)
}
} else {
// 由于每次加速前,都会检测加速会员权限,所以一般不会出现这种场景,这里只是为了兼容特殊情况,弹出相应提示
if (state.isPermissionExpired) {
// 加速会员过期 ,如果之前没有弹出过耗尽提醒,则弹出提示
val reminder =
AccelerateExpirationDialogFragment.ExpirationReminder.TimeRunsOut(sourceEntrance)
AccelerateExpirationDialogFragment.checkDialogShown(requireContext(), reminder) {
if (!it) {
// 之前弹出过耗尽提醒弹窗,这里直接展示加速失败
AcceleratorDialogFragment.show(
SPEED_START_FAILURE,
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
sourceEntrance,
requireContext()
)
}
}
}
iAcceleratorProvider?.loadQyUserPermissionData()
// 加速失败上传奇游加速器log
unloadAcceleratorErrorLog(game)
}
if (state.isPermissionExpired) {
// 加速会员过期刷新本地vip状态
val userId = UserManager.getInstance().userId
UserRepository.getInstance().refreshVipStatus(userId, true)
// 加速失败上传奇游加速器log
unloadAcceleratorErrorLog(game)
}
dismissAllowingStateLoss()
@ -199,6 +178,14 @@ class StartingAcceleratorDialogFragment : BaseDialogFragment() {
binding.tvProgress.text = getString(R.string.accelerating_with_progress, "0")
iAcceleratorProvider?.bindAccRelatedListener("", accelerationListener)
val isVip = acceleratorDataHolder.isVip
val isNewUser = acceleratorDataHolder.isNewUser
if (isNewUser && !isVip) {
// 新用户并且还不是vip
viewModel.rechargeTrial()
} else {
startGameAccelerate()
}
viewModel.restartingAcceleratorAction.observe(viewLifecycleOwner, EventObserver {
startGameAccelerate()
@ -221,162 +208,6 @@ class StartingAcceleratorDialogFragment : BaseDialogFragment() {
}
})
viewModel.vipEntity.observe(viewLifecycleOwner, EventObserver {
if (it == null) {
// 获取用户vip数据失败直接弹出加速失败
AcceleratorDialogFragment.show(
SPEED_START_FAILURE,
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
sourceEntrance,
requireContext()
)
dismissAllowingStateLoss()
trackNetworkAccelerationStartupResult("失败(获取vip数据失败)")
} else {
// 获取用户vip数据成功
canUserEnableAcceleration(it) {
startGameAccelerate()
}
}
})
viewModel.loadVipStatus()
}
private fun canUserEnableAcceleration(vipEntity: VipEntity, callback: () -> Unit) {
if (vipEntity.isNewUser) {
// 如果是新用户,下单试用套餐
viewModel.rechargeTrial()
return
}
val serviceTime = vipEntity.timestamp
val userRemainingDays = calculateCalendarDaysDiff(vipEntity.vipExpireTime, serviceTime)
//如果是试用会员 或者是 包月会员,并且还没有临期: 直接启动。
if (vipEntity.isTryVip || (vipEntity.vipStatus && userRemainingDays > 3)) {
callback()
return
}
// 包月会员不足三天
if (vipEntity.isMonthVip && userRemainingDays in 1..3) {
val reminder = AccelerateExpirationDialogFragment.ExpirationReminder.UserExpired(
userRemainingDays, game.id, game.name ?: "", game.getUniquePackageName() ?: "", sourceEntrance
)
AccelerateExpirationDialogFragment.checkDialogShown(requireContext(), reminder) {
if (it) {
dismissAllowingStateLoss()
} else {
callback()
}
}
return
}
// 检查时长套餐是否快要耗尽
val durationRemainingMinute = vipEntity.durationExpiredTime
if (vipEntity.isDurationVip && durationRemainingMinute < 0) {
// 用户是时长会员,但是剩余时长为 -1 ,说明后端拉取奇游剩余时长失败,此时无法判断用户的时长会员是否到期,直接开启加速
callback()
return
}
// 用户是时长会员,并且将要消耗完毕
val durationRemainingHours = ceil(durationRemainingMinute / 60.0).toLong()
if (vipEntity.isDurationVip && durationRemainingHours in 1..3) {
val reminder = AccelerateExpirationDialogFragment.ExpirationReminder.InsufficientDuration(
durationRemainingHours, game.id, game.name ?: "", game.getUniquePackageName() ?: "", sourceEntrance
)
AccelerateExpirationDialogFragment.checkDialogShown(requireContext(), reminder) {
if (it) {
dismissAllowingStateLoss()
} else {
callback()
}
}
return
}
// 检查时长套餐是否快要过期
val durationRemainingDays = calculateCalendarDaysDiff(vipEntity.durationVipExpiredTime, serviceTime)
if (vipEntity.isDurationVip && durationRemainingDays in 1..3) {
val reminder = AccelerateExpirationDialogFragment.ExpirationReminder.DurationExpired(
durationRemainingDays, game.id, game.name ?: "", game.getUniquePackageName() ?: "", sourceEntrance
)
AccelerateExpirationDialogFragment.checkDialogShown(requireContext(), reminder) {
if (it) {
dismissAllowingStateLoss()
} else {
callback()
}
}
return
}
// 检查会员是否到期
val hasShow = SPUtils.getBoolean(Constants.SP_ACCELERATOR_MEMBERSHIP_EXPIRED)
val showExpiredDialog = !vipEntity.vipStatus &&
!vipEntity.isNewUser &&
!vipEntity.isVipRefundUser &&
!vipEntity.isDurationRefundUser &&
!hasShow
if (showExpiredDialog) {
val reminder = AccelerateExpirationDialogFragment.ExpirationReminder.TimeRunsOut(sourceEntrance)
AccelerateExpirationDialogFragment.checkDialogShown(requireContext(), reminder) {
if (it) {
dismissAllowingStateLoss()
} else {
callback()
}
}
return
}
// 检查是否是会员
if (vipEntity.vipStatus) {
callback()
} else {
// 弹出去充值弹窗
AcceleratorDialogFragment.show(
SPEED_ENABLE_VIP,
game.getUniquePackageName() ?: "",
game.id,
game.name ?: "",
sourceEntrance,
requireContext()
)
dismissAllowingStateLoss()
}
}
private fun calculateCalendarDaysDiff(timeSeconds1: Long, timeSeconds2: Long): Long {
// 转换秒为毫秒
val millis1 = timeSeconds1 * 1000
val millis2 = timeSeconds2 * 1000
val calendar1 = Calendar.getInstance().apply {
timeInMillis = millis1
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
val calendar2 = Calendar.getInstance().apply {
timeInMillis = millis2
set(Calendar.HOUR_OF_DAY, 0)
set(Calendar.MINUTE, 0)
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}
// 计算两个日期零点之间的差值(毫秒),再转换为天数
val diff = (calendar1.timeInMillis - calendar2.timeInMillis) / (1000 * 60 * 60 * 24)
// 当diff == 0 时,当天过期,此时剩余天数为 1 天
return if (diff >= 0) diff + 1 else -1
}

View File

@ -86,8 +86,7 @@ class GameDetailFragment : LazyFragment(), IScrollable {
GameDetailCoverAdapter(
requireContext(),
this@GameDetailFragment,
viewModel,
scrollCalculatorHelper
viewModel
) {
binding.coverSfv.isVisible = shouldShowCoverFilter && it
}
@ -201,7 +200,6 @@ class GameDetailFragment : LazyFragment(), IScrollable {
"game_name" to gameEntity?.name
"sequence" to viewModel.coverTabSequence
"tab_name" to viewModel.coverTabName
"game_type" to gameEntity?.categoryChinese
})
if (currentCoverEntity?.tabName == checkedText) return@setOnCheckedCallback
@ -313,7 +311,7 @@ class GameDetailFragment : LazyFragment(), IScrollable {
private fun initDetailRv() {
binding.detailRv.run {
itemAnimator = null
(itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
layoutManager = detailLayoutManager
adapter = detailListAdapter
addOnScrollListener(object : OnScrollListener() {
@ -684,7 +682,7 @@ class GameDetailFragment : LazyFragment(), IScrollable {
null,
gameStatus = gameStatus
)
GameFunctionDialogFragment.show(requireContext(), gameEntity, gameDetailInfoTag.infoTags, gameDetailInfoTag.link)
GameFunctionDialogFragment.show(requireContext(), gameDetailInfoTag.infoTags)
}
}
}

View File

@ -23,7 +23,6 @@ import com.gh.gamecenter.databinding.ItemGameCoverVideoBinding
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.detail.GameDetailFragment
import com.gh.gamecenter.gamedetail.entity.CoverEntity
import com.gh.gamecenter.gamedetail.video.GameDetailScrollCalculatorHelper
import com.gh.gamecenter.gamedetail.video.TopVideoView
import com.lightgame.adapter.BaseRecyclerAdapter
import com.shuyu.gsyvideoplayer.builder.GSYVideoOptionBuilder
@ -34,7 +33,6 @@ class GameDetailCoverAdapter(
context: Context,
private val fragment: GameDetailFragment,
private val viewModel: GameDetailViewModel,
private val scrollCalculatorHelper: GameDetailScrollCalculatorHelper,
private val showOrHideCoverFilter: ((Boolean) -> Unit)
) : BaseRecyclerAdapter<RecyclerView.ViewHolder>(context) {
@ -110,14 +108,6 @@ class GameDetailCoverAdapter(
.setAutoFullWithSize(true)
.setDismissControlTime(5000)
.setVideoAllCallBack(object : GSYSampleCallBack() {
override fun onClickStartIcon(url: String?, vararg objects: Any?) {
scrollCalculatorHelper.currentPlayer = holder.binding.player
}
override fun onClickResume(url: String?, vararg objects: Any?) {
scrollCalculatorHelper.currentPlayer = holder.binding.player
}
override fun onQuitFullscreen(url: String?, vararg objects: Any) {
orientationUtils.backToProtVideo()
holder.binding.player.uploadVideoStreamingPlaying("退出全屏")
@ -127,7 +117,6 @@ class GameDetailCoverAdapter(
holder.binding.player.viewModel = viewModel
holder.binding.player.showOrHideCoverFilter = showOrHideCoverFilter
holder.binding.player.scrollCalculatorHelper = scrollCalculatorHelper
holder.binding.player.video = topVideo
holder.binding.player.updateThumb(topVideo.poster)

View File

@ -46,7 +46,7 @@ class GameDetailBriefItemViewHolder(
highlightedTextClickListener = TextHelper.CopyToClipboardHighlightedTextClick()
)
briefTv.post {
expandTv.isVisible = briefTv.lineCount == 3 && (briefTv.layout?.getEllipsisCount(2) ?: 0) > 0
expandTv.isVisible = briefTv.lineCount == 3 && briefTv.layout.getEllipsisCount(2) > 0
}
expandTv.setOnClickListener {
SensorsBridge.trackGameDetailModuleClick(
@ -57,7 +57,6 @@ class GameDetailBriefItemViewHolder(
moduleType,
entity.name,
sequence,
"简介文案",
gameStatus = gameStatus
)
GameDetailScrollableTextDialogFragment.show(
@ -87,9 +86,6 @@ class GameDetailBriefItemViewHolder(
moduleType,
itemData?.linkGameBrief?.name,
sequence,
"游戏标签",
index + 1,
linkText = gameTag.name,
gameStatus = gameStatus
)
context.startActivity(

View File

@ -82,7 +82,6 @@ class GameDetailComprehensivePanelItemViewHolder(
root.updateLayoutParams<MarginLayoutParams> {
topMargin = if (position == 0) 0 else 8F.dip2px()
}
titleIv.goneIf(!data.showTitle)
titleTv.goneIf(!data.showTitle) {
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
titleTv.text = data.title
@ -128,7 +127,6 @@ class GameDetailComprehensivePanelItemViewHolder(
topMargin = if (position == 0) 0 else 16F.dip2px()
bottomMargin = if (position == itemCount - 1) 0 else if (data.showTitle) 12F.dip2px() else 8F.dip2px()
}
titleIv.goneIf(!data.showTitle)
titleTv.goneIf(!data.showTitle) {
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
titleTv.text = data.title
@ -152,7 +150,6 @@ class GameDetailComprehensivePanelItemViewHolder(
root.updateLayoutParams<MarginLayoutParams> {
topMargin = if (position == 0) 0 else 12F.dip2px()
}
titleIv.goneIf(!data.showTitle)
titleTv.goneIf(!data.showTitle) {
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
titleTv.text = data.title
@ -197,8 +194,6 @@ class GameDetailComprehensivePanelItemViewHolder(
maxLines = if (parentViewHolder.showPart && !parentViewHolder.isExpand) 1 else Int.MAX_VALUE
text = data.text
post {
if (layout == null) return@post
val hasEllipsize = layout.getEllipsisCount(0) > 0
if (parentViewHolder.showPart && hasEllipsize && !parentViewHolder.binding.expandTv.isVisible) {
parentViewHolder.binding.expandTv.isVisible = true

View File

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

View File

@ -1,9 +1,12 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.content.Context
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import androidx.lifecycle.LifecycleOwner
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.common.constant.Config
import com.gh.common.util.DirectUtils
import com.gh.common.util.NewFlatLogUtils
@ -16,12 +19,10 @@ import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.TextBannerView
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.databinding.ItemGameDetailContentCardSingleBinding
import com.gh.gamecenter.databinding.LayoutGameDetailContentCardBinding
import com.gh.gamecenter.feature.entity.MeEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.CardBackground
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity.Companion.TYPE_ARCHIVE
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity.Companion.TYPE_BBS
@ -50,154 +51,148 @@ class GameDetailContentCardSingleItemViewHolder(
val contentCardEntity = data.linkContentCard?.first() ?: return
binding.run {
bindContentCard(
context,
lifecycleOwner,
viewModel,
contentCardEntity,
data.cardBackground,
firstCardContainer,
container,
titleTv,
iconIv,
contentTv,
contentBannerView,
redDotTv,
newIv,
baseTrackData.apply { supSequence = 0 }
) { gameStatus }
}
}
companion object {
fun bindContentCard(
context: Context,
lifecycleOwner: LifecycleOwner,
viewModel: GameDetailViewModel,
contentCardEntity: ContentCardEntity,
cardBackground: CardBackground?,
itemBinding: LayoutGameDetailContentCardBinding,
containerView: ConstraintLayout,
titleTv: TextView,
iconIv: SimpleDraweeView,
contentTv: TextView,
contentBannerView: TextBannerView,
redDotTv: TextView,
newIv: ImageView,
trackData: GameDetailModuleTrackData,
getGameStatus: () -> String
) {
with(itemBinding) {
val context = root.context
if (contentCardEntity.id == root.tag) {
if (contentBannerView.isVisible) contentBannerView.updateView()
cardBackground?.colorTransparent?.let {
root.setRoundedColorBackgroundByColorInt(
it.RGBAToARGB().hexStringToIntColor(),
8F
)
} ?: run {
root.background = com.gh.gamecenter.common.R.drawable.bg_shape_f8_radius_8.toDrawable(context)
if (contentCardEntity.id == containerView.tag) {
if (contentBannerView.isVisible) contentBannerView.updateView()
containerView.background = com.gh.gamecenter.common.R.drawable.bg_shape_f8_radius_8.toDrawable(context)
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
contentTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
return
}
containerView.tag = contentCardEntity.id
titleTv.text = contentCardEntity.title
ImageUtils.display(iconIv, contentCardEntity.icon)
val showContentTv =
contentCardEntity.showDes && (contentCardEntity.des.isNotEmpty() || contentCardEntity.link.type == TYPE_GIFT)
contentTv.visibleIf(showContentTv) {
contentTv.text =
if (contentCardEntity.link.type == TYPE_GIFT && contentCardEntity.des.isEmpty()) "${contentCardEntity.libao?.total}个游戏礼包" else contentCardEntity.des
contentTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
}
val showContentBannerView =
contentCardEntity.showDes && contentCardEntity.link.type == TYPE_SERVER && contentCardEntity.server != null && contentCardEntity.des.isEmpty()
contentBannerView.goneIf(!showContentBannerView) {
val server = contentCardEntity.server ?: return@goneIf
val nowTime = System.currentTimeMillis()
var timeDiff = 0L
var closestIndex = 0
val bannerTextDataList = server.calendar.mapIndexed { index, serverCalendarEntity ->
val diff = abs(nowTime - serverCalendarEntity.getTime() * 1000L)
if (diff < timeDiff || index == 0) {
timeDiff = diff
closestIndex = index
}
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
contentTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
return@with
val serverTime =
if (TimeUtils.isToday(serverCalendarEntity.getTime()))
serverCalendarEntity.getFormatTime("今天 HH:mm")
else if (TimeUtils.isTomorrow(serverCalendarEntity.getTime()))
serverCalendarEntity.getFormatTime("明天 HH:mm")
else
serverCalendarEntity.getFormatTime("MM-dd HH:mm")
TextBannerView.BannerTextData("${serverTime ?: ""} ${serverCalendarEntity.type}")
}
contentBannerView.setDataList(bannerTextDataList, closestIndex)
contentBannerView.startBannerLoop()
}
root.tag = contentCardEntity.id
cardBackground?.colorTransparent?.let {
root.setRoundedColorBackgroundByColorInt(
it.RGBAToARGB().hexStringToIntColor(),
8F
)
}
titleTv.text = contentCardEntity.title
ImageUtils.display(iconIv, contentCardEntity.icon)
redDotTv.goneIf(!((contentCardEntity.link.type == TYPE_SERVER && contentCardEntity.server?.total != 0) || contentCardEntity.link.type == TYPE_GIFT)) {
if ((contentCardEntity.link.type == TYPE_SERVER) && (contentCardEntity.server?.calendar?.isNotEmpty() == true))
redDotTv.text = contentCardEntity.server?.total.toString()
if ((contentCardEntity.link.type == TYPE_GIFT) && (contentCardEntity.libao != null))
redDotTv.text = contentCardEntity.libao?.total.toString()
}
val showContentTv =
contentCardEntity.showDes && (contentCardEntity.des.isNotEmpty() || contentCardEntity.link.type == TYPE_GIFT)
contentTv.visibleIf(showContentTv) {
contentTv.text =
if (contentCardEntity.link.type == TYPE_GIFT && contentCardEntity.des.isEmpty()) "${contentCardEntity.libao?.total}个游戏礼包" else contentCardEntity.des
contentTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
}
val showNewTag = contentCardEntity.link.type == TYPE_ARCHIVE && contentCardEntity.archive != null && contentCardEntity.showNewTag
newIv.goneIf(!showNewTag)
val showContentBannerView =
contentCardEntity.showDes && contentCardEntity.link.type == TYPE_SERVER && contentCardEntity.server != null && contentCardEntity.des.isEmpty()
contentBannerView.goneIf(!showContentBannerView) {
val server = contentCardEntity.server ?: return@goneIf
val nowTime = System.currentTimeMillis()
var timeDiff = 0L
var closestIndex = 0
val bannerTextDataList = server.calendar.mapIndexed { index, serverCalendarEntity ->
val diff = abs(nowTime - serverCalendarEntity.getTime() * 1000L)
if (diff < timeDiff || index == 0) {
timeDiff = diff
closestIndex = index
containerView.setOnClickListener {
NewFlatLogUtils.logGameDetailGameContentCardClick(
contentCardEntity.title ?: "",
viewModel.game?.name ?: "",
viewModel.game?.id ?: "",
contentCardEntity.link.type ?: "",
contentCardEntity.link.link ?: "",
contentCardEntity.link.text ?: ""
)
SensorsBridge.trackGameDetailModuleClick(
gameId = trackData.gameId,
gameName = trackData.gameName,
gameType = trackData.gameType,
contentType = "组件内容",
moduleType = trackData.moduleType,
moduleName = trackData.moduleName,
sequence = trackData.sequence,
linkText = contentCardEntity.link.text ?: "",
linkType = contentCardEntity.link.type ?: "",
linkId = contentCardEntity.id,
gameStatus = getGameStatus()
)
val dialog = contentCardEntity.dialog
if (dialog != null) {// 展示内容卡片提示弹窗
DialogHelper.showDialog(
context = context,
title = dialog.title ?: "",
content = dialog.body ?: "",
confirmText = context.getString(com.gh.gamecenter.common.R.string.confirm),
cancelText = context.getString(com.gh.gamecenter.common.R.string.cancel),
confirmClickCallback = {
jumpToContentCardLink(context, contentCardEntity, viewModel)
}
val serverTime =
if (TimeUtils.isToday(serverCalendarEntity.getTime()))
serverCalendarEntity.getFormatTime("今天 HH:mm")
else if (TimeUtils.isTomorrow(serverCalendarEntity.getTime()))
serverCalendarEntity.getFormatTime("明天 HH:mm")
else
serverCalendarEntity.getFormatTime("MM-dd HH:mm")
TextBannerView.BannerTextData("${serverTime ?: ""} ${serverCalendarEntity.type}")
}
contentBannerView.setDataList(bannerTextDataList, closestIndex)
contentBannerView.startBannerLoop()
}
val showRedDot = contentCardEntity.redDot != 0 && viewModel.shouldShowContentCardRedDot(contentCardEntity)
redDotTv.goneIf(!showRedDot) {
redDotTv.text = contentCardEntity.redDot.toString()
}
val showNewTag = contentCardEntity.link.type == TYPE_ARCHIVE && contentCardEntity.archive != null && contentCardEntity.showNewTag
newIv.goneIf(!showNewTag)
root.setOnClickListener {
NewFlatLogUtils.logGameDetailGameContentCardClick(
contentCardEntity.title ?: "",
viewModel.game?.name ?: "",
viewModel.game?.id ?: "",
contentCardEntity.link.type ?: "",
contentCardEntity.link.link ?: "",
contentCardEntity.link.text ?: ""
)
SensorsBridge.trackGameDetailModuleClick(
gameId = trackData.gameId,
gameName = trackData.gameName,
gameType = trackData.gameType,
contentType = "组件内容",
moduleType = trackData.moduleType,
moduleName = trackData.moduleName,
sequence = trackData.sequence,
linkText = contentCardEntity.link.text ?: "",
linkType = contentCardEntity.link.type ?: "",
linkId = contentCardEntity.id,
gameStatus = getGameStatus()
)
val dialog = contentCardEntity.dialog
if (dialog != null) {// 展示内容卡片提示弹窗
DialogHelper.showDialog(
context = context,
title = dialog.title ?: "",
content = dialog.body ?: "",
confirmText = context.getString(com.gh.gamecenter.common.R.string.confirm),
cancelText = context.getString(com.gh.gamecenter.common.R.string.cancel),
confirmClickCallback = {
jumpToContentCardLink(context, contentCardEntity, viewModel, redDotTv)
}
)
} else {
jumpToContentCardLink(context, contentCardEntity, viewModel, redDotTv)
}
} else {
jumpToContentCardLink(context, contentCardEntity, viewModel)
}
}
viewModel.contentCardClickedLiveData.observe(lifecycleOwner) {
if (it.peekContent() == contentCardEntity.link.type) {
it.getContentWithHandled()?.let { root.performClick() }
}
viewModel.contentCardClickedLiveData.observe(lifecycleOwner) {
if (it.peekContent() == contentCardEntity.link.type) {
it.getContentWithHandled()?.let { containerView.performClick() }
}
}
}
fun jumpToContentCardLink(context: Context, contentCardEntity: ContentCardEntity, viewModel: GameDetailViewModel, redDotTv: TextView) {
private fun jumpToContentCardLink(context: Context, contentCardEntity: ContentCardEntity, viewModel: GameDetailViewModel) {
val path = "游戏详情->内容卡片"
when (contentCardEntity.link.type) {
TYPE_GIFT,
TYPE_ARCHIVE -> {
if (contentCardEntity.link.type == TYPE_GIFT) {
viewModel.markContentCardRedDot(contentCardEntity)
redDotTv.isVisible = false
}
val type = if (contentCardEntity.link.type == TYPE_GIFT) GameDetailTabEntity.TYPE_GIFT else GameDetailTabEntity.TYPE_ARCHIVE
val tabList = viewModel.gameDetailTabListLiveData.value?.data
if (tabList?.find { it.type == type } != null) {
@ -212,8 +207,6 @@ class GameDetailContentCardSingleItemViewHolder(
TYPE_SERVER -> {
if (viewModel.game != null && contentCardEntity.server != null) {
viewModel.markContentCardRedDot(contentCardEntity)
redDotTv.isVisible = false
context.startActivity(
ServersCalendarActivity.getIntent(
context,

View File

@ -1,15 +1,10 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import androidx.lifecycle.LifecycleOwner
import com.gh.common.util.NewFlatLogUtils
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.ItemGameDetailContentCardTripleBinding
import com.gh.gamecenter.databinding.LayoutGameDetailContentCardSmallBinding
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.detail.viewholder.GameDetailContentCardSingleItemViewHolder.Companion.jumpToContentCardLink
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity.Companion.TYPE_ARCHIVE
import com.gh.gamecenter.gamedetail.detail.viewholder.GameDetailContentCardDoubleItemViewHolder.Companion.bindContentCard
import com.gh.gamecenter.gamedetail.entity.GameDetailData
class GameDetailContentCardTripleItemViewHolder(
@ -22,16 +17,15 @@ class GameDetailContentCardTripleItemViewHolder(
super.bindView(data)
data.linkContentCard?.let {
val getGameStatus = { gameStatus }
GameDetailContentCardSingleItemViewHolder.bindContentCard(
bindContentCard(
lifecycleOwner,
viewModel,
it.first(),
data.cardBackground,
binding.firstCardContainer,
baseTrackData.apply { supSequence = 0 },
getGameStatus
)
bindSmallContentCard(
bindContentCard(
lifecycleOwner,
viewModel,
it[1].apply { showDes = false },
@ -39,7 +33,7 @@ class GameDetailContentCardTripleItemViewHolder(
baseTrackData.apply { supSequence = 1 },
getGameStatus
)
bindSmallContentCard(
bindContentCard(
lifecycleOwner,
viewModel,
it[2].apply { showDes = false },
@ -49,83 +43,4 @@ class GameDetailContentCardTripleItemViewHolder(
)
}
}
companion object {
fun bindSmallContentCard(
lifecycleOwner: LifecycleOwner,
viewModel: GameDetailViewModel,
contentCardEntity: ContentCardEntity,
itemBinding: LayoutGameDetailContentCardSmallBinding,
trackData: GameDetailModuleTrackData,
getGameStatus: () -> String
) {
with(itemBinding) {
val context = root.context
if (contentCardEntity.id == root.tag) {
root.background = com.gh.gamecenter.common.R.drawable.bg_shape_f8_radius_8.toDrawable(context)
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
return@with
}
root.tag = contentCardEntity.id
titleTv.text = contentCardEntity.title
ImageUtils.display(iconIv, contentCardEntity.icon)
val showRedDot = contentCardEntity.redDot != 0 && viewModel.shouldShowContentCardRedDot(contentCardEntity)
redDotTv.goneIf(!showRedDot) {
redDotTv.text = contentCardEntity.redDot.toString()
}
val showNewTag = contentCardEntity.link.type == TYPE_ARCHIVE && contentCardEntity.archive != null && contentCardEntity.showNewTag
newIv.goneIf(!showNewTag)
root.setOnClickListener {
NewFlatLogUtils.logGameDetailGameContentCardClick(
contentCardEntity.title ?: "",
viewModel.game?.name ?: "",
viewModel.game?.id ?: "",
contentCardEntity.link.type ?: "",
contentCardEntity.link.link ?: "",
contentCardEntity.link.text ?: ""
)
SensorsBridge.trackGameDetailModuleClick(
gameId = trackData.gameId,
gameName = trackData.gameName,
gameType = trackData.gameType,
contentType = "组件内容",
moduleType = trackData.moduleType,
moduleName = trackData.moduleName,
sequence = trackData.sequence,
linkText = contentCardEntity.link.text ?: "",
linkType = contentCardEntity.link.type ?: "",
linkId = contentCardEntity.id,
gameStatus = getGameStatus()
)
val dialog = contentCardEntity.dialog
if (dialog != null) {// 展示内容卡片提示弹窗
DialogHelper.showDialog(
context = context,
title = dialog.title ?: "",
content = dialog.body ?: "",
confirmText = context.getString(com.gh.gamecenter.common.R.string.confirm),
cancelText = context.getString(com.gh.gamecenter.common.R.string.cancel),
confirmClickCallback = {
jumpToContentCardLink(context, contentCardEntity, viewModel, redDotTv)
}
)
} else {
jumpToContentCardLink(context, contentCardEntity, viewModel, redDotTv)
}
}
viewModel.contentCardClickedLiveData.observe(lifecycleOwner) {
if (it.peekContent() == contentCardEntity.link.type) {
it.getContentWithHandled()?.let { root.performClick() }
}
}
}
}
}
}

View File

@ -27,7 +27,7 @@ class GameDetailDeveloperWordItemViewHolder(
.fromHtmlCompat(PicassoImageGetter(contentTv), ExtraTagHandler())
)
contentTv.post {
expandTv.isVisible = (contentTv.lineCount == 3 && (contentTv.layout?.getEllipsisCount(2) ?: 0) > 0) || contentTv.lineCount > 3
expandTv.isVisible = (contentTv.lineCount == 3 && contentTv.layout.getEllipsisCount(2) > 0) || contentTv.lineCount > 3
}
expandTv.background = R.drawable.bg_ui_surface_expand_gradient.toDrawable(context)
expandTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))

View File

@ -36,7 +36,7 @@ class GameDetailUpdateItemViewHolder(
}
contentTv.text = entity.updateDes
contentTv.post {
expandTv.isVisible = contentTv.lineCount == 3 && (contentTv.layout?.getEllipsisCount(2) ?: 0) > 0
expandTv.isVisible = contentTv.lineCount == 3 && contentTv.layout.getEllipsisCount(2) > 0
}
expandTv.background = R.drawable.bg_ui_surface_expand_gradient.toDrawable(context)
expandTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))

View File

@ -1,176 +0,0 @@
package com.gh.gamecenter.gamedetail.dialog
import android.content.Context
import android.os.Bundle
import android.view.MotionEvent
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import com.gh.common.util.DetailDownloadUtils
import com.gh.common.util.DirectUtils
import com.gh.download.DownloadManager
import com.gh.gamecenter.R
import com.gh.gamecenter.adapter.viewholder.DetailViewHolder
import com.gh.gamecenter.common.base.fragment.BaseBottomDialogFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.core.utils.CurrentActivityHolder
import com.gh.gamecenter.databinding.DialogFragmentDspGameDetailBinding
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.feature.entity.GameEntity
import com.lightgame.download.DataWatcher
import com.lightgame.download.DownloadEntity
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import org.json.JSONObject
class DspGameDetailDialogFragment : BaseBottomDialogFragment<DialogFragmentDspGameDetailBinding>() {
private var gameEntity: GameEntity? = null
private val dataWatcher = object : DataWatcher() {
override fun onDataChanged(downloadEntity: DownloadEntity) {
updateDownloadStatus()
}
override fun onDataInit(downloadEntity: DownloadEntity) {
updateDownloadStatus()
}
}
private fun updateDownloadStatus() {
gameEntity?.let {
DetailDownloadUtils.updateViewHolder(
DetailViewHolder(
mBinding.detailLlBottom,
null,
it,
false,
"",
"",
"",
null,
false,
null,
null
)
)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
gameEntity = arguments?.getParcelable(EntranceConsts.KEY_GAME_ENTITY)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
gameEntity?.let {
val apk = it.getApk().firstOrNull()
mBinding.ivIcon.displayGameIcon(it)
mBinding.tvName.text = it.name
mBinding.tvVersion.text = getString(R.string.version_name, apk?.version ?: "")
mBinding.titleView.setOnRightClickListener { dismiss() }
mBinding.tvDeveloper.text = getString(R.string.developer_name, it.developer ?: "")
mBinding.tvPermissions.setOnClickListener {
logClickEvent("游戏详情-应用权限")
DirectUtils.directToWebView(
context = requireContext(),
url = gameEntity?.permissionsUrl ?: "",
title = "应用权限")
}
mBinding.tvPrivacyPolicy.setOnClickListener {
logClickEvent("游戏详情-隐私政策")
DirectUtils.directToWebView(
context = requireContext(),
url = gameEntity?.privacyUrl ?: "",
title = "隐私政策"
)
}
mBinding.detailProgressbar.goneIf(apk == null)
if (apk != null) {
mBinding.detailProgressbar.setOnTouchListener { _, motionEvent ->
if (motionEvent.action == MotionEvent.ACTION_DOWN) {
logClickEvent("游戏详情-按钮")
}
return@setOnTouchListener false
}
DetailDownloadUtils.updateViewHolder(
DetailViewHolder(
mBinding.detailLlBottom,
null,
it,
false,
"",
"",
"",
null,
false,
null
)
)
}
}
}
override fun onResume() {
super.onResume()
DownloadManager.getInstance().addObserver(dataWatcher)
}
override fun onPause() {
super.onPause()
DownloadManager.getInstance().removeObserver(dataWatcher)
}
override fun onDestroyView() {
super.onDestroyView()
logClickEvent("游戏详情-关闭弹窗")
}
private fun logClickEvent(text: String) {
gameEntity?.let {
val dspLogMap = it.tempDspLogMap as? HashMap<String, String>
dspLogMap?.let {
dspLogMap["text"] = text
SensorsBridge.trackDspAdClick(JSONObject(dspLogMap as Map<*, *>?))
}
}
}
// 接受安装、卸载消息
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(busFour: EBPackage) {
if (gameEntity != null && gameEntity!!.getApk().size > 0) {
for (apk in gameEntity!!.getApk()) {
if (busFour.packageName == apk.packageName) {
updateDownloadStatus()
}
}
}
}
companion object {
fun show(context: Context, gameEntity: GameEntity) {
if (context is AppCompatActivity) {
context.supportFragmentManager
} else {
(CurrentActivityHolder.getCurrentActivity() as? AppCompatActivity)?.supportFragmentManager
}?.let {
val fragment = DspGameDetailDialogFragment().apply {
arguments = Bundle().apply {
putParcelable(EntranceConsts.KEY_GAME_ENTITY, gameEntity)
}
}
fragment.show(it, fragment::class.java.simpleName)
}
}
}
}

View File

@ -9,29 +9,19 @@ import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.common.base.fragment.BaseBottomDialogFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.databinding.DialogGameDetailRecyclerViewBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.gamedetail.entity.GameDetailInfoTag
import com.lightgame.utils.AppManager
class GameFunctionDialogFragment: BaseBottomDialogFragment<DialogGameDetailRecyclerViewBinding>() {
private var infoTags: List<GameDetailInfoTag.InfoTag> = arrayListOf()
private var linkEntity: LinkEntity? = null
private var gameEntity: GameEntity? = null
private val adapter by lazy { GameFunctionAdapter(requireContext(), infoTags) }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
gameEntity = arguments?.getParcelable(EntranceConsts.KEY_GAME_ENTITY)
infoTags = arguments?.getParcelableArrayList(KEY_INFO_TAG) ?: arrayListOf()
linkEntity = arguments?.getParcelable(EntranceConsts.KEY_LINK)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -40,28 +30,6 @@ class GameFunctionDialogFragment: BaseBottomDialogFragment<DialogGameDetailRecyc
mBinding.closeIv.setOnClickListener {
dismissAllowingStateLoss()
}
mBinding.subtitleTv.goneIf(linkEntity == null) {
mBinding.subtitleTv.text = linkEntity?.text
mBinding.subtitleTv.setOnClickListener { _ ->
linkEntity?.let {
DirectUtils.directToLinkPage(requireContext(), it, "游戏详情-功能说明", "")
SensorsBridge.trackGameDetailModuleClick(
gameEntity?.id,
gameEntity?.name,
gameEntity?.categoryChinese,
"组件内容",
"功能标签",
"功能标签",
null,
subModuleName = "跳转入口",
linkType = it.type,
linkId = it.link,
linkText = it.text,
gameStatus = null
)
}
}
}
mBinding.recyclerView.run {
layoutManager = LinearLayoutManager(requireContext())
adapter = this@GameFunctionDialogFragment.adapter
@ -78,7 +46,7 @@ class GameFunctionDialogFragment: BaseBottomDialogFragment<DialogGameDetailRecyc
const val KEY_INFO_TAG = "info_tag"
@JvmStatic
fun show(context: Context?, gameEntity: GameEntity?, infoTags: List<GameDetailInfoTag.InfoTag>, linkEntity: LinkEntity?) {
fun show(context: Context?, infoTags: List<GameDetailInfoTag.InfoTag>) {
val fragmentActivity: FragmentActivity = if (context is FragmentActivity) {
context
} else if (BuildConfig.DEBUG) {
@ -97,9 +65,7 @@ class GameFunctionDialogFragment: BaseBottomDialogFragment<DialogGameDetailRecyc
val dialogFragment = GameFunctionDialogFragment()
dialogFragment.arguments = bundleOf(
EntranceConsts.KEY_GAME_ENTITY to gameEntity,
KEY_INFO_TAG to infoTags,
EntranceConsts.KEY_LINK to linkEntity
KEY_INFO_TAG to infoTags
)
dialogFragment.show(fragmentActivity.supportFragmentManager, GameFunctionDialogFragment::class.java.name)
}

View File

@ -31,13 +31,6 @@ class ContentCardEntity(
var showNewTag: Boolean = false,
) {
val redDot
get() = when (link.type) {
TYPE_SERVER -> server?.total ?: 0
TYPE_GIFT -> libao?.total ?: 0
else -> 0
}
@Keep
class Dialog(
@SerializedName("_id")

View File

@ -29,8 +29,6 @@ data class GameDetailData(
val linkInfoTag: GameDetailInfoTag? = null,
@SerializedName("link_content_card")
val linkContentCard: List<ContentCardEntity>? = null,
@SerializedName("card_background")
val cardBackground: CardBackground? = null,
@SerializedName("link_advertising")
val linkAdvertising: GameDetailLink? = null,
@SerializedName("link_comprehensive")
@ -138,13 +136,6 @@ data class GameDetailData(
}
}
data class CardBackground(
val color: String = "",
val transparent: String = "",
@SerializedName("color-transparent")
val colorTransparent: String = "", // RGBA格式
)
data class Order(
@SerializedName("before_download")
val beforeDownload: Int = -1, // 下载前排序
@ -219,9 +210,7 @@ data class GameDetailInfoTag(
@SerializedName("info_tags")
val infoTags: List<InfoTag> = listOf(), // 功能标签
@SerializedName("request_speed_status")
val requestSpeedStatus: String = "", // 求加速状态, on/off
@SerializedName("plugin_tutorial_link")
val link: LinkEntity? = null, // 插件教程链接
val requestSpeedStatus: String = "" // 求加速状态, on/off
) {
@Parcelize
data class InfoTag(

View File

@ -63,7 +63,7 @@ class HistoryApkListAdapter(
holder.binding.expandTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(mContext))
holder.binding.updateDescTv.text = apkEntity.updateDesc
holder.binding.updateDescTv.post {
holder.binding.expandTv.isVisible = holder.binding.updateDescTv.lineCount == 3 && (holder.binding.updateDescTv.layout?.getEllipsisCount(2) ?: 0) > 0
holder.binding.expandTv.isVisible = holder.binding.updateDescTv.lineCount == 3 && holder.binding.updateDescTv.layout.getEllipsisCount(2) > 0
}
holder.binding.versionTv.text = "版本${apkEntity.version}"
holder.binding.releaseDateTv.text = TimeUtils.getFormatTime(apkEntity.updateTime)

View File

@ -30,9 +30,7 @@ import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.CustomLinkMovementMethod
import com.gh.gamecenter.common.view.DrawableView
import com.gh.gamecenter.core.utils.CenterImageSpan
import com.gh.gamecenter.core.utils.NumberUtils
import com.gh.gamecenter.core.utils.SpanBuilder
import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.databinding.RatingCommentItemBinding
import com.gh.gamecenter.entity.RatingComment
import com.gh.gamecenter.feature.entity.GameEntity
@ -251,7 +249,7 @@ class RatingCommentItemViewHolder(val binding: RatingCommentItemBinding, val pat
if (game.getApk().size > 0 && game.getApk()[0].version == commentData.gameVersion) {
version.text = "当前版本"
} else {
version.text = commentData.gameVersion
version.text = ("版本:" + commentData.gameVersion)
}
}
}

View File

@ -7,7 +7,6 @@ import android.text.Spanned
import android.text.TextPaint
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
@ -16,7 +15,6 @@ import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.content.ContextCompat
import androidx.core.view.updateLayoutParams
import com.gh.common.util.*
import com.gh.common.util.NewFlatLogUtils
import com.gh.common.util.NewLogUtils
@ -29,8 +27,7 @@ import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.DrawableView
import com.gh.gamecenter.core.utils.NumberUtils
import com.gh.gamecenter.core.utils.SpanBuilder
import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.databinding.ItemArticleDetailCommentBinding
import com.gh.gamecenter.entity.RatingComment
import com.gh.gamecenter.gamedetail.rating.edit.RatingEditActivity
@ -168,36 +165,7 @@ class RatingDetailCommentItemViewHolder(val binding: ItemArticleDetailCommentBin
if (game != null && game.getApk().size > 0 && game.getApk()[0].version == commentData.gameVersion) {
version.text = "当前版本"
} else {
version.text = commentData.gameVersion
}
version.buttonDrawable = R.drawable.ic_version.toDrawable(context)
version.post {
ConstraintSet().apply {
clone(bottomContainer)
if ((version.layout?.lineCount ?: 1) > 1) {
version.gravity = Gravity.TOP
connect(version.id, ConstraintSet.START, device.id, ConstraintSet.START)
connect(version.id, ConstraintSet.TOP, device.id, ConstraintSet.BOTTOM, 6F.dip2px())
connect(version.id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM, 16F.dip2px())
connect(device.id, ConstraintSet.BOTTOM, version.id, ConstraintSet.TOP)
connect(device.id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP, 16F.dip2px())
clear(device.id, ConstraintSet.END)
clear(likeCountTv.id, ConstraintSet.TOP)
connect(likeCountTv.id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM, 4F.dip2px())
bottomContainer.updateLayoutParams<ConstraintLayout.LayoutParams> { height = ConstraintLayout.LayoutParams.WRAP_CONTENT }
} else {
version.gravity = Gravity.CENTER_VERTICAL
connect(version.id, ConstraintSet.START, device.id, ConstraintSet.END)
connect(version.id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP, 0)
connect(version.id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM, 0)
connect(device.id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM)
connect(device.id, ConstraintSet.END, version.id, ConstraintSet.START)
connect(device.id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP, 0)
connect(likeCountTv.id, ConstraintSet.TOP, timeTv.id, ConstraintSet.TOP, 0)
connect(likeCountTv.id, ConstraintSet.BOTTOM, timeTv.id, ConstraintSet.BOTTOM, 0)
bottomContainer.updateLayoutParams<ConstraintLayout.LayoutParams> { height = 48F.dip2px() }
}
}.applyTo(bottomContainer)
version.text = ("版本:" + commentData.gameVersion)
}
if (commentData.me.isCommented) {

View File

@ -50,7 +50,6 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS
private var mLastGetContentLengthTime = 0L
var showOrHideCoverFilter: ((Boolean) -> Unit)? = null
var scrollCalculatorHelper: GameDetailScrollCalculatorHelper? = null
init {
post {
@ -257,7 +256,6 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS
// 不需要弹弹窗,直接播放
override fun showWifiDialog() {
scrollCalculatorHelper?.currentPlayer = this
startPlayLogic(false)
//val trafficVideo = PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SettingsFragment.TRAFFIC_VIDEO_SP_KEY, false)
//if (trafficVideo) {

View File

@ -62,7 +62,6 @@ import com.gh.gamecenter.feature.exposure.ExposureType
import com.gh.gamecenter.feature.minigame.MiniGameItemHelper
import com.gh.gamecenter.feature.utils.SentryHelper
import com.gh.gamecenter.game.commoncollection.detail.CustomCommonCollectionDetailActivity
import com.gh.gamecenter.gamedetail.dialog.DspGameDetailDialogFragment
import com.gh.gamecenter.gamedetail.rating.RatingReplyActivity
import com.gh.gamecenter.home.PageConfigure
import com.gh.gamecenter.home.custom.adapter.CustomPageAdapter
@ -131,7 +130,6 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable, IB
}
}
override fun onDataInit(downloadEntity: DownloadEntity) {
adapter.notifyDownload(downloadEntity)
}
@ -268,11 +266,7 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable, IB
if (status == LoadStatus.INIT_LOADING) {
binding.reuseLoading.root.goneIf(false)
binding.root.setBackgroundColor(
com.gh.gamecenter.common.R.color.ui_surface.toColor(
requireContext()
)
)
binding.root.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_surface.toColor(requireContext()))
} else {
binding.reuseLoading.root.goneIf(true)
binding.root.setBackgroundColor(Color.TRANSPARENT)
@ -330,27 +324,15 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable, IB
}
gameDetailDestination.observe(viewLifecycleOwner, EventObserver { (entrance, game) ->
when {
game.isDspGame -> {
if (game.interactionType == 2) {
DspGameDetailDialogFragment.show(requireContext(), game)
} else if (game.interactionType == 4) {
MiniGameItemHelper.launchMiniGame(game)
}
}
game.isMiniGame() -> {
MiniGameItemHelper.launchMiniGame(game)
}
else -> {
GameDetailActivity.startGameDetailActivity(
requireContext(),
game.id,
entrance,
traceEvent = game.exposureEvent,
)
}
if (game.isMiniGame()) {
MiniGameItemHelper.launchMiniGame(game)
} else {
GameDetailActivity.startGameDetailActivity(
requireContext(),
game.id,
entrance,
traceEvent = game.exposureEvent,
)
}
})
@ -797,11 +779,7 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable, IB
adapter.notifyItemRangeChanged(0, adapter.itemCount)
binding.gameList.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_surface.toColor(requireContext()))
searchBarBinding?.searchTv?.setHintTextColor(
com.gh.gamecenter.common.R.color.text_instance.toColor(
requireContext()
)
)
searchBarBinding?.searchTv?.setHintTextColor(com.gh.gamecenter.common.R.color.text_instance.toColor(requireContext()))
onScrollChanged(true)
}
}
@ -838,7 +816,6 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable, IB
RefreshState.TwoLevel -> {
setScrollEnabled(false)
}
RefreshState.None -> {
setScrollEnabled(true)
}

View File

@ -86,7 +86,6 @@ class CustomPageViewModel(
addSource(repository.wechatMiniGameCPMItemLiveData, ::notifyWechatMiniGameCPMSubjectItemChanged)
addSource(repository.customRecentAcceleratorItemLiveData, ::notifyItemChanged)
addSource(repository.pkDataListLiveData, ::notifyPKDataChanged)
addSource(repository.dspSubjectLiveData, ::notifyDspSubjectItemChanged)
}
val dataList: LiveData<List<CustomPageItem>> = _dataList
@ -581,43 +580,6 @@ class CustomPageViewModel(
_dataList.value = newData
}
/**
* 通过 dsp 接口异步获取数据插入
*/
private fun notifyDspSubjectItemChanged(result: Pair<String, List<GameEntity>>) {
val (subjectId, gameList) = result
val oldData = dataList.value ?: return
val index = oldData.indexOfFirst {
it is CustomDspPlaceholderItem && subjectId == it.data.id
}
if (index == -1) {
return
}
val subjectItem = (oldData[index] as CustomDspPlaceholderItem).apply {
data.data = gameList.toMutableList()
data.isDspSubject = true
}
val position = subjectItem.position
val componentPosition = subjectItem.componentPosition
val newData = oldData.toMutableList()
val customPageItemList = repository.convertColumnDetailSubjectItems(
position,
componentPosition,
subjectItem.link,
subjectItem.data
)
if (customPageItemList.isEmpty()) return
newData[index] = customPageItemList[0]
newData.addAll(index + 1, customPageItemList.subList(1, customPageItemList.size))
newData.forEachIndexed { pos, customPageItem ->
customPageItem.position = pos
}
repository.notifyPositionChanged(newData.size)
gamePositionAndPackageHelper.clear()
gamePositionAndPackageHelper.putAll(getPositionAndPackageMap(newData))
_dataList.value = newData
}
private fun notifyPKDataChanged(pkList: List<PKEntity>) {
if (pkList.isEmpty()) return

View File

@ -91,8 +91,7 @@ class CustomGamePluginAdapter(
binding.gameRating.textSize =
(if (gameEntity.commentCount > 3) 12 else 10).toFloat()
BindingAdapters.setGameName(binding.gameName, gameEntity, true)
binding.gamePlayCount.visibility = View.GONE
BindingAdapters.setGameTagsWithSellingPoint(binding.labelList, binding.layoutSellingPoints, gameEntity, "")
BindingAdapters.setGameTags(binding.labelList, gameEntity, "")
binding.gameRating.setDrawableStart(
if (gameEntity.commentCount > 3) com.gh.gamecenter.feature.R.drawable.game_horizontal_rating.toDrawable() else null,
null,

View File

@ -133,8 +133,7 @@ class CustomGameRefreshVerticalAdapter(
with(ui) {
gameNameTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
serverTypeTv.setTextColor(com.gh.gamecenter.common.R.color.primary_theme.toColor(context))
downloadTv.background =
com.gh.gamecenter.common.R.drawable.download_button_normal_style.toDrawable(context)
downloadTv.background = com.gh.gamecenter.common.R.drawable.download_button_normal_style.toDrawable(context)
gameDesTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
BindingAdapters.setGameName(
@ -143,7 +142,7 @@ class CustomGameRefreshVerticalAdapter(
false
)
BindingAdapters.setGame(iconIv, gameEntity)
BindingAdapters.setGameTagsWithSellingPoint(gameTagContainer, sellingPointsBinding, gameEntity, "")
BindingAdapters.setGameTags(gameTagContainer, gameEntity, "")
GameItemViewHolder.initServerType(gameNameTv, serverTypeTv, gameEntity)
gameDesTv.text = gameEntity.decoratedDes
GameItemViewHolder.initGameSubtitleAndAdLabel(

View File

@ -468,11 +468,6 @@ class CustomPageAdapter(
} else {
block(viewHolder)
}
} else {
// 当 RecyclerView 中没有找到对应的 ViewHolder 时,尝试调用 notifyItemChanged 来通知更新
// 这可能是因为 ViewHolder 已经被回收或不在屏幕上。在这种情况下,我们仍然希望更新数据
// 但是这并不能保证些什么,因为 ViewHolder 可能已经被销毁
recyclerView?.adapter?.notifyItemChanged(position)
}
}

View File

@ -61,7 +61,9 @@ class CustomRecentAcceleratorAdapter(
SCENE_TYPE_NO_GUIDE_LAYER,
SOURCE_ENTRANCE_RECENTLY_PLAYED
)
val request = AcceleratorValidator.Request(game, SOURCE_ENTRANCE_RECENTLY_PLAYED)
val isVip = acceleratorDataHolder.isVip
val isNewUser = acceleratorDataHolder.isNewUser
val request = AcceleratorValidator.Request(isVip, isNewUser, game, SOURCE_ENTRANCE_RECENTLY_PLAYED)
AcceleratorClient.newInstance()
.execute(context, request, object : AcceleratorValidator.ValidateListener {
override fun finished(context: Context) {
@ -76,7 +78,6 @@ class CustomRecentAcceleratorAdapter(
}
})
}
}
}

View File

@ -6,57 +6,26 @@ import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.home.custom.CustomPageViewModel
import com.gh.gamecenter.home.custom.model.CustomSubjectItem
import com.gh.gamecenter.home.custom.tracker.SubjectTracker
import com.lightgame.utils.Utils
class SubjectEventHelper(viewModel: CustomPageViewModel) : CustomPageItemChildEventHelper(viewModel) {
private val tracker = SubjectTracker(viewModel.pageLocation)
fun navigateToGameDetailPage(position: Int, gameEntity: GameEntity) {
when {
gameEntity.isDspGame -> {
val json = tracker.trackDspGameClick(_item, gameEntity, "广告内容", "自定义页面")
// Parse the JSON string into a JSONObject
if (json != null) {
val map = mutableMapOf<String, String>()
val keys = json.keys() // Returns an Iterator
while (keys.hasNext()) {
val key = keys.next()
try {
val value = json.get(key)
map[key] = value.toString()
} catch (e: Exception) {
Utils.log("Error parsing JSON key: $key, value: ${json.get(key)}")
}
}
gameEntity.tempDspLogMap = map.toMap() // Return an immutable copy
}
}
gameEntity.isMiniGame() -> {
tracker.trackMiniGameClick(_item, gameEntity)
}
else -> {
tracker.trackColumnClick(_item, gameEntity, "游戏")
}
if (gameEntity.isMiniGame()) {
tracker.trackMiniGameClick(_item, gameEntity)
} else {
tracker.trackColumnClick(_item, gameEntity, "游戏")
}
viewModel.navigateToGameDetailPageByGame(_item, position, gameEntity)
}
fun onDownloadButtonClick(childPosition: Int, gameEntity: GameEntity) {
when {
gameEntity.isDspGame -> {
tracker.trackDspGameClick(_item, gameEntity, "按钮", "自定义页面")
}
gameEntity.isMiniGame() -> {
tracker.trackMiniGameClick(_item, gameEntity)
}
else -> {
tracker.trackColumnClick(_item, gameEntity, "按钮")
}
if (gameEntity.isMiniGame()) {
tracker.trackMiniGameClick(_item, gameEntity)
} else {
tracker.trackColumnClick(_item, gameEntity, "按钮")
}
}
@ -65,6 +34,7 @@ class SubjectEventHelper(viewModel: CustomPageViewModel) : CustomPageItemChildEv
(_item as? CustomSubjectItem)?.data?.let {
viewModel.navigateSubjectPage(_item, it)
}
}
fun onChangeABatch(subject: SubjectEntity) {
@ -82,4 +52,5 @@ class SubjectEventHelper(viewModel: CustomPageViewModel) : CustomPageItemChildEv
viewModel.navigateSubjectPage(_item, subject)
}
}

View File

@ -82,8 +82,6 @@ class CustomPageData(
val linkQqGameRecentlyPlayed: LinkRecentlyPlayed? = null,
@SerializedName("link_accelerator_recently_played")
val linkAcceleratorRecentlyPlayed: LinkRecentlyPlayed? = null,
@SerializedName("link_dsp_game_column")
val linkDspGameColumn: SubjectEntity? = null,
var linkPK: PKEntity? = null,
) {
// 游戏专题

View File

@ -96,7 +96,6 @@ abstract class CustomPageItem(
const val CUSTOM_LINK_TYPE_COMMON_COLLECTION = "common_collection"
const val CUSTOM_LINK_TYPE_TOP_GAME_COMMENT = "top_game_comment"
const val CUSTOM_LINK_TYPE_PK = "pk"
const val CUSTOM_LINK_TYPE_DSP_GAME_COLUMN = "dsp_game_column"
// 游戏专题
const val COMPONENTS_GAME_SUBJECT_TYPE_DOUBLE_CARD = "game_double_card"
@ -480,12 +479,7 @@ data class CustomSplitSubjectItem(
get() = (startChildPosition + step) >= (data.data?.size ?: 0)
val exposureSource: List<ExposureSource>
get() = listOf(
ExposureSource(
if (data.isDspSubject) "dsp广告专题" else "专题",
"${data.name ?: ""}+${componentStyle}+${data.id}"
)
)
get() = listOf(ExposureSource("专题", "${data.name ?: ""}+${componentStyle}+${data.id}"))
override fun doAreItemsTheSame(other: CustomPageItem): Boolean {
return other is CustomSplitSubjectItem
@ -912,24 +906,6 @@ data class CustomPKItem(
}
}
data class CustomDspPlaceholderItem(
private val _link: LinkEntity,
val data: SubjectEntity,
private val _position: Int,
private val _componentPosition: Int
) : CustomPageItem(_link, _position, _componentPosition) {
override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_MINI_GAME_RECENT_PLAY
override fun doAreContentsTheSames(other: CustomPageItem): Boolean {
return other is CustomDspPlaceholderItem
&& data == other.data
&& position == other.position
&& componentPosition == other.componentPosition
}
}
object CustomFooterItem : CustomPageItem(LinkEntity(), -1, -1) {
override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_FOOTER

View File

@ -12,19 +12,23 @@ import com.gh.download.DownloadManager
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.entity.PKEntity
import com.gh.gamecenter.common.livedata.NonStickyMutableLiveData
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.TextHelper
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.common.utils.sp2px
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.dsp.data.GameSubjectDSPRemoteDataSource
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.entity.DiscoveryCardEntity
import com.gh.gamecenter.entity.GameUpdateEntity
import com.gh.gamecenter.entity.PullDownPush
import com.gh.gamecenter.entity.SubjectEntity
import com.gh.gamecenter.feature.entity.*
import com.gh.gamecenter.entity.SubjectEntity.Companion.SUBJECT_TAG_SELLING_POINT
import com.gh.gamecenter.entity.SubjectEntity.Companion.SUBJECT_TAG_UPDATE
import com.gh.gamecenter.feature.entity.FloatingWindowEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.PluginLocation
import com.gh.gamecenter.feature.entity.TagStyleEntity
import com.gh.gamecenter.feature.utils.ApkActiveUtils
import com.gh.gamecenter.gamedetail.rating.edit.RatingEditActivity
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_BANNER
@ -51,14 +55,13 @@ import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMPONENTS_S
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMPONENTS_SUBJECT_TYPE_GAME_TIMELINE_HORIZONTAL_SLIDE
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMPONENTS_SUBJECT_TYPE_GAME_VERTICAL_IMAGE_TEXT
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMPONENTS_SUBJECT_TYPE_GAME_VERTICAL_SLIDE
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_LINK_TYPE_ACCELERATOR_RECENTLY_PLAYED
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_LINK_TYPE_COLUMN
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_LINK_TYPE_DSP_GAME_COLUMN
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_LINK_TYPE_GAME
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_LINK_TYPE_HALO_RECOMMEND
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_LINK_TYPE_PK
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_LINK_TYPE_QQ_GAME_RECENTLY_PLAYED
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_LINK_TYPE_QQ_MINI_GAME_COLUMN_DETAIL
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_LINK_TYPE_ACCELERATOR_RECENTLY_PLAYED
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_LINK_TYPE_WECHAT_GAME_RECENTLY_PLAYED
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_LINK_TYPE_WECHAT_MINI_GAME_COLUMN_DETAIL
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_LINK_TYPE_WECHAT_WECHAT_GAME_CPM_COLUMN_DETAIL
@ -72,18 +75,17 @@ import com.gh.gamecenter.wrapper.MainWrapperRepository
import com.gh.vspace.VHelper
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import okhttp3.ResponseBody
import java.util.concurrent.TimeUnit
import java.util.concurrent.atomic.AtomicInteger
import java.util.regex.Pattern
class CustomPageRepository private constructor(
private val remoteDataSource: CustomPageRemoteDataSource,
private val localDataSource: CustomPageLocalDataSource,
private val wGameSubjectCPMRemoteDataSource: WGameSubjectCPMRemoteDataSource,
private val wGameSubjectCPMRemoteDataSource: WGameSubjectCPMRemoteDataSource
) {
private val pageInfo = PageInfo()
@ -96,8 +98,6 @@ class CustomPageRepository private constructor(
WGameSubjectCPMListRepository(wGameSubjectCPMRemoteDataSource)
)
private val dspSubjectUseCase = GameSubjectDSPRemoteDataSource(RetrofitManager.getInstance().dspApiService)
private var recentItem: CustomRecentGamesItem? = null
private var recentQQItem: CustomRecentQqMiniGamesItem? = null
@ -168,9 +168,6 @@ class CustomPageRepository private constructor(
val pkDataListLiveData = MutableLiveData<List<PKEntity>>()
private val _dspGameList = NonStickyMutableLiveData<Pair<String, List<GameEntity>>>()
val dspSubjectLiveData: LiveData<Pair<String, List<GameEntity>>> = _dspGameList
private var lastComponentType = ""
private var lastSubjectId = ""
private var gameChildPosition = 0
@ -289,6 +286,8 @@ class CustomPageRepository private constructor(
// 如果当前item组件id不等于上一个item的组件id则说明这是展开大图的第一个item
if (lastSubjectId != item.linkColumn?.id) {
gameChildPosition = 0
}
// 如果当前item为展开大图并且上一个item也是展开大图但是组件id不同则说明这是一个新的并且连续的展开大图组件
if (lastComponentType == item.link.type && lastSubjectId != item.linkColumn?.id) {
@ -342,6 +341,7 @@ class CustomPageRepository private constructor(
pageInfo.positionIncrement()
pageInfo.componentPositionIncrement()
}
}
// 微信小游戏-最近在玩
@ -373,34 +373,6 @@ class CustomPageRepository private constructor(
}
}
// DSP 专题
item.link.type == CUSTOM_LINK_TYPE_DSP_GAME_COLUMN -> {
item.linkDspGameColumn?.let { subject ->
list.add(
CustomDspPlaceholderItem(
item.link,
subject,
pageInfo.position,
pageInfo.componentPosition
)
)
dspSubjectUseCase.getDspGames(subject.columnType, subject.size.index)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BiResponse<List<GameEntity>>() {
override fun onSuccess(data: List<GameEntity>) {
for (game in data) {
game.subjectId = subject.id
game.subjectName = subject.name
}
_dspGameList.value = subject.id!! to data
}
})
pageInfo.positionIncrement()
pageInfo.componentPositionIncrement()
}
}
item.linkColumnCollection != null -> {
// 专题合集
val linkColumnCollection = item.linkColumnCollection

View File

@ -1,9 +1,7 @@
package com.gh.gamecenter.home.custom.tracker
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.dsp.DspReportHelper
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.PageLocation
import com.gh.gamecenter.home.custom.model.CustomGameItem
@ -11,7 +9,6 @@ import com.gh.gamecenter.home.custom.model.CustomPageItem
import com.gh.gamecenter.home.custom.model.CustomSplitSubjectItem
import com.gh.gamecenter.home.custom.model.CustomSubjectItem
import com.gh.gamecenter.feature.minigame.MiniGameItemHelper
import org.json.JSONObject
class SubjectTracker(private val pageLocation: PageLocation) {
@ -88,45 +85,4 @@ class SubjectTracker(private val pageLocation: PageLocation) {
game?.name ?: ""
)
}
fun trackDspGameClick(
item: CustomPageItem,
game: GameEntity,
text: String,
location: String
): JSONObject? {
val subject = (item as? CustomSplitSubjectItem)?.data
?: (item as? CustomSubjectItem)?.data
?: return null
// 先尝试上报 show 再上报 click保证先 show 后 click (已在 report 方法中进行简单去重处理)
DspReportHelper.report(game.showUrl)
DspReportHelper.report(game.clickUrl)
val json = SensorsBridge.trackDspAdClick(
pageId = pageLocation.pageId,
pageBusinessId = GlobalActivityManager.getCurrentPageEntity().pageBusinessId,
lastPageId = GlobalActivityManager.getLastPageEntity().pageId,
lastPageName = GlobalActivityManager.getLastPageEntity().pageName,
lastPageBusinessId = GlobalActivityManager.getLastPageEntity().pageBusinessId,
location = location,
bottomTab = pageLocation.bottomTab,
severalTabPageName = pageLocation.severalTabPageName,
severalTabPageId = pageLocation.severalTabPageId,
position = pageLocation.tabPosition,
tabContent = pageLocation.tabContent,
customPageName = pageLocation.pageName,
customPageId = pageLocation.pageId,
gameColumnName = subject.name ?: "",
gameColumnId = subject.id ?: "",
searchContent = "",
adId = game.dspAdId ?: "",
interactionType = game.interactionType.toString(),
wechatAppid = game.miniGameAppId,
gameName = game.name ?: "",
text = text
)
return json
}
}

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