Compare commits

..

2 Commits

Author SHA1 Message Date
0a9b49c330 fix: va_core npe导致的32位游戏闪退 2024-08-09 17:02:15 +08:00
5b212bec2a chore: 组件升级 2.0.5-debug 2024-08-09 14:08:36 +08:00
115 changed files with 3398 additions and 1089 deletions

View File

@ -72,7 +72,6 @@ android_build:
only:
- dev
- release
- feat/GHZSCY-6644
# 代码检查
sonarqube_analysis:
@ -157,5 +156,4 @@ oss-upload&send-email:
- /usr/local/bin/python /ci-android-mail-jira-comment.py
only:
- dev
- release
- feat/GHZSCY-6644
- release

View File

@ -333,6 +333,14 @@
android:screenOrientation="portrait"
android:windowSoftInputMode="stateHidden" />
<activity
android:name=".category.CategoryDirectoryActivity"
android:screenOrientation="portrait" />
<activity
android:name=".category.CategoryListActivity"
android:screenOrientation="portrait" />
<activity
android:name="com.gh.gamecenter.UserInfoActivity"
android:screenOrientation="portrait" />
@ -554,6 +562,14 @@
android:name=".simulatorgame.SimulatorManagementActivity"
android:screenOrientation="portrait" />
<activity
android:name=".catalog.CatalogActivity"
android:screenOrientation="portrait" />
<activity
android:name=".catalog.NewCatalogListActivity"
android:screenOrientation="portrait" />
<activity
android:name=".forum.search.ForumOrUserSearchActivity"
android:screenOrientation="portrait" />

View File

