Compare commits
77 Commits
v5.41.3-11
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| c944e529ba | |||
| eaa1a3609f | |||
| 2b9478a1b9 | |||
| dbd6e7bb43 | |||
| 09a823d4da | |||
| 0b49c58884 | |||
| 2e93bcc173 | |||
| 03f6906b46 | |||
| 0f382baec1 | |||
| 62f80bbfdd | |||
| 63be9ac221 | |||
| ade32ef92d | |||
| 58702b46aa | |||
| e6d6fc5309 | |||
| 1460348582 | |||
| c49b3c5efe | |||
| 4f7c468cde | |||
| df93bdda2e | |||
| 086796915b | |||
| f99e5f403d | |||
| 9785f4fa13 | |||
| 6cdff338ed | |||
| 761093a768 | |||
| e5a0db4513 | |||
| a3df62bba8 | |||
| c41939cd79 | |||
| 125ef60176 | |||
| e2bf2773e4 | |||
| e3596c758e | |||
| 75ca0a8379 | |||
| ef7d2860e5 | |||
| e608a51cce | |||
| 4f2aea7fcf | |||
| 3abcad5d5d | |||
| 1cca908327 | |||
| 63c0b859f4 | |||
| 5deb741942 | |||
| e855f59ae4 | |||
| 9ea4b1367e | |||
| e44c11349b | |||
| a0ea9f9f44 | |||
| 082d3297f7 | |||
| f81055ae7f | |||
| 8e3de4e441 | |||
| 7cf66c4362 | |||
| d514e4ce1b | |||
| 9d0f75a882 | |||
| 6c8c2f2340 | |||
| 8d7afea0f6 | |||
| aff6cbccdc | |||
| b19b6960ac | |||
| c60b317125 | |||
| 4fbb9d0c88 | |||
| f039d71562 | |||
| 1d74bfc7a8 | |||
| 3d118544bb | |||
| 730611362f | |||
| 9825bb7d3f | |||
| 7dfb69fd34 | |||
| 380939b8c4 | |||
| 83d43f32bf | |||
| 184e0731fb | |||
| 085356d85e | |||
| 44138225fc | |||
| 09e439f143 | |||
| c23a78a546 | |||
| 9158d951f4 | |||
| 1c7f5c2d26 | |||
| bc630f63d4 | |||
| 1a2cfb79c2 | |||
| 7b708ebd8d | |||
| d3351ec0b6 | |||
| e769c0e5f5 | |||
| bb3a849671 | |||
| 4ed1e1e64a | |||
| 9f4eaa67fa | |||
| 655173482a |
@ -157,3 +157,4 @@ oss-upload&send-email:
|
||||
only:
|
||||
- dev
|
||||
- release
|
||||
|
||||
|
||||
@ -154,7 +154,7 @@ android {
|
||||
zipAlignEnabled false
|
||||
signingConfig signingConfigs.debug
|
||||
|
||||
buildConfigField "String", "EXPOSURE_REPO", "\"test\""
|
||||
buildConfigField "String", "EXPOSURE_REPO", "\"exposure\""
|
||||
buildConfigField "String", "EXPOSURE_VERSION", "\"E4\""
|
||||
|
||||
multiDexKeepProguard file("tinker_multidexkeep.pro")
|
||||
@ -539,6 +539,8 @@ dependencies {
|
||||
if(!gradle.ext.excludeOptionalModules || gradle.ext.enableWechatPay){
|
||||
implementation(project(":feature:wechat_pay"))
|
||||
}
|
||||
|
||||
implementation "me.xdrop:fuzzywuzzy:${fuzzyVersion}"
|
||||
}
|
||||
|
||||
File propFile = file('sign.properties')
|
||||
|
||||
@ -3,9 +3,14 @@ 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
|
||||
@ -37,6 +42,9 @@ class InstallExternalGameFragment : ToolbarFragment(), OnItemClickListener {
|
||||
|
||||
private var uninstallDisposable: Disposable? = null
|
||||
|
||||
|
||||
private var shouldScan = false
|
||||
|
||||
override fun getLayoutId() = 0
|
||||
|
||||
override fun getInflatedLayout() =
|
||||
@ -74,7 +82,24 @@ class InstallExternalGameFragment : ToolbarFragment(), OnItemClickListener {
|
||||
adapter.notifyDataSetChanged()
|
||||
}
|
||||
|
||||
requestStoragePermission()
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
private fun requestStoragePermission() {
|
||||
@ -96,6 +121,12 @@ class InstallExternalGameFragment : ToolbarFragment(), OnItemClickListener {
|
||||
}
|
||||
}
|
||||
|
||||
override fun onStart() {
|
||||
super.onStart()
|
||||
if (shouldScan) {
|
||||
mViewModel.scanPaths()
|
||||
}
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
dialog = DialogUtils.showWaitDialog(requireContext(), "")
|
||||
@ -152,7 +183,7 @@ class InstallExternalGameFragment : ToolbarFragment(), OnItemClickListener {
|
||||
}, true)
|
||||
|
||||
VHelper.newCwValidateVspaceBeforeAction(
|
||||
requireContext(),null,
|
||||
requireContext(), null,
|
||||
) {
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
@ -622,32 +622,32 @@ object AdDelegateHelper {
|
||||
), null, null
|
||||
)
|
||||
|
||||
adImage.visibleIf(true)
|
||||
ImageUtils.displayWithCallback(adImage, ad.img, true, object : BaseControllerListener<ImageInfo>() {
|
||||
override fun onSubmit(id: String?, callerContext: Any?) {
|
||||
super.onSubmit(id, callerContext)
|
||||
adImage.post {
|
||||
ownerSplashAdLoadTime = System.currentTimeMillis()
|
||||
NewFlatLogUtils.logSplashAdLoad(ad.id)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFinalImageSet(id: String?, imageInfo: ImageInfo?, animatable: Animatable?) {
|
||||
isOwnerSplashAdShown = true
|
||||
adImage.post {
|
||||
NewFlatLogUtils.logSplashAdShow(ad.id, System.currentTimeMillis() - ownerSplashAdLoadTime)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(id: String?, throwable: Throwable?) {
|
||||
super.onFailure(id, throwable)
|
||||
NewFlatLogUtils.logSplashAdFail(ad.id, "启动广告图加载失败")
|
||||
}
|
||||
})
|
||||
|
||||
if (ad.isImageType) {
|
||||
adVideo.visibleIf(false)
|
||||
adImage.visibleIf(true)
|
||||
ImageUtils.displayWithCallback(adImage, ad.img, true, object : BaseControllerListener<ImageInfo>() {
|
||||
override fun onSubmit(id: String?, callerContext: Any?) {
|
||||
super.onSubmit(id, callerContext)
|
||||
adImage.post {
|
||||
ownerSplashAdLoadTime = System.currentTimeMillis()
|
||||
NewFlatLogUtils.logSplashAdLoad(ad.id)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFinalImageSet(id: String?, imageInfo: ImageInfo?, animatable: Animatable?) {
|
||||
isOwnerSplashAdShown = true
|
||||
adImage.post {
|
||||
NewFlatLogUtils.logSplashAdShow(ad.id, System.currentTimeMillis() - ownerSplashAdLoadTime)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(id: String?, throwable: Throwable?) {
|
||||
super.onFailure(id, throwable)
|
||||
NewFlatLogUtils.logSplashAdFail(ad.id, "启动广告图加载失败")
|
||||
}
|
||||
})
|
||||
} else {
|
||||
adImage.visibleIf(false)
|
||||
adVideo.startPlay(ad.video.url)
|
||||
}
|
||||
startAdContainer.setOnClickListener {
|
||||
|
||||
@ -16,12 +16,10 @@ import com.gh.gamecenter.GameDetailActivity
|
||||
import com.gh.gamecenter.R
|
||||
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
|
||||
import com.gh.gamecenter.common.base.fragment.BaseDialogFragment
|
||||
import com.gh.gamecenter.common.constant.Constants
|
||||
import com.gh.gamecenter.common.exposure.ExposureSource
|
||||
import com.gh.gamecenter.common.utils.*
|
||||
import com.gh.gamecenter.common.view.FixLinearLayoutManager
|
||||
import com.gh.gamecenter.core.utils.DisplayUtils
|
||||
import com.gh.gamecenter.core.utils.SPUtils.getBoolean
|
||||
import com.gh.gamecenter.databinding.DialogReserveBinding
|
||||
import com.gh.gamecenter.databinding.DialogReserveItemBinding
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
|
||||
@ -0,0 +1,54 @@
|
||||
package com.gh.common.exposure
|
||||
|
||||
import com.gh.gamecenter.common.constant.Constants
|
||||
import com.gh.gamecenter.feature.exposure.ExposureEvent
|
||||
import com.gh.gamecenter.feature.exposure.IExposureStateChangeListener
|
||||
import com.gh.gamecenter.feature.exposure.RecyclerViewExposureHelper
|
||||
import com.lightgame.utils.Utils
|
||||
|
||||
class DefaultExposureStateChangeListener : IExposureStateChangeListener {
|
||||
|
||||
override fun onExposureStateChange(
|
||||
exposureEvent: ExposureEvent,
|
||||
position: Int,
|
||||
inExposure: Boolean
|
||||
) {
|
||||
val exposureStatus = if (inExposure) "曝光中" else "结束曝光"
|
||||
|
||||
val isCPMExposureEvent = exposureEvent.payload.miniGameType == Constants.WECHAT_MINI_GAME_CPM
|
||||
|
||||
Utils.log(
|
||||
RecyclerViewExposureHelper.TAG,
|
||||
"onExposureStateChange: 名称 ${exposureEvent.payload.gameName} 位置 $position, $exposureStatus"
|
||||
)
|
||||
|
||||
// 如果是 CPM 曝光事件,且在曝光中,直接上报 CPM 曝光 (内部有做简单的去重处理,CPM 曝光追求一个及时上报,不用理会曝光时长)
|
||||
if (isCPMExposureEvent && inExposure) {
|
||||
Utils.log(
|
||||
RecyclerViewExposureHelper.TAG,
|
||||
"上报 CPM 曝光 ${exposureEvent.payload.gameName} ${exposureEvent.id}"
|
||||
)
|
||||
ExposureManager.logCPM(exposureEvent)
|
||||
}
|
||||
|
||||
if (!inExposure
|
||||
&& System.currentTimeMillis() - exposureEvent.timeInMillisecond > DEFAULT_VALID_EXPOSURE_THRESHOLD
|
||||
) {
|
||||
Utils.log(
|
||||
RecyclerViewExposureHelper.TAG,
|
||||
"上报曝光 ${exposureEvent.payload.gameName} ${exposureEvent.id},是否为 CPM 小游戏 -> ${isCPMExposureEvent}," +
|
||||
"曝光时长为 ${System.currentTimeMillis() - exposureEvent.timeInMillisecond}ms"
|
||||
)
|
||||
|
||||
// 标记当前 ExposureEvent 已经被使用过
|
||||
exposureEvent.markIsUsed()
|
||||
ExposureManager.log(exposureEvent)
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
// 默认曝光有效时长
|
||||
const val DEFAULT_VALID_EXPOSURE_THRESHOLD = 1000L
|
||||
}
|
||||
|
||||
}
|
||||
@ -4,12 +4,14 @@ import com.gh.gamecenter.BuildConfig
|
||||
import com.gh.gamecenter.common.constant.Constants
|
||||
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.google.gson.ExclusionStrategy
|
||||
import com.google.gson.FieldAttributes
|
||||
import com.google.gson.GsonBuilder
|
||||
import com.lightgame.utils.Utils
|
||||
import com.volcengine.model.tls.LogItem
|
||||
|
||||
@ -31,6 +33,20 @@ object ExposureManager {
|
||||
private val exposureSet by lazy { hashSetOf<ExposureEvent>() }
|
||||
private val exposureCache by lazy { FixedSizeLinkedHashSet<String>(300) }
|
||||
|
||||
private val gson by lazy {
|
||||
GsonBuilder()
|
||||
.addSerializationExclusionStrategy(object: ExclusionStrategy {
|
||||
override fun shouldSkipClass(clazz: Class<*>): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun shouldSkipField(f: FieldAttributes): Boolean {
|
||||
return f.name == "additional"
|
||||
}
|
||||
})
|
||||
.create()
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a single exposure event.
|
||||
*/
|
||||
@ -86,6 +102,15 @@ object ExposureManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Log a wechat mini game cpm collection of exposure event.
|
||||
*/
|
||||
fun logCPM(event: ExposureEvent) {
|
||||
AppExecutor.logExecutor.execute {
|
||||
WGameSubjectCPMListReportHelper.reportExposure(event)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param forcedUpload Ignore all restrictions.
|
||||
*/
|
||||
@ -114,14 +139,14 @@ object ExposureManager {
|
||||
|
||||
private fun buildLog(event: ExposureEvent) = LogItem(System.currentTimeMillis()).apply {
|
||||
addContent("__id", event.id)
|
||||
addContent("payload", event.payload.toJson())
|
||||
addContent("payload", gson.toJson(event.payload))
|
||||
addContent("event", event.event.toString())
|
||||
addContent("source", eliminateMultipleBrackets(event.source.toJson()))
|
||||
addContent("meta", event.meta.toJson())
|
||||
addContent("source", eliminateMultipleBrackets(gson.toJson(event.source)))
|
||||
addContent("meta", gson.toJson(event.meta))
|
||||
addContent("real_millisecond", event.timeInMillisecond.toString())
|
||||
addContent(
|
||||
"e-traces", if (event.eTrace != null) {
|
||||
eliminateMultipleBrackets(event.eTrace?.toJson() ?: "")
|
||||
eliminateMultipleBrackets(gson.toJson(event.eTrace))
|
||||
} else ""
|
||||
)
|
||||
}
|
||||
|
||||
@ -8,13 +8,13 @@ object ExposureTraceUtils {
|
||||
val traceList = arrayListOf<ExposureEvent>()
|
||||
|
||||
event?.let {
|
||||
//这里使用deepCopy,是为了防止循环引用调用hashCode方法触发StackOverflowError错误
|
||||
val deepCopy = it.deepCopy()
|
||||
if (deepCopy.eTrace == null) {
|
||||
traceList.add(deepCopy)
|
||||
//这里使用 copy,是为了防止循环引用调用hashCode方法触发StackOverflowError错误
|
||||
val exposureCopy = it.shallowCopy()
|
||||
if (exposureCopy.eTrace == null) {
|
||||
traceList.add(exposureCopy)
|
||||
} else {
|
||||
traceList.addAll(deepCopy.eTrace!!)
|
||||
traceList.add(flattenTrace(deepCopy))
|
||||
traceList.addAll(exposureCopy.eTrace!!)
|
||||
traceList.add(flattenTrace(exposureCopy))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -5,6 +5,7 @@ import com.gh.common.util.DirectUtils
|
||||
import com.gh.gamecenter.BuildConfig
|
||||
import com.gh.gamecenter.common.entity.LaunchRedirect
|
||||
import com.gh.gamecenter.common.entity.LaunchRedirectWrapper
|
||||
import com.gh.gamecenter.common.pagelevel.PageLevelManager
|
||||
import com.gh.gamecenter.common.retrofit.BiResponse
|
||||
import com.gh.gamecenter.common.utils.singleToMain
|
||||
import com.gh.gamecenter.core.AppExecutor
|
||||
@ -65,9 +66,11 @@ class LaunchRedirectHandler(priority: Int) : PriorityChainHandler(priority) {
|
||||
override fun onProcess(): Boolean {
|
||||
val currentActivity = CurrentActivityHolder.getCurrentActivity()
|
||||
|
||||
if (GlobalPriorityChainHelper.isThisActivityValid(currentActivity)) {
|
||||
if (GlobalPriorityChainHelper.isThisActivityValid(currentActivity) && launchData?.type != "bottom_tab") {
|
||||
if (getStatus() == STATUS_VALID) {
|
||||
// 当 type 为 "bottom_tab" 时上面 doPreProcess 中已经处理过了,但再选中一次好像也没有什么问题,先不特殊处理这个 case 了
|
||||
// 设置下一个页面为顶级页面
|
||||
PageLevelManager.setNextPageAsSupremePageLevel()
|
||||
|
||||
DirectUtils.directToLinkPage(currentActivity!!, launchData!!, "首次启动跳转", "")
|
||||
// 跳转页面不管回调,延迟 500ms 后执行下一个 handler
|
||||
AppExecutor.uiExecutor.executeWithDelay({
|
||||
|
||||
@ -30,6 +30,8 @@ class DownloadButtonClickedProviderImpl : IDownloadButtonClickedProvider {
|
||||
var packageName = ""
|
||||
var exposureSourceList: List<ExposureSource>? = null
|
||||
var customPageTrackData: CustomPageTrackData? = null
|
||||
var isAd = false
|
||||
var adGroupId = ""
|
||||
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()
|
||||
@ -60,6 +62,8 @@ class DownloadButtonClickedProviderImpl : IDownloadButtonClickedProvider {
|
||||
packageName = boundedObject.getUniquePackageName() ?: ""
|
||||
exposureSourceList = boundedObject.exposureEvent?.source
|
||||
customPageTrackData = boundedObject.customPageTrackData
|
||||
isAd = boundedObject.adGroupId.isNotEmpty()
|
||||
adGroupId = boundedObject.adGroupId
|
||||
}
|
||||
|
||||
is GameUpdateEntity -> {
|
||||
@ -144,6 +148,8 @@ class DownloadButtonClickedProviderImpl : IDownloadButtonClickedProvider {
|
||||
"last_page_name", GlobalActivityManager.getLastPageEntity().pageName,
|
||||
"last_page_id", GlobalActivityManager.getLastPageEntity().pageId,
|
||||
"last_page_business_id", GlobalActivityManager.getLastPageEntity().pageBusinessId,
|
||||
"is_ad", isAd.toString(),
|
||||
"ad_group_id", adGroupId,
|
||||
"is_from_push_notifications", isFromPush,
|
||||
"message_id", pushMessageId,
|
||||
"link_id", pushLinkId,
|
||||
|
||||
@ -1,9 +1,6 @@
|
||||
package com.gh.common.provider
|
||||
|
||||
import android.content.Context
|
||||
import com.therouter.router.Route
|
||||
import com.gh.common.exposure.ExposureManager
|
||||
import com.gh.gamecenter.common.constant.RouteConsts
|
||||
import com.gh.gamecenter.feature.exposure.ExposureEvent
|
||||
import com.gh.gamecenter.feature.provider.IExposureManagerProvider
|
||||
|
||||
@ -12,4 +9,8 @@ class ExposureManagerProviderImpl: IExposureManagerProvider {
|
||||
override fun logExposure(exposureEvent: ExposureEvent) {
|
||||
ExposureManager.log(exposureEvent)
|
||||
}
|
||||
|
||||
override fun logExposureList(exposureEventList: List<ExposureEvent>) {
|
||||
ExposureManager.log(exposureEventList)
|
||||
}
|
||||
}
|
||||
@ -1,8 +1,20 @@
|
||||
package com.gh.common.util
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.text.TextUtils
|
||||
import com.gh.common.constant.Config
|
||||
import com.gh.gamecenter.common.entity.IdfaData
|
||||
import com.gh.gamecenter.common.pagelevel.IPageLevelProvider
|
||||
import com.gh.gamecenter.common.retrofit.BiResponse
|
||||
import com.gh.gamecenter.common.tracker.IBusiness
|
||||
import com.gh.gamecenter.common.utils.SensorsBridge
|
||||
import com.gh.gamecenter.core.utils.CurrentActivityHolder
|
||||
import com.gh.gamecenter.entity.StartupAdEntity
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.feature.entity.SettingsEntity
|
||||
import com.gh.gamecenter.feature.exposure.ExposureEvent
|
||||
import com.gh.gamecenter.retrofit.RetrofitManager
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
|
||||
object AdHelper {
|
||||
|
||||
@ -12,6 +24,102 @@ object AdHelper {
|
||||
const val LOCATION_SUGGESTION_FUNCTION = "suggestion_function"
|
||||
const val LOCATION_SIMULATOR_GAME = "simulator_game"
|
||||
|
||||
// 广告标识 https://jira.shanqu.cc/browse/GHZSCY-7041
|
||||
private var idfa = ""
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
@JvmStatic
|
||||
fun getIdfa(versionName: String, channel: String) {
|
||||
RetrofitManager.getInstance().newApi.getIdfa(versionName, channel)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(object : BiResponse<IdfaData>() {
|
||||
override fun onSuccess(data: IdfaData) {
|
||||
idfa = data.AD
|
||||
|
||||
SensorsBridge.trackGetAdTag(data.AD)
|
||||
}
|
||||
|
||||
override fun onFailure(exception: Exception) {
|
||||
super.onFailure(exception)
|
||||
|
||||
SensorsBridge.trackGetAdTag("")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fun getIdfaString() : String {
|
||||
return idfa
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录广告游戏填充
|
||||
*/
|
||||
fun reportAdRequest(exposureEvent: ExposureEvent) {
|
||||
val payload = exposureEvent.payload
|
||||
NewFlatLogUtils.logAdRequest(
|
||||
pageLevel = payload.pageLevel ?: "",
|
||||
currentPageCode = payload.currentPageCode ?: "",
|
||||
currentPageId = payload.currentPageId ?: "",
|
||||
gameName = payload.gameName ?: "",
|
||||
gameId = payload.gameId ?: "",
|
||||
moduleType = payload.moduleType ?: "",
|
||||
moduleStyle = payload.moduleStyle ?: "",
|
||||
moduleName = payload.moduleName ?: "",
|
||||
moduleId = payload.moduleId ?: "",
|
||||
searchContent = payload.searchContent ?: "",
|
||||
sequence = payload.sequence ?: -1,
|
||||
outerSequence = payload.outerSequence ?: -1,
|
||||
adGroupId = payload.adGroupId ?: "",
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 记录广告游戏填充
|
||||
*/
|
||||
fun reportAdRequest(gameEntity: GameEntity) {
|
||||
val currentActivity = CurrentActivityHolder.getCurrentActivity()
|
||||
val subPageCode = gameEntity.subPageCode
|
||||
val subPageId = gameEntity.subPageId
|
||||
|
||||
val currentActivitySimpleName = if (currentActivity != null) currentActivity::class.simpleName else "unknown"
|
||||
val businessId = if (currentActivity is IBusiness) currentActivity.getBusinessId() else null
|
||||
|
||||
var currentPageCode = currentActivitySimpleName
|
||||
var currentPageId = businessId?.first
|
||||
|
||||
// 子页面的 pageCode 添加到主页面后, pageId 优先级高于主页面 pageId
|
||||
if (!subPageCode.isNullOrEmpty()) {
|
||||
currentPageCode = "$currentActivitySimpleName-$subPageCode"
|
||||
if (!subPageId.isNullOrEmpty()) {
|
||||
currentPageId = subPageId
|
||||
}
|
||||
}
|
||||
|
||||
var pageLevelString: String? = gameEntity.pageLevelString
|
||||
|
||||
if (TextUtils.isEmpty(pageLevelString)) {
|
||||
val pageLevelProvider = currentActivity as? IPageLevelProvider
|
||||
pageLevelString = pageLevelProvider?.getPageLevel()?.toFormattedString()
|
||||
}
|
||||
|
||||
NewFlatLogUtils.logAdRequest(
|
||||
pageLevel = pageLevelString ?: "",
|
||||
currentPageCode = currentPageCode ?: "",
|
||||
currentPageId = currentPageId ?: "",
|
||||
gameName = gameEntity.name ?: "",
|
||||
gameId = gameEntity.id,
|
||||
moduleStyle = gameEntity.customPageTrackData?.modulePattern ?: "",
|
||||
moduleType = gameEntity.customPageTrackData?.moduleType ?: "",
|
||||
moduleName = gameEntity.customPageTrackData?.linkContentName ?: "",
|
||||
moduleId = gameEntity.customPageTrackData?.linkContentId ?: "",
|
||||
searchContent = "",
|
||||
sequence = gameEntity.sequence ?: -1,
|
||||
outerSequence = gameEntity.outerSequence ?: -1,
|
||||
adGroupId = gameEntity.adGroupId,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@JvmStatic
|
||||
fun getStartUp(): StartupAdEntity? {
|
||||
return Config.getNewApiSettingsEntity()?.startup
|
||||
|
||||
@ -1534,8 +1534,8 @@ object DirectUtils {
|
||||
if (categoryId.isEmpty()) return
|
||||
val bundle = Bundle()
|
||||
bundle.putString(KEY_TO, CategoryV2Activity::class.java.name)
|
||||
bundle.putString(KEY_CATEGORY_ID, categoryId)
|
||||
bundle.putString(KEY_CATEGORY_TITLE, categoryTitle)
|
||||
bundle.putString(KEY_PAGE_ID, categoryId)
|
||||
bundle.putString(KEY_PAGE_NAME, categoryTitle)
|
||||
bundle.putString(KEY_ENTRANCE, BaseActivity.mergeEntranceAndPath(entrance, path))
|
||||
if (exposureEvent != null) bundle.putParcelableArrayList(
|
||||
KEY_EXPOSURE_SOURCE_LIST,
|
||||
|
||||
@ -2,10 +2,9 @@ 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.toResString
|
||||
import com.gh.gamecenter.common.utils.replaceLineBreakWithBr
|
||||
import com.gh.gamecenter.core.utils.EmptyCallback
|
||||
import com.gh.gamecenter.feature.entity.ApkEntity
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
@ -32,7 +31,7 @@ object DownloadDialogHelper {
|
||||
DialogHelper.showDialogWithHtmlContent(
|
||||
context,
|
||||
dialog.title,
|
||||
dialog.content,
|
||||
dialog.content.replaceLineBreakWithBr(),
|
||||
"继续下载",
|
||||
"取消",
|
||||
confirmClickCallback = {
|
||||
@ -59,7 +58,8 @@ object DownloadDialogHelper {
|
||||
gameName = gameEntity.name ?: "",
|
||||
gameType = gameEntity.categoryChinese
|
||||
)
|
||||
}
|
||||
},
|
||||
extraConfig = DialogHelper.Config(centerTitle = true)
|
||||
)
|
||||
} else {
|
||||
callback.onCallback()
|
||||
|
||||
@ -867,6 +867,8 @@ object DownloadItemUtils {
|
||||
"last_page_id", getLastPageEntity().pageId,
|
||||
"last_page_business_id", getLastPageEntity().pageBusinessId,
|
||||
"source", gameEntity.exposureEvent?.source?.toString() ?: "",
|
||||
"is_ad", if (gameEntity.adGroupId.isEmpty()) "false" else "true",
|
||||
"ad_group_id", gameEntity.adGroupId,
|
||||
*gameEntity.customPageTrackData?.toKV() ?: arrayOf()
|
||||
)
|
||||
allStateClickCallback?.onCallback()
|
||||
|
||||
@ -501,6 +501,7 @@ object DownloadObserver {
|
||||
|
||||
val isPlatformRecommend =
|
||||
java.lang.Boolean.parseBoolean(downloadEntity.getMetaExtra(Constants.IS_PLATFORM_RECOMMEND))
|
||||
val adGroupId = downloadEntity.getMetaExtra(Constants.AD_GROUP_ID)
|
||||
val exposureEvent = ExposureUtils.logADownloadCompleteExposureEvent(
|
||||
GameEntity(
|
||||
_id = downloadEntity.gameId,
|
||||
@ -534,6 +535,8 @@ object DownloadObserver {
|
||||
"game_id", downloadEntity.gameId,
|
||||
"game_type", downloadEntity.categoryChinese,
|
||||
"game_schema_type", if (downloadEntity.getMetaExtra(Constants.KEY_BIT) == "32") "32位" else "64位",
|
||||
"is_ad", if (adGroupId.isEmpty()) "false" else "true",
|
||||
"ad_group_id", adGroupId,
|
||||
*kvs
|
||||
)
|
||||
} else if (downloadEntity.gameId == Constants.HALO_FUN_GAME_ID) {
|
||||
@ -598,6 +601,8 @@ object DownloadObserver {
|
||||
if (downloadEntity.asVGame()) "畅玩下载" else "本地下载",
|
||||
"is_from_push_notifications",
|
||||
isFromPush,
|
||||
"is_ad", if (adGroupId.isEmpty()) "false" else "true",
|
||||
"ad_group_id", adGroupId,
|
||||
"message_id",
|
||||
pushMessageId,
|
||||
"link_id",
|
||||
|
||||
@ -99,6 +99,11 @@ object GameSubstituteRepositoryHelper {
|
||||
continue
|
||||
}
|
||||
|
||||
// 广告系统的广告游戏不替换
|
||||
if (game.adGroupId.isNotEmpty()) {
|
||||
continue
|
||||
}
|
||||
|
||||
// 这个 position 的游戏是否需要被替换
|
||||
var thisPositionNeedToBeReplaced = false
|
||||
|
||||
|
||||
@ -324,7 +324,19 @@ public class LibaoUtils {
|
||||
libaoBtn.setText(com.gh.gamecenter.feature.R.string.libao_finish);
|
||||
libaoBtn.setBackgroundResource(R.drawable.button_border_round_gray);
|
||||
libaoBtn.setTextColor(context.getResources().getColor(com.gh.gamecenter.common.R.color.button_gray));
|
||||
libaoBtn.setOnClickListener(v -> ToastUtils.toast("兑换码领取已结束"));
|
||||
libaoBtn.setOnClickListener(v ->
|
||||
{
|
||||
ToastUtils.toast("兑换码领取已结束");
|
||||
SensorsBridge.trackEvent("GameGiftDraw",
|
||||
"gift_type", "兑换码",
|
||||
"game_name", libaoEntity.getGame().getName(),
|
||||
"game_id", libaoEntity.getGame().getId(),
|
||||
"gift_id", libaoEntity.getId(),
|
||||
"gift_name", libaoEntity.getName(),
|
||||
"source_entrance", sourceEntrance,
|
||||
"button_name", libaoBtn.getText().toString()
|
||||
);
|
||||
});
|
||||
} else {
|
||||
libaoBtn.setText(R.string.libao_copy);
|
||||
libaoBtn.setTextColor(ExtensionsKt.toColor(com.gh.gamecenter.common.R.color.white, context));
|
||||
@ -338,6 +350,23 @@ public class LibaoUtils {
|
||||
libaoEntity.getGame().getId(),
|
||||
libaoEntity.getGame().getName()
|
||||
);
|
||||
SensorsBridge.trackEvent("GameGiftDraw",
|
||||
"gift_type", "兑换码",
|
||||
"game_name", libaoEntity.getGame().getName(),
|
||||
"game_id", libaoEntity.getGame().getId(),
|
||||
"gift_id", libaoEntity.getId(),
|
||||
"gift_name", libaoEntity.getName(),
|
||||
"source_entrance", sourceEntrance,
|
||||
"button_name", libaoBtn.getText().toString()
|
||||
);
|
||||
SensorsBridge.trackEvent("GameGiftDrawResult",
|
||||
"gift_type", "兑换码",
|
||||
"game_name", libaoEntity.getGame().getName(),
|
||||
"game_id", libaoEntity.getGame().getId(),
|
||||
"gift_id", libaoEntity.getId(),
|
||||
"gift_name", libaoEntity.getName(),
|
||||
"source_entrance", sourceEntrance
|
||||
);
|
||||
ExtensionsKt.copyTextAndToast(libaoEntity.getCode(), libaoEntity.getToast());
|
||||
});
|
||||
}
|
||||
@ -346,6 +375,25 @@ public class LibaoUtils {
|
||||
|
||||
libaoBtn.setOnClickListener(v -> {
|
||||
String btnStatus = libaoBtn.getText().toString();
|
||||
String giftType;
|
||||
if ("ling".equals(libaoEntity.getStatus()) || "linged".equals(libaoEntity.getStatus())) {
|
||||
giftType = "普通礼包";
|
||||
} else if ("copy".equals(libaoEntity.getReceiveMethod())) {
|
||||
giftType = "兑换码";
|
||||
} else {
|
||||
giftType = "淘号礼包";
|
||||
}
|
||||
|
||||
SensorsBridge.trackEvent("GameGiftDraw",
|
||||
"gift_type", giftType,
|
||||
"game_name", libaoEntity.getGame().getName(),
|
||||
"game_id", libaoEntity.getGame().getId(),
|
||||
"gift_id", libaoEntity.getId(),
|
||||
"gift_name", libaoEntity.getName(),
|
||||
"source_entrance", sourceEntrance,
|
||||
"button_name", btnStatus
|
||||
);
|
||||
|
||||
// 领取限制
|
||||
CheckLoginUtils.checkLogin(context, "礼包详情-[" + btnStatus + "]", () -> {
|
||||
if ("领取".equals(btnStatus) || "淘号".equals(btnStatus)) {
|
||||
@ -408,27 +456,11 @@ public class LibaoUtils {
|
||||
} else {
|
||||
libaoLing(context, libaoBtn, libaoEntity, adapter, isInstallRequired, null, entrance, sourceEntrance, listener);
|
||||
}
|
||||
SensorsBridge.trackEvent("GameGiftDraw",
|
||||
"gift_type", "普通礼包",
|
||||
"game_name", libaoEntity.getGame().getName(),
|
||||
"game_id", libaoEntity.getGame().getId(),
|
||||
"gift_id", libaoEntity.getId(),
|
||||
"gift_name", libaoEntity.getName(),
|
||||
"source_entrance", sourceEntrance
|
||||
);
|
||||
break;
|
||||
case "再淘":
|
||||
case "再淘一个":
|
||||
case "淘号":
|
||||
libaoTao(context, libaoBtn, libaoEntity, isInstallRequired, adapter, status, entrance, sourceEntrance, listener);
|
||||
SensorsBridge.trackEvent("GameGiftDraw",
|
||||
"gift_type", "淘号礼包",
|
||||
"game_name", libaoEntity.getGame().getName(),
|
||||
"game_id", libaoEntity.getGame().getId(),
|
||||
"gift_id", libaoEntity.getId(),
|
||||
"gift_name", libaoEntity.getName(),
|
||||
"source_entrance", sourceEntrance
|
||||
);
|
||||
break;
|
||||
}
|
||||
});
|
||||
@ -515,6 +547,7 @@ public class LibaoUtils {
|
||||
try {
|
||||
JSONObject errorJson = new JSONObject(exception.response().errorBody().string());
|
||||
String detail = errorJson.getString("detail").toLowerCase();
|
||||
int code = errorJson.getInt("code");
|
||||
switch (detail) {
|
||||
case "coming":
|
||||
Utils.toast(context, "礼包领取时间未开始");
|
||||
@ -545,7 +578,13 @@ public class LibaoUtils {
|
||||
Utils.toast(context, "淘号失败,稍后重试");
|
||||
break;
|
||||
default:
|
||||
Utils.toast(context, "操作失败");
|
||||
if (code == 403211) {
|
||||
Utils.toast(context, "条件不符,请先提交游戏评价");
|
||||
} else if (code == 403212) {
|
||||
Utils.toast(context, "条件不符,请修改游戏评价");
|
||||
} else {
|
||||
Utils.toast(context, "操作失败");
|
||||
}
|
||||
break;
|
||||
|
||||
}
|
||||
@ -663,6 +702,7 @@ public class LibaoUtils {
|
||||
String string = exception.response().errorBody().string();
|
||||
JSONObject errorJson = new JSONObject(string);
|
||||
String detail = errorJson.getString("detail").toLowerCase();
|
||||
int code = errorJson.getInt("code");
|
||||
switch (detail) {
|
||||
case "coming":
|
||||
Utils.toast(context, "礼包领取时间未开始");
|
||||
@ -702,7 +742,13 @@ public class LibaoUtils {
|
||||
Utils.toast(context, "网络状态异常,请稍后再试");
|
||||
break;
|
||||
default:
|
||||
Utils.toast(context, "操作失败");
|
||||
if (code == 403211) {
|
||||
Utils.toast(context, "条件不符,请先提交游戏评价");
|
||||
} else if (code == 403212) {
|
||||
Utils.toast(context, "条件不符,请修改游戏评价");
|
||||
} else {
|
||||
Utils.toast(context, "操作失败");
|
||||
}
|
||||
break;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
|
||||
@ -2779,6 +2779,44 @@ object NewFlatLogUtils {
|
||||
}.let(::log)
|
||||
}
|
||||
|
||||
/**
|
||||
* 广告游戏填充
|
||||
* 广告游戏填充到广告位置时,上报该广告游戏插入的位置信息(用于数据回收验证策略的效果,无须曝光,只要请求了广告,就上报)
|
||||
*/
|
||||
fun logAdRequest(
|
||||
pageLevel: String,
|
||||
currentPageCode: String,
|
||||
currentPageId: String,
|
||||
gameName: String,
|
||||
gameId: String,
|
||||
moduleType: String,
|
||||
moduleStyle: String,
|
||||
moduleName: String,
|
||||
moduleId: String,
|
||||
searchContent: String,
|
||||
sequence: Int,
|
||||
outerSequence: Int,
|
||||
adGroupId: String,
|
||||
) {
|
||||
json {
|
||||
KEY_EVENT to "ad_request"
|
||||
"page_level" to pageLevel
|
||||
"current_page_code" to currentPageCode
|
||||
"current_page_id" to currentPageId
|
||||
KEY_GAME_NAME to gameName
|
||||
KEY_GAME_ID to gameId
|
||||
"module_type" to moduleType
|
||||
"module_style" to moduleStyle
|
||||
"module_name" to moduleName
|
||||
"module_id" to moduleId
|
||||
"search_content" to searchContent
|
||||
"sequence" to sequence
|
||||
"outer_sequence" to outerSequence
|
||||
"ad_group_id" to adGroupId
|
||||
parseAndPutMeta()(this)
|
||||
}.let(::log)
|
||||
}
|
||||
|
||||
// 自有开屏广告加载
|
||||
fun logSplashAdLoad(id: String) {
|
||||
json {
|
||||
|
||||
@ -64,8 +64,6 @@ object PackageInstaller {
|
||||
showUnzipToast: Boolean,
|
||||
ignoreAsVGame: Boolean,
|
||||
) {
|
||||
val pkgPath = downloadEntity.path
|
||||
val isXapk = XapkInstaller.XAPK_EXTENSION_NAME == pkgPath.getExtension()
|
||||
val isDownloadAsVGame = downloadEntity.getMetaExtra(Constants.EXTRA_DOWNLOAD_TYPE) == Constants.VGAME
|
||||
|| downloadEntity.getMetaExtra(Constants.EXTRA_DOWNLOAD_TYPE) == Constants.DUAL_DOWNLOAD_VGAME
|
||||
|
||||
@ -99,7 +97,7 @@ object PackageInstaller {
|
||||
context.startActivity(mainIntent)
|
||||
Runtime.getRuntime().exit(0)
|
||||
} else {
|
||||
if (isXapk) {
|
||||
if (XapkInstaller.isXapk(downloadEntity)) {
|
||||
XapkInstaller.install(context, downloadEntity, showUnzipToast)
|
||||
} else {
|
||||
install(context, downloadEntity.isPlugin, downloadEntity.path, downloadEntity)
|
||||
@ -211,9 +209,6 @@ object PackageInstaller {
|
||||
pkgPath: String,
|
||||
sessionId: Int = -1
|
||||
) {
|
||||
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
||||
return
|
||||
}
|
||||
val installer = context.packageManager.packageInstaller
|
||||
val session = installer.openSession(sessionId)
|
||||
// 监听安装回调的组件,可以是Activity、Service或者是BroadcastReceiver
|
||||
|
||||
@ -80,6 +80,8 @@ object ReservationHelper {
|
||||
"last_page_id", getLastPageEntity().pageId,
|
||||
"last_page_business_id", getLastPageEntity().pageBusinessId,
|
||||
"source", game?.exposureEvent?.source?.toString() ?: "",
|
||||
"is_ad", if (game?.adGroupId.isNullOrEmpty() == true) "false" else "true",
|
||||
"ad_group_id", game?.adGroupId ?: "",
|
||||
*game?.customPageTrackData?.toKV() ?: arrayOf()
|
||||
)
|
||||
|
||||
@ -118,6 +120,8 @@ object ReservationHelper {
|
||||
"last_page_id", getLastPageEntity().pageId,
|
||||
"last_page_business_id", getLastPageEntity().pageBusinessId,
|
||||
"source", game?.exposureEvent?.source?.toString() ?: "",
|
||||
"is_ad", if (game?.adGroupId.isNullOrEmpty() == true) "false" else "true",
|
||||
"ad_group_id", game?.adGroupId ?: "",
|
||||
*game?.customPageTrackData?.toKV() ?: arrayOf()
|
||||
)
|
||||
ToastUtils.showToast(exception.message ?: "")
|
||||
|
||||
@ -147,6 +147,15 @@ 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)
|
||||
}
|
||||
@ -176,15 +185,6 @@ 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 +199,8 @@ object ViewPagerFragmentHelper {
|
||||
// 分类2.0
|
||||
TYPE_CATEGORY_V2 -> {
|
||||
className = CategoryV2Fragment::class.java.name
|
||||
bundle.putString(EntranceConsts.KEY_CATEGORY_ID, entity.link)
|
||||
bundle.putString(EntranceConsts.KEY_CATEGORY_TITLE, entity.text)
|
||||
bundle.putString(EntranceConsts.KEY_PAGE_ID, entity.link)
|
||||
bundle.putString(EntranceConsts.KEY_PAGE_NAME, entity.text)
|
||||
bundle.putBoolean(EntranceConsts.KEY_SHOW_DOWNLOAD_MENU, !isTabWrapper)
|
||||
}
|
||||
// 通用内容合集详情页
|
||||
|
||||
@ -1,38 +1,48 @@
|
||||
package com.gh.common.view
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.Typeface
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.PopupWindow
|
||||
import android.widget.TextView
|
||||
import android.widget.RadioButton
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.forEach
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.gh.gamecenter.R
|
||||
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.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.entity.SubjectSettingEntity
|
||||
import com.google.android.flexbox.FlexboxLayout
|
||||
|
||||
class CategoryFilterView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null,
|
||||
defStyleAttr: Int = 0
|
||||
) : LinearLayout(context, attrs, defStyleAttr) {
|
||||
) : ConstraintLayout(context, attrs, defStyleAttr) {
|
||||
|
||||
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 binding: LayoutCategoryFilterBinding
|
||||
|
||||
private var mTypeFilterArray = arrayOf(SortType.RECOMMENDED, SortType.NEWEST, SortType.RATING)
|
||||
private var sizeFilterArray: ArrayList<SubjectSettingEntity.Size>? = null
|
||||
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 mOnCategoryFilterSetupListener: OnCategoryFilterSetupListener? = null
|
||||
private var mOnFilterClickListener: OnFilterClickListener? = null
|
||||
@ -41,29 +51,28 @@ class CategoryFilterView @JvmOverloads constructor(
|
||||
private var mSizePopupWindow: PopupWindow? = null
|
||||
|
||||
init {
|
||||
View.inflate(context, R.layout.layout_category_filter, this)
|
||||
|
||||
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
|
||||
val inflater = LayoutInflater.from(context)
|
||||
binding = LayoutCategoryFilterBinding.inflate(inflater, this, true)
|
||||
|
||||
mTypeContainer.setOnClickListener {
|
||||
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 {
|
||||
mOnFilterClickListener?.onTypeClick()
|
||||
showSelectTypePopupWindow(this, mTypeTv, mTypeTv.text.toString())
|
||||
showSelectTypePopupWindow()
|
||||
}
|
||||
|
||||
mCatalogContainer.setOnClickListener {
|
||||
mOnFilterClickListener?.onCategoryClick()
|
||||
mOnCategoryFilterSetupListener?.onSetupSortCategory()
|
||||
}
|
||||
|
||||
mSizeContainer.setOnClickListener {
|
||||
binding.llSizeContainer.setOnClickListener {
|
||||
mOnFilterClickListener?.onSizeClick()
|
||||
showSelectSizePopupWindow(this, mSizeTv, mSizeTv.text.toString())
|
||||
showSelectSizePopupWindow()
|
||||
}
|
||||
}
|
||||
|
||||
@ -76,154 +85,133 @@ class CategoryFilterView @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
fun resetSortSize() {
|
||||
mSizeTv.text = "全部大小"
|
||||
binding.tvType.text = R.string.size.toResString()
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
}
|
||||
private fun showSelectTypePopupWindow() {
|
||||
binding.tvType.setTextColor(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)
|
||||
binding.ivTypeArrow.setImageResource(R.drawable.ic_arrow_up)
|
||||
binding.ivTypeArrow.imageTintList =
|
||||
ColorStateList.valueOf(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
|
||||
|
||||
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 inflater = LayoutInflater.from(context)
|
||||
val typeBinding = PopCategoryFilterTypeBinding.inflate(inflater, null, false)
|
||||
val windowWidth = resources.displayMetrics.widthPixels - 80F.dip2px()
|
||||
val windowHeight = mOnCategoryFilterSetupListener?.getPopHeight() ?: 0
|
||||
val popupWindow = PopupWindow(
|
||||
layout,
|
||||
typeBinding.root,
|
||||
windowWidth,
|
||||
LayoutParams.WRAP_CONTENT
|
||||
if (windowHeight == 0) ViewGroup.LayoutParams.WRAP_CONTENT else (windowHeight - height)
|
||||
).apply { mTypePopupWindow = this }
|
||||
|
||||
val flexboxLayout = layout.findViewById<FlexboxLayout>(R.id.flexbox)
|
||||
val backgroundView = layout.findViewById<View>(R.id.background)
|
||||
|
||||
backgroundView.setOnClickListener {
|
||||
typeBinding.root.setOnClickListener {
|
||||
popupWindow.dismiss()
|
||||
}
|
||||
|
||||
for (type in mTypeFilterArray) {
|
||||
val item = inflater.inflate(R.layout.item_filter_size, flexboxLayout, false)
|
||||
val checkedId = when (binding.tvType.text.toString()) {
|
||||
"最新" -> R.id.rb_newest
|
||||
"评分" -> R.id.rb_score
|
||||
else -> R.id.rb_popular
|
||||
}
|
||||
|
||||
// 单列 3 个,强行设置宽度为屏幕的 1/3
|
||||
val width = windowWidth / 3
|
||||
val height = item.layoutParams.height
|
||||
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)
|
||||
|
||||
item.layoutParams = ViewGroup.LayoutParams(width, height)
|
||||
flexboxLayout.addView(item)
|
||||
|
||||
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()
|
||||
typeTv.text = type.value
|
||||
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)
|
||||
|
||||
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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
popupWindow.setOnDismissListener {
|
||||
typeTv.setTextColor(com.gh.gamecenter.common.R.color.text_757575.toColor(context))
|
||||
typeTv.setDrawableEnd(R.drawable.ic_filter_arrow_down)
|
||||
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))
|
||||
mTypePopupWindow = null
|
||||
}
|
||||
|
||||
popupWindow.isTouchable = true
|
||||
popupWindow.isFocusable = true
|
||||
popupWindow.animationStyle = 0
|
||||
popupWindow.showAsDropDown(containerView, 0, 0)
|
||||
popupWindow.showAsDropDown(this, 0, 0)
|
||||
}
|
||||
|
||||
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)
|
||||
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))
|
||||
|
||||
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 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 popupWindow = PopupWindow(
|
||||
layout,
|
||||
sizeBinding.root,
|
||||
windowWidth,
|
||||
LayoutParams.WRAP_CONTENT
|
||||
if (windowHeight == 0) ViewGroup.LayoutParams.WRAP_CONTENT else (windowHeight - height)
|
||||
).apply { mSizePopupWindow = this }
|
||||
|
||||
val flexboxLayout = layout.findViewById<FlexboxLayout>(R.id.flexbox)
|
||||
val backgroundView = layout.findViewById<View>(R.id.background)
|
||||
|
||||
sizeFilterArray = if (sizeFilterArray == null) {
|
||||
getDefaultSizeFilterArray()
|
||||
} else {
|
||||
sizeFilterArray?.apply {
|
||||
if (firstOrNull()?.text != "全部大小") {
|
||||
add(0, SubjectSettingEntity.Size(min = -1, max = -1, text = "全部大小"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
backgroundView.setOnClickListener {
|
||||
sizeBinding.root.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)
|
||||
}
|
||||
sizeBinding.rvSize.setOnClickListener {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
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)
|
||||
|
||||
popupWindow.setOnDismissListener {
|
||||
sizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_757575.toColor(context))
|
||||
sizeTv.setDrawableEnd(R.drawable.ic_filter_arrow_down)
|
||||
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))
|
||||
mSizePopupWindow = null
|
||||
}
|
||||
|
||||
popupWindow.isTouchable = true
|
||||
popupWindow.isFocusable = true
|
||||
popupWindow.animationStyle = 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以上"))
|
||||
}
|
||||
popupWindow.showAsDropDown(this, 0, 0)
|
||||
}
|
||||
|
||||
fun setRootBackgroundColor(@ColorInt color: Int) {
|
||||
@ -231,21 +219,23 @@ class CategoryFilterView @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
fun setItemTextColor(@ColorInt color: Int) {
|
||||
mTypeTv.setTextColor(color)
|
||||
mCatalogTv.setTextColor(color)
|
||||
mSizeTv.setTextColor(color)
|
||||
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)
|
||||
}
|
||||
|
||||
fun updatePopupWindow() {
|
||||
when {
|
||||
mTypePopupWindow != null && mTypePopupWindow!!.isShowing -> {
|
||||
mTypePopupWindow?.dismiss()
|
||||
showSelectTypePopupWindow(this, mTypeTv, mTypeTv.text.toString())
|
||||
showSelectTypePopupWindow()
|
||||
}
|
||||
|
||||
mSizePopupWindow != null && mSizePopupWindow!!.isShowing -> {
|
||||
mSizePopupWindow?.dismiss()
|
||||
showSelectSizePopupWindow(this, mSizeTv, mSizeTv.text.toString())
|
||||
showSelectSizePopupWindow()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -253,18 +243,69 @@ class CategoryFilterView @JvmOverloads constructor(
|
||||
interface OnCategoryFilterSetupListener {
|
||||
fun onSetupSortSize(sortSize: SubjectSettingEntity.Size)
|
||||
fun onSetupSortType(sortType: SortType)
|
||||
fun onSetupSortCategory()
|
||||
fun getPopHeight(): Int
|
||||
}
|
||||
|
||||
interface OnFilterClickListener {
|
||||
fun onCategoryClick()
|
||||
fun onTypeClick()
|
||||
fun onSizeClick()
|
||||
}
|
||||
|
||||
enum class SortType(val value: String) {
|
||||
RECOMMENDED("热门推荐"),
|
||||
NEWEST("最新上线"),
|
||||
RATING("最高评分")
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -1,20 +1,26 @@
|
||||
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
|
||||
import splitties.views.leftPadding
|
||||
|
||||
class ConfigFilterView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
@ -30,11 +36,15 @@ 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
|
||||
|
||||
var sizeFilterArray: ArrayList<SubjectSettingEntity.Size>? = null
|
||||
private 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)
|
||||
@ -45,6 +55,9 @@ 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)
|
||||
|
||||
@ -54,24 +67,30 @@ 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) {
|
||||
@ -80,14 +99,17 @@ class ConfigFilterView @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun updateAllTextView(sortType: SortType) {
|
||||
when (sortType) {
|
||||
SortType.RECOMMENDED -> updateHighlightedTextView(recommendedTv)
|
||||
SortType.NEWEST -> updateHighlightedTextView(newestTv)
|
||||
SortType.RATING -> updateHighlightedTextView(ratingTv)
|
||||
SortType.UPDATE -> updateHighlightedTextView(updateTv)
|
||||
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))
|
||||
}
|
||||
mSizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_757575.toColor(context))
|
||||
}
|
||||
|
||||
fun setOnConfigSetupListener(onConfigFilterSetupListener: OnConfigFilterSetupListener) {
|
||||
@ -96,11 +118,11 @@ class ConfigFilterView @JvmOverloads constructor(
|
||||
|
||||
private fun toggleHighlightedTextView(targetTextView: TextView, highlightIt: Boolean) {
|
||||
if (highlightIt) {
|
||||
targetTextView.background = R.drawable.bg_tag_text.toDrawable()
|
||||
targetTextView.setTextColor(com.gh.gamecenter.common.R.color.text_white.toColor(context))
|
||||
targetTextView.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
|
||||
targetTextView.setTypeface(Typeface.DEFAULT, Typeface.BOLD)
|
||||
} else {
|
||||
targetTextView.background = null
|
||||
targetTextView.setTextColor(com.gh.gamecenter.common.R.color.text_757575.toColor(context))
|
||||
targetTextView.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
|
||||
targetTextView.setTypeface(Typeface.DEFAULT, Typeface.NORMAL)
|
||||
}
|
||||
}
|
||||
|
||||
@ -112,8 +134,8 @@ class ConfigFilterView @JvmOverloads constructor(
|
||||
}
|
||||
|
||||
private fun showSelectionPopupWindow(containerView: View, sizeTv: TextView, sizeText: String) {
|
||||
sizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(sizeTv.context))
|
||||
sizeTv.setDrawableEnd(R.drawable.ic_filter_arrow_up)
|
||||
sizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(sizeTv.context))
|
||||
sizeTv.setDrawableEnd(R.drawable.ic_auxiliary_arrow_up_primary_8)
|
||||
|
||||
val inflater = LayoutInflater.from(sizeTv.context)
|
||||
val layout = inflater.inflate(R.layout.layout_filter_size, null)
|
||||
@ -136,43 +158,39 @@ class ConfigFilterView @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
flexboxLayout.setOnClickListener { }
|
||||
backgroundView.setOnClickListener {
|
||||
popupWindow.dismiss()
|
||||
}
|
||||
|
||||
for (size in sizeFilterArray!!) {
|
||||
val item = inflater.inflate(R.layout.item_filter_size, flexboxLayout, false)
|
||||
val item = inflater.inflate(R.layout.item_config_filter_size, flexboxLayout, false)
|
||||
|
||||
// 单列 4 个,强行设置宽度为屏幕的 1/4
|
||||
val width = sizeTv.context.resources.displayMetrics.widthPixels / 4
|
||||
val width = (sizeTv.context.resources.displayMetrics.widthPixels - 56F.dip2px()) / 4
|
||||
val height = item.layoutParams.height
|
||||
|
||||
item.layoutParams = ViewGroup.LayoutParams(width, height)
|
||||
flexboxLayout.addView(item)
|
||||
|
||||
val tv = item.findViewById<TextView>(R.id.size_tv)
|
||||
val tv = item.findViewById<CheckedTextView>(R.id.size_tv)
|
||||
tv.text = size.text
|
||||
|
||||
if (sizeText == size.text) {
|
||||
toggleHighlightedTextView(tv, true)
|
||||
} else {
|
||||
toggleHighlightedTextView(tv, false)
|
||||
}
|
||||
|
||||
tv.isChecked = sizeText == size.text
|
||||
tv.tag = size.text
|
||||
|
||||
item.setOnClickListener {
|
||||
toggleHighlightedTextView(tv, true)
|
||||
tv.setOnClickListener {
|
||||
flexboxLayout.forEach { checkedTv ->
|
||||
(checkedTv as CheckedTextView).isChecked = checkedTv == tv
|
||||
}
|
||||
popupWindow.dismiss()
|
||||
sizeTv.text = size.text
|
||||
|
||||
mOnConfigFilterSetupListener?.onSetupSortSize(size)
|
||||
}
|
||||
}
|
||||
|
||||
popupWindow.setOnDismissListener {
|
||||
sizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_757575.toColor(sizeTv.context))
|
||||
sizeTv.setDrawableEnd(R.drawable.ic_filter_arrow_down)
|
||||
sizeTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(sizeTv.context))
|
||||
sizeTv.setDrawableEnd(R.drawable.ic_auxiliary_arrow_down_8)
|
||||
mPopupWindow = null
|
||||
}
|
||||
|
||||
@ -193,6 +211,53 @@ class ConfigFilterView @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun initSubjectFilterView(subjectSetting: SubjectSettingEntity) {
|
||||
ratingTv.visibility = View.VISIBLE
|
||||
|
||||
if (subjectSetting.typeEntity.layout == "hide") {
|
||||
container.leftPadding = 8F.dip2px()
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
package com.gh.common.xapk
|
||||
|
||||
import com.lightgame.download.DownloadEntity
|
||||
|
||||
interface IXapkUnzipListener {
|
||||
fun onProgress(downloadEntity: DownloadEntity, unzipPath: String, unzipSize: Long, unzipProgress: Long)
|
||||
|
||||
fun onNext(downloadEntity: DownloadEntity, unzipPath: String)
|
||||
|
||||
fun onCancel(downloadEntity: DownloadEntity)
|
||||
|
||||
fun onFailure(downloadEntity: DownloadEntity, exception: Exception)
|
||||
|
||||
fun onSuccess(downloadEntity: DownloadEntity)
|
||||
}
|
||||
@ -52,6 +52,12 @@ import com.gh.gamecenter.core.utils.SPUtils
|
||||
*
|
||||
* obb文件直接解压至根目录即可,无需操作文件
|
||||
* apk文件这解压的gh-files文件夹中
|
||||
*
|
||||
* update: 2025/4/29
|
||||
* 简单来说,有两种XAPK文件的格式:
|
||||
* XAPK = Android App Bundle(基础APK + 配置APK) 【downloadentity.format == xapk(apks)】
|
||||
* XAPK = APK文件 + OBB数据文件 【downloadentity.format == xapk】
|
||||
*
|
||||
*/
|
||||
@SuppressLint("StaticFieldLeak")
|
||||
object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
|
||||
@ -92,85 +98,90 @@ object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
|
||||
|
||||
private val mPendingSessionInfoMap = HashMap<String, XapkPendingSessionInfo>()
|
||||
|
||||
fun isXapk(downloadEntity: DownloadEntity) = XAPK_EXTENSION_NAME == downloadEntity.path.getExtension()
|
||||
|
||||
private fun isXapkApks(downloadEntity: DownloadEntity) = downloadEntity.format == Constants.XAPK_APKS_FORMAT
|
||||
|
||||
/**
|
||||
* precondition: assert_true(isXapk())
|
||||
*
|
||||
*/
|
||||
// 按并行解压
|
||||
@JvmStatic
|
||||
fun install(context: Context, downloadEntity: DownloadEntity, showUnzipToast: Boolean = false) {
|
||||
this.mContext = context
|
||||
|
||||
val filePath = downloadEntity.path
|
||||
if (XAPK_EXTENSION_NAME == filePath.getExtension()) {
|
||||
if (MiuiUtils.isMiui() && !MiuiUtils.isMiuiOptimizationDisabled() && downloadEntity.format == Constants.XAPK_APKS_FORMAT) {// 小米手机开启miui以后,需要引导用户关闭miui优化
|
||||
DialogHelper.showMiuiOptimizationWarning(
|
||||
context,
|
||||
downloadEntity,
|
||||
onHintClick = {
|
||||
val guides = Config.getNewApiSettingsEntity()?.install
|
||||
val miuiOptimizationGuide = guides?.guides?.findLast {
|
||||
it.type == GUIDE_TYPE_MIUI_OPTIMIZATION
|
||||
}
|
||||
if (miuiOptimizationGuide != null) {
|
||||
DirectUtils.directToLinkPage(
|
||||
context,
|
||||
miuiOptimizationGuide.link,
|
||||
MIUI_OPTIMIZATION_WARNING_DIALOG_ENTRANCE,
|
||||
""
|
||||
)
|
||||
}
|
||||
},
|
||||
onConfirmClick = {
|
||||
if (SystemUtils.isDevelopmentSettingsEnabled(context)) {
|
||||
context.startActivity(
|
||||
Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
)
|
||||
it.markDismissByTouchInside()
|
||||
it.dismiss()
|
||||
} else {
|
||||
ToastUtils.showToast(context.getString(R.string.miui_open_adb_hint))
|
||||
}
|
||||
val isXapkApks = isXapkApks(downloadEntity)
|
||||
|
||||
if (isXapkApks && MiuiUtils.isMiui() && !MiuiUtils.isMiuiOptimizationDisabled()) {
|
||||
// 小米手机开启miui以后,需要引导用户关闭miui优化
|
||||
DialogHelper.showMiuiOptimizationWarning(
|
||||
context,
|
||||
downloadEntity,
|
||||
onHintClick = {
|
||||
val guides = Config.getNewApiSettingsEntity()?.install
|
||||
val miuiOptimizationGuide = guides?.guides?.findLast {
|
||||
it.type == GUIDE_TYPE_MIUI_OPTIMIZATION
|
||||
}
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
PermissionHelper.checkManageAllFilesOrStoragePermissionBeforeAction(context) {
|
||||
val unzipAction = {
|
||||
DownloadManager.getInstance().getDownloadEntitySnapshot(downloadEntity.url, downloadEntity.gameId)
|
||||
?.let {
|
||||
unzipXapkFile(it)
|
||||
if (showUnzipToast) {
|
||||
Utils.toast(mContext, "解压过程请勿退出光环助手!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// XAPK (apks) 格式,不在乎 obb 文件夹是否可读
|
||||
if (downloadEntity.format == Constants.XAPK_APKS_FORMAT) {
|
||||
unzipAction.invoke()
|
||||
return@checkManageAllFilesOrStoragePermissionBeforeAction
|
||||
}
|
||||
|
||||
// 以 file 的方式访问 obb 文件夹是否可行
|
||||
val isFileStyleObbFolderReadable =
|
||||
if (systemHasFlaw) {
|
||||
File(Environment.getExternalStorageDirectory().path, "\u200bAndroid/obb").list() != null
|
||||
if (miuiOptimizationGuide != null) {
|
||||
DirectUtils.directToLinkPage(
|
||||
context,
|
||||
miuiOptimizationGuide.link,
|
||||
MIUI_OPTIMIZATION_WARNING_DIALOG_ENTRANCE,
|
||||
""
|
||||
)
|
||||
}
|
||||
},
|
||||
onConfirmClick = {
|
||||
if (SystemUtils.isDevelopmentSettingsEnabled(context)) {
|
||||
context.startActivity(
|
||||
Intent(Settings.ACTION_APPLICATION_DEVELOPMENT_SETTINGS)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||
)
|
||||
it.markDismissByTouchInside()
|
||||
it.dismiss()
|
||||
} else {
|
||||
File(Environment.getExternalStorageDirectory().path, "Android/obb").list() != null
|
||||
ToastUtils.showToast(context.getString(R.string.miui_open_adb_hint))
|
||||
}
|
||||
|
||||
// 如果是文件夹风格的 obb 文件夹可读,或当前上下文不是 AppCompatActivity,或当前上下文已经被销毁,则直接解压
|
||||
if (isFileStyleObbFolderReadable
|
||||
|| context !is AppCompatActivity
|
||||
|| context.isFinishing
|
||||
) {
|
||||
unzipAction.invoke()
|
||||
} else {
|
||||
unzipWithCulpritHandled(context, downloadEntity.url, unzipAction)
|
||||
}
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
PermissionHelper.checkManageAllFilesOrStoragePermissionBeforeAction(context) {
|
||||
val unzipAction = {
|
||||
DownloadManager.getInstance().getDownloadEntitySnapshot(downloadEntity.url, downloadEntity.gameId)
|
||||
?.let {
|
||||
unzipXapkFile(it, isXapkApks)
|
||||
if (showUnzipToast) {
|
||||
Utils.toast(mContext, "解压过程请勿退出光环助手!")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// XAPK (apks) 格式,不在乎 obb 文件夹是否可读
|
||||
if (isXapkApks) {
|
||||
unzipAction.invoke()
|
||||
return@checkManageAllFilesOrStoragePermissionBeforeAction
|
||||
}
|
||||
|
||||
// 以 file 的方式访问 obb 文件夹是否可行
|
||||
val isFileStyleObbFolderReadable =
|
||||
if (systemHasFlaw) {
|
||||
File(Environment.getExternalStorageDirectory().path, "\u200bAndroid/obb").list() != null
|
||||
} else {
|
||||
File(Environment.getExternalStorageDirectory().path, "Android/obb").list() != null
|
||||
}
|
||||
|
||||
// 如果是文件夹风格的 obb 文件夹可读,或当前上下文不是 AppCompatActivity,或当前上下文已经被销毁,则直接解压
|
||||
if (isFileStyleObbFolderReadable
|
||||
|| context !is AppCompatActivity
|
||||
|| context.isFinishing
|
||||
) {
|
||||
unzipAction.invoke()
|
||||
} else {
|
||||
unzipWithCulpritHandled(context, downloadEntity.url, unzipAction)
|
||||
}
|
||||
} else {
|
||||
throwExceptionInDebug("如果是Apk包请使用PackageInstaller进行安装")
|
||||
PackageInstaller.install(mContext, downloadEntity)
|
||||
}
|
||||
}
|
||||
|
||||
@ -250,13 +261,8 @@ object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
|
||||
}
|
||||
}
|
||||
|
||||
private fun unzipXapkFile(downloadEntity: DownloadEntity) {
|
||||
mXApkUnZipper.unzip(
|
||||
XApkUnZipEntry(
|
||||
downloadEntity.path,
|
||||
File(downloadEntity.path)
|
||||
)
|
||||
)
|
||||
private fun unzipXapkFile(downloadEntity: DownloadEntity, isXapkApks: Boolean) {
|
||||
XApkUnZipEntry(downloadEntity.path, File(downloadEntity.path), isXapkApks).let(mXApkUnZipper::unzip)
|
||||
mDownloadEntityMap[downloadEntity.path] = downloadEntity
|
||||
}
|
||||
|
||||
@ -469,13 +475,16 @@ object XapkInstaller : XApkUnZipCallback, XApkUnZipOutputFactory {
|
||||
|
||||
mPendingSessionInfoMap[downloadEntity.path] = XapkPendingSessionInfo(downloadEntity.path, sessionId)
|
||||
AppExecutor.ioExecutor.execute {// 有可能卡顿造成anr
|
||||
PackageInstaller.installMultiple(
|
||||
applicationContext,
|
||||
downloadEntity.packageName,
|
||||
downloadEntity.path,
|
||||
sessionId
|
||||
)
|
||||
NDataChanger.notifyDataChanged(downloadEntity)
|
||||
try {
|
||||
PackageInstaller.installMultiple(
|
||||
applicationContext,
|
||||
downloadEntity.packageName,
|
||||
downloadEntity.path,
|
||||
sessionId
|
||||
)
|
||||
NDataChanger.notifyDataChanged(downloadEntity)
|
||||
} catch (ignore: Exception) {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,243 +0,0 @@
|
||||
package com.gh.common.xapk
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Environment
|
||||
import com.gh.gamecenter.BuildConfig
|
||||
import com.gh.gamecenter.common.utils.*
|
||||
import com.gh.gamecenter.core.utils.MD5Utils
|
||||
import com.halo.assistant.HaloApp
|
||||
import com.lightgame.download.DownloadEntity
|
||||
import com.lightgame.utils.Utils
|
||||
import net.lingala.zip4j.progress.ProgressMonitor
|
||||
import java.io.File
|
||||
import java.util.zip.ZipFile
|
||||
|
||||
|
||||
class XapkUnzipThread(
|
||||
private var mDownloadEntity: DownloadEntity,
|
||||
private var mUnzipListener: IXapkUnzipListener
|
||||
) : Thread() {
|
||||
|
||||
private val mDefaultBufferSize = 1024 * 1024
|
||||
|
||||
var canceled = false
|
||||
|
||||
override fun run() {
|
||||
super.run()
|
||||
try {
|
||||
val path = mDownloadEntity.path
|
||||
var unzipSize = 0L
|
||||
try {
|
||||
unzipSize = getUnzipSize(path)
|
||||
} catch (e: Exception) {
|
||||
planB()
|
||||
return
|
||||
}
|
||||
|
||||
val msg = FileUtils.isCanDownload(HaloApp.getInstance().application, unzipSize)
|
||||
if (!msg.isNullOrEmpty()) {
|
||||
// 空间不足应该不用刷新页面,保持不变就好
|
||||
Utils.toast(HaloApp.getInstance().application, "设备存储空间不足,请清理后重试!")
|
||||
mUnzipListener.onCancel(mDownloadEntity)
|
||||
return
|
||||
}
|
||||
var unzipProgress = 0L
|
||||
val absolutePath = Environment.getExternalStorageDirectory().absolutePath
|
||||
val xapkFile = File(path)
|
||||
|
||||
val unzipClosure: (zip: ZipFile) -> Unit = { zip ->
|
||||
for (zipEntry in zip.entries().asSequence()) {
|
||||
val outputFile = if (zipEntry.name.getExtension() == XapkInstaller.XAPK_DATA_EXTENSION_NAME) {
|
||||
File(absolutePath + File.separator + zipEntry.name)
|
||||
} else if (zipEntry.name.getExtension() == XapkInstaller.PACKAGE_EXTENSION_NAME) {
|
||||
// apk文件名称 = xapk文件名 + MD5(本身的文件名称) + ".apk"
|
||||
// 如 abc_com.gh.gamecenter.apk 这样的文件名,在使用浏览器安装时系统浏览器可能会抹掉文件类型,导致无法唤起下载完自动安装,这里去掉多个 "." 号,保证浏览器安装正常使用
|
||||
val fileName = xapkFile.nameWithoutExtension + "_" + MD5Utils.getContentMD5(zipEntry.name) + "." + XapkInstaller.PACKAGE_EXTENSION_NAME
|
||||
File(FileUtils.getDownloadPath(HaloApp.getInstance().application, fileName))
|
||||
} else continue // 暂时只需要解压xpk/obb文件
|
||||
|
||||
if (zipEntry.isDirectory) {
|
||||
if (!outputFile.exists()) {
|
||||
throwException("unzip create file path failure", !outputFile.mkdirs())
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if (!outputFile.parentFile.exists()) {
|
||||
throwException("unzip create file path failure", !outputFile.parentFile.mkdirs())
|
||||
}
|
||||
|
||||
// 如果存在同名且大小一致,可以认为该文件已经解压缩(在不解压缩的情况下如果如果获取压缩文件的MD5????)
|
||||
if (!outputFile.exists()) {
|
||||
throwException("unzip create file failure", !outputFile.createNewFile())
|
||||
} else if (outputFile.length() != zipEntry.size) {
|
||||
throwException("unzip delete existing file failure", !outputFile.delete())
|
||||
throwException("unzip create file failure", !outputFile.createNewFile())
|
||||
} else {
|
||||
unzipProgress += zipEntry.size
|
||||
mUnzipListener.onProgress(mDownloadEntity, outputFile.path, unzipSize, unzipProgress)
|
||||
mUnzipListener.onNext(mDownloadEntity, outputFile.path)
|
||||
continue
|
||||
}
|
||||
|
||||
// unzip
|
||||
zip.getInputStream(zipEntry).use { input ->
|
||||
outputFile.outputStream().use { output ->
|
||||
val buffer = ByteArray(mDefaultBufferSize)
|
||||
var bytes = input.read(buffer)
|
||||
while (bytes >= 0) {
|
||||
output.write(buffer, 0, bytes)
|
||||
unzipProgress += bytes
|
||||
bytes = input.read(buffer)
|
||||
if (canceled) {
|
||||
mUnzipListener.onCancel(mDownloadEntity)
|
||||
return@use
|
||||
} else {
|
||||
// 防止多次短时间内多次触发onProgress方法导致阻塞主线程(低端机会出现十分明显的卡顿)
|
||||
debounceActionWithInterval(-1, 500) {
|
||||
mUnzipListener.onProgress(
|
||||
mDownloadEntity,
|
||||
outputFile.path,
|
||||
unzipSize,
|
||||
unzipProgress
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
mUnzipListener.onNext(mDownloadEntity, outputFile.path)
|
||||
}
|
||||
}
|
||||
|
||||
// Kotlin 1.4.X 在安卓 4.4 以下使用 use 默认关闭 ZipFile 的流时会触发
|
||||
// java.lang.IncompatibleClassChangeError: interface not implemented 的 Error (Throwable)
|
||||
// 但实测是不影响解压的,所以这里换用 let 不关闭流,确保不闪退,并且不影响解压结果
|
||||
// 帮用户解压了,但游戏能不能安装就看天吧 (毕竟支持4.X的游戏也不多了)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
ZipFile(File(path)).use { unzipClosure.invoke(it) }
|
||||
} else {
|
||||
ZipFile(File(path)).let { unzipClosure.invoke(it) }
|
||||
}
|
||||
mUnzipListener.onSuccess(mDownloadEntity)
|
||||
} catch (e: Exception) {
|
||||
if (BuildConfig.DEBUG) throw e
|
||||
mUnzipListener.onFailure(mDownloadEntity, e)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 部分未知情况(有可能是项目配置问题,有可能时Zip包破损)原生的ZipFile无法打开压缩包,则启动以下备用方案
|
||||
* 具体复现机型:小米 9(Android 9)
|
||||
* 具体复现的压缩包:太空竞速2 具体可以咨询陈思雨
|
||||
*
|
||||
* 实现方式请参考:https://github.com/srikanth-lingala/zip4j
|
||||
* 注意使用该库的ZipInputStream依然无法解压,提示头文件丢失
|
||||
*
|
||||
* 后续如果有需要可以直接使用该方法进行解压
|
||||
*/
|
||||
private fun planB() {
|
||||
val zipPath = mDownloadEntity.path
|
||||
|
||||
val absolutePath = Environment.getExternalStorageDirectory().absolutePath
|
||||
|
||||
val zipFile = net.lingala.zip4j.ZipFile(zipPath)
|
||||
val progressMonitor = zipFile.progressMonitor
|
||||
zipFile.isRunInThread = true
|
||||
|
||||
val fileHeaders = zipFile.fileHeaders
|
||||
var unzipSize = 0L
|
||||
for (fileHeader in fileHeaders) {
|
||||
unzipSize += fileHeader.uncompressedSize;
|
||||
}
|
||||
|
||||
var unzipProgress = 0L
|
||||
var completedSize = 0L
|
||||
|
||||
for (fileHeader in fileHeaders) {
|
||||
if (canceled) {
|
||||
mUnzipListener.onCancel(mDownloadEntity)
|
||||
return
|
||||
}
|
||||
|
||||
// 暂时只需要解压xpk/obb文件
|
||||
val extension = fileHeader.fileName.getExtension()
|
||||
if (extension != XapkInstaller.XAPK_DATA_EXTENSION_NAME && extension != XapkInstaller.PACKAGE_EXTENSION_NAME) continue
|
||||
|
||||
|
||||
var unzipPath = ""
|
||||
if (extension == XapkInstaller.XAPK_DATA_EXTENSION_NAME) {
|
||||
unzipPath = absolutePath + File.separator + fileHeader.fileName
|
||||
|
||||
if (hasUnzipFile(unzipPath, fileHeader.uncompressedSize)) {
|
||||
mUnzipListener.onNext(mDownloadEntity, unzipPath)
|
||||
continue
|
||||
}
|
||||
|
||||
zipFile.extractFile(fileHeader.fileName, absolutePath)
|
||||
}
|
||||
|
||||
if (extension == XapkInstaller.PACKAGE_EXTENSION_NAME) {
|
||||
val downloadDir = FileUtils.getDownloadDir(HaloApp.getInstance().application)
|
||||
val unzipFileName = File(zipPath).nameWithoutExtension + "_" + fileHeader.fileName
|
||||
unzipPath = downloadDir + File.separator + unzipFileName
|
||||
|
||||
if (hasUnzipFile(unzipPath, fileHeader.uncompressedSize)) {
|
||||
mUnzipListener.onNext(mDownloadEntity, unzipPath)
|
||||
continue
|
||||
}
|
||||
|
||||
zipFile.extractFile(fileHeader.fileName, downloadDir, unzipFileName)
|
||||
}
|
||||
|
||||
throwExceptionInDebug("check unzipPath", unzipPath.isEmpty())
|
||||
|
||||
// 回调太频繁了,变态吗? 4K回调一次,还不能改,FUCK
|
||||
var filterCounter = 0
|
||||
val filterInterval = 1024 * 10
|
||||
while (progressMonitor.state != ProgressMonitor.State.READY) {
|
||||
filterCounter++
|
||||
if (filterCounter % filterInterval == 0) {
|
||||
unzipProgress = completedSize + progressMonitor.workCompleted
|
||||
mUnzipListener.onProgress(mDownloadEntity, unzipPath, unzipSize, unzipProgress)
|
||||
|
||||
if (canceled) {
|
||||
progressMonitor.isCancelAllTasks = true
|
||||
mUnzipListener.onCancel(mDownloadEntity)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
completedSize += fileHeader.uncompressedSize
|
||||
mUnzipListener.onNext(mDownloadEntity, unzipPath)
|
||||
}
|
||||
|
||||
mUnzipListener.onSuccess(mDownloadEntity)
|
||||
}
|
||||
|
||||
private fun hasUnzipFile(unzipPath: String, uncompressedSize: Long): Boolean {
|
||||
val unzipFile = File(unzipPath)
|
||||
if (unzipFile.exists() || unzipFile.length() == uncompressedSize) return true
|
||||
return false
|
||||
}
|
||||
|
||||
private fun getUnzipSize(path: String): Long {
|
||||
var totalSize = 0L
|
||||
|
||||
val calculateSizeClosure: (zip: ZipFile) -> Unit = { zip ->
|
||||
for (entry in zip.entries()) {
|
||||
totalSize += entry.size
|
||||
}
|
||||
}
|
||||
|
||||
// Kotlin 1.4.X 在安卓 4.4 以下使用 use 默认ZipFile 的流时会触发
|
||||
// java.lang.IncompatibleClassChangeError: interface not implemented 的 Error (Throwable)
|
||||
// 实测是不影响解压,所以这里换用 let 不关闭流,确保不闪退
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
ZipFile(File(path)).use { calculateSizeClosure.invoke(it) }
|
||||
} else {
|
||||
ZipFile(File(path)).let { calculateSizeClosure.invoke(it) }
|
||||
}
|
||||
return totalSize
|
||||
}
|
||||
}
|
||||
@ -60,6 +60,7 @@ import com.gh.ndownload.NDownloadBridge;
|
||||
import com.gh.ndownload.NDownloadService;
|
||||
import com.gh.vspace.VHelper;
|
||||
import com.halo.assistant.HaloApp;
|
||||
import com.lg.vspace.archive.common.Const;
|
||||
import com.lightgame.download.DataWatcher;
|
||||
import com.lightgame.download.DownloadConfig;
|
||||
import com.lightgame.download.DownloadDao;
|
||||
@ -413,6 +414,9 @@ public class DownloadManager implements DownloadStatusListener {
|
||||
// 将下载事件放入 downloadEntity 中供下载完成时取出使用
|
||||
downloadEntity.setExposureTrace(GsonUtils.toJson(downloadExposureEvent));
|
||||
|
||||
// 记录广告组 id
|
||||
ExtensionsKt.addMetaExtra(downloadEntity, Constants.AD_GROUP_ID, gameEntity.getAdGroupId());
|
||||
|
||||
// 保存所有游戏标签
|
||||
List<String> tags = new ArrayList<>();
|
||||
for (TagStyleEntity tag : gameEntity.getTagStyle()) {
|
||||
@ -445,7 +449,9 @@ public class DownloadManager implements DownloadStatusListener {
|
||||
"game_name", gameEntity.getName(),
|
||||
"game_id", gameEntity.getId(),
|
||||
"game_type", gameEntity.getCategoryChinese(),
|
||||
"game_schema_type", gameEntity.getGameBitChinese()
|
||||
"game_schema_type", gameEntity.getGameBitChinese(),
|
||||
"is_ad", TextUtils.isEmpty(gameEntity.getAdGroupId()) ? "false" : "true",
|
||||
"ad_group_id", gameEntity.getAdGroupId(),
|
||||
};
|
||||
List<String> vaList = new ArrayList<>(Arrays.asList(vaKvs));
|
||||
if (customPageTrackData != null) {
|
||||
@ -475,6 +481,8 @@ public class DownloadManager implements DownloadStatusListener {
|
||||
"game_id", gameEntity.getId(),
|
||||
"game_name", gameEntity.getName(),
|
||||
"game_type", gameEntity.getCategoryChinese(),
|
||||
"is_ad", TextUtils.isEmpty(gameEntity.getAdGroupId()) ? "false" : "true",
|
||||
"ad_group_id", gameEntity.getAdGroupId(),
|
||||
"game_label", String.join(",", tags),
|
||||
"game_schema_type", gameEntity.getGameBitChinese(),
|
||||
"page_name", GlobalActivityManager.getCurrentPageEntity().getPageName(),
|
||||
|
||||
@ -76,6 +76,8 @@ import com.gh.gamecenter.common.entity.SuggestType;
|
||||
import com.gh.gamecenter.common.eventbus.EBNetworkState;
|
||||
import com.gh.gamecenter.common.eventbus.EBReuse;
|
||||
import com.gh.gamecenter.common.exposure.meta.MetaUtil;
|
||||
import com.gh.gamecenter.common.pagelevel.PageLevel;
|
||||
import com.gh.gamecenter.common.pagelevel.PageLevelManager;
|
||||
import com.gh.gamecenter.common.retrofit.BiResponse;
|
||||
import com.gh.gamecenter.common.retrofit.Response;
|
||||
import com.gh.gamecenter.common.utils.DialogHelper;
|
||||
@ -88,7 +90,6 @@ import com.gh.gamecenter.core.AppExecutor;
|
||||
import com.gh.gamecenter.core.utils.ClassUtils;
|
||||
import com.gh.gamecenter.core.utils.DisplayUtils;
|
||||
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.core.utils.UrlFilterUtils;
|
||||
@ -228,9 +229,6 @@ public class MainActivity extends BaseActivity {
|
||||
},
|
||||
() -> {
|
||||
DirectUtils.directToSuggestion(MainActivity.this, SuggestType.APP, "APP闪退:", false, 100);
|
||||
MtaHelper.onEventWithBasicDeviceInfo(
|
||||
"闪退弹窗",
|
||||
"玩家操作", "点击反馈");
|
||||
return null;
|
||||
});
|
||||
} else {
|
||||
@ -238,17 +236,9 @@ public class MainActivity extends BaseActivity {
|
||||
, "暂不", "马上反馈",
|
||||
() -> {
|
||||
DirectUtils.directToSuggestion(MainActivity.this, SuggestType.APP, "APP闪退:", false, 100);
|
||||
MtaHelper.onEventWithBasicDeviceInfo(
|
||||
"闪退弹窗",
|
||||
"玩家操作", "点击反馈");
|
||||
return null;
|
||||
},
|
||||
() -> {
|
||||
MtaHelper.onEventWithBasicDeviceInfo(
|
||||
"闪退弹窗",
|
||||
"玩家操作", "点击关闭");
|
||||
return null;
|
||||
});
|
||||
() -> null);
|
||||
}
|
||||
}
|
||||
|
||||
@ -393,7 +383,6 @@ public class MainActivity extends BaseActivity {
|
||||
HistoryHelper.deleteAttentionVideoRecord();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -1110,6 +1099,28 @@ public class MainActivity extends BaseActivity {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initPageLevel(@Nullable Bundle savedInstanceState) {
|
||||
if (savedInstanceState != null) {
|
||||
if (savedInstanceState.getParcelable(PageLevelManager.KEY_PAGE_LEVEL) != null) {
|
||||
mPageLevel = savedInstanceState.getParcelable(PageLevelManager.KEY_PAGE_LEVEL);
|
||||
}
|
||||
}
|
||||
|
||||
if (mPageLevel == null) {
|
||||
mPageLevel = new PageLevel(
|
||||
PageLevel.TYPE_T,
|
||||
-1,
|
||||
-1,
|
||||
new HashMap<>(),
|
||||
-1,
|
||||
null);
|
||||
}
|
||||
|
||||
PageLevelManager.INSTANCE.setCurrentPageLevel(mPageLevel);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Pair<String, String> getBusinessId() {
|
||||
if (mMainWrapperFragment != null) {
|
||||
|
||||
@ -330,6 +330,9 @@ class SplashScreenActivity : BaseActivity(), ISplashScreen {
|
||||
}
|
||||
}
|
||||
|
||||
override fun initPageLevel(savedInstanceState: Bundle?) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val KEY_REGISTRATION_ID = "registration_id"
|
||||
|
||||
@ -422,9 +422,66 @@ public class LibaoDetailAdapter extends BaseRecyclerAdapter<ViewHolder> {
|
||||
ExtensionsKt.setDrawableEnd(holder.binding.libaodetailCondition, com.gh.gamecenter.common.R.drawable.ic_libao_activity_arrow, null, null);
|
||||
holder.binding.libaodetailCondition.setCompoundDrawablePadding(DisplayUtils.dip2px(4F));
|
||||
}
|
||||
} else if (mLibaoDetailEntity.getReceiveCondition() != null) {
|
||||
holder.binding.libaodetailCondition.setVisibility(View.VISIBLE);
|
||||
holder.binding.libaodetailConditionDescTv.setVisibility(View.VISIBLE);
|
||||
holder.binding.libaodetailCondition.setText("领取条件:");
|
||||
holder.binding.libaodetailConditionDescTv.setText(
|
||||
getConditionDescText(
|
||||
mGameEntity.getName(),
|
||||
mLibaoDetailEntity.getReceiveCondition().getStar(),
|
||||
mLibaoDetailEntity.getReceiveCondition().getWords()
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据评分和字数限制生成文案。
|
||||
*
|
||||
* @param gameName 游戏名称
|
||||
* @param star 评分选项 (-1: 无限制, 5: 5星好评, 4: 4星及以上, 3: 3星及以上, 2: 2星及以上, 1: 1星及以上)
|
||||
* @param words 字数限制 (-1: 无限制, n: 字数不少于n)
|
||||
* @return 生成的文案
|
||||
*/
|
||||
private String getConditionDescText(String gameName, int star, int words) {
|
||||
StringBuilder text = new StringBuilder();
|
||||
|
||||
text.append("发表《").append(gameName).append("》");
|
||||
|
||||
if (words == -1) {
|
||||
text.append("的游戏评价");
|
||||
} else {
|
||||
text.append("不少于").append(words).append("字的游戏评价");
|
||||
}
|
||||
|
||||
if (star != -1) {
|
||||
text.append("并给予");
|
||||
switch (star) {
|
||||
case 5:
|
||||
text.append("5星好评");
|
||||
break;
|
||||
case 4:
|
||||
text.append("4星及以上评分");
|
||||
break;
|
||||
case 3:
|
||||
text.append("3星及以上评分");
|
||||
break;
|
||||
case 2:
|
||||
text.append("2星及以上评分");
|
||||
break;
|
||||
case 1:
|
||||
text.append("1星及以上评分");
|
||||
break;
|
||||
default:
|
||||
//throw new IllegalArgumentException("Invalid star value: " + star); // 或者返回一个默认值,比如空字符串或错误消息
|
||||
return ""; // or return a default string or throw an exception.
|
||||
}
|
||||
}
|
||||
|
||||
return text.toString();
|
||||
}
|
||||
|
||||
public LibaoEntity getLibaoEntity() {
|
||||
return mLibaoEntity;
|
||||
}
|
||||
|
||||
@ -135,7 +135,7 @@ class DetailViewHolder(
|
||||
vUpdate = view.findViewById(R.id.v_update)
|
||||
tvUpdate = view.findViewById(R.id.tv_update)
|
||||
|
||||
context = view.context
|
||||
context = view.context.getActivity() ?: view.context
|
||||
com.gh.gamecenter.common.R.color.text_aw_primary.toColor()
|
||||
|
||||
var gameDownloadMode = gameEntity.getGameDownloadButtonMode()
|
||||
@ -605,6 +605,8 @@ class DetailViewHolder(
|
||||
"last_page_id", getLastPageEntity().pageId,
|
||||
"last_page_business_id", getLastPageEntity().pageBusinessId,
|
||||
"source", mGameEntity.exposureEvent?.source?.toString() ?: "",
|
||||
"is_ad", if (mGameEntity.adGroupId.isEmpty()) "false" else "true",
|
||||
"ad_group_id", mGameEntity.adGroupId,
|
||||
*mGameEntity.customPageTrackData?.toKV() ?: arrayOf()
|
||||
)
|
||||
CheckLoginUtils.checkLogin(mViewHolder.context, mEntrance) {
|
||||
|
||||
@ -8,14 +8,20 @@ import com.gh.gamecenter.common.utils.goneIf
|
||||
import com.gh.gamecenter.core.utils.DisplayUtils
|
||||
import com.gh.gamecenter.databinding.GameImageItemBinding
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.feature.exposure.ExposureEvent
|
||||
import com.gh.gamecenter.feature.exposure.IExposureProvider
|
||||
|
||||
/**
|
||||
* 游戏专题-大图-显示/只显示
|
||||
*/
|
||||
class GameImageViewHolder(var binding: GameImageItemBinding) : BaseRecyclerViewHolder<Any>(binding.root) {
|
||||
class GameImageViewHolder(var binding: GameImageItemBinding) : BaseRecyclerViewHolder<Any>(binding.root),
|
||||
IExposureProvider {
|
||||
|
||||
private var boundedGameEntity: GameEntity? = null
|
||||
|
||||
// 注意:专题详情的大图不能用此方法
|
||||
fun bindImage(entity: GameEntity, applyRoundCorner: Boolean = false) {
|
||||
boundedGameEntity = entity
|
||||
binding.run {
|
||||
gameContainer.goneIf(!(entity.type == "game" && entity.getApk().isNotEmpty()))
|
||||
gameIcon.displayGameIcon(entity)
|
||||
@ -28,11 +34,17 @@ class GameImageViewHolder(var binding: GameImageItemBinding) : BaseRecyclerViewH
|
||||
|
||||
if (applyRoundCorner) {
|
||||
val roundingParams = RoundingParams.fromCornersRadius(
|
||||
binding.root.resources.getDimensionPixelSize(com.gh.gamecenter.common.R.dimen.home_large_image_radius).toFloat()
|
||||
binding.root.resources.getDimensionPixelSize(com.gh.gamecenter.common.R.dimen.home_large_image_radius)
|
||||
.toFloat()
|
||||
)
|
||||
binding.gameImageIcon.hierarchy.roundingParams = roundingParams
|
||||
}
|
||||
|
||||
ImageUtils.displayWithAdaptiveHeight(binding.gameImageIcon, entity.image, width)
|
||||
}
|
||||
|
||||
override fun provideExposureData(): ExposureEvent? {
|
||||
return boundedGameEntity?.exposureEvent?.getFreshExposureEvent()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -28,7 +28,6 @@ class BannerAdapter(
|
||||
private var mExposureSourceList = ArrayList<ExposureSource>()
|
||||
|
||||
init {
|
||||
mItemData.exposureEventList = arrayListOf()
|
||||
mExposureSourceList.addAll(mExposureSource)
|
||||
mExposureSourceList.add(ExposureSource("精选页轮播图"))
|
||||
}
|
||||
@ -67,7 +66,6 @@ class BannerAdapter(
|
||||
payload.sourcePageName = it[PageSwitchDataHelper.PAGE_BUSINESS_NAME]
|
||||
}
|
||||
}
|
||||
mItemData.exposureEventList?.add(exposureEvent)
|
||||
}
|
||||
|
||||
view.setOnClickListener {
|
||||
@ -88,12 +86,6 @@ class BannerAdapter(
|
||||
mBanners = banners
|
||||
}
|
||||
|
||||
if (mItemData.exposureEventList != null) {
|
||||
mItemData.exposureEventList?.clear()
|
||||
mItemData = itemData
|
||||
mItemData.exposureEventList = arrayListOf()
|
||||
}
|
||||
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
@ -13,27 +13,27 @@ import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
|
||||
import com.gh.gamecenter.common.constant.ItemViewType
|
||||
import com.gh.common.exposure.IExposable
|
||||
import com.gh.common.util.*
|
||||
import com.gh.gamecenter.R
|
||||
import com.gh.gamecenter.common.viewholder.FooterViewHolder
|
||||
import com.gh.gamecenter.common.baselist.ListAdapter
|
||||
import com.gh.gamecenter.common.exposure.ExposureSource
|
||||
import com.gh.gamecenter.common.utils.*
|
||||
import com.gh.gamecenter.core.runOnIoThread
|
||||
import com.gh.gamecenter.core.utils.PageSwitchDataHelper
|
||||
import com.gh.gamecenter.databinding.*
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.entity.SpecialCatalogEntity
|
||||
import com.gh.gamecenter.feature.exposure.ExposureEvent
|
||||
import com.gh.gamecenter.feature.exposure.IExposureProvider
|
||||
import com.gh.gamecenter.subject.SubjectActivity.Companion.startSubjectActivity
|
||||
|
||||
class SpecialCatalogAdapter(
|
||||
context: Context,
|
||||
private val mCatalogViewModel: SpecialCatalogViewModel,
|
||||
private val mLastPageDataMap: HashMap<String, String>? = null
|
||||
) : ListAdapter<SpecialCatalogItemData>(context), IExposable {
|
||||
) : ListAdapter<SpecialCatalogItemData>(context) {
|
||||
|
||||
private val mExposureEventSparseArray: SparseArray<ExposureEvent> = SparseArray()
|
||||
var isAutoScroll = true
|
||||
var isBannerSizeMoreThanOne = false
|
||||
|
||||
@ -153,8 +153,8 @@ class SpecialCatalogAdapter(
|
||||
payload.sourcePageName = it[PageSwitchDataHelper.PAGE_BUSINESS_NAME]
|
||||
}
|
||||
}
|
||||
mExposureEventSparseArray.append(position, exposureEvent)
|
||||
}
|
||||
holder.exposureEvent = exposureEvent
|
||||
root.setOnClickListener {
|
||||
DirectUtils.directToLinkPage(
|
||||
mContext,
|
||||
@ -208,28 +208,32 @@ class SpecialCatalogAdapter(
|
||||
is CatalogSubjectItemHolder -> {
|
||||
val subject = mEntityList[position].subject!!
|
||||
|
||||
val exposureList = arrayListOf<ExposureEvent>()
|
||||
for ((index, game) in subject.link.data.withIndex()) {
|
||||
game.sequence = index
|
||||
game.subjectName = subject.link.text
|
||||
game.outerSequence = mEntityList[position].position
|
||||
runOnIoThread(isLightWeightTask = true) {
|
||||
for ((index, game) in subject.link.data.withIndex()) {
|
||||
game.sequence = index
|
||||
game.subjectName = subject.link.text
|
||||
game.outerSequence = mEntityList[position].position
|
||||
|
||||
val exposureEvent = ExposureEvent.createEventWithSourceConcat(
|
||||
game,
|
||||
mCatalogViewModel.basicExposureSource,
|
||||
listOf(ExposureSource("精选页专题", subject.link.text ?: ""))
|
||||
).apply {
|
||||
mLastPageDataMap?.let {
|
||||
payload.sourcePage = it[PageSwitchDataHelper.PAGE_BUSINESS_TYPE]
|
||||
payload.sourcePageId = it[PageSwitchDataHelper.PAGE_BUSINESS_ID]
|
||||
payload.sourcePageName = it[PageSwitchDataHelper.PAGE_BUSINESS_NAME]
|
||||
val exposureEvent = ExposureEvent.createEventWithSourceConcat(
|
||||
game,
|
||||
mCatalogViewModel.basicExposureSource,
|
||||
listOf(ExposureSource("精选页专题", subject.link.text ?: ""))
|
||||
).apply {
|
||||
mLastPageDataMap?.let {
|
||||
payload.sourcePage = it[PageSwitchDataHelper.PAGE_BUSINESS_TYPE]
|
||||
payload.sourcePageId = it[PageSwitchDataHelper.PAGE_BUSINESS_ID]
|
||||
payload.sourcePageName = it[PageSwitchDataHelper.PAGE_BUSINESS_NAME]
|
||||
}
|
||||
}
|
||||
|
||||
game.exposureEvent = exposureEvent
|
||||
|
||||
if (game.adGroupId.isNotEmpty() && !game.isAdRequestReported) {
|
||||
AdHelper.reportAdRequest(exposureEvent)
|
||||
game.isAdRequestReported = true
|
||||
}
|
||||
}
|
||||
exposureList.add(exposureEvent)
|
||||
|
||||
game.exposureEvent = exposureEvent
|
||||
}
|
||||
mEntityList[position].exposureEventList = exposureList
|
||||
|
||||
holder.bindSubject(subject.link.data, mEntityList[position].position)
|
||||
}
|
||||
@ -265,10 +269,6 @@ class SpecialCatalogAdapter(
|
||||
}
|
||||
}
|
||||
|
||||
override fun getEventByPosition(pos: Int): ExposureEvent? = mExposureEventSparseArray.get(pos)
|
||||
|
||||
override fun getEventListByPosition(pos: Int): List<ExposureEvent>? = mEntityList[pos].exposureEventList
|
||||
|
||||
inner class CatalogBannerItemHolder(val binding: CatalogBannerItemBinding) :
|
||||
BaseRecyclerViewHolder<Any>(binding.root) {
|
||||
|
||||
@ -393,7 +393,14 @@ class SpecialCatalogAdapter(
|
||||
}
|
||||
}
|
||||
|
||||
inner class CatalogImageItemHolder(val binding: CatalogImageItemBinding) : BaseRecyclerViewHolder<Any>(binding.root)
|
||||
inner class CatalogImageItemHolder(val binding: CatalogImageItemBinding) :
|
||||
BaseRecyclerViewHolder<Any>(binding.root), IExposureProvider {
|
||||
var exposureEvent: ExposureEvent? = null
|
||||
|
||||
override fun provideExposureData(): ExposureEvent? {
|
||||
return exposureEvent?.getFreshExposureEvent()
|
||||
}
|
||||
}
|
||||
|
||||
inner class CatalogHeaderItemHolder(val binding: CatalogHeaderItemBinding) :
|
||||
BaseRecyclerViewHolder<Any>(binding.root)
|
||||
|
||||
@ -3,14 +3,15 @@ package com.gh.gamecenter.catalog
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import com.ethanhua.skeleton.Skeleton
|
||||
import com.gh.common.exposure.DefaultExposureStateChangeListener
|
||||
import com.gh.gamecenter.common.constant.Constants
|
||||
import com.gh.common.exposure.ExposureListener
|
||||
import com.gh.gamecenter.common.constant.EntranceConsts
|
||||
import com.gh.gamecenter.common.utils.viewModelProvider
|
||||
import com.gh.gamecenter.R
|
||||
import com.gh.gamecenter.common.baselist.ListFragment
|
||||
import com.gh.gamecenter.common.utils.toColor
|
||||
import com.gh.gamecenter.databinding.FragmentListBaseSkeletonBinding
|
||||
import com.gh.gamecenter.feature.exposure.addExposureHelper
|
||||
|
||||
class SpecialCatalogFragment : ListFragment<SpecialCatalogItemData, SpecialCatalogViewModel>() {
|
||||
|
||||
@ -21,8 +22,6 @@ class SpecialCatalogFragment : ListFragment<SpecialCatalogItemData, SpecialCatal
|
||||
private var mAdapter: SpecialCatalogAdapter? = null
|
||||
private var mLastPageDataMap: HashMap<String, String>? = null
|
||||
|
||||
private lateinit var mExposureListener: ExposureListener
|
||||
|
||||
override fun getLayoutId() = 0
|
||||
|
||||
override fun getInflatedLayout() = mBinding.root
|
||||
@ -43,7 +42,6 @@ class SpecialCatalogFragment : ListFragment<SpecialCatalogItemData, SpecialCatal
|
||||
mLastPageDataMap
|
||||
).apply {
|
||||
mAdapter = this
|
||||
mExposureListener = ExposureListener(this@SpecialCatalogFragment, this)
|
||||
}
|
||||
|
||||
override fun getItemDecoration() = null
|
||||
@ -51,7 +49,6 @@ class SpecialCatalogFragment : ListFragment<SpecialCatalogItemData, SpecialCatal
|
||||
override fun isAutomaticLoad(): Boolean = false
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
|
||||
mIsCategoryV2 = arguments?.getBoolean(EntranceConsts.KEY_IS_CATEGORY_V2) ?: false
|
||||
mCatalogId = arguments?.getString(EntranceConsts.KEY_CATALOG_ID) ?: ""
|
||||
mCatalogTitle = arguments?.getString(EntranceConsts.KEY_CATALOG_TITLE) ?: ""
|
||||
@ -65,6 +62,8 @@ class SpecialCatalogFragment : ListFragment<SpecialCatalogItemData, SpecialCatal
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
mListRv.addExposureHelper(this, DefaultExposureStateChangeListener())
|
||||
|
||||
val skeletonLayoutId =
|
||||
if (mIsCategoryV2) R.layout.fragment_special_catalog_second_skeleton else R.layout.fragment_special_catalog_first_skeleton
|
||||
mSkeletonScreen = Skeleton.bind(mBinding.listSkeleton)
|
||||
@ -77,8 +76,6 @@ class SpecialCatalogFragment : ListFragment<SpecialCatalogItemData, SpecialCatal
|
||||
.load(skeletonLayoutId)
|
||||
.show()
|
||||
onLoadRefresh()
|
||||
|
||||
mListRv.addOnScrollListener(mExposureListener)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package com.gh.gamecenter.catalog
|
||||
|
||||
import com.gh.gamecenter.feature.exposure.ExposureEvent
|
||||
import com.gh.gamecenter.entity.SpecialCatalogEntity
|
||||
|
||||
class SpecialCatalogItemData(
|
||||
@ -11,5 +10,4 @@ class SpecialCatalogItemData(
|
||||
val subjectCollection: SpecialCatalogEntity? = null,
|
||||
|
||||
var position: Int = 0,
|
||||
var exposureEventList: ArrayList<ExposureEvent>? = null
|
||||
)
|
||||
|
||||
@ -9,6 +9,8 @@ import com.gh.gamecenter.common.utils.toBinding
|
||||
import com.gh.gamecenter.common.utils.toColor
|
||||
import com.gh.gamecenter.databinding.CatalogSubjectGameItemBinding
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.feature.exposure.ExposureEvent
|
||||
import com.gh.gamecenter.feature.exposure.IExposureProvider
|
||||
import com.lightgame.adapter.BaseRecyclerAdapter
|
||||
|
||||
class SpecialCatalogSubjectAdapter(
|
||||
@ -41,6 +43,8 @@ class SpecialCatalogSubjectAdapter(
|
||||
}
|
||||
|
||||
val entity = mList[position]
|
||||
|
||||
holder.bindGameEntity(entity)
|
||||
gameIcon.displayGameIcon(entity)
|
||||
gameName.text = entity.name
|
||||
gameName.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(mContext))
|
||||
@ -84,5 +88,15 @@ class SpecialCatalogSubjectAdapter(
|
||||
}
|
||||
|
||||
class CatalogSubjectGameItemViewHolder(val binding: CatalogSubjectGameItemBinding) :
|
||||
BaseRecyclerViewHolder<Any>(binding.root)
|
||||
BaseRecyclerViewHolder<Any>(binding.root), IExposureProvider {
|
||||
private var boundedGameEntity: GameEntity? = null
|
||||
|
||||
fun bindGameEntity(gameEntity: GameEntity) {
|
||||
boundedGameEntity = gameEntity
|
||||
}
|
||||
|
||||
override fun provideExposureData(): ExposureEvent? {
|
||||
return boundedGameEntity?.exposureEvent?.getFreshExposureEvent()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -35,7 +35,6 @@ class SpecialCatalogSubjectCollectionAdapter(
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||
CatalogSubjectCollectionListItemViewHolder(parent.toBinding())
|
||||
|
||||
|
||||
override fun onBindViewHolder(holder: CatalogSubjectCollectionListItemViewHolder, position: Int) {
|
||||
holder.binding.run {
|
||||
root.layoutParams = (root.layoutParams as ViewGroup.MarginLayoutParams).apply {
|
||||
|
||||
@ -9,6 +9,7 @@ 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.entity.SpecialCatalogEntity
|
||||
import com.gh.gamecenter.feature.exposure.ExposureConstants
|
||||
import com.gh.gamecenter.retrofit.RetrofitManager
|
||||
import com.halo.assistant.HaloApp
|
||||
import io.reactivex.Observable
|
||||
@ -60,6 +61,8 @@ class SpecialCatalogViewModel(
|
||||
game.containerType =
|
||||
if (mIsCategoryV2) ExposureEntity.CATEGORY_V2_ID else ExposureEntity.CATEGORY_ID
|
||||
game.containerId = mCatalogId
|
||||
game.subPageCode = ExposureConstants.CATEGORY_V2
|
||||
game.subPageId = mCatalogId
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,82 +1,89 @@
|
||||
package com.gh.gamecenter.category2
|
||||
|
||||
import android.content.Context
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
|
||||
import com.gh.gamecenter.common.utils.dip2px
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
import com.gh.gamecenter.common.utils.toBinding
|
||||
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(
|
||||
context: Context,
|
||||
private val mViewModel: CategoryV2ViewModel,
|
||||
private var mList: List<CategoryEntity>
|
||||
) : BaseRecyclerAdapter<CategoryDirectoryAdapter.CategoryDirectoryItemViewHolder>(context) {
|
||||
private val listener: SearchCategoryPop.OnSearchCategoryListener
|
||||
) : RecyclerView.Adapter<CategoryDirectoryAdapter.CategoryDirectoryItemViewHolder>() {
|
||||
|
||||
val width = mContext.resources.displayMetrics.widthPixels * 260 / 360
|
||||
private val data = arrayListOf<CategoryEntity>()
|
||||
|
||||
fun setListData(list: List<CategoryEntity>) {
|
||||
mList = list
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
fun setListData(newData: List<CategoryEntity>) {
|
||||
data.clear()
|
||||
data.addAll(newData)
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
override fun getItemCount() = mList.size
|
||||
override fun getItemCount() = data.size
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||
CategoryDirectoryItemViewHolder(CategoryDirectoryItemBinding.inflate(mLayoutInflater))
|
||||
CategoryDirectoryItemViewHolder(listener, parent.toBinding())
|
||||
|
||||
override fun onBindViewHolder(holder: CategoryDirectoryItemViewHolder, position: Int) {
|
||||
holder.binding.run {
|
||||
root.layoutParams = root.layoutParams?.apply {
|
||||
width = mContext.resources.displayMetrics.widthPixels * 260 / 360
|
||||
} ?: RecyclerView.LayoutParams(width, RecyclerView.LayoutParams.WRAP_CONTENT)
|
||||
holder.onBind(position, data[position])
|
||||
}
|
||||
|
||||
val padTop = if (position == 0) 16F.dip2px() else 24F.dip2px()
|
||||
root.setPadding(16F.dip2px(), padTop, 16F.dip2px(), 0)
|
||||
override fun onBindViewHolder(holder: CategoryDirectoryItemViewHolder, position: Int, payloads: MutableList<Any>) {
|
||||
if (payloads.isEmpty()) {
|
||||
super.onBindViewHolder(holder, position, payloads)
|
||||
} else {
|
||||
holder.notifyItemSelectedChanged()
|
||||
}
|
||||
|
||||
val entity = mList[position]
|
||||
title.text = entity.name
|
||||
title.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(mContext))
|
||||
}
|
||||
|
||||
subCategoryRv.run {
|
||||
if (adapter is SubCategoryAdapter) {
|
||||
layoutManager = GridLayoutManager(mContext, 3)
|
||||
adapter = entity.data?.let {
|
||||
SubCategoryAdapter(
|
||||
mContext,
|
||||
mViewModel,
|
||||
it,
|
||||
position
|
||||
)
|
||||
}
|
||||
} 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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
fun notifyItemSelectedChanged(parentId: String) {
|
||||
val position = data.indexOfFirst { it.id == parentId }
|
||||
if (position != -1) {
|
||||
notifyItemChanged(position, "")
|
||||
}
|
||||
}
|
||||
|
||||
class CategoryDirectoryItemViewHolder(val binding: CategoryDirectoryItemBinding) :
|
||||
BaseRecyclerViewHolder<Any>(binding.root)
|
||||
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
|
||||
}
|
||||
}
|
||||
binding.subCategoryRv.adapter = childAdapter
|
||||
binding.subCategoryRv.addItemDecoration(
|
||||
GridSpacingItemColorDecoration(
|
||||
context,
|
||||
8,
|
||||
8,
|
||||
com.gh.gamecenter.common.R.color.transparent
|
||||
)
|
||||
)
|
||||
}
|
||||
childAdapter.setData(position, item)
|
||||
}
|
||||
|
||||
fun notifyItemSelectedChanged() {
|
||||
childAdapter.notifyItemRangeChanged(0, childAdapter.itemCount, "")
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -28,6 +28,11 @@ class CategoryV2Activity : DownloadToolbarActivity() {
|
||||
|
||||
override fun isAutoResetViewBackgroundEnabled() = true
|
||||
|
||||
override fun getBusinessId(): Pair<String, String> {
|
||||
val categoryId = intent.extras?.getString(EntranceConsts.KEY_CATEGORY_ID, "") ?: ""
|
||||
return Pair(categoryId, "")
|
||||
}
|
||||
|
||||
override fun onDarkModeChanged() {
|
||||
super.onDarkModeChanged()
|
||||
updateStatusBarColor(com.gh.gamecenter.common.R.color.ui_surface, com.gh.gamecenter.common.R.color.ui_surface)
|
||||
@ -37,8 +42,8 @@ class CategoryV2Activity : DownloadToolbarActivity() {
|
||||
companion object {
|
||||
fun getIntent(context: Context, catalogId: String, catalogTitle: String, entrance: String): Intent {
|
||||
val bundle = Bundle()
|
||||
bundle.putString(EntranceConsts.KEY_CATEGORY_ID, catalogId)
|
||||
bundle.putString(EntranceConsts.KEY_CATEGORY_TITLE, catalogTitle)
|
||||
bundle.putString(EntranceConsts.KEY_PAGE_ID, catalogId)
|
||||
bundle.putString(EntranceConsts.KEY_PAGE_NAME, catalogTitle)
|
||||
bundle.putString(EntranceConsts.KEY_ENTRANCE, entrance)
|
||||
return getTargetIntent(context, CategoryV2Activity::class.java, CategoryV2Fragment::class.java, bundle)
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
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
|
||||
@ -13,11 +14,13 @@ 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) =
|
||||
@ -28,25 +31,35 @@ class CategoryV2Adapter(
|
||||
val catalogEntity = mList[position]
|
||||
catalogName.text = catalogEntity.name
|
||||
recommendTag.goneIf(!catalogEntity.recommended)
|
||||
if (catalogEntity.name == mViewModel.selectedCategoryName) {
|
||||
if (position == selectedPosition) {
|
||||
selectedTag.visibility = View.VISIBLE
|
||||
catalogName.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(mContext))
|
||||
catalogName.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(mContext))
|
||||
catalogName.setTypeface(null, Typeface.BOLD)
|
||||
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_primary.toColor(mContext))
|
||||
catalogName.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(mContext))
|
||||
catalogName.setTypeface(null, Typeface.NORMAL)
|
||||
root.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_background.toColor(mContext))
|
||||
}
|
||||
root.setOnClickListener {
|
||||
if (catalogEntity.name != mViewModel.selectedCategoryName) {
|
||||
mViewModel.selectedCategoryName = catalogEntity.name
|
||||
mFragment.changeCategory(position)
|
||||
if (position != selectedPosition) {
|
||||
mViewModel.logClickSide()
|
||||
notifyDataSetChanged()
|
||||
mViewModel.selectSidebarsPosition(position)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
@ -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.view.GravityCompat
|
||||
import androidx.drawerlayout.widget.DrawerLayout
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.core.widget.TextViewCompat
|
||||
import androidx.fragment.app.viewModels
|
||||
import com.gh.common.util.LogUtils
|
||||
import com.gh.gamecenter.R
|
||||
import com.gh.gamecenter.SearchActivity
|
||||
@ -22,35 +22,49 @@ 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 var mViewModel: CategoryV2ViewModel? = null
|
||||
|
||||
private val viewModel by viewModels<CategoryV2ViewModel>()
|
||||
|
||||
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 mCategoryId: String = ""
|
||||
private var mCategoryTitle: String = ""
|
||||
private var pageId: String = ""
|
||||
private var pageName: 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) {
|
||||
mViewModel?.run {
|
||||
outState.putInt(EntranceConsts.KEY_LAST_SELECTED_POSITION, selectedCategoryPosition)
|
||||
}
|
||||
outState.putInt(EntranceConsts.KEY_LAST_SELECTED_POSITION, viewModel.selectedSidebarsPosition.value ?: 0)
|
||||
super.onSaveInstanceState(outState)
|
||||
}
|
||||
|
||||
@ -61,100 +75,40 @@ 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)
|
||||
|
||||
// 除了这里以外,下面还有一个判断是否为首页 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()
|
||||
viewModel.logAppearance()
|
||||
|
||||
super.onFragmentFirstVisible()
|
||||
|
||||
viewModel.loadData(pageId, pageName)
|
||||
}
|
||||
|
||||
|
||||
override fun initRealView() {
|
||||
super.initRealView()
|
||||
initMenu(R.menu.menu_search)
|
||||
setNavigationTitle(mCategoryTitle)
|
||||
setNavigationTitle(pageName)
|
||||
|
||||
mBinding?.run {
|
||||
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()))
|
||||
}
|
||||
tvMoreCategory.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()))
|
||||
tvTagNumber.typeface =
|
||||
Typeface.createFromAsset(requireContext().assets, Constants.DIN_FONT_PATH)
|
||||
}
|
||||
|
||||
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 {
|
||||
viewModel.sidebarsLiveData.observe(viewLifecycleOwner) { sidebars ->
|
||||
mBinding?.run {
|
||||
reuseLoading.root.visibility = View.GONE
|
||||
if (it != null) {
|
||||
if (sidebars != null) {
|
||||
reuseNoConnection.root.visibility = View.GONE
|
||||
categoryContainer.visibility = View.VISIBLE
|
||||
reuseNoneData.root.visibility = View.GONE
|
||||
mEntity = it
|
||||
(mEntity!!.sidebars as ArrayList).run {
|
||||
mEntity = sidebars
|
||||
(sidebars.sidebars as ArrayList).run {
|
||||
val allEntity = SidebarsEntity.SidebarEntity(name = "全部", categoryId = "all")
|
||||
if (mEntity!!.hasSpecial) {
|
||||
add(0, allEntity)
|
||||
if (sidebars.hasSpecial) {
|
||||
val specialEntity = SidebarsEntity.SidebarEntity(name = "精选")
|
||||
add(0, specialEntity)
|
||||
add(1, allEntity)
|
||||
} else {
|
||||
add(0, allEntity)
|
||||
add(1, specialEntity)
|
||||
}
|
||||
}
|
||||
initView()
|
||||
@ -165,12 +119,70 @@ class CategoryV2Fragment : LazyFragment() {
|
||||
reuseNoConnection.root.setOnClickListener {
|
||||
reuseNoConnection.root.visibility = View.GONE
|
||||
reuseLoading.root.visibility = View.VISIBLE
|
||||
mViewModel?.getSidebars()
|
||||
mViewModel?.getCategoryDirectories()
|
||||
viewModel.loadData(pageId, pageName)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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() {
|
||||
@ -181,15 +193,7 @@ 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({
|
||||
@ -204,7 +208,7 @@ class CategoryV2Fragment : LazyFragment() {
|
||||
override fun onMenuItemClick(menuItem: MenuItem?) {
|
||||
menuItem?.run {
|
||||
if (itemId == R.id.menu_search) {
|
||||
LogUtils.uploadSearchGame("access_to_search", mCategoryTitle, "", "")
|
||||
LogUtils.uploadSearchGame("access_to_search", pageName, "", "")
|
||||
val intent = SearchActivity.getIntent(
|
||||
requireContext(),
|
||||
false,
|
||||
@ -217,63 +221,64 @@ class CategoryV2Fragment : LazyFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun initDirectoryView(list: List<CategoryEntity>) {
|
||||
mBinding?.run {
|
||||
mViewModel?.run {
|
||||
if (directoryRv.adapter != null) {
|
||||
(directoryRv.adapter as? CategoryDirectoryAdapter)?.setListData(list)
|
||||
} else {
|
||||
directoryRv.layoutManager = FixLinearLayoutManager(requireContext())
|
||||
directoryRv.adapter = CategoryDirectoryAdapter(
|
||||
requireContext(),
|
||||
this,
|
||||
list
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
resetTv.setOnClickListener {
|
||||
mViewModel?.logClickReset("全部类别")
|
||||
confirmTv.text = "确定"
|
||||
mViewModel?.resetDirectoryList()
|
||||
mCategoryV2ListFragment?.changeCategoryTab()
|
||||
}
|
||||
|
||||
confirmTv.setOnClickListener {
|
||||
mViewModel?.logClickDetermine()
|
||||
drawerLayout.closeDrawer(GravityCompat.START)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun initView() {
|
||||
if (mEntity?.sidebars?.isNullOrEmpty() == true || mViewModel == null) return
|
||||
if (mEntity?.sidebars.isNullOrEmpty()) return
|
||||
initSelectedCategory()
|
||||
initCategoryRv()
|
||||
initContentFragment()
|
||||
|
||||
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()
|
||||
}
|
||||
}, 200)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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 initSelectedCategory() {
|
||||
mEntity?.run {
|
||||
mViewModel?.run {
|
||||
if (mLastSelectedPosition != -1) {
|
||||
selectedCategoryPosition = mLastSelectedPosition
|
||||
selectedCategoryName = sidebars[mLastSelectedPosition].name
|
||||
} else {
|
||||
selectedCategoryPosition = 0
|
||||
selectedCategoryName = sidebars[0].name
|
||||
}
|
||||
}
|
||||
if (mLastSelectedPosition != -1) {
|
||||
viewModel.selectSidebarsPosition(mLastSelectedPosition)
|
||||
} else {
|
||||
// 默认选中第 0 个 位置
|
||||
viewModel.selectSidebarsPosition(0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun initCategoryRv() {
|
||||
mEntity?.run {
|
||||
mViewModel?.run {
|
||||
viewModel.run {
|
||||
mBinding?.categoryRv?.layoutManager = FixLinearLayoutManager(requireContext())
|
||||
mBinding?.categoryRv?.adapter = CategoryV2Adapter(
|
||||
requireContext(),
|
||||
this@CategoryV2Fragment,
|
||||
this,
|
||||
sidebars
|
||||
)
|
||||
@ -281,226 +286,91 @@ class CategoryV2Fragment : LazyFragment() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun initContentFragment() {
|
||||
mEntity?.apply {
|
||||
mViewModel?.apply {
|
||||
if (hasSpecial && selectedCategoryPosition == 0) {
|
||||
initSpecialCatalogFragment()
|
||||
} else {
|
||||
initCategoryV2ListFragment()
|
||||
private fun onSelectedPositionChanged(position: Int) {
|
||||
mEntity?.run {
|
||||
viewModel.run {
|
||||
clearSelectedTag()
|
||||
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
|
||||
|
||||
// 第一次点"全部"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)
|
||||
} 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
mCategoryV2ListFragment!!,
|
||||
CategoryV2ListFragment::class.java.name
|
||||
)
|
||||
.replace(R.id.gamesContainer, targetFragment, targetFragment::class.java.name)
|
||||
.commitAllowingStateLoss()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
selectedCategoryPosition = position
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun openDirectoryLayout() {
|
||||
private fun updateMoreCategory(size: Int) {
|
||||
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
|
||||
}
|
||||
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"
|
||||
}
|
||||
} 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)
|
||||
}
|
||||
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?.categoryRv?.adapter?.run {
|
||||
mBinding?.categoryRv?.recycledViewPool?.clear()
|
||||
notifyItemRangeChanged(0, itemCount)
|
||||
}
|
||||
mBinding?.directoryRv?.adapter?.run {
|
||||
mBinding?.directoryRv?.recycledViewPool?.clear()
|
||||
notifyItemRangeChanged(0, itemCount)
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val SPECIAL_CATEGORY_POSITION = 1
|
||||
}
|
||||
}
|
||||
@ -1,12 +1,11 @@
|
||||
package com.gh.gamecenter.category2
|
||||
|
||||
import android.content.Context
|
||||
import android.util.SparseArray
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.gh.common.databind.BindingAdapters
|
||||
import com.gh.common.exposure.IExposable
|
||||
import com.gh.common.util.AdHelper
|
||||
import com.gh.common.util.DownloadItemUtils
|
||||
import com.gh.gamecenter.GameDetailActivity
|
||||
import com.gh.gamecenter.adapter.viewholder.GameViewHolder
|
||||
@ -17,6 +16,7 @@ import com.gh.gamecenter.common.exposure.ExposureSource
|
||||
import com.gh.gamecenter.common.utils.*
|
||||
import com.gh.gamecenter.common.view.DrawableView
|
||||
import com.gh.gamecenter.common.viewholder.FooterViewHolder
|
||||
import com.gh.gamecenter.core.runOnIoThread
|
||||
import com.gh.gamecenter.core.utils.PageSwitchDataHelper
|
||||
import com.gh.gamecenter.core.utils.StringUtils
|
||||
import com.gh.gamecenter.databinding.CategoryGameItemBinding
|
||||
@ -24,6 +24,7 @@ import com.gh.gamecenter.eventbus.EBDownloadStatus
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.feature.exposure.ExposureEvent
|
||||
import com.gh.gamecenter.feature.exposure.ExposureType
|
||||
import com.gh.gamecenter.feature.exposure.IExposureProvider
|
||||
import com.gh.gamecenter.feature.game.GameItemViewHolder
|
||||
import com.lightgame.download.DownloadEntity
|
||||
import org.json.JSONException
|
||||
@ -35,9 +36,7 @@ class CategoryV2ListAdapter(
|
||||
private val mCategoryViewModel: CategoryV2ViewModel,
|
||||
private val mEntrance: String?,
|
||||
private var mLastPageDataMap: HashMap<String, String>? = null
|
||||
) : ListAdapter<GameEntity>(context), IExposable {
|
||||
|
||||
private val mExposureEventSparseArray: SparseArray<ExposureEvent> = SparseArray()
|
||||
) : ListAdapter<GameEntity>(context) {
|
||||
|
||||
val positionAndPackageMap = HashMap<String, Int>()
|
||||
|
||||
@ -76,8 +75,15 @@ 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
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -94,20 +100,12 @@ class CategoryV2ListAdapter(
|
||||
holder.bindGameItem(gameEntity)
|
||||
holder.initServerType(gameEntity)
|
||||
|
||||
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 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 exposureSources = ArrayList<ExposureSource>()
|
||||
if (!mViewModel.exposureSourceList.isNullOrEmpty()) {
|
||||
@ -126,7 +124,13 @@ class CategoryV2ListAdapter(
|
||||
payload.sourcePageName = it[PageSwitchDataHelper.PAGE_BUSINESS_NAME]
|
||||
}
|
||||
}
|
||||
mExposureEventSparseArray.put(position, event)
|
||||
|
||||
runOnIoThread(isLightWeightTask = true) {
|
||||
if (gameEntity.adGroupId.isNotEmpty() && !gameEntity.isAdRequestReported) {
|
||||
AdHelper.reportAdRequest(event)
|
||||
gameEntity.isAdRequestReported = true
|
||||
}
|
||||
}
|
||||
|
||||
holder.itemView.setOnClickListener {
|
||||
GameDetailActivity.startGameDetailActivity(
|
||||
@ -172,8 +176,13 @@ class CategoryV2ListAdapter(
|
||||
) {
|
||||
val trackEvent = JSONObject()
|
||||
try {
|
||||
trackEvent.put("navigation_bar_name", mCategoryViewModel.selectedCategoryName)
|
||||
trackEvent.put("game_tag", mCategoryViewModel.selectedCategoryList.map { it.name })
|
||||
trackEvent.put("navigation_bar_name", mCategoryViewModel.selectedSidebarsName)
|
||||
trackEvent.put(
|
||||
"game_tag",
|
||||
(mCategoryViewModel.selectedSubCategories.value ?: listOf())
|
||||
.map {
|
||||
it.category.name
|
||||
})
|
||||
trackEvent.put("game_status", gameEntity.category)
|
||||
trackEvent.put(
|
||||
"inclusion_size",
|
||||
@ -227,17 +236,13 @@ class CategoryV2ListAdapter(
|
||||
}
|
||||
}
|
||||
|
||||
override fun getEventByPosition(pos: Int): ExposureEvent? {
|
||||
return mExposureEventSparseArray.get(pos)
|
||||
}
|
||||
|
||||
override fun getEventListByPosition(pos: Int): List<ExposureEvent>? {
|
||||
return null
|
||||
}
|
||||
|
||||
inner class CategoryGameItemViewHolder(val binding: CategoryGameItemBinding) :
|
||||
BaseRecyclerViewHolder<Any>(binding.root) {
|
||||
BaseRecyclerViewHolder<Any>(binding.root), IExposureProvider {
|
||||
|
||||
private var boundedGameEntity: GameEntity? = null
|
||||
|
||||
fun bindGameItem(gameEntity: GameEntity) {
|
||||
boundedGameEntity = gameEntity
|
||||
binding.run {
|
||||
gameIconView.displayGameIcon(gameEntity)
|
||||
gameRating.textSize = if (gameEntity.commentCount > 3) 12F else 10F
|
||||
@ -266,24 +271,34 @@ 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
|
||||
}
|
||||
|
||||
// 由于RecyclerView的复用机制 需要每次测量gameName的宽
|
||||
binding.gameName.requestLayout()
|
||||
}
|
||||
|
||||
override fun provideExposureData(): ExposureEvent? {
|
||||
return boundedGameEntity?.exposureEvent?.getFreshExposureEvent()
|
||||
}
|
||||
}
|
||||
|
||||
inner class CategoryGameViewHolder(val binding: CategoryGameItemBinding) : GameViewHolder(binding.root) {
|
||||
|
||||
@ -2,10 +2,11 @@ package com.gh.gamecenter.category2
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.ethanhua.skeleton.Skeleton
|
||||
import com.gh.gamecenter.common.constant.Constants
|
||||
import com.gh.common.exposure.ExposureListener
|
||||
import com.gh.common.exposure.DefaultExposureStateChangeListener
|
||||
import com.gh.common.util.DialogUtils
|
||||
import com.gh.common.view.CategoryFilterView
|
||||
import com.gh.common.xapk.XapkInstaller
|
||||
@ -13,16 +14,19 @@ 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.*
|
||||
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.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.exposure.addExposureHelper
|
||||
import com.google.android.flexbox.FlexboxLayout
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.lightgame.download.DataWatcher
|
||||
import com.lightgame.download.DownloadEntity
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
@ -30,13 +34,18 @@ import org.greenrobot.eventbus.ThreadMode
|
||||
|
||||
class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>() {
|
||||
|
||||
private var mCategoryId: String = ""
|
||||
private var mSubCategoryId: String = ""
|
||||
private var mCategoryTitle: String = ""
|
||||
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 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) {
|
||||
@ -52,6 +61,12 @@ 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() =
|
||||
@ -60,8 +75,7 @@ class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>
|
||||
override fun provideListViewModel() =
|
||||
viewModelProvider<CategoryV2ListViewModel>(
|
||||
CategoryV2ListViewModel.Factory(
|
||||
mCategoryId,
|
||||
mSubCategoryId,
|
||||
pageId,
|
||||
arguments?.getParcelableArrayList(EntranceConsts.KEY_EXPOSURE_SOURCE_LIST)
|
||||
)
|
||||
)
|
||||
@ -70,12 +84,7 @@ class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>
|
||||
?: CategoryV2ListAdapter(
|
||||
requireContext(),
|
||||
mListViewModel ?: provideListViewModel(),
|
||||
mCategoryViewModel ?: viewModelProviderFromParent(
|
||||
CategoryV2ViewModel.Factory(
|
||||
mCategoryId,
|
||||
mCategoryTitle
|
||||
), mCategoryId
|
||||
),
|
||||
parentViewModel,
|
||||
mEntrance,
|
||||
mLastPageDataMap
|
||||
).apply { mAdapter = this }
|
||||
@ -83,12 +92,10 @@ class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>
|
||||
override fun getItemDecoration() = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
mCategoryId = arguments?.getString(EntranceConsts.KEY_CATEGORY_ID) ?: ""
|
||||
mSubCategoryId = arguments?.getString(EntranceConsts.KEY_SUB_CATEGORY_ID) ?: ""
|
||||
mCategoryTitle = arguments?.getString(EntranceConsts.KEY_CATEGORY_TITLE) ?: ""
|
||||
pageId = arguments?.getString(EntranceConsts.KEY_PAGE_ID) ?: ""
|
||||
pageName = arguments?.getString(EntranceConsts.KEY_PAGE_NAME) ?: ""
|
||||
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)
|
||||
@ -104,22 +111,11 @@ class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>
|
||||
|
||||
initFilterView()
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
mListViewModel?.refresh?.observeNonNull(viewLifecycleOwner) {
|
||||
onRefresh()
|
||||
}
|
||||
|
||||
mListRv.addOnScrollListener(ExposureListener(this, provideListAdapter()))
|
||||
mListRv.addExposureHelper(this, DefaultExposureStateChangeListener())
|
||||
|
||||
mSkeletonScreen = Skeleton.bind(mBinding?.listSkeleton)
|
||||
.shimmer(true)
|
||||
@ -132,19 +128,40 @@ class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>
|
||||
.show()
|
||||
|
||||
mBinding?.reuseNoneData?.reuseResetLoadTv?.setOnClickListener {
|
||||
mCategoryViewModel?.run {
|
||||
// 重试时,
|
||||
// 清空所有筛选条件
|
||||
with(parentViewModel) {
|
||||
logClickReset("游戏列表")
|
||||
resetDirectoryList()
|
||||
clearSelectedTag()
|
||||
// 移除大小限制
|
||||
mBinding?.filterContainer?.resetSortSize()
|
||||
val size = SubjectSettingEntity.Size()
|
||||
updateGameFiltered(size)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
with(parentViewModel) {
|
||||
gameFiltered.observe(viewLifecycleOwner) {
|
||||
mListViewModel.updateSortConfig(it)
|
||||
}
|
||||
|
||||
selectedSubCategories.observe(viewLifecycleOwner) {
|
||||
updateSelectedTags(it)
|
||||
}
|
||||
resetSortSize()
|
||||
changeCategoryTab()
|
||||
// openDirectoryLayout()
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetSortSize() {
|
||||
mBinding?.filterContainer?.resetSortSize()
|
||||
mListViewModel?.sortSize = SubjectSettingEntity.Size(min = -1, max = -1, text = "全部大小")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -154,22 +171,19 @@ class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>
|
||||
|
||||
setOnConfigSetupListener(object : CategoryFilterView.OnCategoryFilterSetupListener {
|
||||
override fun onSetupSortSize(sortSize: SubjectSettingEntity.Size) {
|
||||
mListViewModel?.updateSortConfig(sortSize = sortSize)
|
||||
parentViewModel.updateGameFiltered(size = sortSize)
|
||||
}
|
||||
|
||||
override fun onSetupSortType(sortType: CategoryFilterView.SortType) {
|
||||
mListViewModel?.updateSortConfig(sortType = sortType)
|
||||
parentViewModel.updateGameFiltered(sortType = sortType)
|
||||
}
|
||||
|
||||
override fun onSetupSortCategory() {
|
||||
openDirectoryLayout()
|
||||
override fun getPopHeight(): Int {
|
||||
return (mBinding?.root?.height ?: 0) - (mBinding?.flTagsContainer?.height ?: 0)
|
||||
}
|
||||
})
|
||||
|
||||
setOnFilterClickListener(object : CategoryFilterView.OnFilterClickListener {
|
||||
override fun onCategoryClick() {
|
||||
removeGuide()
|
||||
}
|
||||
|
||||
override fun onTypeClick() {
|
||||
removeGuide()
|
||||
@ -183,101 +197,6 @@ 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()
|
||||
}
|
||||
@ -328,10 +247,6 @@ class CategoryV2ListFragment : ListFragment<GameEntity, CategoryV2ListViewModel>
|
||||
}
|
||||
}
|
||||
|
||||
fun openDirectoryLayout() {
|
||||
(parentFragment as? CategoryV2Fragment)?.openDirectoryLayout()
|
||||
}
|
||||
|
||||
override fun onDarkModeChanged() {
|
||||
super.onDarkModeChanged()
|
||||
mBinding?.filterContainer?.run {
|
||||
|
||||
@ -4,14 +4,15 @@ 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.util.AdHelper
|
||||
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.feature.exposure.ExposureConstants
|
||||
import com.gh.gamecenter.retrofit.RetrofitManager
|
||||
import com.halo.assistant.HaloApp
|
||||
import io.reactivex.Observable
|
||||
@ -20,14 +21,13 @@ 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 sortType = CategoryFilterView.SortType.RECOMMENDED
|
||||
var sortSize = SubjectSettingEntity.Size()
|
||||
var gameFiltered = CategoryV2ViewModel.GameFiltered()
|
||||
private set
|
||||
|
||||
override fun provideDataObservable(page: Int): Observable<List<GameEntity>>? = null
|
||||
|
||||
@ -35,7 +35,7 @@ class CategoryV2ListViewModel(
|
||||
return RetrofitManager
|
||||
.getInstance()
|
||||
.api
|
||||
.getCategoryV2Games(categoryId, getFilter(), getSortType(), page)
|
||||
.getCategoryV2Games(categoryId, getFilter(), getSortType(), AdHelper.getIdfaString(), page)
|
||||
}
|
||||
|
||||
override fun mergeResultLiveData() {
|
||||
@ -45,58 +45,45 @@ class CategoryV2ListViewModel(
|
||||
containerId = categoryId,
|
||||
containerType = ExposureEntity.CATEGORY_V2_ID
|
||||
)
|
||||
it.forEach { game -> game.hideSizeInsideDes = true }
|
||||
it.forEach { game ->
|
||||
game.hideSizeInsideDes = true
|
||||
game.subPageCode = ExposureConstants.CATEGORY_V2
|
||||
game.subPageId = categoryId
|
||||
}
|
||||
mResultLiveData.postValue(it)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateSortConfig(
|
||||
sortSize: SubjectSettingEntity.Size? = null,
|
||||
sortType: CategoryFilterView.SortType? = null,
|
||||
categoryIds: String? = null
|
||||
gameFiltered: CategoryV2ViewModel.GameFiltered
|
||||
) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
this.gameFiltered = gameFiltered
|
||||
refresh.postValue(true)
|
||||
}
|
||||
|
||||
private fun getFilter(): String? {
|
||||
return UrlFilterUtils.getFilterQuery(
|
||||
"category_ids", categoryIds,
|
||||
"min_size", sortSize.min.toString(),
|
||||
"max_size", sortSize.max.toString()
|
||||
)
|
||||
|
||||
return UrlFilterUtils.getFilterQuery(
|
||||
"category_ids", gameFiltered.categoryIds.ifBlank { gameFiltered.sidebarCategoryId },
|
||||
"min_size", gameFiltered.size.min.toString(),
|
||||
"max_size", gameFiltered.size.max.toString()
|
||||
)
|
||||
}
|
||||
|
||||
private fun getSortType(): String? {
|
||||
return when (sortType) {
|
||||
return when (gameFiltered.sortType) {
|
||||
CategoryFilterView.SortType.RECOMMENDED -> "download:-1"
|
||||
CategoryFilterView.SortType.NEWEST -> "publish:-1"
|
||||
CategoryFilterView.SortType.RATING -> "star:-1"
|
||||
}
|
||||
}
|
||||
|
||||
class Factory(val categoryId: String, val categoryIds: String, val exposureSourceList: List<ExposureSource>?) :
|
||||
class Factory(val categoryId: 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
|
||||
}
|
||||
|
||||
@ -1,160 +1,214 @@
|
||||
package com.gh.gamecenter.category2
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
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.common.retrofit.BiResponse
|
||||
import com.gh.gamecenter.entity.SubjectSettingEntity
|
||||
import com.gh.gamecenter.livedata.Event
|
||||
import com.gh.gamecenter.retrofit.RetrofitManager
|
||||
import com.halo.assistant.HaloApp
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
|
||||
class CategoryV2ViewModel(
|
||||
application: Application,
|
||||
private val mCategoryId: String,
|
||||
val categoryTitle: String
|
||||
) : AndroidViewModel(application) {
|
||||
class CategoryV2ViewModel : ViewModel() {
|
||||
|
||||
private val compositeDisposable = CompositeDisposable()
|
||||
private val api = RetrofitManager.getInstance().api
|
||||
var sidebarsLiveData = MutableLiveData<SidebarsEntity>()
|
||||
var directories = ArrayList<CategoryEntity>()
|
||||
var sidebarsLiveData = MutableLiveData<SidebarsEntity?>()
|
||||
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
|
||||
|
||||
init {
|
||||
getSidebars()
|
||||
getCategoryDirectories()
|
||||
fun init(entrance: String) {
|
||||
this.entrance = entrance
|
||||
}
|
||||
|
||||
@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)
|
||||
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
|
||||
}
|
||||
|
||||
override fun onFailure(exception: Exception) {
|
||||
super.onFailure(exception)
|
||||
sidebarsLiveData.postValue(null)
|
||||
}
|
||||
})
|
||||
|
||||
}).let(compositeDisposable::add)
|
||||
}
|
||||
|
||||
@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 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
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
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, categoryTitle)
|
||||
LogUtils.logCategoryV2AppearanceEvent(entrance, pageName)
|
||||
}
|
||||
|
||||
fun logClickSide() {
|
||||
LogUtils.logCategoryV2ClickSideEvent(entrance, categoryTitle, selectedCategoryName, selectedCategoryPosition)
|
||||
val sidebars = sidebarsLiveData.value?.sidebars
|
||||
val selectedPosition = selectedSidebarsPosition.value ?: 0
|
||||
val selectedCategoryName = sidebars?.getOrNull(selectedPosition)?.name ?: ""
|
||||
LogUtils.logCategoryV2ClickSideEvent(entrance, pageName, selectedCategoryName, selectedPosition)
|
||||
}
|
||||
|
||||
fun logClickClassification(primaryIndex: Int, categoryName: String, position: Int) {
|
||||
private fun logClickClassification(selected: SelectedTags) {
|
||||
LogUtils.logCategoryV2ClickClassificationEvent(
|
||||
entrance,
|
||||
categoryTitle,
|
||||
selectedCategoryName,
|
||||
directories[primaryIndex].name,
|
||||
categoryName,
|
||||
primaryIndex,
|
||||
position
|
||||
pageName,
|
||||
selectedSidebarsName,
|
||||
selected.parentName,
|
||||
selected.category.name ?: "",
|
||||
selected.parentPosition,
|
||||
selected.position
|
||||
)
|
||||
}
|
||||
|
||||
fun logClickClassificationDelete(primaryIndex: Int, categoryName: String, location: String) {
|
||||
private fun logClickClassificationDelete(directoryName: String, categoryName: String, location: String) {
|
||||
LogUtils.logCategoryV2ClickClassificationDeleteEvent(
|
||||
entrance,
|
||||
categoryTitle,
|
||||
directories[primaryIndex].name,
|
||||
pageName,
|
||||
directoryName,
|
||||
categoryName,
|
||||
location
|
||||
)
|
||||
}
|
||||
|
||||
fun logClickDetermine() {
|
||||
val categoryName = StringBuilder()
|
||||
selectedCategoryList.forEachIndexed { index, s ->
|
||||
categoryName.append(s.name)
|
||||
if (index != selectedCategoryList.size - 1) {
|
||||
categoryName.append("+")
|
||||
}
|
||||
}
|
||||
LogUtils.logCategoryV2ClickDetermineEvent(entrance, categoryTitle, categoryName.toString())
|
||||
val categoryNames = selectedSubCategories.value?.joinToString("+") { it.category.name ?: "" }
|
||||
LogUtils.logCategoryV2ClickDetermineEvent(entrance, pageName, categoryNames ?: "")
|
||||
}
|
||||
|
||||
fun logClickReset(location: String) {
|
||||
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)
|
||||
val categoryName = selectedSubCategories.value?.joinToString("+") { it.category.name ?: "" }
|
||||
LogUtils.logCategoryV2ClickResetEvent(entrance, pageName, categoryName ?: "", location)
|
||||
}
|
||||
|
||||
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
|
||||
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 != INVALID_POSITION) {
|
||||
updateGameFiltered()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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 = "全部"
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,292 @@
|
||||
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()
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,57 @@
|
||||
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) {
|
||||
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,48 @@
|
||||
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)
|
||||
}
|
||||
@ -1,84 +1,94 @@
|
||||
package com.gh.gamecenter.category2
|
||||
|
||||
import android.content.Context
|
||||
import android.view.View
|
||||
import android.annotation.SuppressLint
|
||||
import android.view.ViewGroup
|
||||
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 androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.ViewHolder
|
||||
import com.gh.gamecenter.R
|
||||
import com.gh.gamecenter.common.utils.toDrawable
|
||||
import com.gh.gamecenter.common.utils.*
|
||||
import com.gh.gamecenter.core.utils.ToastUtils
|
||||
import com.gh.gamecenter.databinding.SubCategoryItemBinding
|
||||
import com.gh.gamecenter.entity.CategoryEntity
|
||||
import com.lightgame.adapter.BaseRecyclerAdapter
|
||||
|
||||
class SubCategoryAdapter(
|
||||
context: Context,
|
||||
private val mViewModel: CategoryV2ViewModel,
|
||||
private val mList: List<CategoryEntity>,
|
||||
private val mPrimaryIndex: Int
|
||||
) : BaseRecyclerAdapter<SubCategoryAdapter.SubCategoryItemViewHolder>(context) {
|
||||
private val listener: SearchCategoryPop.OnSearchCategoryListener,
|
||||
) : RecyclerView.Adapter<SubCategoryAdapter.SubCategoryItemViewHolder>() {
|
||||
|
||||
override fun getItemCount() = mList.size
|
||||
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 onCreateViewHolder(parent: ViewGroup, viewType: Int) =
|
||||
SubCategoryItemViewHolder(SubCategoryItemBinding.inflate(mLayoutInflater))
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: SubCategoryItemViewHolder, position: Int) {
|
||||
holder.binding.run {
|
||||
val categoryEntity = mList[position]
|
||||
name.text = categoryEntity.name
|
||||
val categoryEntity = data[position]
|
||||
tvName.text = categoryEntity.name
|
||||
recommendIv.goneIf(categoryEntity.recommend == false)
|
||||
|
||||
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))
|
||||
}
|
||||
updateSelectedState(categoryEntity.selected, this)
|
||||
|
||||
root.setOnClickListener {
|
||||
when {
|
||||
mViewModel.selectedCount >= 5 && !categoryEntity.selected -> ToastUtils.toast("最多只能选择5个类别")
|
||||
!categoryEntity.selected && !listener.isEnableSelected() -> {
|
||||
ToastUtils.toast(R.string.selected_category_tags_max_toast.toResString())
|
||||
}
|
||||
|
||||
categoryEntity.selected -> {
|
||||
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 ?: "", "全部类别")
|
||||
}
|
||||
}
|
||||
|
||||
listener.onItemRemoved(itemData.id, categoryEntity.id)
|
||||
}
|
||||
|
||||
!categoryEntity.selected -> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
listener.onItemSelected(
|
||||
CategoryV2ViewModel.SelectedTags(
|
||||
itemData.id,
|
||||
itemData.name ?: "",
|
||||
directoryPosition,
|
||||
categoryEntity,
|
||||
position
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class SubCategoryItemViewHolder(val binding: SubCategoryItemBinding) : BaseRecyclerViewHolder<Any>(binding.root)
|
||||
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)
|
||||
}
|
||||
@ -33,8 +33,10 @@ import com.gh.gamecenter.discovery.interestedgame.InterestedGameActivity
|
||||
import com.gh.gamecenter.entity.DiscoveryGameCardLabel
|
||||
import com.gh.gamecenter.eventbus.EBDownloadStatus
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.feature.exposure.ExposureConstants
|
||||
import com.gh.gamecenter.feature.exposure.ExposureEvent
|
||||
import com.gh.gamecenter.feature.exposure.ExposureType
|
||||
import com.gh.gamecenter.feature.exposure.IExposureProvider
|
||||
import com.gh.gamecenter.feature.game.GameItemViewHolder
|
||||
import com.lightgame.download.DownloadEntity
|
||||
|
||||
@ -137,6 +139,7 @@ class DiscoveryAdapter(
|
||||
if (mOuterSequence >= 0) {
|
||||
outerSequence = mOuterSequence
|
||||
}
|
||||
subPageCode = ExposureConstants.DISCOVERY
|
||||
},
|
||||
mBaseExposureSource,
|
||||
exposureSources,
|
||||
@ -346,7 +349,9 @@ class DiscoveryAdapter(
|
||||
return null
|
||||
}
|
||||
|
||||
class DiscoveryGameViewHolder(val binding: DiscoveryGameItemBinding) : GameViewHolder(binding.root) {
|
||||
class DiscoveryGameViewHolder(val binding: DiscoveryGameItemBinding) : GameViewHolder(binding.root), IExposureProvider {
|
||||
private var boundedGameEntity: GameEntity? = null
|
||||
|
||||
init {
|
||||
gameDownloadBtn = binding.downloadBtn
|
||||
gameDes = binding.gameDes
|
||||
@ -358,6 +363,7 @@ class DiscoveryAdapter(
|
||||
}
|
||||
|
||||
fun bindGameItem(gameEntity: GameEntity) {
|
||||
boundedGameEntity = gameEntity
|
||||
binding.run {
|
||||
root.background = com.gh.gamecenter.common.R.drawable.reuse_listview_item_style.toDrawable(root.context)
|
||||
gameKaifuType.setBackgroundColor(com.gh.gamecenter.common.R.color.primary_theme.toColor(root.context))
|
||||
@ -415,8 +421,11 @@ class DiscoveryAdapter(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun provideExposureData(): ExposureEvent? {
|
||||
return boundedGameEntity?.exposureEvent?.getFreshExposureEvent()
|
||||
}
|
||||
}
|
||||
|
||||
class RecommendInterestViewHolder(val binding: ItemRecommendInterestBinding) :
|
||||
BaseRecyclerViewHolder<Any>(binding.root)
|
||||
|
||||
@ -9,7 +9,7 @@ import io.reactivex.Single
|
||||
|
||||
class GameSubjectDSPRemoteDataSource(private val api: DspApiService = RetrofitManager.getInstance().dspApiService) {
|
||||
|
||||
fun getDspGames(type: String, count: Int): Single<List<GameEntity>> {
|
||||
fun getDspGames(count: Int): Single<List<GameEntity>> {
|
||||
val meta = MetaUtil.getMeta()
|
||||
val request = mapOf(
|
||||
"device" to mapOf(
|
||||
|
||||
@ -4,6 +4,7 @@ import android.os.Parcelable
|
||||
import com.gh.gamecenter.common.entity.IconFloat
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.feature.entity.TagStyleEntity
|
||||
import com.gh.gamecenter.feature.exposure.ExposureEvent
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
@ -42,12 +43,16 @@ data class AmwayCommentEntity(
|
||||
// 曝光用的位置
|
||||
var sequence: Int = 0,
|
||||
var outerSequence: Int = 0
|
||||
|
||||
) : Parcelable {
|
||||
|
||||
@IgnoredOnParcel
|
||||
val name: String?
|
||||
get() = mName.removeSuffix(".")
|
||||
|
||||
@IgnoredOnParcel
|
||||
var exposureEvent: ExposureEvent? = null
|
||||
|
||||
fun toGameEntity(): GameEntity {
|
||||
val gameEntity = GameEntity()
|
||||
gameEntity.id = id
|
||||
|
||||
@ -2,16 +2,22 @@ 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")
|
||||
var id: String? = "",
|
||||
private var _id: String? = null,
|
||||
var icon: String? = "",
|
||||
var name: String? = "",
|
||||
var recommend: Boolean? = false,
|
||||
var data: List<CategoryEntity>? = null,
|
||||
var selected: Boolean = false,
|
||||
var primaryIndex: Int = -1
|
||||
) : Parcelable
|
||||
var data: List<CategoryEntity>? = null
|
||||
) : Parcelable {
|
||||
|
||||
val id: String
|
||||
get() = _id ?: ""
|
||||
|
||||
@IgnoredOnParcel
|
||||
var selected: Boolean = false
|
||||
}
|
||||
@ -7,5 +7,8 @@ class GameColumnCollection(
|
||||
val id: String = "",
|
||||
val name: String = "",
|
||||
// 取值为 "1-1" 或 "1-2" 或 "top" 相应地代表 1行1个 或 1行2个 或 排行榜
|
||||
val style: String = ""
|
||||
val style: String = "",
|
||||
val custom: Boolean = false, // 自定义设置
|
||||
@SerializedName("custom_size")
|
||||
val customSize: Int = 0 // 默认显示前X个专题
|
||||
)
|
||||
@ -0,0 +1,26 @@
|
||||
package com.gh.gamecenter.entity
|
||||
|
||||
import com.gh.gamecenter.servers.gametest2.GameServerTestV2ViewModel
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class GameServerTestDisplaySetting(
|
||||
@SerializedName("time_text_past")
|
||||
val timeTextPast: String = RECENT,
|
||||
@SerializedName("time_text_present")
|
||||
val timeTextPresent: String = TODAY,
|
||||
@SerializedName("time_text_future")
|
||||
val timeTextFuture: String = FUTURE,
|
||||
@SerializedName("game_category")
|
||||
val gameCategory: List<String> = listOf(
|
||||
GameServerTestV2ViewModel.GameCategory.Local.value,
|
||||
GameServerTestV2ViewModel.GameCategory.Online.value,
|
||||
GameServerTestV2ViewModel.GameCategory.Welfare.value,
|
||||
GameServerTestV2ViewModel.GameCategory.Gjonline.value
|
||||
),
|
||||
) {
|
||||
companion object {
|
||||
const val RECENT = "近期"
|
||||
const val TODAY = "今天"
|
||||
const val FUTURE = "预约"
|
||||
}
|
||||
}
|
||||
@ -13,6 +13,14 @@ class LibaoDetailEntity {
|
||||
|
||||
var des: String? = null
|
||||
|
||||
// 领取限制 发表游戏评价 (game_comment)
|
||||
@SerializedName("receive_limit")
|
||||
var receiveLimit: String? = ""
|
||||
|
||||
// 领取条件
|
||||
@SerializedName("receive_condition")
|
||||
var receiveCondition: Condition ? = null
|
||||
|
||||
@SerializedName("new_des")
|
||||
var newDes: String? = null
|
||||
|
||||
@ -24,4 +32,10 @@ class LibaoDetailEntity {
|
||||
@SerializedName("me")
|
||||
var me: MeEntity? = null
|
||||
|
||||
class Condition(
|
||||
// 评分,-1/5/4/3/2/1 => 无限制、5星好评、4星及以上评分、3星及以上评分、2星及以上评分、1星及以上评分
|
||||
val star: Int = 0,
|
||||
// 字数,-1/n => 无限制、数量
|
||||
val words: Int = 0
|
||||
)
|
||||
}
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
package com.gh.gamecenter.entity
|
||||
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class SearchGameUnionEntity(
|
||||
@SerializedName("type")
|
||||
private val _type: String? = null,
|
||||
@SerializedName("link_game")
|
||||
val linkGame: GameEntity? = null,
|
||||
@SerializedName("link_wechat_game_cpm_column")
|
||||
val linkWechatGameCpmColumn: SearchSubjectEntity? = null,
|
||||
@SerializedName("link_dsp_game_column")
|
||||
val linkDspGameColumn: SearchSubjectEntity? = null,
|
||||
@SerializedName("link_wechat_game")
|
||||
val linkWechatGame: GameEntity? = null,
|
||||
@SerializedName("link_column")
|
||||
val linkColum: SearchSubjectEntity? = null,
|
||||
@SerializedName("link_ad_space")
|
||||
val linkAdSpace: AdConfig? = null
|
||||
) {
|
||||
val type: String
|
||||
get() = _type ?: ""
|
||||
|
||||
companion object {
|
||||
const val TYPE_GAME = "game"
|
||||
const val TYPE_WECHAT_GAME_CPM_COLUMN = "wechat_game_cpm_column"
|
||||
const val TYPE_DSP_GAME_COLUMN = "dsp_game_column"
|
||||
const val TYPE_WECHAT_GAME = "wechat_game"
|
||||
const val TYPE_COLUMN = "column"
|
||||
const val TYPE_AD_SPACE = "ad_space"
|
||||
}
|
||||
}
|
||||
@ -2,8 +2,9 @@ package com.gh.gamecenter.entity
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.gh.common.filter.RegionSettingHelper
|
||||
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_DSP_GAME_COLUMN
|
||||
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_WECHAT_GAME_CPM_COLUMN
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.personalhome.home.UserHistoryViewModel
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@ -18,10 +19,6 @@ data class SearchSubjectEntity(
|
||||
val codeId: String = "", // 广告CODE_ID(本地字段),不为空时为广告专题
|
||||
@SerializedName("ad_icon_active")
|
||||
val adIconActive: Boolean = false,
|
||||
// 本地字段,标记是否为微信小游戏CPM专题
|
||||
var isWGameSubjectCPM: Boolean = false,
|
||||
// 本地字段,标记是否为DSP专题
|
||||
var isDspSubject: Boolean = false,
|
||||
val type: String = "",
|
||||
|
||||
@SerializedName("column_type")
|
||||
@ -31,10 +28,11 @@ data class SearchSubjectEntity(
|
||||
val size: Int = -1, // 专题游戏数量
|
||||
) : Parcelable {
|
||||
|
||||
companion object {
|
||||
const val TYPE_WECHAT_GAME_CPM_COLUMN = "wechat_game_cpm_column"
|
||||
const val TYPE_DSP_GAME_COLUMN = "dsp_game_column"
|
||||
}
|
||||
val isWGameSubjectCPM: Boolean
|
||||
get() = type == TYPE_WECHAT_GAME_CPM_COLUMN
|
||||
|
||||
val isDspSubject: Boolean
|
||||
get() = type == TYPE_DSP_GAME_COLUMN
|
||||
|
||||
fun getFilterGame() = RegionSettingHelper.filterGame(games)
|
||||
}
|
||||
@ -7,7 +7,7 @@ import kotlinx.parcelize.IgnoredOnParcel
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@Parcelize
|
||||
class SubjectData(
|
||||
data class SubjectData(
|
||||
// 入口必填
|
||||
var subjectId: String?,
|
||||
var subjectName: String?,
|
||||
@ -21,13 +21,28 @@ 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 codeId: String = "", // 广告CODE_ID(本地字段),不为空时为广告专题
|
||||
var tag: String = "" // 分类标签,埋点用
|
||||
) : 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] ?: ""
|
||||
|
||||
@ -122,7 +122,9 @@ data class SubjectEntity(
|
||||
@SerializedName("column_type")
|
||||
private val _columnType: String? = null,
|
||||
@SerializedName("size")
|
||||
private val _size: Size? = null
|
||||
private val _size: Size? = null,
|
||||
@SerializedName("onlyFee")
|
||||
private val _onlyFee: Boolean? = false,
|
||||
) : Parcelable {
|
||||
|
||||
@IgnoredOnParcel
|
||||
@ -161,6 +163,9 @@ data class SubjectEntity(
|
||||
val size: Size
|
||||
get() = _size ?: Size()
|
||||
|
||||
val onlyFee: Boolean
|
||||
get() = _onlyFee ?: false
|
||||
|
||||
var isDspSubject: Boolean = false
|
||||
|
||||
companion object {
|
||||
@ -173,9 +178,13 @@ data class SubjectEntity(
|
||||
@Parcelize
|
||||
data class Size(
|
||||
@SerializedName("index")
|
||||
private val _index: Int? = null
|
||||
private val _index: Int? = null,
|
||||
@SerializedName("limit")
|
||||
private val _limit: Int? = null,
|
||||
) : Parcelable {
|
||||
val index: Int
|
||||
get() = _index ?: 0
|
||||
val limit: Int
|
||||
get() = _limit ?: -1
|
||||
}
|
||||
}
|
||||
@ -13,7 +13,7 @@ class SubjectSettingEntity(
|
||||
@SerializedName("type")
|
||||
var typeEntity: TypeEntity = TypeEntity(),
|
||||
var tag: String = "",
|
||||
var filter: String = "", // rows: off/on
|
||||
var filter: String = "", // off/on
|
||||
var order: Boolean = false, // 是否显示序号
|
||||
|
||||
@SerializedName("brief_style")
|
||||
@ -34,6 +34,9 @@ class SubjectSettingEntity(
|
||||
private val _showDetailIconSubscript: Boolean? = null
|
||||
) : Parcelable {
|
||||
|
||||
val isFilterEnabled: Boolean
|
||||
get() = filter == "on"
|
||||
|
||||
val showDetailSubtitle: Boolean
|
||||
get() = _showDetailSubtitle ?: false
|
||||
|
||||
|
||||
@ -33,7 +33,6 @@ class FollowCommonCollectionViewHolder(
|
||||
override fun addExposureEvent(childPosition: Int, link: ExposureLinkEntity) = Unit
|
||||
|
||||
override fun onChildItemClick(childPosition: Int, entity: CommonCollectionContentEntity) {
|
||||
|
||||
val linkEntity = entity.linkEntity
|
||||
|
||||
NewLogUtils.logCommonCollectionClick(
|
||||
|
||||
@ -50,9 +50,6 @@ class FollowHomeSlideListViewHolder(
|
||||
}
|
||||
|
||||
override fun updateImmersiveColor(color: Int) = Unit
|
||||
|
||||
override fun createExposureEvent(childPosition: Int, game: GameEntity?): ExposureEvent? = null
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -29,15 +29,12 @@ class FollowHomeSlideWithCardsViewHolder(
|
||||
useCase,
|
||||
lifecycleOwner,
|
||||
binding,
|
||||
0,
|
||||
null,
|
||||
"",
|
||||
object : CommonContentHomeSlideWithCardsUi.HomeSLideWithCardsEventListener {
|
||||
override fun updateImmersiveColor(color: Int) = Unit
|
||||
|
||||
override fun createEventWithSourceConcat(game: GameEntity, subSlideId: String) = Unit
|
||||
|
||||
override fun createExposureEvent(actualPosition: Int, game: GameEntity?): ExposureEvent? = null
|
||||
|
||||
override fun addGameExposureEvent(position: Int, game: GameEntity, subSlideId: String) = Unit
|
||||
|
||||
override fun navigateToGameDetailPage(
|
||||
childPosition: Int,
|
||||
gameEntity: GameEntity,
|
||||
|
||||
@ -399,7 +399,8 @@ class GameFragmentAdapter(
|
||||
position = prefixedPosition,
|
||||
gameColumnName = gameEntity.name ?: "",
|
||||
gameColumnId = gameEntity.link ?: "",
|
||||
text = "游戏专题"
|
||||
text = "游戏专题",
|
||||
adGroupId = gameEntity.adGroupId
|
||||
)
|
||||
}
|
||||
|
||||
@ -574,7 +575,7 @@ class GameFragmentAdapter(
|
||||
position = sequence,
|
||||
gameColumnName = subject.name ?: "",
|
||||
gameColumnId = subject.id ?: "",
|
||||
text = "游戏专题"
|
||||
text = "游戏专题",
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -21,6 +21,8 @@ import com.gh.gamecenter.common.viewholder.FooterViewHolder
|
||||
import com.gh.gamecenter.core.utils.MtaHelper
|
||||
import com.gh.gamecenter.core.utils.PageSwitchDataHelper
|
||||
import com.gh.gamecenter.databinding.GameColumnCollectionItemBinding
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.feature.exposure.ExposureConstants
|
||||
import com.gh.gamecenter.feature.exposure.ExposureEvent
|
||||
|
||||
class ColumnCollectionDetailAdapter(
|
||||
@ -95,8 +97,13 @@ class ColumnCollectionDetailAdapter(
|
||||
text = "游戏专题"
|
||||
)
|
||||
}
|
||||
|
||||
val fakeGameEntity = GameEntity(_id = ExposureConstants.COLUMN_COLLECTION_DETAIL)
|
||||
fakeGameEntity.subPageCode = ExposureConstants.COLUMN_COLLECTION_DETAIL
|
||||
fakeGameEntity.subPageId = mViewModel.collectionId
|
||||
|
||||
val exposureEvent = ExposureEvent.createEventWithSourceConcat(
|
||||
null,
|
||||
fakeGameEntity,
|
||||
mBasicExposureSourceList ?: arrayListOf(),
|
||||
arrayListOf(
|
||||
ExposureSource("合集详情", ""),
|
||||
|
||||
@ -2,7 +2,7 @@ package com.gh.gamecenter.game.columncollection.detail
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.core.view.isVisible
|
||||
import com.gh.gamecenter.R
|
||||
import com.gh.gamecenter.common.base.GlobalActivityManager
|
||||
import com.gh.gamecenter.common.baselist.LazyListFragment
|
||||
@ -14,6 +14,8 @@ 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
|
||||
@ -23,6 +25,7 @@ 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
|
||||
@ -56,6 +59,11 @@ 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)
|
||||
@ -91,7 +99,7 @@ class ColumnCollectionDetailFragment : LazyListFragment<LinkEntity, ColumnCollec
|
||||
}
|
||||
|
||||
override fun onChanged(list: MutableList<LinkEntity>?) {
|
||||
if (list != null && list.isNotEmpty()) {
|
||||
if (!list.isNullOrEmpty()) {
|
||||
showSubjectTab(list)
|
||||
}
|
||||
}
|
||||
@ -115,25 +123,28 @@ class ColumnCollectionDetailFragment : LazyListFragment<LinkEntity, ColumnCollec
|
||||
isOrder = false,
|
||||
requireUpdateSetting = true,
|
||||
filter = "type:全部", // 默认显示大图
|
||||
subjectType = subjectType
|
||||
subjectType = subjectType,
|
||||
customLimit = link.customLimit
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val fragment = childFragmentManager.findFragmentByTag(SubjectTabFragment::class.java.name)
|
||||
mFragment = childFragmentManager.findFragmentByTag(SubjectTabFragment::class.java.name) as? SubjectTabFragment
|
||||
?: 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)
|
||||
|
||||
fragment.arguments = bundle
|
||||
mFragment!!.arguments = bundle
|
||||
mBinding?.placeholder?.visibility = View.VISIBLE
|
||||
childFragmentManager.beginTransaction().replace(R.id.placeholder, fragment, SubjectTabFragment::class.java.name)
|
||||
childFragmentManager.beginTransaction().replace(R.id.placeholder, mFragment!!, SubjectTabFragment::class.java.name)
|
||||
.commitAllowingStateLoss()
|
||||
}
|
||||
|
||||
@ -167,9 +178,18 @@ class ColumnCollectionDetailFragment : LazyListFragment<LinkEntity, ColumnCollec
|
||||
arguments?.getString(EntranceConsts.KEY_COLLECTION_ID)
|
||||
?: ""
|
||||
)
|
||||
return ViewModelProviders.of(this, factory).get(ColumnCollectionDetailViewModel::class.java)
|
||||
return viewModelProviderFromParent(factory, arguments?.getString(EntranceConsts.KEY_COLLECTION_ID) ?: "")
|
||||
}
|
||||
|
||||
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"
|
||||
|
||||
@ -18,6 +18,7 @@ import com.gh.gamecenter.common.utils.goneIf
|
||||
import com.gh.gamecenter.common.utils.toBinding
|
||||
import com.gh.gamecenter.common.viewholder.FooterViewHolder
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.feature.exposure.ExposureConstants
|
||||
import com.gh.gamecenter.feature.exposure.ExposureEvent
|
||||
import com.gh.gamecenter.game.data.CommonContentCollectionDetailItem
|
||||
import com.gh.gamecenter.game.data.CommonContentCollectionDetailRecommendCardItem
|
||||
@ -142,6 +143,8 @@ class CustomCommonCollectionDetailAdapter(
|
||||
ExposureEvent.createEventWithSourceConcat(
|
||||
gameEntity.also {
|
||||
it.sequence = position
|
||||
it.subPageCode = ExposureConstants.CUSTOM_COMMON_COLLECTION_DETAIL
|
||||
it.subPageId = commonCollectionEntity?.id ?: ""
|
||||
},
|
||||
basicSource = mBasicExposureSourceList ?: listOf(),
|
||||
listOf(
|
||||
|
||||
@ -18,6 +18,8 @@ import com.gh.gamecenter.gamedetail.detail.viewholder.BaseGameDetailItemViewHold
|
||||
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.subjectTypeToComponentStyle
|
||||
import com.lightgame.adapter.BaseRecyclerAdapter
|
||||
import com.lightgame.download.DownloadEntity
|
||||
import kotlin.collections.forEachIndexed
|
||||
import kotlin.collections.isNotEmpty
|
||||
|
||||
class GameHorizontalAdapter(
|
||||
context: Context,
|
||||
@ -136,7 +138,8 @@ class GameHorizontalAdapter(
|
||||
gameColumnName = subjectEntity.name ?: "",
|
||||
gameColumnId = subjectEntity.id ?: "",
|
||||
text = "游戏",
|
||||
columnPattern = subjectTypeToComponentStyle[subjectEntity.type] ?: ""
|
||||
columnPattern = subjectTypeToComponentStyle[subjectEntity.type] ?: "",
|
||||
adGroupId = gameEntity.adGroupId,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -6,9 +6,22 @@ import android.widget.TextView
|
||||
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
|
||||
import com.gh.gamecenter.databinding.GameHorizontalSimpleItemBinding
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.feature.exposure.ExposureEvent
|
||||
import com.gh.gamecenter.feature.exposure.IExposureProvider
|
||||
|
||||
class GameHorizontalSimpleItemViewHolder(val binding: GameHorizontalSimpleItemBinding) :
|
||||
BaseRecyclerViewHolder<GameEntity>(binding.root) {
|
||||
BaseRecyclerViewHolder<GameEntity>(binding.root), IExposureProvider {
|
||||
|
||||
private var boundedGameEntity: GameEntity? = null
|
||||
|
||||
fun bindData(game: GameEntity) {
|
||||
boundedGameEntity = game
|
||||
}
|
||||
|
||||
override fun provideExposureData(): ExposureEvent? {
|
||||
return boundedGameEntity?.exposureEvent?.getFreshExposureEvent()
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun setHorizontalNameAndGravity(view: TextView, name: String?) {
|
||||
|
||||
@ -26,8 +26,9 @@ class HotGameListViewModel(
|
||||
subjectData.subjectId,
|
||||
subjectData.sort,
|
||||
subjectData.filter.ifEmpty { "type:全部" },
|
||||
page
|
||||
)
|
||||
"",
|
||||
"",
|
||||
page)
|
||||
}
|
||||
|
||||
override fun mergeResultLiveData() {
|
||||
|
||||
@ -30,6 +30,7 @@ import com.gh.gamecenter.entity.GamesCollectionDetailEntity
|
||||
import com.gh.gamecenter.eventbus.EBUserFollow
|
||||
import com.gh.gamecenter.feature.entity.CommentEntity
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.feature.exposure.ExposureConstants
|
||||
import com.gh.gamecenter.login.user.UserManager
|
||||
import com.gh.gamecenter.qa.article.detail.CommentItemData
|
||||
import com.gh.gamecenter.qa.comment.base.BaseCommentViewModel
|
||||
@ -136,6 +137,9 @@ open class GameCollectionDetailViewModel(
|
||||
|
||||
games?.forEach {
|
||||
it.isAdData = adIconActive
|
||||
it.subPageCode = ExposureConstants.GAME_COLLECTION_DETAIL
|
||||
it.subPageId = gameCollectionId
|
||||
|
||||
add(CommentItemData(game = it))
|
||||
gameList?.add(it)
|
||||
}
|
||||
|
||||
@ -30,6 +30,7 @@ import com.gh.gamecenter.entity.RecommendPopupEntity
|
||||
import com.gh.gamecenter.entity.SubjectEntity
|
||||
import com.gh.gamecenter.entity.UnifiedUserTrendEntity
|
||||
import com.gh.gamecenter.feature.entity.*
|
||||
import com.gh.gamecenter.feature.exposure.ExposureConstants
|
||||
import com.gh.gamecenter.feature.utils.ApkActiveUtils
|
||||
import com.gh.gamecenter.feature.utils.ConcernUtils
|
||||
import com.gh.gamecenter.feature.utils.ContentBlockedHelper
|
||||
@ -37,7 +38,6 @@ 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
|
||||
@ -105,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)
|
||||
}
|
||||
@ -134,9 +134,9 @@ class GameDetailViewModel(
|
||||
private var isGameUpdatable = false
|
||||
|
||||
private val compositeDisposable = CompositeDisposable()
|
||||
|
||||
|
||||
private var userRelatedInfoReceivedCallback: (() -> Unit)? = null
|
||||
|
||||
|
||||
init {
|
||||
loadData()
|
||||
}
|
||||
@ -191,6 +191,7 @@ class GameDetailViewModel(
|
||||
.subscribe(object : Response<GameEntity>() {
|
||||
override fun onResponse(response: GameEntity?) {
|
||||
game = response
|
||||
game?.subPageCode = ExposureConstants.GAME_DETAIL
|
||||
gameLiveData.postValue(Resource.success(game))
|
||||
loadGameDetailData()
|
||||
}
|
||||
@ -558,6 +559,9 @@ class GameDetailViewModel(
|
||||
detailDataList.find { it.linkEveryonePlaying != null }?.let {
|
||||
if (relatedGameList.isNotEmpty()) {
|
||||
relatedGameList.shuffle()
|
||||
relatedGameList.forEach { game ->
|
||||
game.subPageCode = ExposureConstants.GAME_DETAIL
|
||||
}
|
||||
val recommendedGames = SubjectEntity().apply { data = relatedGameList }
|
||||
it.linkEveryonePlaying?.recommendedGames = recommendedGames
|
||||
} else {
|
||||
@ -1117,6 +1121,14 @@ 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?,
|
||||
@ -1133,6 +1145,7 @@ 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>?) {
|
||||
|
||||
@ -14,6 +14,7 @@ import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.view.menu.ActionMenuItemView
|
||||
import androidx.constraintlayout.widget.ConstraintLayout
|
||||
import androidx.core.view.doOnNextLayout
|
||||
import androidx.core.view.isVisible
|
||||
@ -96,6 +97,7 @@ import io.reactivex.disposables.Disposable
|
||||
import org.greenrobot.eventbus.Subscribe
|
||||
import org.greenrobot.eventbus.ThreadMode
|
||||
import retrofit2.HttpException
|
||||
import splitties.views.horizontalPadding
|
||||
import java.util.*
|
||||
|
||||
class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
|
||||
@ -286,7 +288,7 @@ class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
|
||||
initSkeleton()
|
||||
|
||||
binding.reuseNoneData.reuseNoneDataTv.text = "页面不见了"
|
||||
bodyBinding.tabIndicator.setIndicatorWidth(12)
|
||||
bodyBinding.tabIndicator.setIndicatorWidth(16)
|
||||
bodyBinding.viewPager.offscreenPageLimit = 4
|
||||
binding.expandSpecialDownloadIv.enlargeTouchArea()
|
||||
|
||||
@ -357,8 +359,14 @@ class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
|
||||
}
|
||||
backBtn.setOnClickListener { requireActivity().finish() }
|
||||
moreMenuItem = actionMenuView.menu.findItem(R.id.menu_more)
|
||||
downloadMenuItem = actionMenuView.menu.findItem(R.id.menu_download)
|
||||
downloadMenuItem = actionMenuView.menu.findItem(R.id.menu_download)?.apply {
|
||||
actionView?.updateLayoutParams<LayoutParams> { width = 40F.dip2px() }
|
||||
}
|
||||
downloadMenuItem?.isVisible = Config.isShow()
|
||||
actionMenuView.findViewById<ActionMenuItemView>(R.id.menu_more)?.run {
|
||||
updateLayoutParams<LayoutParams> { width = 40F.dip2px() }
|
||||
horizontalPadding = 8F.dip2px()
|
||||
}
|
||||
}
|
||||
|
||||
downloadMenuIcon = downloadMenuItem?.actionView?.findViewById(R.id.menu_download_iv)
|
||||
@ -467,6 +475,10 @@ class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
|
||||
gameEntity?.gameBitChinese ?: "",
|
||||
"download_type",
|
||||
gameEntity?.downloadType ?: "",
|
||||
"is_ad",
|
||||
traceEvent?.payload?.isAd ?: false,
|
||||
"ad_group_id",
|
||||
traceEvent?.payload?.adGroupId ?: "",
|
||||
*(traceEvent?.additional ?: emptyArray())
|
||||
)
|
||||
}, 120)
|
||||
@ -635,7 +647,7 @@ class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
|
||||
DialogHelper.showDialogWithHtmlContent(
|
||||
requireContext(),
|
||||
dialog.title,
|
||||
dialog.content,
|
||||
dialog.content.replaceLineBreakWithBr(),
|
||||
dialog.confirmButton.text.toString(),
|
||||
dialog.closeButtonText,
|
||||
{
|
||||
@ -669,7 +681,8 @@ class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
|
||||
"game_type", gameEntity?.categoryChinese ?: "",
|
||||
"button_name", "关闭弹窗"
|
||||
)
|
||||
}
|
||||
},
|
||||
extraConfig = DialogHelper.Config(centerTitle = true)
|
||||
)
|
||||
}
|
||||
|
||||
@ -802,7 +815,10 @@ class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
|
||||
downloadStatus = gameEntity?.downloadStatusChinese ?: "",
|
||||
gameType = gameEntity?.categoryChinese ?: "",
|
||||
position = position,
|
||||
tabContent = tabEntity.name
|
||||
tabContent = tabEntity.name,
|
||||
linkType = tabEntity.link?.type ?: "",
|
||||
linkId = tabEntity.link?.link ?: "",
|
||||
linkText = tabEntity.link?.text ?: ""
|
||||
)
|
||||
|
||||
val entrance = if (mEntrance.contains("论坛详情")) "论坛" else "游戏"
|
||||
@ -905,7 +921,7 @@ class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
|
||||
tab.customView = tabItemBinding.root
|
||||
updateTabStyle(tab, i == bodyBinding.viewPager.currentItem)
|
||||
tab.view.clipChildren = false
|
||||
tab.view.setPadding(0, 0, 0, 0)
|
||||
tab.view.setPadding(0, 0, if (i == tabEntityList.size - 1) 8F.dip2px() else 0, 0)
|
||||
tab.view.setOnTouchListener { _, event ->
|
||||
if (event.action == MotionEvent.ACTION_DOWN) {
|
||||
handleTabTouchEvent(tabEntity?.name ?: "")
|
||||
@ -932,7 +948,7 @@ class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
|
||||
tab.customView?.findViewById<TextView>(R.id.tab_title)
|
||||
?.setTypeface(if (isChecked) Typeface.DEFAULT_BOLD else Typeface.DEFAULT)
|
||||
tab.customView?.findViewById<TextView>(R.id.tab_title)?.setTextColor(
|
||||
if (isChecked) com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()) else com.gh.gamecenter.common.R.color.text_tertiary.toColor(
|
||||
if (isChecked) com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()) else com.gh.gamecenter.common.R.color.text_secondary.toColor(
|
||||
requireContext()
|
||||
)
|
||||
)
|
||||
|
||||
@ -684,7 +684,7 @@ class GameDetailFragment : LazyFragment(), IScrollable {
|
||||
null,
|
||||
gameStatus = gameStatus
|
||||
)
|
||||
GameFunctionDialogFragment.show(requireContext(), gameDetailInfoTag.infoTags)
|
||||
GameFunctionDialogFragment.show(requireContext(), gameEntity, gameDetailInfoTag.infoTags, gameDetailInfoTag.link)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -253,7 +253,7 @@ class GameLibaoAdapter(
|
||||
null,
|
||||
true,
|
||||
"游戏详情",
|
||||
"游戏详情"
|
||||
"礼包列表页"
|
||||
) {
|
||||
adapter.notifyItemChanged(position)
|
||||
}
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.gh.gamecenter.gamedetail.detail.viewholder
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.TextView
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import com.gh.common.constant.Config
|
||||
@ -131,11 +132,9 @@ class GameDetailContentCardSingleItemViewHolder(
|
||||
contentBannerView.startBannerLoop()
|
||||
}
|
||||
|
||||
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 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
|
||||
@ -173,11 +172,11 @@ class GameDetailContentCardSingleItemViewHolder(
|
||||
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)
|
||||
jumpToContentCardLink(context, contentCardEntity, viewModel, redDotTv)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
jumpToContentCardLink(context, contentCardEntity, viewModel)
|
||||
jumpToContentCardLink(context, contentCardEntity, viewModel, redDotTv)
|
||||
}
|
||||
}
|
||||
|
||||
@ -189,11 +188,16 @@ class GameDetailContentCardSingleItemViewHolder(
|
||||
}
|
||||
}
|
||||
|
||||
fun jumpToContentCardLink(context: Context, contentCardEntity: ContentCardEntity, viewModel: GameDetailViewModel) {
|
||||
fun jumpToContentCardLink(context: Context, contentCardEntity: ContentCardEntity, viewModel: GameDetailViewModel, redDotTv: TextView) {
|
||||
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) {
|
||||
@ -208,6 +212,8 @@ class GameDetailContentCardSingleItemViewHolder(
|
||||
|
||||
TYPE_SERVER -> {
|
||||
if (viewModel.game != null && contentCardEntity.server != null) {
|
||||
viewModel.markContentCardRedDot(contentCardEntity)
|
||||
redDotTv.isVisible = false
|
||||
context.startActivity(
|
||||
ServersCalendarActivity.getIntent(
|
||||
context,
|
||||
|
||||
@ -10,8 +10,6 @@ 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.entity.ContentCardEntity.Companion.TYPE_GIFT
|
||||
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity.Companion.TYPE_SERVER
|
||||
import com.gh.gamecenter.gamedetail.entity.GameDetailData
|
||||
|
||||
class GameDetailContentCardTripleItemViewHolder(
|
||||
@ -74,11 +72,9 @@ class GameDetailContentCardTripleItemViewHolder(
|
||||
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 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
|
||||
@ -116,11 +112,11 @@ class GameDetailContentCardTripleItemViewHolder(
|
||||
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)
|
||||
jumpToContentCardLink(context, contentCardEntity, viewModel, redDotTv)
|
||||
}
|
||||
)
|
||||
} else {
|
||||
jumpToContentCardLink(context, contentCardEntity, viewModel)
|
||||
jumpToContentCardLink(context, contentCardEntity, viewModel, redDotTv)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -20,6 +20,8 @@ import com.gh.gamecenter.game.horizontal.GameHorizontalListType
|
||||
import com.gh.gamecenter.gamedetail.GameDetailViewModel
|
||||
import com.gh.gamecenter.gamedetail.entity.GameDetailData
|
||||
import com.gh.gamecenter.gamedetail.entity.GameDetailRecommendColumn
|
||||
import kotlin.collections.firstOrNull
|
||||
import kotlin.collections.withIndex
|
||||
|
||||
class GameDetailRecommendColumnItemViewHolder(
|
||||
val binding: ItemGameDetailRecyclerViewBinding,
|
||||
@ -73,7 +75,8 @@ class GameDetailRecommendColumnItemViewHolder(
|
||||
linkType = columnData.display?.moreLink?.type ?: "",
|
||||
linkId = columnData.display?.moreLink?.link ?: "",
|
||||
text = "右上角",
|
||||
buttonType = columnData.display?.home ?: ""
|
||||
buttonType = columnData.display?.home ?: "",
|
||||
adGroupId = viewModel.game?.adGroupId ?: ""
|
||||
)
|
||||
|
||||
when (columnData.display?.home) {
|
||||
|
||||
@ -9,19 +9,29 @@ 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?) {
|
||||
@ -30,6 +40,28 @@ 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
|
||||
@ -46,7 +78,7 @@ class GameFunctionDialogFragment: BaseBottomDialogFragment<DialogGameDetailRecyc
|
||||
const val KEY_INFO_TAG = "info_tag"
|
||||
|
||||
@JvmStatic
|
||||
fun show(context: Context?, infoTags: List<GameDetailInfoTag.InfoTag>) {
|
||||
fun show(context: Context?, gameEntity: GameEntity?, infoTags: List<GameDetailInfoTag.InfoTag>, linkEntity: LinkEntity?) {
|
||||
val fragmentActivity: FragmentActivity = if (context is FragmentActivity) {
|
||||
context
|
||||
} else if (BuildConfig.DEBUG) {
|
||||
@ -65,7 +97,9 @@ class GameFunctionDialogFragment: BaseBottomDialogFragment<DialogGameDetailRecyc
|
||||
|
||||
val dialogFragment = GameFunctionDialogFragment()
|
||||
dialogFragment.arguments = bundleOf(
|
||||
KEY_INFO_TAG to infoTags
|
||||
EntranceConsts.KEY_GAME_ENTITY to gameEntity,
|
||||
KEY_INFO_TAG to infoTags,
|
||||
EntranceConsts.KEY_LINK to linkEntity
|
||||
)
|
||||
dialogFragment.show(fragmentActivity.supportFragmentManager, GameFunctionDialogFragment::class.java.name)
|
||||
}
|
||||
|
||||
@ -8,10 +8,9 @@ import android.view.*
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.gh.common.util.DirectUtils
|
||||
import com.gh.common.util.PackageInstaller
|
||||
import com.gh.common.util.PackageLauncher
|
||||
import com.gh.common.util.PackageUtils
|
||||
import com.gh.common.util.*
|
||||
import com.gh.common.xapk.XapkInstaller
|
||||
import com.gh.common.xapk.XapkUnzipStatus
|
||||
import com.gh.download.DownloadManager
|
||||
import com.gh.gamecenter.R
|
||||
import com.gh.gamecenter.ShellActivity
|
||||
@ -80,6 +79,15 @@ class SpecialDownloadDialogFragment : BaseDraggableDialogFragment() {
|
||||
DownloadStatus.done -> {
|
||||
downloadBtn?.setProgress(1.0F)
|
||||
downloadBtn?.setText(context?.getString(com.gh.gamecenter.feature.R.string.install) ?: "")
|
||||
val xapkStatus = downloadEntity.meta[XapkInstaller.XAPK_UNZIP_STATUS]
|
||||
when {
|
||||
(XapkUnzipStatus.SUCCESS.name == xapkStatus || XapkUnzipStatus.INSTALLED.name == xapkStatus) && XapkInstaller.isInstalling(downloadEntity.path) -> {
|
||||
downloadBtn?.setText(context?.getString(com.gh.gamecenter.feature.R.string.installing) ?: "")
|
||||
}
|
||||
XapkUnzipStatus.UNZIPPING.name == xapkStatus -> {
|
||||
downloadBtn?.setText(context?.getString(com.gh.gamecenter.feature.R.string.unzipping) ?: "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DownloadStatus.cancel -> {
|
||||
|
||||
@ -31,6 +31,13 @@ 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")
|
||||
|
||||
@ -219,7 +219,9 @@ data class GameDetailInfoTag(
|
||||
@SerializedName("info_tags")
|
||||
val infoTags: List<InfoTag> = listOf(), // 功能标签
|
||||
@SerializedName("request_speed_status")
|
||||
val requestSpeedStatus: String = "" // 求加速状态, on/off
|
||||
val requestSpeedStatus: String = "", // 求加速状态, on/off
|
||||
@SerializedName("plugin_tutorial_link")
|
||||
val link: LinkEntity? = null, // 插件教程链接
|
||||
) {
|
||||
@Parcelize
|
||||
data class InfoTag(
|
||||
|
||||
@ -360,7 +360,8 @@ class LegacyHomeFragmentAdapterAssistant(
|
||||
position = prefixedPosition,
|
||||
gameColumnName = gameEntity.name ?: "",
|
||||
gameColumnId = gameEntity.link ?: "",
|
||||
text = "游戏专题"
|
||||
text = "游戏专题",
|
||||
adGroupId = gameEntity.adGroupId
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,9 +1,11 @@
|
||||
package com.gh.gamecenter.home.custom
|
||||
|
||||
import com.gh.common.util.AdHelper
|
||||
import com.gh.gamecenter.common.exposure.ExposureSource
|
||||
import com.gh.gamecenter.core.runOnIoThread
|
||||
import com.gh.gamecenter.feature.entity.CustomPageTrackData
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.feature.exposure.ExposureConstants
|
||||
import com.gh.gamecenter.feature.exposure.ExposureEvent
|
||||
import com.gh.gamecenter.home.custom.model.CustomSubjectCollectionItem
|
||||
|
||||
@ -18,6 +20,9 @@ fun createExposureEvent(
|
||||
game?.sequence = childPosition
|
||||
game?.outerSequence = position
|
||||
game?.customPageTrackData = customPageTrackData
|
||||
game?.pageLevelString = customPageTrackData.pageLocation.pageLevelString
|
||||
game?.subPageCode = ExposureConstants.CUSTOM_PAGE
|
||||
game?.subPageId = customPageTrackData.pageLocation.pageId
|
||||
val event = ExposureEvent.createEventWithSourceConcat(
|
||||
gameEntity = game,
|
||||
basicSource = base,
|
||||
@ -32,9 +37,7 @@ fun fillExposureInSubjectCollection(
|
||||
base: List<ExposureSource>,
|
||||
customPageTrackData: CustomPageTrackData
|
||||
) {
|
||||
|
||||
val eventList = arrayListOf<ExposureEvent>()
|
||||
runOnIoThread(true) {
|
||||
runOnIoThread(isLightWeightTask = true) {
|
||||
item.data.data.forEachIndexed { index, customSubject ->
|
||||
val source = if (item.isSubjectCollection) {
|
||||
listOf(
|
||||
@ -51,8 +54,9 @@ fun fillExposureInSubjectCollection(
|
||||
)
|
||||
}
|
||||
customSubject.games.forEach { game ->
|
||||
game.pageLevelString = customPageTrackData.pageLocation.pageLevelString
|
||||
game.isAdData = customSubject.adIconActive
|
||||
val event = createExposureEvent(
|
||||
game.exposureEvent = createExposureEvent(
|
||||
game,
|
||||
source,
|
||||
base,
|
||||
@ -60,10 +64,15 @@ fun fillExposureInSubjectCollection(
|
||||
item.componentPosition,
|
||||
customPageTrackData
|
||||
)
|
||||
eventList.add(event)
|
||||
game.subPageCode = ExposureConstants.CUSTOM_PAGE
|
||||
game.subPageId = customPageTrackData.pageLocation.pageId
|
||||
game.exposureSource = game.exposureEvent?.source
|
||||
|
||||
if (game.adGroupId.isNotEmpty() && !game.isAdRequestReported) {
|
||||
AdHelper.reportAdRequest(game.exposureEvent!!)
|
||||
game.isAdRequestReported = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
item.exposureEventList = eventList
|
||||
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ import androidx.fragment.app.viewModels
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.OnScrollListener
|
||||
import com.gh.common.exposure.ExposureListener
|
||||
import com.gh.common.exposure.DefaultExposureStateChangeListener
|
||||
import com.gh.common.exposure.ExposureManager
|
||||
import com.gh.common.iinterface.ISearchToolbarTab
|
||||
import com.gh.common.iinterface.ISmartRefresh
|
||||
@ -41,6 +41,7 @@ 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.tracker.IBusiness
|
||||
import com.gh.gamecenter.common.pagelevel.IPageLevelProvider
|
||||
import com.gh.gamecenter.common.utils.*
|
||||
import com.gh.gamecenter.common.view.ScrollableLinearLayoutManager
|
||||
import com.gh.gamecenter.core.AppExecutor
|
||||
@ -59,6 +60,7 @@ import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.feature.entity.PageLocation
|
||||
import com.gh.gamecenter.feature.exposure.ExposureEvent
|
||||
import com.gh.gamecenter.feature.exposure.ExposureType
|
||||
import com.gh.gamecenter.feature.exposure.addExposureHelper
|
||||
import com.gh.gamecenter.feature.minigame.MiniGameItemHelper
|
||||
import com.gh.gamecenter.feature.utils.SentryHelper
|
||||
import com.gh.gamecenter.game.commoncollection.detail.CustomCommonCollectionDetailActivity
|
||||
@ -88,7 +90,11 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable, IB
|
||||
private var searchToolbarTabWrapperViewModel: SearchToolbarTabWrapperViewModel? = null
|
||||
private var mainWrapperViewModel: MainWrapperViewModel? = null
|
||||
|
||||
private val viewModel by viewModels<CustomPageViewModel>()
|
||||
private val viewModel by viewModels<CustomPageViewModel>(
|
||||
factoryProducer = {
|
||||
CustomPageViewModel.Factory(com.gh.gamecenter.login.HaloApp.getInstance())
|
||||
}
|
||||
)
|
||||
|
||||
private lateinit var binding: FragmentCustomBinding
|
||||
|
||||
@ -111,6 +117,7 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable, IB
|
||||
private var customPageName = ""
|
||||
private var bottomTabId = ""
|
||||
private var bottomTabName = ""
|
||||
private var bottomTabIndex = -1
|
||||
private var tabIndex = -1
|
||||
private var showFloatingWindow = true
|
||||
private var showPullDownPush = true
|
||||
@ -144,6 +151,7 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable, IB
|
||||
customPageName = arguments?.getString(EntranceConsts.KEY_CUSTOM_PAGE_NAME, "") ?: ""
|
||||
bottomTabId = arguments?.getString(EntranceConsts.KEY_BOTTOM_TAB_ID, "") ?: ""
|
||||
bottomTabName = arguments?.getString(EntranceConsts.KEY_BOTTOM_TAB_NAME, "") ?: ""
|
||||
bottomTabIndex = arguments?.getInt(EntranceConsts.KEY_BOTTOM_TAB_INDEX, -1) ?: -1
|
||||
tabIndex = arguments?.getInt(EntranceConsts.KEY_TAB_INDEX, -1) ?: -1
|
||||
showFloatingWindow = arguments?.getBoolean(EntranceConsts.KEY_SHOW_FLOATING_WINDOW, true) ?: true
|
||||
showPullDownPush = arguments?.getBoolean(EntranceConsts.KEY_SHOW_PULL_DOWN_PUSH, true) ?: true
|
||||
@ -182,8 +190,17 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable, IB
|
||||
exposureSourceList,
|
||||
isInSearchToolbarTabWrapperPage
|
||||
)
|
||||
|
||||
val precisePageLevelString = (activity as? IPageLevelProvider)
|
||||
?.getPageLevel()
|
||||
?.getTempNewPageLevel(topTabPosition = tabIndex, bottomTabPosition = bottomTabIndex)
|
||||
?.toFormattedString(ignoreBottomTabPositionMap = true)
|
||||
?: ""
|
||||
|
||||
pageLocation = PageLocation(
|
||||
bottomTabName,
|
||||
bottomTabIndex,
|
||||
precisePageLevelString,
|
||||
multiTabNavName,
|
||||
multiTabNavId,
|
||||
tabIndex,
|
||||
@ -230,7 +247,12 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable, IB
|
||||
})
|
||||
|
||||
dataList.observe(viewLifecycleOwner) {
|
||||
adapter.submitList(it)
|
||||
adapter.submitList(it) {
|
||||
if (shouldScrollToTop) {
|
||||
shouldScrollToTop = false
|
||||
binding.gameList.scrollToPosition(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
loadStatus.observe(viewLifecycleOwner) { (status, isPullToRefresh) ->
|
||||
@ -352,7 +374,6 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable, IB
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
subjectDestination.observe(viewLifecycleOwner, EventObserver { subject ->
|
||||
@ -553,6 +574,7 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable, IB
|
||||
binding.gameList.itemAnimator = null
|
||||
binding.gameList.layoutManager = layoutManager
|
||||
binding.gameList.adapter = adapter
|
||||
binding.gameList.addExposureHelper(this, DefaultExposureStateChangeListener())
|
||||
|
||||
var listScrollHeight = 0
|
||||
binding.gameList.addOnScrollListener(object : OnScrollListener() {
|
||||
@ -570,9 +592,6 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable, IB
|
||||
}
|
||||
})
|
||||
|
||||
val exposureListener = ExposureListener(this, adapter)
|
||||
binding.gameList.addOnScrollListener(exposureListener)
|
||||
|
||||
binding.gameRefresh.setOnRefreshListener {
|
||||
onRefresh()
|
||||
}
|
||||
|
||||
@ -7,6 +7,8 @@ import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MediatorLiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.gh.common.util.GameUtils
|
||||
import com.gh.common.util.NewFlatLogUtils
|
||||
import com.gh.common.util.NewLogUtils
|
||||
@ -31,10 +33,24 @@ import com.gh.gamecenter.feature.exposure.ExposureEvent
|
||||
import com.gh.gamecenter.home.PageConfigure
|
||||
import com.gh.gamecenter.home.custom.GamePositionAndPackageHelper.Companion.putGameWithPosition
|
||||
import com.gh.gamecenter.home.custom.eventlistener.OnCustomPageEventListener
|
||||
import com.gh.gamecenter.home.custom.model.*
|
||||
import com.gh.gamecenter.home.custom.model.CustomCommonContentCollectionItem
|
||||
import com.gh.gamecenter.home.custom.model.CustomDspPlaceholderItem
|
||||
import com.gh.gamecenter.home.custom.model.CustomPKItem
|
||||
import com.gh.gamecenter.home.custom.model.CustomPageData
|
||||
import com.gh.gamecenter.home.custom.model.CustomPageItem
|
||||
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMPONENTS_COLLECTION_STYLE_REFRESH_ICONS_4_2
|
||||
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMPONENTS_COLLECTION_STYLE_REFRESH_SLIDE_LIST
|
||||
import com.gh.gamecenter.home.custom.model.CustomPageRepository
|
||||
import com.gh.gamecenter.home.custom.model.CustomPluginItem
|
||||
import com.gh.gamecenter.home.custom.model.CustomRecentGamesItem
|
||||
import com.gh.gamecenter.home.custom.model.CustomSplitCommonContentCollectionItem
|
||||
import com.gh.gamecenter.home.custom.model.CustomSplitSubjectItem
|
||||
import com.gh.gamecenter.home.custom.model.CustomSubjectCollectionItem
|
||||
import com.gh.gamecenter.home.custom.model.CustomSubjectItem
|
||||
import com.gh.gamecenter.home.custom.model.CustomWeChatMiniGamesCPMSubjectItem
|
||||
import com.gh.gamecenter.livedata.Event
|
||||
import com.gh.gamecenter.login.user.UserRepository
|
||||
import com.gh.gamecenter.login.user.UserViewModel
|
||||
import com.gh.gamecenter.wrapper.SearchToolbarTabWrapperViewModel
|
||||
import com.lightgame.utils.Utils
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
@ -44,9 +60,7 @@ import io.reactivex.schedulers.Schedulers
|
||||
import retrofit2.HttpException
|
||||
import kotlin.collections.set
|
||||
|
||||
class CustomPageViewModel(
|
||||
application: Application
|
||||
) : AndroidViewModel(application), OnCustomPageEventListener {
|
||||
class CustomPageViewModel(application: Application) : AndroidViewModel(application), OnCustomPageEventListener {
|
||||
|
||||
private val compositeDisposable = CompositeDisposable()
|
||||
|
||||
@ -108,8 +122,6 @@ class CustomPageViewModel(
|
||||
*/
|
||||
private val cpmSubjectChangedPageMap: ArrayMap<String, Int> = ArrayMap()
|
||||
|
||||
var slideDiscoveryGamesPage = -1
|
||||
|
||||
private lateinit var _pageTracker: CustomPageTracker
|
||||
|
||||
val pageTracker: CustomPageTracker
|
||||
@ -120,10 +132,12 @@ class CustomPageViewModel(
|
||||
|
||||
val pkVoteResultLiveData = MutableLiveData<Event<Pair<String, Boolean>>>()
|
||||
|
||||
var shouldScrollToTop: Boolean = false
|
||||
|
||||
fun init(
|
||||
pageConfigure: PageConfigure,
|
||||
searchToolbarTabWrapperViewModel: SearchToolbarTabWrapperViewModel?,
|
||||
pageLocation: PageLocation
|
||||
pageLocation: PageLocation,
|
||||
) {
|
||||
this.searchToolbarTabWrapperViewModel = searchToolbarTabWrapperViewModel
|
||||
_pageConfigure = pageConfigure
|
||||
@ -397,7 +411,7 @@ class CustomPageViewModel(
|
||||
if (gameList != null) {// 直接读取缓存数据
|
||||
notifyWGameCPMABatchChanged(gameList, subjectId, page)
|
||||
} else {
|
||||
repository.loadChangeSubjectWGameCPM(page)
|
||||
repository.loadChangeSubjectWGameCPM(page, subjectEntity.size.limit, subjectEntity.onlyFee)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : Response<List<GameEntity>>() {
|
||||
@ -459,7 +473,7 @@ class CustomPageViewModel(
|
||||
// 随机产生专题数据(换一换)
|
||||
private fun getRandomGameList(
|
||||
oldList: MutableList<GameEntity>?,
|
||||
sourceList: MutableList<GameEntity>
|
||||
sourceList: MutableList<GameEntity>,
|
||||
): MutableList<GameEntity> {
|
||||
val resultGameList = ArrayList<GameEntity>()
|
||||
val filterGameList = GameUtils.removeDuplicateData(oldList, sourceList)//排除重复
|
||||
@ -570,6 +584,11 @@ class CustomPageViewModel(
|
||||
subjectItem.data
|
||||
)
|
||||
if (customPageItemList.isEmpty()) return
|
||||
|
||||
if (index == 0) {
|
||||
shouldScrollToTop = true
|
||||
}
|
||||
|
||||
newData[index] = customPageItemList[0]
|
||||
newData.addAll(index + 1, customPageItemList.subList(1, customPageItemList.size))
|
||||
newData.forEachIndexed { pos, customPageItem ->
|
||||
@ -607,6 +626,10 @@ class CustomPageViewModel(
|
||||
subjectItem.data
|
||||
)
|
||||
if (customPageItemList.isEmpty()) return
|
||||
|
||||
if (index == 0) {
|
||||
shouldScrollToTop = true
|
||||
}
|
||||
newData[index] = customPageItemList[0]
|
||||
newData.addAll(index + 1, customPageItemList.subList(1, customPageItemList.size))
|
||||
newData.forEachIndexed { pos, customPageItem ->
|
||||
@ -701,7 +724,7 @@ class CustomPageViewModel(
|
||||
|
||||
private fun getPositionAndPackageMap(
|
||||
customPageItem: CustomPageItem,
|
||||
gameList: List<GameEntity>?
|
||||
gameList: List<GameEntity>?,
|
||||
): HashMap<String, Int> {
|
||||
val hashMap = hashMapOf<String, Int>()
|
||||
if (customPageItem.shouldShowDownloadButton) {
|
||||
@ -720,7 +743,7 @@ class CustomPageViewModel(
|
||||
item: CustomPageItem,
|
||||
childPosition: Int,
|
||||
game: GameEntity,
|
||||
text: String
|
||||
text: String,
|
||||
) {
|
||||
_gameDetailDestination.value = Event(Pair("", game))
|
||||
}
|
||||
@ -746,7 +769,7 @@ class CustomPageViewModel(
|
||||
|
||||
override fun navigateSubjectDetailPage(
|
||||
item: CustomSubjectCollectionItem,
|
||||
subject: CustomPageData.LinkColumnCollection.CustomSubjectEntity
|
||||
subject: CustomPageData.LinkColumnCollection.CustomSubjectEntity,
|
||||
) {
|
||||
_subjectDestinationWithCustom.value = Event(subject to item.data.subjectType)
|
||||
}
|
||||
@ -759,7 +782,7 @@ class CustomPageViewModel(
|
||||
override fun navigateSubjectCollectionPage(
|
||||
item: CustomSubjectCollectionItem,
|
||||
childPosition: Int,
|
||||
subject: CustomPageData.LinkColumnCollection.CustomSubjectEntity?
|
||||
subject: CustomPageData.LinkColumnCollection.CustomSubjectEntity?,
|
||||
) {
|
||||
_subjectCollectionDestination.value = Event(Triple(item, childPosition, subject))
|
||||
}
|
||||
@ -772,7 +795,7 @@ class CustomPageViewModel(
|
||||
override fun navigateGameListDetailPage(
|
||||
item: CustomSubjectCollectionItem,
|
||||
childPosition: Int,
|
||||
subject: CustomPageData.LinkColumnCollection.CustomSubjectEntity
|
||||
subject: CustomPageData.LinkColumnCollection.CustomSubjectEntity,
|
||||
) {
|
||||
_gameListDetailDestination.value = Event(Triple(item, childPosition, subject))
|
||||
}
|
||||
@ -808,7 +831,7 @@ class CustomPageViewModel(
|
||||
item: CustomPageItem,
|
||||
link: LinkEntity,
|
||||
text: String,
|
||||
exposureEvent: ExposureEvent?
|
||||
exposureEvent: ExposureEvent?,
|
||||
) {
|
||||
_linkDestination.value = Event(Pair(link, exposureEvent))
|
||||
}
|
||||
@ -834,7 +857,7 @@ class CustomPageViewModel(
|
||||
item: CustomPageItem,
|
||||
gameId: String,
|
||||
gameName: String?,
|
||||
childPosition: Int
|
||||
childPosition: Int,
|
||||
) {
|
||||
val exposureEvent = item.exposureEventList.getOrNull(childPosition)
|
||||
_gameDetailDestinationOnAmway.value = Event(Pair(gameId, exposureEvent))
|
||||
@ -909,7 +932,7 @@ class CustomPageViewModel(
|
||||
|
||||
class SubjectChanged(
|
||||
val subjectId: String,
|
||||
val page: Int
|
||||
val page: Int,
|
||||
) {
|
||||
companion object {
|
||||
private const val HASH = 30
|
||||
@ -929,4 +952,13 @@ class CustomPageViewModel(
|
||||
|
||||
}
|
||||
|
||||
class Factory(private val mApplication: Application)
|
||||
: ViewModelProvider.NewInstanceFactory() {
|
||||
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return CustomPageViewModel(mApplication) as T
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -7,6 +7,8 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import com.gh.gamecenter.common.utils.ImageUtils
|
||||
import com.gh.gamecenter.common.utils.toBinding
|
||||
import com.gh.gamecenter.databinding.GameCollectionBannerItemBinding
|
||||
import com.gh.gamecenter.feature.exposure.ExposureEvent
|
||||
import com.gh.gamecenter.feature.exposure.IExposureProvider
|
||||
import com.gh.gamecenter.home.custom.model.CustomPageData
|
||||
|
||||
class AnnouncementBannerAdapter(
|
||||
@ -44,7 +46,6 @@ class AnnouncementBannerAdapter(
|
||||
} else {
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -72,7 +73,6 @@ class AnnouncementBannerAdapter(
|
||||
|
||||
override fun onBindViewHolder(holder: AnnouncementBannerChildViewHolder, position: Int) {
|
||||
val item = getItem(position)
|
||||
listener.exposure(position, item)
|
||||
holder.bind(item)
|
||||
holder.binding.bannerIv.setOnClickListener {
|
||||
listener.onItemClick(getDataPosition(position), item)
|
||||
@ -80,19 +80,23 @@ class AnnouncementBannerAdapter(
|
||||
}
|
||||
|
||||
class AnnouncementBannerChildViewHolder(val binding: GameCollectionBannerItemBinding) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
RecyclerView.ViewHolder(binding.root), IExposureProvider {
|
||||
|
||||
var exposureEvent: ExposureEvent? = null
|
||||
|
||||
fun bind(item: CustomPageData.Announcement) {
|
||||
exposureEvent = item.exposureEvent
|
||||
ImageUtils.display(binding.bannerIv, item.image)
|
||||
}
|
||||
|
||||
override fun provideExposureData(): ExposureEvent? {
|
||||
return exposureEvent?.getFreshExposureEvent()
|
||||
}
|
||||
}
|
||||
|
||||
interface OnChildEventListener {
|
||||
|
||||
fun onItemClick(childPosition: Int, announcement: CustomPageData.Announcement)
|
||||
|
||||
fun getCurrentPosition(): Int
|
||||
|
||||
fun exposure(childPosition: Int, announcement: CustomPageData.Announcement)
|
||||
}
|
||||
}
|
||||
@ -9,12 +9,13 @@ import com.gh.gamecenter.common.utils.toBinding
|
||||
import com.gh.gamecenter.common.utils.toColor
|
||||
import com.gh.gamecenter.common.utils.toDrawable
|
||||
import com.gh.gamecenter.databinding.RecyclerContentLabelLaneItemBinding
|
||||
import com.gh.gamecenter.feature.exposure.ExposureEvent
|
||||
import com.gh.gamecenter.feature.exposure.IExposureProvider
|
||||
import com.gh.gamecenter.home.custom.model.CustomPageData
|
||||
|
||||
class ContentLabelLaneAdapter(
|
||||
context: Context,
|
||||
private val clickInvoke: (Int, CustomPageData.CommonContentCollection.ContentTag) -> Unit,
|
||||
private val exposureInvoke: (Int, CustomPageData.CommonContentCollection.ContentTag) -> Unit
|
||||
) : CustomBaseChildAdapter<CustomPageData.CommonContentCollection.ContentTag, ContentLabelLaneAdapter.ContentLabelChildViewHolder>(
|
||||
context
|
||||
) {
|
||||
@ -29,6 +30,9 @@ class ContentLabelLaneAdapter(
|
||||
|
||||
override fun onBindViewHolder(holder: ContentLabelChildViewHolder, position: Int) {
|
||||
val item = getItem(position)
|
||||
|
||||
holder.exposureEvent = item.exposureEvent
|
||||
|
||||
with(holder.binding) {
|
||||
vBackground.background = R.drawable.bg_shape_content_label_lane_item.toDrawable(context)
|
||||
tvTitle.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
|
||||
@ -47,7 +51,6 @@ class ContentLabelLaneAdapter(
|
||||
}
|
||||
}
|
||||
|
||||
exposureInvoke(position, item)
|
||||
holder.itemView.setOnClickListener {
|
||||
clickInvoke(position, item)
|
||||
}
|
||||
@ -55,9 +58,12 @@ class ContentLabelLaneAdapter(
|
||||
|
||||
class ContentLabelChildViewHolder(
|
||||
val binding: RecyclerContentLabelLaneItemBinding
|
||||
) : RecyclerView.ViewHolder(binding.root) {
|
||||
) : RecyclerView.ViewHolder(binding.root), IExposureProvider {
|
||||
var exposureEvent : ExposureEvent? = null
|
||||
|
||||
override fun provideExposureData(): ExposureEvent? {
|
||||
return exposureEvent?.getFreshExposureEvent()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -9,6 +9,8 @@ import com.gh.gamecenter.common.utils.*
|
||||
import com.gh.gamecenter.databinding.CommonCollectionItemBinding
|
||||
import com.gh.gamecenter.entity.CommonCollectionContentEntity
|
||||
import com.gh.gamecenter.entity.ExposureLinkEntity
|
||||
import com.gh.gamecenter.feature.exposure.ExposureEvent
|
||||
import com.gh.gamecenter.feature.exposure.IExposureProvider
|
||||
import com.gh.gamecenter.home.custom.model.CustomPageData
|
||||
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_IMAGE_TEXT
|
||||
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_SLIDE_BANNER
|
||||
@ -57,6 +59,7 @@ class CustomCommonCollectionAdapter(
|
||||
}
|
||||
}
|
||||
listener.addExposureEvent(position, item.linkEntity)
|
||||
holder.boundedLinkEntity = item.linkEntity
|
||||
|
||||
holder.binding.apply {
|
||||
ImageUtils.display(commonCollectionImage, item.image)
|
||||
@ -109,10 +112,15 @@ class CustomCommonCollectionAdapter(
|
||||
}
|
||||
|
||||
class CustomCommonCollectionItemViewHolder(val binding: CommonCollectionItemBinding) :
|
||||
BaseRecyclerViewHolder<Any>(binding.root)
|
||||
BaseRecyclerViewHolder<Any>(binding.root), IExposureProvider {
|
||||
var boundedLinkEntity: ExposureLinkEntity? = null
|
||||
|
||||
override fun provideExposureData(): ExposureEvent? {
|
||||
return boundedLinkEntity?.exposureEvent?.getFreshExposureEvent()
|
||||
}
|
||||
}
|
||||
|
||||
interface OnEventListener {
|
||||
|
||||
fun addExposureEvent(childPosition: Int, exposureLinkEntity: ExposureLinkEntity)
|
||||
|
||||
fun onChildItemClick(childPosition: Int, item: CommonCollectionContentEntity)
|
||||
|
||||
@ -166,7 +166,6 @@ class CustomDiscoverCardGameAdapter(
|
||||
RetrofitManager.getInstance().api.discorveryFeedback(gameId, paramsMap.toRequestBody())
|
||||
.compose(singleToMain())
|
||||
.subscribe(object : BiResponse<ResponseBody>() {
|
||||
|
||||
override fun onSuccess(data: ResponseBody) {
|
||||
callback.invoke()
|
||||
}
|
||||
@ -194,7 +193,6 @@ class CustomDiscoverCardGameAdapter(
|
||||
notifyChildItem(busFour.packageName)
|
||||
}
|
||||
|
||||
|
||||
private fun notifyChildItem(packageName: String) {
|
||||
dataList.forEachIndexed { position, gameEntity ->
|
||||
gameEntity.getApk().forEach { apkEntity ->
|
||||
|
||||
@ -25,8 +25,9 @@ import com.gh.gamecenter.core.utils.StringUtils
|
||||
import com.gh.gamecenter.databinding.RecyclerFoldSlideLargeImageItemBinding
|
||||
import com.gh.gamecenter.eventbus.EBDownloadStatus
|
||||
import com.gh.gamecenter.eventbus.EBPackage
|
||||
import com.gh.gamecenter.feature.entity.CustomPageTrackData
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.feature.exposure.ExposureEvent
|
||||
import com.gh.gamecenter.feature.exposure.IExposureProvider
|
||||
import com.gh.gamecenter.feature.game.GameItemViewHolder
|
||||
import com.gh.gamecenter.feature.minigame.MiniGameItemHelper
|
||||
import com.gh.gamecenter.home.custom.eventlistener.SubjectEventHelper
|
||||
@ -53,7 +54,6 @@ class CustomFoldSlideLargeImageItemAdapter(
|
||||
}
|
||||
submitList(list, true)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun getKey(t: GameEntity): String {
|
||||
@ -88,10 +88,8 @@ class CustomFoldSlideLargeImageItemAdapter(
|
||||
holder.itemView.setOnClickListener {
|
||||
eventHelper.navigateToGameDetailPage(realPosition, game)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
|
||||
_recyclerView = recyclerView
|
||||
}
|
||||
@ -107,7 +105,6 @@ class CustomFoldSlideLargeImageItemAdapter(
|
||||
return@forEach
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
override fun notifyDownloadDeleted(status: EBDownloadStatus) {
|
||||
@ -139,7 +136,6 @@ class CustomFoldSlideLargeImageItemAdapter(
|
||||
action(position, game)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun getInitPosition() =
|
||||
@ -153,7 +149,7 @@ class CustomFoldSlideLargeImageItemAdapter(
|
||||
private val eventHelper: SubjectEventHelper,
|
||||
val binding: RecyclerFoldSlideLargeImageItemBinding
|
||||
) :
|
||||
RecyclerView.ViewHolder(binding.root) {
|
||||
RecyclerView.ViewHolder(binding.root), IExposureProvider {
|
||||
|
||||
private lateinit var item: GameEntity
|
||||
|
||||
@ -200,7 +196,6 @@ class CustomFoldSlideLargeImageItemAdapter(
|
||||
}
|
||||
|
||||
if (data.shouldShowDownloadButton) {
|
||||
|
||||
binding.btnDownload.goneIf(false)
|
||||
DownloadItemUtils.setOnClickListener(
|
||||
itemView.context, binding.btnDownload, game, bindingAdapterPosition,
|
||||
@ -226,7 +221,6 @@ class CustomFoldSlideLargeImageItemAdapter(
|
||||
} else {
|
||||
binding.btnDownload.goneIf(true)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun getBottomBackground(oColor: String): Pair<Int, Drawable> {
|
||||
@ -303,6 +297,10 @@ class CustomFoldSlideLargeImageItemAdapter(
|
||||
}
|
||||
}
|
||||
|
||||
override fun provideExposureData(): ExposureEvent? {
|
||||
return item.exposureEvent?.getFreshExposureEvent()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val BUBBLE_SHOW_DURATION = 4000L
|
||||
}
|
||||
|
||||
@ -24,7 +24,6 @@ class CustomGameHorizontalSlideAdapter(
|
||||
|
||||
var gameName = ""
|
||||
var entrance = ""
|
||||
private var _exposureEventList: List<ExposureEvent>? = null
|
||||
|
||||
private var isShowFirstLine = false
|
||||
private var isShowSecondLine = false
|
||||
@ -42,12 +41,11 @@ class CustomGameHorizontalSlideAdapter(
|
||||
get() = _data.data
|
||||
|
||||
|
||||
fun setData(data: CustomSubjectItem, exposureEventList: List<ExposureEvent>?) {
|
||||
fun setData(data: CustomSubjectItem) {
|
||||
isShowFirstLine = false
|
||||
isShowSecondLine = false
|
||||
hasTwoLinesName = false
|
||||
_data = data
|
||||
_exposureEventList = exposureEventList
|
||||
data.data.data?.forEach {
|
||||
if (!isShowFirstLine && it.assignRemark.firstLine.isNotEmpty()) {
|
||||
isShowFirstLine = true
|
||||
@ -86,7 +84,6 @@ class CustomGameHorizontalSlideAdapter(
|
||||
}
|
||||
holder.bindGameHorizontalItem(gameEntity, subjectEntity, isShowFirstLine, isShowSecondLine)
|
||||
holder.itemView.setOnClickListener {
|
||||
gameEntity.exposureEvent = _exposureEventList?.getOrNull(position)
|
||||
eventHelper.navigateToGameDetailPage(position, gameEntity)
|
||||
}
|
||||
|
||||
@ -99,7 +96,7 @@ class CustomGameHorizontalSlideAdapter(
|
||||
this,
|
||||
StringUtils.buildString("(游戏-专题:", subjectEntity.name, "-列表[", (position + 1).toString(), "])"),
|
||||
location = StringUtils.buildString("游戏-专题-", subjectEntity.name, ":", gameEntity.name),
|
||||
traceEvent = _exposureEventList?.getOrNull(position)
|
||||
traceEvent = gameEntity.exposureEvent
|
||||
) {
|
||||
eventHelper.onDownloadButtonClick(position, gameEntity)
|
||||
}
|
||||
|
||||
@ -20,6 +20,7 @@ import com.gh.gamecenter.entity.GameNavigationEntity
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.feature.exposure.ExposureEvent
|
||||
import com.gh.gamecenter.feature.exposure.ExposureType
|
||||
import com.gh.gamecenter.feature.exposure.IExposureProvider
|
||||
|
||||
class CustomGameNavigationAdapter(
|
||||
context: Context,
|
||||
@ -46,6 +47,7 @@ class CustomGameNavigationAdapter(
|
||||
// 是否显示小红点
|
||||
var isShowHint = false
|
||||
val entity = dataList[position]
|
||||
holder.exposureEvent = exposureEventList?.getOrNull(position)
|
||||
ImageUtils.display(holder.binding.navigationView, entity.image)
|
||||
holder.binding.navigationNameTv.text = if (entity.isShowEntryName) {
|
||||
entity.entryName
|
||||
@ -132,7 +134,6 @@ class CustomGameNavigationAdapter(
|
||||
listener.navigateToLinkPage(it, "导航栏", exposureEvent)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun showGuide(entity: GameNavigationEntity, binding: ItemGameNavigationCustomBinding) {
|
||||
@ -156,7 +157,6 @@ class CustomGameNavigationAdapter(
|
||||
gradientDrawable.setStroke(1F.dip2px(), entity.guide.borderColorInt)
|
||||
gradientDrawable.setColor(entity.guide.backgroundColorInt)
|
||||
binding.tvBubble.background = gradientDrawable
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
@ -164,7 +164,14 @@ class CustomGameNavigationAdapter(
|
||||
}
|
||||
|
||||
class GameNavigationViewHolder(val binding: ItemGameNavigationCustomBinding) :
|
||||
BaseRecyclerViewHolder<RecyclerView.ViewHolder>(binding.root)
|
||||
BaseRecyclerViewHolder<RecyclerView.ViewHolder>(binding.root), IExposureProvider {
|
||||
|
||||
var exposureEvent: ExposureEvent? = null
|
||||
|
||||
override fun provideExposureData(): ExposureEvent? {
|
||||
return exposureEvent?.getFreshExposureEvent()
|
||||
}
|
||||
}
|
||||
|
||||
interface OnEventListener {
|
||||
fun navigateToLinkPage(link: LinkEntity, text: String, exposureEvent: ExposureEvent?)
|
||||
|
||||
@ -15,7 +15,6 @@ import com.gh.common.util.HomePluggableHelper
|
||||
import com.gh.gamecenter.common.constant.ItemViewType
|
||||
import com.gh.gamecenter.common.utils.*
|
||||
import com.gh.gamecenter.core.utils.DisplayUtils
|
||||
import com.gh.gamecenter.core.utils.MtaHelper.onEvent
|
||||
import com.gh.gamecenter.core.utils.StringUtils
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.feature.entity.PluginLocation
|
||||
@ -120,7 +119,6 @@ class CustomGamePluginAdapter(
|
||||
null
|
||||
)
|
||||
holder.itemView.setOnClickListener { v: View? ->
|
||||
onEvent("首页_新", "点击", "插件化" + (position + 1) + "_" + gameEntity.name)
|
||||
DataCollectionUtils.uploadClick(
|
||||
context,
|
||||
"插件化" + "-列表",
|
||||
|
||||
@ -11,10 +11,10 @@ import com.gh.gamecenter.adapter.viewholder.GameViewHolder
|
||||
import com.gh.gamecenter.common.utils.*
|
||||
import com.gh.gamecenter.eventbus.EBDownloadStatus
|
||||
import com.gh.gamecenter.eventbus.EBPackage
|
||||
import com.gh.gamecenter.feature.entity.CustomPageTrackData
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.feature.exposure.ExposureEvent
|
||||
import com.gh.gamecenter.feature.exposure.IExposureProvider
|
||||
import com.gh.gamecenter.feature.game.GameItemViewHolder
|
||||
import com.gh.gamecenter.feature.minigame.MiniGameItemHelper
|
||||
import com.gh.gamecenter.game.vertical.GameItemUi
|
||||
import com.gh.gamecenter.home.custom.eventlistener.GameSubjectCollectionEventHelper
|
||||
import com.gh.gamecenter.home.custom.model.CustomPageData
|
||||
@ -95,12 +95,13 @@ class CustomGameRefreshVerticalAdapter(
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
inner class SimpleGameItemViewHolder(private val ui: GameItemUi) : ViewHolder(ui.root) {
|
||||
inner class SimpleGameItemViewHolder(private val ui: GameItemUi) : ViewHolder(ui.root), IExposureProvider {
|
||||
var placeholderGameViewHolder: GameViewHolder? = null
|
||||
|
||||
private var exposureEvent: ExposureEvent? = null
|
||||
|
||||
fun bindSimpleGameItem(
|
||||
adapter: RecyclerView.Adapter<ViewHolder>,
|
||||
gameEntity: GameEntity,
|
||||
@ -124,6 +125,8 @@ class CustomGameRefreshVerticalAdapter(
|
||||
var paddingEnd = if (isEndOfRow) 16F.dip2px() else 0F.dip2px()
|
||||
val height = 80F.dip2px()
|
||||
|
||||
exposureEvent = gameEntity.exposureEvent
|
||||
|
||||
itemView.layoutParams = if (!isEndOfRow) {
|
||||
paddingEnd += 1
|
||||
ViewGroup.LayoutParams(maxWidth - 24F.dip2px(), height)
|
||||
@ -210,6 +213,9 @@ class CustomGameRefreshVerticalAdapter(
|
||||
root.setPadding(paddingStart, 8F.dip2px(), paddingEnd, 8F.dip2px())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun provideExposureData(): ExposureEvent? {
|
||||
return exposureEvent?.getFreshExposureEvent()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -13,8 +13,9 @@ import com.gh.gamecenter.common.utils.*
|
||||
import com.gh.gamecenter.entity.SubjectEntity
|
||||
import com.gh.gamecenter.eventbus.EBDownloadStatus
|
||||
import com.gh.gamecenter.eventbus.EBPackage
|
||||
import com.gh.gamecenter.feature.entity.CustomPageTrackData
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.feature.exposure.ExposureEvent
|
||||
import com.gh.gamecenter.feature.exposure.IExposureProvider
|
||||
import com.gh.gamecenter.feature.game.GameItemViewHolder
|
||||
import com.gh.gamecenter.feature.minigame.MiniGameItemHelper
|
||||
import com.gh.gamecenter.game.vertical.GameItemUi
|
||||
@ -103,9 +104,11 @@ class CustomGameVerticalAdapter(
|
||||
class SimpleGameItemViewHolder(
|
||||
private val ui: GameItemUi,
|
||||
private val eventHelper: SubjectEventHelper
|
||||
) : ViewHolder(ui.root) {
|
||||
) : ViewHolder(ui.root), IExposureProvider {
|
||||
var placeholderGameViewHolder: GameViewHolder? = null
|
||||
|
||||
private var boundedGameEntity: GameEntity? = null
|
||||
|
||||
fun bindSimpleGameItem(
|
||||
adapter: RecyclerView.Adapter<ViewHolder>,
|
||||
gameEntity: GameEntity,
|
||||
@ -122,6 +125,8 @@ class CustomGameVerticalAdapter(
|
||||
) {
|
||||
val context = itemView.context
|
||||
|
||||
boundedGameEntity = gameEntity
|
||||
|
||||
val paddingStart = 16F.dip2px()
|
||||
val isEndOfRow = position >= if (adapter.itemCount % spanCount == 0) {
|
||||
adapter.itemCount - spanCount
|
||||
@ -220,6 +225,10 @@ class CustomGameVerticalAdapter(
|
||||
root.setPadding(paddingStart, root.paddingTop, paddingEnd, root.paddingBottom)
|
||||
}
|
||||
}
|
||||
|
||||
override fun provideExposureData(): ExposureEvent? {
|
||||
return boundedGameEntity?.exposureEvent?.getFreshExposureEvent()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user