From 8076c3a70ac8a2d094e5737f84a8ad17cb5151b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=BE=E7=A5=A5=E4=BF=8A?= Date: Mon, 28 Oct 2024 14:54:49 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20CPM=E5=BE=AE=E4=BF=A1=E5=B0=8F=E6=B8=B8?= =?UTF-8?q?=E6=88=8F=E4=B8=80=E6=9C=9F=E4=BC=98=E5=8C=96=E2=80=94=E5=AE=A2?= =?UTF-8?q?=E6=88=B7=E7=AB=AF=20https://jira.shanqu.cc/browse/GHZSCY-6828?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../gamecenter/entity/SearchSubjectEntity.kt | 8 +- .../home/custom/model/CustomPageRepository.kt | 2 +- .../MiniGameSearchResultRepository.kt | 2 +- .../wechat/WGameSubjectCPMListRepository.kt | 2 +- .../wechat/WGameSubjectCPMRemoteDataSource.kt | 45 +++++- .../search/SearchGameResultAdapter.kt | 67 ++++---- .../search/SearchGameResultRepository.kt | 2 +- .../search/SearchGameResultViewModel.kt | 153 +++++++++--------- .../feature/retrofit/WGameCPMApiService.kt | 3 + 9 files changed, 175 insertions(+), 109 deletions(-) diff --git a/app/src/main/java/com/gh/gamecenter/entity/SearchSubjectEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/SearchSubjectEntity.kt index bc5baaac12..7d1cc572a5 100644 --- a/app/src/main/java/com/gh/gamecenter/entity/SearchSubjectEntity.kt +++ b/app/src/main/java/com/gh/gamecenter/entity/SearchSubjectEntity.kt @@ -18,7 +18,13 @@ data class SearchSubjectEntity( @SerializedName("ad_icon_active") val adIconActive: Boolean = false, // 本地字段,标记是否为微信小游戏CPM专题 - var isWGameSubjectCPM: Boolean = false + var isWGameSubjectCPM: Boolean = false, + val type: String = "" ) : Parcelable { + + companion object { + const val TYPE_WECHAT_GAME_CPM_COLUMN = "wechat_game_cpm_column" + } + fun getFilterGame() = RegionSettingHelper.filterGame(games) } \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/home/custom/model/CustomPageRepository.kt b/app/src/main/java/com/gh/gamecenter/home/custom/model/CustomPageRepository.kt index 505900aadd..a311f1bab5 100644 --- a/app/src/main/java/com/gh/gamecenter/home/custom/model/CustomPageRepository.kt +++ b/app/src/main/java/com/gh/gamecenter/home/custom/model/CustomPageRepository.kt @@ -806,7 +806,7 @@ class CustomPageRepository private constructor( fun loadChangeSubjectGame(subjectEntity: SubjectEntity): Observable> = if (subjectEntity.isWechatColumnCPM) {// 微信小游戏CPM专题的“换一批”接口 - wGameSubjectCPMRemoteDataSource.getRecommendCPMList(2, 10).toObservable() + wGameSubjectCPMRemoteDataSource.getEditorRecommendCPMList(2, 10).toObservable() } else { remoteDataSource.loadChangeSubjectGame(subjectEntity) } diff --git a/app/src/main/java/com/gh/gamecenter/minigame/MiniGameSearchResultRepository.kt b/app/src/main/java/com/gh/gamecenter/minigame/MiniGameSearchResultRepository.kt index 2ee2e575c5..9bb1a924d6 100644 --- a/app/src/main/java/com/gh/gamecenter/minigame/MiniGameSearchResultRepository.kt +++ b/app/src/main/java/com/gh/gamecenter/minigame/MiniGameSearchResultRepository.kt @@ -26,6 +26,6 @@ class MiniGameSearchResultRepository( } override fun getWGameCPMGameList(): Single> { - return mWGameSubjectCPMDataSource.getRecommendCPMList(1) + return mWGameSubjectCPMDataSource.getUserRecommendCPMList() } } \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/minigame/wechat/WGameSubjectCPMListRepository.kt b/app/src/main/java/com/gh/gamecenter/minigame/wechat/WGameSubjectCPMListRepository.kt index 1c83f933fc..dc23bb2d83 100644 --- a/app/src/main/java/com/gh/gamecenter/minigame/wechat/WGameSubjectCPMListRepository.kt +++ b/app/src/main/java/com/gh/gamecenter/minigame/wechat/WGameSubjectCPMListRepository.kt @@ -11,7 +11,7 @@ class WGameSubjectCPMListRepository( ) : ISubjectListRepository { override fun getColumn(column_id: String?, page: Int, sort: String?, order: String?): Single> { - return dataSource.getRecommendCPMList(page) + return dataSource.getEditorRecommendCPMList(page) } override fun getColumnSettings(column_id: String?): Observable { diff --git a/app/src/main/java/com/gh/gamecenter/minigame/wechat/WGameSubjectCPMRemoteDataSource.kt b/app/src/main/java/com/gh/gamecenter/minigame/wechat/WGameSubjectCPMRemoteDataSource.kt index 0c6e8ee365..00ad669131 100644 --- a/app/src/main/java/com/gh/gamecenter/minigame/wechat/WGameSubjectCPMRemoteDataSource.kt +++ b/app/src/main/java/com/gh/gamecenter/minigame/wechat/WGameSubjectCPMRemoteDataSource.kt @@ -14,7 +14,50 @@ class WGameSubjectCPMRemoteDataSource( private val api: WGameCPMApiService = RetrofitManager.getInstance().wGameCPMApi ) { - fun getRecommendCPMList(page: Int, pageSize: Int = 10): Single> { + fun getEditorRecommendCPMList(page: Int, pageSize: Int = 10): Single> { + val meta = MetaUtil.getMeta() + val request = mapOf( + "head" to mapOf( + "busiAppid" to Config.WGAME_CPM_BUSIAPPID, + "oaid" to (meta.oaid ?: ""), + "manufacturer" to (meta.manufacturer ?: ""), + "mode" to (meta.model ?: ""), + "androidId" to (MetaUtil.getAndroidId()), + "imei" to (MetaUtil.getIMEI()) + ), + "body" to mapOf( + "page" to page - 1, + "pageSize" to pageSize, + ) + ) + return api.getEditorRecommendList(request.toRequestBody()) + .map { + if (it.ret == 0) { + it.appInfoList.map { info -> + GameEntity( + mName = info.appName, + mIcon = info.logo, + mBrief = info.briefIntro, + miniGameUid = info.appID, + miniGameAppId = info.userName, + miniGameType = Constants.WECHAT_MINI_GAME_CPM, + miniGameAppStatus = 2, + miniGameAppPath = info.wechatAppPath, + miniGameExtData = info.extData, + miniGameRecommendId = info.recommendID, + mTagStyle = arrayListOf( + TagStyleEntity(name = info.categoryName), + TagStyleEntity(name = info.subcategoryName) + ) + ) + }.toMutableList() + } else { + mutableListOf() + } + } + } + + fun getUserRecommendCPMList(page: Int = 1, pageSize: Int = 10): Single> { val meta = MetaUtil.getMeta() val request = mapOf( "head" to mapOf( diff --git a/app/src/main/java/com/gh/gamecenter/search/SearchGameResultAdapter.kt b/app/src/main/java/com/gh/gamecenter/search/SearchGameResultAdapter.kt index c4467320de..b64cf1d956 100644 --- a/app/src/main/java/com/gh/gamecenter/search/SearchGameResultAdapter.kt +++ b/app/src/main/java/com/gh/gamecenter/search/SearchGameResultAdapter.kt @@ -107,6 +107,7 @@ class SearchGameResultAdapter( return when { oldItem?.subject != null && newItem?.subject != null -> { oldItem.subject.columnId == newItem.subject.columnId + && oldItem.subject.games == newItem.subject.games } oldItem?.game != null && newItem?.game != null -> { @@ -121,6 +122,7 @@ class SearchGameResultAdapter( return when { oldItem?.subject != null && newItem?.subject != null -> { oldItem.subject.columnId == newItem.subject.columnId + && oldItem.subject.games == newItem.subject.games } oldItem?.game != null && newItem?.game != null -> { @@ -169,39 +171,50 @@ class SearchGameResultAdapter( override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (holder) { is SearchSubjectItemViewHolder -> { - holder.binding.run { - when { - position == 0 -> topDivider.visibility = View.GONE - position > 0 -> { - val gameEntity = mEntityList[position - 1].game - if (gameEntity != null) { - val isShowTag = gameEntity.contentTag != null - && (gameEntity.contentTag!!.custom.isNotEmpty() - || gameEntity.contentTag!!.zone.link.isNotEmpty() - || gameEntity.contentTag!!.isLibaoExists - || gameEntity.contentTag!!.server) - val isShowTagByMirror = - if (gameEntity.shouldUseMirrorInfo()) isShowTag && gameEntity.obtainMirrorData()?.contentTagStatus == "on" else isShowTag - if (isShowTagByMirror) { - topDivider.visibility = View.GONE + val itemData = mEntityList[position] + if (itemData.subject == null || itemData.subject.games.isEmpty()) { + holder.binding.topDivider.visibility = View.GONE + holder.binding.bottomDivider.visibility = View.GONE + holder.binding.headContainer.root.visibility = View.GONE + holder.binding.subjectRv.visibility = View.GONE + } else { + holder.binding.headContainer.root.visibility = View.VISIBLE + holder.binding.subjectRv.visibility = View.VISIBLE + + holder.binding.run { + when { + position == 0 -> topDivider.visibility = View.GONE + position > 0 -> { + val gameEntity = mEntityList[position - 1].game + if (gameEntity != null) { + val isShowTag = gameEntity.contentTag != null + && (gameEntity.contentTag!!.custom.isNotEmpty() + || gameEntity.contentTag!!.zone.link.isNotEmpty() + || gameEntity.contentTag!!.isLibaoExists + || gameEntity.contentTag!!.server) + val isShowTagByMirror = + if (gameEntity.shouldUseMirrorInfo()) isShowTag && gameEntity.obtainMirrorData()?.contentTagStatus == "on" else isShowTag + if (isShowTagByMirror) { + topDivider.visibility = View.GONE + } else { + topDivider.visibility = View.VISIBLE + } } else { - topDivider.visibility = View.VISIBLE + topDivider.visibility = View.GONE } - } else { - topDivider.visibility = View.GONE } } + bottomDivider.visibility = View.VISIBLE } - bottomDivider.visibility = View.VISIBLE + holder.bindSubjectItem( + mContext, + itemData, + SearchType.fromString(type).toChinese(), + key, + dao, + sourceEntrance = sourceEntrance + ) } - holder.bindSubjectItem( - mContext, - mEntityList[position], - SearchType.fromString(type).toChinese(), - key, - dao, - sourceEntrance = sourceEntrance - ) } is SearchGameFirstItemViewHolder -> { diff --git a/app/src/main/java/com/gh/gamecenter/search/SearchGameResultRepository.kt b/app/src/main/java/com/gh/gamecenter/search/SearchGameResultRepository.kt index 5812acef8c..9dbe3b208f 100644 --- a/app/src/main/java/com/gh/gamecenter/search/SearchGameResultRepository.kt +++ b/app/src/main/java/com/gh/gamecenter/search/SearchGameResultRepository.kt @@ -36,6 +36,6 @@ class SearchGameResultRepository( } override fun getWGameCPMGameList(): Single> { - return mWGameSubjectCPMDataSource.getRecommendCPMList(1) + return mWGameSubjectCPMDataSource.getUserRecommendCPMList() } } \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/search/SearchGameResultViewModel.kt b/app/src/main/java/com/gh/gamecenter/search/SearchGameResultViewModel.kt index 4f23b81793..c67199c19a 100644 --- a/app/src/main/java/com/gh/gamecenter/search/SearchGameResultViewModel.kt +++ b/app/src/main/java/com/gh/gamecenter/search/SearchGameResultViewModel.kt @@ -76,40 +76,39 @@ class SearchGameResultViewModel( refreshWrongInstallStatus() repository.getSearchSubject(mSearchKey, mPage) - .map { dataList -> - mSearchSubjects.addAll(dataList) - var cpmSearchSubject: SearchSubjectEntity? = null - mSearchSubjects.forEach { - // 微信小游戏CPM专题需要等搜索广告位插入完成后再插入 - if (it.location == WGAME_CPM_SUBJECT_POSITION) { - cpmSearchSubject = it.apply { isWGameSubjectCPM = true } - } else { - val item = SearchItemData(subject = it) - if (it.location <= 0 || it.location > itemDataList.size) { - itemDataList.add(item) - } else { - itemDataList.add(it.location - 1, item) - } - } - } - cpmSearchSubject to dataList - } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ result -> + .subscribe({ mutableList -> + val cpmSearchSubjects = mutableListOf() + mSearchSubjects.addAll(mutableList) + mSearchSubjects.forEach { + if (it.type == SearchSubjectEntity.TYPE_WECHAT_GAME_CPM_COLUMN) { + cpmSearchSubjects.add(it.apply { isWGameSubjectCPM = 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, result.first) + updateAdConfigAndDecorateList(itemDataList, list) } else { AdDelegateHelper.requestAdConfig(false, mSearchKey ?: "") { - updateAdConfigAndDecorateList(itemDataList, list, result.first) + updateAdConfigAndDecorateList(itemDataList, list) } } } else { - decorateWithWGameSubjectCPM(itemDataList, list, result.first) + postResultList(itemDataList, list) + } + + if (cpmSearchSubjects.isNotEmpty()) { + decorateWithWGameCPMList(cpmSearchSubjects, itemDataList, list) } }, { it.printStackTrace() @@ -118,12 +117,51 @@ class SearchGameResultViewModel( }) } + /** + * 请求微信小游戏CPM接口获取专题游戏数据,并插入对应的专题项中,相关需求如下: + * @see 【光环助手】CPM微信小游戏API接入工作 + * @see 【光环助手】CPM微信小游戏一期优化 + */ @SuppressLint("CheckResult") - private fun updateAdConfigAndDecorateList( + private fun decorateWithWGameCPMList( + subjects: List, itemDataList: ArrayList, - list: MutableList, - cpmSubjectEntity: SearchSubjectEntity? + list: MutableList ) { + val subjectList = subjects.filterNot { + it.games.isNotEmpty() + } + if (subjectList.isEmpty()) return + val subjectSingleList = subjectList.map { subject -> + repository.getWGameCPMGameList() + .onErrorReturnItem(mutableListOf()) + .map { subject.columnId to it } + } + Single.zip(subjectSingleList) { it.map { item -> item as Pair> } } + .map { dataList -> + val decoratedItemDataList = mResultLiveData.value?.toArrayList() ?: itemDataList + for (index in decoratedItemDataList.indices) { + val itemData = decoratedItemDataList[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) + ) + decoratedItemDataList[index] = newItemData + } + decoratedItemDataList + } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ dataList -> postResultList(dataList, list) }, {}) + + } + + @SuppressLint("CheckResult") + private fun updateAdConfigAndDecorateList(itemDataList: ArrayList, list: MutableList) { mGameSearchAdList = AdDelegateHelper.getGameSearchAdList().filter { AdDelegateHelper.shouldShowGameSearchAd(it) }.toArrayList() .apply { sortBy { it.position } } @@ -137,7 +175,6 @@ class SearchGameResultViewModel( mAdPositionSet = adPositionSet if (adPositionSet.isNotEmpty()) { - val decoratedItemDataList = ArrayList(itemDataList) val ownerAdList = arrayListOf() val thirdPartyAdList = arrayListOf() @@ -175,18 +212,18 @@ class SearchGameResultViewModel( Single.zip(requestSingleList) {} .compose(singleToMain()) .subscribe({ - decorateListWithAd(itemDataList, decoratedItemDataList, list, cpmSubjectEntity) + decorateListWithAd(itemDataList, list) }, { - decorateListWithAd(itemDataList, decoratedItemDataList, list, cpmSubjectEntity) + decorateListWithAd(itemDataList, list) }) } else { - decorateListWithAd(itemDataList, decoratedItemDataList, list, cpmSubjectEntity) + decorateListWithAd(itemDataList, list) } } else { - decorateListWithThirdPartyAdOnly(decoratedItemDataList, thirdPartyAdList, list, cpmSubjectEntity) + decorateListWithThirdPartyAdOnly(itemDataList, thirdPartyAdList, list) } } else { - decorateWithWGameSubjectCPM(itemDataList, list, cpmSubjectEntity) + postResultList(itemDataList, list) } } @@ -197,27 +234,26 @@ class SearchGameResultViewModel( } private fun decorateListWithThirdPartyAdOnly( - decoratedItemDataList: ArrayList, + itemDataList: ArrayList, thirdPartyAdList: List, - list: List, - cpmSubjectEntity: SearchSubjectEntity? + list: List ) { thirdPartyAdList.forEach { - decoratedItemDataList.add(it.position - 1, SearchItemData(ad = it.thirdPartyAd, adConfig = it)) + 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()) } - decorateWithWGameSubjectCPM(decoratedItemDataList, list, cpmSubjectEntity) + postResultList(itemDataList, list) } private fun decorateListWithAd( itemDataList: ArrayList, - decoratedItemDataList: ArrayList, - list: List, - cpmSubjectEntity: SearchSubjectEntity? + list: List ) { val adGameOneIdSet = HashSet() // 展示样式为单个游戏时记录游戏ID,避免重复 + val decoratedItemDataList = mResultLiveData.value?.toArrayList() ?: itemDataList + val decoratedItemDataSize = decoratedItemDataList.size for ((index, position) in mAdPositionSet!!.withIndex()) { - if (position < itemDataList.size + index + 1) { + 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 @@ -281,41 +317,7 @@ class SearchGameResultViewModel( break } } - decorateWithWGameSubjectCPM(decoratedItemDataList, list, cpmSubjectEntity) - } - - @SuppressLint("CheckResult") - private fun decorateWithWGameSubjectCPM( - resultList: ArrayList, - list: List, - cpmSubjectEntity: SearchSubjectEntity? - ) { - // 微信小游戏CPM专题搜索结果存在,则请求CPM接口获取微信小游戏列表数据,并将列表数据插入缓存的CPM专题中, - // 再根据location的值(固定为4)将CPM专题插入搜索结果列表中 - // 相关需求:https://jira.shanqu.cc/browse/GHZSCY-6710 - cpmSubjectEntity?.let { subject -> - if (subject.games.isNotEmpty()) { - Single.just(subject.games.toMutableList()) - } else { - repository.getWGameCPMGameList() - .onErrorReturnItem(mutableListOf()) - } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe( - { - val cpmSearchItemData = SearchItemData(subject = subject.apply { games = it }) - if (subject.location <= 0 || subject.location > resultList.size) { - resultList.add(cpmSearchItemData) - } else { - resultList.add(subject.location - 1, cpmSearchItemData) - } - postResultList(resultList, list) - }, - { - postResultList(resultList, list) - }) - } ?: postResultList(resultList, list) + postResultList(decoratedItemDataList, list) } @SuppressLint("CheckResult") @@ -431,7 +433,6 @@ class SearchGameResultViewModel( companion object { const val AD_SUBJECT_GAME_MAX_COUNT = 8 - const val WGAME_CPM_SUBJECT_POSITION = 4 } } \ No newline at end of file diff --git a/module_core_feature/src/main/java/com/gh/gamecenter/feature/retrofit/WGameCPMApiService.kt b/module_core_feature/src/main/java/com/gh/gamecenter/feature/retrofit/WGameCPMApiService.kt index d5eee0606c..c794e67c3a 100644 --- a/module_core_feature/src/main/java/com/gh/gamecenter/feature/retrofit/WGameCPMApiService.kt +++ b/module_core_feature/src/main/java/com/gh/gamecenter/feature/retrofit/WGameCPMApiService.kt @@ -9,6 +9,9 @@ import retrofit2.http.POST interface WGameCPMApiService { + @POST("geteditorrecommend") + fun getEditorRecommendList(@Body body: RequestBody): Single + @POST("getuserrecommend") fun getUserRecommendList(@Body body: RequestBody): Single