@ -543,14 +543,15 @@ object AdDelegateHelper {
), null, null
)
adImage.visibleIf(true)
ImageUtils.display(adImage, ad.img)
if (ad.isImageType) {
adImage.visibleIf(true)
adVideo.visibleIf(false)
ImageUtils.display(adImage, ad.img)
} else {
adImage.visibleIf(false)
adVideo.visibleIf(true)
adVideo.startPlay(ad.video.url)
adVideo.startPlay(ad.img, ad.video.url)
}
startAdContainer.setOnClickListener {
// 拦截点击事件传递

View File

@ -1,11 +1,12 @@
package com.gh.ad
import android.app.Activity
import android.content.Context
import android.util.AttributeSet
import android.view.WindowManager
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.video.detail.CustomManager
import com.shuyu.gsyvideoplayer.utils.Debuger
import com.shuyu.gsyvideoplayer.utils.GSYVideoType
import com.shuyu.gsyvideoplayer.utils.GSYVideoType.SCREEN_TYPE_FULL
import com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer
@ -17,14 +18,18 @@ class SplashAdVideoView @JvmOverloads constructor(
) :
StandardGSYVideoPlayer(context, attrs) {
fun startPlay(url: String) {
fun startPlay(cover: String, url: String) {
GSYVideoType.setShowType(SCREEN_TYPE_FULL)
GSYVideoType.setRenderType(GSYVideoType.SUFRACE)
CustomManager.getCustomManager(getKey()).isNeedMute = true
setUp(url, true, "")
val ivCover = findViewById<SimpleDraweeView>(R.id.thumbImage)
ImageUtils.display(ivCover, cover)
setNeedAutoAdaptation(false)
isLooping = true
startPlayLogic()
}
@ -44,31 +49,6 @@ class SplashAdVideoView @JvmOverloads constructor(
return R.layout.layout_splash_ad_video
}
override fun onAutoCompletion() {
setStateAndUi(CURRENT_STATE_AUTO_COMPLETE);
mSaveChangeViewTIme = 0
mCurrentPosition = 0
if (!mIfCurrentIsFullscreen) {
getGSYVideoManager().setLastListener(null)
}
mAudioManager.abandonAudioFocus(onAudioFocusChangeListener);
if (mContext is Activity) {
try {
(mContext as Activity).getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} catch (e: Exception) {
e.printStackTrace()
}
}
releaseNetWorkState()
if (mVideoAllCallBack != null && isCurrentMediaListener()) {
mVideoAllCallBack.onAutoComplete(mOriginUrl, mTitle, this)
}
mHadPlay = false
}
fun clearAll() {
GSYVideoType.setShowType(GSYVideoType.SCREEN_TYPE_DEFAULT)
GSYVideoType.setRenderType(GSYVideoType.TEXTURE)

View File

@ -268,6 +268,10 @@ object DefaultUrlHandler {
directToGameVideo(context, id, entrance, "")
}
EntranceConsts.HOST_CATEGORY -> {
val title = uri.getQueryParameter("title")
DirectUtils.directCategoryDirectory(context, id, title ?: "", entrance, "")
}
EntranceConsts.HOST_COLUMN_COLLECTION -> {
val name = uri.getQueryParameter("name")
DirectUtils.directToColumnCollection(context, id, -1, entrance, name ?: "")

View File

@ -1,16 +0,0 @@
package com.gh.common.prioritychain
import com.gh.gamecenter.home.video.ScrollCalculatorHelper
class VideoHandler(priority: Int, val scrollCalculatorHelper: ScrollCalculatorHelper): PriorityChainHandler(priority) {
init {
updateStatus(STATUS_VALID)
}
override fun onProcess(): Boolean {
scrollCalculatorHelper.enableAndPlayIfValid()
return true
}
}

View File

@ -2,15 +2,12 @@ package com.gh.common.provider
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import androidx.fragment.app.FragmentActivity
import com.alibaba.android.arouter.facade.annotation.Route
import com.gh.common.util.EntranceUtils
import com.gh.gamecenter.common.avoidcallback.Callback
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.core.provider.IEntranceUtilsProvider
import com.lightgame.utils.AppManager
@Route(path = RouteConsts.provider.entranceUtils, name = "EntranceUtils暴露服务")
class EntranceUtilsProviderImpl : IEntranceUtilsProvider {
@ -23,16 +20,11 @@ class EntranceUtilsProviderImpl : IEntranceUtilsProvider {
}
override fun jumpActivityWithCallback(context: Context, bundle: Bundle, callback: () -> Unit) {
if (context is FragmentActivity && !context.supportFragmentManager.isDestroyed) {
EntranceUtils.jumpActivity(context, null, bundle, object : Callback {
override fun onActivityResult(resultCode: Int, data: Intent?) {
callback()
}
})
} else {
EntranceUtils.jumpActivity(AppManager.getInstance().currentActivity(), bundle)
}
EntranceUtils.jumpActivity(context, null, bundle, object : Callback {
override fun onActivityResult(resultCode: Int, data: Intent?) {
callback()
}
})
}
override fun init(context: Context?) {

View File

@ -19,6 +19,8 @@ import com.gh.common.util.EntranceUtils.jumpActivity
import com.gh.common.util.EntranceUtils.jumpActivityCompat
import com.gh.gamecenter.*
import com.gh.gamecenter.amway.AmwayActivity
import com.gh.gamecenter.catalog.CatalogActivity
import com.gh.gamecenter.category.CategoryDirectoryActivity
import com.gh.gamecenter.category2.CategoryV2Activity
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.base.activity.BaseActivity_TabLayout
@ -149,12 +151,14 @@ object DirectUtils {
"qqqun",
"tag",
"all_community_article",
"category",
"block",
"column_collection",
"server",
"top_game_comment",
"wechat_bind",
"video",
"catalog",
"category_v2",
"common_collection",
"game_list",
@ -281,6 +285,10 @@ object DirectUtils {
)
)
"category", "分类" -> directCategoryDirectory(context, linkEntity.link!!, linkEntity.text!!)
"catalog" -> directCatalog(context, linkEntity.link!!, linkEntity.text!!, entrance, path)
"category_v2" -> directCategoryV2(
context,
linkEntity.link!!,
@ -1415,6 +1423,47 @@ object DirectUtils {
}
}
/**
* 跳转分类
*/
@JvmStatic
fun directCategoryDirectory(
context: Context,
categoryId: String,
categoryTitle: String,
entrance: String? = null,
path: String? = ""
) {
if (categoryId.isEmpty()) return
val bundle = Bundle()
bundle.putString(KEY_TO, CategoryDirectoryActivity::class.java.name)
bundle.putString(KEY_CATEGORY_ID, categoryId)
bundle.putString(KEY_CATEGORY_TITLE, categoryTitle)
bundle.putString(KEY_ENTRANCE, entrance ?: ENTRANCE_BROWSER)
bundle.putString(KEY_PATH, path)
jumpActivity(context, bundle)
}
/**
* 跳转新分类
*/
@JvmStatic
fun directCatalog(
context: Context,
catalogId: String,
catalogTitle: String,
entrance: String? = null,
path: String? = "",
) {
if (catalogId.isEmpty()) return
val bundle = Bundle()
bundle.putString(KEY_TO, CatalogActivity::class.java.name)
bundle.putString(KEY_CATALOG_ID, catalogId)
bundle.putString(KEY_CATALOG_TITLE, catalogTitle)
bundle.putString(KEY_ENTRANCE, BaseActivity.mergeEntranceAndPath(entrance, path))
jumpActivity(context, bundle)
}
/**
* 跳转新分类2.0
*/

View File

@ -37,10 +37,10 @@ import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.PluginLocation
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.minigame.MiniGameItemHelper
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.dialog.GamePermissionDialogFragment
import com.gh.gamecenter.manager.PackagesManager
import com.gh.gamecenter.feature.minigame.MiniGameItemHelper
import com.gh.gamecenter.teenagermode.TeenagerModeActivity
import com.gh.vspace.VHelper
import com.lightgame.download.DownloadConfig
@ -697,7 +697,7 @@ object DownloadItemUtils {
// 为 downloadButton 添加游戏实体,供点击的时候上报用
downloadBtn.putObject(gameEntity)
val gamePermissionDialogFragment = (context as? AppCompatActivity)?.supportFragmentManager?.findFragmentByTag(
val gamePermissionDialogFragment = (context as AppCompatActivity).supportFragmentManager.findFragmentByTag(
GamePermissionDialogFragment::class.java.name
) as GamePermissionDialogFragment?
gamePermissionDialogFragment?.dismissAllowingStateLoss()

View File

@ -105,14 +105,9 @@ public class EntranceUtils {
//TODO:添加FLAG_ACTIVITY_NEW_TASK会导致一跳转页面callback就被调用
//intent1.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent1.putExtras(bundle);
if (context instanceof AppCompatActivity) {
AvoidOnResultManager.Companion.getInstance((AppCompatActivity) context)
.startForResult(intent1, callback);
} else {
// 不要回调,正常跳转
context.startActivity(intent1);
}
AvoidOnResultManager.Companion.getInstance((AppCompatActivity) context)
.startForResult(intent1, callback);
}
} else {
// 应用未在运行

View File

@ -7,7 +7,6 @@ import com.gh.gamecenter.common.utils.toObject
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.entity.SubjectEntity
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.manager.PackagesManager
import com.gh.gamecenter.retrofit.RetrofitManager
import com.halo.assistant.HaloApp
import io.reactivex.schedulers.Schedulers
@ -112,14 +111,6 @@ object GameSubstituteRepositoryHelper {
}
}
// 检查是否已安装该游戏 id
if (PackagesManager.getInstalledDataByGameId(game.id) != null) {
// 将该位置的游戏标记为需要替换
positionOfGameToBeReplacedList.add(index)
thisPositionNeedToBeReplaced = true
break
}
// 若此游戏所包含的 apk 没有已安装,那么再检查是否已安装有预设相关包名
if (!thisPositionNeedToBeReplaced) {
var relatedPackageList = arrayListOf<String>()

View File

@ -308,7 +308,7 @@ object GameUtils {
}
/**
* 是否默认以畅玩游戏的形式来处理
* 是否以畅玩游戏的形式来处理
*/
@JvmStatic
fun shouldPerformAsVGame(gameEntity: GameEntity): Boolean {

View File

@ -57,10 +57,6 @@ object HomeBottomBarHelper {
return BottomTab(name = "我的光环", jsCode = animationCode, iconSelector = R.drawable.selector_ic_user, link = LinkEntity(type = TYPE_MY_HALO))
}
fun isDefaultHomeBottomTabDataExist(): Boolean {
return SPUtils.getString(KEY_HOME_BOTTOM_TAB).isNotEmpty()
}
@JvmStatic
fun getDefaultHomeBottomTabData(): List<BottomTab> {
try {

View File

@ -734,6 +734,18 @@ public class LogUtils {
LoghubUtils.log(object, LOG_STORE_EVENT, false);
}
public static void logNewCatalogAppearanceEvent(String entrance, String key) {
logCatalogEvent("access_to_classification", entrance, key, -1, -1, -1, -1);
}
public static void logSubCatalogClickEvent(String entrance, String key, int seq1) {
logCatalogEvent("click_first_classification", entrance, key, seq1, -1, -1, -1);
}
public static void logSubCatalogContentClickEvent(String entrance, String key, int seq1, int seq2) {
logCatalogEvent("click_secondary_classification", entrance, key, seq1, seq2, -1, -1);
}
public static void logSpecialCatalogContentClickEvent(String entrance, String key, int seq1, int seqContent) {
logCatalogEvent("click_content", entrance, key, seq1, -1, seqContent, -1);
}

View File

@ -90,7 +90,7 @@ object PackageChangeHelper : DefaultLifecycleObserver {
pendingPackageTriple = null
pendingGhId = null
PackageRepository.addInstalledGame(PackageRepository.packageFilterManager, packageName)
PackageRepository.addInstalledGame(packageName)
// 添加到额外的包名白名单,下次启动同时查询此包的安装情况
PackageHelper.updateAdditionalWhiteListPackageName(packageName = packageName, isAdd = true)

View File

@ -81,28 +81,23 @@ object PackageHelper {
// 评论黑名单包名列表,避免用户安装了 Xposed Installer 这样的工具,也能在包含该安装包的游戏详情页评论
private var _commentPackageNameBlackList = arrayListOf<String>()
val commentPackageNameBlackList: ArrayList<String>
get() = _commentPackageNameBlackList
val commentPackageNameBlackList: ArrayList<String> = _commentPackageNameBlackList
// 关闭下载的包列表
private var _downloadPackageNameBlackList = arrayListOf<String>()
val downloadPackageNameBlackList: ArrayList<String>
get() = _downloadPackageNameBlackList
val downloadPackageNameBlackList: ArrayList<String> = _downloadPackageNameBlackList
// 本地已安装的包去掉关闭下载的包后的列表
private var _validLocalPackageNameSet = hashSetOf<String>()
val validLocalPackageNameSet: HashSet<String>
get() = _validLocalPackageNameSet
val validLocalPackageNameSet: HashSet<String> = _validLocalPackageNameSet
// 游戏包名匹配列表
private var _relatedPackageList = arrayListOf<SettingsEntity.GameWithPackages>()
val relatedPackageList: ArrayList<SettingsEntity.GameWithPackages>
get() = _relatedPackageList
val relatedPackageList: ArrayList<SettingsEntity.GameWithPackages> = _relatedPackageList
// 接口控制的已安装应用列表获取开关状态 (UI 显示)
private var _installedPackageApiSwitchStatusLiveData = MutableLiveData<Boolean>()
val installedPackageApiSwitchStatusLiveData: LiveData<Boolean>
get() = _installedPackageApiSwitchStatusLiveData
val installedPackageApiSwitchStatusLiveData: LiveData<Boolean> = _installedPackageApiSwitchStatusLiveData
// 本地已安装包的列表
var localPackageNameSet = hashSetOf<String>()
@ -546,7 +541,6 @@ object PackageHelper {
Utils.log(TAG, "addInstalledButMissingPackages 需要请求接口获取的包数量为 ${installedPackageNameSet.size}")
PackageRepository.addInstalledGames(
packageFilterManager = PackageRepository.packageFilterManager,
pkgNameList = ArrayList(installedPackageNameSet),
updateInstallStatus = true
)

View File

@ -14,7 +14,6 @@ import android.os.Build;
import android.os.Bundle;
import android.os.PowerManager;
import android.text.TextUtils;
import android.util.AndroidException;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@ -223,19 +222,8 @@ public class PackageUtils {
if (metaDate != null) {
return metaDate.get(name);
}
} catch (Exception e) {
e.printStackTrace();
if (e instanceof AndroidException) {
// 有些设备会出现 DeadSystemException
SentryHelper.INSTANCE.onEvent(
"GET_META_DATA_ERROR",
"packageName",
packageName,
"exception_digest",
e.getLocalizedMessage()
);
}
} catch (NameNotFoundException e) {
// e.printStackTrace();
}
return null;
}
@ -639,19 +627,8 @@ public class PackageUtils {
try {
return HaloApp.getInstance().getApplication().getPackageManager().getPackageInfo(packageName,
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT).versionName;
} catch (Exception e) {
e.printStackTrace();
if (e instanceof AndroidException) {
// 有些设备会出现 DeadSystemException
SentryHelper.INSTANCE.onEvent(
"GET_VERSION_NAME_ERROR",
"packageName",
packageName,
"exception_digest",
e.getLocalizedMessage()
);
}
} catch (NameNotFoundException e) {
// do nothing
}
return null;
}
@ -663,18 +640,8 @@ public class PackageUtils {
try {
return HaloApp.getInstance().getApplication().getPackageManager().getPackageInfo(packageName,
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT).versionCode;
} catch (Exception e) {
e.printStackTrace();
if (e instanceof AndroidException) {
// 有些设备会出现 DeadSystemException
SentryHelper.INSTANCE.onEvent(
"GET_VERSION_CODE_ERROR",
"packageName",
packageName,
"exception_digest",
e.getLocalizedMessage()
);
}
} catch (NameNotFoundException e) {
// do nothing
}
return 0;
}
@ -687,18 +654,8 @@ public class PackageUtils {
try {
PackageManager packageManager = context.getApplicationContext().getPackageManager();
return packageManager.getApplicationIcon(packageName);
} catch (Exception e) {
e.printStackTrace();
if (e instanceof AndroidException) {
// 有些设备会出现 DeadSystemException
SentryHelper.INSTANCE.onEvent(
"GET_ICON_ERROR",
"packageName",
packageName,
"exception_digest",
e.getLocalizedMessage()
);
}
} catch (NameNotFoundException e) {
// do nothing
}
return null;
}
@ -749,19 +706,8 @@ public class PackageUtils {
jsonObject.put("version", packageInfo.versionName);
}
return jsonObject;
} catch (Exception e) {
} catch (JSONException | NameNotFoundException e) {
e.printStackTrace();
if (e instanceof AndroidException) {
// 有些设备会出现 DeadSystemException
SentryHelper.INSTANCE.onEvent(
"GET_APP_BASIC_INFO_BY_PACKAGE_NAME",
"packageName",
packageName,
"exception_digest",
e.getLocalizedMessage()
);
}
return jsonObject;
}
}

View File

@ -0,0 +1,331 @@
package com.gh.common.view
import android.content.Context
import android.graphics.Color
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.PopupWindow
import android.widget.TextView
import androidx.annotation.ColorInt
import androidx.core.content.ContextCompat
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.setDrawableEnd
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.visibleIf
import com.gh.gamecenter.entity.CatalogEntity
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.google.android.flexbox.FlexboxLayout
class CatalogFilterView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
private var mTypeTv: TextView
private var mCatalogTv: TextView
private var mSizeTv: TextView
private var mTypeContainer: View
private var mCatalogContainer: View
private var mSizeContainer: View
private var mTypeFilterArray = ArrayList<SortType>()
private var mCatalogFilterArray = ArrayList<CatalogEntity.SubCatalogEntity>()
private var sizeFilterArray: ArrayList<SubjectSettingEntity.Size>? = null
private var mOnCatalogFilterSetupListener: OnCatalogFilterSetupListener? = null
private var mTypePopupWindow: PopupWindow? = null
private var mCatalogPopupWindow: PopupWindow? = null
private var mSizePopupWindow: PopupWindow? = null
init {
View.inflate(context, R.layout.layout_catalog_filter, this)
mTypeTv = findViewById(R.id.type_tv)
mCatalogTv = findViewById(R.id.catalog_tv)
mSizeTv = findViewById(R.id.size_tv)
mTypeContainer = findViewById(R.id.container_type)
mCatalogContainer = findViewById(R.id.container_catalog)
mSizeContainer = findViewById(R.id.container_size)
mTypeContainer.setOnClickListener {
showSelectTypePopupWindow(this, mTypeTv, mTypeTv.text.toString())
}
mCatalogContainer.setOnClickListener {
showSelectCatalogPopupWindow(this, mCatalogTv, mCatalogTv.text.toString())
}
mSizeContainer.setOnClickListener {
showSelectSizePopupWindow(this, mSizeTv, mSizeTv.text.toString())
}
}
fun setTypeList(switch: CatalogEntity.CatalogSwitch) {
switch.run {
if ("on" == hotSort) mTypeFilterArray.add(SortType.RECOMMENDED)
if ("on" == newSort) mTypeFilterArray.add(SortType.NEWEST)
if ("on" == starSort) mTypeFilterArray.add(SortType.RATING)
}
if (mTypeFilterArray.isNotEmpty()) mTypeTv.text = mTypeFilterArray[0].value
}
fun setCatalogList(subCatalogList: List<CatalogEntity.SubCatalogEntity>, initCatalogName: String) {
mCatalogFilterArray = ArrayList(subCatalogList)
mCatalogTv.text = initCatalogName
}
fun setOnConfigSetupListener(onCatalogFilterSetupListener: OnCatalogFilterSetupListener) {
mOnCatalogFilterSetupListener = onCatalogFilterSetupListener
}
private fun toggleHighlightedTextView(targetTextView: TextView, highlightIt: Boolean) {
if (highlightIt) {
targetTextView.background = ContextCompat.getDrawable(targetTextView.context, R.drawable.bg_tag_text)
targetTextView.setTextColor(Color.WHITE)
} else {
targetTextView.background = null
targetTextView.setTextColor(R.color.text_757575.toColor(context))
}
}
private fun showSelectTypePopupWindow(containerView: View, typeTv: TextView, typeText: String) {
typeTv.setTextColor(R.color.text_theme.toColor(context))
typeTv.setDrawableEnd(R.drawable.ic_filter_arrow_up)
val inflater = LayoutInflater.from(typeTv.context)
val layout = inflater.inflate(R.layout.layout_filter_size, null)
val popupWindow = PopupWindow(
layout,
LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT
).apply { mTypePopupWindow = this }
val flexboxLayout = layout.findViewById<FlexboxLayout>(R.id.flexbox)
val backgroundView = layout.findViewById<View>(R.id.background)
backgroundView.setOnClickListener {
popupWindow.dismiss()
}
for (type in mTypeFilterArray) {
val item = inflater.inflate(R.layout.item_filter_size, flexboxLayout, false)
// 单列 3 个,强行设置宽度为屏幕的 1/3
val width = typeTv.context.resources.displayMetrics.widthPixels / 3
val height = item.layoutParams.height
item.layoutParams = ViewGroup.LayoutParams(width, height)
flexboxLayout.addView(item)
val tv = item.findViewById<TextView>(R.id.size_tv)
tv.text = type.value
toggleHighlightedTextView(tv, typeText == type.value)
tv.tag = type.value
item.setOnClickListener {
toggleHighlightedTextView(tv, true)
popupWindow.dismiss()
typeTv.text = type.value
mOnCatalogFilterSetupListener?.onSetupSortType(type)
}
}
popupWindow.setOnDismissListener {
typeTv.setTextColor(R.color.text_757575.toColor(context))
typeTv.setDrawableEnd(R.drawable.ic_filter_arrow_down)
mTypePopupWindow = null
}
popupWindow.isTouchable = true
popupWindow.isFocusable = true
popupWindow.animationStyle = 0
popupWindow.showAsDropDown(containerView, 0, 0)
}
private fun showSelectCatalogPopupWindow(containerView: View, catalogTv: TextView, catalogText: String) {
catalogTv.setTextColor(R.color.text_theme.toColor(context))
catalogTv.setDrawableEnd(R.drawable.ic_filter_arrow_up)
val inflater = LayoutInflater.from(catalogTv.context)
val layout = inflater.inflate(R.layout.layout_filter_size, null)
val popupWindow = PopupWindow(
layout,
LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT
).apply { mCatalogPopupWindow = this }
val flexboxLayout = layout.findViewById<FlexboxLayout>(R.id.flexbox)
val backgroundView = layout.findViewById<View>(R.id.background)
backgroundView.setOnClickListener {
popupWindow.dismiss()
}
for (entity in mCatalogFilterArray) {
val item = inflater.inflate(R.layout.item_filter_size, flexboxLayout, false)
// 单列 3 个,强行设置宽度为屏幕的 1/3
val width = catalogTv.context.resources.displayMetrics.widthPixels / 3
val height = item.layoutParams.height
item.layoutParams = ViewGroup.LayoutParams(width, height)
flexboxLayout.addView(item)
val tv = item.findViewById<TextView>(R.id.size_tv)
val iv = item.findViewById<ImageView>(R.id.recommend_iv)
tv.text = entity.name
iv.visibleIf(entity.recommended)
toggleHighlightedTextView(tv, catalogText == entity.name)
tv.tag = entity.name
item.setOnClickListener {
toggleHighlightedTextView(tv, true)
popupWindow.dismiss()
catalogTv.text = entity.name
mOnCatalogFilterSetupListener?.onSetupSortCatalog(entity)
}
}
popupWindow.setOnDismissListener {
catalogTv.setTextColor(R.color.text_757575.toColor(context))
catalogTv.setDrawableEnd(R.drawable.ic_filter_arrow_down)
mCatalogPopupWindow = null
}
popupWindow.isTouchable = true
popupWindow.isFocusable = true
popupWindow.animationStyle = 0
popupWindow.showAsDropDown(containerView, 0, 0)
}
private fun showSelectSizePopupWindow(containerView: View, sizeTv: TextView, sizeText: String) {
sizeTv.setTextColor(R.color.text_theme.toColor(context))
sizeTv.setDrawableEnd(R.drawable.ic_filter_arrow_up)
val inflater = LayoutInflater.from(sizeTv.context)
val layout = inflater.inflate(R.layout.layout_filter_size, null)
val popupWindow = PopupWindow(
layout,
LayoutParams.MATCH_PARENT,
LayoutParams.WRAP_CONTENT
).apply { mSizePopupWindow = this }
val flexboxLayout = layout.findViewById<FlexboxLayout>(R.id.flexbox)
val backgroundView = layout.findViewById<View>(R.id.background)
sizeFilterArray = if (sizeFilterArray == null) {
getDefaultSizeFilterArray()
} else {
sizeFilterArray?.apply {
if (firstOrNull()?.text != "全部大小") {
add(0, SubjectSettingEntity.Size(min = -1, max = -1, text = "全部大小"))
}
}
}
backgroundView.setOnClickListener {
popupWindow.dismiss()
}
for (size in sizeFilterArray!!) {
val item = inflater.inflate(R.layout.item_filter_size, flexboxLayout, false)
// 单列 3 个,强行设置宽度为屏幕的 1/3
val width = sizeTv.context.resources.displayMetrics.widthPixels / 3
val height = item.layoutParams.height
item.layoutParams = ViewGroup.LayoutParams(width, height)
flexboxLayout.addView(item)
val tv = item.findViewById<TextView>(R.id.size_tv)
tv.text = size.text
toggleHighlightedTextView(tv, sizeText == size.text)
tv.tag = size.text
item.setOnClickListener {
toggleHighlightedTextView(tv, true)
popupWindow.dismiss()
sizeTv.text = size.text
mOnCatalogFilterSetupListener?.onSetupSortSize(size)
}
}
popupWindow.setOnDismissListener {
sizeTv.setTextColor(R.color.text_757575.toColor(context))
sizeTv.setDrawableEnd(R.drawable.ic_filter_arrow_down)
mSizePopupWindow = null
}
popupWindow.isTouchable = true
popupWindow.isFocusable = true
popupWindow.animationStyle = 0
popupWindow.showAsDropDown(containerView, 0, 0)
}
private fun getDefaultSizeFilterArray(): ArrayList<SubjectSettingEntity.Size> {
return arrayListOf<SubjectSettingEntity.Size>().apply {
add(SubjectSettingEntity.Size(min = -1, max = -1, text = "全部大小"))
add(SubjectSettingEntity.Size(min = -1, max = 100, text = "100M以下"))
add(SubjectSettingEntity.Size(min = 100, max = 300, text = "100-300M"))
add(SubjectSettingEntity.Size(min = 300, max = 500, text = "300-500M"))
add(SubjectSettingEntity.Size(min = 500, max = 1000, text = "500M-1G"))
add(SubjectSettingEntity.Size(min = 1000, max = -1, text = "1G以上"))
}
}
fun setRootBackgroundColor(@ColorInt color: Int) {
findViewById<View>(R.id.config_controller).setBackgroundColor(color)
}
fun setItemTextColor(@ColorInt color: Int) {
mTypeTv.setTextColor(color)
mCatalogTv.setTextColor(color)
mSizeTv.setTextColor(color)
}
fun updatePopupWindow() {
when {
mTypePopupWindow != null && mTypePopupWindow!!.isShowing -> {
mTypePopupWindow?.dismiss()
showSelectTypePopupWindow(this, mTypeTv, mTypeTv.text.toString())
}
mCatalogPopupWindow != null && mCatalogPopupWindow!!.isShowing -> {
mCatalogPopupWindow?.dismiss()
showSelectCatalogPopupWindow(this, mCatalogTv, mCatalogTv.text.toString())
}
mSizePopupWindow != null && mSizePopupWindow!!.isShowing -> {
mSizePopupWindow?.dismiss()
showSelectSizePopupWindow(this, mSizeTv, mSizeTv.text.toString())
}
}
}
interface OnCatalogFilterSetupListener {
fun onSetupSortSize(sortSize: SubjectSettingEntity.Size)
fun onSetupSortType(sortType: SortType)
fun onSetupSortCatalog(sortCatalog: CatalogEntity.SubCatalogEntity)
}
enum class SortType(val value: String) {
RECOMMENDED("热门推荐"),
NEWEST("最新上线"),
RATING("最高评分")
}
}

View File

@ -0,0 +1,64 @@
package com.gh.common.view
import android.content.Context
import android.util.AttributeSet
import android.view.View
import android.widget.LinearLayout
import android.widget.TextView
import com.gh.gamecenter.R
import com.gh.gamecenter.category.CategoryListActivity
import com.gh.gamecenter.entity.CategoryEntity
class SubCategoryView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
LinearLayout(context, attrs, defStyleAttr) {
var leftTv: TextView
var centerTv: TextView
var rightTv: TextView
var leftDivider: View
var rightDivider: View
var primeCategory: CategoryEntity? = null
var categoryTitle: String? = ""
init {
View.inflate(context, R.layout.layout_sub_category, this)
leftTv = findViewById(R.id.tv_left_sub_category)
centerTv = findViewById(R.id.tv_center_sub_category)
rightTv = findViewById(R.id.tv_right_sub_category)
leftDivider = findViewById(R.id.divider_left)
rightDivider = findViewById(R.id.divider_right)
}
fun setLeftCategory(category: CategoryEntity) {
setCategory(leftTv, category)
}
fun setCenterCategory(category: CategoryEntity) {
setCategory(centerTv, category)
leftDivider.visibility = View.VISIBLE
}
fun setRightCategory(category: CategoryEntity) {
setCategory(rightTv, category)
rightDivider.visibility = View.VISIBLE
}
private fun setCategory(tv: TextView, category: CategoryEntity) {
tv.text = category.name
tv.setOnClickListener {
tv.context.startActivity(
CategoryListActivity.getIntent(
tv.context,
categoryTitle!!,
primeCategory!!,
category.name!!
)
)
}
}
}

View File

@ -59,8 +59,8 @@ class GameDetailActivity : DownloadToolbarActivity() {
}
override fun getBusinessId(): Pair<String, String> {
val fragment = targetFragment as? GameDetailFragment?
return if (fragment?.arguments != null) {
val fragment = targetFragment as GameDetailFragment
return if (fragment.arguments != null) {
Pair(fragment.requireArguments().getString(EntranceConsts.KEY_GAMEID) ?: "", "")
} else {
super.getBusinessId()

View File

@ -5,6 +5,7 @@ import static com.gh.gamecenter.common.constant.EntranceConsts.ENTRANCE_PUSH;
import static com.gh.gamecenter.common.constant.EntranceConsts.HOST_ANSWER;
import static com.gh.gamecenter.common.constant.EntranceConsts.HOST_ARCHIVE_LOGIN;
import static com.gh.gamecenter.common.constant.EntranceConsts.HOST_ARTICLE;
import static com.gh.gamecenter.common.constant.EntranceConsts.HOST_CATEGORY;
import static com.gh.gamecenter.common.constant.EntranceConsts.HOST_COLUMN;
import static com.gh.gamecenter.common.constant.EntranceConsts.HOST_COLUMN_COLLECTION;
import static com.gh.gamecenter.common.constant.EntranceConsts.HOST_COMMUNITY;
@ -314,6 +315,10 @@ public class SkipActivity extends BaseActivity {
DirectUtils.directToCommunityColumn(this, community, columnId, entrance, "");
break;
case HOST_CATEGORY:
title = uri.getQueryParameter("title");
DirectUtils.directCategoryDirectory(this, path, title, entrance, pathName);
break;
case HOST_COLUMN_COLLECTION:
DirectUtils.directToColumnCollection(this, path, -1, entrance, "", "", "", "", null,false);
break;

View File

@ -0,0 +1,43 @@
package com.gh.gamecenter.catalog
import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.gh.base.DownloadToolbarActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.updateStatusBarColor
class CatalogActivity : DownloadToolbarActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setToolbarMenu(R.menu.menu_download)
updateStatusBarColor(R.color.ui_surface, R.color.ui_surface)
}
override fun showDownloadMenu(): Boolean {
return true
}
override fun provideNormalIntent(): Intent {
return getTargetIntent(this, CatalogActivity::class.java, CatalogFragment::class.java)
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
updateStatusBarColor(R.color.ui_surface, R.color.ui_surface)
}
override fun isAutoResetViewBackgroundEnabled() = true
companion object {
fun getIntent(context: Context, catalogId: String, catalogTitle: String, entrance: String): Intent {
val bundle = Bundle()
bundle.putString(EntranceConsts.KEY_CATALOG_ID, catalogId)
bundle.putString(EntranceConsts.KEY_CATALOG_TITLE, catalogTitle)
bundle.putString(EntranceConsts.KEY_ENTRANCE, entrance)
return getTargetIntent(context, CatalogActivity::class.java, CatalogFragment::class.java, bundle)
}
}
}

View File

@ -0,0 +1,53 @@
package com.gh.gamecenter.catalog
import android.content.Context
import android.view.View
import android.view.ViewGroup
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.R
import com.gh.gamecenter.databinding.CatalogItemBinding
import com.gh.gamecenter.entity.CatalogEntity
import com.lightgame.adapter.BaseRecyclerAdapter
class CatalogAdapter(
context: Context,
private val mFragment: CatalogFragment,
private val mViewModel: CatalogViewModel,
private val mList: List<CatalogEntity.SubCatalogEntity>
) : BaseRecyclerAdapter<CatalogAdapter.CatalogItemViewHolder>(context) {
override fun getItemCount() = mList.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
CatalogItemViewHolder(parent.toBinding())
override fun onBindViewHolder(holder: CatalogItemViewHolder, position: Int) {
holder.binding.run {
val catalogEntity = mList[position]
catalogName.text = catalogEntity.name
recommendTag.goneIf(!catalogEntity.recommended)
if (catalogEntity.name == mViewModel.selectedCatalogName) {
selectedTag.visibility = View.VISIBLE
catalogName.setTextColor(R.color.text_theme.toColor(mContext))
root.setBackgroundColor(R.color.ui_surface.toColor(mContext))
} else {
selectedTag.visibility = View.GONE
catalogName.setTextColor(R.color.text_primary.toColor(mContext))
root.background = null
}
root.setOnClickListener {
if (catalogEntity.name != mViewModel.selectedCatalogName) {
mViewModel.selectedCatalogName = catalogEntity.name
mViewModel.logSubCatalogClick(position)
mFragment.changeCatalog(position)
notifyDataSetChanged()
}
}
}
}
class CatalogItemViewHolder(val binding: CatalogItemBinding) : BaseRecyclerViewHolder<Any>(binding.root)
}

View File

@ -0,0 +1,240 @@
package com.gh.gamecenter.catalog
import android.os.Bundle
import android.view.View
import androidx.core.os.bundleOf
import androidx.lifecycle.Observer
import com.gh.gamecenter.common.base.fragment.LazyFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.viewModelProviderFromParent
import com.gh.gamecenter.common.view.FixLinearLayoutManager
import com.gh.gamecenter.R
import com.gh.gamecenter.databinding.FragmentCatalogBinding
import com.gh.gamecenter.entity.CatalogEntity
class CatalogFragment : LazyFragment() {
private var mBinding: FragmentCatalogBinding? = null
private var mViewModel: CatalogViewModel? = null
private var mEntity: CatalogEntity? = null
private var mSpecialCatalogFragment: SpecialCatalogFragment? = null
private var mSubCatalogFragment: SubCatalogFragment? = null
private var mCatalogId: String = ""
private var mCatalogTitle: String = ""
private var mLastSelectedPosition = -1
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
savedInstanceState?.run {
mLastSelectedPosition = getInt(EntranceConsts.KEY_LAST_SELECTED_POSITION)
}
}
override fun onSaveInstanceState(outState: Bundle) {
mViewModel?.run {
outState.putInt(EntranceConsts.KEY_LAST_SELECTED_POSITION, selectedCatalogPosition)
}
super.onSaveInstanceState(outState)
}
override fun getRealLayoutId() = R.layout.fragment_catalog
override fun onRealLayoutInflated(inflatedView: View) {
mBinding = FragmentCatalogBinding.bind(inflatedView)
}
override fun onFragmentFirstVisible() {
mCatalogId = arguments?.getString(EntranceConsts.KEY_CATALOG_ID) ?: ""
mCatalogTitle = arguments?.getString(EntranceConsts.KEY_CATALOG_TITLE) ?: ""
mViewModel = viewModelProviderFromParent(CatalogViewModel.Factory(mCatalogId, mCatalogTitle), mCatalogId)
mViewModel?.validEntranceName = if (mEntrance.contains("首页")) "首页" else "板块"
if (arguments?.getBoolean(EntranceConsts.KEY_IS_HOME) == true) {
mViewModel?.validEntranceName = "首页Tab栏"
}
mViewModel?.logAppearance()
super.onFragmentFirstVisible()
}
override fun initRealView() {
super.initRealView()
setNavigationTitle(mCatalogTitle)
mViewModel?.catalogs?.observe(viewLifecycleOwner, Observer {
mBinding?.run {
reuseLoading.root.visibility = View.GONE
if (it != null) {
reuseNoConnection.root.visibility = View.GONE
if (it.subCatalog.isNotEmpty()) {
containerCatalog.visibility = View.VISIBLE
reuseNoneData.root.visibility = View.GONE
mEntity = it
if (mEntity!!.hasSpecial) {
val specialEntity = CatalogEntity.SubCatalogEntity(name = "精选")
(mEntity!!.subCatalog as ArrayList).add(0, specialEntity)
}
initView()
} else {
containerCatalog.visibility = View.GONE
reuseNoneData.root.visibility = View.VISIBLE
}
} else {
containerCatalog.visibility = View.GONE
reuseNoneData.root.visibility = View.GONE
reuseNoConnection.root.visibility = View.VISIBLE
reuseNoConnection.root.setOnClickListener {
reuseNoConnection.root.visibility = View.GONE
reuseLoading.root.visibility = View.VISIBLE
mViewModel?.getCatalogs()
}
}
}
})
// 嵌入在首页时特殊处理
if (arguments?.getBoolean(EntranceConsts.KEY_IS_HOME) == true) {
mBinding?.divider?.visibility = View.GONE
mBinding?.root?.setBackgroundColor(R.color.ui_surface.toColor(requireContext()))
mBinding?.root?.setPadding(0, 8F.dip2px(), 0, 0)
mBinding?.rvCatalog?.setBackgroundColor(R.color.ui_background.toColor(requireContext()))
}
}
private fun initView() {
mEntity?.run {
mViewModel?.run {
if (subCatalog.isNotEmpty()) {
if (mLastSelectedPosition != -1) {
selectedCatalogPosition = mLastSelectedPosition
selectedCatalogName = subCatalog[mLastSelectedPosition].name
} else {
selectedCatalogPosition = 0
selectedCatalogName = subCatalog[0].name
}
mBinding?.rvCatalog?.layoutManager = FixLinearLayoutManager(requireContext())
mBinding?.rvCatalog?.adapter =
CatalogAdapter(requireContext(), this@CatalogFragment, this, subCatalog)
if (hasSpecial && selectedCatalogPosition == 0) {
mSpecialCatalogFragment =
childFragmentManager.findFragmentByTag(SpecialCatalogFragment::class.java.name) as? SpecialCatalogFragment
?: SpecialCatalogFragment()
mSpecialCatalogFragment?.arguments = bundleOf(
EntranceConsts.KEY_IS_CATEGORY_V2 to false,
EntranceConsts.KEY_CATALOG_ID to id,
EntranceConsts.KEY_CATALOG_TITLE to mCatalogTitle,
EntranceConsts.KEY_EXPOSURE_SOURCE to arguments?.getParcelable(
EntranceConsts.KEY_EXPOSURE_SOURCE
)
)
childFragmentManager.beginTransaction().replace(
R.id.container_sub_catalog,
mSpecialCatalogFragment!!,
SpecialCatalogFragment::class.java.name
).commitAllowingStateLoss()
} else {
mSubCatalogFragment =
childFragmentManager.findFragmentByTag(SubCatalogFragment::class.java.name) as? SubCatalogFragment
?: SubCatalogFragment()
mSubCatalogFragment?.arguments = bundleOf(
EntranceConsts.KEY_CATALOG_ID to id,
EntranceConsts.KEY_PRIMARY_CATALOG_ID to subCatalog[selectedCatalogPosition].id,
EntranceConsts.KEY_CATALOG_TITLE to mCatalogTitle
)
childFragmentManager.beginTransaction().replace(
R.id.container_sub_catalog,
mSubCatalogFragment!!,
SubCatalogFragment::class.java.name
).commitAllowingStateLoss()
}
}
}
}
}
fun changeCatalog(position: Int) {
mEntity?.run {
mViewModel?.run {
if (hasSpecial) {
changeCatalogIfHasSpecial(position)
} else {
changeSubCatalogInSubCatalogFragment(position)
}
selectedCatalogPosition = position
}
}
}
private fun changeCatalogIfHasSpecial(position: Int) {
mViewModel?.run {
when {
selectedCatalogPosition == 0 -> changeToSubCatalogFragment(position)
position == 0 -> changeToSpecialCatalogFragment()
else -> changeSubCatalogInSubCatalogFragment(position)
}
}
}
private fun changeToSubCatalogFragment(position: Int) {
mEntity?.run {
mSubCatalogFragment =
childFragmentManager.findFragmentByTag(SubCatalogFragment::class.java.name) as? SubCatalogFragment
?: SubCatalogFragment()
mSubCatalogFragment?.arguments = bundleOf(
EntranceConsts.KEY_CATALOG_ID to id,
EntranceConsts.KEY_PRIMARY_CATALOG_ID to subCatalog[position].id,
EntranceConsts.KEY_CATALOG_TITLE to mCatalogTitle
)
childFragmentManager.beginTransaction().replace(
R.id.container_sub_catalog,
mSubCatalogFragment!!,
SubCatalogFragment::class.java.name
).commitAllowingStateLoss()
}
}
private fun changeToSpecialCatalogFragment() {
mEntity?.run {
mSpecialCatalogFragment =
childFragmentManager.findFragmentByTag(SpecialCatalogFragment::class.java.name) as? SpecialCatalogFragment
?: SpecialCatalogFragment()
mSpecialCatalogFragment?.arguments = bundleOf(
EntranceConsts.KEY_IS_CATEGORY_V2 to false,
EntranceConsts.KEY_CATALOG_ID to id,
EntranceConsts.KEY_CATALOG_TITLE to mCatalogTitle,
EntranceConsts.KEY_EXPOSURE_SOURCE to arguments?.getParcelable(
EntranceConsts.KEY_EXPOSURE_SOURCE
)
)
childFragmentManager.beginTransaction().replace(
R.id.container_sub_catalog,
mSpecialCatalogFragment!!,
SpecialCatalogFragment::class.java.name
).commitAllowingStateLoss()
}
}
private fun changeSubCatalogInSubCatalogFragment(position: Int) {
mEntity?.run {
if (mSubCatalogFragment?.isStateSaved == false) {
mSubCatalogFragment?.arguments = bundleOf(
EntranceConsts.KEY_CATALOG_ID to id,
EntranceConsts.KEY_PRIMARY_CATALOG_ID to subCatalog[position].id,
EntranceConsts.KEY_CATALOG_TITLE to mCatalogTitle
)
}
mSubCatalogFragment?.changeSubCatalog(subCatalog[position].id)
}
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
mBinding?.rvCatalog?.adapter?.run {
notifyItemRangeChanged(0, itemCount)
}
}
}

View File

@ -0,0 +1,72 @@
package com.gh.gamecenter.catalog
import android.annotation.SuppressLint
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.gh.common.util.LogUtils
import com.gh.gamecenter.entity.CatalogEntity
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.retrofit.RetrofitManager
import com.halo.assistant.HaloApp
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
class CatalogViewModel(application: Application, val catalogId: String, val catalogTitle: String) :
AndroidViewModel(application) {
private val api = RetrofitManager.getInstance().api
var catalogs = MutableLiveData<CatalogEntity>()
var selectedCatalogName: String = ""
var selectedCatalogPosition: Int = 0
var validEntranceName: String = ""
init {
getCatalogs()
}
@SuppressLint("CheckResult")
fun getCatalogs() {
api.getCatalogs(catalogId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BiResponse<CatalogEntity>() {
override fun onSuccess(data: CatalogEntity) {
catalogs.postValue(data)
}
override fun onFailure(exception: Exception) {
super.onFailure(exception)
catalogs.postValue(null)
}
})
}
fun logAppearance() {
LogUtils.logNewCatalogAppearanceEvent(validEntranceName, catalogTitle)
}
fun logSubCatalogClick(position: Int) {
LogUtils.logSubCatalogClickEvent(validEntranceName, "${catalogTitle}_${selectedCatalogName}", position)
}
fun logSubCatalogContentClick(itemName: String, listPosition: Int) {
LogUtils.logSubCatalogContentClickEvent(
validEntranceName,
"${catalogTitle}_${selectedCatalogName}_${itemName}",
selectedCatalogPosition,
listPosition
)
}
class Factory(private val catalogId: String, private val catalogTitle: String) :
ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return CatalogViewModel(HaloApp.getInstance().application, catalogId, catalogTitle) as T
}
}
}

View File

@ -0,0 +1,56 @@
package com.gh.gamecenter.catalog
import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.gh.base.DownloadToolbarActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.updateStatusBarColor
import com.gh.gamecenter.entity.CatalogEntity
class NewCatalogListActivity : DownloadToolbarActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setToolbarMenu(R.menu.menu_download)
updateStatusBarColor(R.color.ui_surface, R.color.ui_surface)
}
override fun showDownloadMenu(): Boolean {
return true
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
updateStatusBarColor(R.color.ui_surface, R.color.ui_surface)
}
override fun isAutoResetViewBackgroundEnabled() = true
companion object {
fun getIntent(
context: Context,
primaryCatalogId: String, // 一级分类 id
primaryCatalogName: String, // 一级分类名
catalogTitle: String,
catalog: CatalogEntity,
initTitle: String
): Intent {
val bundle = Bundle()
bundle.putParcelable(EntranceConsts.KEY_DATA, catalog)
bundle.putString(EntranceConsts.KEY_PRIMARY_CATALOG_ID, primaryCatalogId)
bundle.putString(EntranceConsts.KEY_PRIMARY_CATALOG_NAME, primaryCatalogName)
bundle.putString(EntranceConsts.KEY_NAME, catalog.name)
bundle.putString(EntranceConsts.KEY_CATALOG_TITLE, catalogTitle)
bundle.putString(EntranceConsts.KEY_CATALOG_INIT_TITLE, initTitle)
return getTargetIntent(
context,
NewCatalogListActivity::class.java,
NewCatalogListFragment::class.java,
bundle
)
}
}
}

View File

@ -0,0 +1,192 @@
package com.gh.gamecenter.catalog
import android.content.Context
import android.util.SparseArray
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.exposure.IExposable
import com.gh.common.util.DownloadItemUtils
import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.adapter.viewholder.GameViewHolder
import com.gh.gamecenter.common.baselist.ListAdapter
import com.gh.gamecenter.common.constant.ItemViewType
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.viewholder.FooterViewHolder
import com.gh.gamecenter.core.utils.StringUtils
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.ExposureType
import com.gh.gamecenter.feature.game.GameItemViewHolder
import com.lightgame.download.DownloadEntity
class NewCatalogListAdapter(
context: Context,
private val mBaseExposureSource: ExposureSource,
private val mViewModel: NewCatalogListViewModel,
private val mEntrance: String?
) : ListAdapter<GameEntity>(context), IExposable {
private val mExposureEventSparseArray: SparseArray<ExposureEvent> = SparseArray()
val positionAndPackageMap = HashMap<String, Int>()
override fun setListData(updateData: MutableList<GameEntity>?) {
// 记录游戏位置
if (updateData != null) {
for (i in 0 until updateData.size) {
val gameEntity = updateData[i]
var packages = gameEntity.id
for (apkEntity in gameEntity.getApk()) {
packages += apkEntity.packageName
}
positionAndPackageMap[packages + i] = i
}
}
super.setListData(updateData)
}
fun clearPositionAndPackageMap() {
positionAndPackageMap.clear()
}
override fun areItemsTheSame(oldItem: GameEntity?, newItem: GameEntity?): Boolean {
return oldItem?.id == newItem?.id
}
override fun getItemViewType(position: Int): Int {
if (position == itemCount - 1) {
return ItemViewType.ITEM_FOOTER
}
return ItemViewType.GAME_NORMAL
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ItemViewType.GAME_NORMAL -> {
GameItemViewHolder(parent.toBinding())
}
else -> {
FooterViewHolder(mLayoutInflater.inflate(R.layout.refresh_footerview, parent, false))
}
}
}
override fun getItemCount(): Int {
return if (mEntityList == null || mEntityList.isEmpty()) return 0 else mEntityList.size + 1
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is GameItemViewHolder) {
val padTop = if (position == 0) 16F.dip2px() else 8F.dip2px()
holder.itemView.setPadding(16F.dip2px(), padTop, 16F.dip2px(), 8F.dip2px())
val gameEntity = mEntityList[position]
holder.bindGameItem(gameEntity)
holder.initServerType(gameEntity)
val sortType = mViewModel.sortType.value
val sortSize = mViewModel.sortSize.text
val toolbarTitle = mViewModel.title
val selectedCatalogName = mViewModel.selectedCatalog.name
val exposureSources = ArrayList<ExposureSource>()
exposureSources.add(mBaseExposureSource)
exposureSources.add(ExposureSource(toolbarTitle))
exposureSources.add(ExposureSource("二级分类详情", "$selectedCatalogName+$sortType+$sortSize"))
val event = ExposureEvent.createEvent(gameEntity, exposureSources, null, ExposureType.EXPOSURE)
mExposureEventSparseArray.put(position, event)
holder.itemView.setOnClickListener {
GameDetailActivity.startGameDetailActivity(
mContext,
gameEntity,
StringUtils.buildString(
mEntrance,
"+(",
toolbarTitle,
":列表[",
selectedCatalogName,
"=",
sortType,
"=",
(position + 1).toString(),
"])"
),
traceEvent = event
)
}
DownloadItemUtils.setOnClickListener(
mContext,
holder.binding.downloadBtn,
gameEntity,
position,
this,
StringUtils.buildString(
StringUtils.buildString(
mEntrance,
"+(",
toolbarTitle,
":列表[",
selectedCatalogName,
"=",
sortType,
"=",
(position + 1).toString(),
"])"
)
),
location = StringUtils.buildString(selectedCatalogName, ":", gameEntity.name),
traceEvent = event
)
DownloadItemUtils.updateItem(mContext, gameEntity, GameViewHolder(holder.binding), "star&brief")
} else if (holder is FooterViewHolder) {
holder.initItemPadding()
holder.initFooterViewHolder(mViewModel, mIsLoading, mIsNetworkError, mIsOver)
}
}
fun notifyItemByDownload(download: DownloadEntity) {
for (key in positionAndPackageMap.keys) {
// sentry上报download.packageName可能为空
if (download.packageName != null
&& download.gameId != null
&& key.contains(download.packageName)
&& key.contains(download.gameId)
) {
val position = positionAndPackageMap[key]
if (position != null && mEntityList != null && position < mEntityList.size) {
mEntityList[position].getEntryMap()[download.platform] = download
notifyItemChanged(position)
}
}
}
}
fun notifyItemAndRemoveDownload(status: EBDownloadStatus) {
for (key in positionAndPackageMap.keys) {
if (key.contains(status.packageName) && key.contains(status.gameId)) {
val position = positionAndPackageMap[key]
if (position != null && mEntityList != null && position < mEntityList.size) {
mEntityList[position].getEntryMap().remove(status.platform)
notifyItemChanged(position)
}
}
}
}
override fun getEventByPosition(pos: Int): ExposureEvent? {
return mExposureEventSparseArray.get(pos)
}
override fun getEventListByPosition(pos: Int): List<ExposureEvent>? {
return null
}
}

View File

@ -0,0 +1,209 @@
package com.gh.gamecenter.catalog
import android.os.Bundle
import android.view.View
import com.ethanhua.skeleton.Skeleton
import com.gh.common.exposure.ExposureListener
import com.gh.common.util.DialogUtils
import com.gh.common.view.CatalogFilterView
import com.gh.common.xapk.XapkInstaller
import com.gh.common.xapk.XapkUnzipStatus
import com.gh.download.DownloadManager
import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.observeNonNull
import com.gh.gamecenter.common.baselist.ListFragment
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.viewModelProvider
import com.gh.gamecenter.databinding.FragmentCatalogListBinding
import com.gh.gamecenter.entity.CatalogEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.eventbus.EBPackage
import com.lightgame.download.DataWatcher
import com.lightgame.download.DownloadEntity
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class NewCatalogListFragment : ListFragment<GameEntity, NewCatalogListViewModel>() {
private var mPrimaryCatalogName: String = "" // 一级分类名
private var mPrimaryCatalogId: String = "" // 一级分类 id
private var mPrimeCatalog: CatalogEntity? = null
private var mSubCatalogList = arrayListOf<CatalogEntity.SubCatalogEntity>()
private var mInitCatalogName = ""
private var mAdapter: NewCatalogListAdapter? = null
private val mDataWatcher = object : DataWatcher() {
override fun onDataChanged(downloadEntity: DownloadEntity) {
mAdapter?.notifyItemByDownload(downloadEntity)
if (downloadEntity.meta[XapkInstaller.XAPK_UNZIP_STATUS] == XapkUnzipStatus.FAILURE.name) {
showUnzipFailureDialog(downloadEntity)
}
}
override fun onDataInit(downloadEntity: DownloadEntity) {
mAdapter?.notifyItemByDownload(downloadEntity)
}
}
private var mBinding: FragmentCatalogListBinding? = null
private lateinit var mExposureListener: ExposureListener
private lateinit var mViewModel: NewCatalogListViewModel
override fun getLayoutId() = 0
override fun getInflatedLayout() = FragmentCatalogListBinding.inflate(layoutInflater).apply { mBinding = this }.root
override fun provideListViewModel() = viewModelProvider<NewCatalogListViewModel>()
override fun provideListAdapter() = mAdapter
?: NewCatalogListAdapter(
requireContext(),
ExposureSource(mPrimaryCatalogName),
mViewModel,
mEntrance
).apply { mAdapter = this }
override fun getItemDecoration() = null
override fun onCreate(savedInstanceState: Bundle?) {
mViewModel = provideListViewModel()
mViewModel.title = arguments?.getString(EntranceConsts.KEY_NAME) ?: ""
mViewModel.categoryTitle = arguments?.getString(EntranceConsts.KEY_CATALOG_TITLE) ?: ""
mEntrance = arguments?.getString(EntranceConsts.KEY_ENTRANCE) ?: Constants.ENTRANCE_UNKNOWN
mPrimeCatalog = arguments?.getParcelable(EntranceConsts.KEY_DATA)
mSubCatalogList = mPrimeCatalog?.subCatalog as? ArrayList<CatalogEntity.SubCatalogEntity> ?: arrayListOf()
mInitCatalogName = arguments?.getString(EntranceConsts.KEY_CATALOG_INIT_TITLE) ?: ""
mPrimaryCatalogName = arguments?.getString(EntranceConsts.KEY_PRIMARY_CATALOG_NAME) ?: ""
mPrimaryCatalogId = arguments?.getString(EntranceConsts.KEY_PRIMARY_CATALOG_ID) ?: ""
mViewModel.selectedCatalog = mSubCatalogList.find { entity -> entity.name == mInitCatalogName }
?: CatalogEntity.SubCatalogEntity()
mViewModel.primaryCatalogId = mPrimaryCatalogId
initSortType()
super.onCreate(savedInstanceState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setNavigationTitle(mViewModel.title)
initFilterView()
mViewModel.refresh.observeNonNull(this) { onRefresh() }
mExposureListener = ExposureListener(this, mAdapter!!)
mListRv.addOnScrollListener(mExposureListener)
mSkeletonScreen = Skeleton.bind(mBinding?.listSkeleton)
.shimmer(true)
.angle(Constants.SHIMMER_ANGLE)
.color(R.color.ui_skeleton_highlight)
.duration(Constants.SHIMMER_DURATION)
.maskWidth(Constants.MASK_WIDTH)
.gradientCenterColorWidth(Constants.GRADIENT_CENTER_COLOR_WIDTH)
.load(R.layout.fragment_tags_skeleton)
.show()
}
private fun initSortType() {
mPrimeCatalog?.switch?.run {
if (hotSort == "on") {
mViewModel.sortType = CatalogFilterView.SortType.RECOMMENDED
return@run
}
if (newSort == "on") {
mViewModel.sortType = CatalogFilterView.SortType.NEWEST
return@run
}
if (starSort == "on") {
mViewModel.sortType = CatalogFilterView.SortType.RATING
return@run
}
}
}
private fun initFilterView() {
mBinding?.filterContainer?.run {
visibility = View.VISIBLE
setTypeList(mPrimeCatalog?.switch ?: CatalogEntity.CatalogSwitch())
setCatalogList(mSubCatalogList, mInitCatalogName)
setOnConfigSetupListener(object : CatalogFilterView.OnCatalogFilterSetupListener {
override fun onSetupSortSize(sortSize: SubjectSettingEntity.Size) {
mViewModel.updateSortConfig(sortSize = sortSize)
}
override fun onSetupSortType(sortType: CatalogFilterView.SortType) {
mViewModel.updateSortConfig(sortType = sortType)
}
override fun onSetupSortCatalog(sortCatalog: CatalogEntity.SubCatalogEntity) {
mViewModel.updateSortConfig(sortCatalog = sortCatalog)
}
})
}
}
override fun onResume() {
super.onResume()
DownloadManager.getInstance().addObserver(mDataWatcher)
}
override fun onPause() {
super.onPause()
DownloadManager.getInstance().removeObserver(mDataWatcher)
}
override fun onRefresh() {
mAdapter?.clearPositionAndPackageMap()
super.onRefresh()
}
// 下载被删除事件
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(status: EBDownloadStatus) {
if ("delete" == status.status) {
mAdapter?.notifyItemAndRemoveDownload(status)
}
}
// 安装/卸载 事件
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(busFour: EBPackage) {
if (busFour.isInstalledOrUninstalled()) {
mAdapter?.notifyDataSetChanged()
}
}
fun showUnzipFailureDialog(downloadEntity: DownloadEntity) {
val data = mAdapter?.positionAndPackageMap ?: return
for (gameAndPosition in data) {
if (gameAndPosition.key.contains(downloadEntity.packageName)) {
val targetView = mLayoutManager.findViewByPosition(gameAndPosition.value)
if (targetView != null) {
DialogUtils.showUnzipFailureDialog(requireContext(), downloadEntity)
return
}
}
}
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
mBinding?.filterContainer?.run {
setRootBackgroundColor(R.color.ui_surface.toColor(requireContext()))
setItemTextColor(R.color.text_secondary.toColor(requireContext()))
updatePopupWindow()
}
}
}

View File

@ -0,0 +1,98 @@
package com.gh.gamecenter.catalog
import android.app.Application
import androidx.lifecycle.MutableLiveData
import com.gh.gamecenter.common.entity.ExposureEntity
import com.gh.common.exposure.ExposureUtils
import com.gh.gamecenter.core.utils.UrlFilterUtils
import com.gh.common.view.CatalogFilterView
import com.gh.gamecenter.common.baselist.ListViewModel
import com.gh.gamecenter.entity.CatalogEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.gh.gamecenter.retrofit.RetrofitManager
import io.reactivex.Observable
import io.reactivex.Single
class NewCatalogListViewModel(application: Application) : ListViewModel<GameEntity, GameEntity>(application) {
var title = "" // 显示在 Toolbar 的标题
var categoryTitle = "" // 跳转进来的分类的标题
val refresh = MutableLiveData<Boolean>()
var selectedCatalog = CatalogEntity.SubCatalogEntity()
var sortType = CatalogFilterView.SortType.RECOMMENDED
var sortSize = SubjectSettingEntity.Size()
var primaryCatalogId = "" // 一级分类 id
private val sensitiveApi = RetrofitManager.getInstance().api
override fun provideDataObservable(page: Int): Observable<List<GameEntity>>? = null
override fun provideDataSingle(page: Int): Single<List<GameEntity>> {
return if (selectedCatalog.link.type == "column") { // column(专题)/tag(标签)
sensitiveApi.getColumn(selectedCatalog.link.link, getSortType(), getSortSize(), page) // 专题
} else {
sensitiveApi.getGamesWithSpecificTag(getSortSize(), getSortType(), page) // 标签
}
}
override fun mergeResultLiveData() {
mResultLiveData.addSource(mListLiveData) {
it.forEach { game -> game.hideSizeInsideDes = true }
ExposureUtils.updateExposureInfo(
gameList = it,
containerId = primaryCatalogId,
containerType = ExposureEntity.CATEGORY_ID
)
mResultLiveData.postValue(it)
}
}
fun updateSortConfig(
sortSize: SubjectSettingEntity.Size? = null,
sortType: CatalogFilterView.SortType? = null,
sortCatalog: CatalogEntity.SubCatalogEntity? = null
) {
when {
sortSize != null && sortSize != this.sortSize -> {
this.sortSize = sortSize
refresh.postValue(true)
}
sortType != null && sortType != this.sortType -> {
this.sortType = sortType
refresh.postValue(true)
}
sortCatalog != null && sortCatalog != selectedCatalog -> {
selectedCatalog = sortCatalog
refresh.postValue(true)
}
}
}
private fun getSortSize(): String? {
return if (selectedCatalog.link.type == "column") {
UrlFilterUtils.getFilterQuery(
"min_size", sortSize.min.toString(),
"max_size", sortSize.max.toString()
)
} else {
UrlFilterUtils.getFilterQuery(
"tag_id", selectedCatalog.link.link,
"min_size", sortSize.min.toString(),
"max_size", sortSize.max.toString()
)
}
}
private fun getSortType(): String? {
return when (sortType) {
CatalogFilterView.SortType.RECOMMENDED -> if (selectedCatalog.link.type == "column") "position:1" else "download:-1"
CatalogFilterView.SortType.NEWEST -> "publish:-1"
CatalogFilterView.SortType.RATING -> "star:-1"
}
}
}

View File

@ -0,0 +1,54 @@
package com.gh.gamecenter.catalog
import android.content.Context
import android.view.ViewGroup
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.SubCatalogItemBinding
import com.gh.gamecenter.entity.CatalogEntity
import com.lightgame.adapter.BaseRecyclerAdapter
class SubCatalogAdapter(
context: Context,
private val mCatalogViewModel: CatalogViewModel,
private val mPrimaryCatalog: CatalogEntity,
private var mList: List<CatalogEntity.SubCatalogEntity>
) : BaseRecyclerAdapter<SubCatalogAdapter.SubCatalogItemViewHolder>(context) {
private val mTypes = listOf("专题", "标签")
override fun getItemCount() = mList.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
SubCatalogItemViewHolder(parent.toBinding())
override fun onBindViewHolder(holder: SubCatalogItemViewHolder, position: Int) {
holder.binding.run {
val catalogEntity = mList[position]
ImageUtils.display(catalogIcon, catalogEntity.icon)
catalogName.text = catalogEntity.name
catalogName.setTextColor(R.color.text_primary.toColor(mContext))
recommendTag.goneIf(!catalogEntity.recommended)
root.setOnClickListener {
mCatalogViewModel.logSubCatalogContentClick(catalogEntity.name, position)
if (mTypes.contains(catalogEntity.type)) {
root.context.startActivity(
NewCatalogListActivity.getIntent(
mContext,
mCatalogViewModel.catalogId,
mCatalogViewModel.catalogTitle,
catalogEntity.name,
mPrimaryCatalog,
catalogEntity.name
)
)
} else {
DialogHelper.showUpgradeDialog(mContext)
}
}
}
}
class SubCatalogItemViewHolder(val binding: SubCatalogItemBinding) : BaseRecyclerViewHolder<Any>(binding.root)
}

View File

@ -0,0 +1,94 @@
package com.gh.gamecenter.catalog
import android.os.Bundle
import android.view.View
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.GridLayoutManager
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.viewModelProvider
import com.gh.gamecenter.common.utils.viewModelProviderFromParent
import com.gh.gamecenter.databinding.FragmentSubCatalogBinding
import com.gh.gamecenter.entity.CatalogEntity
import com.gh.gamecenter.common.base.fragment.ToolbarFragment
class SubCatalogFragment : ToolbarFragment() {
private var mBinding: FragmentSubCatalogBinding? = null
private var mViewModel: SubCatalogViewModel? = null
private var mCatalogViewModel: CatalogViewModel? = null
private var mEntity: CatalogEntity? = null
private var mCatalogId: String = ""
private var mCatalogTitle: String = ""
private var mPrimaryCatalogId: String = ""
override fun getLayoutId() = 0
override fun getInflatedLayout() = FragmentSubCatalogBinding.inflate(layoutInflater).apply { mBinding = this }.root
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mCatalogId = arguments?.getString(EntranceConsts.KEY_CATALOG_ID) ?: ""
mCatalogTitle = arguments?.getString(EntranceConsts.KEY_CATALOG_TITLE) ?: ""
mPrimaryCatalogId = arguments?.getString(EntranceConsts.KEY_PRIMARY_CATALOG_ID) ?: ""
mViewModel = viewModelProvider(SubCatalogViewModel.Factory(mCatalogId))
mCatalogViewModel = viewModelProviderFromParent(CatalogViewModel.Factory(mCatalogId, mCatalogTitle), mCatalogId)
mViewModel?.getSubCatalogs(mPrimaryCatalogId)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mViewModel?.catalogs?.observe(viewLifecycleOwner, Observer {
mBinding?.run {
reuseLoading.root.visibility = View.GONE
if (it != null) {
reuseNoConnection.root.visibility = View.GONE
if (it.subCatalog.isNotEmpty()) {
rvSubCatalog.visibility = View.VISIBLE
reuseNoneData.root.visibility = View.GONE
mEntity = it
initView()
} else {
rvSubCatalog.visibility = View.GONE
reuseNoneData.root.visibility = View.VISIBLE
}
} else {
rvSubCatalog.visibility = View.GONE
reuseNoneData.root.visibility = View.GONE
reuseNoConnection.root.visibility = View.VISIBLE
reuseNoConnection.root.setOnClickListener {
reuseLoading.root.visibility = View.VISIBLE
mViewModel?.getSubCatalogs(mPrimaryCatalogId)
}
}
}
})
}
private fun initView() {
mEntity?.takeIf { mCatalogViewModel != null }?.run {
mBinding?.rvSubCatalog?.layoutManager = GridLayoutManager(requireContext(), 3)
mBinding?.rvSubCatalog?.adapter = SubCatalogAdapter(requireContext(), mCatalogViewModel!!, this, subCatalog)
}
}
fun changeSubCatalog(primaryCatalogId: String) {
mBinding?.run {
mPrimaryCatalogId = primaryCatalogId
rvSubCatalog.visibility = View.GONE
reuseNoneData.root.visibility = View.GONE
reuseNoConnection.root.visibility = View.GONE
reuseLoading.root.visibility = View.VISIBLE
mViewModel?.getSubCatalogs(mPrimaryCatalogId)
}
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
mBinding?.rvSubCatalog?.adapter?.run {
mBinding?.rvSubCatalog?.recycledViewPool?.clear()
notifyItemRangeChanged(0, itemCount)
}
}
}

View File

@ -0,0 +1,44 @@
package com.gh.gamecenter.catalog
import android.annotation.SuppressLint
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.gh.gamecenter.entity.CatalogEntity
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.retrofit.RetrofitManager
import com.halo.assistant.HaloApp
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
class SubCatalogViewModel(application: Application, private val catalogId: String) : AndroidViewModel(application) {
private val api = RetrofitManager.getInstance().api
var catalogs = MutableLiveData<CatalogEntity>()
@SuppressLint("CheckResult")
fun getSubCatalogs(primaryCatalogId: String) {
api.getSubCatalogs(catalogId, primaryCatalogId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BiResponse<CatalogEntity>() {
override fun onSuccess(data: CatalogEntity) {
catalogs.postValue(data)
}
override fun onFailure(exception: Exception) {
super.onFailure(exception)
catalogs.postValue(null)
}
})
}
class Factory(private val catalogId: String) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return SubCatalogViewModel(HaloApp.getInstance().application, catalogId) as T
}
}
}

View File

@ -0,0 +1,51 @@
package com.gh.gamecenter.category
import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.gh.base.DownloadToolbarActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.updateStatusBarColor
class CategoryDirectoryActivity : DownloadToolbarActivity() {
companion object {
fun getIntent(context: Context, categoryId: String, categoryTitle: String): Intent {
val bundle = Bundle()
bundle.putString(EntranceConsts.KEY_CATEGORY_ID, categoryId)
bundle.putString(EntranceConsts.KEY_CATEGORY_TITLE, categoryTitle)
return getTargetIntent(
context,
CategoryDirectoryActivity::class.java,
CategoryDirectoryFragment::class.java,
bundle
)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setToolbarMenu(R.menu.menu_download)
updateStatusBarColor(R.color.ui_surface, R.color.ui_surface)
}
override fun showDownloadMenu(): Boolean {
return true
}
override fun getActivityNameInChinese(): String {
return "游戏分类"
}
override fun provideNormalIntent(): Intent {
return getTargetIntent(this, CategoryDirectoryActivity::class.java, CategoryDirectoryFragment::class.java)
}
override fun isAutoResetViewBackgroundEnabled() = true
override fun onDarkModeChanged() {
super.onDarkModeChanged()
updateStatusBarColor(R.color.ui_surface, R.color.ui_surface)
}
}

View File

@ -0,0 +1,157 @@
package com.gh.gamecenter.category
import android.content.Context
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.view.SubCategoryView
import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.ListAdapter
import com.gh.gamecenter.category.CategoryListActivity.Companion.getIntent
import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.databinding.ItemCategoryBinding
import com.gh.gamecenter.entity.CategoryEntity
import net.cachapa.expandablelayout.ExpandableLayout
class CategoryDirectoryAdapter(context: Context, var categoryTitle: String) : ListAdapter<CategoryEntity>(context) {
var recyclerView: RecyclerView? = null
var expandStatusMap: HashMap<Int, Boolean> = HashMap()
private var sixteenDp: Int = 0
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
if (sixteenDp == 0) sixteenDp = DisplayUtils.dip2px(parent.context, 16f)
return CategoryViewHolder(parent.toBinding())
}
override fun getItemCount(): Int {
return mEntityList.size
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is CategoryViewHolder -> {
val category = mEntityList[position]
holder.binding.run {
divider.setBackgroundColor(R.color.ui_background.toColor(mContext))
containerPrimaryCategory.setOnClickListener {
root.context.startActivity(getIntent(root.context, categoryTitle, category, "全部"))
}
ImageUtils.display(iconIv, category.icon)
categoryName.text = category.name
}
holder.bindCategory(
category = mEntityList[position],
expandableStatusMap = expandStatusMap,
isExpended = expandStatusMap[position] != null && expandStatusMap[position] == true,
marginTop = sixteenDp,
categoryTitle = categoryTitle,
expandedAction = { recyclerView?.smoothScrollToPosition(position) })
}
}
}
internal class CategoryViewHolder(var binding: ItemCategoryBinding) : RecyclerView.ViewHolder(binding.root) {
fun bindCategory(
category: CategoryEntity,
expandableStatusMap: HashMap<Int, Boolean>,
isExpended: Boolean,
marginTop: Int,
categoryTitle: String,
expandedAction: () -> Unit
) {
category.data?.let {
var subCategoryView: SubCategoryView? = null
binding.containerUnexpandable.removeAllViews()
val unexpandableSize = if (it.size > 6) 6 else it.size
val unexpandableCategoryList = it.subList(0, unexpandableSize)
unexpandableCategoryList.forEachIndexed { index, c ->
when (index % 3) {
0 -> {
val params = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT
)
if (subCategoryView != null) params.setMargins(0, marginTop, 0, 0)
subCategoryView = SubCategoryView(binding.root.context)
subCategoryView?.categoryTitle = categoryTitle
subCategoryView?.primeCategory = category
subCategoryView?.layoutParams = params
binding.containerUnexpandable.addView(subCategoryView)
subCategoryView?.setLeftCategory(c)
}
1 -> subCategoryView?.setCenterCategory(c)
2 -> subCategoryView?.setRightCategory(c)
}
}
if (it.size >= 7) {
val extraCategoryList = it.subList(6, it.size)
binding.ivToggle.visibility = View.VISIBLE
binding.ivToggle.setOnClickListener { binding.containerExpandable.toggle() }
when {
isExpended -> binding.ivToggle.setImageResource(R.drawable.ic_category_arrow_up)
else -> binding.ivToggle.setImageResource(R.drawable.ic_category_arrow_down)
}
val subCategoryViewLinearLayout = LinearLayout(binding.root.context)
subCategoryViewLinearLayout.orientation = LinearLayout.VERTICAL
subCategoryView = SubCategoryView(binding.root.context)
binding.containerExpandable.removeAllViews()
binding.containerExpandable.addView(subCategoryViewLinearLayout)
binding.containerExpandable.setExpanded(isExpended, false)
binding.containerExpandable.setOnExpansionUpdateListener { _, state ->
when (state) {
ExpandableLayout.State.COLLAPSED -> {
binding.ivToggle.setImageResource(R.drawable.ic_category_arrow_down)
expandableStatusMap[adapterPosition] = false
}
ExpandableLayout.State.EXPANDED -> {
binding.ivToggle.setImageResource(R.drawable.ic_category_arrow_up)
expandableStatusMap[adapterPosition] = true
expandedAction.invoke()
}
}
}
extraCategoryList.forEachIndexed { index, c ->
when (index % 3) {
0 -> {
subCategoryView = SubCategoryView(binding.root.context)
val params = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.MATCH_PARENT
)
params.setMargins(0, marginTop, 0, 0)
subCategoryView?.categoryTitle = categoryTitle
subCategoryView?.primeCategory = category
subCategoryView?.layoutParams = params
subCategoryViewLinearLayout.addView(subCategoryView)
subCategoryView?.setLeftCategory(c)
}
1 -> subCategoryView?.setCenterCategory(c)
2 -> subCategoryView?.setRightCategory(c)
}
}
} else {
binding.ivToggle.visibility = View.GONE
binding.containerExpandable.removeAllViews()
}
}
}
}
}

View File

@ -0,0 +1,104 @@
package com.gh.gamecenter.category
import android.graphics.Color
import android.view.View
import androidx.lifecycle.ViewModelProviders
import com.ethanhua.skeleton.Skeleton
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.view.VerticalItemDecoration
import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.LazyListFragment
import com.gh.gamecenter.databinding.FragmentListBaseSkeletonBinding
import com.gh.gamecenter.entity.CategoryEntity
class CategoryDirectoryFragment : LazyListFragment<CategoryEntity, CategoryDirectoryListViewModel>() {
private lateinit var mViewModel: CategoryDirectoryListViewModel
private lateinit var mAdapter: CategoryDirectoryAdapter
private var mBinding: FragmentListBaseSkeletonBinding? = null
override fun getRealLayoutId() = R.layout.fragment_list_base_skeleton
override fun onRealLayoutInflated(inflatedView: View) {
super.onRealLayoutInflated(inflatedView)
mBinding = FragmentListBaseSkeletonBinding.bind(inflatedView)
}
override fun onFragmentFirstVisible() {
mViewModel = ViewModelProviders.of(this).get(CategoryDirectoryListViewModel::class.java)
mViewModel.categoryId = arguments?.getString(EntranceConsts.KEY_CATEGORY_ID) ?: ""
mAdapter =
CategoryDirectoryAdapter(requireContext(), arguments?.getString(EntranceConsts.KEY_CATEGORY_TITLE) ?: "")
super.onFragmentFirstVisible()
}
override fun initRealView() {
super.initRealView()
setNavigationTitle(arguments?.getString(EntranceConsts.KEY_CATEGORY_TITLE))
mSkeletonScreen = Skeleton.bind(mBinding?.listSkeleton)
.shimmer(true)
.angle(Constants.SHIMMER_ANGLE)
.color(R.color.ui_skeleton_highlight)
.duration(Constants.SHIMMER_DURATION)
.maskWidth(Constants.MASK_WIDTH)
.gradientCenterColorWidth(Constants.GRADIENT_CENTER_COLOR_WIDTH)
.load(R.layout.fragment_category_skeleton)
.show()
onLoadRefresh()
if (arguments?.getBoolean(EntranceConsts.KEY_IS_HOME) == true) {
mListRv?.setPadding(
0,
context?.resources?.getDimension(R.dimen.home_recyclerview_padding_top)?.toInt() ?: 0,
0,
0
)
mListRv?.clipToPadding = false
}
mListRv?.setBackgroundColor(R.color.ui_surface.toColor(requireContext()))
mAdapter.recyclerView = mListRv
mListRefresh?.isEnabled = false
}
override fun isAutomaticLoad() = false
override fun provideListAdapter() = mAdapter
override fun provideListViewModel() = mViewModel
override fun getItemDecoration() = VerticalItemDecoration(context, 0F, false)
override fun onLoadRefresh() {
mCachedView?.setBackgroundColor(R.color.ui_surface.toColor(requireContext()))
super.onLoadRefresh()
}
override fun onLoadDone() {
mCachedView?.setBackgroundColor(Color.TRANSPARENT)
super.onLoadDone()
}
override fun onLoadEmpty() {
mCachedView?.setBackgroundColor(Color.TRANSPARENT)
super.onLoadEmpty()
}
override fun onLoadError() {
mCachedView?.setBackgroundColor(Color.TRANSPARENT)
super.onLoadError()
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
mListRv?.setBackgroundColor(R.color.ui_surface.toColor(requireContext()))
mListRv?.adapter?.run {
mListRv?.recycledViewPool?.clear()
notifyItemRangeChanged(0, itemCount)
}
}
}

View File

@ -0,0 +1,21 @@
package com.gh.gamecenter.category
import android.app.Application
import com.gh.gamecenter.common.baselist.ListViewModel
import com.gh.gamecenter.entity.CategoryEntity
import com.gh.gamecenter.retrofit.RetrofitManager
import io.reactivex.Observable
class CategoryDirectoryListViewModel(application: Application) :
ListViewModel<CategoryEntity, CategoryEntity>(application) {
var categoryId = ""
override fun mergeResultLiveData() {
mResultLiveData.addSource(mListLiveData, mResultLiveData::postValue)
}
override fun provideDataObservable(page: Int): Observable<MutableList<CategoryEntity>> {
return RetrofitManager.getInstance().api.getCategories(categoryId, page)
}
}

View File

@ -0,0 +1,53 @@
package com.gh.gamecenter.category
import android.content.Context
import android.content.Intent
import android.os.Bundle
import com.gh.base.DownloadToolbarActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.updateStatusBarColor
import com.gh.gamecenter.entity.CategoryEntity
class CategoryListActivity : DownloadToolbarActivity() {
companion object {
fun getIntent(context: Context, categoryTitle: String, category: CategoryEntity, initTitle: String): Intent {
val bundle = Bundle()
if (category.data!!.isEmpty() || category.data!![0].name != "全部") {
val plainCategory = CategoryEntity(id = category.id, name = "全部")
((category.data) as ArrayList).add(0, plainCategory)
}
bundle.putParcelable(EntranceConsts.KEY_DATA, category)
bundle.putString(EntranceConsts.KEY_NAME, category.name)
bundle.putString(EntranceConsts.KEY_CATEGORY_TITLE, categoryTitle)
bundle.putString(EntranceConsts.KEY_CATEGORY_INIT_TITLE, initTitle)
return getTargetIntent(
context,
CategoryListActivity::class.java,
NewCategoryListFragment::class.java,
bundle
)
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setToolbarMenu(R.menu.menu_download)
updateStatusBarColor(R.color.ui_surface, R.color.ui_surface)
}
override fun showDownloadMenu(): Boolean {
return true
}
override fun getActivityNameInChinese(): String {
return "游戏分类详情"
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
updateStatusBarColor(R.color.ui_surface, R.color.ui_surface)
}
}

View File

@ -0,0 +1,51 @@
package com.gh.gamecenter.category
import android.content.Context
import android.view.ViewGroup
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.toDrawable
import com.gh.gamecenter.databinding.ItemTagBinding
import com.gh.gamecenter.entity.CategoryEntity
import com.lightgame.adapter.BaseRecyclerAdapter
class NewCategoryHorizontalAdapter(
context: Context,
private val mViewModel: NewCategoryListViewModel,
private val mCategoryList: List<CategoryEntity>,
private val mSmoothScrollAction: (position: Int) -> Unit
) : BaseRecyclerAdapter<TagsViewHolder>(context) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TagsViewHolder {
return TagsViewHolder(parent.toBinding())
}
override fun onBindViewHolder(holder: TagsViewHolder, position: Int) {
val categoryEntity = mCategoryList[position]
if (mViewModel.selectedCategory.name == categoryEntity.name) {
holder.binding.tagTv.background = R.drawable.bg_tag_text.toDrawable()
holder.binding.tagTv.setTextColor(R.color.text_white.toColor(mContext))
} else {
holder.binding.tagTv.background = null
holder.binding.tagTv.setTextColor(R.color.text_757575.toColor(mContext))
}
holder.binding.tagTv.text = categoryEntity.name
holder.binding.tagTv.setOnClickListener {
mViewModel.changeSelectedCategory(categoryEntity)
notifyDataSetChanged()
mSmoothScrollAction.invoke(position)
}
}
override fun getItemCount(): Int {
return mCategoryList.size
}
}
class TagsViewHolder(val binding: ItemTagBinding) : BaseRecyclerViewHolder<Any>(binding.root)

View File

@ -0,0 +1,185 @@
package com.gh.gamecenter.category
import android.content.Context
import android.util.SparseArray
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.exposure.IExposable
import com.gh.common.util.DownloadItemUtils
import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.adapter.viewholder.GameViewHolder
import com.gh.gamecenter.common.baselist.ListAdapter
import com.gh.gamecenter.common.constant.ItemViewType
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.viewholder.FooterViewHolder
import com.gh.gamecenter.core.utils.StringUtils
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.ExposureType
import com.gh.gamecenter.feature.game.GameItemViewHolder
import com.lightgame.download.DownloadEntity
class NewCategoryListAdapter(
context: Context,
private val mViewModel: NewCategoryListViewModel,
private val mEntrance: String?
) : ListAdapter<GameEntity>(context), IExposable {
private val mExposureEventSparseArray: SparseArray<ExposureEvent> = SparseArray()
val positionAndPackageMap = HashMap<String, Int>()
override fun setListData(updateData: MutableList<GameEntity>?) {
// 记录游戏位置
if (updateData != null) {
for (i in 0 until updateData.size) {
val gameEntity = updateData[i]
var packages = gameEntity.id
for (apkEntity in gameEntity.getApk()) {
packages += apkEntity.packageName
}
positionAndPackageMap[packages + i] = i
}
}
super.setListData(updateData)
}
fun clearPositionAndPackageMap() {
positionAndPackageMap.clear()
}
override fun areItemsTheSame(oldItem: GameEntity?, newItem: GameEntity?): Boolean {
return oldItem?.id == newItem?.id
}
override fun getItemViewType(position: Int): Int {
if (position == itemCount - 1) {
return ItemViewType.ITEM_FOOTER
}
return ItemViewType.GAME_NORMAL
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ItemViewType.GAME_NORMAL -> {
GameItemViewHolder(parent.toBinding())
}
else -> {
FooterViewHolder(mLayoutInflater.inflate(R.layout.refresh_footerview, parent, false))
}
}
}
override fun getItemCount(): Int {
return if (mEntityList == null || mEntityList.isEmpty()) return 0 else mEntityList.size + 1
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is GameItemViewHolder) {
val gameEntity = mEntityList[position]
holder.bindGameItem(gameEntity)
holder.initServerType(gameEntity)
val padTop = if (position == 0) 16F.dip2px() else 8F.dip2px()
holder.itemView.setPadding(16F.dip2px(), padTop, 16F.dip2px(), 8F.dip2px())
val sortType = if ("download:-1" == mViewModel.getSortType()) "最热" else "最新"
val toolbarTitle = mViewModel.title
val categoryTitle = mViewModel.categoryTitle
val selectedCategoryName = mViewModel.selectedCategory.name ?: ""
val exposureSources = ArrayList<ExposureSource>()
exposureSources.add(ExposureSource(categoryTitle, selectedCategoryName))
exposureSources.add(ExposureSource("二级分类", "$selectedCategoryName+$sortType"))
val event = ExposureEvent.createEvent(gameEntity, exposureSources, null, ExposureType.EXPOSURE)
mExposureEventSparseArray.put(position, event)
holder.itemView.setOnClickListener {
GameDetailActivity.startGameDetailActivity(
mContext,
gameEntity,
StringUtils.buildString(
mEntrance,
"+(",
toolbarTitle,
":列表[",
selectedCategoryName,
"=",
sortType,
"=",
(position + 1).toString(),
"])"
),
traceEvent = event
)
}
DownloadItemUtils.setOnClickListener(
mContext,
holder.binding.downloadBtn,
gameEntity,
position,
this,
StringUtils.buildString(
StringUtils.buildString(
mEntrance,
"+(",
toolbarTitle,
":列表[",
selectedCategoryName,
"=",
sortType,
"=",
(position + 1).toString(),
"])"
)
),
location = StringUtils.buildString(selectedCategoryName, ":", gameEntity.name),
traceEvent = event
)
DownloadItemUtils.updateItem(mContext, gameEntity, GameViewHolder(holder.binding))
} else if (holder is FooterViewHolder) {
holder.initItemPadding()
holder.initFooterViewHolder(mViewModel, mIsLoading, mIsNetworkError, mIsOver)
}
}
fun notifyItemByDownload(download: DownloadEntity) {
for (key in positionAndPackageMap.keys) {
if (key.contains(download.packageName) && key.contains(download.gameId)) {
val position = positionAndPackageMap[key]
if (position != null && mEntityList != null && position < mEntityList.size) {
mEntityList[position].getEntryMap()[download.platform] = download
notifyItemChanged(position)
}
}
}
}
fun notifyItemAndRemoveDownload(status: EBDownloadStatus) {
for (key in positionAndPackageMap.keys) {
if (key.contains(status.packageName) && key.contains(status.gameId)) {
val position = positionAndPackageMap[key]
if (position != null && mEntityList != null && position < mEntityList.size) {
mEntityList[position].getEntryMap().remove(status.platform)
notifyItemChanged(position)
}
}
}
}
override fun getEventByPosition(pos: Int): ExposureEvent? {
return mExposureEventSparseArray.get(pos)
}
override fun getEventListByPosition(pos: Int): List<ExposureEvent>? {
return null
}
}

