Compare commits
3 Commits
dev
...
feat/GHZSC
| Author | SHA1 | Date | |
|---|---|---|---|
| e2ebaa303e | |||
| e4f32302a5 | |||
| f227146f72 |
@ -72,6 +72,7 @@ android_build:
|
||||
only:
|
||||
- dev
|
||||
- release
|
||||
- feat/GHZSCY-8069
|
||||
|
||||
# 代码检查
|
||||
sonarqube_analysis:
|
||||
@ -103,6 +104,7 @@ sonarqube_analysis:
|
||||
only:
|
||||
- dev
|
||||
- release
|
||||
- feat/GHZSCY-8069
|
||||
|
||||
## 发送简易检测结果报告
|
||||
send_sonar_report:
|
||||
@ -121,6 +123,7 @@ send_sonar_report:
|
||||
only:
|
||||
- dev
|
||||
- release
|
||||
- feat/GHZSCY-8069
|
||||
|
||||
oss-upload&send-email:
|
||||
tags:
|
||||
@ -157,4 +160,5 @@ oss-upload&send-email:
|
||||
only:
|
||||
- dev
|
||||
- release
|
||||
- feat/GHZSCY-8069
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ import io.reactivex.Single
|
||||
|
||||
class GameSubjectDSPRemoteDataSource(private val api: DspApiService = RetrofitManager.getInstance().dspApiService) {
|
||||
|
||||
fun getDspGames(count: Int): Single<List<GameEntity>> {
|
||||
fun getDspGames(type: String, count: Int): Single<List<GameEntity>> {
|
||||
val meta = MetaUtil.getMeta()
|
||||
val request = mapOf(
|
||||
"device" to mapOf(
|
||||
|
||||
@ -1,26 +0,0 @@
|
||||
package com.gh.gamecenter.entity
|
||||
|
||||
import com.gh.gamecenter.servers.gametest2.GameServerTestV2ViewModel
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class GameServerTestDisplaySetting(
|
||||
@SerializedName("time_text_past")
|
||||
val timeTextPast: String = RECENT,
|
||||
@SerializedName("time_text_present")
|
||||
val timeTextPresent: String = TODAY,
|
||||
@SerializedName("time_text_future")
|
||||
val timeTextFuture: String = FUTURE,
|
||||
@SerializedName("game_category")
|
||||
val gameCategory: List<String> = listOf(
|
||||
GameServerTestV2ViewModel.GameCategory.Local.value,
|
||||
GameServerTestV2ViewModel.GameCategory.Online.value,
|
||||
GameServerTestV2ViewModel.GameCategory.Welfare.value,
|
||||
GameServerTestV2ViewModel.GameCategory.Gjonline.value
|
||||
),
|
||||
) {
|
||||
companion object {
|
||||
const val RECENT = "近期"
|
||||
const val TODAY = "今天"
|
||||
const val FUTURE = "预约"
|
||||
}
|
||||
}
|
||||
@ -1,33 +0,0 @@
|
||||
package com.gh.gamecenter.entity
|
||||
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.google.gson.annotations.SerializedName
|
||||
|
||||
data class SearchGameUnionEntity(
|
||||
@SerializedName("type")
|
||||
private val _type: String? = null,
|
||||
@SerializedName("link_game")
|
||||
val linkGame: GameEntity? = null,
|
||||
@SerializedName("link_wechat_game_cpm_column")
|
||||
val linkWechatGameCpmColumn: SearchSubjectEntity? = null,
|
||||
@SerializedName("link_dsp_game_column")
|
||||
val linkDspGameColumn: SearchSubjectEntity? = null,
|
||||
@SerializedName("link_wechat_game")
|
||||
val linkWechatGame: GameEntity? = null,
|
||||
@SerializedName("link_column")
|
||||
val linkColum: SearchSubjectEntity? = null,
|
||||
@SerializedName("link_ad_space")
|
||||
val linkAdSpace: AdConfig? = null
|
||||
) {
|
||||
val type: String
|
||||
get() = _type ?: ""
|
||||
|
||||
companion object {
|
||||
const val TYPE_GAME = "game"
|
||||
const val TYPE_WECHAT_GAME_CPM_COLUMN = "wechat_game_cpm_column"
|
||||
const val TYPE_DSP_GAME_COLUMN = "dsp_game_column"
|
||||
const val TYPE_WECHAT_GAME = "wechat_game"
|
||||
const val TYPE_COLUMN = "column"
|
||||
const val TYPE_AD_SPACE = "ad_space"
|
||||
}
|
||||
}
|
||||
@ -2,9 +2,8 @@ package com.gh.gamecenter.entity
|
||||
|
||||
import android.os.Parcelable
|
||||
import com.gh.common.filter.RegionSettingHelper
|
||||
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_DSP_GAME_COLUMN
|
||||
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_WECHAT_GAME_CPM_COLUMN
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.personalhome.home.UserHistoryViewModel
|
||||
import com.google.gson.annotations.SerializedName
|
||||
import kotlinx.parcelize.Parcelize
|
||||
|
||||
@ -19,6 +18,10 @@ data class SearchSubjectEntity(
|
||||
val codeId: String = "", // 广告CODE_ID(本地字段),不为空时为广告专题
|
||||
@SerializedName("ad_icon_active")
|
||||
val adIconActive: Boolean = false,
|
||||
// 本地字段,标记是否为微信小游戏CPM专题
|
||||
var isWGameSubjectCPM: Boolean = false,
|
||||
// 本地字段,标记是否为DSP专题
|
||||
var isDspSubject: Boolean = false,
|
||||
val type: String = "",
|
||||
|
||||
@SerializedName("column_type")
|
||||
@ -28,11 +31,10 @@ data class SearchSubjectEntity(
|
||||
val size: Int = -1, // 专题游戏数量
|
||||
) : Parcelable {
|
||||
|
||||
val isWGameSubjectCPM: Boolean
|
||||
get() = type == TYPE_WECHAT_GAME_CPM_COLUMN
|
||||
|
||||
val isDspSubject: Boolean
|
||||
get() = type == TYPE_DSP_GAME_COLUMN
|
||||
companion object {
|
||||
const val TYPE_WECHAT_GAME_CPM_COLUMN = "wechat_game_cpm_column"
|
||||
const val TYPE_DSP_GAME_COLUMN = "dsp_game_column"
|
||||
}
|
||||
|
||||
fun getFilterGame() = RegionSettingHelper.filterGame(games)
|
||||
}
|
||||
@ -815,10 +815,7 @@ class GameDetailWrapperFragment : BaseLazyFragment(), IScrollable {
|
||||
downloadStatus = gameEntity?.downloadStatusChinese ?: "",
|
||||
gameType = gameEntity?.categoryChinese ?: "",
|
||||
position = position,
|
||||
tabContent = tabEntity.name,
|
||||
linkType = tabEntity.link?.type ?: "",
|
||||
linkId = tabEntity.link?.link ?: "",
|
||||
linkText = tabEntity.link?.text ?: ""
|
||||
tabContent = tabEntity.name
|
||||
)
|
||||
|
||||
val entrance = if (mEntrance.contains("论坛详情")) "论坛" else "游戏"
|
||||
|
||||
@ -3,12 +3,7 @@ package com.gh.gamecenter.home.custom
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import androidx.collection.ArrayMap
|
||||
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 androidx.lifecycle.*
|
||||
import com.gh.common.util.GameUtils
|
||||
import com.gh.common.util.NewFlatLogUtils
|
||||
import com.gh.common.util.NewLogUtils
|
||||
@ -33,24 +28,10 @@ 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.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.*
|
||||
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
|
||||
@ -117,11 +98,6 @@ class CustomPageViewModel(application: Application) : AndroidViewModel(applicati
|
||||
|
||||
private val subjectChangedMap: ArrayMap<SubjectChanged, List<GameEntity>> = ArrayMap()
|
||||
|
||||
/**
|
||||
* 微信CPM专题当前的页码记录
|
||||
*/
|
||||
private val cpmSubjectChangedPageMap: ArrayMap<String, Int> = ArrayMap()
|
||||
|
||||
private lateinit var _pageTracker: CustomPageTracker
|
||||
|
||||
val pageTracker: CustomPageTracker
|
||||
@ -134,6 +110,9 @@ class CustomPageViewModel(application: Application) : AndroidViewModel(applicati
|
||||
|
||||
var shouldScrollToTop: Boolean = false
|
||||
|
||||
private var loadFirstDisposable: Disposable? = null
|
||||
private var loadMoreDisposable: Disposable? = null
|
||||
|
||||
fun init(
|
||||
pageConfigure: PageConfigure,
|
||||
searchToolbarTabWrapperViewModel: SearchToolbarTabWrapperViewModel?,
|
||||
@ -208,7 +187,15 @@ class CustomPageViewModel(application: Application) : AndroidViewModel(applicati
|
||||
|
||||
fun loadFirst(isPullToRefresh: Boolean, forceLoad: Boolean = false) {
|
||||
_loadStatus.value = LoadStatus.INIT_LOADING to isPullToRefresh
|
||||
repository.loadFirstCustomPageData(pageConfigure.pageId, forceLoad)
|
||||
|
||||
if (loadFirstDisposable != null && !loadFirstDisposable!!.isDisposed) {
|
||||
// 有可能上一次刷新数据还未获取成功,又再次触发了下拉刷新
|
||||
loadFirstDisposable?.dispose()
|
||||
}
|
||||
if (loadMoreDisposable != null && !loadMoreDisposable!!.isDisposed) {
|
||||
loadMoreDisposable?.dispose()
|
||||
}
|
||||
loadFirstDisposable = repository.loadFirstCustomPageData(pageConfigure.pageId, forceLoad)
|
||||
.map { (custom, list) ->
|
||||
Triple(custom, list, getPositionAndPackageMap(list))
|
||||
}
|
||||
@ -255,7 +242,7 @@ class CustomPageViewModel(application: Application) : AndroidViewModel(applicati
|
||||
|
||||
}
|
||||
|
||||
}).addDisposable()
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
@ -273,7 +260,7 @@ class CustomPageViewModel(application: Application) : AndroidViewModel(applicati
|
||||
}
|
||||
if (repository.isLoadingPKData) return
|
||||
_loadStatus.value = LoadStatus.LIST_LOADING to false
|
||||
repository.loadNextCustomPageData(pageConfigure.pageId)
|
||||
loadMoreDisposable = repository.loadNextCustomPageData(pageConfigure.pageId)
|
||||
.map {
|
||||
it to getPositionAndPackageMap(it)
|
||||
}
|
||||
@ -298,7 +285,7 @@ class CustomPageViewModel(application: Application) : AndroidViewModel(applicati
|
||||
super.onFailure(exception)
|
||||
_loadStatus.value = LoadStatus.LIST_FAILED to false
|
||||
}
|
||||
}).addDisposable()
|
||||
})
|
||||
}
|
||||
|
||||
override fun onRetry() {
|
||||
@ -388,59 +375,7 @@ class CustomPageViewModel(application: Application) : AndroidViewModel(applicati
|
||||
}
|
||||
|
||||
override fun onChangeABatch(subjectEntity: SubjectEntity) =
|
||||
if (subjectEntity.isWechatColumnCPM) {
|
||||
onChangeWGameCPMABatch(subjectEntity)
|
||||
} else {
|
||||
onChangeNormalGameABatch(subjectEntity)
|
||||
}
|
||||
|
||||
/**
|
||||
* 微信小游戏CPM的“换一批”功能实现
|
||||
*
|
||||
* @see <a href="https://jira.shanqu.cc/browse/GHZSCY-7167">【光环助手】CPM微信小游戏“换一批”功能优化</a>
|
||||
*/
|
||||
private fun onChangeWGameCPMABatch(subjectEntity: SubjectEntity) {
|
||||
val subjectId = subjectEntity.id ?: return
|
||||
val page = cpmSubjectChangedPageMap[subjectId] ?: let {
|
||||
// 第一次点击“换一批”时,先缓存第一页的数据
|
||||
subjectChangedMap[SubjectChanged(subjectId, 1)] = subjectEntity.data
|
||||
2
|
||||
}
|
||||
val subjectChanged = SubjectChanged(subjectId, page)
|
||||
val gameList = subjectChangedMap[subjectChanged]
|
||||
if (gameList != null) {// 直接读取缓存数据
|
||||
notifyWGameCPMABatchChanged(gameList, subjectId, page)
|
||||
} else {
|
||||
repository.loadChangeSubjectWGameCPM(page, subjectEntity.size.limit, subjectEntity.onlyFee)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : Response<List<GameEntity>>() {
|
||||
override fun onResponse(response: List<GameEntity>?) {
|
||||
if (response != null) {
|
||||
subjectChangedMap[subjectChanged] = response
|
||||
notifyWGameCPMABatchChanged(response, subjectId, page)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onFailure(e: HttpException?) {
|
||||
Utils.toast(getApplication(), "网络异常")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyWGameCPMABatchChanged(gameList: List<GameEntity>, subjectId: String, page: Int) {
|
||||
val nextPage: Int
|
||||
val cpmGameList = if (gameList.isEmpty()) {
|
||||
nextPage = 1// 已经到最后一页了,下一页是第一页
|
||||
subjectChangedMap[SubjectChanged(subjectId, 1)]!!
|
||||
} else {
|
||||
nextPage = page + 1
|
||||
gameList
|
||||
}
|
||||
cpmSubjectChangedPageMap[subjectId] = nextPage// 加载下一页数据
|
||||
changeSubjectCustomPageItem(subjectId, ArrayList(cpmGameList))
|
||||
}
|
||||
onChangeNormalGameABatch(subjectEntity)
|
||||
|
||||
/**
|
||||
* 光环游戏的“换一批”功能实现
|
||||
@ -564,40 +499,94 @@ class CustomPageViewModel(application: Application) : AndroidViewModel(applicati
|
||||
/**
|
||||
* 通过CPM接口异步请求获取的微信小游戏数据插入
|
||||
*/
|
||||
private fun notifyWechatMiniGameCPMSubjectItemChanged(result: Pair<String, List<GameEntity>>) {
|
||||
private fun notifyWechatMiniGameCPMSubjectItemChanged(result: Pair<Int, List<GameEntity>>) {
|
||||
|
||||
val (componentPosition, gameList) = result
|
||||
val oldData = _dataList.value ?: return
|
||||
val index = oldData.indexOfFirst {
|
||||
it is CustomWeChatMiniGamesCPMSubjectItem && it.data.id == result.first
|
||||
}
|
||||
if (index == -1) return
|
||||
val subjectItem = (oldData[index] as CustomWeChatMiniGamesCPMSubjectItem).apply {
|
||||
data.data = result.second.toMutableList()
|
||||
data.isWechatColumnCPM = true
|
||||
}
|
||||
val position = subjectItem.position
|
||||
val componentPosition = subjectItem.componentPosition
|
||||
val newData = oldData.toMutableList()
|
||||
val customPageItemList = repository.convertColumnDetailSubjectItems(
|
||||
position,
|
||||
componentPosition,
|
||||
subjectItem.link,
|
||||
subjectItem.data
|
||||
)
|
||||
if (customPageItemList.isEmpty()) return
|
||||
|
||||
if (index == 0) {
|
||||
shouldScrollToTop = true
|
||||
// 是否是cpm第一页数据
|
||||
val itemPosition = oldData.indexOfFirst { it.componentPosition == componentPosition }
|
||||
val targetItem = oldData.getOrNull(itemPosition)
|
||||
when {
|
||||
targetItem is CustomWeChatMiniGamesCPMSubjectItem -> {
|
||||
// 第一次添加数据
|
||||
val subject = targetItem.data
|
||||
subject.data = gameList.toMutableList()
|
||||
subject.isWechatColumnCPM = true
|
||||
|
||||
val cpmSubjectItemList = repository.convertColumnDetailSubjectItems(
|
||||
targetItem.position,
|
||||
componentPosition,
|
||||
targetItem.link,
|
||||
subject
|
||||
)
|
||||
if (cpmSubjectItemList.isEmpty()) return
|
||||
|
||||
if (itemPosition == 0) {
|
||||
shouldScrollToTop = true
|
||||
}
|
||||
|
||||
val newData = oldData.toMutableList()
|
||||
newData[itemPosition] = cpmSubjectItemList[0]
|
||||
newData.addAll(itemPosition + 1, cpmSubjectItemList.subList(1, cpmSubjectItemList.size))
|
||||
newData.forEachIndexed { pos, customPageItem ->
|
||||
customPageItem.position = pos
|
||||
}
|
||||
repository.notifyPositionChanged(newData.size)
|
||||
gamePositionAndPackageHelper.clear()
|
||||
gamePositionAndPackageHelper.putAll(getPositionAndPackageMap(newData))
|
||||
_dataList.value = newData
|
||||
}
|
||||
|
||||
targetItem is CustomSplitSubjectItem -> {// 添加后续的数据
|
||||
val subject = targetItem.data
|
||||
subject.data = ((subject.data ?: emptyList()) + gameList).toMutableList()
|
||||
subject.isWechatColumnCPM = true
|
||||
|
||||
val cpmSubjectItemList = repository.convertColumnDetailSubjectItems(
|
||||
targetItem.position,
|
||||
componentPosition,
|
||||
targetItem.link,
|
||||
subject
|
||||
)
|
||||
if (cpmSubjectItemList.isEmpty()) return
|
||||
val newData = oldData.toMutableList()
|
||||
|
||||
val position = newData.indexOfFirst { it.componentPosition == componentPosition }
|
||||
// 移除旧数据
|
||||
newData.removeAll { it.componentPosition == componentPosition }
|
||||
// 在原来的位置上插入新数据
|
||||
newData.addAll(position, cpmSubjectItemList)
|
||||
newData.forEachIndexed { pos, customPageItem ->
|
||||
customPageItem.position = pos
|
||||
}
|
||||
repository.notifyPositionChanged(newData.size)
|
||||
gamePositionAndPackageHelper.clear()
|
||||
gamePositionAndPackageHelper.putAll(getPositionAndPackageMap(newData))
|
||||
_dataList.value = newData
|
||||
}
|
||||
|
||||
targetItem is CustomSubjectItem -> {
|
||||
val subject = targetItem.data.copy()
|
||||
val originalGameList = subject.data ?: mutableListOf()
|
||||
subject.data = (originalGameList + gameList).toMutableList()
|
||||
subject.isWechatColumnCPM = true
|
||||
val cpmSubjectItemList = repository.convertColumnDetailSubjectItems(
|
||||
targetItem.position,
|
||||
componentPosition,
|
||||
targetItem.link,
|
||||
subject
|
||||
)
|
||||
if (cpmSubjectItemList.isEmpty()) return
|
||||
|
||||
val newData = oldData.toMutableList()
|
||||
newData[itemPosition] = cpmSubjectItemList.first()
|
||||
gamePositionAndPackageHelper.clear()
|
||||
gamePositionAndPackageHelper.putAll(getPositionAndPackageMap(newData))
|
||||
_dataList.value = newData
|
||||
}
|
||||
}
|
||||
|
||||
newData[index] = customPageItemList[0]
|
||||
newData.addAll(index + 1, customPageItemList.subList(1, customPageItemList.size))
|
||||
newData.forEachIndexed { pos, customPageItem ->
|
||||
customPageItem.position = pos
|
||||
}
|
||||
repository.notifyPositionChanged(newData.size)
|
||||
gamePositionAndPackageHelper.clear()
|
||||
gamePositionAndPackageHelper.putAll(getPositionAndPackageMap(newData))
|
||||
_dataList.value = newData
|
||||
}
|
||||
|
||||
/**
|
||||
@ -926,6 +915,12 @@ class CustomPageViewModel(application: Application) : AndroidViewModel(applicati
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
if (loadFirstDisposable != null && !loadFirstDisposable!!.isDisposed) {
|
||||
loadFirstDisposable?.dispose()
|
||||
}
|
||||
if (loadMoreDisposable != null && !loadMoreDisposable!!.isDisposed) {
|
||||
loadMoreDisposable?.dispose()
|
||||
}
|
||||
compositeDisposable.clear()
|
||||
repository.onClear()
|
||||
}
|
||||
@ -952,8 +947,7 @@ class CustomPageViewModel(application: Application) : AndroidViewModel(applicati
|
||||
|
||||
}
|
||||
|
||||
class Factory(private val mApplication: Application)
|
||||
: ViewModelProvider.NewInstanceFactory() {
|
||||
class Factory(private val mApplication: Application) : ViewModelProvider.NewInstanceFactory() {
|
||||
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return CustomPageViewModel(mApplication) as T
|
||||
|
||||
@ -159,7 +159,7 @@ class CustomPageRepository private constructor(
|
||||
}
|
||||
}
|
||||
|
||||
val wechatMiniGameCPMItemLiveData: LiveData<Pair<String, List<GameEntity>>> =
|
||||
val wechatMiniGameCPMItemLiveData: LiveData<Pair<Int, List<GameEntity>>> =
|
||||
wGameSubjectCPMListUseCase.wechatMiniGameCPMListLiveData
|
||||
|
||||
val hiddenNotifications: LiveData<HashMap<String, MutableSet<String>>>
|
||||
@ -257,7 +257,8 @@ class CustomPageRepository private constructor(
|
||||
gameChildPosition = 0
|
||||
displayingGameIdSet.clear()
|
||||
isLoadingPKData = false
|
||||
return remoteDataSource.loadCustomPageData(pageId, pageInfo.page, forceLoad)
|
||||
wGameSubjectCPMListUseCase.clear()
|
||||
return remoteDataSource.loadCustomPageData(pageId, 1, forceLoad)
|
||||
.map {
|
||||
it to transformRawDataIntoItemData(it, forceLoad)
|
||||
}
|
||||
@ -367,7 +368,12 @@ class CustomPageRepository private constructor(
|
||||
pageInfo.componentPosition
|
||||
)
|
||||
)
|
||||
wGameSubjectCPMListUseCase.getWechatMiniGameCPMList(subject.id, subject.size.limit, subject.onlyFee)
|
||||
wGameSubjectCPMListUseCase.getWechatMiniGameCPMList(
|
||||
pageInfo.componentPosition,
|
||||
subject.onlyFee,
|
||||
subject.size.index,
|
||||
subject.size.limit,
|
||||
)
|
||||
pageInfo.positionIncrement()
|
||||
pageInfo.componentPositionIncrement()
|
||||
}
|
||||
@ -384,7 +390,7 @@ class CustomPageRepository private constructor(
|
||||
pageInfo.componentPosition
|
||||
)
|
||||
)
|
||||
dspSubjectUseCase.getDspGames(subject.size.index)
|
||||
dspSubjectUseCase.getDspGames(subject.columnType, subject.size.index)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe(object : BiResponse<List<GameEntity>>() {
|
||||
@ -903,12 +909,6 @@ class CustomPageRepository private constructor(
|
||||
.map(RegionSettingHelper.filterGame)
|
||||
.map(ApkActiveUtils.filterMapperList)
|
||||
|
||||
fun loadChangeSubjectWGameCPM(page: Int, minimumSize: Int, onlyFee: Boolean): Observable<MutableList<GameEntity>> =
|
||||
wGameSubjectCPMRemoteDataSource.getEditorRecommendCPMList(page = page, minimumSize = minimumSize, onlyFee = onlyFee)// 微信小游戏CPM专题的“换一批”接口
|
||||
.toObservable()
|
||||
.map(RegionSettingHelper.filterGame)
|
||||
.map(ApkActiveUtils.filterMapperList)
|
||||
|
||||
fun loadSlideDiscoverCardGames(): Observable<List<GameEntity>> =
|
||||
remoteDataSource.loadSlideDiscoverCardGames()
|
||||
.map(RegionSettingHelper.filterGame)
|
||||
@ -1013,6 +1013,7 @@ class CustomPageRepository private constructor(
|
||||
}
|
||||
|
||||
fun onClear() {
|
||||
wGameSubjectCPMListUseCase.clear()
|
||||
compositeDisposable.clear()
|
||||
}
|
||||
|
||||
|
||||
@ -4,7 +4,7 @@ import com.gh.gamecenter.SearchActivity.Companion.TRACK_SEARCH_TYPE_INPUT
|
||||
import com.gh.gamecenter.common.constant.EntranceConsts
|
||||
import com.gh.gamecenter.common.utils.viewModelProvider
|
||||
import com.gh.gamecenter.search.SearchGameResultFragment
|
||||
import com.gh.gamecenter.search.viewmodel.SearchGameResultViewModel
|
||||
import com.gh.gamecenter.search.SearchGameResultViewModel
|
||||
import com.halo.assistant.HaloApp
|
||||
|
||||
/**
|
||||
@ -19,6 +19,7 @@ class MiniGameSearchResultFragment : SearchGameResultFragment() {
|
||||
val factory = SearchGameResultViewModel.Factory(
|
||||
HaloApp.getInstance().application,
|
||||
mKey,
|
||||
true,
|
||||
MiniGameSearchResultRepository(),
|
||||
TRACK_SEARCH_TYPE_INPUT,
|
||||
activity?.intent?.getStringExtra(EntranceConsts.KEY_SOURCE_ENTRANCE) ?: ""
|
||||
|
||||
@ -1,25 +1,45 @@
|
||||
package com.gh.gamecenter.minigame
|
||||
|
||||
import com.gh.gamecenter.dsp.data.GameSubjectDSPRemoteDataSource
|
||||
import com.gh.gamecenter.entity.SearchSubjectEntity
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.minigame.wechat.WGameSubjectCPMRemoteDataSource
|
||||
import com.gh.gamecenter.retrofit.RetrofitManager
|
||||
import com.gh.gamecenter.retrofit.service.ApiService
|
||||
import com.gh.gamecenter.search.ISearchGameResultRepository
|
||||
import com.gh.gamecenter.search.SearchItemData
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
|
||||
class MiniGameSearchResultRepository(
|
||||
private val api: ApiService = RetrofitManager.getInstance().newApi
|
||||
private val api: ApiService = RetrofitManager.getInstance().newApi,
|
||||
private val mWGameSubjectCPMDataSource: WGameSubjectCPMRemoteDataSource = WGameSubjectCPMRemoteDataSource(),
|
||||
private val mGameSubjectDSPDataSource: GameSubjectDSPRemoteDataSource = GameSubjectDSPRemoteDataSource(RetrofitManager.getInstance().dspApiService)
|
||||
) : ISearchGameResultRepository {
|
||||
|
||||
override fun getSearchGame(
|
||||
key: String?,
|
||||
page: Int
|
||||
): Observable<List<SearchItemData>> {
|
||||
): Observable<List<GameEntity>> {
|
||||
return api.getSearchMiniGameList(key, page, 20)
|
||||
.map { data ->
|
||||
data.map {
|
||||
SearchItemData(it, type = "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSearchMiniGameCPM(key: String?): Observable<List<GameEntity>> {
|
||||
return Observable.just(emptyList())
|
||||
}
|
||||
|
||||
override fun getSearchSubject(key: String?, page: Int): Observable<List<SearchSubjectEntity>> {
|
||||
return Observable.just(emptyList())
|
||||
}
|
||||
|
||||
override fun getWGameCPMGameList(size: Int): Single<MutableList<GameEntity>> {
|
||||
return mWGameSubjectCPMDataSource.getUserRecommendCPMList(pageSize = size)
|
||||
}
|
||||
|
||||
override fun getDspGameList(
|
||||
columnType: String,
|
||||
showDownload: Boolean,
|
||||
size: Int,
|
||||
): Single<List<GameEntity>> {
|
||||
return mGameSubjectDSPDataSource.getDspGames(columnType, size)
|
||||
}
|
||||
}
|
||||
@ -19,7 +19,6 @@ class QGameSubjectListRepository(
|
||||
order: String?,
|
||||
ad: String?,
|
||||
columnCollectionId: String?,
|
||||
minimumSize: Int,
|
||||
onlyFee: Boolean,
|
||||
): Single<MutableList<GameEntity>> {
|
||||
return api.getQGameColumn(column_id, order, page, 20)
|
||||
|
||||
@ -17,10 +17,9 @@ class WGameSubjectCPMListRepository(
|
||||
order: String?,
|
||||
ad: String?,
|
||||
columnCollectionId: String?,
|
||||
minimumSize: Int,
|
||||
onlyFee: Boolean
|
||||
): Single<MutableList<GameEntity>> {
|
||||
return dataSource.getEditorRecommendCPMList(page = page, minimumSize = minimumSize, onlyFee = onlyFee)
|
||||
return dataSource.getEditorRecommendCPMList(page = page, onlyFee = onlyFee)
|
||||
}
|
||||
|
||||
override fun getColumnSettings(column_id: String?): Observable<SubjectSettingEntity> {
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
package com.gh.gamecenter.minigame.wechat
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import androidx.lifecycle.LiveData
|
||||
import com.gh.gamecenter.common.livedata.NonStickyMutableLiveData
|
||||
import com.gh.gamecenter.common.retrofit.BiResponse
|
||||
import com.gh.gamecenter.common.utils.singleToMain
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
|
||||
/**
|
||||
* 微信小游戏CPM-网域层
|
||||
@ -15,32 +14,87 @@ class WGameSubjectCPMListUseCase(
|
||||
private val repository: WGameSubjectCPMListRepository = WGameSubjectCPMListRepository()
|
||||
) {
|
||||
|
||||
/**
|
||||
* 微信专题CPM请求记录,用于避免重复请求,以专题ID作为Key
|
||||
*/
|
||||
private val requestKeyList = mutableListOf<String>()
|
||||
private val compositeDisposable = CompositeDisposable()
|
||||
|
||||
/**
|
||||
* 微信小游戏CPM列表,这里的LiveData充当类似于EventBus的角色,因此使用NonStickyMutableLiveData
|
||||
*/
|
||||
private val _wechatMiniGameCPMListLiveData = NonStickyMutableLiveData<Pair<String, List<GameEntity>>>()
|
||||
val wechatMiniGameCPMListLiveData: LiveData<Pair<String, List<GameEntity>>> = _wechatMiniGameCPMListLiveData
|
||||
private val _wechatMiniGameCPMListLiveData = NonStickyMutableLiveData<Pair<Int, List<GameEntity>>>()
|
||||
val wechatMiniGameCPMListLiveData: LiveData<Pair<Int, List<GameEntity>>> = _wechatMiniGameCPMListLiveData
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
fun getWechatMiniGameCPMList(subjectId: String?, minimumSize: Int, onlyFee: Boolean) {
|
||||
if (subjectId.isNullOrEmpty() || requestKeyList.contains(subjectId)) {
|
||||
return
|
||||
}
|
||||
/**
|
||||
* componentPosition:组件位置,记录当前cpm数据需要插入的位置
|
||||
* 由于用户触发下拉刷新,componentPosition位置可能发生变化,所以当出现下拉刷新时,需要取消这里的所有请求
|
||||
*/
|
||||
fun getWechatMiniGameCPMList(componentPosition: Int, onlyFee: Boolean, targetSize: Int, minimumSize: Int) {
|
||||
loadCpmGameList(1, componentPosition, onlyFee, targetSize, minimumSize, 0, 1, listOf())
|
||||
}
|
||||
|
||||
requestKeyList.add(subjectId)
|
||||
repository.getColumn(null, 1, null, null, null, null, minimumSize, onlyFee)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
fun loadCpmGameList(
|
||||
page: Int,
|
||||
componentPosition: Int,
|
||||
onlyFee: Boolean,
|
||||
targetSize: Int,
|
||||
minimumSize: Int,
|
||||
currentSize: Int,
|
||||
count: Int,
|
||||
gameList: List<GameEntity>
|
||||
) {
|
||||
repository.getColumn(null, page, null, null, null, null, onlyFee)
|
||||
.compose(singleToMain())
|
||||
.subscribe(object : BiResponse<List<GameEntity>>() {
|
||||
override fun onSuccess(data: List<GameEntity>) {
|
||||
requestKeyList.remove(subjectId)
|
||||
_wechatMiniGameCPMListLiveData.value = subjectId to data
|
||||
val total = currentSize + data.size
|
||||
val needShowGames = gameList + data
|
||||
if (total >= targetSize || count >= MAX_LOAD_COUNT) {
|
||||
// 停止继续加载
|
||||
if (total >= minimumSize && needShowGames.isNotEmpty()) {
|
||||
// 游戏数量不小于最小游戏数量才可以显示
|
||||
_wechatMiniGameCPMListLiveData.value = componentPosition to needShowGames
|
||||
}
|
||||
} else {
|
||||
// 继续加载下一页数据
|
||||
if (total >= minimumSize) {
|
||||
// 游戏数量不小于最小游戏数量才可以显示
|
||||
if (needShowGames.isNotEmpty()) {
|
||||
_wechatMiniGameCPMListLiveData.value = componentPosition to needShowGames
|
||||
}
|
||||
|
||||
loadCpmGameList(
|
||||
page + 1,
|
||||
componentPosition,
|
||||
onlyFee,
|
||||
targetSize,
|
||||
minimumSize,
|
||||
total,
|
||||
count + 1,
|
||||
listOf()
|
||||
)
|
||||
} else {
|
||||
// 游戏数量太少,暂不显示,等后续数据加载出来,达到最小数量限制后一起显示
|
||||
loadCpmGameList(
|
||||
page + 1,
|
||||
componentPosition,
|
||||
onlyFee,
|
||||
targetSize,
|
||||
minimumSize,
|
||||
total,
|
||||
count + 1,
|
||||
needShowGames
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
}).let(compositeDisposable::add)
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
compositeDisposable.clear()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val MAX_LOAD_COUNT = 6
|
||||
}
|
||||
}
|
||||
@ -17,7 +17,6 @@ class WGameSubjectCPMRemoteDataSource(
|
||||
fun getEditorRecommendCPMList(
|
||||
page: Int,
|
||||
pageSize: Int = 10,
|
||||
minimumSize: Int,
|
||||
onlyFee: Boolean
|
||||
): Single<MutableList<GameEntity>> {
|
||||
val meta = MetaUtil.getMeta()
|
||||
@ -39,38 +38,33 @@ class WGameSubjectCPMRemoteDataSource(
|
||||
return api.getEditorRecommendList(request.toRequestBody())
|
||||
.map {
|
||||
if (it.ret == 0) {
|
||||
// 数量不满足最小值时直接返回空
|
||||
if (onlyFee && minimumSize > 0 && it.appInfoList.size < minimumSize) {
|
||||
mutableListOf()
|
||||
} else {
|
||||
it.appInfoList.map { info ->
|
||||
GameEntity(
|
||||
mName = info.appName,
|
||||
mIcon = info.logo,
|
||||
mBrief = info.briefIntro,
|
||||
miniGameUid = info.appID,
|
||||
miniGameAppId = info.userName,
|
||||
miniGameCategory = Constants.WECHAT_MINI_GAME,
|
||||
profit = Constants.WECHAT_MINI_GAME_PROFIT_CPM,
|
||||
miniGameAppStatus = 2,
|
||||
miniGameAppPath = info.wechatAppPath,
|
||||
miniGameExtData = info.extData,
|
||||
miniGameRecommendId = info.recommendID,
|
||||
mTagStyle = arrayListOf(
|
||||
TagStyleEntity(
|
||||
name = info.categoryName,
|
||||
color = TAG_COLOR,
|
||||
background = TAG_BACKGROUND
|
||||
),
|
||||
TagStyleEntity(
|
||||
name = info.subcategoryName,
|
||||
color = TAG_COLOR,
|
||||
background = TAG_BACKGROUND
|
||||
)
|
||||
it.appInfoList.map { info ->
|
||||
GameEntity(
|
||||
mName = info.appName,
|
||||
mIcon = info.logo,
|
||||
mBrief = info.briefIntro,
|
||||
miniGameUid = info.appID,
|
||||
miniGameAppId = info.userName,
|
||||
miniGameCategory = Constants.WECHAT_MINI_GAME,
|
||||
profit = Constants.WECHAT_MINI_GAME_PROFIT_CPM,
|
||||
miniGameAppStatus = 2,
|
||||
miniGameAppPath = info.wechatAppPath,
|
||||
miniGameExtData = info.extData,
|
||||
miniGameRecommendId = info.recommendID,
|
||||
mTagStyle = arrayListOf(
|
||||
TagStyleEntity(
|
||||
name = info.categoryName,
|
||||
color = TAG_COLOR,
|
||||
background = TAG_BACKGROUND
|
||||
),
|
||||
TagStyleEntity(
|
||||
name = info.subcategoryName,
|
||||
color = TAG_COLOR,
|
||||
background = TAG_BACKGROUND
|
||||
)
|
||||
)
|
||||
}.toMutableList()
|
||||
}
|
||||
)
|
||||
}.toMutableList()
|
||||
} else {
|
||||
mutableListOf()
|
||||
}
|
||||
|
||||
@ -19,7 +19,6 @@ class WGameSubjectListRepository(
|
||||
order: String?,
|
||||
ad: String?,
|
||||
columnCollectionId: String?,
|
||||
minimumSize: Int,
|
||||
onlyFee: Boolean,
|
||||
): Single<MutableList<GameEntity>> {
|
||||
return api.getWGameColumn(column_id, order, page, 20)
|
||||
|
||||
@ -50,7 +50,6 @@ import com.gh.gamecenter.entity.GameColumnCollection;
|
||||
import com.gh.gamecenter.entity.GameData;
|
||||
import com.gh.gamecenter.entity.GameDigestEntity;
|
||||
import com.gh.gamecenter.entity.GameGuidePopupEntity;
|
||||
import com.gh.gamecenter.entity.GameServerTestDisplaySetting;
|
||||
import com.gh.gamecenter.entity.GameServerTestTopGame;
|
||||
import com.gh.gamecenter.entity.GameServerTestV2Entity;
|
||||
import com.gh.gamecenter.entity.GameVideoInfo;
|
||||
@ -78,7 +77,6 @@ import com.gh.gamecenter.entity.RatingReplyEntity;
|
||||
import com.gh.gamecenter.entity.RecommendPopupEntity;
|
||||
import com.gh.gamecenter.entity.ReserveModifyEntity;
|
||||
import com.gh.gamecenter.entity.ReserveReminderEntity;
|
||||
import com.gh.gamecenter.entity.SearchGameUnionEntity;
|
||||
import com.gh.gamecenter.entity.SearchSubjectEntity;
|
||||
import com.gh.gamecenter.entity.ServerPublishEntity;
|
||||
import com.gh.gamecenter.entity.ServerSubscriptionEntity;
|
||||
@ -328,18 +326,6 @@ public interface ApiService {
|
||||
@GET
|
||||
Observable<List<GameEntity>> getSearchGame(@Url String url);
|
||||
|
||||
/**
|
||||
* 搜索游戏(新:整合了 cpm dsp 广告位置排序问题)
|
||||
*/
|
||||
@GET("./games:search?view=union")
|
||||
Observable<List<SearchGameUnionEntity>> getSearchGameUnionData(
|
||||
@Query("keyword") String keyword,
|
||||
@Query("page") int page,
|
||||
@Query("version") String version,
|
||||
@Query("channel") String channel,
|
||||
@Query("AD") String ad
|
||||
);
|
||||
|
||||
/**
|
||||
* 搜索CPM微信小游戏
|
||||
*/
|
||||
@ -3106,12 +3092,6 @@ public interface ApiService {
|
||||
@GET("columns/tests/v2")
|
||||
Observable<GameServerTestV2Entity> getServerTestV2(@Query("filter") String filter);
|
||||
|
||||
/**
|
||||
* 新游开测-显示配置
|
||||
*/
|
||||
@GET("app/column_test_v2/{link_id}/display_setting")
|
||||
Single<GameServerTestDisplaySetting> getGameServerTestDisplaySetting(@Path("link_id") String linkId);
|
||||
|
||||
/**
|
||||
* 新游开测-详情列表
|
||||
*/
|
||||
|
||||
@ -1,7 +1,18 @@
|
||||
package com.gh.gamecenter.search
|
||||
|
||||
import com.gh.gamecenter.entity.SearchSubjectEntity
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
|
||||
interface ISearchGameResultRepository {
|
||||
fun getSearchGame(key: String?, page: Int): Observable<List<SearchItemData>>
|
||||
fun getSearchGame(key: String?, page: Int): Observable<List<GameEntity>>
|
||||
|
||||
fun getSearchMiniGameCPM(key: String?): Observable<List<GameEntity>>
|
||||
|
||||
fun getSearchSubject(key: String?, page: Int): Observable<List<SearchSubjectEntity>>
|
||||
|
||||
fun getWGameCPMGameList(size: Int): Single<MutableList<GameEntity>>
|
||||
|
||||
fun getDspGameList(columnType: String, showDownload: Boolean, size: Int): Single<List<GameEntity>>
|
||||
}
|
||||
@ -31,7 +31,6 @@ import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.feature.entity.SettingsEntity
|
||||
import com.gh.gamecenter.feature.exposure.addExposureHelper
|
||||
import com.gh.gamecenter.help.HelpAndFeedbackBridge
|
||||
import com.gh.gamecenter.search.viewmodel.SearchGameResultViewModel
|
||||
import com.halo.assistant.HaloApp
|
||||
import com.lightgame.download.DataWatcher
|
||||
import com.lightgame.download.DownloadEntity
|
||||
@ -68,6 +67,7 @@ class SearchGameIndexFragment : ListFragment<GameEntity, SearchGameResultViewMod
|
||||
SearchGameResultViewModel.Factory(
|
||||
HaloApp.getInstance(),
|
||||
mKey,
|
||||
false,
|
||||
SearchGameResultRepository(),
|
||||
mType,
|
||||
activity?.intent?.getStringExtra(EntranceConsts.KEY_SOURCE_ENTRANCE) ?: ""
|
||||
@ -198,6 +198,7 @@ class SearchGameIndexFragment : ListFragment<GameEntity, SearchGameResultViewMod
|
||||
this.mType = type
|
||||
mAdapter?.key = key
|
||||
mListViewModel?.updateSearchKeyWithType(key, type)
|
||||
mListViewModel?.clearSearchSubjects()
|
||||
mListViewModel?.load(LoadType.REFRESH)
|
||||
}
|
||||
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
package com.gh.gamecenter.search
|
||||
|
||||
import android.content.Context
|
||||
import android.util.SparseArray
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
@ -59,7 +60,6 @@ import com.gh.gamecenter.gamedetail.accelerator.dialog.StartingAcceleratorDialog
|
||||
import com.gh.gamecenter.gamedetail.entity.GameDetailTabEntity
|
||||
import com.gh.gamecenter.help.HelpAndFeedbackBridge
|
||||
import com.gh.gamecenter.minigame.MiniGameSearchResultFragment
|
||||
import com.gh.gamecenter.search.viewmodel.SearchGameResultViewModel
|
||||
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
|
||||
import com.lightgame.download.DownloadEntity
|
||||
import com.lightgame.utils.Util_System_Keyboard
|
||||
|
||||
@ -13,6 +13,7 @@ import android.view.animation.TranslateAnimation
|
||||
import android.widget.TextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.fragment.app.viewModels
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.gh.common.exposure.DefaultExposureStateChangeListener
|
||||
import com.gh.common.util.*
|
||||
@ -40,10 +41,10 @@ import com.gh.gamecenter.eventbus.EBPackage
|
||||
import com.gh.gamecenter.eventbus.EBStartupAcceleration
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.feature.exposure.addExposureHelper
|
||||
import com.gh.gamecenter.feature.entity.VipEntity
|
||||
import com.gh.gamecenter.help.HelpAndFeedbackBridge
|
||||
import com.gh.gamecenter.livedata.EventObserver
|
||||
import com.gh.gamecenter.minigame.MiniGameSearchResultFragment
|
||||
import com.gh.gamecenter.search.viewmodel.SearchGameResultViewModel
|
||||
import com.gh.gamecenter.search.viewmodel.SearchTabViewModel
|
||||
import com.halo.assistant.HaloApp
|
||||
import com.halo.assistant.accelerator.repository.AcceleratorDataHolder
|
||||
@ -125,6 +126,7 @@ open class SearchGameResultFragment : ListFragment<GameEntity, SearchGameResultV
|
||||
SearchGameResultViewModel.Factory(
|
||||
HaloApp.getInstance(),
|
||||
mKey,
|
||||
true,
|
||||
SearchGameResultRepository(),
|
||||
mType,
|
||||
activity?.intent?.getStringExtra(EntranceConsts.KEY_SOURCE_ENTRANCE) ?: "",
|
||||
@ -442,6 +444,7 @@ open class SearchGameResultFragment : ListFragment<GameEntity, SearchGameResultV
|
||||
this.mKey = key
|
||||
mAdapter?.key = key
|
||||
mAdapter?.clearAdIdSet()
|
||||
mListViewModel?.clearSearchSubjects()
|
||||
mListViewModel?.load(LoadType.REFRESH)
|
||||
}
|
||||
|
||||
|
||||
@ -1,218 +1,88 @@
|
||||
package com.gh.gamecenter.search
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import com.gh.ad.AdDelegateHelper
|
||||
import com.gh.common.filter.RegionSettingHelper
|
||||
import com.gh.common.constant.Config
|
||||
import com.gh.common.util.AdHelper
|
||||
import com.gh.common.util.PackageUtils
|
||||
import com.gh.gamecenter.common.retrofit.BiResponse
|
||||
import com.gh.gamecenter.common.utils.safelyGetInRelease
|
||||
import com.gh.gamecenter.common.utils.singleToMain
|
||||
import com.gh.gamecenter.BuildConfig
|
||||
import com.gh.gamecenter.dsp.data.GameSubjectDSPRemoteDataSource
|
||||
import com.gh.gamecenter.entity.AdConfig
|
||||
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_AD_SPACE
|
||||
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_COLUMN
|
||||
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_DSP_GAME_COLUMN
|
||||
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_GAME
|
||||
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_WECHAT_GAME
|
||||
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_WECHAT_GAME_CPM_COLUMN
|
||||
import com.gh.gamecenter.entity.SearchSubjectEntity
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.livedata.Event
|
||||
import com.gh.gamecenter.gamedetail.accelerator.AccelerationDataBase
|
||||
import com.gh.gamecenter.minigame.wechat.WGameSubjectCPMRemoteDataSource
|
||||
import com.gh.gamecenter.retrofit.RetrofitManager
|
||||
import com.gh.gamecenter.retrofit.service.ApiService
|
||||
import com.halo.assistant.HaloApp
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.disposables.CompositeDisposable
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.random.Random
|
||||
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import java.net.URLEncoder
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class SearchGameResultRepository(
|
||||
private val mApi: ApiService = RetrofitManager.getInstance().api,
|
||||
private val mNewApi: ApiService = RetrofitManager.getInstance().newApi,
|
||||
private val mWGameSubjectCPMDataSource: WGameSubjectCPMRemoteDataSource = WGameSubjectCPMRemoteDataSource(),
|
||||
private val mGameSubjectDSPDataSource: GameSubjectDSPRemoteDataSource = GameSubjectDSPRemoteDataSource(
|
||||
RetrofitManager.getInstance().dspApiService
|
||||
)
|
||||
private val mGameSubjectDSPDataSource: GameSubjectDSPRemoteDataSource = GameSubjectDSPRemoteDataSource(RetrofitManager.getInstance().dspApiService)
|
||||
) : ISearchGameResultRepository {
|
||||
|
||||
private val compositeDisposable = CompositeDisposable()
|
||||
private var currentSearchKey: String? = null
|
||||
|
||||
private val _dataUpdateEvent = MutableLiveData<Event<SearchItemData>>()
|
||||
val dataUpdateEvent: LiveData<Event<SearchItemData>> = _dataUpdateEvent
|
||||
private var currentMiniGameCPMSearchList: MutableList<GameEntity>? = null
|
||||
|
||||
private val adGameOneIdSet = Collections.newSetFromMap(ConcurrentHashMap<String, Boolean>())
|
||||
override fun getSearchGame(
|
||||
key: String?,
|
||||
page: Int
|
||||
): Observable<List<GameEntity>> {
|
||||
// 可能会有特殊字符,需要 encode 处理
|
||||
val encodedKey = URLEncoder.encode(key, "utf-8")
|
||||
return mApi.getSearchGame(
|
||||
Config.API_HOST
|
||||
+ "games:search?keyword=" + encodedKey
|
||||
+ "&view=digest&page=" + page
|
||||
+ "&channel=" + HaloApp.getInstance().channel
|
||||
+ "&version=" + BuildConfig.VERSION_NAME
|
||||
+ "&AD=" + AdHelper.getIdfaString()
|
||||
)
|
||||
.map { games ->
|
||||
// 获取已加速成功过的区服
|
||||
val gameCanSpeed = games.filter { it.canSpeed }
|
||||
val gameIds = gameCanSpeed
|
||||
.map { it.id }
|
||||
val acctGameList = AccelerationDataBase.instance.accelerationDao().queryAcctGameInfoByGameId(gameIds)
|
||||
gameCanSpeed.forEach { game ->
|
||||
game.lastAcctGame = acctGameList.find { game.id == it.gameId }
|
||||
}
|
||||
games
|
||||
}
|
||||
}
|
||||
|
||||
override fun getSearchGame(key: String?, page: Int): Observable<List<SearchItemData>> {
|
||||
if (page == 1) {
|
||||
adGameOneIdSet.clear()
|
||||
override fun getSearchMiniGameCPM(key: String?): Observable<List<GameEntity>> {
|
||||
val currentMiniGameCPMSearchList = currentMiniGameCPMSearchList
|
||||
if (key == currentSearchKey && currentMiniGameCPMSearchList != null) {
|
||||
return Observable.just(currentMiniGameCPMSearchList)
|
||||
}
|
||||
val version = PackageUtils.getGhVersionName()
|
||||
val channel = HaloApp.getInstance().channel
|
||||
return mApi.getSearchGameUnionData(key, page, version, channel, AdHelper.getIdfaString())
|
||||
.map { data ->
|
||||
data.map {
|
||||
when (it.type) {
|
||||
TYPE_GAME -> SearchItemData(it.linkGame, type = it.type)
|
||||
TYPE_WECHAT_GAME_CPM_COLUMN -> {
|
||||
it.linkWechatGameCpmColumn?.let(::loadThirdPartyData)
|
||||
SearchItemData(type = it.type, placeHolderId = it.linkWechatGameCpmColumn?.columnId)
|
||||
}
|
||||
|
||||
TYPE_DSP_GAME_COLUMN -> {
|
||||
it.linkDspGameColumn?.let(::loadThirdPartyData)
|
||||
SearchItemData(type = it.type, placeHolderId = it.linkDspGameColumn?.columnId)
|
||||
}
|
||||
|
||||
TYPE_WECHAT_GAME -> SearchItemData(game = it.linkWechatGame, type = it.type)
|
||||
TYPE_COLUMN -> SearchItemData(subject = it.linkColum, type = it.type)
|
||||
TYPE_AD_SPACE -> {
|
||||
val adConfig = it.linkAdSpace
|
||||
if (adConfig != null) {
|
||||
val shouldShowAd = AdDelegateHelper.shouldShowGameSearchAd(adConfig)
|
||||
when {
|
||||
shouldShowAd && showThirdPartyAd(adConfig) -> {
|
||||
SearchItemData(
|
||||
ad = adConfig.thirdPartyAd,
|
||||
adConfig = it.linkAdSpace,
|
||||
type = TYPE_AD_SPACE
|
||||
)
|
||||
}
|
||||
|
||||
shouldShowAd && showOwnerAd(adConfig) -> {
|
||||
loadOwnerAdData(adConfig)
|
||||
SearchItemData(type = it.type, placeHolderId = getAdPlaceHolderId(adConfig))
|
||||
}
|
||||
|
||||
else -> SearchItemData(type = it.type, placeHolderId = INVALID_PLACEHOLDER_ID)
|
||||
}
|
||||
} else {
|
||||
SearchItemData(type = it.type, placeHolderId = INVALID_PLACEHOLDER_ID)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
else -> {
|
||||
// 无效数据
|
||||
SearchItemData(type = it.type, placeHolderId = INVALID_PLACEHOLDER_ID)
|
||||
}
|
||||
}
|
||||
}
|
||||
return mNewApi.getSearchWechatMiniCPMGame(key)
|
||||
.timeout(5, TimeUnit.SECONDS)
|
||||
.onErrorReturnItem(emptyList())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.doOnNext {
|
||||
this.currentSearchKey = key
|
||||
this.currentMiniGameCPMSearchList = it
|
||||
}
|
||||
}
|
||||
|
||||
private fun loadThirdPartyData(subject: SearchSubjectEntity) {
|
||||
if (subject.isWGameSubjectCPM) {
|
||||
mWGameSubjectCPMDataSource.getUserRecommendCPMList(pageSize = subject.size)
|
||||
} else {
|
||||
mGameSubjectDSPDataSource.getDspGames(subject.size)
|
||||
}.compose(singleToMain())
|
||||
.subscribe(object : BiResponse<List<GameEntity>>() {
|
||||
override fun onSuccess(data: List<GameEntity>) {
|
||||
subject.games = data
|
||||
_dataUpdateEvent.value = Event(SearchItemData(type = subject.type, subject = subject))
|
||||
}
|
||||
}).let(compositeDisposable::add)
|
||||
override fun getSearchSubject(key: String?, page: Int): Observable<List<SearchSubjectEntity>> {
|
||||
return mApi.getSearchSubject(key, page)
|
||||
}
|
||||
|
||||
private fun loadOwnerAdData(adConfig: AdConfig?) {
|
||||
adConfig ?: return
|
||||
val pageSize = adConfig.ownerAd?.adSource?.gamesIds?.size ?: 20
|
||||
val codeId = adConfig.ownerAd?.id
|
||||
val paramsMap = mapOf("page" to "1,$pageSize", "code_id" to codeId)
|
||||
mNewApi.getAdGames(adConfig.id, paramsMap)
|
||||
.map { data ->
|
||||
data.forEach {
|
||||
it.run {
|
||||
adIconActive = adConfig.ownerAd?.adSource?.adIconActive ?: false
|
||||
adSpaceId = adConfig.id
|
||||
gameAdSourceId = adConfig.ownerAd?.id ?: ""
|
||||
}
|
||||
}
|
||||
data
|
||||
}
|
||||
.compose(singleToMain())
|
||||
.subscribe(object : BiResponse<List<GameEntity>>() {
|
||||
override fun onSuccess(data: List<GameEntity>) {
|
||||
val filterGameList = RegionSettingHelper.filterGame(data)
|
||||
handleOwnerAdGames(adConfig, filterGameList)
|
||||
}
|
||||
|
||||
override fun onFailure(exception: Exception) {
|
||||
super.onFailure(exception)
|
||||
handleOwnerAdGames(adConfig, emptyList())
|
||||
}
|
||||
|
||||
}).let(compositeDisposable::add)
|
||||
override fun getWGameCPMGameList(size: Int,): Single<MutableList<GameEntity>> {
|
||||
return mWGameSubjectCPMDataSource.getUserRecommendCPMList(pageSize = size)
|
||||
}
|
||||
|
||||
private fun handleOwnerAdGames(adConfig: AdConfig, games: List<GameEntity>) {
|
||||
val showAdColumn = adConfig.ownerAd?.adSource?.displayStyle == "game_zone"
|
||||
val showOnFailed = adConfig.displayRule.onFailedAction == "show"
|
||||
when {
|
||||
games.isNotEmpty() && showAdColumn -> {
|
||||
SearchItemData(
|
||||
subject = SearchSubjectEntity(
|
||||
name = adConfig.ownerAd?.adSource?.title ?: "",
|
||||
games = games.take(AD_SUBJECT_GAME_MAX_COUNT),
|
||||
adId = adConfig.id,
|
||||
codeId = adConfig.ownerAd?.id ?: "",
|
||||
adIconActive = adConfig.ownerAd?.adSource?.adIconActive ?: false
|
||||
),
|
||||
adConfig = adConfig,
|
||||
type = TYPE_AD_SPACE
|
||||
)
|
||||
}
|
||||
|
||||
games.isNotEmpty() -> {
|
||||
var randomGame = games.safelyGetInRelease(Random.nextInt(games.size))
|
||||
if (games.size > 1 && adGameOneIdSet.contains(randomGame?.id)) {
|
||||
// 存在重复游戏时重新获取随机游戏
|
||||
randomGame = games.safelyGetInRelease(Random.nextInt(games.size))
|
||||
}
|
||||
randomGame?.id?.let(adGameOneIdSet::add)
|
||||
SearchItemData(game = randomGame, adConfig = adConfig, type = TYPE_AD_SPACE)
|
||||
}
|
||||
|
||||
showOnFailed && adConfig.thirdPartyAd != null -> {
|
||||
// 自有广告为空时,显示第三方广告
|
||||
SearchItemData(ad = adConfig.thirdPartyAd, adConfig = adConfig, type = TYPE_AD_SPACE)
|
||||
}
|
||||
|
||||
else -> null
|
||||
}?.let {
|
||||
_dataUpdateEvent.value = Event(it)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
fun clear() {
|
||||
compositeDisposable.clear()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val AD_SUBJECT_GAME_MAX_COUNT = 8
|
||||
private const val INVALID_PLACEHOLDER_ID = "Invalid"
|
||||
|
||||
fun getAdPlaceHolderId(adConfig: AdConfig?) = "${adConfig?.id}-${adConfig?.position}"
|
||||
|
||||
fun showThirdPartyAd(adConfig: AdConfig?) =
|
||||
(adConfig?.displayRule?.adSource == AdDelegateHelper.AD_TYPE_SDK && adConfig.thirdPartyAd != null) ||
|
||||
(adConfig?.displayRule?.adSource == AdDelegateHelper.AD_TYPE_OWNER &&
|
||||
adConfig.ownerAd == null &&
|
||||
adConfig.thirdPartyAd != null &&
|
||||
adConfig.displayRule.onFailedAction == "show")
|
||||
|
||||
fun showOwnerAd(adConfig: AdConfig?) =
|
||||
(adConfig?.displayRule?.adSource == AdDelegateHelper.AD_TYPE_OWNER &&
|
||||
adConfig.ownerAd != null) ||
|
||||
(adConfig?.displayRule?.adSource == AdDelegateHelper.AD_TYPE_SDK &&
|
||||
adConfig.thirdPartyAd == null &&
|
||||
adConfig.ownerAd != null &&
|
||||
adConfig.displayRule.onFailedAction == "show")
|
||||
override fun getDspGameList(
|
||||
columnType: String,
|
||||
showDownload: Boolean,
|
||||
size: Int,
|
||||
): Single<List<GameEntity>> {
|
||||
return mGameSubjectDSPDataSource.getDspGames(columnType, size)
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,469 @@
|
||||
package com.gh.gamecenter.search
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.gh.ad.AdDelegateHelper
|
||||
import com.gh.common.filter.RegionSettingHelper
|
||||
import com.gh.common.util.PackageHelper
|
||||
import com.gh.gamecenter.SearchActivity
|
||||
import com.gh.gamecenter.common.base.GlobalActivityManager
|
||||
import com.gh.gamecenter.common.baselist.ListViewModel
|
||||
import com.gh.gamecenter.common.baselist.LoadParams
|
||||
import com.gh.gamecenter.common.baselist.LoadStatus
|
||||
import com.gh.gamecenter.common.constant.Constants
|
||||
import com.gh.gamecenter.common.utils.SensorsBridge
|
||||
import com.gh.gamecenter.common.utils.safelyGetInRelease
|
||||
import com.gh.gamecenter.common.utils.singleToMain
|
||||
import com.gh.gamecenter.common.utils.toArrayList
|
||||
import com.gh.gamecenter.core.utils.SPUtils
|
||||
import com.gh.gamecenter.entity.AdConfig
|
||||
import com.gh.gamecenter.entity.SearchSubjectEntity
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.livedata.Event
|
||||
import com.gh.gamecenter.minigame.MiniGameSearchResultRepository
|
||||
import com.gh.gamecenter.retrofit.RetrofitManager
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import kotlin.random.Random
|
||||
|
||||
class SearchGameResultViewModel(
|
||||
application: Application,
|
||||
private var mSearchKey: String?,
|
||||
private var mIsManuallySearch: Boolean,
|
||||
private val repository: ISearchGameResultRepository,
|
||||
private var mSearchType: String,
|
||||
val sourceEntrance: String
|
||||
) : ListViewModel<GameEntity, SearchItemData>(application) {
|
||||
|
||||
private var mPage = 0
|
||||
private var mSearchSubjects = arrayListOf<SearchSubjectEntity>()
|
||||
|
||||
// 游戏广告列表,需要先本地按插入 position 排序,避免后续问题
|
||||
private var mGameSearchAdList: List<AdConfig>? = null
|
||||
private var mAdPositionSet: HashSet<Int>? = null
|
||||
private val mAdGameMap = ConcurrentHashMap<String, List<GameEntity>>()
|
||||
|
||||
private var mGameEntityList = arrayListOf<GameEntity>()
|
||||
|
||||
fun updateSearchKeyWithType(searchKey: String, searchType: String) {
|
||||
mSearchKey = searchKey
|
||||
mSearchType = searchType
|
||||
}
|
||||
|
||||
fun clearSearchSubjects() {
|
||||
mSearchSubjects.clear()
|
||||
}
|
||||
|
||||
override fun mergeResultLiveData() {
|
||||
mResultLiveData.addSource(mListLiveData) { list ->
|
||||
mGameEntityList = ArrayList(list)
|
||||
decorateListAndPost(list)
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
private fun decorateListAndPost(list: MutableList<GameEntity>) {
|
||||
val itemDataList = ArrayList<SearchItemData>()
|
||||
val combineGameList = list.toMutableList()
|
||||
|
||||
refreshWrongInstallStatus()
|
||||
|
||||
repository.getSearchMiniGameCPM(mSearchKey)
|
||||
.zipWith(repository.getSearchSubject(mSearchKey, mPage)) { cpmGameList, subjectList -> // CPM游戏搜索结果列表合并
|
||||
for (cpmGame in cpmGameList) {
|
||||
if (cpmGame.location <= 0 || cpmGame.location > list.size) {
|
||||
combineGameList.add(cpmGame)
|
||||
} else {
|
||||
combineGameList.add(cpmGame.location - 1, cpmGame)
|
||||
}
|
||||
}
|
||||
itemDataList.addAll(
|
||||
combineGameList.mapIndexed { index, game ->
|
||||
SearchItemData(game = game, gamePosition = index, isFirst = index == 0)
|
||||
}
|
||||
)
|
||||
subjectList
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ mutableList ->
|
||||
// 数据源来自于第三方的专题列表,包括 CPM 专题和 DSP 专题
|
||||
val thirdPartySearchSubjectList = mutableListOf<SearchSubjectEntity>()
|
||||
|
||||
for (item in mutableList) {
|
||||
// 避免同一个位置重复的专题
|
||||
if (!mSearchSubjects.any {
|
||||
it.location == item.location && it.columnId == item.columnId
|
||||
}) {
|
||||
mSearchSubjects.add(item)
|
||||
}
|
||||
}
|
||||
|
||||
mSearchSubjects.forEach {
|
||||
if (it.type == SearchSubjectEntity.TYPE_WECHAT_GAME_CPM_COLUMN) {
|
||||
thirdPartySearchSubjectList.add(it.apply { isWGameSubjectCPM = true })
|
||||
} else if (it.type == SearchSubjectEntity.TYPE_DSP_GAME_COLUMN) {
|
||||
thirdPartySearchSubjectList.add(it.apply { isDspSubject = true })
|
||||
}
|
||||
val item = SearchItemData(subject = it)
|
||||
if (it.location <= 0 || it.location > itemDataList.size) {
|
||||
itemDataList.add(item)
|
||||
} else {
|
||||
itemDataList.add(it.location - 1, item)
|
||||
}
|
||||
}
|
||||
// 处理初始化列表且游戏列表size为0的情况
|
||||
handleLoadStatusWhenGameListIsEmpty(list, itemDataList)
|
||||
|
||||
if (mIsManuallySearch) {
|
||||
if (mSearchKey == AdDelegateHelper.gameSearchKeyword) {
|
||||
updateAdConfigAndDecorateList(itemDataList, list)
|
||||
} else {
|
||||
AdDelegateHelper.requestAdConfig(false, mSearchKey ?: "") {
|
||||
updateAdConfigAndDecorateList(itemDataList, list)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
postResultList(itemDataList, list)
|
||||
}
|
||||
|
||||
if (thirdPartySearchSubjectList.isNotEmpty()) {
|
||||
decorateListWithThirdPartyData(thirdPartySearchSubjectList, itemDataList, list)
|
||||
}
|
||||
}, {
|
||||
it.printStackTrace()
|
||||
handleLoadStatusWhenGameListIsEmpty(list, itemDataList)
|
||||
mResultLiveData.postValue(itemDataList)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求第三方接口数据来填充专题游戏数据,包含 DSP 专题和 CPM 专题
|
||||
*/
|
||||
@SuppressLint("CheckResult")
|
||||
private fun decorateListWithThirdPartyData(
|
||||
subjects: List<SearchSubjectEntity>,
|
||||
itemDataList: ArrayList<SearchItemData>,
|
||||
list: MutableList<GameEntity>
|
||||
) {
|
||||
val subjectList = subjects.filterNot { it.games.isNotEmpty() }
|
||||
if (subjectList.isEmpty()) return
|
||||
val subjectSingleList = subjectList.map { subject ->
|
||||
if (subject.isWGameSubjectCPM) {
|
||||
repository.getWGameCPMGameList(subject.size)
|
||||
.onErrorReturnItem(mutableListOf())
|
||||
.map { subject.columnId to it }
|
||||
} else {
|
||||
repository.getDspGameList(subject.columnType, subject.showDownload, subject.size)
|
||||
.onErrorReturnItem(mutableListOf())
|
||||
.map { subject.columnId to it }
|
||||
}
|
||||
}
|
||||
Single.zip(subjectSingleList) { it.map { item -> item as Pair<String, MutableList<GameEntity>> } }
|
||||
.map { dataList ->
|
||||
for (index in itemDataList.indices) {
|
||||
val itemData = itemDataList[index]
|
||||
val subject = itemData.subject ?: continue
|
||||
if (subject.games.isNotEmpty()) continue
|
||||
val pair = dataList.firstOrNull { data ->
|
||||
data.first == subject.columnId
|
||||
} ?: continue
|
||||
val newItemData = SearchItemData(
|
||||
subject = subject.copy(games = pair.second)
|
||||
)
|
||||
itemDataList[index] = newItemData
|
||||
}
|
||||
itemDataList
|
||||
}
|
||||
.subscribeOn(Schedulers.io())
|
||||
.observeOn(AndroidSchedulers.mainThread())
|
||||
.subscribe({ dataList -> postResultList(dataList, list) }, {})
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
private fun updateAdConfigAndDecorateList(itemDataList: ArrayList<SearchItemData>, list: MutableList<GameEntity>) {
|
||||
mGameSearchAdList =
|
||||
AdDelegateHelper.getGameSearchAdList().filter { AdDelegateHelper.shouldShowGameSearchAd(it) }.toArrayList()
|
||||
.apply { sortBy { it.position } }
|
||||
|
||||
val adPositionSet = hashSetOf<Int>()
|
||||
if (!mGameSearchAdList.isNullOrEmpty()) {
|
||||
for (config in mGameSearchAdList!!) {
|
||||
adPositionSet.add(config.position)
|
||||
}
|
||||
}
|
||||
mAdPositionSet = adPositionSet
|
||||
|
||||
if (adPositionSet.isNotEmpty()) {
|
||||
val ownerAdList = arrayListOf<AdConfig>()
|
||||
val thirdPartyAdList = arrayListOf<AdConfig>()
|
||||
|
||||
// 位置已经排过序,若插入位置大于列表的 size 那么直接 break 放弃
|
||||
for ((index, position) in adPositionSet.withIndex()) {
|
||||
if (position < itemDataList.size + index + 1) {
|
||||
val adConfig = mGameSearchAdList!!.safelyGetInRelease(index)
|
||||
val showThirdPartyAd = adConfig?.displayRule?.adSource == AdDelegateHelper.AD_TYPE_SDK
|
||||
val showOwnerAd = adConfig?.displayRule?.adSource == AdDelegateHelper.AD_TYPE_OWNER
|
||||
val showOnFailed = adConfig?.displayRule?.onFailedAction == "show"
|
||||
if ((showThirdPartyAd && adConfig?.thirdPartyAd != null)
|
||||
|| (showOwnerAd && adConfig?.ownerAd == null && adConfig?.thirdPartyAd != null && showOnFailed)
|
||||
) {
|
||||
thirdPartyAdList.add(adConfig)
|
||||
} else if ((showOwnerAd && adConfig?.ownerAd != null)
|
||||
|| (showThirdPartyAd && adConfig?.ownerAd != null && showOnFailed)
|
||||
) {
|
||||
ownerAdList.add(adConfig)
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (ownerAdList.isNotEmpty()) {
|
||||
// 存在自有广告时获取对应广告ID的游戏
|
||||
val requestSingleList = arrayListOf<Single<List<GameEntity>>>()
|
||||
ownerAdList.forEach {
|
||||
// 没有对应广告ID的游戏数据时才添加到请求列表
|
||||
if (mAdGameMap[it.id] == null) {
|
||||
requestSingleList.add(createAdGameListSingle(it))
|
||||
}
|
||||
}
|
||||
if (requestSingleList.isNotEmpty()) {
|
||||
Single.zip(requestSingleList) {}
|
||||
.compose(singleToMain())
|
||||
.subscribe({
|
||||
decorateListWithAd(itemDataList, list)
|
||||
}, {
|
||||
decorateListWithAd(itemDataList, list)
|
||||
})
|
||||
} else {
|
||||
decorateListWithAd(itemDataList, list)
|
||||
}
|
||||
} else {
|
||||
decorateListWithThirdPartyAdOnly(itemDataList, thirdPartyAdList, list)
|
||||
}
|
||||
} else {
|
||||
postResultList(itemDataList, list)
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshWrongInstallStatus() {
|
||||
if (mGameEntityList.isNotEmpty()) {
|
||||
PackageHelper.refreshWrongInstallStatus(mGameEntityList)
|
||||
}
|
||||
}
|
||||
|
||||
private fun decorateListWithThirdPartyAdOnly(
|
||||
itemDataList: ArrayList<SearchItemData>,
|
||||
thirdPartyAdList: List<AdConfig>,
|
||||
list: List<GameEntity>
|
||||
) {
|
||||
thirdPartyAdList.forEach {
|
||||
itemDataList.add(it.position - 1, SearchItemData(ad = it.thirdPartyAd, adConfig = it))
|
||||
SPUtils.setLong(Constants.SP_LAST_GAME_SEARCH_AD_SHOW_TIME + it.position, System.currentTimeMillis())
|
||||
}
|
||||
postResultList(itemDataList, list)
|
||||
}
|
||||
|
||||
private fun decorateListWithAd(
|
||||
itemDataList: ArrayList<SearchItemData>,
|
||||
list: List<GameEntity>
|
||||
) {
|
||||
val adGameOneIdSet = HashSet<String>() // 展示样式为单个游戏时记录游戏ID,避免重复
|
||||
val decoratedItemDataSize = itemDataList.size
|
||||
for ((index, position) in mAdPositionSet!!.withIndex()) {
|
||||
if (position < decoratedItemDataSize + index + 1) {
|
||||
val adConfig = mGameSearchAdList!!.safelyGetInRelease(index)
|
||||
val showThirdPartyAd = adConfig?.displayRule?.adSource == AdDelegateHelper.AD_TYPE_SDK
|
||||
val showOwnerAd = adConfig?.displayRule?.adSource == AdDelegateHelper.AD_TYPE_OWNER
|
||||
val showOnFailed = adConfig?.displayRule?.onFailedAction == "show"
|
||||
if ((showThirdPartyAd && adConfig?.thirdPartyAd != null)
|
||||
|| (showOwnerAd && adConfig?.ownerAd == null && adConfig?.thirdPartyAd != null && showOnFailed)
|
||||
) {
|
||||
itemDataList.add(position - 1, SearchItemData(ad = adConfig.thirdPartyAd, adConfig = adConfig))
|
||||
SPUtils.setLong(
|
||||
Constants.SP_LAST_GAME_SEARCH_AD_SHOW_TIME + adConfig.position,
|
||||
System.currentTimeMillis()
|
||||
)
|
||||
} else if ((showOwnerAd && adConfig?.ownerAd != null)
|
||||
|| (showThirdPartyAd && adConfig?.ownerAd != null && showOnFailed)
|
||||
) {
|
||||
val gameList = mAdGameMap[adConfig.id]
|
||||
if (!gameList.isNullOrEmpty()) {
|
||||
if (adConfig.ownerAd.adSource?.displayStyle == "game_zone") {
|
||||
// 游戏专题
|
||||
itemDataList.add(
|
||||
position - 1,
|
||||
SearchItemData(
|
||||
subject = SearchSubjectEntity(
|
||||
name = adConfig.ownerAd.adSource?.title ?: "",
|
||||
games = gameList.take(AD_SUBJECT_GAME_MAX_COUNT),
|
||||
adId = adConfig.id,
|
||||
codeId = adConfig.ownerAd.id,
|
||||
adIconActive = adConfig.ownerAd.adSource?.adIconActive ?: false
|
||||
),
|
||||
adConfig = adConfig
|
||||
)
|
||||
)
|
||||
} else {
|
||||
// 单个游戏
|
||||
var randomGameEntity = gameList.safelyGetInRelease(Random.nextInt(gameList.size))
|
||||
if (gameList.size != 1 && adGameOneIdSet.contains(randomGameEntity?.id)) {
|
||||
// 存在重复游戏时重新获取随机游戏
|
||||
randomGameEntity = gameList.safelyGetInRelease(Random.nextInt(gameList.size))
|
||||
}
|
||||
randomGameEntity?.id?.let { adGameOneIdSet.add(it) }
|
||||
|
||||
itemDataList.add(
|
||||
position - 1,
|
||||
SearchItemData(game = randomGameEntity, adConfig = adConfig)
|
||||
)
|
||||
}
|
||||
SPUtils.setLong(
|
||||
Constants.SP_LAST_GAME_SEARCH_AD_SHOW_TIME + adConfig.position,
|
||||
System.currentTimeMillis()
|
||||
)
|
||||
} else if (showOnFailed && adConfig.thirdPartyAd != null) {
|
||||
// 自有广告为空时,显示第三方广告
|
||||
itemDataList.add(position - 1, SearchItemData(ad = adConfig.thirdPartyAd, adConfig = adConfig))
|
||||
SPUtils.setLong(
|
||||
Constants.SP_LAST_GAME_SEARCH_AD_SHOW_TIME + adConfig.position,
|
||||
System.currentTimeMillis()
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
postResultList(itemDataList, list)
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
private fun createAdGameListSingle(adConfig: AdConfig) = Single.create { emitter ->
|
||||
val pageSize = adConfig.ownerAd?.adSource?.gamesIds?.size ?: 20
|
||||
val codeId = adConfig.ownerAd?.id
|
||||
val paramsMap = mapOf("page" to "1,$pageSize", "code_id" to codeId)
|
||||
RetrofitManager.getInstance().newApi.getAdGames(adConfig.id, paramsMap)
|
||||
.compose(singleToMain())
|
||||
.subscribe({ data ->
|
||||
val filterGameList = RegionSettingHelper.filterGame(data)
|
||||
filterGameList.forEach {
|
||||
it.run {
|
||||
adIconActive = adConfig.ownerAd?.adSource?.adIconActive ?: false
|
||||
adSpaceId = adConfig.id
|
||||
gameAdSourceId = adConfig.ownerAd?.id ?: ""
|
||||
}
|
||||
}
|
||||
mAdGameMap[adConfig.id] = filterGameList
|
||||
emitter.onSuccess(filterGameList)
|
||||
}, {
|
||||
mAdGameMap[adConfig.id] = emptyList()
|
||||
emitter.onSuccess(emptyList<GameEntity>())
|
||||
})
|
||||
}
|
||||
|
||||
private fun postResultList(resultList: ArrayList<SearchItemData>, list: List<GameEntity>) {
|
||||
mResultLiveData.postValue(resultList.toMutableList())
|
||||
if (mPage == 1) {
|
||||
if (repository is MiniGameSearchResultRepository) {
|
||||
SensorsBridge.trackMiniGameSearchResultReturn(
|
||||
GlobalActivityManager.getCurrentPageEntity().pageId,
|
||||
GlobalActivityManager.getCurrentPageEntity().pageName,
|
||||
sourceEntrance,
|
||||
mSearchKey ?: "",
|
||||
SearchActivity.toTrackSearchType(mSearchType),
|
||||
list.isNotEmpty()
|
||||
)
|
||||
} else {
|
||||
SensorsBridge.trackGameSearchResultReturn(
|
||||
GlobalActivityManager.getCurrentPageEntity().pageId,
|
||||
GlobalActivityManager.getCurrentPageEntity().pageName,
|
||||
sourceEntrance,
|
||||
mSearchKey ?: "",
|
||||
SearchActivity.toTrackSearchType(mSearchType),
|
||||
list.isNotEmpty()
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleLoadStatusWhenGameListIsEmpty(list: List<GameEntity>, itemDataList: ArrayList<SearchItemData>) {
|
||||
if (mPage == 1 && list.isEmpty()) {
|
||||
mLoadStatusLiveData.value = if (itemDataList.isEmpty()) LoadStatus.INIT_EMPTY else LoadStatus.INIT_OVER
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadStatusControl(size: Int) {
|
||||
if (mCurLoadParams.loadOffset == LoadParams.DEFAULT_OFFSET) { // 初始化列表
|
||||
if (size == 0) {
|
||||
// 初始化列表size为0情况放到mergeResultLiveData方法处理
|
||||
} else if (size == REQUEST_FAILURE_SIZE) {
|
||||
mLoadStatusLiveData.setValue(LoadStatus.INIT_FAILED)
|
||||
} else if (size < mOverLimitSize) { // 避免一个屏幕出现两次分页
|
||||
mLoadStatusLiveData.setValue(LoadStatus.INIT_OVER)
|
||||
} else {
|
||||
mLoadStatusLiveData.setValue(LoadStatus.INIT_LOADED)
|
||||
}
|
||||
} else {
|
||||
if (size == REQUEST_FAILURE_SIZE) {
|
||||
mLoadStatusLiveData.setValue(LoadStatus.LIST_FAILED)
|
||||
} else if (size == 0) {
|
||||
mLoadStatusLiveData.setValue(LoadStatus.LIST_OVER)
|
||||
} else {
|
||||
mLoadStatusLiveData.setValue(LoadStatus.LIST_LOADED)
|
||||
}
|
||||
}
|
||||
|
||||
if (size == REQUEST_FAILURE_SIZE) {
|
||||
mRetryParams = mCurLoadParams
|
||||
} else {
|
||||
mRetryParams = null
|
||||
mCurLoadParams.loadOffset = mCurLoadParams.loadOffset + 1 // 页数 + 1
|
||||
}
|
||||
}
|
||||
|
||||
override fun provideDataObservable(page: Int): Observable<List<GameEntity>> {
|
||||
mPage = page
|
||||
return repository.getSearchGame(mSearchKey, page)
|
||||
}
|
||||
|
||||
private val _refreshVipLoading = MutableLiveData<Event<Boolean>>()
|
||||
val refreshVipLoading: LiveData<Event<Boolean>> = _refreshVipLoading
|
||||
fun showRefreshVipLoading(show: Boolean) {
|
||||
_refreshVipLoading.value = Event(show)
|
||||
}
|
||||
|
||||
class Factory(
|
||||
private val mApplication: Application,
|
||||
private val mSearchKey: String?,
|
||||
private val mIsManuallySearch: Boolean,
|
||||
private val repository: ISearchGameResultRepository,
|
||||
private val mSearchType: String,
|
||||
private val mSourceEntrance: String
|
||||
) : ViewModelProvider.NewInstanceFactory() {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return SearchGameResultViewModel(
|
||||
mApplication,
|
||||
mSearchKey,
|
||||
mIsManuallySearch,
|
||||
repository,
|
||||
mSearchType,
|
||||
mSourceEntrance
|
||||
) as T
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val AD_SUBJECT_GAME_MAX_COUNT = 8
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,40 +1,20 @@
|
||||
package com.gh.gamecenter.search
|
||||
|
||||
import com.gh.gamecenter.entity.AdConfig
|
||||
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_AD_SPACE
|
||||
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_DSP_GAME_COLUMN
|
||||
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_WECHAT_GAME_CPM_COLUMN
|
||||
import com.gh.gamecenter.entity.SearchSubjectEntity
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.feature.exposure.ExposureEvent
|
||||
|
||||
data class SearchItemData(
|
||||
class SearchItemData(
|
||||
val game: GameEntity? = null,
|
||||
val subject: SearchSubjectEntity? = null,
|
||||
val ad: AdConfig.ThirdPartyAd? = null,
|
||||
val adConfig: AdConfig? = null,
|
||||
var gamePosition: Int = -1,
|
||||
val gamePosition: Int = -1,
|
||||
var exposureEventList: ArrayList<ExposureEvent>? = null,
|
||||
private val isFirst: Boolean = false,
|
||||
val type: String,
|
||||
var placeHolderId: String? = null // 如果当前itemBeam还未填充实际数据,仅做占位作用,placeHolderId会被赋值,作为实际数据填充的唯一标识,当实际数据填充后,placeHolderId置为空
|
||||
) {
|
||||
|
||||
val isShowFirstSetting: Boolean
|
||||
get() = isFirst && game?.firstSetting != null && !game.firstSetting?.firstPosition.isNullOrBlank()
|
||||
|
||||
val isPlaceHolder: Boolean
|
||||
get() = placeHolderId != null
|
||||
|
||||
fun matchPlaceHolder(other: SearchItemData) =
|
||||
if (type == other.type) {
|
||||
when (type) {
|
||||
TYPE_WECHAT_GAME_CPM_COLUMN, TYPE_DSP_GAME_COLUMN -> placeHolderId == other.subject?.columnId
|
||||
TYPE_AD_SPACE -> placeHolderId == SearchGameResultRepository.getAdPlaceHolderId(other.adConfig)
|
||||
else -> false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -1,255 +0,0 @@
|
||||
package com.gh.gamecenter.search.viewmodel
|
||||
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.gh.common.util.PackageHelper
|
||||
import com.gh.gamecenter.SearchActivity
|
||||
import com.gh.gamecenter.common.base.GlobalActivityManager
|
||||
import com.gh.gamecenter.common.baselist.ListViewModel
|
||||
import com.gh.gamecenter.common.baselist.LoadParams
|
||||
import com.gh.gamecenter.common.baselist.LoadStatus
|
||||
import com.gh.gamecenter.common.retrofit.Response
|
||||
import com.gh.gamecenter.common.utils.SensorsBridge
|
||||
import com.gh.gamecenter.common.utils.observableToMain
|
||||
import com.gh.gamecenter.core.provider.IHandleGameResponseProvider
|
||||
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_GAME
|
||||
import com.gh.gamecenter.entity.SearchGameUnionEntity.Companion.TYPE_WECHAT_GAME
|
||||
import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.livedata.Event
|
||||
import com.gh.gamecenter.livedata.EventObserver
|
||||
import com.gh.gamecenter.minigame.MiniGameSearchResultRepository
|
||||
import com.gh.gamecenter.search.ISearchGameResultRepository
|
||||
import com.gh.gamecenter.search.SearchGameResultRepository
|
||||
import com.gh.gamecenter.search.SearchItemData
|
||||
import com.therouter.TheRouter.get
|
||||
import io.reactivex.Observable
|
||||
import retrofit2.HttpException
|
||||
|
||||
class SearchGameResultViewModel(
|
||||
application: Application,
|
||||
private var mSearchKey: String?,
|
||||
private val repository: ISearchGameResultRepository,
|
||||
private var mSearchType: String,
|
||||
val sourceEntrance: String
|
||||
) : ListViewModel<SearchItemData, SearchItemData>(application) {
|
||||
|
||||
private var mPage = 0
|
||||
|
||||
private var gameList = arrayListOf<GameEntity>()
|
||||
|
||||
private val dataUpdateObserver = EventObserver<SearchItemData> {
|
||||
notifyItemChanged(it)
|
||||
}
|
||||
|
||||
private var originalDataList = arrayListOf<SearchItemData>()
|
||||
|
||||
init {
|
||||
if (repository is SearchGameResultRepository) {
|
||||
repository.dataUpdateEvent.observeForever(dataUpdateObserver)
|
||||
}
|
||||
}
|
||||
|
||||
fun updateSearchKeyWithType(searchKey: String, searchType: String) {
|
||||
mSearchKey = searchKey
|
||||
mSearchType = searchType
|
||||
}
|
||||
|
||||
override fun loadData() {
|
||||
if (mCurLoadParams == null) initLoadParams()
|
||||
|
||||
val loadParams = if (mRetryParams != null) mRetryParams else mCurLoadParams
|
||||
val curStatus = mLoadStatusLiveData.value
|
||||
|
||||
if (curStatus != null && curStatus != LoadStatus.INIT_LOADED && curStatus != LoadStatus.LIST_LOADED && curStatus != LoadStatus.INIT) return
|
||||
|
||||
if (mCurLoadParams.loadOffset == LoadParams.DEFAULT_OFFSET) {
|
||||
mLoadStatusLiveData.setValue(LoadStatus.INIT_LOADING)
|
||||
} else {
|
||||
mLoadStatusLiveData.setValue(LoadStatus.LIST_LOADING)
|
||||
}
|
||||
mPage = loadParams.loadOffset
|
||||
repository.getSearchGame(mSearchKey, loadParams.loadOffset)
|
||||
.compose(observableToMain())
|
||||
.subscribe(object : Response<List<SearchItemData>>() {
|
||||
|
||||
override fun onResponse(response: List<SearchItemData>?) {
|
||||
handleSuccess(response ?: emptyList(), curStatus)
|
||||
}
|
||||
|
||||
override fun onFailure(e: HttpException?) {
|
||||
e?.let(::handleFailure)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
private fun handleSuccess(response: List<SearchItemData>, curStatus: LoadStatus?) {
|
||||
if (mCurLoadParams.loadOffset == LoadParams.DEFAULT_OFFSET || curStatus == LoadStatus.INIT) {
|
||||
originalDataList.clear()
|
||||
}
|
||||
|
||||
// 针对游戏的一些操作(过滤隐藏游戏,过滤隐藏APK吗,增加下载数据)
|
||||
val handleGameResponse = get(IHandleGameResponseProvider::class.java)
|
||||
val gameList = handleGameResponse?.handleGameResponse(response, mEntrance) ?: response
|
||||
|
||||
originalDataList.addAll(gameList as List<SearchItemData>)
|
||||
mListLiveData.postValue(originalDataList)
|
||||
loadStatusControl(response.size)
|
||||
}
|
||||
|
||||
private fun handleFailure(exception: Exception) {
|
||||
if (exception is HttpException && exception.code() == 404) {
|
||||
loadStatusControl(0)
|
||||
mLoadStatusLiveData.value = LoadStatus.INIT_EXCEPTION
|
||||
mLoadExceptionLiveData.postValue(exception)
|
||||
} else {
|
||||
loadStatusControl(REQUEST_FAILURE_SIZE)
|
||||
if (exception is HttpException) {
|
||||
mLoadExceptionLiveData.postValue(exception)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun mergeResultLiveData() {
|
||||
mResultLiveData.addSource(mListLiveData) { list ->
|
||||
val data = arrayListOf<SearchItemData>()
|
||||
var gamePosition = 0
|
||||
gameList.clear()
|
||||
originalDataList.forEach {
|
||||
if (it.type == TYPE_GAME || it.type == TYPE_WECHAT_GAME) {
|
||||
it.gamePosition = gamePosition
|
||||
gamePosition++
|
||||
it.game?.let(gameList::add)
|
||||
}
|
||||
if (!it.isPlaceHolder) {
|
||||
data.add(it)
|
||||
}
|
||||
}
|
||||
|
||||
refreshWrongInstallStatus()
|
||||
|
||||
if (data.isEmpty()) {
|
||||
handleLoadStatusWhenGameListIsEmpty(data, list)
|
||||
} else {
|
||||
mResultLiveData.postValue(data)
|
||||
}
|
||||
|
||||
if (mPage == 1) {
|
||||
if (repository is MiniGameSearchResultRepository) {
|
||||
SensorsBridge.trackMiniGameSearchResultReturn(
|
||||
GlobalActivityManager.getCurrentPageEntity().pageId,
|
||||
GlobalActivityManager.getCurrentPageEntity().pageName,
|
||||
sourceEntrance,
|
||||
mSearchKey ?: "",
|
||||
SearchActivity.toTrackSearchType(mSearchType),
|
||||
list.isNotEmpty()
|
||||
)
|
||||
} else {
|
||||
SensorsBridge.trackGameSearchResultReturn(
|
||||
GlobalActivityManager.getCurrentPageEntity().pageId,
|
||||
GlobalActivityManager.getCurrentPageEntity().pageName,
|
||||
sourceEntrance,
|
||||
mSearchKey ?: "",
|
||||
SearchActivity.toTrackSearchType(mSearchType),
|
||||
list.isNotEmpty()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun notifyItemChanged(newItem: SearchItemData) {
|
||||
val positionList = arrayListOf<Int>()
|
||||
originalDataList.forEachIndexed { index, item ->
|
||||
if (item.matchPlaceHolder(newItem)) {
|
||||
positionList.add(index)
|
||||
}
|
||||
|
||||
}
|
||||
positionList.forEach {
|
||||
originalDataList[it] = newItem
|
||||
}
|
||||
|
||||
val displayItems = originalDataList.filterNot { it.isPlaceHolder }
|
||||
mResultLiveData.postValue(displayItems)
|
||||
}
|
||||
|
||||
fun refreshWrongInstallStatus() {
|
||||
if (gameList.isNotEmpty()) {
|
||||
PackageHelper.refreshWrongInstallStatus(gameList)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun handleLoadStatusWhenGameListIsEmpty(list: List<SearchItemData>, itemDataList: List<SearchItemData>) {
|
||||
if (mPage == 1 && list.isEmpty()) {
|
||||
mLoadStatusLiveData.value = if (itemDataList.isEmpty()) LoadStatus.INIT_EMPTY else LoadStatus.INIT_OVER
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadStatusControl(size: Int) {
|
||||
if (mCurLoadParams.loadOffset == LoadParams.DEFAULT_OFFSET) { // 初始化列表
|
||||
if (size == 0) {
|
||||
// 初始化列表size为0情况放到mergeResultLiveData方法处理
|
||||
} else if (size == REQUEST_FAILURE_SIZE) {
|
||||
mLoadStatusLiveData.setValue(LoadStatus.INIT_FAILED)
|
||||
} else if (size < mOverLimitSize) { // 避免一个屏幕出现两次分页
|
||||
mLoadStatusLiveData.setValue(LoadStatus.INIT_OVER)
|
||||
} else {
|
||||
mLoadStatusLiveData.setValue(LoadStatus.INIT_LOADED)
|
||||
}
|
||||
} else {
|
||||
if (size == REQUEST_FAILURE_SIZE) {
|
||||
mLoadStatusLiveData.setValue(LoadStatus.LIST_FAILED)
|
||||
} else if (size == 0) {
|
||||
mLoadStatusLiveData.setValue(LoadStatus.LIST_OVER)
|
||||
} else {
|
||||
mLoadStatusLiveData.setValue(LoadStatus.LIST_LOADED)
|
||||
}
|
||||
}
|
||||
|
||||
if (size == REQUEST_FAILURE_SIZE) {
|
||||
mRetryParams = mCurLoadParams
|
||||
} else {
|
||||
mRetryParams = null
|
||||
mCurLoadParams.loadOffset = mCurLoadParams.loadOffset + 1 // 页数 + 1
|
||||
}
|
||||
}
|
||||
|
||||
override fun provideDataObservable(page: Int): Observable<List<SearchItemData>>? = null
|
||||
|
||||
private val _refreshVipLoading = MutableLiveData<Event<Boolean>>()
|
||||
val refreshVipLoading: LiveData<Event<Boolean>> = _refreshVipLoading
|
||||
fun showRefreshVipLoading(show: Boolean) {
|
||||
_refreshVipLoading.value = Event(show)
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
if (repository is SearchGameResultRepository) {
|
||||
repository.dataUpdateEvent.removeObserver(dataUpdateObserver)
|
||||
}
|
||||
}
|
||||
|
||||
class Factory(
|
||||
private val mApplication: Application,
|
||||
private val mSearchKey: String?,
|
||||
private val repository: ISearchGameResultRepository,
|
||||
private val mSearchType: String,
|
||||
private val mSourceEntrance: String
|
||||
) : ViewModelProvider.NewInstanceFactory() {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return SearchGameResultViewModel(
|
||||
mApplication,
|
||||
mSearchKey,
|
||||
repository,
|
||||
mSearchType,
|
||||
mSourceEntrance
|
||||
) as T
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -32,7 +32,6 @@ class GameServerTestV2Fragment : LazyFragment() {
|
||||
private var mSettingBinding: LayoutGameServerTestV2SettingBinding? = null
|
||||
private var mCurrentFragment: GameServerTestV2ListFragment? = null
|
||||
private var mLinkText = ""
|
||||
private var mLinkId = ""
|
||||
private var mPageLocation: PageLocation? = null
|
||||
private var mIsSettingAnimating = false
|
||||
|
||||
@ -40,7 +39,6 @@ class GameServerTestV2Fragment : LazyFragment() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
mLinkId = arguments?.getString(EntranceConsts.KEY_ID, "none") ?: "none"
|
||||
mLinkText = requireArguments().getString(EntranceConsts.KEY_NAME, "")
|
||||
val topTabIndex = requireArguments().getInt(EntranceConsts.KEY_TAB_INDEX, -1)
|
||||
val bottomTabIndex = requireArguments().getInt(EntranceConsts.KEY_BOTTOM_TAB_INDEX, -1)
|
||||
@ -66,40 +64,22 @@ class GameServerTestV2Fragment : LazyFragment() {
|
||||
}
|
||||
|
||||
override fun onFragmentFirstVisible() {
|
||||
mViewModel = viewModelProviderFromParent(GameServerTestV2ViewModel.Factory(mLinkId))
|
||||
mViewModel = viewModelProviderFromParent()
|
||||
super.onFragmentFirstVisible()
|
||||
mViewModel?.run {
|
||||
displaySettingLiveData.observe(viewLifecycleOwner) {
|
||||
mBinding?.filterView?.setItemList(timeFilterList, DEFAULT_TIME_FILTER_INDEX)
|
||||
mViewModel?.selectedTimeFilterLiveData?.value = mViewModel?.timeFilterList?.getOrNull(DEFAULT_TIME_FILTER_INDEX) ?: ""
|
||||
mBinding?.filterView?.isVisible = true
|
||||
mBinding?.optionIv?.setOnClickListener {
|
||||
showSettingView()
|
||||
SensorsBridge.trackColumnTestClick(
|
||||
location = "详情页",
|
||||
recommendType = mLinkText,
|
||||
text = "设置",
|
||||
bottomTab = mPageLocation?.bottomTab,
|
||||
multiTabName = mPageLocation?.severalTabPageName,
|
||||
multiTabId = mPageLocation?.severalTabPageId,
|
||||
position = mPageLocation?.tabPosition,
|
||||
tabContent = mPageLocation?.tabContent
|
||||
)
|
||||
}
|
||||
mCurrentFragment?.loadData()
|
||||
|
||||
if (SPUtils.getBoolean(Constants.SP_SHOW_GAME_SERVER_TEST_V2_SETTING_GUIDE, true)) {
|
||||
mBaseHandler.post {
|
||||
showGuide()
|
||||
}
|
||||
SPUtils.setBoolean(Constants.SP_SHOW_GAME_SERVER_TEST_V2_SETTING_GUIDE, false)
|
||||
}
|
||||
}
|
||||
selectedTimeFilterLiveData.observe(viewLifecycleOwner) {
|
||||
mBinding?.filterView?.setItemList(timeFilterList, DEFAULT_TIME_FILTER_INDEX)
|
||||
selectedTimeFilterLiveData.observe(this@GameServerTestV2Fragment) {
|
||||
mBinding?.filterView?.setCurrentItem(timeFilterList.indexOf(it))
|
||||
}
|
||||
}
|
||||
changeFragment()
|
||||
|
||||
if (SPUtils.getBoolean(Constants.SP_SHOW_GAME_SERVER_TEST_V2_SETTING_GUIDE, true)) {
|
||||
mBaseHandler.post {
|
||||
showGuide()
|
||||
}
|
||||
SPUtils.setBoolean(Constants.SP_SHOW_GAME_SERVER_TEST_V2_SETTING_GUIDE, false)
|
||||
}
|
||||
}
|
||||
|
||||
override fun initRealView() {
|
||||
@ -109,7 +89,7 @@ class GameServerTestV2Fragment : LazyFragment() {
|
||||
filterContainer.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_background.toColor(requireContext()))
|
||||
filterView.onCheckedAction = { index ->
|
||||
mViewModel?.let {
|
||||
it.setSelectedTimeFilter(it.timeFilterList.getOrNull(index))
|
||||
it.setSelectedTimeFilter(it.timeFilterList[index])
|
||||
mCurrentFragment?.changeTimeFilter()
|
||||
SensorsBridge.trackColumnTestClick(
|
||||
location = "详情页",
|
||||
@ -120,7 +100,7 @@ class GameServerTestV2Fragment : LazyFragment() {
|
||||
multiTabId = mPageLocation?.severalTabPageId,
|
||||
position = mPageLocation?.tabPosition,
|
||||
tabContent = mPageLocation?.tabContent,
|
||||
buttonType = it.timeFilterList.getOrNull(index) ?: ""
|
||||
buttonType = it.timeFilterList[index]
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -144,6 +124,19 @@ class GameServerTestV2Fragment : LazyFragment() {
|
||||
)
|
||||
}
|
||||
}
|
||||
optionIv.setOnClickListener {
|
||||
showSettingView()
|
||||
SensorsBridge.trackColumnTestClick(
|
||||
location = "详情页",
|
||||
recommendType = mLinkText,
|
||||
text = "设置",
|
||||
bottomTab = mPageLocation?.bottomTab,
|
||||
multiTabName = mPageLocation?.severalTabPageName,
|
||||
multiTabId = mPageLocation?.severalTabPageId,
|
||||
position = mPageLocation?.tabPosition,
|
||||
tabContent = mPageLocation?.tabContent
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,7 +190,7 @@ class GameServerTestV2Fragment : LazyFragment() {
|
||||
val isCategoryFilterChanged = selectedCategoryFilterSet != tempCategoryFilterSet
|
||||
selectedCategoryFilterSet = HashSet(tempCategoryFilterSet)
|
||||
SPUtils.setStringSet(Constants.SP_GAME_SERVER_TEST_V2_CATEGORY_SET, selectedCategoryFilterSet)
|
||||
setSelectedTimeFilter(timeFilterList.getOrNull(1))
|
||||
setSelectedTimeFilter(GameServerTestV2ViewModel.TODAY_GAME)
|
||||
if (isCategoryFilterChanged) {
|
||||
mCurrentFragment?.changeCategoryFilter()
|
||||
}
|
||||
@ -219,8 +212,8 @@ class GameServerTestV2Fragment : LazyFragment() {
|
||||
}
|
||||
|
||||
val itemList = arrayListOf<TextView>()
|
||||
val allItem = getItemTextView(GameServerTestV2ViewModel.GameCategory.All.displayName)
|
||||
allItem.tag = GameServerTestV2ViewModel.GameCategory.All.displayName
|
||||
val allItem = getItemTextView("全部")
|
||||
allItem.tag = "全部"
|
||||
binding.flexbox.addView(allItem)
|
||||
toggleHighlightedTextView(allItem, mViewModel?.tempCategoryFilterSet.isNullOrEmpty())
|
||||
allItem.setOnClickListener {
|
||||
@ -345,6 +338,7 @@ class GameServerTestV2Fragment : LazyFragment() {
|
||||
}
|
||||
|
||||
private fun changeFragment() {
|
||||
mViewModel?.selectedTimeFilterLiveData?.value = mViewModel?.timeFilterList?.get(DEFAULT_TIME_FILTER_INDEX) ?: ""
|
||||
mCurrentFragment =
|
||||
childFragmentManager.findFragmentByTag(GameServerTestV2ViewModel::class.java.name) as? GameServerTestV2ListFragment
|
||||
?: GameServerTestV2ListFragment()
|
||||
|
||||
@ -18,7 +18,6 @@ import com.gh.download.DownloadManager
|
||||
import com.gh.gamecenter.R
|
||||
import com.gh.gamecenter.common.baselist.LazyListFragment
|
||||
import com.gh.gamecenter.common.baselist.ListAdapter
|
||||
import com.gh.gamecenter.common.baselist.LoadType
|
||||
import com.gh.gamecenter.common.constant.Constants
|
||||
import com.gh.gamecenter.common.constant.EntranceConsts
|
||||
import com.gh.gamecenter.common.exposure.ExposureSource
|
||||
@ -77,12 +76,6 @@ class GameServerTestV2ListFragment :
|
||||
)
|
||||
}
|
||||
|
||||
override fun isAutomaticLoad(): Boolean = false
|
||||
|
||||
fun loadData() {
|
||||
mListViewModel.load(LoadType.NORMAL)
|
||||
}
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
mGameServerTestV2ViewModel = viewModelProviderFromParent()
|
||||
super.onCreate(savedInstanceState)
|
||||
@ -363,7 +356,7 @@ class GameServerTestV2ListFragment :
|
||||
override fun onChanged(ts: MutableList<GameServerTestV2ListViewModel.ItemData>?) {
|
||||
super.onChanged(ts)
|
||||
if (!ts.isNullOrEmpty() && mListViewModel.isLoadFirstPage) {
|
||||
scrollToTime(GameServerTestV2ViewModel.TimeFilter.Today.value)
|
||||
scrollToTime(GameServerTestV2ViewModel.TimeFilter.TODAY.value)
|
||||
mListRv?.postDelayed({
|
||||
scroll()
|
||||
mScrollCalculatorHelper.onScrollStateChanged(RecyclerView.SCROLL_STATE_IDLE)
|
||||
|
||||
@ -55,9 +55,14 @@ class GameServerTestV2ListViewModel(
|
||||
mListLiveData.value = null
|
||||
mLoadStatusLiveData.postValue(LoadStatus.INIT_LOADING)
|
||||
|
||||
val categoryFilter = mGameServerTestV2ViewModel?.selectedCategoryFilterSet?.joinToString("-") {
|
||||
GameServerTestV2ViewModel.GameCategory.fromDisplayName(it).value
|
||||
} ?: "" //如果选择全部类型,则传空值
|
||||
var categoryFilter = "" //如果选择全部类型,则传空值
|
||||
mGameServerTestV2ViewModel?.selectedCategoryFilterSet?.forEachIndexed { index, category ->
|
||||
if (index == 0) {
|
||||
categoryFilter = mGameServerTestV2ViewModel.getCategoryFilterValue(category).value
|
||||
} else {
|
||||
categoryFilter += "-${mGameServerTestV2ViewModel.getCategoryFilterValue(category).value}"
|
||||
}
|
||||
}
|
||||
|
||||
Observable.zip(provideDetailObservable(categoryFilter), provideTopGamesObservable()) { t1, t2 ->
|
||||
mTopGameList = ArrayList(t2).apply {
|
||||
@ -146,11 +151,11 @@ class GameServerTestV2ListViewModel(
|
||||
|
||||
// 置顶游戏固定显示在今天上方,如果今天没有数据则展示在未来上方;如果今天和未来都没有数据,则显示在近期上方
|
||||
val topGamesReadableDaysOffset = if (mTodayCount != 0) {
|
||||
GameServerTestV2ViewModel.TimeFilter.Today.value
|
||||
GameServerTestV2ViewModel.TimeFilter.TODAY.value
|
||||
} else if (mFutureCount != 0) {
|
||||
GameServerTestV2ViewModel.TimeFilter.Future.value
|
||||
GameServerTestV2ViewModel.TimeFilter.UPCOMING_DAY.value
|
||||
} else {
|
||||
GameServerTestV2ViewModel.TimeFilter.Recent.value
|
||||
GameServerTestV2ViewModel.TimeFilter.PAST_DAY.value
|
||||
}
|
||||
var isTopGamesAdded = false
|
||||
|
||||
@ -174,7 +179,11 @@ class GameServerTestV2ListViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
val readableDaysOffset = slice.timeType
|
||||
val readableDaysOffset = when (slice.timeType) {
|
||||
"recent" -> GameServerTestV2ViewModel.TimeFilter.PAST_DAY.value
|
||||
"future" -> GameServerTestV2ViewModel.TimeFilter.UPCOMING_DAY.value
|
||||
else -> GameServerTestV2ViewModel.TimeFilter.TODAY.value
|
||||
}
|
||||
|
||||
if (!mTopGameList.isNullOrEmpty() && readableDaysOffset == topGamesReadableDaysOffset && !isTopGamesAdded) {
|
||||
subList.add(
|
||||
@ -326,31 +335,31 @@ class GameServerTestV2ListViewModel(
|
||||
if (position >= 0) return position
|
||||
|
||||
val recentCount =
|
||||
itemDataList.count { it.readableDaysOffset == GameServerTestV2ViewModel.TimeFilter.Recent.value }
|
||||
itemDataList.count { it.readableDaysOffset == GameServerTestV2ViewModel.TimeFilter.PAST_DAY.value }
|
||||
val todayCount =
|
||||
itemDataList.count { it.readableDaysOffset == GameServerTestV2ViewModel.TimeFilter.Today.value }
|
||||
itemDataList.count { it.readableDaysOffset == GameServerTestV2ViewModel.TimeFilter.TODAY.value }
|
||||
|
||||
when (timeFilter) {
|
||||
GameServerTestV2ViewModel.TimeFilter.Recent.value -> {
|
||||
position = mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.Today.value, -1)
|
||||
GameServerTestV2ViewModel.TimeFilter.PAST_DAY.value -> {
|
||||
position = mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.TODAY.value, -1)
|
||||
if (position >= 0) return position
|
||||
position =
|
||||
mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.Future.value, -1)
|
||||
mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.UPCOMING_DAY.value, -1)
|
||||
if (position >= 0) return position
|
||||
}
|
||||
GameServerTestV2ViewModel.TimeFilter.Today.value -> {
|
||||
GameServerTestV2ViewModel.TimeFilter.TODAY.value -> {
|
||||
position =
|
||||
mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.Future.value, -1)
|
||||
mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.UPCOMING_DAY.value, -1)
|
||||
if (position >= 0) return position
|
||||
position =
|
||||
mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.Recent.value, -1)
|
||||
mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.PAST_DAY.value, -1)
|
||||
if (position >= 0) return recentCount - 1
|
||||
}
|
||||
GameServerTestV2ViewModel.TimeFilter.Future.value -> {
|
||||
position = mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.Today.value, -1)
|
||||
GameServerTestV2ViewModel.TimeFilter.UPCOMING_DAY.value -> {
|
||||
position = mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.TODAY.value, -1)
|
||||
if (position >= 0) return recentCount + todayCount - 1
|
||||
position =
|
||||
mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.Recent.value, -1)
|
||||
mTimePositionArrayMap.getOrDefault(GameServerTestV2ViewModel.TimeFilter.PAST_DAY.value, -1)
|
||||
if (position >= 0) recentCount - 1
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package com.gh.gamecenter.servers.gametest2
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.app.Application
|
||||
import androidx.lifecycle.AndroidViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
@ -8,129 +7,89 @@ import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import com.gh.common.constant.Config
|
||||
import com.gh.gamecenter.common.constant.Constants
|
||||
import com.gh.gamecenter.common.utils.singleToMain
|
||||
import com.gh.gamecenter.core.utils.SPUtils
|
||||
import com.gh.gamecenter.entity.GameServerTestDisplaySetting
|
||||
import com.gh.gamecenter.retrofit.RetrofitManager
|
||||
import com.gh.gamecenter.servers.gametest2.GameServerTestV2ViewModel.GameCategory.*
|
||||
import com.halo.assistant.HaloApp
|
||||
|
||||
class GameServerTestV2ViewModel(application: Application, private val linkId: String = "none") : AndroidViewModel(application) {
|
||||
class GameServerTestV2ViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
val timeFilterList = arrayListOf(
|
||||
GameServerTestDisplaySetting.RECENT,
|
||||
GameServerTestDisplaySetting.TODAY,
|
||||
GameServerTestDisplaySetting.FUTURE
|
||||
)
|
||||
val categoryFilterList = arrayListOf(
|
||||
Local.displayName,
|
||||
Online.displayName,
|
||||
Welfare.displayName,
|
||||
Gjonline.displayName
|
||||
)
|
||||
val timeFilterList = arrayListOf(RECENT_GAME, TODAY_GAME, FUTURE_GAME)
|
||||
val categoryFilterList = arrayListOf("单机游戏", "网络游戏", "福利游戏", "国际服游戏")
|
||||
var selectedCategoryFilterSet = SPUtils.getStringSet(Constants.SP_GAME_SERVER_TEST_V2_CATEGORY_SET).toHashSet()
|
||||
var tempCategoryFilterSet = SPUtils.getStringSet(Constants.SP_GAME_SERVER_TEST_V2_CATEGORY_SET).toHashSet()
|
||||
var selectedTimeFilterLiveData = MutableLiveData<String?>()
|
||||
var selectedTimeFilterLiveData = MutableLiveData(timeFilterList[GameServerTestV2Fragment.DEFAULT_TIME_FILTER_INDEX])
|
||||
var bigImageMode = SPUtils.getBoolean(
|
||||
Constants.SP_GAME_SERVER_TEST_V2_BIG_IMAGE_MODE,
|
||||
Config.getSettings() == null || Config.getSettings()?.columnTestV2Setting?.defaultView == "on"
|
||||
)
|
||||
|
||||
val displaySettingLiveData = MutableLiveData<GameServerTestDisplaySetting>()
|
||||
|
||||
init {
|
||||
loadDisplaySetting()
|
||||
fun getCategoryFilterString(): String {
|
||||
var selectedCategoryFilter = "全部类型"
|
||||
selectedCategoryFilterSet.forEachIndexed { index, category ->
|
||||
if (index == 0) {
|
||||
selectedCategoryFilter = category
|
||||
} else {
|
||||
selectedCategoryFilter += "+$category"
|
||||
}
|
||||
}
|
||||
return selectedCategoryFilter
|
||||
}
|
||||
|
||||
@SuppressLint("CheckResult")
|
||||
fun loadDisplaySetting() {
|
||||
RetrofitManager.getInstance().newApi.getGameServerTestDisplaySetting(linkId)
|
||||
.compose(singleToMain())
|
||||
.subscribe({ setting ->
|
||||
timeFilterList.clear()
|
||||
timeFilterList.addAll(listOf(setting.timeTextPast, setting.timeTextPresent, setting.timeTextFuture))
|
||||
TimeFilter.Recent.displayName = setting.timeTextPast
|
||||
TimeFilter.Today.displayName = setting.timeTextPresent
|
||||
TimeFilter.Future.displayName = setting.timeTextFuture
|
||||
categoryFilterList.clear()
|
||||
categoryFilterList.addAll(setting.gameCategory.map { GameCategory.fromValue(it).displayName })
|
||||
selectedCategoryFilterSet.removeAll { !categoryFilterList.contains(it) }
|
||||
tempCategoryFilterSet.removeAll { !categoryFilterList.contains(it) }
|
||||
displaySettingLiveData.postValue(setting)
|
||||
}, {
|
||||
it.printStackTrace()
|
||||
displaySettingLiveData.postValue(GameServerTestDisplaySetting())
|
||||
})
|
||||
fun getCategoryFilterValue(category: String): GameCategory {
|
||||
return when (category) {
|
||||
"单机游戏" -> GameCategory.LOCAL
|
||||
"网络游戏" -> GameCategory.ONLINE
|
||||
"福利游戏" -> GameCategory.WELFARE
|
||||
"国际服游戏" -> GameCategory.GJONLINE
|
||||
else -> GameCategory.ALL
|
||||
}
|
||||
}
|
||||
|
||||
fun getCategoryFilterString() = selectedCategoryFilterSet.joinToString("+").ifEmpty { "全部类型" }
|
||||
fun getSelectedTimeFilterString(dayFilter: String): String {
|
||||
return when (dayFilter) {
|
||||
TimeFilter.PAST_DAY.value -> RECENT_GAME
|
||||
TimeFilter.TODAY.value -> TODAY_GAME
|
||||
TimeFilter.UPCOMING_DAY.value -> FUTURE_GAME
|
||||
else -> TODAY_GAME
|
||||
}
|
||||
}
|
||||
|
||||
fun getSelectedTimeFilterString(dayFilter: String) = TimeFilter.fromValue(dayFilter).displayName
|
||||
|
||||
fun getCurrentSelectedTimeFilter() = TimeFilter.fromDisplayName(selectedTimeFilterLiveData.value ?: "").value
|
||||
fun getCurrentSelectedTimeFilter(): String {
|
||||
return when (selectedTimeFilterLiveData.value) {
|
||||
RECENT_GAME -> TimeFilter.PAST_DAY.value
|
||||
TODAY_GAME -> TimeFilter.TODAY.value
|
||||
FUTURE_GAME -> TimeFilter.UPCOMING_DAY.value
|
||||
else -> TimeFilter.TODAY.value
|
||||
}
|
||||
}
|
||||
|
||||
fun setSelectedTimeFilter(filter: String?) {
|
||||
if (filter.isNullOrEmpty()) return
|
||||
selectedTimeFilterLiveData.value = filter
|
||||
}
|
||||
|
||||
sealed class TimeFilter(val value: String, var displayName: String) {
|
||||
data object Recent : TimeFilter("recent", GameServerTestDisplaySetting.RECENT)
|
||||
data object Today : TimeFilter("today", GameServerTestDisplaySetting.TODAY)
|
||||
data object Future : TimeFilter("future", GameServerTestDisplaySetting.FUTURE)
|
||||
|
||||
companion object {
|
||||
fun fromDisplayName(displayName: String): TimeFilter {
|
||||
return when (displayName) {
|
||||
Recent.displayName -> Recent
|
||||
Future.displayName -> Future
|
||||
else -> Today
|
||||
}
|
||||
}
|
||||
|
||||
fun fromValue(value: String): TimeFilter {
|
||||
return when (value) {
|
||||
Recent.value -> Recent
|
||||
Future.value -> Future
|
||||
else -> Today
|
||||
}
|
||||
}
|
||||
}
|
||||
enum class TimeFilter(val value: String) {
|
||||
PAST_DAY("recent"),
|
||||
TODAY("today"),
|
||||
UPCOMING_DAY("future")
|
||||
}
|
||||
|
||||
sealed class GameCategory(val value: String, val displayName: String) {
|
||||
data object All : GameCategory("all", "全部")
|
||||
data object Local : GameCategory("local", "单机游戏")
|
||||
data object Online : GameCategory("online", "网络游戏")
|
||||
data object Welfare : GameCategory("welfare", "福利游戏")
|
||||
data object Gjonline : GameCategory("gjonline", "国际服游戏")
|
||||
|
||||
companion object {
|
||||
fun fromDisplayName(displayName: String): GameCategory {
|
||||
return when (displayName) {
|
||||
Local.displayName -> Local
|
||||
Online.displayName -> Online
|
||||
Welfare.displayName -> Welfare
|
||||
Gjonline.displayName -> Gjonline
|
||||
else -> All
|
||||
}
|
||||
}
|
||||
|
||||
fun fromValue(value: String): GameCategory {
|
||||
return when (value) {
|
||||
Local.value -> Local
|
||||
Online.value -> Online
|
||||
Welfare.value -> Welfare
|
||||
Gjonline.value -> Gjonline
|
||||
else -> All
|
||||
}
|
||||
}
|
||||
}
|
||||
enum class GameCategory(val value: String) {
|
||||
ALL("all"),
|
||||
LOCAL("local"),
|
||||
ONLINE("online"),
|
||||
WELFARE("welfare"),
|
||||
GJONLINE("gjonline"),
|
||||
}
|
||||
|
||||
class Factory(private val linkId: String = "none") :
|
||||
companion object {
|
||||
const val RECENT_GAME = "近期"
|
||||
const val TODAY_GAME = "今天"
|
||||
const val FUTURE_GAME = "预约"
|
||||
}
|
||||
|
||||
class Factory(private val mApplication: Application) :
|
||||
ViewModelProvider.NewInstanceFactory() {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return GameServerTestV2ViewModel(HaloApp.getInstance(), linkId) as T
|
||||
return GameServerTestV2ViewModel(mApplication) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -14,7 +14,6 @@ interface ISubjectListRepository {
|
||||
order: String?,
|
||||
ad: String?,
|
||||
columnCollectionId: String?,
|
||||
minimumSize: Int,
|
||||
onlyFee: Boolean
|
||||
): Single<MutableList<GameEntity>>
|
||||
|
||||
|
||||
@ -302,6 +302,14 @@ open class SubjectListFragment : LazyListFragment<GameEntity, SubjectListViewMod
|
||||
}
|
||||
}
|
||||
|
||||
override fun theNumberNeededToFillAScreen(): Int {
|
||||
return if (mListViewModel.subjectData.subjectType == SubjectData.SubjectType.WECHAT_GAME_CPM) 20 else super.theNumberNeededToFillAScreen()
|
||||
}
|
||||
|
||||
override fun autoLoadMore() {
|
||||
mListViewModel.load(LoadType.NORMAL)
|
||||
}
|
||||
|
||||
override fun onDarkModeChanged() {
|
||||
super.onDarkModeChanged()
|
||||
mAdapter?.let { it.notifyItemRangeChanged(0, it.itemCount) }
|
||||
|
||||
@ -18,7 +18,6 @@ class SubjectListRepository(
|
||||
order: String?,
|
||||
ad: String?,
|
||||
columnCollectionId: String?,
|
||||
minimumSize: Int,
|
||||
onlyFee: Boolean,
|
||||
): Single<MutableList<GameEntity>> {
|
||||
return api.getColumn(column_id, sort, order, ad, columnCollectionId, page)
|
||||
|
||||
@ -6,6 +6,7 @@ import androidx.lifecycle.ViewModelProvider
|
||||
import com.gh.common.exposure.ExposureUtils
|
||||
import com.gh.common.util.AdHelper
|
||||
import com.gh.gamecenter.common.baselist.ListViewModel
|
||||
import com.gh.gamecenter.common.baselist.LoadParams
|
||||
import com.gh.gamecenter.common.baselist.LoadStatus
|
||||
import com.gh.gamecenter.common.baselist.LoadType
|
||||
import com.gh.gamecenter.common.exposure.ExposureSource
|
||||
@ -17,7 +18,6 @@ import com.gh.gamecenter.feature.entity.GameEntity
|
||||
import com.gh.gamecenter.feature.entity.PageLocation
|
||||
import com.gh.gamecenter.feature.entity.TagStyleEntity
|
||||
import com.gh.gamecenter.feature.exposure.ExposureConstants
|
||||
import com.gh.gamecenter.retrofit.RetrofitManager
|
||||
import io.reactivex.Observable
|
||||
import io.reactivex.Single
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
@ -38,6 +38,8 @@ open class SubjectListViewModel(
|
||||
|
||||
var lastPageDataMap: HashMap<String, String>? = null
|
||||
|
||||
private var total = 0
|
||||
|
||||
private val repository = SubjectRepositoryFactory.createListRepo(subjectData.subjectType)
|
||||
|
||||
override fun provideDataObservable(page: Int): Observable<List<GameEntity>>? = null
|
||||
@ -50,7 +52,6 @@ open class SubjectListViewModel(
|
||||
subjectData.filter.ifEmpty { "type:全部" },
|
||||
AdHelper.getIdfaString(),
|
||||
columnCollectionId,
|
||||
-1,
|
||||
false
|
||||
)
|
||||
}
|
||||
@ -110,6 +111,7 @@ open class SubjectListViewModel(
|
||||
|
||||
override fun load(loadType: LoadType) {
|
||||
if (loadType == LoadType.REFRESH) {
|
||||
total = 0
|
||||
initLoadParams()
|
||||
} else if (loadType == LoadType.RETRY) {
|
||||
mLoadStatusLiveData.value = LoadStatus.LIST_LOADED
|
||||
@ -122,6 +124,49 @@ open class SubjectListViewModel(
|
||||
}
|
||||
}
|
||||
|
||||
override fun loadStatusControl(size: Int) {
|
||||
if (size > 0) {
|
||||
total += size
|
||||
}
|
||||
if (subjectData.subjectType == SubjectData.SubjectType.WECHAT_GAME_CPM) {
|
||||
|
||||
if (mCurLoadParams.loadOffset == LoadParams.DEFAULT_OFFSET) {
|
||||
// 第一页不管有没有数据,都设置初始化完毕状态(如果游戏数量不够,会触发自动加载下一页)
|
||||
mLoadStatusLiveData.value = LoadStatus.INIT_LOADED
|
||||
} else {
|
||||
when {
|
||||
total >= CPM_MAX_SIZE -> { // 请求游戏个数到达最大显示数量,列表加载完毕
|
||||
mLoadStatusLiveData.value = LoadStatus.LIST_OVER
|
||||
}
|
||||
|
||||
mCurLoadParams.loadOffset >= CPM_MAX_LOAD_COUNT && total > 0 -> { // 请求次数到达最大请求次数,且游戏总量大于0,列表加载完毕
|
||||
mLoadStatusLiveData.value = LoadStatus.LIST_OVER
|
||||
}
|
||||
|
||||
mCurLoadParams.loadOffset >= CPM_MAX_LOAD_COUNT -> { // 请求次数搭配大最大请求次数,且游戏总量为0,说明没有数据
|
||||
mLoadStatusLiveData.value = LoadStatus.INIT_EMPTY
|
||||
}
|
||||
|
||||
else -> {
|
||||
mLoadStatusLiveData.value = LoadStatus.LIST_LOADED
|
||||
// 继续加载下一页数据
|
||||
load(LoadType.NORMAL)
|
||||
}
|
||||
}
|
||||
}
|
||||
mCurLoadParams.loadOffset += 1
|
||||
} else {
|
||||
super.loadStatusControl(size)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val CPM_MAX_LOAD_COUNT = 6
|
||||
private const val CPM_MAX_SIZE = 20
|
||||
private const val AUTO_LOAD_DURATION = 500L
|
||||
}
|
||||
|
||||
class Factory(
|
||||
private val mApplication: Application,
|
||||
private val subjectData: SubjectData,
|
||||
@ -130,7 +175,13 @@ open class SubjectListViewModel(
|
||||
private val columnCollectionId: String? = null
|
||||
) : ViewModelProvider.NewInstanceFactory() {
|
||||
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||
return SubjectListViewModel(mApplication, subjectData, pageLocation, exposureSourceList, columnCollectionId) as T
|
||||
return SubjectListViewModel(
|
||||
mApplication,
|
||||
subjectData,
|
||||
pageLocation,
|
||||
exposureSourceList,
|
||||
columnCollectionId
|
||||
) as T
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@ class SubjectViewModel(
|
||||
) : AndroidViewModel(application) {
|
||||
|
||||
val subjectNameLD = MutableLiveData<String>()
|
||||
val subjectSettingLD = MutableLiveData<SubjectSettingEntity>()
|
||||
val subjectSettingLD = MutableLiveData<SubjectSettingEntity?>()
|
||||
|
||||
private val repository =
|
||||
SubjectRepositoryFactory.createRepo(subjectData?.subjectType ?: SubjectData.SubjectType.NORMAL)
|
||||
@ -34,7 +34,7 @@ class SubjectViewModel(
|
||||
if (subjectData?.subjectName.isNullOrEmpty()) {
|
||||
loadSubjectName()
|
||||
} else {
|
||||
subjectNameLD.postValue(subjectData?.subjectName)
|
||||
subjectNameLD.postValue(subjectData?.subjectName ?: "")
|
||||
loadSubjectType()
|
||||
}
|
||||
} else {
|
||||
|
||||
@ -56,10 +56,9 @@
|
||||
|
||||
<com.gh.gamecenter.common.view.SegmentedIconFilterView
|
||||
android:id="@+id/filterView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="130dp"
|
||||
android:layout_height="28dp"
|
||||
android:layout_marginStart="16dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
@ -143,7 +143,7 @@ ext {
|
||||
|
||||
acloudPush = "3.8.8.1"
|
||||
jpushVersion = "5.4.0"
|
||||
jverifiationVersion = "3.4.2"
|
||||
jverifiationVersion = "3.2.5"
|
||||
honorPushVersion = "7.0.61.303"
|
||||
|
||||
volcTlsVersion = "1.1.4"
|
||||
|
||||
@ -517,10 +517,7 @@ object SensorsBridge {
|
||||
downloadStatus: String,
|
||||
gameType: String,
|
||||
position: Int,
|
||||
tabContent: String,
|
||||
linkType: String,
|
||||
linkId: String,
|
||||
linkText: String,
|
||||
tabContent: String
|
||||
) {
|
||||
val json = json {
|
||||
KEY_GAME_ID to gameId
|
||||
@ -535,9 +532,6 @@ object SensorsBridge {
|
||||
KEY_GAME_TYPE to gameType
|
||||
KEY_POSITION to position
|
||||
KEY_TAB_CONTENT to tabContent
|
||||
KEY_LINK_TYPE to linkType
|
||||
KEY_LINK_ID to linkId
|
||||
KEY_LINK_TEXT to linkText
|
||||
}
|
||||
|
||||
trackEvent(EVENT_GAME_DETAIL_PAGE_TAB_SELECT, json)
|
||||
|
||||
@ -5,12 +5,10 @@ import android.graphics.drawable.Drawable
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.widget.FrameLayout
|
||||
import androidx.core.view.setPadding
|
||||
import com.gh.gamecenter.common.R
|
||||
import com.gh.gamecenter.common.databinding.ViewSegmentedIconFilterBinding
|
||||
import com.gh.gamecenter.common.utils.DarkModeUtils
|
||||
import com.gh.gamecenter.common.utils.dip2px
|
||||
import com.gh.gamecenter.common.utils.toDrawable
|
||||
|
||||
class SegmentedIconFilterView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null): FrameLayout(context, attrs) {
|
||||
private var textSize = 12F
|
||||
@ -25,12 +23,15 @@ class SegmentedIconFilterView @JvmOverloads constructor(context: Context, attrs:
|
||||
var onCheckedAction: ((Int) -> Unit)? = null
|
||||
|
||||
init {
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.view_segmented_icon_filter, this, false)
|
||||
val view = LayoutInflater.from(context).inflate(R.layout.view_segmented_icon_filter, this, true)
|
||||
binding = ViewSegmentedIconFilterBinding.bind(view)
|
||||
addView(view)
|
||||
initView(attrs)
|
||||
}
|
||||
|
||||
fun setContainerBackground(drawable: Drawable?) {
|
||||
binding.motionLayout.background = drawable
|
||||
}
|
||||
|
||||
private fun initView(attrs: AttributeSet?) {
|
||||
if (attrs != null) {
|
||||
val ta = context.obtainStyledAttributes(attrs, R.styleable.SegmentedFilterView)
|
||||
@ -46,8 +47,12 @@ class SegmentedIconFilterView @JvmOverloads constructor(context: Context, attrs:
|
||||
ta.recycle()
|
||||
}
|
||||
|
||||
background = containerBackground ?: R.drawable.bg_shape_ui_container_2_radius_999.toDrawable(context)
|
||||
setPadding(containerPadding)
|
||||
binding.motionLayout.run {
|
||||
setPadding(containerPadding, containerPadding, containerPadding, containerPadding)
|
||||
containerBackground?.let {
|
||||
background = it
|
||||
}
|
||||
}
|
||||
indicatorBackground?.let {
|
||||
binding.indicator.background = it
|
||||
}
|
||||
@ -124,7 +129,6 @@ class SegmentedIconFilterView @JvmOverloads constructor(context: Context, attrs:
|
||||
}
|
||||
|
||||
fun updateView(isDarkModeOn: Boolean) {
|
||||
background = containerBackground ?: R.drawable.bg_shape_ui_container_2_radius_999.toDrawable(context)
|
||||
binding.motionLayout.run {
|
||||
when (currentIndex) {
|
||||
0 -> jumpToState(if (isDarkModeOn) R.id.filterDark1 else R.id.filter1)
|
||||
|
||||
@ -1,60 +1,66 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.motion.widget.MotionLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/motionLayout"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="24dp"
|
||||
app:layoutDescription="@xml/view_segmented_icon_filter_scene">
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<View
|
||||
android:id="@+id/indicator"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@drawable/background_shape_white_radius_999" />
|
||||
<androidx.constraintlayout.motion.widget.MotionLayout
|
||||
android:id="@+id/motionLayout"
|
||||
android:layout_width="130dp"
|
||||
android:layout_height="28dp"
|
||||
android:background="@drawable/bg_shape_ui_container_2_radius_999"
|
||||
android:padding="2dp"
|
||||
app:layoutDescription="@xml/view_segmented_icon_filter_scene">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon1Iv"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
app:srcCompat="@drawable/ic_segmented_icon_filter_dot" />
|
||||
<View
|
||||
android:id="@+id/indicator"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="@drawable/background_shape_white_radius_999" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/filter1Tv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:gravity="center"
|
||||
android:includeFontPadding="false"
|
||||
android:text="近期"
|
||||
android:textSize="@dimen/secondary_size" />
|
||||
<ImageView
|
||||
android:id="@+id/icon1Iv"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
app:srcCompat="@drawable/ic_segmented_icon_filter_dot" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon2Iv"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
app:srcCompat="@drawable/ic_segmented_icon_filter_dot" />
|
||||
<TextView
|
||||
android:id="@+id/filter1Tv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="28dp"
|
||||
android:gravity="center"
|
||||
android:includeFontPadding="false"
|
||||
android:text="近期"
|
||||
android:textSize="@dimen/secondary_size" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/filter2Tv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:gravity="center"
|
||||
android:includeFontPadding="false"
|
||||
android:text="今天"
|
||||
android:textSize="@dimen/secondary_size" />
|
||||
<ImageView
|
||||
android:id="@+id/icon2Iv"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
app:srcCompat="@drawable/ic_segmented_icon_filter_dot" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon3Iv"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
app:srcCompat="@drawable/ic_segmented_icon_filter_dot" />
|
||||
<TextView
|
||||
android:id="@+id/filter2Tv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="28dp"
|
||||
android:gravity="center"
|
||||
android:includeFontPadding="false"
|
||||
android:text="今天"
|
||||
android:textSize="@dimen/secondary_size" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/filter3Tv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:gravity="center"
|
||||
android:includeFontPadding="false"
|
||||
android:text="未来"
|
||||
android:textSize="@dimen/secondary_size" />
|
||||
</androidx.constraintlayout.motion.widget.MotionLayout>
|
||||
<ImageView
|
||||
android:id="@+id/icon3Iv"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
app:srcCompat="@drawable/ic_segmented_icon_filter_dot" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/filter3Tv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="28dp"
|
||||
android:gravity="center"
|
||||
android:includeFontPadding="false"
|
||||
android:text="未来"
|
||||
android:textSize="@dimen/secondary_size" />
|
||||
</androidx.constraintlayout.motion.widget.MotionLayout>
|
||||
</FrameLayout>
|
||||
@ -31,11 +31,9 @@
|
||||
<ConstraintSet android:id="@+id/filter1">
|
||||
<Constraint
|
||||
android:id="@+id/indicator"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="24dp"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintEnd_toStartOf="@+id/filter2Tv"
|
||||
motion:layout_constraintStart_toStartOf="parent"
|
||||
motion:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
@ -66,18 +64,18 @@
|
||||
android:id="@+id/icon2Iv"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:alpha="0"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintStart_toStartOf="@+id/filter2Tv"
|
||||
motion:layout_constraintStart_toEndOf="@+id/indicator"
|
||||
motion:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/filter2Tv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="14dp"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
|
||||
motion:layout_constraintStart_toEndOf="@+id/indicator"
|
||||
motion:layout_constraintTop_toTopOf="parent">
|
||||
<CustomAttribute
|
||||
motion:attributeName="textColor"
|
||||
@ -88,19 +86,18 @@
|
||||
android:id="@+id/icon3Iv"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:alpha="0"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintStart_toStartOf="@+id/filter3Tv"
|
||||
motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
|
||||
motion:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/filter3Tv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="14dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginStart="-2dp"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintEnd_toEndOf="parent"
|
||||
motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
|
||||
motion:layout_constraintTop_toTopOf="parent">
|
||||
<CustomAttribute
|
||||
@ -112,12 +109,9 @@
|
||||
<ConstraintSet android:id="@+id/filter2">
|
||||
<Constraint
|
||||
android:id="@+id/indicator"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="24dp"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintEnd_toStartOf="@+id/filter3Tv"
|
||||
motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
|
||||
motion:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
@ -125,16 +119,17 @@
|
||||
android:id="@+id/icon1Iv"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:alpha="0"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintStart_toStartOf="@+id/filter1Tv"
|
||||
motion:layout_constraintStart_toStartOf="parent"
|
||||
motion:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/filter1Tv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginStart="-2dp"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintStart_toStartOf="parent"
|
||||
motion:layout_constraintTop_toTopOf="parent">
|
||||
@ -147,7 +142,7 @@
|
||||
android:id="@+id/icon2Iv"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginStart="14dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:alpha="1"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
|
||||
@ -178,13 +173,11 @@
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/filter3Tv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="14dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginEnd="-2dp"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintEnd_toEndOf="parent"
|
||||
motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
|
||||
motion:layout_constraintTop_toTopOf="parent">
|
||||
<CustomAttribute
|
||||
motion:attributeName="textColor"
|
||||
@ -195,28 +188,27 @@
|
||||
<ConstraintSet android:id="@+id/filter3">
|
||||
<Constraint
|
||||
android:id="@+id/indicator"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="24dp"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintEnd_toEndOf="parent"
|
||||
motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
|
||||
motion:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/icon1Iv"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:alpha="0"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintStart_toStartOf="@+id/filter1Tv"
|
||||
motion:layout_constraintStart_toStartOf="parent"
|
||||
motion:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/filter1Tv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginStart="-2dp"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintStart_toStartOf="parent"
|
||||
motion:layout_constraintTop_toTopOf="parent">
|
||||
@ -229,16 +221,17 @@
|
||||
android:id="@+id/icon2Iv"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:alpha="0"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintStart_toStartOf="@+id/filter2Tv"
|
||||
motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
|
||||
motion:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/filter2Tv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="14dp"
|
||||
android:layout_marginStart="-2dp"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
|
||||
motion:layout_constraintTop_toTopOf="parent">
|
||||
@ -251,7 +244,7 @@
|
||||
android:id="@+id/icon3Iv"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginStart="14dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:alpha="1"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
|
||||
@ -262,9 +255,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="2dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintEnd_toEndOf="parent"
|
||||
motion:layout_constraintStart_toEndOf="@+id/icon3Iv"
|
||||
motion:layout_constraintTop_toTopOf="parent">
|
||||
<CustomAttribute
|
||||
@ -276,11 +267,9 @@
|
||||
<ConstraintSet android:id="@+id/filterDark1">
|
||||
<Constraint
|
||||
android:id="@+id/indicator"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="24dp"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintEnd_toStartOf="@+id/filter2Tv"
|
||||
motion:layout_constraintStart_toStartOf="parent"
|
||||
motion:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
@ -311,18 +300,18 @@
|
||||
android:id="@+id/icon2Iv"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:alpha="0"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintStart_toStartOf="@+id/filter2Tv"
|
||||
motion:layout_constraintStart_toEndOf="@+id/indicator"
|
||||
motion:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/filter2Tv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="14dp"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
|
||||
motion:layout_constraintStart_toEndOf="@+id/indicator"
|
||||
motion:layout_constraintTop_toTopOf="parent">
|
||||
<CustomAttribute
|
||||
motion:attributeName="textColor"
|
||||
@ -333,19 +322,18 @@
|
||||
android:id="@+id/icon3Iv"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:alpha="0"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintStart_toStartOf="@+id/filter3Tv"
|
||||
motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
|
||||
motion:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/filter3Tv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="14dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginStart="-2dp"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintEnd_toEndOf="parent"
|
||||
motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
|
||||
motion:layout_constraintTop_toTopOf="parent">
|
||||
<CustomAttribute
|
||||
@ -357,12 +345,9 @@
|
||||
<ConstraintSet android:id="@+id/filterDark2">
|
||||
<Constraint
|
||||
android:id="@+id/indicator"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="24dp"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintEnd_toStartOf="@+id/filter3Tv"
|
||||
motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
|
||||
motion:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
@ -370,16 +355,17 @@
|
||||
android:id="@+id/icon1Iv"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:alpha="0"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintStart_toStartOf="@+id/filter1Tv"
|
||||
motion:layout_constraintStart_toStartOf="parent"
|
||||
motion:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/filter1Tv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginStart="-2dp"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintStart_toStartOf="parent"
|
||||
motion:layout_constraintTop_toTopOf="parent">
|
||||
@ -392,7 +378,7 @@
|
||||
android:id="@+id/icon2Iv"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginStart="14dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:alpha="1"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
|
||||
@ -415,6 +401,7 @@
|
||||
android:id="@+id/icon3Iv"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:alpha="0"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintStart_toStartOf="@+id/filter3Tv"
|
||||
@ -422,13 +409,11 @@
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/filter3Tv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="14dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
android:layout_marginEnd="-2dp"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintEnd_toEndOf="parent"
|
||||
motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
|
||||
motion:layout_constraintTop_toTopOf="parent">
|
||||
<CustomAttribute
|
||||
motion:attributeName="textColor"
|
||||
@ -439,28 +424,27 @@
|
||||
<ConstraintSet android:id="@+id/filterDark3">
|
||||
<Constraint
|
||||
android:id="@+id/indicator"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="24dp"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintEnd_toEndOf="parent"
|
||||
motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
|
||||
motion:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/icon1Iv"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:alpha="0"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintStart_toStartOf="@+id/filter1Tv"
|
||||
motion:layout_constraintStart_toStartOf="parent"
|
||||
motion:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/filter1Tv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginStart="-2dp"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintStart_toStartOf="parent"
|
||||
motion:layout_constraintTop_toTopOf="parent">
|
||||
@ -473,16 +457,17 @@
|
||||
android:id="@+id/icon2Iv"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:alpha="0"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintStart_toStartOf="@+id/filter2Tv"
|
||||
motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
|
||||
motion:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<Constraint
|
||||
android:id="@+id/filter2Tv"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="14dp"
|
||||
android:layout_marginStart="-2dp"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintStart_toEndOf="@+id/filter1Tv"
|
||||
motion:layout_constraintTop_toTopOf="parent">
|
||||
@ -495,7 +480,7 @@
|
||||
android:id="@+id/icon3Iv"
|
||||
android:layout_width="12dp"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginStart="14dp"
|
||||
android:layout_marginStart="6dp"
|
||||
android:alpha="1"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintStart_toEndOf="@+id/filter2Tv"
|
||||
@ -506,9 +491,7 @@
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="0dp"
|
||||
android:layout_marginStart="2dp"
|
||||
android:layout_marginEnd="6dp"
|
||||
motion:layout_constraintBottom_toBottomOf="parent"
|
||||
motion:layout_constraintEnd_toEndOf="parent"
|
||||
motion:layout_constraintStart_toEndOf="@+id/icon3Iv"
|
||||
motion:layout_constraintTop_toTopOf="parent">
|
||||
<CustomAttribute
|
||||
|
||||
Reference in New Issue
Block a user