View File

@ -0,0 +1,198 @@
package com.gh.gamecenter.category
import android.os.Bundle
import android.view.View
import androidx.recyclerview.widget.LinearLayoutManager
import com.ethanhua.skeleton.Skeleton
import com.gh.common.exposure.ExposureListener
import com.gh.common.util.DialogUtils
import com.gh.common.view.ConfigFilterView
import com.gh.common.xapk.XapkInstaller
import com.gh.common.xapk.XapkUnzipStatus
import com.gh.download.DownloadManager
import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.baselist.ListAdapter
import com.gh.gamecenter.common.baselist.ListFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.observeNonNull
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.tryCatchInRelease
import com.gh.gamecenter.common.utils.viewModelProvider
import com.gh.gamecenter.databinding.FragmentTagsBinding
import com.gh.gamecenter.entity.CategoryEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.eventbus.EBPackage
import com.lightgame.download.DataWatcher
import com.lightgame.download.DownloadEntity
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class NewCategoryListFragment : ListFragment<GameEntity, NewCategoryListViewModel>() {
private var mPrimeCategory: CategoryEntity? = null
private var mSubCategoryList = arrayListOf<CategoryEntity>()
private val mBinding by lazy { FragmentTagsBinding.inflate(layoutInflater) }
private var mAdapter: NewCategoryListAdapter? = null
private val mDataWatcher = object : DataWatcher() {
override fun onDataChanged(downloadEntity: DownloadEntity) {
mAdapter?.notifyItemByDownload(downloadEntity)
if (downloadEntity.meta[XapkInstaller.XAPK_UNZIP_STATUS] == XapkUnzipStatus.FAILURE.name) {
showUnzipFailureDialog(downloadEntity)
}
}
override fun onDataInit(downloadEntity: DownloadEntity) {
mAdapter?.notifyItemByDownload(downloadEntity)
}
}
private lateinit var mExposureListener: ExposureListener
private lateinit var mViewModel: NewCategoryListViewModel
override fun onCreate(savedInstanceState: Bundle?) {
mViewModel = provideListViewModel()
mViewModel.title = arguments?.getString(EntranceConsts.KEY_NAME) ?: ""
mViewModel.categoryTitle = arguments?.getString(EntranceConsts.KEY_CATEGORY_TITLE) ?: ""
mEntrance = arguments?.getString(EntranceConsts.KEY_ENTRANCE) ?: Constants.ENTRANCE_UNKNOWN
mPrimeCategory = arguments?.getParcelable(EntranceConsts.KEY_DATA)
mSubCategoryList = mPrimeCategory?.data as? ArrayList<CategoryEntity> ?: arrayListOf()
val initSelectedCategory = arguments?.getString(EntranceConsts.KEY_CATEGORY_INIT_TITLE)
mViewModel.selectedCategory =
mSubCategoryList.find { categoryEntity -> categoryEntity.name == initSelectedCategory }
?: CategoryEntity()
super.onCreate(savedInstanceState)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
setNavigationTitle(mViewModel.title)
mViewModel.refresh.observeNonNull(this) { onRefresh() }
mBinding.configContainer.visibility = View.VISIBLE
mBinding.configContainer.updateAllTextView(mViewModel.sortType)
mBinding.configContainer.setOnConfigSetupListener(object : ConfigFilterView.OnConfigFilterSetupListener {
override fun onShowSortSize() {}
override fun onSetupSortSize(sortSize: SubjectSettingEntity.Size) {
mViewModel.updateSortConfig(sortSize = sortSize)
}
override fun onSetupSortType(sortType: ConfigFilterView.SortType) {
mViewModel.updateSortConfig(sortType = sortType)
}
})
mExposureListener = ExposureListener(this, mAdapter!!)
mListRv.addOnScrollListener(mExposureListener)
updateCategoriesView(mSubCategoryList)
mSkeletonScreen =
Skeleton.bind(mBinding.listSkeleton).shimmer(false).load(R.layout.fragment_subject_skeleton).show()
}
override fun onResume() {
super.onResume()
DownloadManager.getInstance().addObserver(mDataWatcher)
}
override fun onPause() {
super.onPause()
DownloadManager.getInstance().removeObserver(mDataWatcher)
}
override fun getItemDecoration() = null
override fun getLayoutId() = 0
override fun getInflatedLayout() = mBinding.root
override fun provideListViewModel(): NewCategoryListViewModel {
return viewModelProvider()
}
override fun provideListAdapter(): ListAdapter<GameEntity> {
return mAdapter
?: NewCategoryListAdapter(requireContext(), mViewModel, mEntrance).apply { mAdapter = this }
}
private fun updateCategoriesView(categoryList: ArrayList<CategoryEntity>) {
mBinding.tagsRecyclerView.layoutManager =
LinearLayoutManager(requireContext(), LinearLayoutManager.HORIZONTAL, false)
mBinding.tagsRecyclerView.adapter = NewCategoryHorizontalAdapter(requireContext(), mViewModel, categoryList) {
mBinding.tagsRecyclerView.smoothScrollToPosition(it)
}
// 调整选中标签位置
for (index in 0 until categoryList.size) {
if (categoryList[index].name == mViewModel.selectedCategory.name) {
mBinding.tagsRecyclerView.postDelayed({
tryCatchInRelease { mBinding.tagsRecyclerView.smoothScrollToPosition(index) }
}, 200)
break
}
}
}
override fun onRefresh() {
mAdapter?.clearPositionAndPackageMap()
super.onRefresh()
}
// 下载被删除事件
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(status: EBDownloadStatus) {
if ("delete" == status.status) {
mAdapter?.notifyItemAndRemoveDownload(status)
}
}
// 安装/卸载 事件
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(busFour: EBPackage) {
if (busFour.isInstalledOrUninstalled()) {
mAdapter?.notifyDataSetChanged()
}
}
fun showUnzipFailureDialog(downloadEntity: DownloadEntity) {
val data = mAdapter?.positionAndPackageMap ?: return
for (gameAndPosition in data) {
if (gameAndPosition.key.contains(downloadEntity.packageName)) {
val targetView = mLayoutManager.findViewByPosition(gameAndPosition.value)
if (targetView != null) {
DialogUtils.showUnzipFailureDialog(requireContext(), downloadEntity)
return
}
}
}
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
mBinding.run {
divider.setBackgroundColor(R.color.ui_background.toColor(requireContext()))
tagsRecyclerView.setBackgroundColor(R.color.ui_surface.toColor(requireContext()))
tagsRecyclerView.adapter?.run {
notifyItemRangeChanged(0, itemCount)
}
configContainer.run {
container.setBackgroundColor(R.color.ui_surface.toColor(requireContext()))
updateAllTextView(mViewModel.sortType)
updatePopupWindow()
}
}
}
}

View File

@ -0,0 +1,81 @@
package com.gh.gamecenter.category
import android.app.Application
import androidx.lifecycle.MutableLiveData
import com.gh.common.exposure.ExposureUtils
import com.gh.gamecenter.core.utils.UrlFilterUtils
import com.gh.common.view.ConfigFilterView
import com.gh.gamecenter.common.baselist.ListViewModel
import com.gh.gamecenter.entity.CategoryEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.entity.SubjectSettingEntity
import com.gh.gamecenter.retrofit.RetrofitManager
import io.reactivex.Observable
import io.reactivex.Single
class NewCategoryListViewModel(application: Application) : ListViewModel<GameEntity, GameEntity>(application) {
var title = "" // 显示在 Toolbar 的标题
var categoryTitle = "" // 跳转进来的分类的标题
val refresh = MutableLiveData<Boolean>()
var selectedCategory = CategoryEntity()
var sortType = ConfigFilterView.SortType.RECOMMENDED
private var mSortSize = SubjectSettingEntity.Size()
override fun provideDataObservable(page: Int): Observable<List<GameEntity>>? {
return null
}
override fun provideDataSingle(page: Int): Single<List<GameEntity>> {
return RetrofitManager.getInstance()
.api
.getGamesInCategory(selectedCategory.id, getSortType(), getSortSize(), page)
}
override fun mergeResultLiveData() {
mResultLiveData.addSource(mListLiveData) {
ExposureUtils.updateExposureInfo(it)
mResultLiveData.postValue(it)
}
}
fun changeSelectedCategory(category: CategoryEntity) {
if (selectedCategory != category) {
selectedCategory = category
refresh.postValue(true)
}
}
fun updateSortConfig(
sortSize: SubjectSettingEntity.Size? = null,
sortType: ConfigFilterView.SortType? = null
) {
if (sortSize != null && sortSize != mSortSize) {
mSortSize = sortSize
refresh.postValue(true)
} else if (sortType != null && sortType != this.sortType) {
this.sortType = sortType
refresh.postValue(true)
}
}
private fun getSortSize(): String? {
return UrlFilterUtils.getFilterQuery(
"min_size", mSortSize.min.toString(),
"max_size", mSortSize.max.toString()
)
}
fun getSortType(): String? {
return if (sortType == ConfigFilterView.SortType.RECOMMENDED) {
"download:-1"
} else {
"publish:-1"
}
}
}

View File

@ -161,7 +161,6 @@ class DownloadFragment : BaseFragment_TabLayout() {
val showOnFailed = downloadManagerAd?.displayRule?.onFailedAction == "show"
if ((showThirdPartyAd && thirdPartyAd != null) || (!showThirdPartyAd && thirdPartyAd != null && ownerAd == null)) {
initThirdPartyAd(thirdPartyAd) { isSuccess ->
if (!isAdded) return@initThirdPartyAd
mBinding.maskView.goneIf(!isSuccess)
if (!isSuccess && ownerAd != null && showOnFailed) {
mSlideInterval = ownerAd.adSource?.sliderInterval ?: -1
@ -194,7 +193,7 @@ class DownloadFragment : BaseFragment_TabLayout() {
}
private fun initOwnerAd(adConfig: AdConfig) {
if (!isAdded || adConfig.id.isEmpty()) return
if (adConfig.id.isEmpty()) return
mAdGameViewModel = viewModelProvider(AdGameViewModel.Factory(adConfig))
initAdGameBanner(adConfig)
mBinding.closeAdIv.setOnClickListener {
@ -228,7 +227,6 @@ class DownloadFragment : BaseFragment_TabLayout() {
adConfig.displayRule.onFailedAction == "show" && adConfig.thirdPartyAd != null) {
// 自有广告游戏为空时,显示第三方广告
initThirdPartyAd(adConfig.thirdPartyAd) { isSuccess ->
if (!isAdded) return@initThirdPartyAd
mBinding.maskView.goneIf(!isSuccess)
if (isSuccess) {
SPUtils.setLong(Constants.SP_LAST_DOWNLOAD_MANAGER_AD_SHOW_TIME, System.currentTimeMillis())

View File

@ -6,6 +6,40 @@ import com.gh.gamecenter.feature.entity.GameEntity
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
data class CatalogEntity(
@SerializedName("_id")
var id: String = "",
var name: String = "",
var switch: CatalogSwitch = CatalogSwitch(),
@SerializedName("has_special")
var hasSpecial: Boolean = false,
@SerializedName("sub_catalogs")
var subCatalog: List<SubCatalogEntity> = emptyList()
) : Parcelable {
@Parcelize
data class SubCatalogEntity(
@SerializedName("_id")
var id: String = "",
var name: String = "",
var icon: String = "",
var type: String = "",
var link: LinkEntity = LinkEntity(),
var recommended: Boolean = false
) : Parcelable
@Parcelize
data class CatalogSwitch(
@SerializedName("sort_hot")
var hotSort: String = "",
@SerializedName("sort_new")
var newSort: String = "",
@SerializedName("sort_star")
var starSort: String = ""
) : Parcelable
}
@Parcelize
class SpecialCatalogEntity(

View File

@ -73,6 +73,8 @@ data class SubjectRecommendEntity(
"column" -> "专题"
"column_collection" -> "专题合集"
"block" -> "版块"
"category" -> "分类"
"catalog" -> "新分类"
"category_v2" -> "新分类2.0"
"column_test" -> "开测表"
"server" -> "开服表"

View File

@ -11,6 +11,8 @@ import androidx.fragment.app.Fragment
import androidx.viewpager.widget.ViewPager
import com.gh.gamecenter.R
import com.gh.gamecenter.amway.AmwayFragment
import com.gh.gamecenter.catalog.CatalogFragment
import com.gh.gamecenter.category.CategoryDirectoryFragment
import com.gh.gamecenter.category2.CategoryV2Fragment
import com.gh.gamecenter.common.base.adapter.FragmentAdapter
import com.gh.gamecenter.common.constant.EntranceConsts
@ -157,6 +159,16 @@ abstract class HomeTabWrapperFragment : SearchToolWrapperFragment() {
putString(GameServersTestFragment.TEST_COLUMN_ID, tabEntity.link)
})
"category" -> CategoryDirectoryFragment().with(Bundle().apply {
putString(EntranceConsts.KEY_CATEGORY_ID, tabEntity.link)
putString(EntranceConsts.KEY_CATEGORY_TITLE, tabEntity.text)
})
"catalog" -> CatalogFragment().with(Bundle().apply {
putString(EntranceConsts.KEY_CATALOG_ID, tabEntity.link)
putString(EntranceConsts.KEY_CATALOG_TITLE, tabEntity.text)
})
"category_v2" -> CategoryV2Fragment().with(Bundle().apply {
putString(EntranceConsts.KEY_CATEGORY_ID, tabEntity.link)
putString(EntranceConsts.KEY_CATEGORY_TITLE, tabEntity.text)

View File

@ -26,6 +26,7 @@ import com.gh.gamecenter.adapter.viewholder.GameHeadViewHolder
import com.gh.gamecenter.adapter.viewholder.GameImageViewHolder
import com.gh.gamecenter.adapter.viewholder.GameViewHolder
import com.gh.gamecenter.adapter.viewholder.GameViewPagerViewHolder
import com.gh.gamecenter.category.CategoryDirectoryActivity
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.callback.OnViewClickListener
import com.gh.gamecenter.common.constant.EntranceConsts
@ -979,6 +980,14 @@ class GameFragmentAdapter(
)
)
"category" -> mContext.startActivity(
CategoryDirectoryActivity.getIntent(
mContext,
entity.link!!,
entity.text!!
)
)
"column" -> {
SubjectActivity.startSubjectActivity(
mContext,

View File

@ -2,8 +2,8 @@ package com.gh.gamecenter.game.columncollection.detail
import android.content.Intent
import android.os.Bundle
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.activity.ToolBarActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.updateStatusBarColor
@ -34,8 +34,8 @@ class ColumnCollectionDetailActivity : ToolBarActivity() {
}
override fun getBusinessId(): Pair<String, String> {
val fragment = targetFragment as? ColumnCollectionDetailFragment?
return if (fragment?.arguments != null) {
val fragment = targetFragment as ColumnCollectionDetailFragment
return if (fragment.arguments != null) {
Pair(fragment.requireArguments().getString(EntranceConsts.KEY_COLLECTION_ID) ?: "", "")
} else {
super.getBusinessId()

View File

@ -36,8 +36,8 @@ class CommonCollectionDetailActivity : ToolBarActivity() {
}
override fun getBusinessId(): Pair<String, String> {
val fragment = targetFragment as? CommonCollectionDetailFragment?
return if (fragment?.arguments != null) {
val fragment = targetFragment as CommonCollectionDetailFragment
return if (fragment.arguments != null) {
Pair(
fragment.requireArguments().getString(EntranceConsts.KEY_COLLECTION_ID) ?: "",
fragment.requireArguments().getString(EntranceConsts.KEY_BLOCK_ID) ?: ""

View File

@ -36,8 +36,8 @@ class CustomCommonCollectionDetailActivity : ToolBarActivity() {
}
override fun getBusinessId(): Pair<String, String> {
val fragment = targetFragment as? CustomCommonCollectionDetailFragment?
return if (fragment?.arguments != null) {
val fragment = targetFragment as CustomCommonCollectionDetailFragment
return if (fragment.arguments != null) {
Pair(
fragment.requireArguments().getString(EntranceConsts.KEY_COLLECTION_ID) ?: "",
fragment.requireArguments().getString(EntranceConsts.KEY_BLOCK_ID) ?: ""

View File

@ -182,9 +182,7 @@ class AddGamesDialogFragment : BaseDialogFragment() {
return super.onBack()
}
private fun showGuidePopupWindow(): PopupWindow? {
if (!isAdded) return null
private fun showGuidePopupWindow(): PopupWindow {
val guideBinding = LayoutGameCollectionAddGamesGuideBinding.inflate(layoutInflater)
val popupWindow = BugFixedPopupWindow(
guideBinding.root,

View File

@ -7,8 +7,8 @@ import android.view.View
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.activity.ToolBarActivity
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.common.exposure.ExposureSource
/**
* 游戏单详情
@ -30,8 +30,8 @@ class GameCollectionDetailActivity : ToolBarActivity() {
}
override fun getBusinessId(): Pair<String, String> {
val fragment = targetFragment as? GameCollectionDetailFragment?
return if (fragment?.arguments != null) {
val fragment = targetFragment as GameCollectionDetailFragment
return if (fragment.arguments != null) {
Pair(fragment.requireArguments().getString(EntranceConsts.KEY_GAME_COLLECTION_ID) ?: "", "")
} else {
super.getBusinessId()

View File

@ -149,6 +149,8 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
private var mShowConcernOnMenu = false
private var mSpecialDownloadDetailFragmentIsShowing = false
private val mFragmentsList = ArrayList<Fragment>()
private val mTabTitleList = ArrayList<String>()
private val mTabTypeList = ArrayList<String>() // tab 类型的列表,用于确定某个类型的 tab 在第几个位置
@ -165,7 +167,7 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
|| downloadEntity.status == DownloadStatus.redirected
) {
// 特殊下载弹窗
if (isSpecialDownloadDialogAvailable(downloadEntity) && !isSpecialDownloadDetailFragmentIsShowing()) {
if (isSpecialDownloadDialogAvailable() && !mSpecialDownloadDetailFragmentIsShowing) {
updateSpecialDownloadDialogIcon(true)
if (downloadEntity.status == DownloadStatus.add) {
@ -866,10 +868,8 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
showConcernIconAtBottomBarIfAvailable()
val downloadEntitySnapshot = DownloadManager.getInstance().getDownloadEntitySnapshot(mGameEntity)
if (isSpecialDownloadDialogAvailable(downloadEntitySnapshot)
&& downloadEntitySnapshot != null) {
if (isSpecialDownloadDialogAvailable()
&& DownloadManager.getInstance().getDownloadEntitySnapshot(mGameEntity) != null) {
updateSpecialDownloadDialogIcon(true)
}
@ -2562,15 +2562,12 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
* 2. 未获取到游戏详情数据
* 3. 当前游戏 APK 不为 1 个
* 4. 当前游戏类型不为畅玩
* 5. 当前游戏不是双下载时使用本地下载进行下载
*/
private fun isSpecialDownloadDialogAvailable(downloadEntity: DownloadEntity? = null): Boolean {
private fun isSpecialDownloadDialogAvailable(): Boolean {
if (Config.getNewApiSettingsEntity()?.install?.questionTip?.linkEntity == null) return false
if (mNewGameDetailEntity == null || mGameEntity == null) return false
if (mGameEntity?.getApk()?.size != 1) return false
if (downloadEntity?.asVGame() == true) return false
if (downloadEntity?.isSimulatorGame() == true) return false
if (downloadEntity?.isLocalDownloadInDualDownloadMode() == true) return false
if (GameUtils.shouldPerformAsVGame(mGameEntity!!)) return false
return true
}
@ -2588,6 +2585,7 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
if (fragment == null) {
if (!visibilityViewModel.specialDownloadVisibleLiveData.hasObservers()) {
visibilityViewModel.specialDownloadVisibleLiveData.observe(viewLifecycleOwner) {
mSpecialDownloadDetailFragmentIsShowing = it
updateSpecialDownloadDialogIcon(visible = !it)
}
}
@ -2603,10 +2601,6 @@ class GameDetailFragment : BaseLazyFragment(), IScrollable {
}
}
private fun isSpecialDownloadDetailFragmentIsShowing(): Boolean {
return childFragmentManager.findFragmentByTag(TAG_SPECIAL_DOWNLOAD_DIALOG) != null
}
override fun scrollToTop() {
val fragment = mFragmentsList.safelyGetInRelease(mBodyBinding.gamedetailVp.currentItem)
if (fragment is IScrollable && fragment.isAdded) {

View File

@ -394,7 +394,7 @@ class SpecialDownloadDialogFragment : BaseDraggableDialogFragment() {
SensorsBridge.trackDownloadComponentsContentClick(
gameId = gameEntity.id,
gameName = gameEntity.name ?: "unknown",
gameSchemaType = gameEntity.gameBitChinese,
gameSchemeType = gameEntity.gameBitChinese,
downloadStatus = gameEntity.downloadStatusChinese,
gameType = gameEntity.categoryChinese,
downloadType = if (asVGame) "畅玩下载" else "本地下载",
@ -405,7 +405,7 @@ class SpecialDownloadDialogFragment : BaseDraggableDialogFragment() {
SensorsBridge.trackDownloadComponentsShow(
gameId = gameEntity.id,
gameName = gameEntity.name ?: "unknown",
gameSchemaType = gameEntity.gameBitChinese,
gameSchemeType = gameEntity.gameBitChinese,
downloadStatus = gameEntity.downloadStatusChinese,
gameType = gameEntity.categoryChinese,
downloadType = if (asVGame) "畅玩下载" else "本地下载",

View File

@ -17,7 +17,6 @@ import com.gh.common.iinterface.ISuperiorChain
import com.gh.common.prioritychain.CustomFloatingWindowHandler
import com.gh.common.prioritychain.PriorityChain
import com.gh.common.prioritychain.PullDownPushHandler
import com.gh.common.prioritychain.VideoHandler
import com.gh.common.util.DefaultSearchHintHelper
import com.gh.common.util.DialogUtils
import com.gh.common.util.DirectUtils
@ -469,10 +468,10 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable {
binding = FragmentCustomBinding.bind(mCachedView)
scrollCalculatorHelper = ScrollCalculatorHelper(binding.gameList, R.id.autoVideoView, 0, false)
buildPriorityChain()
scrollCalculatorHelper = ScrollCalculatorHelper(binding.gameList, R.id.autoVideoView, 0)
adapter = CustomPageAdapter(
viewModel,
viewLifecycleOwner,
@ -513,11 +512,9 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable {
private fun buildPriorityChain() {
val floatingWindowHandler = CustomFloatingWindowHandler(23)
val videoHandler = VideoHandler(24, scrollCalculatorHelper)
priorityChain.addHandler(pullDownPushHandler)
priorityChain.addHandler(floatingWindowHandler)
priorityChain.addHandler(videoHandler)
(parentFragment as? BaseTabWrapperFragment)?.addTabGuideHandlerIfExists(priorityChain)
viewModel.floatingWindows.observe(viewLifecycleOwner, EventObserver {

View File

@ -5,6 +5,7 @@ import android.os.Handler
import android.os.Looper
import android.view.View
import android.view.ViewGroup
import androidx.core.view.forEach
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.constant.Constants
@ -14,28 +15,13 @@ import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.video.detail.CustomManager
import com.halo.assistant.HaloApp
class ScrollCalculatorHelper(
val mListRv: RecyclerView,
private val mPlayId: Int,
private val mRangeTop: Int,
private var isEnabled: Boolean = true
) {
class ScrollCalculatorHelper(val mListRv: RecyclerView, private val mPlayId: Int, private val mRangeTop: Int) {
private var mFirstVisible = -1
private var mLastVisible = 0
private var mRunnable: PlayRunnable? = null
private val mPlayHandler = Handler(Looper.getMainLooper())
var currentPlayer: AutomaticVideoView? = null
fun enableAndPlayIfValid() {
isEnabled = true
if (mListRv.isAttachedToWindow
&& mListRv.scrollState == RecyclerView.SCROLL_STATE_IDLE) {
playVideo(mListRv)
}
}
fun onScrollStateChanged(scrollState: Int) {
if (scrollState == RecyclerView.SCROLL_STATE_IDLE) {
playVideo(mListRv)
@ -94,7 +80,7 @@ class ScrollCalculatorHelper(
}
private fun playVideo(view: RecyclerView?) {
if (view == null || !view.isAttachedToWindow || !isEnabled) return
if (view == null) return
val layoutManager = view.layoutManager
var gsyBaseVideoPlayer: AutomaticVideoView
for (i in mFirstVisible until mLastVisible + 1) {

View File

@ -13,7 +13,7 @@ object HeadUpDisplayLogHelper {
source = source,
downloadType = if (downloadEntity.asVGame()) "畅玩下载" else "本地下载",
downloadStatus = downloadEntity.getMetaExtra(Constants.DOWNLOAD_STATUS_IN_CHINESE),
gameSchemaType = if (downloadEntity.getMetaExtra(Constants.KEY_BIT) == "32") "32位" else "64位",
gameSchemeType = if (downloadEntity.getMetaExtra(Constants.KEY_BIT) == "32") "32位" else "64位",
gameType = downloadEntity.getMetaExtra(Constants.GAME_CATEGORY_IN_CHINESE),
gameId = downloadEntity.gameId,
gameName = downloadEntity.name
@ -25,7 +25,7 @@ object HeadUpDisplayLogHelper {
source = source,
downloadType = if (downloadEntity.asVGame()) "畅玩下载" else "本地下载",
downloadStatus = downloadEntity.getMetaExtra(Constants.DOWNLOAD_STATUS_IN_CHINESE),
gameSchemaType = if (downloadEntity.getMetaExtra(Constants.KEY_BIT) == "32") "32位" else "64位",
gameSchemeType = if (downloadEntity.getMetaExtra(Constants.KEY_BIT) == "32") "32位" else "64位",
gameType = downloadEntity.getMetaExtra(Constants.GAME_CATEGORY_IN_CHINESE),
gameId = downloadEntity.gameId,
gameName = downloadEntity.name
@ -36,7 +36,7 @@ object HeadUpDisplayLogHelper {
SensorsBridge.trackAutomaticInstallationPromptBarShow(
downloadType = if (downloadEntity.asVGame()) "畅玩下载" else "本地下载",
downloadStatus = downloadEntity.getMetaExtra(Constants.DOWNLOAD_STATUS_IN_CHINESE),
gameSchemaType = if (downloadEntity.getMetaExtra(Constants.KEY_BIT) == "32") "32位" else "64位",
gameSchemeType = if (downloadEntity.getMetaExtra(Constants.KEY_BIT) == "32") "32位" else "64位",
gameType = downloadEntity.getMetaExtra(Constants.GAME_CATEGORY_IN_CHINESE),
gameId = downloadEntity.gameId,
gameName = downloadEntity.name
@ -47,7 +47,7 @@ object HeadUpDisplayLogHelper {
SensorsBridge.trackAutomaticInstallationPromptBarClick(
downloadType = if (downloadEntity.asVGame()) "畅玩下载" else "本地下载",
downloadStatus = downloadEntity.getMetaExtra(Constants.DOWNLOAD_STATUS_IN_CHINESE),
gameSchemaType = if (downloadEntity.getMetaExtra(Constants.KEY_BIT) == "32") "32位" else "64位",
gameSchemeType = if (downloadEntity.getMetaExtra(Constants.KEY_BIT) == "32") "32位" else "64位",
gameType = downloadEntity.getMetaExtra(Constants.GAME_CATEGORY_IN_CHINESE),
gameId = downloadEntity.gameId,
gameName = downloadEntity.name

View File

@ -130,24 +130,6 @@ object PackagesManager {
return null
}
/**
* 根据游戏 ID 获取已安装的信息
*
* @param gameId 游戏 Id
* @return 如果为空:未安装
*/
fun getInstalledDataByGameId(gameId: String?): GameInstall? {
if (TextUtils.isEmpty(gameId)) {
return null
}
for (gameInstall in mInstalledList) {
if (gameInstall.id == gameId) {
return gameInstall
}
}
return null
}
/**
* 判断包名是否可以更新

View File

@ -515,8 +515,8 @@ public class NewsDetailAdapter extends BaseRecyclerAdapter<ViewHolder> {
TextHelper.highlightTextThatIsWrappedInsideWrapperByDefault(
viewHolder.commentContentTv,
commentEntity.getContent(),
Constants.DEFAULT_TEXT_WRAPPER,
new TextHelper.DirectToWebViewHighlightedTextClick(mContext, mEntrance)
Constants.DEFAULT_TEXT_WRAPPER_2,
new TextHelper.CopyToClipboardHighlightedTextClick()
);
ArticleCommentParent parent = commentEntity.getParent();
if (parent != null && !TextUtils.isEmpty(parent.getUser().getName())) {
@ -540,8 +540,8 @@ public class NewsDetailAdapter extends BaseRecyclerAdapter<ViewHolder> {
TextHelper.highlightTextThatIsWrappedInsideWrapperByDefault(
viewHolder.quoteContentTv,
content,
Constants.DEFAULT_TEXT_WRAPPER,
new TextHelper.DirectToWebViewHighlightedTextClick(mContext, mEntrance)
Constants.DEFAULT_TEXT_WRAPPER_2,
new TextHelper.CopyToClipboardHighlightedTextClick()
);
} else {
viewHolder.quoteContainer.setVisibility(View.GONE);

View File

@ -11,9 +11,11 @@ import com.gh.gamecenter.room.AppDatabase
import com.halo.assistant.HaloApp
import io.reactivex.schedulers.Schedulers
class PackageFilterManager {
object PackageFilterManager {
private var mPackageKey = "" // 用于下次获取包名-游戏信息的 key
private val mPendingPackageNameSet by lazy { hashSetOf<String>() } // 因遇到异常而等待下次操作更新的包名列表
private val mValidPackageNameSet by lazy { hashSetOf<String>() } // 已被收录的游戏包名列表
val packageKey: String
get() = mPackageKey
@ -32,7 +34,7 @@ class PackageFilterManager {
) {
if (appendOnly) {
// 添加因为异常而没能正常更新的包名列表
packageList.addAll(PackageRepository.mPendingPackageNameSet)
packageList.addAll(mPendingPackageNameSet)
}
RetrofitManager.getInstance()
@ -42,12 +44,12 @@ class PackageFilterManager {
.subscribe(object : BiResponse<PackageFilter>() {
override fun onSuccess(data: PackageFilter) {
mPackageKey = data.key
PackageRepository.mPendingPackageNameSet.clear()
mPendingPackageNameSet.clear()
val partialPackageList = arrayListOf<String>()
if (!appendOnly) {
PackageRepository.mValidPackageNameSet.clear()
mValidPackageNameSet.clear()
tryWithDefaultCatch {
AppDatabase.getInstance().packageFilterDao().deleteAllPackageName()
}
@ -61,13 +63,13 @@ class PackageFilterManager {
}
partialPackageList.add(packageName)
PackageRepository.mValidPackageNameSet.add(packageName)
mValidPackageNameSet.add(packageName)
}
if (appendOnly) {
callbackClosure?.invoke(ArrayList(partialPackageList))
} else {
callbackClosure?.invoke(ArrayList(PackageRepository.mValidPackageNameSet))
callbackClosure?.invoke(ArrayList(mValidPackageNameSet))
}
}
@ -75,7 +77,7 @@ class PackageFilterManager {
super.onFailure(exception)
if (appendOnly) {
PackageRepository.mPendingPackageNameSet.addAll(packageList)
mPendingPackageNameSet.addAll(packageList)
} else {
if (exception is retrofit2.HttpException && exception.code() == 403) {
// 403 代表 key 过期,需要重新获取
@ -89,13 +91,28 @@ class PackageFilterManager {
for (packageEntity in packageEntityList) {
// 依然为已安装状态才加入到有效包名列表中
if (PackageUtils.isInstalled(HaloApp.getInstance(), packageEntity.packageName)) {
PackageRepository.mValidPackageNameSet.add(packageEntity.packageName)
mValidPackageNameSet.add(packageEntity.packageName)
}
}
callbackClosure?.invoke(ArrayList(PackageRepository.mValidPackageNameSet))
callbackClosure?.invoke(ArrayList(mValidPackageNameSet))
}
}
})
}
/**
* 该包名是否有效 (即是否已被光环收录)
*/
fun isPackageValid(packageName: String): Boolean {
return mValidPackageNameSet.contains(packageName)
}
/**
* 是否存在因为接口异常而导致没有查询收录情况的包名列表
*/
fun hasPendingPackage(): Boolean {
return mPendingPackageNameSet.isEmpty()
}
}

View File

@ -6,6 +6,7 @@ import android.text.TextUtils
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.gh.common.filter.RegionSettingHelper
import com.gh.gamecenter.feature.utils.ApkActiveUtils
import com.gh.common.util.GameUtils
import com.gh.common.util.PackageUtils
import com.gh.gamecenter.R
@ -15,29 +16,19 @@ import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.retrofit.ObservableUtil
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.common.utils.secondOrNull
import com.gh.gamecenter.common.utils.toArrayList
import com.gh.gamecenter.common.utils.tryCatchInRelease
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.entity.AppEntity
import com.gh.gamecenter.entity.GameUpdateEntity
import com.gh.gamecenter.entity.PackageGame
import com.gh.gamecenter.entity.*
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.GameInstall
import com.gh.gamecenter.feature.utils.ApkActiveUtils
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.manager.PackagesManager
import com.gh.gamecenter.packagehelper.PackageRepository.gameInstalled
import com.gh.gamecenter.packagehelper.PackageRepository.gameUpdate
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.vspace.VHelper
import com.halo.assistant.HaloApp
import com.lightgame.utils.Utils
import io.reactivex.Single
import io.reactivex.SingleObserver
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import org.greenrobot.eventbus.EventBus
import org.json.JSONException
@ -83,10 +74,6 @@ object PackageRepository {
fun changeRecentVaPlayed() {
_recentVaPlayedChanged.postValue(Unit)
}
val packageFilterManager = PackageFilterManager()
var mPendingPackageNameSet = hashSetOf<String>() // 因遇到异常而等待下次操作更新的包名列表
var mValidPackageNameSet = hashSetOf<String>() // 已被收录的游戏包名列表
/**
* 预留方法,如果想手动初始化可以调用
@ -102,51 +89,18 @@ object PackageRepository {
if (gameUpdate.isNotEmpty()) gameUpdate.clear()
if (mInstalledPkgSet.isNotEmpty()) mInstalledPkgSet.clear()
Single.zip<Result<Any?>, Result<Any?>, Result<Any?>>(
Single.create { emitter ->
val list = PackageUtils.getAllPackageName(mApplication)
uploadAppList()
initFilterPackage(list) { filteredList ->
emitter.onSuccess(Result.success(null))
val list = PackageUtils.getAllPackageName(mApplication)
mInstalledPkgSet.addAll(filteredList)
notifyInstallPkgData()
loadInstalledGameDigestAndNotifyData(packageFilterManager.packageKey, filteredList)
}
uploadAppList()
initFilterPackage(list) { filteredList ->
mIsInitialisingData = false
},
Single.create { emitter ->
// 畅玩游戏更新
var allGames = VHelper.getAllVGameSnapshots()
if (allGames.isEmpty()) {
VHelper.refreshVGameSnapshot()
allGames = VHelper.getAllVGameSnapshots()
}
val allGamePkgNames = allGames.map { it.packageName }.toArrayList()
if (allGamePkgNames.isNotEmpty()) {
val packageFilterManager = PackageFilterManager()
updateFilterPackage(packageFilterManager, allGamePkgNames) {
emitter.onSuccess(Result.success(null))
loadInstalledGameDigestAndNotifyData(
packageKey = packageFilterManager.packageKey,
filteredList = allGamePkgNames,
isVGame = true
)
}
}
}
) { t1, t2 -> Result.success(null) }.subscribe(object : SingleObserver<Result<Any?>> {
override fun onSubscribe(d: Disposable) {
}
mInstalledPkgSet.addAll(filteredList)
notifyInstallPkgData()
override fun onSuccess(t: Result<Any?>) {
mIsInitialisingData = false
}
override fun onError(e: Throwable) {
}
})
loadInstalledGameDigestAndNotifyData(filteredList)
}
loadGhzsUpdate()
}
@ -159,15 +113,18 @@ object PackageRepository {
list: MutableList<String>,
callbackClosure: ((list: ArrayList<String>) -> Unit)? = null
) {
packageFilterManager.updateFilterPackages(list, false, callbackClosure)
PackageFilterManager.updateFilterPackages(list, false) {
callbackClosure?.invoke(it)
}
}
private fun updateFilterPackage(
packageFilterManager: PackageFilterManager,
list: MutableList<String>,
callbackClosure: ((list: ArrayList<String>) -> Unit)? = null
) {
packageFilterManager.updateFilterPackages(list, true, callbackClosure)
PackageFilterManager.updateFilterPackages(list, true) {
callbackClosure?.invoke(it)
}
}
/**
@ -178,8 +135,7 @@ object PackageRepository {
PackageUtils.getGhVersionName(),
PackageUtils.getGhVersionCode(),
HaloApp.getInstance().channel,
Build.VERSION.SDK_INT
)
Build.VERSION.SDK_INT)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Response<AppEntity>() {
@ -252,7 +208,6 @@ object PackageRepository {
*/
@SuppressLint("CheckResult")
private fun loadInstalledGameDigestAndNotifyData(
packageKey: String,
filteredList: ArrayList<String>,
isVGame: Boolean = false,
updateInstallStatus: Boolean = false
@ -267,7 +222,7 @@ object PackageRepository {
}, Any())
while (++page <= maxPageCount) {
var observable = mNewApi.getPackageGames(packageKey, page, PAGE_SIZE)
var observable = mNewApi.getPackageGames(PackageFilterManager.packageKey, page, PAGE_SIZE)
.subscribeOn(Schedulers.io())
observable.subscribe(object : BiResponse<List<PackageGame>>() {
@ -283,8 +238,7 @@ object PackageRepository {
}
for (game in validGames) {
val shouldNotifyChanges =
validateGameAndPostChanges(gh_id, game, pkgName, isVGame, updateInstallStatus)
val shouldNotifyChanges = validateGameAndPostChanges(gh_id, game, pkgName, isVGame, updateInstallStatus)
if (!isNotifyUpdate && shouldNotifyChanges) {
isNotifyUpdate = true
}
@ -305,13 +259,11 @@ object PackageRepository {
/**
* 验证游戏并且更新数据
*/
private fun validateGameAndPostChanges(
ghId: Any?,
game: GameEntity,
pkgName: String,
isVGame: Boolean,
updateInstallStatus: Boolean
): Boolean {
private fun validateGameAndPostChanges(ghId: Any?,
game: GameEntity,
pkgName: String,
isVGame: Boolean,
updateInstallStatus: Boolean): Boolean {
if (ghId == null || ghId == game.id) {
gameInstalled.add(GameInstall.transformGameInstall(game, pkgName, isVGame))
mInstalledGameList.add(game)
@ -320,8 +272,7 @@ object PackageRepository {
addCurrentlyInstalledVersionIfValid(game)
if (updateInstallStatus) {
EventBus.getDefault()
.post(EBPackage(EBPackage.TYPE_INSTALLED, pkgName, game.getApk().firstOrNull()?.version))
EventBus.getDefault().post(EBPackage(EBPackage.TYPE_INSTALLED, pkgName, game.getApk().firstOrNull()?.version))
}
if (isCanUpdate || isCanPluggable) {
@ -405,8 +356,7 @@ object PackageRepository {
) {
// 使用了镜像的游戏;插件化关闭的游戏;无需插件化
if (game.shouldUseMirrorInfo()
|| apk.plugin == "close"
) {
|| apk.plugin == "close") {
return false
}
@ -466,11 +416,8 @@ object PackageRepository {
* @param pkgName 已安装的游戏包名
* @param cachedGameEntity 缓存的游戏实体,若存在免去再请求接口的过程
*/
fun addInstalledGame(
packageFilterManager: PackageFilterManager,
pkgName: String,
cachedGameEntity: GameEntity? = null
) {
fun addInstalledGame(pkgName: String,
cachedGameEntity: GameEntity? = null) {
mInstalledPkgSet.add(pkgName)
notifyInstallPkgData()
@ -481,8 +428,7 @@ object PackageRepository {
game = cachedGameEntity,
pkgName = pkgName,
isVGame = false,
updateInstallStatus = false
)
updateInstallStatus = false)
if (containsUpdate) {
notifyGameUpdateData()
@ -490,9 +436,8 @@ object PackageRepository {
} else {
val list = arrayListOf(pkgName)
updateFilterPackage(packageFilterManager, list) {
updateFilterPackage(list) {
loadInstalledGameDigestAndNotifyData(
packageKey = packageFilterManager.packageKey,
filteredList = list,
updateInstallStatus = false
)
@ -508,11 +453,9 @@ object PackageRepository {
* @param isVGame 是否为畅玩游戏
* @param updateInstallStatus 是否更新安装状态
*/
fun addInstalledGames(
packageFilterManager: PackageFilterManager,
pkgNameList: ArrayList<String>,
isVGame: Boolean = false,
updateInstallStatus: Boolean = false,
fun addInstalledGames(pkgNameList: ArrayList<String>,
isVGame: Boolean = false,
updateInstallStatus: Boolean = false,
) {
// 畅玩游戏不添加至本地的已安装包名列表中
if (!isVGame) {
@ -522,9 +465,8 @@ object PackageRepository {
}
notifyInstallPkgData()
updateFilterPackage(packageFilterManager, pkgNameList) {
updateFilterPackage(pkgNameList) {
loadInstalledGameDigestAndNotifyData(
packageKey = packageFilterManager.packageKey,
filteredList = pkgNameList,
isVGame = isVGame,
updateInstallStatus = updateInstallStatus
@ -584,13 +526,6 @@ object PackageRepository {
changeRecentVaPlayed()
}
/**
* 是否存在因为接口异常而导致没有查询收录情况的包名列表
*/
fun hasPendingPackage(): Boolean {
return mPendingPackageNameSet.isEmpty()
}
private fun notifyGameInstallData() {
PackagesManager.initGameInstall(ArrayList(gameInstalled))
gameInstalledLiveData.postValue(ArrayList(gameInstalled))

View File

@ -63,13 +63,7 @@ class PackageViewModel(
* @param cachedGameEntity 缓存的游戏实体
*/
fun addInstalledGame(pkgName: String?, cachedGameEntity: GameEntity? = null) {
if (!TextUtils.isEmpty(pkgName)) {
mRepository.addInstalledGame(
mRepository.packageFilterManager,
pkgName!!,
cachedGameEntity
)
}
if (!TextUtils.isEmpty(pkgName)) mRepository.addInstalledGame(pkgName!!, cachedGameEntity)
}
/**
@ -87,7 +81,9 @@ class PackageViewModel(
// 未同意获取已安装应用权限时不进行数据初始化
if (!PackageHelper.isGetInstalledPackagesAgreed()) return
if (mRepository.gameInstalled.size == 0 || mRepository.hasPendingPackage()) {
if (mRepository.gameInstalled.size == 0
|| PackageFilterManager.hasPendingPackage()
) {
PackageHelper.initPackageRelatedData();
}
}

View File

@ -20,6 +20,7 @@ import com.gh.gamecenter.entity.BadgeEntity;
import com.gh.gamecenter.entity.BlockEntity;
import com.gh.gamecenter.entity.BottomTab;
import com.gh.gamecenter.entity.CarouselEntity;
import com.gh.gamecenter.entity.CatalogEntity;
import com.gh.gamecenter.entity.CategoryEntity;
import com.gh.gamecenter.entity.CommonCollectionContentEntity;
import com.gh.gamecenter.entity.CommonCollectionEntity;
@ -912,6 +913,19 @@ public interface ApiService {
@GET("blocks/{block_id}/columns")
Observable<List<SubjectEntity>> getBlockColumns(@Path("block_id") String blockId, @Query("page") int page);
/**
* 获取分类目录
*/
@Headers({"Content-Type: application/json", "Accept: application/json"})
@GET("categories/{category_id}/directories")
Observable<List<CategoryEntity>> getCategories(@Path("category_id") String categoryId, @Query("page") int page);
/**
* 获取分类游戏
*/
@GET("categories/{category_id}/games")
Single<List<GameEntity>> getGamesInCategory(@Path("category_id") String categoryId, @Query("sort") String sort, @Query("filter") String filter, @Query("page") int page);
/**
* 获取关注的用户列表
*/
@ -2271,6 +2285,18 @@ public interface ApiService {
@Query("page") int page,
@Query("page_size") int pageSize);
/**
* 获取一级分类数据
*/
@GET("catalogs/{catalog_id}")
Single<CatalogEntity> getCatalogs(@Path("catalog_id") String catalogId);
/**
* 获取一级分类数据 及其 二级分类数据
*/
@GET("catalogs/{catalog_id}/{primary_catalog_id}")
Single<CatalogEntity> getSubCatalogs(@Path("catalog_id") String catalogId, @Path("primary_catalog_id") String primaryCatalogId);
/**
* 获取精选分类
*/

View File

@ -80,12 +80,10 @@ class SearchGameResultViewModel(
.subscribe({ mutableList ->
mSearchSubjects.addAll(mutableList)
mSearchSubjects.forEach {
val item = SearchItemData(subject = it)
if (it.location <= 0 || it.location > itemDataList.size) {
itemDataList.add(item)
} else {
itemDataList.add(it.location - 1, item)
}
itemDataList.add(
if (it.location <= 0 || it.location >= itemDataList.size) itemDataList.size else it.location - 1,
SearchItemData(subject = it)
)
}
// 处理初始化列表且游戏列表size为0的情况
handleLoadStatusWhenGameListIsEmpty(list, itemDataList)

View File

@ -16,12 +16,11 @@ import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.databinding.PieceBottomTabBinding
import com.gh.gamecenter.entity.BottomTab
import com.gh.gamecenter.feature.utils.SentryHelper
/**
* 底部Tab 基类
*/
abstract class BaseBottomTabFragment<T : ViewBinding> : ToolbarFragment() {
abstract class BaseBottomTabFragment<T: ViewBinding>: ToolbarFragment() {
protected var mViewPager: ViewPager2? = null
protected var mBottomTabContainer: LinearLayout? = null
@ -45,7 +44,6 @@ abstract class BaseBottomTabFragment<T : ViewBinding> : ToolbarFragment() {
protected fun initBottomTab(bottomTabList: List<BottomTab>) {
mBottomTabBindingList.clear()
mBottomTabContainer?.run {
visibility = View.VISIBLE
removeAllViews()
bottomTabList.forEachIndexed { index, bottomTab ->
addView(
@ -63,13 +61,6 @@ abstract class BaseBottomTabFragment<T : ViewBinding> : ToolbarFragment() {
.apply {
tabTv.text = bottomTab.name
if (!TextUtils.isEmpty(bottomTab.jsCode)) {
tabLottie.setFailureListener {
SentryHelper.onEvent(
SENTRY_ID_BOTTOM_TAB_LOTTIE_LOAD_FAILED,
BOTTOM_TAB_ID, bottomTab.id,
BOTTOM_TAB_NAME, bottomTab.name
)
}
tabLottie.setAnimationFromJson(bottomTab.jsCode, bottomTab.id + bottomTab.name)
}
if (bottomTab.iconSelector != 0) {
@ -131,13 +122,6 @@ abstract class BaseBottomTabFragment<T : ViewBinding> : ToolbarFragment() {
protected open fun onPageSelected(position: Int) {
checkIndex(position)
}
protected open fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {}
protected open fun onPageScrollStateChanged(state: Int) {}
companion object {
private const val SENTRY_ID_BOTTOM_TAB_LOTTIE_LOAD_FAILED = "bottom_tab_lottie_load_failed"
private const val BOTTOM_TAB_ID = "bottom_tab_id"
private const val BOTTOM_TAB_NAME = "bottom_tab_name"
}
}

View File

@ -13,8 +13,6 @@ import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.text.color
import androidx.core.view.doOnNextLayout
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.ethanhua.skeleton.Skeleton
import com.ethanhua.skeleton.SkeletonScreen
import com.gh.common.iinterface.ISuperiorChain
import com.gh.common.prioritychain.BottomTabGuideHandler
import com.gh.common.prioritychain.GlobalPriorityChainHelper
@ -50,8 +48,6 @@ class MainWrapperFragment : BaseBottomTabFragment<PieceBottomTabBinding>(), OnBa
private var mBottomTabGuideHandler: BottomTabGuideHandler? = null
private var mBottomTabGuidePopupWindow: PopupWindow? = null
private var mSkeletonScreen: SkeletonScreen? = null
override fun getLayoutId(): Int = 0
override fun getInflatedLayout(): View = mBinding.root
override fun provideAdapter(): FragmentStateAdapter = mAdapter
@ -67,13 +63,10 @@ class MainWrapperFragment : BaseBottomTabFragment<PieceBottomTabBinding>(), OnBa
super.onCreate(savedInstanceState)
DisplayUtils.transparentStatusBar(requireActivity())
initSkeleton()
mBinding.viewShadow.visibility = if (mIsDarkModeOn) View.GONE else View.VISIBLE
buildPriorityChain()
mViewModel?.bottomTabListLiveData?.observe(this) {
mSkeletonScreen?.hide()
mBottomTabList.clear()
mBottomTabList.addAll(it)
mViewPager?.offscreenPageLimit = it.size
@ -91,18 +84,6 @@ class MainWrapperFragment : BaseBottomTabFragment<PieceBottomTabBinding>(), OnBa
mPriorityChain.addHandler(mBottomTabGuideHandler!!)
}
private fun initSkeleton() {
mSkeletonScreen = Skeleton.bind(mBinding.skeleton)
.shimmer(true)
.angle(Constants.SHIMMER_ANGLE)
.color(R.color.ui_skeleton_highlight)
.duration(Constants.SHIMMER_DURATION)
.maskWidth(Constants.MASK_WIDTH)
.gradientCenterColorWidth(Constants.GRADIENT_CENTER_COLOR_WIDTH)
.load(R.layout.fragment_main_skeleton)
.show()
}
private fun showBottomTabGuideIfExists() {
val guidePosition = mBottomTabList.indexOfFirst { it.guide != null }
if (guidePosition != -1) {

View File

@ -3,10 +3,10 @@ package com.gh.gamecenter.wrapper
import android.annotation.SuppressLint
import androidx.lifecycle.MutableLiveData
import com.gh.common.util.HomeBottomBarHelper
import com.gh.common.util.HomeBottomBarHelper.isDefaultHomeBottomTabDataExist
import com.gh.common.util.ViewPagerFragmentHelper
import com.gh.gamecenter.common.entity.LaunchRedirect
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.core.utils.GsonUtils
import com.gh.gamecenter.core.utils.SingletonHolder
import com.gh.gamecenter.entity.BottomTab
@ -17,7 +17,6 @@ import com.gh.gamecenter.retrofit.RetrofitManager
import io.reactivex.schedulers.Schedulers
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.launch
@ -135,13 +134,7 @@ class MainWrapperRepository {
@SuppressLint("CheckResult")
fun getDataUnion() {
// 若历史 tab 数据存在,优先使用作为占位
if (isDefaultHomeBottomTabDataExist()) {
processBottomTabData(emptyList())
} else {
// 若 timeout 后数据未加载完成,则即便还没回调 onFailure 也生成两个底部 tab
emitDefaultTabDataAfterTimeout()
}
processBottomTabData(emptyList())
mNewApi.dataUnion
.subscribeOn(Schedulers.io())
.subscribe(object : BiResponse<DataUnionEntity>() {
@ -239,15 +232,6 @@ class MainWrapperRepository {
multiTabNavLiveData.postValue(multiTabNav)
}
private fun emitDefaultTabDataAfterTimeout() {
CoroutineScope(SupervisorJob()).launch {
delay(3000)
if (!mMainDataIsLoaded) {
processBottomTabData(emptyList())
}
}
}
companion object : SingletonHolder<MainWrapperRepository>({ MainWrapperRepository() })
}

View File

@ -58,7 +58,6 @@ import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.time.TimeUtil
import com.gh.gamecenter.feature.game.GameItemViewHolder
import com.gh.gamecenter.feature.utils.SentryHelper
import com.gh.gamecenter.gamecollection.square.GameCollectionSquareFragment
import com.gh.gamecenter.home.custom.CustomPageFragment
import com.gh.gamecenter.home.video.ScrollCalculatorHelper
@ -90,18 +89,7 @@ import kotlin.math.roundToInt
* @see Style
*/
class SearchToolbarTabWrapperFragment : BaseTabWrapperFragment(), ISearchToolbarTab, ISmartRefresh, ISuperiorChain {
private val mBinding by lazy {
try {
FragmentSearchToolbarTabWrapperBinding.inflate(layoutInflater)
} catch (e: Exception) {
SentryHelper.onEvent("VIEW_BINDING_BIND_ERROR",
"digest", e.localizedMessage,
"gid", HaloApp.getInstance().gid
)
// 玄学,重试一次,该闪退闪退
FragmentSearchToolbarTabWrapperBinding.inflate(layoutInflater)
}
}
private val mBinding by lazy { FragmentSearchToolbarTabWrapperBinding.inflate(layoutInflater) }
private val mViewModel: SearchToolbarTabWrapperViewModel by lazy {
viewModelProviderFromParent(
SearchToolbarTabWrapperViewModel.Factory(mMultiTabNavId, mNoTabLinkEntity?.link ?: ""),

View File

@ -14,11 +14,9 @@ import android.text.TextUtils;
import androidx.annotation.Nullable;
import androidx.core.app.NotificationCompat;
import com.blankj.utilcode.util.ThreadUtils;
import com.gh.download.DownloadManager;
import com.gh.gamecenter.SplashScreenActivity;
import com.gh.gamecenter.common.base.GlobalActivityManager;
import com.gh.gamecenter.core.AppExecutor;
import com.gh.ndownload.suspendwindow.NDownloadDrawOverlayPermissionWindowController;
import com.gh.ndownload.suspendwindow.NDownloadSuspendWindowController;
import com.gh.ndownload.suspendwindow.utils.NDownloadSuspendWindowHelper;
@ -284,14 +282,6 @@ public class NDownloadService extends Service {
}
private void showDownloadSuspendWindowIfNeeded(DownloadEntity entry) {
if (ThreadUtils.isMainThread()) {
showDownloadSuspendWindowIfNeededInner(entry);
} else {
AppExecutor.getUiExecutor().execute(() -> showDownloadSuspendWindowIfNeededInner(entry));
}
}
private void showDownloadSuspendWindowIfNeededInner(DownloadEntity entry) {
if (!NDownloadSuspendWindowHelper.shouldSkipDownloadEntity(getApplicationContext(), entry)) {
if (NDownloadSuspendWindowHelper.canDrawOverLayer(getApplicationContext())) {// 已开启悬浮窗权限,直接显示悬浮窗
showDownloadSuspendWindow();

View File

@ -4,7 +4,6 @@ import android.app.Activity
import android.app.Application
import android.app.Application.ActivityLifecycleCallbacks
import android.os.Bundle
import androidx.core.view.ViewCompat
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.asVGame
@ -46,9 +45,7 @@ class NDownloadDrawOverlayPermissionWindowController(val application: Applicatio
}
fun show(activity: Activity) {
val decorView = activity.window.decorView
val isAttachedToWindow = ViewCompat.isAttachedToWindow(decorView)
if (isStarted || !isAttachedToWindow) return
if (isStarted) return
isStarted = true
currentActivityRef = WeakReference(activity)
onAttachToUi(activity)
@ -127,10 +124,10 @@ class NDownloadDrawOverlayPermissionWindowController(val application: Applicatio
SensorsBridge.trackDownloadSuspendedWindowGuideShow(
gameId = downloadEntity.gameId,
gameName = downloadEntity.name,
gameSchemaType = if (downloadEntity.getMetaExtra(Constants.KEY_BIT) == "32") "32位" else "64位",
gameSchemeType = if(downloadEntity.getMetaExtra(Constants.KEY_BIT) == "32") "32位" else "64位",
gameType = downloadEntity.categoryChinese,
downloadStatus = downloadEntity.getMetaExtra(Constants.DOWNLOAD_STATUS_IN_CHINESE),
downloadType = if (downloadEntity.asVGame()) "畅玩下载" else "本地下载"
downloadType = if(downloadEntity.asVGame()) "畅玩下载" else "本地下载"
)
}
}

View File

@ -33,7 +33,7 @@ import org.greenrobot.eventbus.ThreadMode
* 下载悬浮窗-控制器
*/
class NDownloadSuspendWindowController(private val application: Application) :
NDownloadSuspendIconLayout.OnDragCallback, Application.ActivityLifecycleCallbacks,
NDownloadSuspendIconLayout.OnDragListener, Application.ActivityLifecycleCallbacks,
PackageInstaller.OnInstallListener, NDownloadSuspendIconView.OnIconClickListener {
private var isStarted = false
@ -75,24 +75,22 @@ class NDownloadSuspendWindowController(private val application: Application) :
downloadCount = 0
currentDownloadEntity = null
onDetachFromUi()
NDataChanger.deleteObserver(downloadObserver)
PackageInstaller.unregisterOnInstallListener(this)
EventBus.getDefault().unregister(this)
application.unregisterActivityLifecycleCallbacks(this)
onDetachFromUi()
isStarted = false
}
private fun onAttachToUi() {
closeWindow.attach()
iconWindow.attach()
iconWindow.setOnDragCallback(this)
iconWindow.setOnDragListener(this)
iconWindow.setOnIconClickListener(this)
}
private fun onDetachFromUi() {
iconWindow.setOnDragCallback(null)
iconWindow.setOnIconClickListener(null)
closeWindow.detach()
iconWindow.detach()
}

View File

@ -12,7 +12,6 @@ import com.gh.download.DownloadManager
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.asVGame
import com.gh.gamecenter.common.utils.getExtension
import com.gh.gamecenter.common.utils.isLocalDownloadInDualDownloadMode
import com.gh.gamecenter.common.utils.isSimulatorDownload
import com.gh.gamecenter.common.utils.isSimulatorGame
import com.gh.gamecenter.core.utils.SPUtils
@ -185,7 +184,6 @@ object NDownloadSuspendWindowHelper {
|| downloadEntity.isPluggable
|| downloadEntity.isSimulatorDownload()
|| downloadEntity.isSimulatorGame()
|| downloadEntity.isLocalDownloadInDualDownloadMode()
@JvmStatic
fun isDrawOverlayPermissionWindowShown(): Boolean =

View File

@ -26,7 +26,7 @@ class NDownloadSuspendIconLayout @JvmOverloads constructor(
ViewConfiguration.get(context).scaledTouchSlop
}
private var onDragCallback: OnDragCallback? = null
private var onDragListener: OnDragListener? = null
val icon = NDownloadSuspendIconView(context).also {
it.clipChildren = false
@ -39,12 +39,12 @@ class NDownloadSuspendIconLayout @JvmOverloads constructor(
addView(it, LayoutParams(size, size))
}
fun setOnDragCallback(onDragCallback: OnDragCallback?) {
this.onDragCallback = onDragCallback
fun setOnDragListener(onDragListener: OnDragListener) {
this.onDragListener = onDragListener
}
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
val onDragCallback = this.onDragCallback
val onDragListener = this.onDragListener
?: return super.onInterceptTouchEvent(ev)
if (ev.action == MotionEvent.ACTION_DOWN) {
@ -66,7 +66,7 @@ class NDownloadSuspendIconLayout @JvmOverloads constructor(
val dragging = xDiff > touchSlop || yDiff > touchSlop
if (dragging) {
isDragging = true
onDragCallback.onDragStart(this)
onDragListener.onDragStart(this)
return true
}
return super.onInterceptTouchEvent(ev)
@ -81,7 +81,7 @@ class NDownloadSuspendIconLayout @JvmOverloads constructor(
@SuppressLint("ClickableViewAccessibility")
override fun onTouchEvent(ev: MotionEvent): Boolean {
val onDragListener = this.onDragCallback
val onDragListener = this.onDragListener
?: return super.onInterceptTouchEvent(ev)
when(ev.action) {
@ -93,7 +93,7 @@ class NDownloadSuspendIconLayout @JvmOverloads constructor(
return super.onTouchEvent(ev)
}
interface OnDragCallback {
interface OnDragListener {
fun onDragStart(v: View)

View File

@ -109,7 +109,7 @@ class NDownloadSuspendIconView @JvmOverloads constructor(
addView(it, layoutParams)
}
fun setOnIconClickListener(listener: OnIconClickListener?) {
fun setOnIconClickListener(listener: OnIconClickListener) {
this.onIconClickListener = listener
}

View File

@ -56,13 +56,6 @@ class NDownloadSuspendCloseWindow(context: Context, suspend: Boolean = true) :
it.visibility = View.GONE
}
override fun onDetach() {
popupAnimator.removeListener(hiddenListener)
popupAnimator.removeListener(shownListener)
popupAnimator.cancel()
root.visibility = View.GONE
}
override fun onViewCreated(view: NDownloadSuspendCloseLayout) {
view.apply {
popupAnimator.addUpdateListener { value ->

View File

@ -19,7 +19,7 @@ import com.gh.ndownload.suspendwindow.view.NDownloadSuspendIconView
*/
class NDownloadSuspendIconWindow(context: Context, suspend: Boolean = true, private val draggable: Boolean = true) :
NDownloadSuspendWindow<NDownloadSuspendIconLayout>(context, suspend),
NDownloadSuspendIconLayout.OnDragCallback {
NDownloadSuspendIconLayout.OnDragListener {
companion object {
private const val TYPE_DRAGGING = 0
@ -40,7 +40,7 @@ class NDownloadSuspendIconWindow(context: Context, suspend: Boolean = true, priv
/**
* 图标拖动回调事件
*/
private var onDragCallback: NDownloadSuspendIconLayout.OnDragCallback? = null
private var onDragListener: NDownloadSuspendIconLayout.OnDragListener? = null
/**
* 手指松开拖动的悬浮图标时,自动吸附到屏幕侧边的动画
@ -67,30 +67,20 @@ class NDownloadSuspendIconWindow(context: Context, suspend: Boolean = true, priv
get() = root.getLocationOnScreen(screenLocation)
.let { screenLocation[1] - layoutParams.y }
fun setOnDragCallback(onDragCallback: NDownloadSuspendIconLayout.OnDragCallback?) {
this.onDragCallback = onDragCallback
fun setOnDragListener(onDragListener: NDownloadSuspendIconLayout.OnDragListener?) {
this.onDragListener = onDragListener
}
override fun onAttach() {
if (draggable) {
root.post(initRunnable)
root.setOnDragCallback(this)
}
}
override fun onDetach() {
if (draggable) {
root.removeCallbacks(initRunnable)
root.setOnDragCallback(null)
}
edgeAnimator.removeAllUpdateListeners()
edgeAnimator.removeAllListeners()
override fun detach() {
super.detach()
root.removeCallbacks(initRunnable)
edgeAnimator.cancel()
}
@SuppressLint("InflateParams")
override fun onCreateView(context: Context): NDownloadSuspendIconLayout =
NDownloadSuspendIconLayout(context).also {
if (draggable) it.setOnDragListener(this)
it.clipChildren = false
it.clipToPadding = false
}
@ -106,6 +96,7 @@ class NDownloadSuspendIconWindow(context: Context, suspend: Boolean = true, priv
override fun onViewCreated(view: NDownloadSuspendIconLayout) {
super.onViewCreated(view)
view.setBackgroundResource(R.drawable.shape_download_suspend_icon_idle_right)
if (draggable) view.post(initRunnable)
}
override fun onConfigurationChanged(configuration: Configuration) {
@ -144,7 +135,7 @@ class NDownloadSuspendIconWindow(context: Context, suspend: Boolean = true, priv
root.icon.setDownloadIconUrl(url)
}
fun setOnIconClickListener(listener: NDownloadSuspendIconView.OnIconClickListener?) {
fun setOnIconClickListener(listener: NDownloadSuspendIconView.OnIconClickListener) {
root.icon.setOnIconClickListener(listener)
}
@ -212,16 +203,16 @@ class NDownloadSuspendIconWindow(context: Context, suspend: Boolean = true, priv
override fun onDragStart(v: View) {
setIconLayoutStyleByType(TYPE_DRAGGING)
onDragCallback?.onDragStart(v)
onDragListener?.onDragStart(v)
}
override fun onDragMove(v: View, x: Float, y: Float) {
moveToPosition(v, x, y)
onDragCallback?.onDragMove(v, x, y)
onDragListener?.onDragMove(v, x, y)
}
override fun onDragEnd(v: View, x: Float, y: Float) {
animateToEdge(v, x, y)
onDragCallback?.onDragEnd(v, x, y)
onDragListener?.onDragEnd(v, x, y)
}
}

View File

@ -46,35 +46,22 @@ abstract class NDownloadSuspendWindow<T : View>(
onViewCreated(it)
}
fun attach() {
open fun attach() {
if (!isAttached) {
try {
_isAttached = true
windowManager.addView(root, layoutParams)
context.registerComponentCallbacks(this)
onAttach()
} catch (e: Throwable) {
// 处理“Unable to add window android.view.ViewRootImpl$W@7bc9502 -- permission denied for window type 2038”的异常
// 目前Sentry上仅Z10型号的手机报这个错误Android 8.1.0这里明明已经授予了SYSTEM_ALERT_WINDOW权限但是就是会抛异常= =,只能暴力捕获处理了。。。
// 相关异常的链接https://sentry.shanqu.cc/organizations/lightgame/issues/407275/?project=22
_isAttached = false
}
_isAttached = true
windowManager.addView(root, layoutParams)
context.registerComponentCallbacks(this)
}
}
fun detach() {
open fun detach() {
if (isAttached) {
_isAttached = false
onDetach()
context.unregisterComponentCallbacks(this)
windowManager.removeView(root)
}
}
protected open fun onAttach() = Unit
protected open fun onDetach() = Unit
protected open fun onViewCreated(view: T) = Unit
override fun onConfigurationChanged(configuration: Configuration) = Unit

View File

@ -56,7 +56,6 @@ import com.gh.gamecenter.history.HistoryActivity
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.manager.PackagesManager
import com.gh.gamecenter.mygame.MyGameActivity
import com.gh.gamecenter.packagehelper.PackageFilterManager
import com.gh.gamecenter.packagehelper.PackageRepository
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.subject.SubjectActivity
@ -1998,7 +1997,7 @@ object VHelper {
validInstalledPackageList.add(packageName)
}
}
PackageRepository.addInstalledGames(PackageFilterManager(), validInstalledPackageList, true)
PackageRepository.addInstalledGames(validInstalledPackageList, true)
}
/**
@ -2278,13 +2277,6 @@ object VHelper {
val token = UserManager.getInstance().token
val userName = UserManager.getInstance().userInfoEntity?.name
val userAvatar = UserManager.getInstance().userInfoEntity?.icon
if (token == null) {
SentryHelper.onEvent("LAUNCH_SUCCESS_BUT_TOKEN_EMPTY", "gid", HaloApp.getInstance().gid)
Utils.log(LOG_TAG, "登录成功,但 token 为空")
return@runOnIoThread
}
Utils.log(
LOG_TAG,
"登录成功,插入用户信息:token=${token},userName=${userName}, uri=${va.getUriAuthorizationString()}"

View File

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<corners android:radius="20dp" />
<solid android:color="@color/ui_skeleton_frame" />
<corners android:radius="14dp" />
<solid android:color="#14FFFFFF" />
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 554 B

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="80dp"
android:layout_height="52dp">
<ImageView
android:id="@+id/selected_tag"
android:layout_width="14dp"
android:layout_height="6dp"
android:layout_centerHorizontal="true"
android:layout_marginTop="34dp"
android:src="@drawable/ic_catalog_selected"
android:visibility="gone"
tools:visibility="visible" />
<TextView
android:id="@+id/catalog_name"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:textColor="@color/text_primary"
android:textSize="12sp"
tools:text="精选" />
<ImageView
android:id="@+id/recommend_tag"
android:layout_width="16dp"
android:layout_height="12dp"
android:layout_marginLeft="52dp"
android:layout_marginTop="16dp"
android:src="@drawable/ic_recommend"
tools:visibility="visible" />
</RelativeLayout>

View File

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/ui_surface">
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/ui_divider" />
<LinearLayout
android:id="@+id/container_catalog"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginTop="1dp"
android:orientation="horizontal">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_catalog"
android:layout_width="80dp"
android:layout_height="match_parent"
android:background="@color/ui_background" />
<FrameLayout
android:id="@+id/container_sub_catalog"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</LinearLayout>
<include
android:id="@+id/reuse_no_connection"
layout="@layout/reuse_no_connection" />
<include
android:id="@+id/reuse_none_data"
layout="@layout/reuse_none_data" />
<include
android:id="@+id/reuse_data_exception"
layout="@layout/reuse_data_exception" />
<include
android:id="@+id/reuse_loading"
layout="@layout/reuse_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
</RelativeLayout>

View File

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<FrameLayout
android:id="@+id/list_skeleton"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/filter_container" />
<com.gh.common.view.CatalogFilterView
android:id="@+id/filter_container"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<View
android:id="@+id/divider"
android:layout_below="@id/filter_container"
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="@color/ui_background" />
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout
android:id="@+id/list_refresh"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/divider">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list_rv"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
<include
layout="@layout/reuse_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
<include
layout="@layout/reuse_no_connection"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/filter_container" />
<include
layout="@layout/reuse_none_data"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/filter_container" />
<include
layout="@layout/reuse_data_exception"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_below="@id/filter_container" />
</RelativeLayout>

View File

@ -0,0 +1,27 @@
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingTop="4dp"
android:orientation="vertical">
<include layout="@layout/item_skeleton_category" />
<include layout="@layout/item_skeleton_category" />
<include layout="@layout/item_skeleton_category" />
<include layout="@layout/item_skeleton_category" />
<include layout="@layout/item_skeleton_category" />
<include layout="@layout/item_skeleton_category" />
<include layout="@layout/item_skeleton_category" />
</LinearLayout>
</RelativeLayout>

View File

@ -142,14 +142,7 @@
android:focusable="true"
android:gravity="center_vertical"
android:orientation="horizontal"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
<FrameLayout
android:id="@+id/skeleton"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</com.gh.gamecenter.common.view.MaterializedConstraintLayout>

View File

@ -1,316 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_above="@id/b"
android:orientation="vertical">
<com.gh.gamecenter.common.view.StatusBarView
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="48dp"
android:paddingStart="16dp"
android:paddingEnd="16dp">
<View
android:id="@+id/sb"
android:layout_width="0dp"
android:layout_height="32dp"
android:layout_marginEnd="16dp"
android:background="@drawable/bg_skeleton_radius_40"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/ic1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/ic1"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="16dp"
android:background="@drawable/bg_skeleton_radius_4"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/ic2"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/ic2"
android:layout_width="24dp"
android:layout_height="24dp"
android:layout_gravity="center_vertical"
android:background="@drawable/bg_skeleton_radius_4"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="28dp"
android:gravity="bottom"
android:paddingTop="8dp">
<View
android:layout_width="40dp"
android:layout_height="20dp"
android:layout_marginStart="16dp"
android:background="@drawable/bg_skeleton_radius_4" />
<View
android:layout_width="32dp"
android:layout_height="16dp"
android:layout_marginStart="24dp"
android:background="@drawable/bg_skeleton_radius_4" />
<View
android:layout_width="64dp"
android:layout_height="16dp"
android:layout_marginStart="24dp"
android:background="@drawable/bg_skeleton_radius_4" />
<View
android:layout_width="32dp"
android:layout_height="16dp"
android:layout_marginStart="24dp"
android:background="@drawable/bg_skeleton_radius_4" />
<View
android:layout_width="32dp"
android:layout_height="16dp"
android:layout_marginStart="24dp"
android:background="@drawable/bg_skeleton_radius_4" />
<View
android:layout_width="32dp"
android:layout_height="16dp"
android:layout_marginStart="24dp"
android:background="@drawable/bg_skeleton_radius_4" />
<View
android:layout_width="64dp"
android:layout_height="16dp"
android:layout_marginStart="24dp"
android:background="@drawable/bg_skeleton_radius_4" />
</LinearLayout>
<ImageView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="29dp"
android:layout_marginTop="7dp"
android:src="@drawable/ic_home_tab_indicator_skeleton" />
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:paddingHorizontal="16dp"
android:paddingVertical="8dp">
<View
android:id="@+id/square"
android:layout_width="0dp"
android:layout_height="140dp"
android:layout_marginEnd="8dp"
android:background="@drawable/bg_skeleton_radius_10"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toStartOf="@id/rect1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<View
android:id="@+id/rect1"
android:layout_width="0dp"
android:layout_height="66dp"
android:background="@drawable/bg_skeleton_radius_10"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toEndOf="@id/square"
app:layout_constraintTop_toTopOf="@id/square" />
<View
android:id="@+id/rect2"
android:layout_width="0dp"
android:layout_height="66dp"
android:layout_marginTop="8dp"
android:background="@drawable/bg_skeleton_radius_10"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0"
app:layout_constraintHorizontal_chainStyle="packed"
app:layout_constraintStart_toStartOf="@id/rect1"
app:layout_constraintTop_toBottomOf="@id/rect1" />
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="87dp"
android:orientation="horizontal"
android:paddingHorizontal="18dp"
android:paddingVertical="16dp">
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:background="@drawable/bg_skeleton_radius_8" />
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="26dp"
android:layout_weight="1"
android:background="@drawable/bg_skeleton_radius_8" />
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="26dp"
android:layout_weight="1"
android:background="@drawable/bg_skeleton_radius_8" />
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="26dp"
android:layout_weight="1"
android:background="@drawable/bg_skeleton_radius_8" />
<View
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_marginStart="26dp"
android:layout_weight="1"
android:background="@drawable/bg_skeleton_radius_8" />
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingTop="16dp"
android:paddingBottom="16dp">
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="18dp">
<View
android:layout_width="54dp"
android:layout_height="18dp"
android:layout_marginStart="16dp"
android:background="@drawable/bg_skeleton_radius_4" />
<View
android:layout_width="32dp"
android:layout_height="12dp"
android:layout_alignParentEnd="true"
android:layout_marginEnd="16dp"
android:background="@drawable/bg_skeleton_radius_4" />
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="16dp">
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="53dp"
android:background="@drawable/bg_skeleton_radius_4" />
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:orientation="horizontal">
<include layout="@layout/fragment_main_skeleton_item_simple_game" />
<include layout="@layout/fragment_main_skeleton_item_simple_game" />
<include layout="@layout/fragment_main_skeleton_item_simple_game" />
<include layout="@layout/fragment_main_skeleton_item_simple_game" />
<include layout="@layout/fragment_main_skeleton_item_simple_game" />
<include layout="@layout/fragment_main_skeleton_item_simple_game" />
<include layout="@layout/fragment_main_skeleton_item_simple_game" />
</LinearLayout>
</HorizontalScrollView>
</RelativeLayout>
</LinearLayout>
<include layout="@layout/fragment_main_skeleton_item_game" />
<include layout="@layout/fragment_main_skeleton_item_game" />
<include layout="@layout/fragment_main_skeleton_item_game" />
</LinearLayout>
<LinearLayout
android:id="@+id/b"
android:layout_width="match_parent"
android:layout_height="54dp"
android:layout_alignParentBottom="true"
android:orientation="horizontal">
<include
layout="@layout/fragment_main_skeleton_item_bottom_tab"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<include
layout="@layout/fragment_main_skeleton_item_bottom_tab"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<include
layout="@layout/fragment_main_skeleton_item_bottom_tab"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<include
layout="@layout/fragment_main_skeleton_item_bottom_tab"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
<include
layout="@layout/fragment_main_skeleton_item_bottom_tab"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1" />
</LinearLayout>
</RelativeLayout>

View File

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<View
android:layout_width="22dp"
android:layout_height="22dp"
android:background="@drawable/bg_skeleton_radius_4" />
<View
android:layout_width="22dp"
android:layout_height="10dp"
android:layout_marginTop="6dp"
android:background="@drawable/bg_skeleton_radius_4" />
</LinearLayout>
</LinearLayout>

View File

@ -1,47 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingLeft="16dp"
android:paddingTop="8dp"
android:paddingRight="16dp"
android:paddingBottom="8dp">
<View
android:layout_width="64dp"
android:layout_height="64dp"
android:background="@drawable/bg_skeleton_radius_14" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginLeft="12dp"
android:layout_marginTop="4dp"
android:layout_weight="1"
android:orientation="vertical">
<View
android:layout_width="72dp"
android:layout_height="16dp"
android:background="@drawable/bg_skeleton_radius_4" />
<View
android:layout_width="160dp"
android:layout_height="12dp"
android:layout_marginTop="8dp"
android:background="@drawable/bg_skeleton_radius_4" />
<View
android:layout_width="32dp"
android:layout_height="12dp"
android:layout_marginTop="8dp"
android:background="@drawable/bg_skeleton_radius_4" />
</LinearLayout>
<View
android:layout_width="56dp"
android:layout_height="28dp"
android:layout_gravity="center_vertical"
android:background="@drawable/bg_skeleton_radius_999" />
</LinearLayout>

View File

@ -1,49 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="80dp"
android:layout_height="wrap_content"
android:gravity="center_horizontal"
android:orientation="vertical">
<View
android:layout_width="28dp"
android:layout_height="11dp"
android:background="@drawable/bg_skeleton_radius_4" />
<View
android:layout_width="44dp"
android:layout_height="11dp"
android:layout_marginTop="8dp"
android:background="@drawable/bg_skeleton_radius_4"/>
<View
android:layout_width="5dp"
android:layout_height="5dp"
android:layout_marginTop="5dp"
android:layout_marginBottom="5dp"
android:background="@drawable/bg_skeleton_radius_40"/>
<View
android:layout_width="64dp"
android:layout_height="64dp"
android:background="@drawable/bg_skeleton_radius_14" />
<View
android:layout_width="64dp"
android:layout_height="16dp"
android:layout_marginTop="8dp"
android:background="@drawable/bg_skeleton_radius_4" />
<View
android:layout_width="56dp"
android:layout_height="28dp"
android:layout_marginTop="12dp"
android:background="@drawable/bg_skeleton_radius_40" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/ui_surface">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rv_sub_catalog"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<include
android:id="@+id/reuse_no_connection"
layout="@layout/reuse_no_connection" />
<include
android:id="@+id/reuse_none_data"
layout="@layout/reuse_none_data" />
<include
android:id="@+id/reuse_data_exception"
layout="@layout/reuse_data_exception" />
<include
android:id="@+id/reuse_loading"
layout="@layout/reuse_loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerInParent="true" />
</RelativeLayout>

View File

@ -0,0 +1,91 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<View
android:id="@+id/divider"
android:layout_width="match_parent"
android:layout_height="8dp"
android:background="@color/ui_background" />
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<RelativeLayout
android:id="@+id/container_category"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="37dp">
<LinearLayout
android:id="@+id/container_primary_category"
android:layout_width="90dp"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/iconIv"
android:layout_width="25dp"
android:layout_height="25dp" />
<TextView
android:id="@+id/categoryName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:textColor="#000"
android:textSize="12sp"
tools:text="不是不能用" />
</LinearLayout>
<LinearLayout
android:id="@+id/container_sub_category"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toRightOf="@id/container_primary_category"
android:gravity="center_vertical"
android:orientation="vertical">
<LinearLayout
android:id="@+id/container_unexpandable"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical" />
<net.cachapa.expandablelayout.ExpandableLayout
android:id="@+id/container_expandable"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:el_expanded="false" />
</LinearLayout>
</RelativeLayout>
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="37dp"
android:layout_below="@id/container_category">
<ImageView
android:id="@+id/iv_toggle"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:paddingLeft="20dp"
android:paddingTop="20dp"
android:paddingRight="20dp"
android:paddingBottom="10dp"
android:src="@drawable/ic_category_arrow_down"
android:visibility="gone"
tools:visibility="visible" />
</RelativeLayout>
</RelativeLayout>
</LinearLayout>

View File

@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingTop="38dp"
android:paddingBottom="36dp"
android:layout_marginTop="8dp">
<LinearLayout
android:id="@+id/viewLeft"
android:layout_width="90dp"
android:layout_height="wrap_content"
android:gravity="center"
android:orientation="vertical">
<View
android:layout_width="28dp"
android:layout_height="28dp"
android:background="@drawable/bg_skeleton_radius_4" />
<View
android:layout_width="28dp"
android:layout_height="12dp"
android:layout_marginTop="8dp"
android:background="@drawable/bg_skeleton_radius_4" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_horizontal"
android:orientation="vertical">
<View
android:layout_width="48dp"
android:layout_height="16dp"
android:background="@drawable/bg_skeleton_radius_4" />
<View
android:layout_width="48dp"
android:layout_height="16dp"
android:layout_marginTop="16dp"
android:background="@drawable/bg_skeleton_radius_4" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_horizontal"
android:orientation="vertical">
<View
android:layout_width="48dp"
android:layout_height="16dp"
android:background="@drawable/bg_skeleton_radius_4" />
<View
android:layout_width="48dp"
android:layout_height="16dp"
android:layout_marginTop="16dp"
android:background="@drawable/bg_skeleton_radius_4" />
</LinearLayout>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="center_horizontal"
android:orientation="vertical">
<View
android:layout_width="48dp"
android:layout_height="16dp"
android:background="@drawable/bg_skeleton_radius_4" />
<View
android:layout_width="48dp"
android:layout_height="16dp"
android:layout_marginTop="16dp"
android:background="@drawable/bg_skeleton_radius_4" />
</LinearLayout>
</LinearLayout>

View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/config_controller"
style="@style/filterRoot">
<LinearLayout
android:id="@+id/container_type"
style="@style/filterItemContainer">
<TextView
android:id="@+id/type_tv"
style="@style/filterItem"
tools:text="热门推荐" />
</LinearLayout>
<LinearLayout
android:id="@+id/container_catalog"
style="@style/filterItemContainer">
<TextView
android:id="@+id/catalog_tv"
style="@style/filterItem"
tools:text="MOD单机" />
</LinearLayout>
<LinearLayout
android:id="@+id/container_size"
style="@style/filterItemContainer">
<TextView
android:id="@+id/size_tv"
style="@style/filterItem"
android:text="全部大小" />
</LinearLayout>
</LinearLayout>

View File

@ -7,4 +7,15 @@
android:id="@+id/surface_container"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<RelativeLayout
android:id="@+id/thumb"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/thumbImage"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</RelativeLayout>
</FrameLayout>

View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<TextView
android:id="@+id/tv_left_sub_category"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:gravity="center"
android:ellipsize="end"
android:textColor="@color/text_black"
android:maxLines="1"
android:textSize="12sp"
tools:text="星海爭霸" />
<View
android:id="@+id/divider_left"
android:layout_width="1dp"
android:layout_height="15dp"
android:background="#ededed"
android:visibility="gone"
tools:visibility="visible" />
<TextView
android:id="@+id/tv_center_sub_category"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:gravity="center"
android:ellipsize="end"
android:maxLines="1"
android:textColor="@color/text_black"
android:textSize="12sp"
tools:text="空海爭霸" />
<View
android:id="@+id/divider_right"
android:layout_width="1dp"
android:layout_height="15dp"
android:background="#ededed"
android:visibility="gone"
tools:visibility="visible" />
<TextView
android:id="@+id/tv_right_sub_category"
android:layout_width="0dp"
android:layout_weight="1"
android:ellipsize="end"
android:layout_height="wrap_content"
android:gravity="center"
android:maxLines="1"
android:textColor="@color/text_black"
android:textSize="12sp"
tools:text="地海爭霸" />
</LinearLayout>

View File

@ -11,12 +11,12 @@
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/adImage"
style="@style/frescoStyle"
android:layout_width="0dp"
android:layout_height="0dp"
android:tint="#01000000"
android:visibility="gone"
app:actualImageScaleType="centerCrop"
app:fadeDuration="0"
android:visibility="gone"
app:layout_constraintBottom_toTopOf="@id/startAdIcpContainer"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_centerHorizontal="true"
android:layout_marginTop="17dp"
android:background="@drawable/bg_sub_catalog_item" />
<com.facebook.drawee.view.SimpleDraweeView
android:id="@+id/catalog_icon"
android:layout_width="64dp"
android:layout_height="64dp"
android:layout_centerHorizontal="true"
android:layout_marginTop="17dp"
app:fadeDuration="500"
app:placeholderImage="@drawable/occupy"
app:placeholderImageScaleType="fitXY"
app:roundedCornerRadius="16dp"
app:roundingBorderColor="@color/ui_background"
app:roundingBorderWidth="1dp" />
<TextView
android:id="@+id/catalog_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/catalog_icon"
android:layout_centerHorizontal="true"
android:layout_marginTop="8dp"
android:layout_marginBottom="7dp"
android:includeFontPadding="false"
android:textColor="@color/text_primary"
android:textSize="12sp"
tools:text="加速插件" />
<ImageView
android:id="@+id/recommend_tag"
android:layout_width="18dp"
android:layout_height="14dp"
android:layout_alignLeft="@+id/catalog_icon"
android:layout_alignBottom="@+id/catalog_icon"
android:layout_marginLeft="50dp"
android:layout_marginBottom="54dp"
android:src="@drawable/ic_recommend"
tools:visibility="visible" />
</RelativeLayout>

View File

@ -17,8 +17,8 @@ buildscript {
password("u9gZYH4MQEwLLQZK")
}
}
maven { url "https://maven.google.com" }
maven { url 'https://jitpack.io' }
maven { url "https://maven.google.com" }
}
dependencies {
@ -41,8 +41,7 @@ allprojects {
google()
jcenter()
mavenCentral()
maven { url 'https://maven.aliyun.com/repository/public' }
maven { url 'https://maven.aliyun.com/repository/central'}
maven { url 'https://jitpack.io' }
maven { url 'https://maven.aliyun.com/nexus/content/repositories/releases/' }
maven { url 'https://artifact.bytedance.com/repository/Volcengine/'}
maven { url 'https://artifact.bytedance.com/repository/pangle' }
@ -54,7 +53,6 @@ allprojects {
// 配置HMS Core SDK的Maven仓地址。
maven { url 'https://developer.huawei.com/repo/' }
maven { url 'https://developer.hihonor.com/repo' }
maven { url 'https://jitpack.io' }
}
}
task clean(type: Delete) {

View File

@ -7,8 +7,8 @@ ext {
targetSdkVersion = 30
// application info (每个大版本之间的 versionCode 增加 20)
versionCode = 1095
versionName = "5.37.5"
versionCode = 1090
versionName = "5.37.0"
applicationId = "com.gh.gamecenter"
applicationIdGat = "com.gh.gamecenter.intl"

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