Compare commits

...

27 Commits

Author SHA1 Message Date
9368d44093 feat: 整理代码 2024-11-05 17:35:34 +08:00
6b04649c34 fix: 修复深色模式显示问题 2024-11-05 17:02:24 +08:00
4497d03dbf feat: 游戏详情页改版优化 https://jira.shanqu.cc/browse/GHZSCY-5855 2024-11-05 17:02:24 +08:00
287bbe7901 feat: 官方入驻标识—客户端 https://jira.shanqu.cc/browse/GHZSCY-6624 2024-11-05 14:09:06 +08:00
fe8f9fcf83 feat: 游戏详情页-自定义栏目-APP—客户端 https://jira.shanqu.cc/browse/GHZSCY-6374 2024-11-05 14:09:06 +08:00
0786342e00 feat: 游戏详情页-综合面板-APP—客户端 https://jira.shanqu.cc/browse/GHZSCY-6167 2024-11-05 14:08:59 +08:00
1676f8bffb feat: 游戏详情页-抽屉列表-APP—客户端 https://jira.shanqu.cc/browse/GHZSCY-6170 2024-11-05 13:36:29 +08:00
2e95a42f3c feat: 游戏详情页-功能标签-APP—客户端 https://jira.shanqu.cc/browse/GHZSCY-6157 2024-11-05 13:36:29 +08:00
da3b3360df fix: 修复深色模式显示问题 2024-11-05 13:36:29 +08:00
080397e0d2 feat: 游戏详情页-内容卡片-APP—客户端 https://jira.shanqu.cc/browse/GHZSCY-6160 2024-11-05 13:36:29 +08:00
48d6533857 feat: 新增PK组件-APP—客户端 https://jira.shanqu.cc/browse/GHZSCY-6194 2024-11-05 13:36:29 +08:00
36bcf7f52a feat: 游戏详情页-内容卡片-APP—客户端 https://jira.shanqu.cc/browse/GHZSCY-6160 2024-11-05 13:36:29 +08:00
da2b817d23 feat: 游戏详情页-游戏礼包-APP—客户端 https://jira.shanqu.cc/browse/GHZSCY-6266 2024-11-05 13:36:29 +08:00
afd0cce849 feat: 游戏详情页改版优化 https://jira.shanqu.cc/browse/GHZSCY-5855 2024-11-05 13:36:29 +08:00
6970aedbd4 feat: 游戏详情页改版优化 https://jira.shanqu.cc/browse/GHZSCY-5855 2024-11-05 13:36:29 +08:00
df21c88ad2 feat: 游戏详情页-详情tab头部信息-APP—客户端 https://jira.shanqu.cc/browse/GHZSCY-6296 2024-11-05 13:36:29 +08:00
7638296860 feat: 游戏详情页改版优化 https://jira.shanqu.cc/browse/GHZSCY-5855 2024-11-05 13:36:29 +08:00
27058d5551 feat: 游戏详情页改版优化 https://jira.shanqu.cc/browse/GHZSCY-5855 2024-11-05 13:36:29 +08:00
74b7bd8b47 feat: 游戏详情页改版优化 https://jira.shanqu.cc/browse/GHZSCY-5855 2024-11-05 13:36:29 +08:00
539c9fb436 feat: 游戏详情页改版优化 https://jira.shanqu.cc/browse/GHZSCY-5855 2024-11-05 13:36:29 +08:00
bc57ba207c feat: 游戏详情页改版优化 https://jira.shanqu.cc/browse/GHZSCY-5855 2024-11-05 13:36:29 +08:00
90b63b287f feat: 游戏详情页改版优化 https://jira.shanqu.cc/browse/GHZSCY-5855 2024-11-05 13:36:29 +08:00
1233ff8458 feat: 游戏详情页改版优化 https://jira.shanqu.cc/browse/GHZSCY-5855 2024-11-05 13:36:28 +08:00
b75fd9cc9b feat: 游戏详情页改版优化 https://jira.shanqu.cc/browse/GHZSCY-5855 2024-11-05 13:36:28 +08:00
6d992c77c7 feat: 游戏详情页改版优化 https://jira.shanqu.cc/browse/GHZSCY-5855 2024-11-05 13:36:28 +08:00
d5dc79f753 feat: 游戏详情页改版优化 https://jira.shanqu.cc/browse/GHZSCY-5855 2024-11-05 13:36:28 +08:00
168f20a162 feat: 游戏详情页改版优化 https://jira.shanqu.cc/browse/GHZSCY-5855 2024-11-05 13:36:28 +08:00
251 changed files with 15031 additions and 7946 deletions

View File

@ -20,6 +20,7 @@
-keep class com.gh.gamecenter.db.info.* {*;}
-keep class com.gh.gamecenter.entity.** {<fields>;}
-keep class com.gh.gamecenter.qa.entity.** {<fields>;}
-keep class com.gh.gamecenter.gamedetail.entity.** {<fields>;}
-keep class com.gh.download.DownloadDataSimpleEntity {<fields>;}
-keep class com.gh.gamecenter.floatingwindow.FloatingWindowEntity {<fields>;}
-keep class com.gh.gamecenter.BR

View File

@ -7,19 +7,20 @@ import androidx.room.TypeConverters
import androidx.room.migration.Migration
import androidx.sqlite.db.SupportSQLiteDatabase
import com.gh.gamecenter.entity.GamesCollectionEntity
import com.gh.gamecenter.entity.HistoryGameDetailEntity
import com.gh.gamecenter.entity.HistoryGameEntity
import com.gh.gamecenter.entity.MyVideoEntity
import com.gh.gamecenter.feature.entity.NewsEntity
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.feature.entity.ArticleEntity
import com.gh.gamecenter.feature.entity.NewsEntity
import com.gh.gamecenter.feature.room.converter.*
import com.gh.gamecenter.room.converter.*
import com.gh.gamecenter.room.dao.*
import com.halo.assistant.HaloApp
@Database(
entities = [AnswerEntity::class, ArticleEntity::class, NewsEntity::class, HistoryGameEntity::class, MyVideoEntity::class, GamesCollectionEntity::class],
version = 14,
entities = [AnswerEntity::class, ArticleEntity::class, NewsEntity::class, HistoryGameEntity::class, MyVideoEntity::class, GamesCollectionEntity::class, HistoryGameDetailEntity::class],
version = 15,
exportSchema = false
)
@TypeConverters(
@ -53,6 +54,7 @@ abstract class HistoryDatabase : RoomDatabase() {
abstract fun gameDao(): GameDao
abstract fun videoHistoryDao(): VideoHistoryDao
abstract fun gamesCollectionDao(): GamesCollectionDao
abstract fun gameDetailDao(): GameDetailHistoryDao
companion object {
@ -152,6 +154,12 @@ abstract class HistoryDatabase : RoomDatabase() {
}
}
val MIGRATION_14_15: Migration = object : Migration(14, 15) {
override fun migrate(database: SupportSQLiteDatabase) {
database.execSQL("CREATE TABLE HistoryGameDetailEntity (id TEXT NOT NULL PRIMARY KEY, name TEXT DEFAULT '')")
}
}
val instance by lazy {
Room.databaseBuilder(
HaloApp.getInstance().application,
@ -170,6 +178,7 @@ abstract class HistoryDatabase : RoomDatabase() {
.addMigrations(MIGRATION_11_12)
.addMigrations(MIGRATION_12_13)
.addMigrations(MIGRATION_13_14)
.addMigrations(MIGRATION_14_15)
.build()
}
}

View File

@ -44,6 +44,20 @@ object HistoryHelper {
runOnIoThread { tryCatchInRelease { HistoryDatabase.instance.gameDao().addGame(historyGameEntity) } }
}
@JvmStatic
fun insertGameDetail(gameEntity: GameEntity) {
val historyGameDetailEntity = HistoryGameDetailEntity(gameEntity.id, gameEntity.name)
runOnIoThread { tryCatchInRelease { HistoryDatabase.instance.gameDetailDao().addGame(historyGameDetailEntity) } }
}
@JvmStatic
fun getHistoryGameDetailById(id: String): HistoryGameDetailEntity? =
try {
HistoryDatabase.instance.gameDetailDao().getHistoryGameDetailById(id)
} catch (e: Throwable) {
null
}
private fun convertGameUpdateEntityToHistoryGameEntity(updateEntity: GameUpdateEntity): HistoryGameEntity {
val historyGame = HistoryGameEntity()
@ -142,6 +156,15 @@ object HistoryHelper {
}
}
@JvmStatic
fun deleteGameDetailEntity(gameId: String) {
runOnIoThread {
tryCatchInRelease {
HistoryDatabase.instance.gameDetailDao().deleteGame(HistoryGameDetailEntity(id = gameId))
}
}
}
@JvmStatic
fun emptyDatabase() {

View File

@ -11,7 +11,6 @@ import android.os.Bundle
import android.text.TextUtils
import androidx.appcompat.app.AppCompatActivity
import androidx.core.os.bundleOf
import com.therouter.TheRouter
import com.gh.ad.AdPluginDownloadHelper
import com.gh.common.constant.Config
import com.gh.common.exposure.ExposureManager.log
@ -19,6 +18,7 @@ import com.gh.common.exposure.ExposureTraceUtils.appendTrace
import com.gh.common.util.EntranceUtils.jumpActivity
import com.gh.common.util.EntranceUtils.jumpActivityCompat
import com.gh.gamecenter.*
import com.gh.gamecenter.ShellActivity.Type
import com.gh.gamecenter.amway.AmwayActivity
import com.gh.gamecenter.category2.CategoryV2Activity
import com.gh.gamecenter.common.base.activity.BaseActivity
@ -56,6 +56,7 @@ import com.gh.gamecenter.gamecollection.detail.GameCollectionDetailActivity
import com.gh.gamecenter.gamecollection.hotlist.GameCollectionHotListActivity
import com.gh.gamecenter.gamecollection.hotlist.GameCollectionListDetailActivity
import com.gh.gamecenter.gamecollection.square.GameCollectionSquareActivity
import com.gh.gamecenter.gamedetail.entity.GameDetailTabEntity
import com.gh.gamecenter.gamedetail.fuli.kaifu.ServersCalendarActivity
import com.gh.gamecenter.gamedetail.fuli.kaifu.ServersCalendarManagementActivity
import com.gh.gamecenter.gamedetail.fuli.kaifu.ServersSubscribedGameListActivity
@ -77,7 +78,6 @@ import com.gh.gamecenter.qa.questions.newdetail.NewQuestionDetailActivity
import com.gh.gamecenter.qa.subject.CommunitySubjectActivity
import com.gh.gamecenter.qa.video.detail.ForumVideoDetailActivity
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.SearchActivity
import com.gh.gamecenter.servers.GameServerTestActivity
import com.gh.gamecenter.servers.GameServersActivity
import com.gh.gamecenter.servers.gametest2.GameServerTestV2Activity
@ -98,6 +98,7 @@ import com.halo.assistant.fragment.WebFragment
import com.lightgame.utils.Utils
import com.tencent.mm.opensdk.modelbiz.WXLaunchMiniProgram
import com.tencent.mm.opensdk.openapi.WXAPIFactory
import com.therouter.TheRouter
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import retrofit2.HttpException
@ -178,7 +179,8 @@ object DirectUtils {
"qa",
"feedback",
"toolkit",
"float_window_game"
"float_window_game",
"archive"
)
fun directToLinkPage(
@ -568,6 +570,8 @@ object DirectUtils {
}
}
"archive" -> directToCloudArchive(context, linkEntity.link ?: "", linkEntity.text ?: "", "", entrance)
"" -> {
// do nothing
}
@ -810,10 +814,10 @@ object DirectUtils {
bundle.putString(KEY_GAMEID, id)
if (!TextUtils.isEmpty(tab)) {
when (tab) {
"comment" -> bundle.putString(KEY_TARGET, EntranceConsts.TAB_TYPE_RATING)
"desc" -> bundle.putString(KEY_TARGET, EntranceConsts.TAB_TYPE_DESC)
"forum" -> bundle.putString(KEY_TARGET, EntranceConsts.TAB_TYPE_BBS)
"zone" -> bundle.putString(KEY_TARGET, EntranceConsts.TAB_TYPE_TRENDS)
"comment" -> bundle.putString(KEY_TARGET, GameDetailTabEntity.TYPE_COMMENT)
"desc" -> bundle.putString(KEY_TARGET, GameDetailTabEntity.TYPE_DETAIL)
"forum" -> bundle.putString(KEY_TARGET, GameDetailTabEntity.TYPE_BBS)
"zone" -> bundle.putString(KEY_TARGET, GameDetailTabEntity.TYPE_ZONE)
}
}
if (traceEvent != null) {
@ -858,7 +862,7 @@ object DirectUtils {
bundle.putString(KEY_ENTRANCE, entrance)
bundle.putString(KEY_GAMEID, id)
bundle.putBoolean(KEY_OPEN_VIDEO_STREAMING, true)
bundle.putString(KEY_TARGET, EntranceConsts.TAB_TYPE_DESC)
bundle.putString(KEY_TARGET, GameDetailTabEntity.TYPE_DETAIL)
jumpActivity(context, bundle)
}
@ -866,7 +870,7 @@ object DirectUtils {
fun directToGameDetail(
context: Context,
id: String,
defaultTab: String = EntranceConsts.TAB_TYPE_DESC,
defaultTab: String = GameDetailTabEntity.TYPE_DETAIL,
entrance: String? = null
) {
val bundle = Bundle()
@ -1524,7 +1528,7 @@ object DirectUtils {
response?.apply {
if (zone.status == "on") {
if (zone.style == "link") {
directToGameDetail(context, gameId, EntranceConsts.TAB_TYPE_TRENDS, entrance)
directToGameDetail(context, gameId, GameDetailTabEntity.TYPE_ZONE, entrance)
} else {
directToWebView(context, url, entrance)
}
@ -2149,4 +2153,16 @@ object DirectUtils {
)
}
}
// 跳转云存档详情页
@JvmStatic
fun directToCloudArchive(context: Context, gameId: String, gameName: String, configUrl: String = "", entrance: String = "") {
val bundle = Bundle()
val gameEntity = GameEntity(id = gameId, name = gameName)
bundle.putParcelable(KEY_GAME_ENTITY, gameEntity)
bundle.putString(KEY_ARCHIVE_CONFIG_URL, configUrl)
bundle.putString(KEY_ENTRANCE, entrance)
bundle.putBoolean(KEY_USE_ALTERNATIVE_LAYOUT, true)
context.startActivity(ShellActivity.getIntent(context, Type.CLOUD_ARCHIVE, bundle))
}
}

View File

@ -19,7 +19,7 @@ import com.gh.gamecenter.game.columncollection.detail.ColumnCollectionDetailFrag
import com.gh.gamecenter.game.commoncollection.detail.CustomCommonCollectionDetailFragment
import com.gh.gamecenter.gamecollection.hotlist.GameCollectionHotListWrapperFragment
import com.gh.gamecenter.gamecollection.square.GameCollectionSquareFragment
import com.gh.gamecenter.gamedetail.GameDetailFragment
import com.gh.gamecenter.gamedetail.GameDetailWrapperFragment
import com.gh.gamecenter.info.InfoWrapperFragment
import com.gh.gamecenter.libao.LibaoDetailFragment
import com.gh.gamecenter.libao.LibaoFragment
@ -88,7 +88,7 @@ object ViewPagerFragmentHelper {
// 游戏详情页
TYPE_GAME -> {
bundle.putString(EntranceConsts.KEY_GAMEID, linkEntity.link)
GameDetailFragment().with(bundle)
GameDetailWrapperFragment().with(bundle)
}
// 我的光环
TYPE_MY_HALO -> {
@ -149,11 +149,11 @@ object ViewPagerFragmentHelper {
NewQuestionDetailFragment().with(bundle)
}
// 其他原来带Toolbar的Fragment
else -> createToolbarWrapperFragment(parentFragment, bundle, linkEntity, isTabWrapper)
else -> createToolbarWrapperFragment(bundle, linkEntity, isTabWrapper)
}
}
private fun createToolbarWrapperFragment(parentFragment: Fragment?, bundle: Bundle, entity: LinkEntity, isTabWrapper: Boolean): Fragment {
private fun createToolbarWrapperFragment(bundle: Bundle, entity: LinkEntity, isTabWrapper: Boolean): Fragment {
var className = ReloadFragment::class.java.name
when (entity.type) {

View File

@ -48,7 +48,7 @@ class FlexLinearLayout @JvmOverloads constructor(context: Context, attrs: Attrib
ta.recycle()
}
fun setTags(tags: ArrayList<TagStyleEntity>) {
fun setTags(tags: List<TagStyleEntity>) {
mTags.clear()
mTotalCount = tags.size
mTotalWidth = measuredWidth

View File

@ -5,15 +5,10 @@ import android.content.ContextWrapper
import android.content.Intent
import android.os.Bundle
import android.view.View
import com.therouter.router.Autowired
import com.therouter.router.Route
import com.therouter.TheRouter
import com.gh.base.DownloadToolbarActivity
import com.gh.common.exposure.ExposureManager
import com.gh.common.exposure.ExposureTraceUtils.appendTrace
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.base.activity.ToolBarActivity.NORMAL_FRAGMENT_BUNDLE
import com.gh.gamecenter.common.base.activity.ToolBarActivity.NORMAL_FRAGMENT_NAME
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.utils.toArrayList
@ -24,7 +19,11 @@ import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.ExposureEvent.Companion.createEvent
import com.gh.gamecenter.feature.exposure.ExposureType
import com.gh.gamecenter.gamedetail.GameDetailFragment
import com.gh.gamecenter.gamedetail.GameDetailWrapperFragment
import com.gh.gamecenter.gamedetail.entity.GameDetailTabEntity
import com.therouter.TheRouter
import com.therouter.router.Autowired
import com.therouter.router.Route
@Route(
path = RouteConsts.activity.gameDetailActivity,
@ -74,11 +73,11 @@ class GameDetailActivity : DownloadToolbarActivity() {
generateDataFromRoute()
super.onCreate(savedInstanceState)
DisplayUtils.transparentStatusBar(this)
DisplayUtils.setStatusBarColor(this, com.gh.gamecenter.common.R.color.transparent, !mIsDarkModeOn)
}
override fun provideNormalIntent(): Intent {
return getTargetIntent(this, GameDetailActivity::class.java, GameDetailFragment::class.java)
return getTargetIntent(this, GameDetailActivity::class.java, GameDetailWrapperFragment::class.java)
}
override fun getLayoutId() = R.layout.activity_game_detail
@ -127,10 +126,15 @@ class GameDetailActivity : DownloadToolbarActivity() {
private fun generateDataFromRoute() {
val bundle = intent.extras
intent?.putExtra(NORMAL_FRAGMENT_NAME, GameDetailFragment::class.java.canonicalName)
intent?.putExtra(NORMAL_FRAGMENT_NAME, GameDetailWrapperFragment::class.java.canonicalName)
intent?.putExtra(NORMAL_FRAGMENT_BUNDLE, bundle)
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
DisplayUtils.setStatusBarColor(this, com.gh.gamecenter.common.R.color.transparent, !mIsDarkModeOn)
}
companion object {
@JvmStatic
@ -190,12 +194,12 @@ class GameDetailActivity : DownloadToolbarActivity() {
}
if (scrollToLibao) {
bundle.putString(EntranceConsts.KEY_TARGET, EntranceConsts.TAB_TYPE_DESC)
bundle.putString(EntranceConsts.KEY_TARGET, GameDetailTabEntity.TYPE_DETAIL)
bundle.putBoolean(EntranceConsts.KEY_SCROLL_TO_LIBAO, true)
}
if (scrollToServer) {
bundle.putString(EntranceConsts.KEY_TARGET, EntranceConsts.TAB_TYPE_DESC)
bundle.putString(EntranceConsts.KEY_TARGET, GameDetailTabEntity.TYPE_DETAIL)
bundle.putBoolean(EntranceConsts.KEY_SCROLL_TO_SERVER, true)
}
@ -302,7 +306,7 @@ class GameDetailActivity : DownloadToolbarActivity() {
}
if (openVideoStreaming) {
bundle.putBoolean(EntranceConsts.KEY_OPEN_VIDEO_STREAMING, true)
bundle.putString(EntranceConsts.KEY_TARGET, EntranceConsts.TAB_TYPE_DESC)
bundle.putString(EntranceConsts.KEY_TARGET, GameDetailTabEntity.TYPE_DETAIL)
}
if (openPlatformWindow) {
bundle.putBoolean(EntranceConsts.KEY_OPEN_PLATFORM_WINDOW, true)
@ -316,7 +320,7 @@ class GameDetailActivity : DownloadToolbarActivity() {
}
}
if (scrollToLibao) {
bundle.putString(EntranceConsts.KEY_TARGET, EntranceConsts.TAB_TYPE_DESC)
bundle.putString(EntranceConsts.KEY_TARGET, GameDetailTabEntity.TYPE_DETAIL)
bundle.putBoolean(EntranceConsts.KEY_SCROLL_TO_LIBAO, true)
}
bundle.putString(EntranceConsts.KEY_GAME_ID, gameId)

View File

@ -5,11 +5,12 @@ import android.content.Intent
import android.os.Bundle
import android.os.Parcelable
import android.view.View
import com.gh.gamecenter.amway.AmwaySuccessFragment
import com.gh.gamecenter.common.base.activity.ToolBarActivity
import com.gh.gamecenter.common.base.fragment.BaseFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.amway.AmwaySuccessFragment
import com.gh.gamecenter.gamedetail.LibaoListFragment
import com.gh.gamecenter.gamedetail.cloudarchive.CloudArchiveFragment
import com.gh.gamecenter.gamedetail.libao.LibaoListFragment
import com.halo.assistant.fragment.SwitchInstallMethodFragment
import com.halo.assistant.fragment.user.ManuallyRealNameFragment
import com.halo.assistant.fragment.user.RealNameInfoFragment
@ -38,6 +39,7 @@ class ShellActivity : ToolBarActivity() {
Type.REAL_NAME_INFO -> startFragment(RealNameInfoFragment().with(bundle))
Type.MANUALLY_REAL_NAME -> startFragment(ManuallyRealNameFragment().with(extraData))
Type.SIMPLE_LIBAO_LIST -> startFragment(LibaoListFragment.newInstance(extraData))
Type.CLOUD_ARCHIVE -> startFragment(CloudArchiveFragment().with(extraData))
}
}
@ -72,7 +74,8 @@ class ShellActivity : ToolBarActivity() {
SWITCH_INSTALL_METHOD("switch_install_method"),
REAL_NAME_INFO("real_name_info"),
MANUALLY_REAL_NAME("manually_real_name"),
SIMPLE_LIBAO_LIST("simple_libao_list");
SIMPLE_LIBAO_LIST("simple_libao_list"),
CLOUD_ARCHIVE("cloud_archive");
companion object {
fun fromString(typeString: String): Type {

View File

@ -2,6 +2,7 @@ package com.gh.gamecenter.adapter;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.text.Html;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
@ -12,6 +13,7 @@ import android.text.style.ClickableSpan;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
@ -20,9 +22,13 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder;
import com.facebook.drawee.view.SimpleDraweeView;
import com.gh.common.util.DirectUtils;
import com.gh.common.util.LibaoUtils;
import com.gh.gamecenter.GameDetailActivity;
import com.gh.gamecenter.ImageViewerActivity;
import com.gh.gamecenter.R;
import com.gh.gamecenter.adapter.viewholder.LibaoDetailContentViewHolder;
import com.gh.gamecenter.adapter.viewholder.LibaoDetailTopViewHolder;
@ -31,6 +37,7 @@ import com.gh.gamecenter.common.entity.SimpleGameEntity;
import com.gh.gamecenter.common.entity.SuggestType;
import com.gh.gamecenter.common.retrofit.Response;
import com.gh.gamecenter.common.utils.ExtensionsKt;
import com.gh.gamecenter.common.utils.ImageUtils;
import com.gh.gamecenter.common.utils.PicassoImageGetter;
import com.gh.gamecenter.common.viewholder.FooterViewHolder;
import com.gh.gamecenter.core.utils.DisplayUtils;
@ -79,6 +86,7 @@ public class LibaoDetailAdapter extends BaseRecyclerAdapter<ViewHolder> {
private String mEntrance;
private final int TYPE_FOOTER = 100;
public LibaoDetailTopViewHolder libaoDetailTopViewHolder;
private ArrayList<View> mImageViewList = new ArrayList<>();
public LibaoDetailAdapter(Context context, OnRequestCallBackListener onRequestCallBackListener,
OnCodeScrollListener onCodeScrollListener, LibaoEntity libaoEntity,
@ -317,6 +325,31 @@ public class LibaoDetailAdapter extends BaseRecyclerAdapter<ViewHolder> {
holder.binding.libaodetailContentLl.setVisibility(View.VISIBLE);
holder.binding.libaodetailContent.setText(Html.fromHtml(mLibaoEntity.getContent()));
}
if (mLibaoEntity.getMaterials().isEmpty()) {
holder.binding.horizontalScrollView.setVisibility(View.GONE);
} else {
holder.binding.horizontalScrollView.setVisibility(View.VISIBLE);
holder.binding.imagesContainer.removeAllViews();
mImageViewList.clear();
for (int i = 0; i < mLibaoEntity.getMaterials().size(); i++) {
String imageUrl = mLibaoEntity.getMaterials().get(i);
SimpleDraweeView imageView = new SimpleDraweeView(mContext);
imageView.setHierarchy(new GenericDraweeHierarchyBuilder(mContext.getResources())
.setFadeDuration(500)
.setPlaceholderImage(com.gh.gamecenter.common.R.drawable.occupy, ScalingUtils.ScaleType.FIT_XY)
.build());
ImageUtils.display(imageView, imageUrl);
final int index = i;
imageView.setOnClickListener(v -> {
Intent intent = ImageViewerActivity.getIntent(mContext, mLibaoEntity.getMaterials(), index, mImageViewList, mEntrance);
mContext.startActivity(intent);
});
mImageViewList.add(imageView);
LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(DisplayUtils.dip2px(24F), DisplayUtils.dip2px(24F));
layoutParams.setMargins(i != 0 ? DisplayUtils.dip2px(16F) : 0, 0, 0, 0);
holder.binding.imagesContainer.addView(imageView, layoutParams);
}
}
if (mLibaoDetailEntity != null) {
holder.binding.libaodetailTimeLl.setVisibility(View.VISIBLE);

View File

@ -31,7 +31,6 @@ import com.gh.gamecenter.common.base.GlobalActivityManager.getCurrentPageEntity
import com.gh.gamecenter.common.base.GlobalActivityManager.getLastPageEntity
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.eventbus.EBReuse
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.utils.NewFlatLogUtils
import com.gh.gamecenter.core.runOnIoThread
@ -44,8 +43,9 @@ import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.feature.view.DownloadButton.ButtonStyle
import com.gh.gamecenter.gamedetail.GameDetailFragment
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.dialog.GamePermissionDialogFragment
import com.gh.gamecenter.gamedetail.entity.GameDetailTabEntity
import com.gh.gamecenter.teenagermode.TeenagerModeActivity.Companion.getIntent
import com.gh.vspace.VHelper
import com.lightgame.download.DownloadEntity
@ -56,13 +56,15 @@ import java.io.File
// 虽然叫 ViewHolder但其实就是一个用来临时放 View 和相关操作的包裹类
class DetailViewHolder(
view: View,
val viewModel: GameDetailViewModel?,
val gameEntity: GameEntity,
val isNewsDetail: Boolean, // 新闻详情不显示下载的游戏名, 只显示下载状态
entrance: String?,
name: String?,
title: String?,
val traceEvent: ExposureEvent?,
val isSupportDualButton: Boolean = false // 是否支持双下载按钮,不支持的时候跟普通列表意义选用优先级高的那个来显示
val isSupportDualButton: Boolean = false, // 是否支持双下载按钮,不支持的时候跟普通列表意义选用优先级高的那个来显示
onDownloadClickAction: ((Boolean) -> Unit)? = null
) {
var context: Context
var downloadBottom: View
@ -115,7 +117,8 @@ class DetailViewHolder(
mTitle = title ?: "",
mAsVGame = false,
mShowDualDownloadButton = gameDownloadMode == GameEntity.GAME_DOWNLOAD_BUTTON_MODE_DUAL,
mTraceEvent = traceEvent
mTraceEvent = traceEvent,
onDownloadClickAction = onDownloadClickAction
)
val vGameDownloadListener = OnDetailDownloadClickListener(
@ -125,7 +128,8 @@ class DetailViewHolder(
mTitle = title ?: "",
mAsVGame = true,
mShowDualDownloadButton = gameDownloadMode == GameEntity.GAME_DOWNLOAD_BUTTON_MODE_DUAL,
mTraceEvent = traceEvent
mTraceEvent = traceEvent,
onDownloadClickAction = onDownloadClickAction
)
// 不支持双下载按钮的情况时,优选一个下载方式显示
@ -183,7 +187,8 @@ class DetailViewHolder(
private val mTitle: String,
private val mAsVGame: Boolean,
private val mShowDualDownloadButton: Boolean,
private val mTraceEvent: ExposureEvent?
private val mTraceEvent: ExposureEvent?,
private val onDownloadClickAction: ((Boolean) -> Unit)? = null
) : View.OnClickListener {
private val mGameEntity: GameEntity = mViewHolder.gameEntity
@ -276,7 +281,7 @@ class DetailViewHolder(
showLandPageAddressDialogIfNeeded()
}
} else if ("toast" == offStatus) {
EventBus.getDefault().post(EBReuse(GameDetailFragment.SKIP_RATING))
mViewHolder.viewModel?.performTabSelected(GameDetailTabEntity.TYPE_COMMENT)
ToastUtils.toast("该游戏因故暂不提供下载,具体详情可在相关评论中查看,敬请谅解~")
showLandPageAddressDialogIfNeeded()
}
@ -295,6 +300,10 @@ class DetailViewHolder(
DataLogUtils.uploadGameLog(mViewHolder.context, mGameEntity.id, mGameEntity.name, mEntrance)
}
val buttonText = mViewHolder.downloadPb.text.ifEmpty { mViewHolder.overlayTv?.text ?: "" }
val isUpdate = mViewHolder.context.getString(com.gh.gamecenter.feature.R.string.update_v) == buttonText || buttonText.contains(mViewHolder.context.getString(com.gh.gamecenter.feature.R.string.update))
onDownloadClickAction?.invoke(isUpdate)
preDownload()
}

View File

@ -2,17 +2,30 @@ package com.gh.gamecenter.cloudarchive
import android.app.Application
import com.gh.gamecenter.common.baselist.ListViewModel
import com.gh.gamecenter.common.utils.toRequestBody
import com.gh.gamecenter.entity.ArchiveEntity
import com.gh.gamecenter.retrofit.RetrofitManager
import com.lightgame.utils.Utils
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import okhttp3.*
import retrofit2.HttpException
import java.io.IOException
open class BaseCloudArchiveViewModel(application: Application, private val mConfigUrl: String): ListViewModel<ArchiveEntity, ArchiveEntity>(application) {
open class BaseCloudArchiveViewModel(
application: Application,
private val mGameId: String,
private var mConfigUrl: String
) : ListViewModel<ArchiveEntity, ArchiveEntity>(application) {
private var mArchiveConfigStr = ""
init {
getArchiveConfigString()
if (mConfigUrl.isEmpty()) {
getArchiveConfigUrl()
} else {
getArchiveConfigString()
}
}
// 通过url获取config字符串内容
@ -41,6 +54,27 @@ open class BaseCloudArchiveViewModel(application: Application, private val mConf
}
}
// 获取游戏存档配置url
private fun getArchiveConfigUrl() {
val map = mapOf(
"game_ids" to listOf(mGameId)
)
RetrofitManager.getInstance().newApi.getGamesArchiveConfigs(map.toRequestBody())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : com.gh.gamecenter.common.retrofit.Response<List<ArchiveEntity>>() {
override fun onResponse(response: List<ArchiveEntity>?) {
mConfigUrl = response?.find { it.gameId == mGameId }?.configUrl ?: return
getArchiveConfigString()
}
override fun onFailure(e: HttpException?) {
super.onFailure(e)
Utils.toast(getApplication(), "获取存档配置失败")
}
})
}
override fun provideDataObservable(page: Int): Observable<List<ArchiveEntity>>? = null
override fun mergeResultLiveData() {}

View File

@ -8,7 +8,6 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.gh.common.util.NewFlatLogUtils
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.toJson
import com.gh.gamecenter.common.utils.toRequestBody
import com.gh.gamecenter.core.utils.GsonUtils
import com.gh.gamecenter.entity.ArchiveEntity
@ -22,7 +21,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import okhttp3.*
import okhttp3.ResponseBody
import retrofit2.HttpException
class CloudArchiveManagerViewModel(
@ -30,7 +29,7 @@ class CloudArchiveManagerViewModel(
val gameId: String,
val gameName: String,
configUrl: String
) : BaseCloudArchiveViewModel(application, configUrl) {
) : BaseCloudArchiveViewModel(application, gameId, configUrl) {
companion object {
private const val SORT_TYPE_CREATE = "time.create:-1"

View File

@ -0,0 +1,11 @@
package com.gh.gamecenter.entity
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
data class HistoryGameDetailEntity(
@PrimaryKey
var id: String = "",
var name: String? = "",
)

View File

@ -8,7 +8,6 @@ import android.text.SpannableStringBuilder
import android.view.View
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.common.util.*
import com.gh.common.util.DialogUtils
import com.gh.common.util.LogUtils
import com.gh.common.util.NewLogUtils
import com.gh.common.view.ImageContainerView
@ -17,22 +16,18 @@ import com.gh.gamecenter.ImageViewerActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.callback.ConfirmListener
import com.gh.gamecenter.common.entity.AdditionalParamsEntity
import com.gh.gamecenter.common.entity.CommunityEntity
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.SpanBuilder
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.databinding.CommunityAnswerItemBinding
import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.eventbus.EBUserFollow
import com.gh.gamecenter.forum.detail.ForumDetailActivity
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.qa.answer.BaseAnswerOrArticleItemViewHolder
import com.gh.gamecenter.qa.article.detail.ArticleDetailActivity
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.feature.entity.ArticleEntity
import com.gh.gamecenter.feature.entity.CommunityItemData
import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.forum.detail.ForumDetailActivity
import com.gh.gamecenter.forum.home.ArticleItemVideoView.Companion.toArticleVideoData
import com.gh.gamecenter.forum.search.CommunitySearchEventListener
import com.gh.gamecenter.forum.search.CommunitySearchEventListener.Companion.SEARCH_BUTTON_COMMENT
@ -44,6 +39,9 @@ import com.gh.gamecenter.forum.search.CommunitySearchEventListener.Companion.SEA
import com.gh.gamecenter.forum.search.CommunitySearchEventListener.Companion.SEARCH_BUTTON_VIEW_IMAGE
import com.gh.gamecenter.forum.search.CommunitySearchEventListener.Companion.SEARCH_BUTTON_VIEW_USER_DETAIL
import com.gh.gamecenter.forum.search.CommunitySearchEventListener.Companion.htmlToString
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.qa.answer.BaseAnswerOrArticleItemViewHolder
import com.gh.gamecenter.qa.article.detail.ArticleDetailActivity
import com.gh.gamecenter.qa.entity.QuestionsDetailEntity
import com.gh.gamecenter.qa.questions.invite.QuestionsInviteActivity
import com.gh.gamecenter.qa.questions.newdetail.NewQuestionDetailActivity
@ -348,8 +346,8 @@ class ForumArticleAskItemViewHolder(
.setReleaseWhenLossAudio(true)
.setLooping(false)
.setShowFullAnimation(false)
.setEnlargeImageRes(R.drawable.ic_game_detail_enter_full_screen)
.setShrinkImageRes(R.drawable.ic_game_detail_exit_full_screen)
.setEnlargeImageRes(R.drawable.ic_video_enter_full_screen)
.setShrinkImageRes(R.drawable.ic_video_exit_full_screen)
.setVideoAllCallBack(object : GSYSampleCallBack() {
override fun onQuitFullscreen(url: String?, vararg objects: Any) {
orientationUtils.backToProtVideo()

View File

@ -3,18 +3,19 @@ package com.gh.gamecenter.game.horizontal
import android.content.Context
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.*
import com.gh.common.util.DataCollectionUtils
import com.gh.common.util.DownloadItemUtils
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.adapter.viewholder.GameViewHolder
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.StringUtils
import com.gh.gamecenter.entity.SubjectEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.subjectTypeToComponentStyle
import com.gh.gamecenter.feature.minigame.MiniGameItemHelper
import com.gh.gamecenter.gamedetail.detail.viewholder.BaseGameDetailItemViewHolder
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.subjectTypeToComponentStyle
import com.lightgame.adapter.BaseRecyclerAdapter
import com.lightgame.download.DownloadEntity
@ -22,7 +23,9 @@ class GameHorizontalAdapter(
context: Context,
private var mSubjectEntity: SubjectEntity,
private var type: GameHorizontalListType = GameHorizontalListType.SubjectHorizontalType,
private val trackColumnClick: Boolean = true
private val trackColumnClick: Boolean = true,
private val gameDetailTrackData: BaseGameDetailItemViewHolder.GameDetailModuleTrackData? = null,
private val getGameStatus: (() -> String)? = null
) : BaseRecyclerAdapter<GameHorizontalItemViewHolder>(context) {
var gameName = ""
@ -85,6 +88,7 @@ class GameHorizontalAdapter(
gameIcon.displayGameIcon(gameEntity)
GameHorizontalSimpleItemViewHolder.setHorizontalNameAndGravity(gameName, gameEntity.name)
downloadBtn.goneIf(!mSubjectEntity.showDownload)
gameName.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(mContext))
gameName.maxLines = if (mSubjectEntity.showDownload) 1 else 2
}
holder.bindGameHorizontalItem(gameEntity, mSubjectEntity)
@ -111,20 +115,15 @@ class GameHorizontalAdapter(
if (type == GameHorizontalListType.GameDetailHorizontalType) {
DataCollectionUtils.uploadClick(mContext, path, "游戏详情", gameEntity.name)
NewLogUtils.logGameDetailPopularClick(gameName, gameId, "game", gameEntity.name ?: "")
SensorsBridge.trackGameDetailPagePopularClick(
gameId = gameId,
gameName = gameName,
pageName = GlobalActivityManager.getCurrentPageEntity().pageName,
pageId = GlobalActivityManager.getCurrentPageEntity().pageId,
pageBusinessId = GlobalActivityManager.getCurrentPageEntity().pageBusinessId,
lastPageName = GlobalActivityManager.getLastPageEntity().pageName,
lastPageId = GlobalActivityManager.getLastPageEntity().pageId,
lastPageBusinessId = GlobalActivityManager.getLastPageEntity().pageBusinessId,
downloadStatus = game?.downloadStatusChinese ?: "",
gameType = game?.categoryChinese ?: "",
clickGameType = gameEntity.categoryChinese,
clickGameName = gameEntity.name ?: "",
clickGameId = gameEntity.id
SensorsBridge.trackGameDetailModuleClick(
gameDetailTrackData?.gameId,
gameDetailTrackData?.gameName,
gameDetailTrackData?.gameType,
"右上角",
gameDetailTrackData?.moduleType,
gameDetailTrackData?.moduleName,
gameDetailTrackData?.sequence,
gameStatus = getGameStatus?.invoke()
)
if (!gameEntity.isMiniGame() && trackColumnClick) {
SensorsBridge.trackColumnClick(

View File

@ -39,7 +39,6 @@ import com.gh.gamecenter.common.syncpage.SyncDataEntity
import com.gh.gamecenter.common.syncpage.SyncFieldConstants
import com.gh.gamecenter.common.syncpage.SyncPageRepository
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.SegmentedFilterView
import com.gh.gamecenter.core.iinterface.IScrollable
import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.databinding.FragmentGameCollectionDetailBinding
@ -48,7 +47,6 @@ import com.gh.gamecenter.entity.GamesCollectionDetailEntity
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.eventbus.EBUserFollow
import com.gh.gamecenter.gamedetail.GameDetailFragment
import com.gh.gamecenter.home.video.ScrollCalculatorHelper
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.login.user.UserViewModel
@ -187,12 +185,10 @@ class GameCollectionDetailFragment :
root.layoutParams = this
}
orderSfv.setItemList(listOf("正序", "倒序"), 0)
orderSfv.setOnCheckedCallback(object : SegmentedFilterView.OnCheckedCallback {
override fun onItemCheck(position: Int) {
getFilterVH()?.binding?.orderSfv?.performClick(position)
updateFilterView()
}
})
orderSfv.setOnCheckedCallback { position ->
getFilterVH()?.binding?.orderSfv?.performClick(position)
updateFilterView()
}
commentHintTv.text = "玩家评论"
}
}
@ -741,7 +737,7 @@ class GameCollectionDetailFragment :
if (activity != null && activity?.isFinishing != true) {
startPlayLogic(isAutoPlay = true)
}
}, GameDetailFragment.INITIAL_DELAY)
}, INITIAL_DELAY)
}
}
}
@ -1209,4 +1205,8 @@ class GameCollectionDetailFragment :
appbar.setExpanded(true)
}
}
companion object {
const val INITIAL_DELAY = 500L
}
}

View File

@ -13,15 +13,15 @@ import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.observer.MuteCallback
import com.gh.gamecenter.common.observer.VolumeObserver
import com.gh.gamecenter.core.runOnIoThread
import com.gh.common.util.*
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.debounceActionWithInterval
import com.gh.gamecenter.common.utils.rxTimer
import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.common.utils.NetworkUtils
import com.gh.gamecenter.common.utils.debounceActionWithInterval
import com.gh.gamecenter.common.utils.rxTimer
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.utils.MD5Utils
import com.gh.gamecenter.entity.GamesCollectionDetailEntity
import com.gh.gamecenter.home.video.ScrollCalculatorHelper
@ -180,7 +180,7 @@ class GameCollectionVideoView @JvmOverloads constructor(context: Context, attrs:
private fun mute(isManual: Boolean = false) {
viewModel?.videoIsMuted = true
volume.setImageResource(R.drawable.ic_game_detail_volume_off)
volume.setImageResource(R.drawable.ic_video_volume_off)
CustomManager.getCustomManager(getKey()).isNeedMute = true
if (isManual) {
Utils.toast(context, "当前处于静音状态")
@ -190,7 +190,7 @@ class GameCollectionVideoView @JvmOverloads constructor(context: Context, attrs:
private fun unMute(isManual: Boolean = false) {
viewModel?.videoIsMuted = false
volume.setImageResource(R.drawable.ic_game_detail_volume_on)
volume.setImageResource(R.drawable.ic_video_volume_on)
CustomManager.getCustomManager(getKey()).isNeedMute = false
if (isManual) {
logVideoEvent("video_game_collect_detail_mute_cancel")
@ -278,9 +278,9 @@ class GameCollectionVideoView @JvmOverloads constructor(context: Context, attrs:
if (mStartButton is ImageView) {
val imageView = mStartButton as ImageView
when (mCurrentState) {
GSYVideoView.CURRENT_STATE_PLAYING -> imageView.setImageResource(R.drawable.ic_game_detail_pause)
GSYVideoView.CURRENT_STATE_ERROR -> imageView.setImageResource(R.drawable.ic_game_detail_play)
else -> imageView.setImageResource(R.drawable.ic_game_detail_play)
GSYVideoView.CURRENT_STATE_PLAYING -> imageView.setImageResource(R.drawable.ic_video_pause)
GSYVideoView.CURRENT_STATE_ERROR -> imageView.setImageResource(R.drawable.ic_video_play)
else -> imageView.setImageResource(R.drawable.ic_video_play)
}
}
}
@ -314,11 +314,11 @@ class GameCollectionVideoView @JvmOverloads constructor(context: Context, attrs:
}
override fun getEnlargeImageRes(): Int {
return R.drawable.ic_game_detail_enter_full_screen
return R.drawable.ic_video_enter_full_screen
}
override fun getShrinkImageRes(): Int {
return R.drawable.ic_game_detail_exit_full_screen
return R.drawable.ic_video_exit_full_screen
}
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {

View File

@ -1,51 +0,0 @@
package com.gh.gamecenter.gamedetail
import android.content.Context
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.utils.safelyGetInRelease
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.databinding.ItemGameDetailContentCardContentBinding
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity
import com.lightgame.adapter.BaseRecyclerAdapter
class GameDetailContentCardContentAdapter(
context: Context,
private val linkEntity: ContentCardEntity,
private val isHighlightBg: Boolean = false
) : BaseRecyclerAdapter<RecyclerView.ViewHolder>(context) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
GameDetailContentCardContentItemViewHolder(parent.toBinding())
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is GameDetailContentCardContentItemViewHolder) {
holder.binding.root.setTextColor(
if (isHighlightBg) com.gh.gamecenter.common.R.color.text_secondary.toColor(mContext) else com.gh.gamecenter.common.R.color.text_tertiary.toColor(
mContext
)
)
if (linkEntity.type == "func_server" && linkEntity.server != null && linkEntity.server?.calendar?.isNotEmpty() == true) {
val calendarList = linkEntity.server!!.calendar
val realPosition = position % calendarList.size
calendarList.safelyGetInRelease(realPosition)?.let {
val serverTime =
if (TimeUtils.isToday(it.getTime()))
it.getFormatTime("今天 HH:mm")
else if (TimeUtils.isTomorrow(it.getTime()))
it.getFormatTime("明天 HH:mm")
else
it.getFormatTime("MM-dd HH:mm")
holder.binding.root.text = "$serverTime ${it.type}"
}
}
}
}
override fun getItemCount(): Int = Int.MAX_VALUE
class GameDetailContentCardContentItemViewHolder(var binding: ItemGameDetailContentCardContentBinding) :
RecyclerView.ViewHolder(binding.root)
}

File diff suppressed because it is too large Load Diff

View File

@ -1,57 +0,0 @@
package com.gh.gamecenter.gamedetail
import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.BaseFragment
import com.gh.gamecenter.common.utils.toDrawable
import com.gh.gamecenter.feature.entity.LibaoEntity
import com.gh.gamecenter.gamedetail.desc.GameLibaoAdapter
class LibaoListFragment : BaseFragment<Any>() {
override fun getLayoutId() = R.layout.fragment_libao_list
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val recyclerView = view.findViewById<RecyclerView>(R.id.recyclerView)
val toolbarTitleTv = view.findViewById<TextView>(R.id.normal_title)
val toolbarBackContainer = view.findViewById<View>(com.gh.gamecenter.selector.R.id.backContainer)
toolbarTitleTv.setText("游戏礼包")
toolbarBackContainer.setOnClickListener { requireActivity().onBackPressed() }
val gameId = arguments?.getString(ARG_GAME_ID) ?: ""
val gameName = arguments?.getString(ARG_GAME_NAME) ?: ""
val libaoList = arguments?.getParcelableArrayList<LibaoEntity>(ARG_LIBAO_LIST) ?: arrayListOf()
recyclerView?.apply {
layoutManager = LinearLayoutManager(requireContext())
adapter = adapter ?: GameLibaoAdapter(requireContext(), libaoList, gameName, gameId, false, true)
}
}
companion object {
private const val ARG_GAME_ID = "game_id"
private const val ARG_GAME_NAME = "game_name"
private const val ARG_LIBAO_LIST = "libao_list"
fun getBundle(gameId: String, gameName: String, libaoList: ArrayList<LibaoEntity>) = Bundle().apply {
putString(ARG_GAME_ID, gameId)
putString(ARG_GAME_NAME, gameName)
putParcelableArrayList(ARG_LIBAO_LIST, libaoList)
}
fun newInstance(bundle: Bundle?) = LibaoListFragment().apply {
if (bundle != null) {
arguments = bundle
}
}
}
}

View File

@ -1,81 +1,119 @@
package com.gh.gamecenter.gamedetail.cloudarchive
import android.os.Bundle
import android.view.View
import android.view.inputmethod.EditorInfo
import androidx.core.os.bundleOf
import com.gh.common.util.NewFlatLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.catalog.SpecialCatalogFragment
import com.gh.gamecenter.cloudarchive.CloudArchiveManagerActivity
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.base.fragment.LazyFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toResString
import com.gh.gamecenter.common.json.json
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.FragmentCloudArchiveAlBinding
import com.gh.gamecenter.databinding.FragmentCloudArchiveBinding
import com.gh.gamecenter.feature.entity.GameEntity
class CloudArchiveFragment : LazyFragment() {
private var mUseAlternativeLayout = false
private var mGameEntity: GameEntity? = null
private var mBinding: FragmentCloudArchiveBinding? = null
private var mAlternativeBinding: FragmentCloudArchiveAlBinding? = null
private var mSearchFragment: CloudArchiveListFragment? = null
private var mNormalFragment: CloudArchiveListFragment? = null
private var mIsSearch = false
private var mOrderList =
listOf(CloudArchiveListViewModel.SortType.NEWEST, CloudArchiveListViewModel.SortType.HOTTEST)
override fun getRealLayoutId() = R.layout.fragment_cloud_archive
private val searchBarBinding
get() = if (mUseAlternativeLayout) mAlternativeBinding?.searchBar else mBinding?.searchBar
private val orderSfv
get() = if (mUseAlternativeLayout) mAlternativeBinding?.orderSfv else mBinding?.orderSfv
private val archiveManageTv
get() = if (mUseAlternativeLayout) mAlternativeBinding?.archiveManageTv else mBinding?.archiveManageTv
override fun onCreate(savedInstanceState: Bundle?) {
mUseAlternativeLayout = arguments?.getBoolean(EntranceConsts.KEY_USE_ALTERNATIVE_LAYOUT) ?: false
super.onCreate(savedInstanceState)
}
override fun getRealLayoutId() =
if (mUseAlternativeLayout) R.layout.fragment_cloud_archive_al else R.layout.fragment_cloud_archive
override fun onRealLayoutInflated(inflatedView: View) {
mBinding = FragmentCloudArchiveBinding.bind(inflatedView)
if (mUseAlternativeLayout) {
mAlternativeBinding = FragmentCloudArchiveAlBinding.bind(inflatedView)
} else {
mBinding = FragmentCloudArchiveBinding.bind(inflatedView)
}
}
override fun onFragmentFirstVisible() {
mGameEntity = arguments?.getParcelable(EntranceConsts.KEY_GAME)
mGameEntity = arguments?.getParcelable(EntranceConsts.KEY_GAME_ENTITY)
super.onFragmentFirstVisible()
changeContentFragment()
SensorsBridge.trackEvent("CloudSavePageView", json {
"game_id" to mGameEntity?.id
"game_name" to mGameEntity?.name
"last_page_name" to GlobalActivityManager.getLastPageEntity().pageName
"last_page_id" to GlobalActivityManager.getLastPageEntity().pageId
})
}
override fun inflateRealView() {
super.inflateRealView()
mBinding?.run {
searchBar.etSearch.hint = R.string.game_detail_cloud_archive_search_hint.toResString()
orderSfv.setItemList(mOrderList.map { it.value }, 1)
orderSfv.setOnCheckedCallback {
mAlternativeBinding?.run {
reuseToolbar.normalTitle.run {
text = "${mGameEntity?.name}-云存档"
marqueeOnce()
}
}
orderSfv?.run {
setItemList(mOrderList.map { it.value }, 1)
setOnCheckedCallback {
if (mIsSearch) {
mSearchFragment?.updateSortType(mOrderList[it])
} else {
mNormalFragment?.updateSortType(mOrderList[it])
}
}
searchBar.etSearch.setOnEditorActionListener { _, actionId, _ ->
}
searchBarBinding?.run {
etSearch.hint = R.string.game_detail_cloud_archive_search_hint.toResString()
tvBack.setOnClickListener {
changeSearchStatus(false)
etSearch.setText("")
}
etSearch.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
searchBar.tvSearch.performClick()
tvSearch.performClick()
}
false
}
searchBar.tvSearch.setOnClickListener {
val keyWord = searchBar.etSearch.text.toString().trim { it <= ' ' }
tvSearch.setOnClickListener {
val keyWord = etSearch.text.toString().trim { it <= ' ' }
if (keyWord.isBlank()) {
toast(R.string.search_hint)
} else {
changeSearchStatus(true)
}
}
searchBar.tvBack.setOnClickListener {
changeSearchStatus(false)
searchBar.etSearch.setText("")
}
archiveManageTv.setOnClickListener {
startActivity(
CloudArchiveManagerActivity.getIntent(
requireContext(),
mGameEntity ?: GameEntity(),
arguments?.getString(EntranceConsts.KEY_ARCHIVE_CONFIG_URL) ?: "",
"游戏详情页"
)
}
archiveManageTv?.setOnClickListener {
startActivity(
CloudArchiveManagerActivity.getIntent(
requireContext(),
mGameEntity ?: GameEntity(),
arguments?.getString(EntranceConsts.KEY_ARCHIVE_CONFIG_URL) ?: "",
"游戏详情页"
)
}
)
}
}
@ -99,7 +137,7 @@ class CloudArchiveFragment : LazyFragment() {
EntranceConsts.KEY_ARCHIVE_CONFIG_URL to arguments?.getString(EntranceConsts.KEY_ARCHIVE_CONFIG_URL, "")
)
if (mIsSearch) {
val keyWord = mBinding?.searchBar?.etSearch?.text?.toString()?.trim { it <= ' ' }
val keyWord = searchBarBinding?.etSearch?.text?.toString()?.trim { it <= ' ' }
bundle.putString(EntranceConsts.KEY_SEARCHKEY, keyWord)
NewFlatLogUtils.logCloudArchiveSearchKeyUpload(
mGameEntity?.id ?: "",
@ -113,7 +151,7 @@ class CloudArchiveFragment : LazyFragment() {
.replace(
R.id.contentFragment,
fragment,
SpecialCatalogFragment::class.java.name
CloudArchiveListFragment::class.java.name
)
.commitAllowingStateLoss()
}
@ -122,14 +160,27 @@ class CloudArchiveFragment : LazyFragment() {
when {
mIsSearch != isSearch -> {
mIsSearch = isSearch
mBinding?.searchBar?.tvBack?.goneIf(!mIsSearch)
searchBarBinding?.tvBack?.goneIf(!mIsSearch)
changeContentFragment()
}
mIsSearch -> {
val keyWord = mBinding?.searchBar?.etSearch?.text?.toString()?.trim { it <= ' ' } ?: ""
val keyWord = searchBarBinding?.etSearch?.text?.toString()?.trim { it <= ' ' } ?: ""
NewFlatLogUtils.logCloudArchiveSearchKeyUpload(mGameEntity?.id ?: "", mGameEntity?.name ?: "", keyWord)
mSearchFragment?.updateSearchKeyWord(keyWord)
}
}
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
orderSfv?.run {
setContainerBackground(com.gh.gamecenter.common.R.drawable.button_round_f5f5f5.toDrawable(requireContext()))
setIndicatorBackground(R.drawable.bg_game_collection_sfv_indicator.toDrawable(requireContext()))
setTextColor(
com.gh.gamecenter.common.R.color.text_secondary.toColor(requireContext()),
com.gh.gamecenter.common.R.color.text_tertiary.toColor(requireContext())
)
}
}
}

View File

@ -15,7 +15,7 @@ class CloudArchiveListViewModel(
private val mGameId: String,
private var mKeyWord: String,
configUrl: String
) : BaseCloudArchiveViewModel(application, configUrl) {
) : BaseCloudArchiveViewModel(application, mGameId, configUrl) {
val refresh = MutableLiveData<Boolean>()
private var mSortType = SortType.HOTTEST

View File

@ -1,337 +0,0 @@
package com.gh.gamecenter.gamedetail.desc
import android.content.Context
import android.graphics.drawable.ColorDrawable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.PopupWindow
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.*
import com.gh.common.util.DialogUtils
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.baselist.ListAdapter
import com.gh.gamecenter.common.callback.ConfirmListener
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.ItemViewType
import com.gh.gamecenter.common.eventbus.EBReuse
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.SpanBuilder
import com.gh.gamecenter.databinding.ItemGameDetailRatingCommentBinding
import com.gh.gamecenter.entity.RatingComment
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.gamedetail.GameDetailFragment
import com.gh.gamecenter.gamedetail.rating.RatingReplyActivity
import com.gh.gamecenter.gamedetail.rating.edit.RatingEditActivity
import com.gh.gamecenter.gamedetail.rating.logs.CommentLogsActivity
import com.gh.gamecenter.login.user.UserManager
import org.greenrobot.eventbus.EventBus
import java.util.regex.Pattern
class DescCommentsAdapter(
context: Context,
var mViewModel: DescViewModel,
private var mEntrance: String,
private var gameName: String?
) : ListAdapter<RatingComment>(context) {
var comments = ArrayList<RatingComment>()
val path = "游戏详情:介绍"
override fun getItemViewType(position: Int): Int {
return if (position == comments.size) {
ItemViewType.ITEM_FOOTER
} else {
ItemViewType.ITEM_BODY
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == ItemViewType.ITEM_BODY) {
GameDetailRatingCommentViewHolder(parent.toBinding())
} else {
MoreViewHolder(LayoutInflater.from(mContext).inflate(R.layout.item_game_detail_comment_more, parent, false))
}
}
override fun getItemCount(): Int {
return comments.size + 1
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is GameDetailRatingCommentViewHolder) {
val commentData = comments[position]
var isChildLongClick = false
holder.binding.run {
ImageUtils.display(userIcon, commentData.user.icon)
ImageUtils.display(userBadge, commentData.user.auth?.icon)
userName.text = commentData.user.name
ratingStart.rating = commentData.star.toFloat()
val p = Pattern.compile(RatingEditActivity.LABEL_REGEX)
val m = p.matcher(commentData.content)
if (m.find()) {
val contents =
TextHelper.getCommentLabelSpannableStringBuilder(commentData.content, com.gh.gamecenter.common.R.color.text_theme)
content.setTextWithHighlightedTextWrappedInsideWrapper(
text = contents,
highlightedTextClickListener = TextHelper.DirectToWebViewHighlightedTextClick(mContext, path)
)
} else {
content.setTextWithHighlightedTextWrappedInsideWrapper(
text = commentData.content,
highlightedTextClickListener = TextHelper.DirectToWebViewHighlightedTextClick(mContext, path)
)
}
if (commentData.user.badge != null) {
sdvUserBadge.visibility = View.VISIBLE
tvBadgeName.visibility = View.VISIBLE
ImageUtils.display(sdvUserBadge, commentData.user.badge?.icon)
tvBadgeName.text = commentData.user.badge?.name
} else {
sdvUserBadge.visibility = View.GONE
tvBadgeName.visibility = View.GONE
}
ipRegionTv.goneIf(!(commentData.source != null && commentData.source.region.isNotEmpty()))
ipRegionTv.text = " · ${commentData.source?.region}"
when {
commentData.isEditContent == null -> {
time.setTextColor(ContextCompat.getColor(mContext, com.gh.gamecenter.common.R.color.text_tertiary))
time.text = if (commentData.ignore) {
val s = "${NewsUtils.getFormattedTime(commentData.time)} 保护期评论不计入总分"
SpanBuilder(s).image(s.length - 12, s.length - 11, R.drawable.ic_ignore_rating_tips)
.color(mContext, s.length - 10, s.length, com.gh.gamecenter.common.R.color.text_secondary).build()
} else {
NewsUtils.getFormattedTime(commentData.time)
}
}
commentData.isEditContent!! -> {
time.setTextColor(ContextCompat.getColor(mContext, com.gh.gamecenter.common.R.color.text_F56614))
time.text = if (commentData.ignore) {
"${NewsUtils.getFormattedTime(commentData.time)} 保护期间修改评论 >"
} else {
"${NewsUtils.getFormattedTime(commentData.time)} 已修改 >"
}
}
else -> {
time.setTextColor(ContextCompat.getColor(mContext, com.gh.gamecenter.common.R.color.text_F56614))
time.text = if (commentData.ignore) {
"${NewsUtils.getFormattedTime(commentData.time)} 保护期间修改评论"
} else {
"${NewsUtils.getFormattedTime(commentData.time)} 已修改"
}
}
}
sdvUserBadge.setOnClickListener {
DialogUtils.showViewBadgeDialog(mContext, commentData.user.badge, object : ConfirmListener {
override fun onConfirm() {
MtaHelper.onEvent(
"进入徽章墙_用户记录",
"游戏详情-玩家评论",
"${commentData.user.name}${commentData.user.id}"
)
MtaHelper.onEvent("徽章中心", "进入徽章中心", "游戏详情-玩家评论")
DirectUtils.directToBadgeWall(
mContext,
commentData.user.id,
commentData.user.name,
commentData.user.icon
)
}
})
}
userIcon.setOnClickListener {
DirectUtils.directToHomeActivity(mContext, commentData.user.id, mEntrance, "游戏详情-玩家评论")
MtaHelper.onEvent("游戏详情_新", "玩家评论_点击用户头像", mViewModel.game?.name)
NewLogUtils.logGameDetailCommentClick(
mViewModel.game?.name ?: "",
mViewModel.game?.id ?: "",
"个人主页"
)
}
userName.setOnClickListener {
userIcon.performClick()
MtaHelper.onEvent("游戏详情_新", "玩家评论_点击用户名字", mViewModel.game?.name)
}
tvBadgeName.setOnClickListener { sdvUserBadge.performClick() }
commentItem.setOnClickListener {
if (isChildLongClick) {
isChildLongClick = false
return@setOnClickListener
}
val exposureSource = arrayListOf(
ExposureSource("游戏详情"),
ExposureSource("详情tab"),
ExposureSource("玩家评价"),
).toJson()
val intent = RatingReplyActivity.getIntent(
context = mContext,
gameId = mViewModel.game?.id ?: "",
commentId = commentData.id,
exposureSource = exposureSource,
entrance = mEntrance,
path = path
)
SyncDataBetweenPageHelper.startActivityForResult(mContext, intent, RATING_REPLY_REQUEST, position)
MtaHelper.onEvent("游戏详情_新", "玩家评论_点击评论", mViewModel.game?.name)
NewLogUtils.logGameDetailCommentClick(
mViewModel.game?.name ?: "",
mViewModel.game?.id ?: "",
"评论内容"
)
}
content.setExpandCallback {
MtaHelper.onEvent("游戏详情_新", "玩家评论_点击全文", mViewModel.game?.name)
}
content.setOnLongClickListener(View.OnLongClickListener {
isChildLongClick = true
commentData.content.replace(RatingEditActivity.LABEL_REPLACE_REGEX.toRegex(), "").copyTextAndToast()
return@OnLongClickListener true
})
more.setOnClickListener {
showMorePopWindow(it, commentData.user.id == UserManager.getInstance().userId) { text ->
when (text) {
"复制" -> {
commentData.content.replace(RatingEditActivity.LABEL_REPLACE_REGEX.toRegex(), "")
.copyTextAndToast()
MtaHelper.onEvent("游戏详情_新", "玩家评论_复制", mViewModel.game?.name)
}
"修改" -> {
MtaHelper.onEvent("游戏详情_新", "玩家评论_修改", mViewModel.game?.name)
val intent = RatingEditActivity.getPatchIntent(mContext, mViewModel.game!!, commentData)
SyncDataBetweenPageHelper.startActivityForResult(
mContext,
intent,
RATING_PATCH_REQUEST,
position
)
}
"投诉" -> {
MtaHelper.onEvent("游戏详情_新", "玩家评论_投诉", mViewModel.game?.name)
mContext.ifLogin(BaseActivity.mergeEntranceAndPath(mEntrance, path)) {
DialogUtils.showReportReasonDialog(
mContext,
Constants.REPORT_LIST.toList() as java.util.ArrayList<String>
) { reason, desc ->
SimpleRequestHelper.reportGameComment(
mViewModel.game?.id ?: "",
commentData.id,
if (reason != "其他原因") reason else desc
)
}
}
}
"删除" -> {
DialogHelper.showDeleteGameCommentDialog(
mContext,
R.string.delete_game_comment.toResString()
) {
SimpleRequestHelper.deleteGameComment(
mViewModel.game?.id ?: "",
commentData.id
) {
// 删除列表中的评论(如果当前列表有的话)
val index = comments.indexOfFirst { item ->
item.id == commentData.id
}
if (index != -1) {
comments.removeAt(index)
notifyItemRemoved(index)
}
}
}
}
}
}
}
time.setOnClickListener {
if (commentData.isEditContent == null && commentData.ignore) {
MtaHelper.onEvent("游戏详情_新", "玩家评论-评论说明", mViewModel.game?.name)
DialogUtils.showStopServerExplanationDialog(
mContext,
if (mViewModel.game?.commentDescription?.isNotEmpty() == true)
mViewModel.game?.commentDescription else mContext.getString(R.string.rating_protection),
mViewModel.game?.name
?: ""
)
} else if (commentData.isEditContent == true) {
MtaHelper.onEvent("游戏详情_新", "玩家评论-点击时间", mViewModel.game?.name)
val intent = CommentLogsActivity.getIntent(mContext, mViewModel.game!!.id, commentData.id)
mContext.startActivity(intent)
}
}
}
} else if (holder is MoreViewHolder) {
holder.itemView.setOnClickListener {
EventBus.getDefault().post(EBReuse(GameDetailFragment.SKIP_RATING))
MtaHelper.onEvent("游戏详情_新", "玩家评论_查看全部评论", gameName)
NewLogUtils.logGameDetailCommentClick(
mViewModel.game?.name ?: "",
mViewModel.game?.id ?: "",
"查看全部评论"
)
}
}
}
private fun showMorePopWindow(v: View, isMyRating: Boolean, clickListener: (String) -> Unit) {
val contentList = if (isMyRating) arrayListOf("复制", "修改", "删除")
else arrayListOf("复制", "投诉")
val inflater = LayoutInflater.from(v.context)
val layout = inflater.inflate(com.gh.gamecenter.common.R.layout.layout_popup_container, null)
val popupWindow = PopupWindow(
layout,
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
popupWindow.apply {
setBackgroundDrawable(ColorDrawable(0))
isTouchable = true
isFocusable = true
isOutsideTouchable = true
}
val container = layout.findViewById<LinearLayout>(R.id.container)
for (text in contentList) {
val item = inflater.inflate(R.layout.layout_popup_option_item, container, false)
container.addView(item)
val hitText = item.findViewById<TextView>(R.id.hint_text)
hitText.text = text
item.setOnClickListener {
clickListener.invoke(text)
popupWindow.dismiss()
}
}
popupWindow.showAutoOrientation(v)
}
class MoreViewHolder(var view: View) : RecyclerView.ViewHolder(view)
class GameDetailRatingCommentViewHolder(var binding: ItemGameDetailRatingCommentBinding) :
RecyclerView.ViewHolder(binding.root)
companion object {
const val RATING_REPLY_REQUEST = 233
const val RATING_PATCH_REQUEST = 234
}
}

View File

@ -1,322 +0,0 @@
package com.gh.gamecenter.gamedetail.desc
import android.app.Activity
import android.content.Intent
import android.graphics.Rect
import android.view.View
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.therouter.TheRouter
import com.gh.common.util.DirectUtils
import com.gh.common.util.OnSyncCallBack
import com.gh.common.util.SyncDataBetweenPageHelper
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.LazyFragment
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.eventbus.EBReuse
import com.gh.gamecenter.common.utils.observeNonNull
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.viewModelProvider
import com.gh.gamecenter.common.utils.viewModelProviderFromParent
import com.gh.gamecenter.core.iinterface.IScrollable
import com.gh.gamecenter.core.provider.IFloatingWindowProvider
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.databinding.FragmentDescBinding
import com.gh.gamecenter.entity.RatingComment
import com.gh.gamecenter.eventbus.EBScroll
import com.gh.gamecenter.eventbus.EBTypeChange
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.WelcomeDialogEntity
import com.gh.gamecenter.gamedetail.GameDetailFragment
import com.gh.gamecenter.gamedetail.GameDetailFragment.Companion.SKIP_DESC
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.DetailEntity
import com.gh.gamecenter.gamedetail.entity.NewGameDetailEntity
import com.gh.gamecenter.gamedetail.rating.RatingFragment
import com.gh.gamecenter.video.detail.VideoDetailActivity
import com.halo.assistant.HaloApp
import io.reactivex.disposables.CompositeDisposable
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
// TODO 处理页面重建时会生成额外 Fragment 的问题
class DescFragment: LazyFragment(), IScrollable {
private var mAdapter: DescAdapter ? = null
private var mLayoutManager: LinearLayoutManager? = null
private var mGameEntity: GameEntity? = null
private var mNewDetailEntity: NewGameDetailEntity? = null
private lateinit var mViewModel: DescViewModel
private lateinit var mBinding: FragmentDescBinding
private var mCompositeDisposable = CompositeDisposable()
var openVideoStreaming = false // 是否自动打开视频流
private var mScrollToLibao = false
private var mScrollToServer = false
override fun getRealLayoutId() = R.layout.fragment_desc
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
val adapter = mAdapter ?: return
if (resultCode == Activity.RESULT_OK) {
if (DescCommentsAdapter.RATING_REPLY_REQUEST == requestCode || DescCommentsAdapter.RATING_PATCH_REQUEST == requestCode) {
var commentPosition = 0
SyncDataBetweenPageHelper.resultHandle(data, object : OnSyncCallBack<RatingComment> {
override fun onData(dataPosition: Int): RatingComment? {
val descItemList = adapter.descItemList
for (i in 0 until descItemList.size) {
val comments = descItemList[i].comment
if (comments != null) {
commentPosition = i
val ratingComment = comments[dataPosition]
if (DescCommentsAdapter.RATING_PATCH_REQUEST == requestCode) {
ratingComment.ignore = mGameEntity?.ignoreComment ?: false
}
return ratingComment
}
}
return null
}
override fun onNotify(dataPosition: Int) {
adapter.notifyItemChanged(commentPosition)
}
})
} else if (requestCode == 100) {
val position = adapter.descItemList.indexOfFirst { it.type == DetailEntity.Type.LIBAO.value }
adapter.notifyItemChanged(position)
}
} else if (requestCode == DescCommentsAdapter.RATING_REPLY_REQUEST && resultCode == RatingFragment.RATING_DELETE_RESULT) {
data?.getParcelableExtra<RatingComment>(RatingComment::class.java.simpleName)?.run {
val descItemList = adapter.descItemList
var commentPosition = 0
for (i in 0 until descItemList.size) {
val comments = descItemList[i].comment
if (comments != null) {
commentPosition = i
val deleteCommentPosition = comments.indexOfFirst { it.id == id }
if (deleteCommentPosition != -1) comments.removeAt(deleteCommentPosition)
break
}
}
adapter.notifyItemChanged(commentPosition)
}
}
}
override fun onFragmentFirstVisible() {
mGameEntity = arguments?.getParcelable(EntranceConsts.KEY_GAME_ENTITY)
openVideoStreaming = arguments?.getBoolean(EntranceConsts.KEY_OPEN_VIDEO_STREAMING, false) ?: false
mScrollToLibao = arguments?.getBoolean(EntranceConsts.KEY_SCROLL_TO_LIBAO, false) ?: false
mScrollToServer = arguments?.getBoolean(EntranceConsts.KEY_SCROLL_TO_SERVER, false) ?: false
val gameDetailFactory =
GameDetailViewModel.Factory(HaloApp.getInstance().application, mGameEntity?.id, mGameEntity)
val gameDetailViewModel: GameDetailViewModel = viewModelProviderFromParent(gameDetailFactory, mGameEntity?.id ?: "")
mNewDetailEntity = gameDetailViewModel.gameDetailLiveData.value?.data
val factory = DescViewModel.Factory(HaloApp.getInstance().application, mGameEntity)
mViewModel = viewModelProvider(factory)
super.onFragmentFirstVisible()
gameDetailViewModel.gameDetailLiveData.observeNonNull(this) { gameDetail ->
if (gameDetail.data == null) return@observeNonNull
mAdapter?.updateDescItemList(mViewModel.decorateList(gameDetail.data!!.detailEntity))
// 非镜像游戏获取大家都在玩 (数据来源看具体方法内容) 数据
if (mGameEntity?.shouldUseMirrorInfo() == false) {
mViewModel.generateRecommendedGamesItem(gameDetail.data!!.detailEntity)
}
if (openVideoStreaming) {
gameDetail.data!!.detailEntity.forEach { entity ->
if (entity.video != null && activity !is VideoDetailActivity) {
DirectUtils.directToVideoDetail(
requireContext(), entity.video?.firstOrNull()?.videoId ?: "", entity.video?.firstOrNull()?.videoId, path = "游戏详情-介绍视频"
)
return@forEach
}
}
}
}
gameDetailViewModel.unifiedGameDetailWithUserRelatedInfoForChildLiveData.observeNonNull(this) {
mAdapter?.updateDescItemList(mViewModel.decorateList(it.detailEntity))
}
mViewModel.list.observe(this) {
mAdapter?.updateDescItemList(it)
if (mScrollToLibao && mViewModel.getLibaoIndexPosition() != -1) {
mScrollToLibao = false
mLayoutManager?.scrollToPositionWithOffset(mViewModel.getLibaoIndexPosition(), 0)
}
if (mScrollToServer && mViewModel.getServerIndexPosition() != -1) {
mScrollToServer = false
mLayoutManager?.scrollToPositionWithOffset(mViewModel.getServerIndexPosition(), 0)
}
}
mViewModel.changeColumnGameLiveData.observe(this) {
val viewHolder =
mBinding.recyclerview.findViewHolderForAdapterPosition(mViewModel.getColumnRecommendPosition()) as? DescAdapter.ColumnRecommendViewHolder
viewHolder?.binding?.run {
headPb.visibility = View.GONE
moreTv.isEnabled = true
}
if (it) {
mAdapter?.notifyItemChanged(mViewModel.getColumnRecommendPosition())
}
}
}
override fun onRealLayoutInflated(inflatedView: View) {
super.onRealLayoutInflated(inflatedView)
mBinding = FragmentDescBinding.bind(inflatedView)
showFloatingWindowIfNeeded()
mBinding.reuseLoading.root.visibility = View.GONE
mAdapter = DescAdapter(requireContext(), mEntrance, mViewModel, mNewDetailEntity)
(mBinding.recyclerview.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
mLayoutManager = LinearLayoutManager(context)
mBinding.recyclerview.layoutManager = mLayoutManager
mBinding.recyclerview.adapter = mAdapter
mBinding.recyclerview.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
val firstCompletelyVisibleItemPosition = mLayoutManager!!.findFirstCompletelyVisibleItemPosition()
val lastCompletelyVisibleItemPosition = mLayoutManager!!.findLastCompletelyVisibleItemPosition()
for (i in firstCompletelyVisibleItemPosition..lastCompletelyVisibleItemPosition) {
if (i < 0) continue
if (mAdapter?.getItemViewType(i) == DescAdapter.CUSTOM_COLUMN
&& mAdapter!!.descItemList[i].customColumn?.showExpandTagsHint == true
) {
SPUtils.setBoolean(Constants.SP_HAS_SHOWN_EXPANDED_GAME_DETAIL_TAGS_HINT, true)
}
}
}
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
EventBus.getDefault().post(EBTypeChange(GameDetailFragment.EB_SCROLLING, 0))
}
exposureScroll(newState)
}
})
exposureScroll(RecyclerView.SCROLL_STATE_IDLE)
}
private fun exposureScroll(newState: Int) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
val layoutManager = mBinding.recyclerview.layoutManager as? LinearLayoutManager
val firstVisibleItem = layoutManager?.findFirstCompletelyVisibleItemPosition() ?: -1
val lastVisibleItem = layoutManager?.findLastCompletelyVisibleItemPosition() ?: -1
val childCount = mBinding.recyclerview.adapter?.itemCount ?: 0
if (firstVisibleItem == -1 || lastVisibleItem == -1) return
for (i in 0 until childCount) {
val viewHolder = mBinding.recyclerview.findViewHolderForAdapterPosition(i)
if (viewHolder != null && viewHolder is ExposureViewHolder) {
if (i in firstVisibleItem..lastVisibleItem) {
val rect = Rect()
viewHolder.itemView.getLocalVisibleRect(rect)
if (rect.top == 0 && rect.bottom == viewHolder.itemView.height) {
viewHolder.startDelayLogRunnable()
}
} else {
viewHolder.removeLogRunnable()
}
}
}
}
}
override fun onFragmentPause() {
super.onFragmentPause()
val childCount = mBinding.recyclerview.adapter?.itemCount ?: 0
for (i in 0 until childCount) {
val viewHolder = mBinding.recyclerview.findViewHolderForAdapterPosition(i)
if (viewHolder is ExposureViewHolder) {
viewHolder.removeLogRunnable()
}
}
}
override fun onDestroy() {
super.onDestroy()
mCompositeDisposable.dispose()
mAdapter?.stopHandlerThread()
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(reuse: EBReuse) {
if (SKIP_DESC == reuse.type) {
mAdapter?.notifyDataSetChanged()
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(bean: EBScroll) {
if (mGameEntity?.id == bean.id) {
val position = mViewModel.getGameInfoPosition()
(mBinding.recyclerview.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(position, 0)
}
}
override fun scrollToTop() {
if (::mBinding.isInitialized) {
mBinding.recyclerview.scrollToPosition(0)
}
}
fun scrollToRelatedVersion() {
if (mViewModel.getRelatedVersionPosition() != -1) {
mLayoutManager?.scrollToPositionWithOffset(mViewModel.getRelatedVersionPosition(), 0)
}
}
fun scrollToLibao() {
if (mViewModel.getDetailLibaoPosition() != -1) {
mLayoutManager?.scrollToPositionWithOffset(mViewModel.getDetailLibaoPosition(), 0)
}
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
if (!::mBinding.isInitialized) return
mBinding.recyclerview.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_background.toColor(requireContext()))
mBinding.recyclerview.recycledViewPool.clear()
mAdapter?.let { it.notifyItemRangeChanged(0, it.itemCount) }
}
private fun showFloatingWindowIfNeeded() {
val floatingWindowProvider = TheRouter.get(IFloatingWindowProvider::class.java)
floatingWindowProvider?.getAndShowFloatingWindow(
mGameEntity?.id ?: "",
mGameEntity?.name ?: "",
"游戏详情",
this,
mBinding.recyclerview
)?.let {
mCompositeDisposable.add(it)
}
}
}

View File

@ -1,460 +0,0 @@
package com.gh.gamecenter.gamedetail.desc
import android.app.Application
import android.os.Build
import android.text.TextUtils
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.facebook.common.util.UriUtil
import com.gh.common.util.PackageUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.exposure.meta.MetaUtil
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.entity.SubjectEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.GameInfo
import com.gh.gamecenter.gamedetail.entity.CustomColumn
import com.gh.gamecenter.gamedetail.entity.DetailEntity
import com.gh.gamecenter.retrofit.RetrofitManager
import com.google.gson.JsonArray
import com.google.gson.reflect.TypeToken
import com.halo.assistant.HaloApp
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import okhttp3.ResponseBody
import org.json.JSONObject
import retrofit2.HttpException
class DescViewModel(
application: Application,
var game: GameEntity?
) : AndroidViewModel(application) {
private var mDataList = arrayListOf<DetailEntity>()
private var mRelatedGameList = arrayListOf<GameEntity>()
private var mGameInfoPosition = 0
private var mLibaoPosition = -1
private var mServerPosition = -1
private var mDetailLibaoPosition = -1
private var mRelatedVersionPosition = -1
private var mColumnRecommendPosition = -1
private var mGameInfo: GameInfo? = null
private val mIdMaps = hashMapOf<String, String>()
private var mSubjectPage = 1
var list = MutableLiveData<ArrayList<DetailEntity>>()
var changeColumnGameLiveData = MutableLiveData<Boolean>()
var gameId = game?.id
/**
* 构建大家都在玩的item
*/
fun generateRecommendedGamesItem(mDataList: ArrayList<DetailEntity>) {
this.mDataList = mDataList
mIdMaps.clear()
val detailEntity = mDataList.find { it.type == DetailEntity.Type.RECOMMENDED_GAMES.value } ?: return
val relatedGames = detailEntity.relatedGames
val installGames = detailEntity.installGames
val downloadGames = detailEntity.downloadGames
if (relatedGames.isNullOrEmpty()
&& installGames.isNullOrEmpty()
&& downloadGames.isNullOrEmpty()
) {
assembleListWithRecommendedGames()
return
}
val labelGames = arrayListOf<String>()
for (relatedGame in relatedGames ?: listOf()) {
relatedGame.game?.let {
labelGames.addAll(it)
}
}
if (labelGames.isNotEmpty()) {
val gameIds = labelGames.take(4)
gameIds.forEach {
mIdMaps[it] = "标签推荐"
}
}
val labelGameCount = mIdMaps.size
if (installGames != null) {
val gameIds = installGames.take(4)
gameIds.forEach {
mIdMaps[it] = "安装推荐"
}
}
if (downloadGames != null) {
val gameIds = downloadGames.take(4)
gameIds.forEach {
mIdMaps[it] = "下载推荐"
}
}
if (mIdMaps.size < 6 && labelGames.size > labelGameCount) {
for (i in labelGameCount until labelGames.size) {
mIdMaps[labelGames[i]] = "标签推荐"
if (mIdMaps.size >= 6) break
}
}
if (mIdMaps.size < 6) {
val entity = mDataList.find { it.type == DetailEntity.Type.RECOMMENDED_GAMES.value }
mDataList.remove(entity)
assembleListWithRecommendedGames()
return
}
getGamesDigestByIds {
mRelatedGameList.clear()
mRelatedGameList.addAll(it)
assembleListWithRecommendedGames()
}
}
private fun getGamesDigestByIds(callback: (List<GameEntity>) -> Unit) {
val ids = mIdMaps.map { it.key }.toList().joinToString("-")
val filterQuery = UrlFilterUtils.getFilterQuery("game_ids", ids)
RetrofitManager.getInstance().api.getGamesDigestByIds(filterQuery)
.compose(observableToMain())
.subscribe(object : Response<List<GameEntity>>() {
override fun onResponse(response: List<GameEntity>?) {
super.onResponse(response)
response?.forEach { it.recommendType = mIdMaps[it.id] ?: "" }
response?.let(callback) ?: callback.invoke(listOf())
}
override fun onFailure(e: HttpException?) {
super.onFailure(e)
callback.invoke(listOf())
}
})
}
private fun assembleListWithRecommendedGames() {
for ((index, data) in mDataList.withIndex()) {
if (data.info != null) {
mGameInfoPosition = index
mGameInfo = data.info!!
}
if (data.server != null) {
mServerPosition = index
}
if (data.libao != null) {
mLibaoPosition = index
}
if (data.columnGames != null) {
mColumnRecommendPosition = index
}
if (data.relatedGames != null) {
mRelatedGameList.shuffle()
val recommendedGames = SubjectEntity().apply {
this.data = mRelatedGameList
}
data.recommendedGames = recommendedGames
}
}
list.postValue(mDataList)
}
fun changeSubjectGame(subjectId: String, gameCount: Int) {
RetrofitManager.getInstance().api
.getSubjectGame(subjectId, mSubjectPage)
.compose(observableToMain())
.subscribe(object : Response<retrofit2.Response<JsonArray>>() {
override fun onResponse(response: retrofit2.Response<JsonArray>?) {
super.onResponse(response)
if (response == null) return
val total = response.headers().get("total")?.toInt() ?: 0
val isHasNextPage = (total - mSubjectPage * 20) > 0
val type = object : TypeToken<ArrayList<GameEntity>>() {}.type
val games = GsonUtils.gson.fromJson<ArrayList<GameEntity>>(response.body()?.toJson() ?: "", type)
val detailEntity =
mDataList.find { data -> data.type == DetailEntity.Type.COLUMN_RECOMMEND.value } ?: return
val randomArray = RandomUtils.getRandomArray(gameCount, games.size)
detailEntity.columnGames?.run {
clear()
for (i in randomArray) {
add(games[i])
}
}
changeColumnGameLiveData.postValue(true)
mSubjectPage = if (isHasNextPage) mSubjectPage + 1 else 1
}
override fun onFailure(e: HttpException?) {
super.onFailure(e)
changeColumnGameLiveData.postValue(false)
}
})
}
fun sendSuggestion() {
val params = hashMapOf<String, String>()
params["from"] = ""
params["ghversion"] = PackageUtils.getGhVersionName()
params["channel"] = HaloApp.getInstance().channel
params["type"] = Build.MODEL
params["sdk"] = Build.VERSION.SDK_INT.toString()
params["version"] = Build.VERSION.RELEASE
params["source"] = HaloApp.getInstance().application.getString(R.string.app_name)
params["jnfj"] = MetaUtil.getBase64EncodedIMEI()
params["manufacturer"] = Build.MANUFACTURER
params["rom"] = MetaUtil.getRom().name + " " + MetaUtil.getRom().versionName
params["suggestion_type"] = "游戏求更新"
params["game_id"] = game?.id ?: ""
params["message"] =
"求更新:${game?.name}(${game?.getApk()?.firstOrNull()?.packageName}, ${game?.getApk()?.firstOrNull()?.version})"
val requestBody = params.createRequestBody()
RetrofitManager.getInstance().api.postSuggestion(requestBody)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Response<ResponseBody?>() {
override fun onResponse(response: ResponseBody?) {
super.onResponse(response)
ToastUtils.showToast("感谢您的反馈信息,我们将尽快处理~")
}
override fun onFailure(e: HttpException?) {
super.onFailure(e)
e?.response()?.errorBody()?.let {
val content = JSONObject(it.string())
if (content.getInt("code") == 403208) {
ToastUtils.showToast("您已经提交过反馈信息,我们将尽快处理~")
}
}
}
})
}
// TODO 装饰操作放到子线程去做
// 装饰列表数据
fun decorateList(detailEntityList: ArrayList<DetailEntity>): ArrayList<DetailEntity> {
val specialPadding = DisplayUtils.dip2px(12F)
val defaultPadding = DisplayUtils.dip2px(15F)
var containsFirstTimeExpandCustomColumnTags = false
var hasShownCustomColumnTagsExpandHint =
SPUtils.getBoolean(Constants.SP_HAS_SHOWN_EXPANDED_GAME_DETAIL_TAGS_HINT)
// A 确定每个 item 的上下内边距
for ((index, rawItem) in detailEntityList.withIndex()) {
rawItem.paddingTop = defaultPadding
rawItem.paddingBottom = defaultPadding
if (index == 0) {
rawItem.paddingTop = specialPadding
}
}
// B 将游戏介绍转为简单的自定义栏目
for (rawItem in detailEntityList) {
if (rawItem.type == DetailEntity.Type.DES.value) {
rawItem.type = DetailEntity.Type.CUSTOM_COLUMN.value
rawItem.customColumn = CustomColumn(
name = "游戏简介",
order = 1,
nameIcon = UriUtil.getUriForResourceId(R.drawable.ic_game_desc).toString(),
des = rawItem.des,
isHtmlDes = false,
showDesRowNum = 3
)
break
}
}
// C 标记"公告"、"自定义栏目"、"详细信息" 的UI样式是否合并在一起处理置顶文章的内容
for ((index, rawItem) in detailEntityList.withIndex()) {
if (rawItem.type == DetailEntity.Type.NOTICE.value
|| rawItem.type == DetailEntity.Type.CUSTOM_COLUMN.value
|| rawItem.type == DetailEntity.Type.GAME_INFO.value
) {
if (index + 1 != detailEntityList.size) {
val nextRawItem = detailEntityList[index + 1]
if (nextRawItem.type == DetailEntity.Type.CUSTOM_COLUMN.value
|| nextRawItem.type == DetailEntity.Type.NOTICE.value
|| nextRawItem.type == DetailEntity.Type.GAME_INFO.value
) {
// 默认情况下 UI 与前后相连
rawItem.shouldBoundWithNextItem = true
nextRawItem.shouldBoundWithPreviousItem = true
// 特殊情况下,如果自定义栏目类型为按钮,且后续的项不是按钮就独立显示,后续的项也为按钮时合并显示 (仅按钮类型的自定义栏目合并)
if (rawItem.type == DetailEntity.Type.CUSTOM_COLUMN.value
&& rawItem.customColumn?.link?.style == "button"
) {
val nextItemIsButtonStyleCustomColumn =
(nextRawItem.type == DetailEntity.Type.CUSTOM_COLUMN.value && nextRawItem.customColumn?.link?.style == "button")
rawItem.paddingBottom = 0
rawItem.shouldBoundWithNextItem = nextItemIsButtonStyleCustomColumn
nextRawItem.shouldBoundWithPreviousItem = nextItemIsButtonStyleCustomColumn
rawItem.paddingTop = specialPadding
nextRawItem.paddingTop = specialPadding
}
// 若当前项不是自定义栏目按钮类型的项,并且下个项是自定义栏目的项,不合并相关 UI
if (nextRawItem.type == DetailEntity.Type.CUSTOM_COLUMN.value
&& nextRawItem.customColumn?.link?.style == "button"
&& (rawItem.type != DetailEntity.Type.CUSTOM_COLUMN.value
|| rawItem.customColumn?.link?.style != "button")
) {
nextRawItem.shouldBoundWithPreviousItem = false
nextRawItem.paddingBottom = 0
}
}
}
}
// 处理置顶文章
if (rawItem.type == "article") {
if (rawItem.articleTop != null && rawItem.articleTop?.size != 0) {
for ((i, article) in rawItem.articleTop!!.withIndex()) {
article.priority = Int.MAX_VALUE
rawItem.article?.add(i, article)
}
rawItem.articleTop?.clear()
}
}
}
// D 处理自定义栏目相关的东西
for (rawItem in detailEntityList) {
if (rawItem.type == DetailEntity.Type.CUSTOM_COLUMN.value) {
// 判断自定义栏目的标签是否需要进入就自动展开
if (rawItem.customColumn?.showInfoTagDesType == "first_expand"
&& !SPUtils.getBoolean(Constants.SP_HAS_EXPANDED_GAME_DETAIL_TAGS)
) {
containsFirstTimeExpandCustomColumnTags = true
rawItem.customColumn?.showInfoTagDesType = "expand"
}
// 正文内容为空,但是后台配置了标签展开的默认展开
if (rawItem.customColumn?.showInfoTag == true
&& TextUtils.isEmpty(rawItem.customColumn?.des)
) {
rawItem.customColumn?.showInfoTagDesType = "expand"
}
// 判断是否显示标签展开提示
if (rawItem.customColumn?.showInfoTagDes == true
&& rawItem.customColumn?.infoTag?.size != 0
&& rawItem.customColumn?.showInfoTagDesType != "expand"
&& !hasShownCustomColumnTagsExpandHint
) {
rawItem.customColumn?.showExpandTagsHint = true
// 内存置为 true 避免出现多个提示
hasShownCustomColumnTagsExpandHint = true
}
// 不存在缩起正文的自定义栏目默认全显示
if (TextUtils.isEmpty(rawItem.customColumn?.desBrief) && rawItem.customColumn?.name != "游戏简介") {
rawItem.customColumn?.showDesType = "all"
}
// 将自定义栏目正文内容里低版本 android 系统不支持的 HTML Tag 转为手动处理
if (!TextUtils.isEmpty(rawItem.customColumn?.desBrief)) {
rawItem.customColumn?.desFull = rawItem.customColumn?.desFull?.replaceUnsupportedHtmlTag()
rawItem.customColumn?.desBrief = rawItem.customColumn?.desBrief?.replaceUnsupportedHtmlTag()
}
rawItem.customColumn?.des = rawItem.customColumn?.desFull
?: rawItem.customColumn?.des
}
}
for ((index, entity) in detailEntityList.withIndex()) {
if (entity.libao != null) {
mDetailLibaoPosition = index
}
if (entity.relatedVersion != null) {
mRelatedVersionPosition = index
}
}
if (containsFirstTimeExpandCustomColumnTags) {
SPUtils.setBoolean(Constants.SP_HAS_EXPANDED_GAME_DETAIL_TAGS, true)
}
loadCustomColumnImageInAdvance(detailEntityList)
return detailEntityList
}
fun getServerIndexPosition() = mServerPosition
fun getLibaoIndexPosition() = mLibaoPosition
fun getGameInfoPosition() = mGameInfoPosition
fun getDetailLibaoPosition() = mDetailLibaoPosition
fun getRelatedVersionPosition() = mRelatedVersionPosition
fun getColumnRecommendPosition() = mColumnRecommendPosition
fun getGameInfo() = mGameInfo
/**
* 预加载自定义栏目的标签小图标
*/
private fun loadCustomColumnImageInAdvance(detailEntityList: ArrayList<DetailEntity>) {
for (item in detailEntityList) {
if (item.type == DetailEntity.Type.CUSTOM_COLUMN.value) {
item.customColumn?.infoTag?.let {
for (tag in it) {
tryWithDefaultCatch {
ImageUtils.picasso.load(tag.icon).fetch()
}
}
}
}
}
}
fun postRequestSpeed() {
RetrofitManager.getInstance().newApi.postRequestSpeed(com.gh.gamecenter.login.user.UserManager.getInstance().userId, gameId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Response<ResponseBody>() {
override fun onResponse(response: ResponseBody?) {
super.onResponse(response)
ToastUtils.showToast("感谢您的反馈信息,我们将尽快处理~")
}
override fun onFailure(e: HttpException?) {
super.onFailure(e)
e?.response()?.errorBody()?.let {
val content = JSONObject(it.string())
if (content.getInt("code") == 403208) {
ToastUtils.showToast("您已经提交过反馈信息,我们将尽快处理~")
}
}
}
})
}
class Factory(
private val mApplication: Application,
private val game: GameEntity?
) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return DescViewModel(mApplication, game) as T
}
}
}

View File

@ -1,30 +0,0 @@
package com.gh.gamecenter.gamedetail.desc
import android.os.Handler
import android.view.View
import androidx.recyclerview.widget.RecyclerView
open class ExposureViewHolder(view: View, val handler: Handler) : RecyclerView.ViewHolder(view) {
private var mLogRunnable: Runnable? = null
fun startDelayLogRunnable() {
if (mLogRunnable == null) {
mLogRunnable = Runnable {
exposureLog()
}
handler.postDelayed(mLogRunnable!!, 3000)
}
}
open fun exposureLog() {
}
fun removeLogRunnable() {
if (mLogRunnable != null) {
handler.removeCallbacks(mLogRunnable!!)
mLogRunnable = null
}
}
}

View File

@ -1,84 +0,0 @@
package com.gh.gamecenter.gamedetail.desc
import android.content.Context
import android.graphics.drawable.BitmapDrawable
import android.net.Uri
import android.text.Spanned
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.common.view.CustomLinkMovementMethod
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.tryWithDefaultCatch
import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.databinding.GamedetailItemCustomColumnItemBinding
import com.gh.gamecenter.entity.TagEntity
import com.lightgame.adapter.BaseRecyclerAdapter
import com.squareup.picasso.Picasso
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
class GameDetailCustomColumnAdapter(context: Context) : BaseRecyclerAdapter<RecyclerView.ViewHolder>(context) {
private var mTags: ArrayList<TagEntity> = arrayListOf()
private var mShowTagDes: Boolean = false
fun updateData(tags: ArrayList<TagEntity>, showTagDes: Boolean) {
this.mTags = tags
this.mShowTagDes = showTagDes
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return GameDetailCustomViewHolderViewHolder(parent.toBinding())
}
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
if (viewHolder is GameDetailCustomViewHolderViewHolder) {
val (_, name, icon, des, color) = mTags[position]
val marginBetweenIconAndName = " "
val content = if (mShowTagDes) "$marginBetweenIconAndName$name $des" else "$marginBetweenIconAndName$name"
val spannable = SpanBuilder(content)
.color(
marginBetweenIconAndName.length,
name?.length?.plus(marginBetweenIconAndName.length) ?: 1,
color ?: "#000000"
)
.build()
tryWithDefaultCatch {
Single.just(icon)
.map {
ImageUtils.picasso.load(Uri.parse(it)).priority(Picasso.Priority.HIGH).get()
}.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
val bitmapDrawable = BitmapDrawable(mContext.resources, it)
bitmapDrawable.setBounds(0, 0, 16F.dip2px(), 16F.dip2px())
spannable.setSpan(CenterImageSpan(bitmapDrawable), 0, 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
viewHolder.binding.contentTv.run {
movementMethod = CustomLinkMovementMethod.getInstance()
text = spannable
}
}, {
it.printStackTrace()
})
}
}
}
override fun getItemViewType(position: Int): Int {
return position
}
override fun getItemCount(): Int {
return mTags.size
}
class GameDetailCustomViewHolderViewHolder(val binding: GamedetailItemCustomColumnItemBinding) :
BaseRecyclerViewHolder<Any>(binding.root)
}

View File

@ -1,217 +0,0 @@
package com.gh.gamecenter.gamedetail.desc
import android.content.Context
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.DefaultUrlHandler
import com.gh.common.util.DirectUtils
import com.gh.common.util.NewFlatLogUtils
import com.gh.gamecenter.WebActivity
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.databinding.ItemGameInfoBinding
import com.gh.gamecenter.feature.entity.GameInfo
import com.gh.gamecenter.gamedetail.dialog.GamePermissionDialogFragment
import com.gh.gamecenter.gamedetail.entity.GameInfoItemData
class GameDetailInfoItemAdapter(
val context: Context,
val gameInfo: GameInfo,
private val mViewModel: DescViewModel,
val gameName: String
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var datas = ArrayList<GameInfoItemData>()
init {
if (gameInfo.manufacturer.isNotEmpty()) {
when (gameInfo.manufacturerType) {
"manufacturer" -> {
datas.add(GameInfoItemData(title = "厂商", info = gameInfo.manufacturer))
}
"publisher" -> {
if (gameInfo.developer.isNotEmpty()) {
datas.add(GameInfoItemData(title = "开发商", info = gameInfo.developer))
}
datas.add(GameInfoItemData(title = "发行商", info = gameInfo.manufacturer))
}
"developer" -> {
datas.add(GameInfoItemData(title = "开发商", info = gameInfo.manufacturer))
if (gameInfo.publisher.isNotEmpty()) {
datas.add(GameInfoItemData(title = "发行商", info = gameInfo.publisher))
}
}
else -> {
//do nothing
}
}
}
if (gameInfo.supplier.isNotEmpty()) {
datas.add(GameInfoItemData(title = "供应商", info = gameInfo.supplier))
}
if (gameInfo.creditCode.isNotEmpty()) {
datas.add(GameInfoItemData(title = "统一社会信用代码", info = gameInfo.creditCode))
}
if (gameInfo.contact != null) {
datas.add(
GameInfoItemData(
title = gameInfo.contact!!.hint,
info = gameInfo.contact!!.qq,
actionStr = if (gameInfo.contact!!.type == "qq") "咨询" else if (gameInfo.contact?.key.isNullOrEmpty()) "复制" else "加入",
key = gameInfo.contact!!.key
)
)
}
if (gameInfo.version.isNotEmpty()) {
datas.add(
GameInfoItemData(
title = "当前版本",
info = gameInfo.version,
actionStr = "",
action2Str = if (gameInfo.requestUpdateStatus == "on" && mViewModel.game?.getApk()
?.isNotEmpty() == true
) "求更新" else ""
)
)
}
if (gameInfo.requestSpeedStatus == "on") {
datas.add(
GameInfoItemData(
title = "游戏加速",
info = "",
actionStr = "求加速",
action2Str = ""
)
)
}
if (gameInfo.size.isNotEmpty()) {
datas.add(GameInfoItemData(title = "游戏大小", info = gameInfo.size))
}
if (gameInfo.updateTime != 0L) {
datas.add(GameInfoItemData(title = "更新时间", info = TimeUtils.getFormatTime(gameInfo.updateTime)))
}
if (gameInfo.recommendAge.isNotEmpty()) {
datas.add(GameInfoItemData(title = "适龄等级", info = gameInfo.recommendAge))
}
if (!gameInfo.internetApp.isNullOrEmpty()) {
datas.add(GameInfoItemData(title = "联网APP", info = if (gameInfo.internetApp == "yes") "" else ""))
}
if (gameInfo.icp != null) {
datas.add(GameInfoItemData(title = "ICP备案号", info = gameInfo.icp?.number ?: ""))
}
if (!gameInfo.permissions.isNullOrEmpty()) {
datas.add(GameInfoItemData(title = "权限及用途", info = "查看"))
}
if (!gameInfo.privacyPolicyUrl.isNullOrEmpty()) {
datas.add(GameInfoItemData(title = "隐私政策", info = "查看"))
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return GameDetailInfoItemViewHolder(parent.toBinding())
}
override fun getItemCount(): Int = datas.size
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val gameInfoItemData = datas[position]
if (holder is GameDetailInfoItemViewHolder) {
holder.binding.divider.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_divider.toColor(holder.binding.divider.context))
holder.binding.infoTv.isSelected = true
holder.binding.infoTv.text = gameInfoItemData.info
holder.binding.titleTv.text = gameInfoItemData.title
holder.binding.infoTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(holder.binding.infoTv.context))
holder.binding.titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(holder.binding.infoTv.context))
holder.binding.actionTv.goneIf(gameInfoItemData.actionStr.isEmpty()) {
holder.binding.actionTv.text = gameInfoItemData.actionStr
holder.binding.actionTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(holder.binding.infoTv.context))
}
holder.binding.action2Tv.goneIf(gameInfoItemData.action2Str.isEmpty()) {
holder.binding.action2Tv.text = gameInfoItemData.action2Str
holder.binding.action2Tv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(holder.binding.infoTv.context))
}
holder.binding.infoTv.layoutParams = (holder.binding.infoTv.layoutParams as MarginLayoutParams).apply {
rightMargin = if (!holder.binding.actionTv.isVisible && !holder.binding.action2Tv.isVisible) 0 else 8F.dip2px()
}
if (gameInfoItemData.title == "权限及用途"
|| gameInfoItemData.title == "隐私政策"
|| gameInfoItemData.title == "ICP备案号") {
holder.binding.infoTv.setTextColor(ContextCompat.getColor(context, com.gh.gamecenter.common.R.color.text_theme))
}
holder.binding.root.setOnClickListener {
when (gameInfoItemData.title) {
"ICP备案号" -> {
DirectUtils.directToExternalBrowser(context, context.getString(com.gh.gamecenter.common.R.string.icp_url))
}
"权限及用途" -> {
GamePermissionDialogFragment.show(context as AppCompatActivity, mViewModel.game, gameInfo)
}
"隐私政策" -> {
gameInfo.privacyPolicyUrl?.let {
if (!DefaultUrlHandler.transformNormalScheme(holder.binding.root.context, it, "隐私政策", "")) {
val intent =
WebActivity.getIntent(holder.binding.root.context, it, "隐私政策", false, false)
holder.binding.root.context.startActivity(intent)
}
}
}
}
}
listOf(holder.binding.actionTv, holder.binding.action2Tv).forEach {
it.setOnClickListener { _ ->
when (if (it == holder.binding.actionTv) gameInfoItemData.actionStr else gameInfoItemData.action2Str) {
"求加速" -> {
it.context.ifLogin("游戏详情-求加速") {
NewFlatLogUtils.logGameDetailClickForAccelerate(mViewModel.gameId ?: "", gameName)
DialogHelper.showDialog(
context, "版本求加速", "如果游戏需要加速版本,您可以提交申请,让小助手尽快研究给您喔!",
"提交申请", "取消", {
mViewModel.postRequestSpeed()
}, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)
)
}
}
"求更新" -> {
MtaHelper.onEvent("游戏详情_新", "详细信息_我要求更新", gameName)
DialogHelper.showDialog(
context, "版本求更新", "如果游戏上线了新版本,您可以提交申请,让小助手尽快更新版本喔!",
"提交申请", "取消", {
mViewModel.sendSuggestion()
}, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)
)
}
"咨询" -> {
MtaHelper.onEvent("游戏详情_新", "详细信息_咨询", gameName)
if (ShareUtils.isQQClientAvailable(context)) {
DirectUtils.directToQqConversation(context, gameInfoItemData.info)
} else {
gameInfoItemData.info.copyTextAndToast("已复制")
}
}
"加入" -> {
MtaHelper.onEvent("游戏详情_新", "详细信息_加入", gameName)
if (ShareUtils.isQQClientAvailable(context)) {
DirectUtils.directToQqGroup(context, gameInfoItemData.key)
} else {
gameInfoItemData.info.copyTextAndToast("已复制")
}
}
"复制" -> {
gameInfoItemData.info.copyTextAndToast()
}
}
}
}
}
}
class GameDetailInfoItemViewHolder(var binding: ItemGameInfoBinding) : RecyclerView.ViewHolder(binding.root)
}

View File

@ -1,141 +0,0 @@
package com.gh.gamecenter.gamedetail.desc
import android.content.Context
import android.util.SparseArray
import android.view.View
import android.view.ViewGroup
import androidx.core.util.forEach
import androidx.recyclerview.widget.RecyclerView
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.common.util.DataCollectionUtils
import com.gh.common.util.DirectUtils
import com.gh.common.util.NewFlatLogUtils
import com.gh.gamecenter.ImageViewerActivity
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.WrapContentDraweeView
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.databinding.GalleryVideoItemBinding
import com.gh.gamecenter.databinding.GamedetailScreenshotItemBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.gamedetail.entity.Video
import com.gh.gamecenter.video.detail.VideoDetailContainerViewModel
class GameGalleryAdapter(
var context: Context,
private val mVideo: ArrayList<Video>? = null,
private val mGallery: ArrayList<String>? = null,
val mGame: GameEntity,
private val mEntrance: String
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val mImageViewArray = SparseArray<SimpleDraweeView>()
private val mDefaultHorizontalPadding by lazy { com.gh.gamecenter.common.R.dimen.game_detail_item_horizontal_padding.toPx() }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
VIDEO -> {
VideoViewHolder(parent.toBinding())
}
IMAGE -> {
GameGalleryViewHolder(parent.toBinding())
}
else -> throw NullPointerException()
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is GameGalleryViewHolder -> {
holder.binding.screenshotItemIv.setTag(ImageUtils.TAG_TARGET_WIDTH, 260F.dip2px())
ImageUtils.display(holder.binding.screenshotItemIv, mGallery?.get(position))
holder.binding.screenshotItemIv.registerLoadingCallback(object : WrapContentDraweeView.LoadingCallback {
override fun loaded() {
holder.binding.screenshotItemIv.post {
holder.binding.screenshotItemIv.layoutParams.apply {
height = holder.binding.screenshotItemIv.height
width =
(holder.binding.screenshotItemIv.height * holder.binding.screenshotItemIv.aspectRatio).toInt()
holder.binding.screenshotItemIv.layoutParams = this
}
holder.itemView.requestLayout()
}
}
})
holder.itemView.setDebouncedClickListener {
DataCollectionUtils.uploadClick(context, "游戏介绍", "游戏详情")
MtaHelper.onEvent("游戏详情_新", "点击游戏截图", mGame.name)
val imageViewList = ArrayList<View>()
mImageViewArray.forEach { _, value ->
imageViewList.add(value)
}
val intent = ImageViewerActivity.getIntent(
context,
mGallery ?: arrayListOf(),
holder.adapterPosition,
imageViewList,
mEntrance
)
context.startActivity(intent)
}
mImageViewArray.put(position, holder.binding.screenshotItemIv)
}
is VideoViewHolder -> {
val video = mVideo?.get(position)
ImageUtils.display(holder.binding.screenshotItemIv, mVideo?.get(position)!!.poster)
holder.binding.videoTitleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(holder.binding.videoTitleTv.context))
holder.binding.usernameTv.text = video?.user?.name
holder.binding.videoTitleTv.text = video?.title
holder.binding.voteCountTv.text = video?.vote.toString()
holder.binding.screenshotItemIv.setOnClickListener {
MtaHelper.onEvent("游戏详情_新", "点击视频", "${mGame.name}+${video?.title}")
NewFlatLogUtils.logClickGameDetailVideoCategory(
"video",
video?.videoId ?: "",
video?.user?.id ?: ""
)
DirectUtils.directToVideoDetail(
context, video?.videoId
?: "", VideoDetailContainerViewModel.Location.GAME_DETAIL.value,
false, mGame.id, mEntrance, "游戏详情"
)
}
}
}
val params = holder.itemView.layoutParams as RecyclerView.LayoutParams
params.leftMargin = if (position == 0) mDefaultHorizontalPadding else 8F.dip2px()
params.rightMargin = if (position == itemCount - 1) mDefaultHorizontalPadding else 0F.dip2px()
holder.itemView.layoutParams = params
}
override fun getItemCount(): Int {
return when {
mVideo != null -> mVideo.size
mGallery != null -> mGallery.size
else -> 0
}
}
override fun getItemViewType(position: Int): Int {
return when {
mVideo != null -> VIDEO
mGallery != null -> IMAGE
else -> 0
}
}
companion object {
const val IMAGE = 223
const val VIDEO = 224
}
class GameGalleryViewHolder(val binding: GamedetailScreenshotItemBinding) :
BaseRecyclerViewHolder<Any>(binding.root)
class VideoViewHolder(var binding: GalleryVideoItemBinding) : RecyclerView.ViewHolder(binding.root)
}

View File

@ -1,244 +0,0 @@
package com.gh.gamecenter.gamedetail.desc
import android.app.Activity
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.LibaoUtils
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.common.utils.copyTextAndToast
import com.gh.gamecenter.common.utils.fromHtml
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.SpanBuilder
import com.gh.gamecenter.databinding.ItemGameDetailMoreBinding
import com.gh.gamecenter.databinding.ItemGameLibaoBinding
import com.gh.gamecenter.feature.entity.LibaoEntity
import com.gh.gamecenter.libao.LibaoDetailActivity
import com.gh.gamecenter.login.user.UserManager
class GameLibaoAdapter(
val context: Context,
val libaos: ArrayList<LibaoEntity>,
val gameName: String,
val gameId: String,
val showExpandIcon: Boolean = true,
val standaloneStyle: Boolean = false,
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var mIsExpand = false
private val mShowItemCount: Int = if (showExpandIcon) 3 else Int.MAX_VALUE // 最多展示多少个礼包
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == LIBAO_ITEM) {
LibaoViewHolder(parent.toBinding())
} else {
MoreViewHolder(ItemGameDetailMoreBinding.inflate(LayoutInflater.from(context), parent, false))
}
}
override fun getItemViewType(position: Int): Int {
return if (libaos.size > mShowItemCount) {
if (!mIsExpand) {
if (position == mShowItemCount) MORE else LIBAO_ITEM
} else {
if (position == libaos.size) MORE else LIBAO_ITEM
}
} else {
LIBAO_ITEM
}
}
override fun getItemCount(): Int {
return if (libaos.size > mShowItemCount) {
if (mIsExpand) libaos.size + 1 else mShowItemCount + 1
} else libaos.size
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is LibaoViewHolder -> {
if (standaloneStyle) {
if (position == 0) {
holder.binding.root.setBackgroundResource(com.gh.gamecenter.common.R.drawable.background_shape_white_radius_5_top_only)
} else if (position == itemCount - 1) {
holder.binding.root.setBackgroundResource(com.gh.gamecenter.common.R.drawable.background_shape_white_radius_5_bottm_only)
} else {
holder.binding.root.setBackgroundResource(com.gh.gamecenter.common.R.color.ui_surface)
}
}
val libaoEntity = libaos[position]
holder.binding.libaoNameTv.text = libaoEntity.name
holder.binding.contentTv.text = libaoEntity.content?.fromHtml()
val isTypeCopy = libaoEntity.receiveMethod == "copy"
if (isTypeCopy) {
// 类型为复制,不需要登录也能直接领取
holder.binding.libaoSchedulePb.visibility = View.GONE
holder.binding.remainingTv.visibility = View.GONE
holder.binding.libaoCodeTv.visibility = View.VISIBLE
val text = "兑换码:${libaoEntity.code}"
holder.binding.libaoCodeTv.text = SpanBuilder(text).color(
holder.binding.root.context,
4,
text.length,
com.gh.gamecenter.common.R.color.text_theme
).build()
holder.binding.copyLibaoCodeIv.visibility = View.VISIBLE
holder.binding.copyLibaoCodeIv.setOnClickListener {
holder.binding.receiveTv.performClick()
}
} else if (libaoEntity.universal || libaoEntity.status == "check") {
//通用码礼包/或者还未添加礼包码时,不显示进度条,显示礼包码
holder.binding.libaoSchedulePb.visibility = View.GONE
holder.binding.remainingTv.visibility = View.GONE
holder.binding.libaoCodeTv.visibility = View.VISIBLE
holder.binding.copyLibaoCodeIv.visibility = View.GONE
if (!UserManager.getInstance().isLoggedIn) {
holder.binding.libaoCodeTv.text = "礼包码:-"
} else {
when (libaoEntity.status) {
"linged", "repeatLing", "repeatLinged", "taoed", "repeatTao", "repeatTaoed" -> {
val size = libaoEntity.me?.userDataLibaoList?.size ?: 0
val code = libaoEntity.me?.userDataLibaoList?.get(size - 1)?.code ?: ""
val text = "礼包码:$code"
holder.binding.libaoCodeTv.text = SpanBuilder(text).color(
holder.binding.root.context,
4,
text.length,
com.gh.gamecenter.common.R.color.text_theme
).build()
holder.binding.copyLibaoCodeIv.visibility = View.VISIBLE
holder.binding.copyLibaoCodeIv.setOnClickListener {
code.copyTextAndToast("$code 复制成功")
}
}
else -> {
holder.binding.libaoCodeTv.text = "礼包码:-"
}
}
}
} else {
if (!UserManager.getInstance().isLoggedIn) {
holder.binding.libaoSchedulePb.visibility = View.VISIBLE
holder.binding.remainingTv.visibility = View.VISIBLE
holder.binding.libaoCodeTv.visibility = View.GONE
holder.binding.copyLibaoCodeIv.visibility = View.GONE
initProgressUI(libaoEntity, holder)
} else {
when (libaoEntity.status) {
"linged", "repeatLing", "repeatLinged", "taoed", "repeatTao", "repeatTaoed" -> {
holder.binding.libaoSchedulePb.visibility = View.GONE
holder.binding.remainingTv.visibility = View.GONE
holder.binding.libaoCodeTv.visibility = View.VISIBLE
val size = libaoEntity.me?.userDataLibaoList?.size ?: 0
val code = libaoEntity.me?.userDataLibaoList?.get(size - 1)?.code ?: ""
val text = "礼包码:$code"
holder.binding.libaoCodeTv.text = SpanBuilder(text).color(
holder.binding.root.context,
4,
text.length,
com.gh.gamecenter.common.R.color.text_theme
).build()
holder.binding.copyLibaoCodeIv.visibility = View.VISIBLE
holder.binding.copyLibaoCodeIv.setOnClickListener {
code.copyTextAndToast("$code 复制成功")
}
}
else -> {
holder.binding.libaoSchedulePb.visibility = View.VISIBLE
holder.binding.remainingTv.visibility = View.VISIBLE
holder.binding.libaoCodeTv.visibility = View.GONE
holder.binding.copyLibaoCodeIv.visibility = View.GONE
initProgressUI(libaoEntity, holder)
}
}
}
}
// LibaoUtils.setLiBaoBtnStatusRound(holder.binding.receiveTv, libaoEntity,true, context)
LibaoUtils.initLibaoBtn(
context,
holder.binding.receiveTv,
libaoEntity,
false,
null,
true,
"游戏详情",
"游戏详情"
) {
notifyItemChanged(position)
}
if (!libaoEntity.packageName.isNullOrEmpty()) {
holder.binding.receiveTv.setOnClickListener {
val intent = LibaoDetailActivity.getIntent(context, libaoEntity, true, "游戏详情")
if (it.context is Activity) {
(it.context as Activity).startActivityForResult(intent, 100)
}
}
}
holder.itemView.setOnClickListener {
if (isTypeCopy) {
// do nothing
} else {
val intent = LibaoDetailActivity.getIntent(context, libaoEntity, "游戏详情")
if (it.context is Activity) {
(it.context as Activity).startActivityForResult(intent, 100)
}
NewLogUtils.logGameDetailGiftClick(gameName, gameId, "礼包详情")
}
}
}
is MoreViewHolder -> {
holder.binding.arrowIv.rotation = if (mIsExpand) 180f else 0f
holder.itemView.setOnClickListener {
if (!mIsExpand) MtaHelper.onEvent("游戏详情_新", "游戏礼包_展开", gameName)
mIsExpand = !mIsExpand
notifyDataSetChanged()
NewLogUtils.logGameDetailGiftClick(gameName, gameId, "展开")
}
}
}
}
private fun initProgressUI(libaoEntity: LibaoEntity, holder: LibaoViewHolder) {
val total = libaoEntity.total
val available = libaoEntity.available
if (total != 0) {
val availablePercent = (available) / total.toFloat() * 100
val count = when {
availablePercent >= 1F -> {
availablePercent.toInt()
}
availablePercent == 0F -> {
0
}
else -> {
1
}
}
holder.binding.remainingTv.text = "剩余${count}%"
holder.binding.libaoSchedulePb.progress = count
}
}
class LibaoViewHolder(var binding: ItemGameLibaoBinding) : RecyclerView.ViewHolder(binding.root)
class MoreViewHolder(var binding: ItemGameDetailMoreBinding) : RecyclerView.ViewHolder(binding.root)
companion object {
const val MORE = 0
const val LIBAO_ITEM = 1
}
}

View File

@ -1,117 +0,0 @@
package com.gh.gamecenter.gamedetail.desc
import android.content.Context
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.DefaultUrlHandler
import com.gh.common.util.DataCollectionUtils
import com.gh.common.util.DirectUtils
import com.gh.common.util.LogUtils
import com.gh.common.util.NewsUtils
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.databinding.ItemGameRaidersBinding
import com.gh.gamecenter.databinding.ItemGameRaidersFixedTopBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.NewsEntity
import com.gh.gamecenter.newsdetail.NewsDetailActivity
class GameRaidersAdapter(
val context: Context,
val articles: ArrayList<NewsEntity>,
val mEntrance: String,
val game: GameEntity?
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val mDefaultHorizontalPadding by lazy { com.gh.gamecenter.common.R.dimen.game_detail_item_horizontal_padding.toPx() }
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == TYPE_ARTICLE) {
RaidersViewHolder(parent.toBinding())
} else {
RaidersFixedTopViewHolder(
ItemGameRaidersFixedTopBinding.inflate(
LayoutInflater.from(context),
parent,
false
)
)
}
}
override fun getItemCount(): Int = articles.size
override fun getItemViewType(position: Int): Int {
return if (articles[position].priority == Int.MAX_VALUE) {
TYPE_ARTICLE_FIXED_TOP
} else {
TYPE_ARTICLE
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val params = holder.itemView.layoutParams as RecyclerView.LayoutParams
params.leftMargin = if (position == 0) mDefaultHorizontalPadding else 8F.dip2px()
params.rightMargin = if (position == itemCount - 1) mDefaultHorizontalPadding else 0F.dip2px()
holder.itemView.layoutParams = params
if (holder is RaidersViewHolder) {
val newsEntity = articles[position]
holder.binding.root.setRootBackgroundDrawable(com.gh.gamecenter.common.R.drawable.background_shape_white_radius_5)
holder.binding.titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
holder.binding.contentTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
holder.binding.timeTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
holder.binding.line.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_divider.toColor(context))
holder.binding.titleTv.text = newsEntity.title ?: ""
holder.binding.contentTv.text = newsEntity.intro ?: ""
holder.binding.timeTv.text =
if (TimeUtils.isToday(newsEntity.publishOn)) "今天" else TimeUtils.getFormatTime(newsEntity.publishOn)
holder.itemView.setOnClickListener {
skipNewsDetail(newsEntity, position)
}
} else if (holder is RaidersFixedTopViewHolder) {
val newsEntity = articles[position]
holder.binding.root.setRootBackgroundDrawable(com.gh.gamecenter.common.R.drawable.background_shape_white_radius_5)
holder.binding.titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
holder.binding.descTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
holder.binding.titleTv.text = newsEntity.title ?: ""
ImageUtils.display(holder.binding.backgroundIv, newsEntity.thumb)
holder.binding.descTv.text = newsEntity.type
holder.itemView.setOnClickListener {
LogUtils.logGameDetailFixedTopArticleClick(game?.id, game?.name, newsEntity.link)
val isUrlIntercepted = DefaultUrlHandler.interceptUrl(
context,
newsEntity.link ?: "",
"新手攻略"
)
if (!isUrlIntercepted) {
DirectUtils.directToWebView(context, newsEntity.link ?: "")
}
}
}
}
private fun skipNewsDetail(article: NewsEntity, position: Int) {
DataCollectionUtils.uploadClick(context, "新手攻略", "游戏详情", article.title)
MtaHelper.onEvent("游戏详情_新", "新手攻略卡片", "${game?.name}+${article.title}")
// 统计阅读量
NewsUtils.statNewsViews(article.id)
NewsDetailActivity.startNewsDetailActivity(
context, article,
mEntrance + "+(游戏详情[" + game?.name + "]:新手攻略-列表[" + (position + 1) + "])"
)
}
companion object {
const val TYPE_ARTICLE = 123
const val TYPE_ARTICLE_FIXED_TOP = 124
}
class RaidersViewHolder(var binding: ItemGameRaidersBinding) : RecyclerView.ViewHolder(binding.root)
class RaidersFixedTopViewHolder(var binding: ItemGameRaidersFixedTopBinding) : RecyclerView.ViewHolder(binding.root)
}

View File

@ -1,4 +1,4 @@
package com.gh.gamecenter.gamedetail.desc
package com.gh.gamecenter.gamedetail.detail
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
@ -6,7 +6,6 @@ import com.gh.common.exposure.ExposureManager
import com.gh.common.filter.RegionSettingHelper
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.utils.*
@ -14,6 +13,7 @@ import com.gh.gamecenter.databinding.GamedetailItemGameCollectionBinding
import com.gh.gamecenter.entity.GameDetailRecommendGameEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.gamedetail.detail.viewholder.BaseGameDetailItemViewHolder
class GameCollectionAdapter(
private val mRecommendGameList: ArrayList<GameDetailRecommendGameEntity>,
@ -22,7 +22,9 @@ class GameCollectionAdapter(
private val mGame: GameEntity?,
private val mEntrance: String,
private val mPath: String,
private val mBasicExposureSource: List<ExposureSource>
private val mBasicExposureSource: List<ExposureSource>,
private val trackData: BaseGameDetailItemViewHolder.GameDetailModuleTrackData,
private val getGameStatus: () -> String
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val mDefaultHorizontalPadding by lazy { com.gh.gamecenter.common.R.dimen.game_detail_item_horizontal_padding.toPx() }
@ -72,20 +74,16 @@ class GameCollectionAdapter(
}
gameCountTv.text = "+${entity.count.game - games.size}"
root.setOnClickListener {
SensorsBridge.trackGameDetailPageGameCollectRecommendClick(
gameId = mGameId,
gameName = mGameName,
pageName = GlobalActivityManager.getCurrentPageEntity().pageName,
pageId = GlobalActivityManager.getCurrentPageEntity().pageId,
pageBusinessId = GlobalActivityManager.getCurrentPageEntity().pageBusinessId,
lastPageId = GlobalActivityManager.getCurrentPageEntity().pageId,
lastPageName = GlobalActivityManager.getCurrentPageEntity().pageName,
lastPageBusinessId = GlobalActivityManager.getCurrentPageEntity().pageBusinessId,
downloadStatus = mGame?.downloadStatusChinese ?: "",
gameType = mGame?.categoryChinese ?: "",
text = "游戏单",
gameCollectId = entity.id,
gameCollectTitle = entity.title
SensorsBridge.trackGameDetailModuleClick(
trackData.gameId,
trackData.gameName,
trackData.gameType,
"组件内容",
trackData.moduleType,
"游戏单推荐",
trackData.sequence,
supSequence = position + 1,
gameStatus = getGameStatus()
)
DirectUtils.directToGameCollectionDetail(
it.context,

View File

@ -0,0 +1,186 @@
package com.gh.gamecenter.gamedetail.detail
import android.app.Activity
import android.content.Context
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.ImageViewerActivity
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.databinding.ItemGameCoverGalleryBinding
import com.gh.gamecenter.databinding.ItemGameCoverVideoBinding
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.CoverEntity
import com.gh.gamecenter.gamedetail.video.TopVideoView
import com.lightgame.adapter.BaseRecyclerAdapter
import com.shuyu.gsyvideoplayer.builder.GSYVideoOptionBuilder
import com.shuyu.gsyvideoplayer.listener.GSYSampleCallBack
import com.shuyu.gsyvideoplayer.utils.OrientationUtils
class GameDetailCoverAdapter(
context: Context,
private val fragment: GameDetailFragment,
private val snapHelper: LeftPagerSnapHelper,
private val viewModel: GameDetailViewModel
) : BaseRecyclerAdapter<RecyclerView.ViewHolder>(context) {
private var coverRecyclerView: RecyclerView? = null
private var coverList = arrayListOf<CoverEntity>()
private val imageViewWidth = ((DisplayUtils.getScreenWidth(fragment.activity) / 2F) - 16F.dip2px()).toInt()
private var firstGalleryItemPosition = -1
private var isSingleGallery = false
private var hasSetFixedHeight = false
fun submitList(data: List<CoverEntity>) {
coverList = ArrayList(data)
firstGalleryItemPosition = coverList.indexOfFirst { it.gallery != null }
isSingleGallery = coverList.count { it.gallery != null } == 1
notifyDataSetChanged()
}
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
coverRecyclerView = recyclerView
}
override fun getItemViewType(position: Int): Int {
val coverEntity = coverList.getOrNull(position)
return when {
coverEntity?.video != null -> ITEM_VIDEO
else -> ITEM_GALLERY
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ITEM_VIDEO -> GameDetailCoverVideoItemViewHolder(parent.toBinding())
else -> GameDetailCoverGalleryItemViewHolder(parent.toBinding())
}
}
override fun getItemCount(): Int = coverList.size
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
coverList.getOrNull(position)?.let {
if (holder is GameDetailCoverVideoItemViewHolder) {
bindVideoItem(holder, it, position)
}
if (holder is GameDetailCoverGalleryItemViewHolder) {
bindGalleryItem(holder, it, position)
}
}
}
private fun bindVideoItem(holder: GameDetailCoverVideoItemViewHolder, entity: CoverEntity, position: Int) {
if (position == 0 && !hasSetFixedHeight) {
holder.binding.root.post {
coverRecyclerView?.updateLayoutParams<ViewGroup.LayoutParams> { height = holder.binding.root.height }
hasSetFixedHeight = true
}
}
val topVideo = entity.video ?: return
val orientationUtils = OrientationUtils(mContext as Activity, holder.binding.player)
orientationUtils.isEnable = false
GSYVideoOptionBuilder()
.setIsTouchWigetFull(false)
.setIsTouchWiget(false)
.setRotateViewAuto(false)
.setShowFullAnimation(false)
.setSeekRatio(1F)
.setUrl(topVideo.url)
.setCacheWithPlay(true)
.setShowPauseCover(true)
.setReleaseWhenLossAudio(false)
.setVideoAllCallBack(object : GSYSampleCallBack() {
override fun onQuitFullscreen(url: String?, vararg objects: Any) {
orientationUtils.backToProtVideo()
holder.binding.player.uploadVideoStreamingPlaying("退出全屏")
}
})
.build(holder.binding.player)
holder.binding.player.gameName = viewModel.game?.name ?: ""
holder.binding.player.viewModel = viewModel
holder.binding.player.video = topVideo
holder.binding.player.updateThumb(topVideo.poster)
holder.binding.player.fullscreenButton.setOnClickListener {
val horizontalVideoView =
holder.binding.player.startWindowFullscreen(mContext, true, true) as? TopVideoView
if (horizontalVideoView == null) {
toastInInternalRelease("全屏失败,请向技术人员提供具体的操作步骤")
return@setOnClickListener
}
orientationUtils.resolveByClick()
horizontalVideoView.uuid = holder.binding.player.uuid
horizontalVideoView.viewModel = viewModel
horizontalVideoView.video = topVideo
horizontalVideoView.updateThumb(topVideo.poster)
horizontalVideoView.violenceUpdateMuteStatus()
holder.binding.player.uploadVideoStreamingPlaying("开始播放")
holder.binding.player.uploadVideoStreamingPlaying("点击全屏")
}
holder.binding.player.observeVolume(fragment)
}
private fun bindGalleryItem(holder: GameDetailCoverGalleryItemViewHolder, entity: CoverEntity, position: Int) {
val imageUrl = entity.gallery?.url ?: ""
val isLastItem = position == coverList.lastIndex
val imageWidth = (entity.gallery?.width ?: return).toInt()
val imageHeight = (entity.gallery?.height ?: return).toInt()
val isLandscape = imageWidth >= imageHeight
val ratio = imageWidth / imageHeight.toFloat()
ConstraintSet().also {
it.clone(holder.binding.root)
it.setDimensionRatio(holder.binding.galleryIv.id, if (isLandscape) "h,344:193" else "")
}.applyTo(holder.binding.root)
holder.binding.root.updateLayoutParams<MarginLayoutParams> {
width = if (isLandscape || isSingleGallery) ViewGroup.LayoutParams.MATCH_PARENT else ViewGroup.LayoutParams.WRAP_CONTENT
height = if (isLandscape || isSingleGallery) ViewGroup.LayoutParams.WRAP_CONTENT else PORTRAIT_IMAGE_HEIGHT_DP.dip2px()
rightMargin = if (isLandscape || isSingleGallery || isLastItem) 8F.dip2px() else 0
}
holder.binding.galleryIv.updateLayoutParams<ConstraintLayout.LayoutParams> {
width = if (isLandscape) 0 else (PORTRAIT_IMAGE_HEIGHT_DP.dip2px() * ratio).toInt()
height = if (isLandscape) 0 else PORTRAIT_IMAGE_HEIGHT_DP.dip2px()
}
if (position == 0 && !hasSetFixedHeight) {
holder.binding.root.post {
coverRecyclerView?.updateLayoutParams<ViewGroup.LayoutParams> { height = holder.binding.root.height }
hasSetFixedHeight = true
}
}
holder.binding.galleryIv.setTag(ImageUtils.TAG_TARGET_WIDTH, imageViewWidth)
ImageUtils.display(holder.binding.galleryIv, imageUrl)
holder.binding.root.setOnClickListener {
val intent = ImageViewerActivity.getIntent(
mContext,
arrayListOf(imageUrl),
0,
listOf(holder.binding.galleryIv),
"游戏详情-视频/图片区域"
)
mContext.startActivity(intent)
}
}
companion object {
const val ITEM_VIDEO = 0
const val ITEM_GALLERY = 1
private const val PORTRAIT_IMAGE_HEIGHT_DP = 280F
}
class GameDetailCoverVideoItemViewHolder(val binding: ItemGameCoverVideoBinding) :
BaseRecyclerViewHolder<Any>(binding.root)
class GameDetailCoverGalleryItemViewHolder(val binding: ItemGameCoverGalleryBinding) :
BaseRecyclerViewHolder<Any>(binding.root)
}

View File

@ -0,0 +1,876 @@
package com.gh.gamecenter.gamedetail.detail
import android.animation.ValueAnimator
import android.app.Activity
import android.content.Intent
import android.graphics.Color
import android.graphics.Typeface
import android.graphics.drawable.GradientDrawable
import android.graphics.drawable.TransitionDrawable
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.view.View
import android.view.ViewGroup
import android.widget.FrameLayout
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.core.view.iterator
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.OnScrollListener
import androidx.viewpager2.widget.ViewPager2
import com.gh.common.util.*
import com.gh.common.util.NewFlatLogUtils
import com.gh.common.util.NewLogUtils
import com.gh.common.view.FlexLinearLayout
import com.gh.gamecenter.ImageViewerActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.GlobalActivityManager.getCurrentPageEntity
import com.gh.gamecenter.common.base.GlobalActivityManager.getLastPageEntity
import com.gh.gamecenter.common.base.fragment.LazyFragment
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.eventbus.EBReuse
import com.gh.gamecenter.common.json.json
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.ScrollEventListener
import com.gh.gamecenter.common.view.TextBannerView
import com.gh.gamecenter.core.iinterface.IScrollable
import com.gh.gamecenter.core.provider.IFloatingWindowProvider
import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.databinding.FragmentNewGameDetailBinding
import com.gh.gamecenter.databinding.ItemGameDetailDataInfoBinding
import com.gh.gamecenter.databinding.ItemGameDetailFunctionTagBinding
import com.gh.gamecenter.databinding.LayoutGuideGameDetailFunctionTagBinding
import com.gh.gamecenter.entity.RatingComment
import com.gh.gamecenter.eventbus.EBTypeChange
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.TagStyleEntity
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.GameDetailWrapperFragment
import com.gh.gamecenter.gamedetail.detail.viewholder.BaseGameDetailItemViewHolder.Companion.getGameStatus
import com.gh.gamecenter.gamedetail.detail.viewholder.GameDetailCommentItemViewHolder
import com.gh.gamecenter.gamedetail.dialog.GameBigEventDialogFragment
import com.gh.gamecenter.gamedetail.dialog.GameFunctionDialogFragment
import com.gh.gamecenter.gamedetail.entity.GameDetailDataInfo
import com.gh.gamecenter.gamedetail.entity.GameDetailInfoTag
import com.gh.gamecenter.gamedetail.entity.GameDetailTabEntity
import com.gh.gamecenter.gamedetail.rating.RatingFragment
import com.gh.gamecenter.gamedetail.video.GameDetailScrollCalculatorHelper
import com.gh.gamecenter.home.video.ScrollCalculatorHelper
import com.gh.gamecenter.livedata.EventObserver
import com.gh.gamecenter.tag.TagsActivity
import com.halo.assistant.HaloApp
import com.shuyu.gsyvideoplayer.video.base.GSYVideoView
import com.therouter.TheRouter
import io.reactivex.disposables.CompositeDisposable
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import java.util.*
import kotlin.math.abs
class GameDetailFragment(private val downloadButton: DownloadButton) : LazyFragment(), IScrollable {
private lateinit var binding: FragmentNewGameDetailBinding
private lateinit var viewModel: GameDetailViewModel
private lateinit var scrollCalculatorHelper: GameDetailScrollCalculatorHelper
private val coverAdapter by lazy {
GameDetailCoverAdapter(
requireContext(),
this@GameDetailFragment,
snapHelper,
viewModel
)
}
private val coverLayoutManager by lazy {
LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL, false)
}
private val snapHelper by lazy { LeftPagerSnapHelper() }
private val coverScrollEventListener by lazy { ScrollEventListener(binding.coverRv) }
private val detailListAdapter by lazy {
GameDetailListAdapter(downloadButton, viewModel, viewLifecycleOwner)
}
private val detailLayoutManager by lazy { LinearLayoutManager(requireContext()) }
private var lastCompletelyVisibleCoverPosition = 0
private var isCoverHeightAnimating = false
private var coverPosition = 0
private var bigEventTransitionDrawable: TransitionDrawable? = null
private var gameEntity: GameEntity? = null
private val gameStatus
get() = getGameStatus(downloadButton.buttonStyle)
private var functionTagGuide: ViewGroup? = null
private var compositeDisposable = CompositeDisposable()
private var isPauseCoverVideo = false
override fun getRealLayoutId(): Int = R.layout.fragment_new_game_detail
override fun onRealLayoutInflated(inflatedView: View) {
super.onRealLayoutInflated(inflatedView)
binding = FragmentNewGameDetailBinding.bind(inflatedView)
scrollCalculatorHelper = GameDetailScrollCalculatorHelper(binding.coverRv, R.id.player, 0)
showFloatingWindowIfNeeded()
}
override fun onFragmentFirstVisible() {
super.onFragmentFirstVisible()
val gameId = arguments?.getString(EntranceConsts.KEY_GAME_ID) ?: ""
gameEntity = arguments?.getParcelable(EntranceConsts.KEY_GAME_ENTITY)
val factory = GameDetailViewModel.Factory(
HaloApp.getInstance().application,
gameId,
gameEntity,
mEntrance
)
viewModel = viewModelProviderFromParent(factory, gameId)
initCover()
initDetailRv()
observeData()
binding.gamedetailAppbar.addOnOffsetChangedListener { _, verticalOffset ->
if (!isAdded) return@addOnOffsetChangedListener
val absVerticalOffset = abs(verticalOffset)
val coverHeight = binding.coverRv.height
if (viewModel.displayCoverVideo && coverHeight != 0) {
val player = scrollCalculatorHelper.currentPlayer
if (absVerticalOffset > coverHeight && !isPauseCoverVideo && player?.currentState == GSYVideoView.CURRENT_STATE_PLAYING) {
pauseVideo()
isPauseCoverVideo = true
} else if (absVerticalOffset == 0 && isPauseCoverVideo && player?.currentState == GSYVideoView.CURRENT_STATE_PAUSE) {
resumeVideo()
isPauseCoverVideo = false
}
}
}
}
private fun observeData() {
viewModel.coverListLiveData.observeNonNull(viewLifecycleOwner) {
coverAdapter.submitList(it)
binding.run {
val defaultCoverEntity = it.find { coverEntity -> coverEntity.isDefault }
val tabNameList = it.map { coverEntity -> coverEntity.tabName }.distinct()
coverSfv.goneIf(tabNameList.size < 2) {
val defaultTabPosition =
tabNameList.indexOfFirst { tabName -> defaultCoverEntity?.tabName == tabName }
coverSfv.setItemList(tabNameList, if (defaultTabPosition != -1) defaultTabPosition else 0)
coverSfv.setOnCheckedCallback { position ->
val checkedText = tabNameList.getOrNull(position)
val currentCoverEntity = it.getOrNull(coverPosition)
if (currentCoverEntity?.tabName == checkedText) return@setOnCheckedCallback
val checkPosition = it.indexOfFirst { coverEntity -> coverEntity.tabName == checkedText }
if (checkPosition != -1) {
coverRv.setCurrentItem(checkPosition)
SensorsBridge.trackEvent("GameDetailMediaTabClick", json {
"game_id" to gameEntity?.id
"game_name" to gameEntity?.name
"sequence" to position + 1
"tab_name" to checkedText
})
}
}
}
coverRv.scrollToPosition(defaultCoverEntity?.index ?: 0)
coverPosition = defaultCoverEntity?.index ?: 0
}
if (viewModel.isTopVideoPartlyCached(viewModel.coverListLiveData.value?.first()?.video?.url ?: "")) {
postRunnable {
notifyScrollStateChanged(RecyclerView.SCROLL_STATE_IDLE)
}
} else {
// 未有缓存时,为避免影响页面加载,延迟自动播放视频
postDelayedRunnable({
if (activity != null && activity?.isFinishing != true) {
notifyScrollStateChanged(RecyclerView.SCROLL_STATE_IDLE)
}
}, COVER_VIDEO_INITIAL_DELAY)
}
}
viewModel.basicInfoLiveData.observeNonNull(viewLifecycleOwner) {
initBasicInfo(it)
}
viewModel.bigEventLiveData.observeNonNull(viewLifecycleOwner) {
GameBigEventDialogFragment.show(
requireContext(),
gameEntity?.id ?: "",
gameEntity?.name ?: "",
it
) { link, _ ->
DirectUtils.directToLinkPage(requireContext(), link, mEntrance, "游戏大事件弹窗", "游戏详情页-大事件")
NewFlatLogUtils.logGameDetailMajorEventDetailClick(
gameEntity?.name ?: "",
gameEntity?.id ?: "",
link.link ?: "",
link.type ?: "",
link.text ?: ""
)
SensorsBridge.trackGameDetailPageMajorEventClick(
gameId = gameEntity?.id ?: "",
gameName = gameEntity?.name ?: "",
pageName = getCurrentPageEntity().pageName,
pageId = getCurrentPageEntity().pageId,
pageBusinessId = getCurrentPageEntity().pageBusinessId,
lastPageName = getLastPageEntity().pageName,
lastPageId = getLastPageEntity().pageId,
lastPageBusinessId = getLastPageEntity().pageBusinessId,
downloadStatus = gameEntity?.downloadStatusChinese ?: "",
gameType = gameEntity?.categoryChinese ?: "",
action = "跳转链接",
linkText = link.text ?: "",
linkType = link.type ?: "",
linkId = link.link ?: ""
)
}
}
viewModel.dataInfoLiveData.observeNonNull(viewLifecycleOwner) {
initDataInfo(it)
}
viewModel.infoTagLiveData.observeNonNull(viewLifecycleOwner) {
initInfoTags(it)
}
viewModel.detailDataListLiveData.observeNonNull(viewLifecycleOwner) {
if (it.isNotEmpty()) {
detailListAdapter.submitList(it)
} else {
binding.coordinator.isVisible = false
binding.reuseNoneData.root.isVisible = true
}
}
viewModel.scrollToListPositionLiveData.observe(viewLifecycleOwner, EventObserver { type ->
val position = viewModel.detailDataListLiveData.value?.indexOfFirst { it.type == type } ?: -1
if (position != -1) {
binding.gamedetailAppbar.setExpanded(false, true)
detailLayoutManager.scrollToPositionWithOffset(position, 0)
}
})
}
private fun initDetailRv() {
binding.detailRv.run {
(itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
layoutManager = detailLayoutManager
adapter = detailListAdapter
addOnScrollListener(object : OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
viewModel.pageDepth = detailLayoutManager.findLastCompletelyVisibleItemPosition()
.coerceAtLeast(viewModel.pageDepth)
}
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
EventBus.getDefault().post(EBTypeChange(GameDetailWrapperFragment.EB_SCROLLING, 0))
}
}
})
}
}
private fun initCover() {
binding.run {
coverRv.layoutManager = coverLayoutManager
coverRv.adapter = coverAdapter
(coverRv.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
coverRv.isNestedScrollingEnabled = false
coverRv.onFlingListener = null
snapHelper.attachToRecyclerView(coverRv)
coverRv.addOnScrollListener(coverScrollEventListener.apply {
registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels)
val firstCompletelyVisibleItemPosition =
coverLayoutManager.findFirstCompletelyVisibleItemPosition()
if (!isCoverHeightAnimating &&
firstCompletelyVisibleItemPosition > -1 &&
firstCompletelyVisibleItemPosition != lastCompletelyVisibleCoverPosition) {
val firstCompletelyVisibleItemView =
coverLayoutManager.findViewByPosition(firstCompletelyVisibleItemPosition)
firstCompletelyVisibleItemView?.post {
if (firstCompletelyVisibleItemView.height > 0 && coverRv.height != firstCompletelyVisibleItemView.height) {
startCoverHeightAnimation(coverRv.height, firstCompletelyVisibleItemView.height)
}
}
}
lastCompletelyVisibleCoverPosition = firstCompletelyVisibleItemPosition
}
override fun onPageSelected(position: Int) {
super.onPageSelected(position)
if (position != coverPosition) {
val selectedCoverEntity = viewModel.coverListLiveData.value?.getOrNull(position)
coverPosition = position
if (coverSfv.isVisible) {
val sfvPosition = coverSfv.getItemList().indexOfFirst { it == selectedCoverEntity?.tabName }
coverSfv.setChecked(sfvPosition, true)
}
}
}
override fun onPageScrollStateChanged(state: Int) {
super.onPageScrollStateChanged(state)
notifyScrollStateChanged(state)
}
})
})
}
}
private fun initBasicInfo(basicInfo: GameEntity) {
binding.run {
gameIconIv.displayGameIcon(basicInfo)
val gameSubtitleStyle = basicInfo.subtitleStyle
val advanceDownload = basicInfo.advanceDownload
val subtitleLayout = FrameLayout(requireContext())
val subtitleTextView = TextView(requireContext()).apply {
layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, 14F.dip2px())
text = if (advanceDownload) "预下载" else basicInfo.subtitle
textSize = 10F
setPadding(2F.dip2px(), 0, 2F.dip2px(), 0)
if (gameSubtitleStyle != null && !advanceDownload) {
setTextColor("#${gameSubtitleStyle.color}".hexStringToIntColor())
background = GradientDrawable().apply {
cornerRadius = 2F.dip2px().toFloat()
if (gameSubtitleStyle.style == "border") {
setColor(Color.TRANSPARENT)
setStroke(0.5F.dip2px(), "#${gameSubtitleStyle.background}".hexStringToIntColor())
} else {
shape = GradientDrawable.RECTANGLE
setColor("#${gameSubtitleStyle.background}".hexStringToIntColor())
}
}
} else {
setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(requireContext()))
background = com.gh.gamecenter.feature.R.drawable.bg_advance_download_game_subtitle.toDrawable(requireContext())
}
}
subtitleLayout.addView(
subtitleTextView,
FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT, 14F.dip2px()).apply {
leftMargin = 4F.dip2px()
})
val subtitleBitmap = subtitleLayout.convertViewToBitmap()
basicInfo.name?.let { name ->
gameNameTv.text = SpannableStringBuilder("$name ").apply {
subtitleBitmap?.let {
setSpan(
CenterImageSpan(
context,
it
), name.length, name.length + 1, Spanned.SPAN_EXCLUSIVE_INCLUSIVE
)
}
}
}
gameNameTv.marqueeOnce()
officialEntryTv.isVisible = basicInfo.officialEntry
descTv.text = basicInfo.sellingPointsText
mBaseHandler.postDelayed({
gameEntity?.gameType = basicInfo.gameTags.joinToString("-") { it.name }
tagContainer.setTags(basicInfo.gameTags.take(3))
}, 5)
tagContainer.onClickListener = object :FlexLinearLayout.OnItemClickListener {
override fun onMoreClickListener() {}
override fun onItemClickListener(tag: TagStyleEntity, position: Int) {
NewLogUtils.logGameDetailTagClick(
gameEntity?.id ?: "",
gameEntity?.name ?: "",
tag.id,
tag.name,
tag.isTop
)
SensorsBridge.trackGameDetailModuleClick(
gameEntity?.id,
gameEntity?.name,
gameEntity?.categoryChinese,
"组件内容",
"卖点标签",
null,
null,
null,
null,
gameStatus = gameStatus
)
requireContext().startActivity(
TagsActivity.getIntent(requireContext(), tag.name, tag.name, mEntrance, "游戏介绍")
)
}
}
scoreContainer.goneIf(basicInfo.welfareText?.status == "on") {
scoreTv.setTypeface(Typeface.createFromAsset(requireContext().assets, Constants.DIN_FONT_PATH))
scoreTv.text = String.format(Locale.getDefault(), "%.1f", basicInfo.star)
scoreContainer.setOnClickListener {
viewModel.performTabSelected(GameDetailTabEntity.TYPE_COMMENT)
}
}
discountContainer.goneIf(basicInfo.welfareText == null || basicInfo.welfareText?.status == "off") {
discountTv.setTypeface(Typeface.createFromAsset(requireContext().assets, Constants.DIN_FONT_PATH))
discountTextTv.text = basicInfo.welfareText?.text
discountTv.text = basicInfo.welfareText?.discount
}
bigEventContainer.goneIf(basicInfo.event.isNullOrEmpty()) {
basicInfo.event?.let {
bigEventTransitionDrawable = R.drawable.transition_bg_game_detail_big_event.toDrawable(requireContext()) as TransitionDrawable
bigEventContainer.background = bigEventTransitionDrawable
val index = if (binding.bigEventBannerView.currentIndex != -1) binding.bigEventBannerView.currentIndex else 0
val isHighLight = it.getOrNull(index)?.highLight ?: false
if (isHighLight) bigEventTransitionDrawable?.reverseTransition(0)
bigEventBannerView.setOnClickListener { _ ->
SensorsBridge.trackGameDetailModuleClick(
gameEntity?.id,
gameEntity?.name,
gameEntity?.categoryChinese,
"组件内容",
"大事件",
null,
null,
null,
null,
gameStatus = gameStatus
)
viewModel.getBigEvent()
}
bigEventBannerView.setDataList(it.map { event ->
var eventStr = if (event.highLight) {
"${TimeUtils.getFormatTime(event.time, "MM-dd")}${TimeUtils.getDayString(event.time)}${event.content}"
} else {
if (TimeUtils.getBeforeDays(event.time) <= 15) "${
TimeUtils.getFormatTime(event.time, "MM-dd")
}${TimeUtils.getDayString(event.time)}${event.content}" else event.content
}
if (eventStr.contains("\n")) eventStr = eventStr.substring(0, eventStr.indexOf("\n"))
TextBannerView.BannerTextData(text = eventStr, highLight = event.highLight)
})
bigEventBannerView.onTextUpdateListener = { lastIndex, nextIndex ->
if (lastIndex in it.indices && nextIndex in it.indices) {
val lastEvent = it[lastIndex]
val nextEvent = it[nextIndex]
if (lastEvent.highLight != nextEvent.highLight) {
bigEventTransitionDrawable?.reverseTransition(
BIG_EVENT_TRANSITION_DURATION
)
}
}
}
bigEventBannerView.startBannerLoop()
}
}
}
}
private fun initDataInfo(dataList: List<GameDetailDataInfo>) {
binding.dataContainer.removeAllViews()
dataList.forEachIndexed { index, dataInfo ->
binding.dataContainer.addView(getDataInfoItemView(dataInfo), 68F.dip2px(), 46F.dip2px())
if (index != dataList.size - 1) {
binding.dataContainer.addView(View(requireContext()).apply {
setBackgroundColor(com.gh.gamecenter.common.R.color.ui_divider.toColor(requireContext()))
}, 1F.dip2px(), 24F.dip2px())
}
}
}
private fun initInfoTags(gameDetailInfoTag: GameDetailInfoTag) {
val isSpeedStatusOn = gameDetailInfoTag.requestSpeedStatus == "on"
val showInfoTags = gameDetailInfoTag.infoTags.isNotEmpty()
binding.infoTagContainer.goneIf(!isSpeedStatusOn && !showInfoTags) {
binding.accelerateTv.goneIf(!isSpeedStatusOn) {
if (gameDetailInfoTag.infoTags.isEmpty()) {
binding.infoTagContainer.updateLayoutParams<LinearLayout.LayoutParams> { width = LinearLayout.LayoutParams.WRAP_CONTENT }
binding.accelerateTipsTv.visibility = View.VISIBLE
}
binding.accelerateTv.setOnClickListener {
it.context.ifLogin("游戏详情-求加速") {
NewFlatLogUtils.logGameDetailClickForAccelerate(gameEntity?.id ?: "", gameEntity?.name ?: "")
SensorsBridge.trackGameDetailModuleClick(
gameEntity?.id,
gameEntity?.name,
gameEntity?.categoryChinese,
"组件内容",
"功能标签",
null,
null,
null,
null,
gameStatus = gameStatus
)
DialogHelper.showDialog(
requireContext(), "版本求加速", "如果游戏需要加速版本,您可以提交申请,让小助手尽快研究给您喔!",
"提交申请", "取消", {
viewModel.postRequestSpeed()
}, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)
)
}
}
if (SPUtils.getBoolean(Constants.SP_SHOW_GAME_DETAIL_FUNCTION_TAG_GUIDE, true)) {
binding.coverRv.post {
showFunctionTagGuide()
}
}
}
binding.functionTagContainer.goneIf(!showInfoTags) {
val iterator = binding.functionTagContainer.iterator()
while (iterator.hasNext()) {
val childView = iterator.next()
if (childView.id != R.id.moreContainer) {
iterator.remove()
}
}
val showTags = gameDetailInfoTag.infoTags.take(3)
showTags.forEachIndexed { index, infoTag ->
binding.functionTagContainer.addView(
getFunctionTagView(infoTag),
index,
LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
).apply {
if (index != 0) {
setMargins(8F.dip2px(), 0, 0, 0)
}
}
)
}
binding.moreContainer.goneIf(gameDetailInfoTag.infoTags.size < 3) {
binding.moreTv.text = "+${gameDetailInfoTag.infoTags.size - 3}"
}
binding.functionTagContainer.setOnClickListener {
SensorsBridge.trackGameDetailModuleClick(
gameEntity?.id,
gameEntity?.name,
gameEntity?.categoryChinese,
"组件内容",
"功能标签",
null,
null,
null,
null,
gameStatus = gameStatus
)
GameFunctionDialogFragment.show(
requireContext(),
gameEntity?.id ?: "",
gameEntity?.name ?: "",
gameDetailInfoTag.infoTags
)
}
}
}
}
private fun showFunctionTagGuide() {
val decorView = activity?.window?.decorView as? FrameLayout
LayoutGuideGameDetailFunctionTagBinding.inflate(layoutInflater, decorView, true).run {
functionTagGuide = root
val screenWidth = DisplayUtils.getScreenWidth(requireActivity())
val infoTagLocation = IntArray(2)
binding.infoTagContainer.getLocationInWindow(infoTagLocation)
val top = (infoTagLocation[1] - 12F.dip2px()).toFloat()
root.targetRect.set(8F.dip2px().toFloat(), top, (screenWidth - 8F.dip2px()).toFloat(), top + 53F.dip2px())
guideIv.translationY = top - 56F.dip2px()
root.setOnClickListener {
hideFunctionTagGuide()
}
}
}
private fun hideFunctionTagGuide() {
val decorView = activity?.window?.decorView as? FrameLayout
functionTagGuide?.animate()?.alpha(0F)?.setDuration(300L)?.apply {
doOnEnd {
decorView?.removeView(functionTagGuide)
functionTagGuide = null
}
}?.start()
SPUtils.setBoolean(Constants.SP_SHOW_GAME_DETAIL_FUNCTION_TAG_GUIDE, false)
}
private fun getFunctionTagView(infoTag: GameDetailInfoTag.InfoTag) =
ItemGameDetailFunctionTagBinding.inflate(layoutInflater).apply {
ImageUtils.display(iconIv, infoTag.icon)
nameTv.text = infoTag.name
}.root
private fun getDataInfoItemView(dataInfo: GameDetailDataInfo) = ItemGameDetailDataInfoBinding.inflate(layoutInflater).apply {
nameTv.text = dataInfo.nameCn
when (dataInfo.name) {
"ranking" -> {
if (dataInfo.ranking != null) {
nameTv.text = dataInfo.ranking.columnName
infoTv.text = "#${dataInfo.ranking.no}"
infoTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()))
infoTv.setTypeface(Typeface.createFromAsset(requireContext().assets, Constants.DIN_FONT_PATH))
infoTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(requireContext()))
root.setOnClickListener {
SensorsBridge.trackGameDetailModuleClick(
gameEntity?.id,
gameEntity?.name,
gameEntity?.categoryChinese,
"组件内容",
"数据信息栏",
null,
null,
null,
null,
gameStatus = gameStatus
)
DirectUtils.directToColumnCollection(
requireContext(),
dataInfo.ranking.collectionId,
entrance = mEntrance,
columnName = dataInfo.ranking.columnName,
)
}
}
}
"real_name" -> {
if (dataInfo.realName != null) {
infoTv.text = if (dataInfo.realName.state == "yes") "" else ""
if (dataInfo.realName.certificationScreenshot.isNotEmpty()) {
infoTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(requireContext()))
root.setOnClickListener {
SensorsBridge.trackGameDetailModuleClick(
gameEntity?.id,
gameEntity?.name,
gameEntity?.categoryChinese,
"组件内容",
"数据信息栏",
null,
null,
null,
null,
gameStatus = gameStatus
)
startActivity(
ImageViewerActivity.getSingleIntent(
requireContext(),
dataInfo.realName.certificationScreenshot,
false
)
)
}
} else {
infoTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()))
}
}
}
else -> {
infoTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()))
infoTv.text = dataInfo.value
}
}
}.root
private fun notifyScrollStateChanged(state: Int) {
val firstVisibleItem = coverLayoutManager.findFirstVisibleItemPosition()
val lastVisibleItem = coverLayoutManager.findLastVisibleItemPosition()
scrollCalculatorHelper.onScrollStateChanged(firstVisibleItem, lastVisibleItem, state)
}
private fun RecyclerView.setCurrentItem(position: Int) {
if (position == coverPosition) return
coverScrollEventListener.notifyProgrammaticScroll(position, true)
if (abs(position - coverPosition) > 3) {
scrollToPosition(if (position > coverPosition) position - 3 else position + 3)
post {
smoothScrollToPosition(position)
}
} else {
smoothScrollToPosition(position)
}
coverPosition = position
}
override fun onFragmentPause() {
super.onFragmentPause()
pauseVideo()
binding.bigEventBannerView.stopBannerLoop()
}
override fun onFragmentResume() {
super.onFragmentResume()
resumeVideo()
if (!viewModel.basicInfoLiveData.value?.event.isNullOrEmpty()) {
binding.bigEventBannerView.startBannerLoop()
}
}
private fun pauseVideo() {
val currentPlayer = scrollCalculatorHelper.currentPlayer
if (currentPlayer != null) {
currentPlayer.onVideoPause()
val currentPosition = currentPlayer.getCurrentPosition()
val videoUrl = currentPlayer.getUrl()
if (videoUrl.isNotEmpty()) {
ScrollCalculatorHelper.savePlaySchedule(MD5Utils.getContentMD5(videoUrl), currentPosition)
}
}
}
private fun resumeVideo() {
val currentPlayer = scrollCalculatorHelper.currentPlayer
if (currentPlayer != null) {
val videoUrl = currentPlayer.getUrl()
if (videoUrl.isNotEmpty()) {
val position = ScrollCalculatorHelper.getPlaySchedule(MD5Utils.getContentMD5(videoUrl))
//这里必须要延迟操作,否则会白屏
mBaseHandler.postDelayed({
if (position != 0L) {
scrollCalculatorHelper.currentPlayer?.seekTo(position)
scrollCalculatorHelper.currentPlayer?.onVideoResume(false)
} else {
scrollCalculatorHelper.currentPlayer?.release()
}
}, 50)
}
}
}
private fun showFloatingWindowIfNeeded() {
val floatingWindowProvider = TheRouter.get(IFloatingWindowProvider::class.java)
floatingWindowProvider?.getAndShowFloatingWindow(
gameEntity?.id ?: "",
gameEntity?.name ?: "",
"游戏详情",
this,
binding.detailRv
)?.let {
compositeDisposable.add(it)
}
}
override fun onDestroy() {
super.onDestroy()
compositeDisposable.dispose()
if (::scrollCalculatorHelper.isInitialized) {
scrollCalculatorHelper.release()
}
}
private fun startCoverHeightAnimation(startHeight: Int, targetHeight: Int) {
if (isCoverHeightAnimating) return
ValueAnimator.ofInt(startHeight, targetHeight).apply {
duration = COVER_HEIGHT_ANIMATION_DURATION
doOnStart {
isCoverHeightAnimating = true
}
doOnEnd {
isCoverHeightAnimating = false
}
addUpdateListener { va ->
binding.coverRv.updateLayoutParams<ViewGroup.LayoutParams> {
height = va.animatedValue as Int
}
}
}.start()
}
override fun scrollToTop() {
binding.gamedetailAppbar.setExpanded(true)
binding.detailRv.scrollToPosition(0)
}
// 登录事件
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(reuse: EBReuse) {
if (reuse.type == Constants.LOGIN_TAG) {
viewModel.detailDataListLiveData.value?.let { viewModel.getUserRelatedInfo(it) }
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
val commentPosition = detailListAdapter.currentList.indexOfFirst { it.linkComment != null }
if (resultCode == Activity.RESULT_OK) {
if (GameDetailCommentItemViewHolder.RATING_REPLY_REQUEST == requestCode || GameDetailCommentItemViewHolder.RATING_PATCH_REQUEST == requestCode) {
SyncDataBetweenPageHelper.resultHandle(data, object : OnSyncCallBack<RatingComment> {
override fun onData(dataPosition: Int): RatingComment? {
return if (commentPosition != -1) {
val commentList = detailListAdapter.currentList[commentPosition].linkComment?.data ?: return null
commentList.getOrNull(dataPosition)?.apply {
if (GameDetailCommentItemViewHolder.RATING_PATCH_REQUEST == requestCode) {
ignore = gameEntity?.ignoreComment ?: false
}
}
} else null
}
override fun onNotify(dataPosition: Int) {
if (commentPosition != -1) {
detailListAdapter.notifyItemChanged(commentPosition)
}
}
})
} else if (requestCode == GameLibaoAdapter.LIBAO_REQUEST) {
val position = detailListAdapter.currentList.indexOfFirst { it.linkLibao != null }
if (position != -1) {
detailListAdapter.notifyItemChanged(position)
}
}
} else if (requestCode == GameDetailCommentItemViewHolder.RATING_REPLY_REQUEST && resultCode == RatingFragment.RATING_DELETE_RESULT) {
data?.getParcelableExtra<RatingComment>(RatingComment::class.java.simpleName)?.run {
if (commentPosition != -1) {
val commentList = detailListAdapter.currentList[commentPosition].linkComment?.data ?: return
val deleteCommentPosition = commentList.indexOfFirst { it.id == id }
if (deleteCommentPosition != -1) {
commentList.removeAt(deleteCommentPosition)
}
detailListAdapter.notifyItemChanged(commentPosition)
}
}
}
}
override fun onBackPressed(): Boolean {
if (functionTagGuide != null) {
hideFunctionTagGuide()
return true
}
return super.onBackPressed()
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
viewModel.basicInfoLiveData.value?.let { initBasicInfo(it) }
val dataInfoList = viewModel.dataInfoLiveData.value
if (!dataInfoList.isNullOrEmpty()) {
initDataInfo(dataInfoList)
}
viewModel.infoTagLiveData.value?.let { initInfoTags(it) }
detailListAdapter.run { notifyItemRangeChanged(0, itemCount) }
}
companion object {
private const val COVER_HEIGHT_ANIMATION_DURATION = 400L
private const val COVER_VIDEO_INITIAL_DELAY = 500L
private const val BIG_EVENT_TRANSITION_DURATION = 1000
}
}

View File

@ -0,0 +1,127 @@
package com.gh.gamecenter.gamedetail.detail
import android.view.ViewGroup
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.ListAdapter
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.detail.viewholder.*
import com.gh.gamecenter.gamedetail.entity.GameDetailData
class GameDetailListAdapter(
private val downloadBtn: DownloadButton,
private val viewModel: GameDetailViewModel,
private val lifecycleOwner: LifecycleOwner
) :
ListAdapter<GameDetailData, BaseGameDetailItemViewHolder>(createDiffCallback()) {
override fun getItemViewType(position: Int): Int {
val data = currentList[position]
return when {
data?.linkContentCard != null -> {
when (data.linkContentCard.size) {
1 -> ITEM_CONTENT_CARD_SINGLE
2 -> ITEM_CONTENT_CARD_DOUBLE
else -> ITEM_CONTENT_CARD_TRIPLE
}
}
data?.linkAdvertising != null -> if (data.linkAdvertising.img.isEmpty()) ITEM_ADVERTISING else ITEM_ADVERTISING_IMAGE
data?.linkComprehensive != null -> ITEM_COMPREHENSIVE_PANEL
data?.linkCustomColumn != null -> ITEM_CUSTOM_COLUMN
data?.linkDrawer != null -> ITEM_DRAWER
data?.linkAnnouncement != null -> ITEM_ANNOUNCEMENT
data?.linkGameBrief != null -> ITEM_GAME_BRIEF
data?.linkDeveloperWord != null -> ITEM_DEVELOPER_WORD
data?.linkUpdate != null -> ITEM_UPDATE
data?.linkComment != null -> ITEM_COMMENT
data?.linkInfo != null -> ITEM_DETAIL_INFO
data?.linkContentRecommend != null -> ITEM_CONTENT_RECOMMEND
data?.linkGameVideo != null -> ITEM_GAME_VIDEO
data?.linkGameGuide != null -> ITEM_GAME_STRATEGY
data?.linkServer != null -> ITEM_SERVER
data?.linkLibao != null -> ITEM_GIFT
data?.linkRelatedGame != null -> ITEM_RELATED_GAME
data?.linkImageRecommend != null -> ITEM_RECOMMEND_IMAGE
data?.linkEveryonePlaying != null -> ITEM_RECOMMEND_GAME
data?.linkRecommendGameList != null -> ITEM_RECOMMEND_GAME_COLLECTION
else -> ITEM_RECOMMEND_COLUMN
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseGameDetailItemViewHolder {
return when (viewType) {
ITEM_CONTENT_CARD_SINGLE -> GameDetailContentCardSingleItemViewHolder(parent.toBinding(), downloadBtn, viewModel, lifecycleOwner)
ITEM_CONTENT_CARD_DOUBLE -> GameDetailContentCardDoubleItemViewHolder(parent.toBinding(), downloadBtn, viewModel, lifecycleOwner)
ITEM_CONTENT_CARD_TRIPLE -> GameDetailContentCardTripleItemViewHolder(parent.toBinding(), downloadBtn, viewModel, lifecycleOwner)
ITEM_ADVERTISING -> GameDetailAdvertisingItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_ADVERTISING_IMAGE -> GameDetailAdvertisingImageItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_COMPREHENSIVE_PANEL -> GameDetailComprehensivePanelItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_CUSTOM_COLUMN -> GameDetailCustomColumnItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_DRAWER -> GameDetailDrawerItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_ANNOUNCEMENT -> GameDetailAnnouncementItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_GAME_BRIEF -> GameDetailBriefItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_DEVELOPER_WORD -> com.gh.gamecenter.gamedetail.detail.viewholder.GameDetailDeveloperWordItemViewHolder(
parent.toBinding(),
downloadBtn,
viewModel
)
ITEM_UPDATE -> GameDetailUpdateItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_COMMENT -> GameDetailCommentItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_DETAIL_INFO -> GameDetailInfoItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_CONTENT_RECOMMEND -> GameDetailContentRecommendItemViewHolder(parent.toBinding(), downloadBtn, viewModel, lifecycleOwner)
ITEM_GAME_VIDEO -> GameDetailVideoItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_GAME_STRATEGY -> GameDetailStrategyItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_SERVER -> GameDetailServerItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_GIFT -> GameDetailGiftItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_RELATED_GAME -> GameDetailRelatedGameItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_RECOMMEND_IMAGE -> GameDetailRecommendImageItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_RECOMMEND_GAME -> GameDetailRecommendGameItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
ITEM_RECOMMEND_GAME_COLLECTION -> GameDetailRecommendGameCollectionItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
else -> GameDetailRecommendColumnItemViewHolder(parent.toBinding(), downloadBtn, viewModel)
}
}
override fun onBindViewHolder(holder: BaseGameDetailItemViewHolder, position: Int) {
val item = getItem(position)
holder.bindView(item)
}
companion object {
const val ITEM_CONTENT_CARD_SINGLE = 0 // 内容卡片-单个
const val ITEM_CONTENT_CARD_DOUBLE = 1 // 内容卡片-两个
const val ITEM_CONTENT_CARD_TRIPLE = 2 // 内容卡片-三个
const val ITEM_ADVERTISING = 3 // 广告推荐-无图片
const val ITEM_ADVERTISING_IMAGE = 4 // 广告推荐-带图片
const val ITEM_COMPREHENSIVE_PANEL = 5 // 综合面板
const val ITEM_CUSTOM_COLUMN = 6 // 自定义栏目
const val ITEM_DRAWER = 7 // 列表抽屉
const val ITEM_ANNOUNCEMENT = 8 // 资讯公告
const val ITEM_GAME_BRIEF = 9 // 游戏简介
const val ITEM_DEVELOPER_WORD = 10 // 开发者的话
const val ITEM_UPDATE = 11 // 更新内容
const val ITEM_COMMENT = 12 // 玩家评论
const val ITEM_DETAIL_INFO = 13 // 详情信息
const val ITEM_CONTENT_RECOMMEND = 14 // 内容推荐PK组件
const val ITEM_GAME_VIDEO = 15 // 视频
const val ITEM_GAME_STRATEGY = 16 // 游戏攻略
const val ITEM_SERVER = 17 // 游戏开服
const val ITEM_GIFT = 18 // 游戏礼包
const val ITEM_RELATED_GAME = 19 // 相关游戏
const val ITEM_RECOMMEND_IMAGE = 20 // 图片推荐
const val ITEM_RECOMMEND_GAME = 21 // 大家都在玩
const val ITEM_RECOMMEND_GAME_COLLECTION = 22 // 游戏单推荐
const val ITEM_RECOMMEND_COLUMN = 23 // 专题推荐
fun createDiffCallback() = object : ItemCallback<GameDetailData>() {
override fun areItemsTheSame(oldItem: GameDetailData, newItem: GameDetailData): Boolean {
return oldItem.areItemsTheSame(newItem)
}
override fun areContentsTheSame(oldItem: GameDetailData, newItem: GameDetailData): Boolean {
return oldItem.areContentsTheSame(newItem)
}
}
}
}

View File

@ -1,39 +1,37 @@
package com.gh.gamecenter.gamedetail.desc
package com.gh.gamecenter.gamedetail.detail
import android.content.Context
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.databind.BindingAdapters
import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.StringUtils
import com.gh.gamecenter.databinding.ItemGameDetailRelatedVersionBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.game.GameItemViewHolder
import com.gh.gamecenter.gamedetail.entity.RelatedVersion
import com.gh.gamecenter.gamedetail.detail.viewholder.BaseGameDetailItemViewHolder
class GameRelatedVersionAdapter(
private val mContext: Context,
private val mGameId: String,
private val mGameName: String,
private val mGame: GameEntity?,
private val mDatas: ArrayList<RelatedVersion>,
private val mEntrance: String
class GameDetailRelatedGameAdapter(
private val context: Context,
private val game: GameEntity?,
private val relatedGameList: ArrayList<GameEntity>,
private val entrance: String,
private val trackData: BaseGameDetailItemViewHolder.GameDetailModuleTrackData,
private val getGameStatus: () -> String
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var exposureEventList: ArrayList<ExposureEvent> = arrayListOf()
private val mMaxWidth = mContext.resources.displayMetrics.widthPixels - 32F.dip2px()
private val maxWidth = context.resources.displayMetrics.widthPixels - 32F.dip2px()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return GameDetailRelatedViewHolder(parent.toBinding())
}
override fun getItemCount(): Int = mDatas.size
override fun getItemCount(): Int = relatedGameList.size
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val relatedVersion = mDatas[position]
val gameEntity = relatedGameList.getOrNull(position) ?: return
if (holder is GameDetailRelatedViewHolder) {
val paddingStart = 16F.dip2px()
val isEndOfRow = position >= if (itemCount % 3 == 0) {
@ -46,48 +44,42 @@ class GameRelatedVersionAdapter(
var paddingEnd = if (isEndOfRow) 16F.dip2px() else 0F.dip2px()
holder.itemView.layoutParams = if (!isEndOfRow) {
paddingEnd += 1
ViewGroup.LayoutParams(mMaxWidth - 24F.dip2px(), ViewGroup.LayoutParams.WRAP_CONTENT)
ViewGroup.LayoutParams(maxWidth - 24F.dip2px(), ViewGroup.LayoutParams.WRAP_CONTENT)
} else {
ViewGroup.LayoutParams(mMaxWidth - 1, ViewGroup.LayoutParams.WRAP_CONTENT)
ViewGroup.LayoutParams(maxWidth - 1, ViewGroup.LayoutParams.WRAP_CONTENT)
}
holder.binding.root.setPadding(paddingStart, 8F.dip2px(), paddingEnd, 8F.dip2px())
val gameEntity = relatedVersion.game ?: return
holder.binding.gameNameTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(mContext))
holder.binding.gameNameTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
holder.binding.gameNameTv.text = gameEntity.name
BindingAdapters.setGameTags(holder.binding.gameLabelLl, gameEntity)
holder.binding.gameIconView.displayGameIcon(gameEntity)
holder.binding.gameIconView.setBorderColor(com.gh.gamecenter.common.R.color.resource_border)
holder.itemView.setOnClickListener {
MtaHelper.onEvent("游戏详情_新", "相关游戏版本", "${mGameName}+${relatedVersion.gameName}")
GameDetailActivity.startGameDetailActivity(
mContext,
relatedVersion.gameId,
context,
gameEntity.id,
StringUtils.buildString(
mEntrance,
entrance,
"+(",
"游戏详情",
"[",
relatedVersion.gameName,
gameEntity.name,
"]:相关游戏[",
(position + 1).toString(),
"])"
),
exposureEventList.safelyGetInRelease(position)
)
SensorsBridge.trackGameDetailPageRelatedGameClick(
gameId = mGameId,
gameName = mGameName,
pageName = GlobalActivityManager.getCurrentPageEntity().pageName,
pageId = GlobalActivityManager.getCurrentPageEntity().pageId,
pageBusinessId = GlobalActivityManager.getCurrentPageEntity().pageBusinessId,
lastPageName = GlobalActivityManager.getLastPageEntity().pageName,
lastPageId = GlobalActivityManager.getLastPageEntity().pageId,
lastPageBusinessId = GlobalActivityManager.getLastPageEntity().pageBusinessId,
downloadStatus = mGame?.downloadStatusChinese ?: "",
gameType = mGame?.categoryChinese ?: "",
clickGameId = gameEntity.id,
clickGameName = gameEntity.name ?: "",
clickGameType = gameEntity.categoryChinese
SensorsBridge.trackGameDetailModuleClick(
trackData.gameId,
trackData.gameName,
trackData.gameType,
"组件内容",
trackData.moduleType,
"相关游戏",
trackData.sequence,
supSequence = position + 1,
gameStatus = getGameStatus()
)
}
GameItemViewHolder.initGameSubtitleAndAdLabel(gameEntity, holder.binding.gameSubtitleTv)

View File

@ -1,4 +1,4 @@
package com.gh.gamecenter.gamedetail.desc
package com.gh.gamecenter.gamedetail.detail
import android.content.Context
import android.view.Gravity
@ -8,9 +8,9 @@ import android.view.ViewGroup
import android.view.ViewTreeObserver
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.common.utils.safelyGetInRelease
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.databinding.ItemGameDetailLatestServiceBinding
import com.gh.gamecenter.databinding.ItemGameDetailMoreBinding
import com.gh.gamecenter.feature.entity.GameEntity

View File

@ -0,0 +1,276 @@
package com.gh.gamecenter.gamedetail.detail
import android.app.Activity
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.widget.LinearLayout
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.RecyclerView
import com.facebook.drawee.drawable.ScalingUtils
import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.common.util.LibaoUtils
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.common.databinding.RefreshFooterviewBinding
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.SpanBuilder
import com.gh.gamecenter.databinding.ItemGameDetailMoreBinding
import com.gh.gamecenter.databinding.ItemGameLibaoBinding
import com.gh.gamecenter.feature.entity.LibaoEntity
import com.gh.gamecenter.libao.LibaoDetailActivity
import com.gh.gamecenter.login.user.UserManager
class GameLibaoAdapter(
val context: Context,
var libaos: ArrayList<LibaoEntity>,
val gameName: String,
val gameId: String,
showExpandIcon: Boolean = true
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var mIsExpand = false
private val mShowItemCount: Int = if (showExpandIcon) 3 else Int.MAX_VALUE // 最多展示多少个礼包
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when(viewType) {
LIBAO_ITEM -> LibaoViewHolder(parent.toBinding())
FOOTER_ITEM -> FooterViewHolder(parent.toBinding())
else -> MoreViewHolder(ItemGameDetailMoreBinding.inflate(LayoutInflater.from(context), parent, false))
}
}
override fun getItemViewType(position: Int): Int {
return if (libaos.size > mShowItemCount) {
if (!mIsExpand) {
if (position == mShowItemCount) MORE else LIBAO_ITEM
} else {
if (position == libaos.size) MORE else LIBAO_ITEM
}
} else {
LIBAO_ITEM
}
}
override fun getItemCount(): Int {
return if (libaos.size > mShowItemCount) {
if (mIsExpand) libaos.size + 1 else mShowItemCount + 1
} else {
libaos.size
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is LibaoViewHolder -> {
val libaoEntity = libaos[position]
holder.bindItem(context, this, gameId, gameName, libaoEntity)
}
is MoreViewHolder -> {
holder.binding.arrowIv.rotation = if (mIsExpand) 180f else 0f
holder.itemView.setOnClickListener {
if (!mIsExpand) MtaHelper.onEvent("游戏详情_新", "游戏礼包_展开", gameName)
mIsExpand = !mIsExpand
notifyDataSetChanged()
NewLogUtils.logGameDetailGiftClick(gameName, gameId, "展开")
}
}
is FooterViewHolder -> {
holder.binding.footerviewHint.setTextColor(com.gh.gamecenter.common.R.color.text_instance.toColor(context))
holder.binding.footerviewHint.text = "没有更多内容了"
holder.binding.footerviewLoading.isVisible = false
}
}
}
class LibaoViewHolder(var binding: ItemGameLibaoBinding) : RecyclerView.ViewHolder(binding.root) {
fun bindItem(context: Context, adapter: RecyclerView.Adapter<*>, gameId: String, gameName: String, libaoEntity: LibaoEntity) {
val position = bindingAdapterPosition
binding.root.updateLayoutParams<MarginLayoutParams> { topMargin = if (position == 0) 0 else 8F.dip2px() }
binding.root.background = com.gh.gamecenter.common.R.drawable.bg_shape_f8_radius_8.toDrawable(context)
binding.libaoNameTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
binding.contentTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
binding.remainingTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
binding.libaoCodeTv.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
binding.libaoNameTv.text = libaoEntity.name
binding.contentTv.text = libaoEntity.content?.fromHtml()
binding.horizontalScrollView.goneIf(libaoEntity.materials.isEmpty()) {
binding.imagesContainer.removeAllViews()
libaoEntity.materials.forEachIndexed { index, image ->
binding.imagesContainer.addView(SimpleDraweeView(context).apply {
hierarchy = GenericDraweeHierarchyBuilder(resources)
.setFadeDuration(500)
.setPlaceholderImage(com.gh.gamecenter.common.R.drawable.occupy, ScalingUtils.ScaleType.FIT_XY)
.build()
ImageUtils.display(this, image)
}, LinearLayout.LayoutParams(24F.dip2px(), 24F.dip2px()).apply {
setMargins(if (index != 0) 16F.dip2px() else 0, 0, 0, 0)
})
}
}
val isTypeCopy = libaoEntity.receiveMethod == "copy"
if (isTypeCopy) {
// 类型为复制,不需要登录也能直接领取
binding.libaoSchedulePb.visibility = View.GONE
binding.remainingTv.visibility = View.GONE
binding.libaoCodeTv.visibility = View.VISIBLE
val text = "兑换码:${libaoEntity.code}"
binding.libaoCodeTv.text = SpanBuilder(text).color(
binding.root.context,
4,
text.length,
com.gh.gamecenter.common.R.color.text_theme
).build()
binding.copyLibaoCodeIv.visibility = View.VISIBLE
binding.copyLibaoCodeIv.setOnClickListener {
binding.receiveTv.performClick()
}
} else if (libaoEntity.universal || libaoEntity.status == "check") {
//通用码礼包/或者还未添加礼包码时,不显示进度条,显示礼包码
binding.libaoSchedulePb.visibility = View.GONE
binding.remainingTv.visibility = View.GONE
binding.libaoCodeTv.visibility = View.VISIBLE
binding.copyLibaoCodeIv.visibility = View.GONE
if (!UserManager.getInstance().isLoggedIn) {
binding.libaoCodeTv.text = "礼包码:-"
} else {
when (libaoEntity.status) {
"linged", "repeatLing", "repeatLinged", "taoed", "repeatTao", "repeatTaoed" -> {
val size = libaoEntity.me?.userDataLibaoList?.size ?: 0
val code = libaoEntity.me?.userDataLibaoList?.get(size - 1)?.code ?: ""
val text = "礼包码:$code"
binding.libaoCodeTv.text = SpanBuilder(text).color(
binding.root.context,
4,
text.length,
com.gh.gamecenter.common.R.color.text_theme
).build()
binding.copyLibaoCodeIv.visibility = View.VISIBLE
binding.copyLibaoCodeIv.setOnClickListener {
code.copyTextAndToast("$code 复制成功")
}
}
else -> {
binding.libaoCodeTv.text = "礼包码:-"
}
}
}
} else {
if (!UserManager.getInstance().isLoggedIn) {
binding.libaoSchedulePb.visibility = View.VISIBLE
binding.remainingTv.visibility = View.VISIBLE
binding.libaoCodeTv.visibility = View.GONE
binding.copyLibaoCodeIv.visibility = View.GONE
initProgressUI(libaoEntity)
} else {
when (libaoEntity.status) {
"linged", "repeatLing", "repeatLinged", "taoed", "repeatTao", "repeatTaoed" -> {
binding.libaoSchedulePb.visibility = View.GONE
binding.remainingTv.visibility = View.GONE
binding.libaoCodeTv.visibility = View.VISIBLE
val size = libaoEntity.me?.userDataLibaoList?.size ?: 0
val code = libaoEntity.me?.userDataLibaoList?.get(size - 1)?.code ?: ""
val text = "礼包码:$code"
binding.libaoCodeTv.text = SpanBuilder(text).color(
binding.root.context,
4,
text.length,
com.gh.gamecenter.common.R.color.text_theme
).build()
binding.copyLibaoCodeIv.visibility = View.VISIBLE
binding.copyLibaoCodeIv.setOnClickListener {
code.copyTextAndToast("$code 复制成功")
}
}
else -> {
binding.libaoSchedulePb.visibility = View.VISIBLE
binding.remainingTv.visibility = View.VISIBLE
binding.libaoCodeTv.visibility = View.GONE
binding.copyLibaoCodeIv.visibility = View.GONE
initProgressUI(libaoEntity)
}
}
}
}
// LibaoUtils.setLiBaoBtnStatusRound(holder.binding.receiveTv, libaoEntity,true, context)
LibaoUtils.initLibaoBtn(
context,
binding.receiveTv,
libaoEntity,
false,
null,
true,
"游戏详情",
"游戏详情"
) {
adapter.notifyItemChanged(position)
}
if (!libaoEntity.packageName.isNullOrEmpty()) {
binding.receiveTv.setOnClickListener {
val intent = LibaoDetailActivity.getIntent(context, libaoEntity, true, "游戏详情")
if (it.context is Activity) {
(it.context as Activity).startActivityForResult(intent, LIBAO_REQUEST)
}
}
}
itemView.setOnClickListener {
if (isTypeCopy) {
// do nothing
} else {
val intent = LibaoDetailActivity.getIntent(context, libaoEntity, "游戏详情")
if (it.context is Activity) {
(it.context as Activity).startActivityForResult(intent, LIBAO_REQUEST)
}
NewLogUtils.logGameDetailGiftClick(gameName, gameId, "礼包详情")
}
}
}
private fun initProgressUI(libaoEntity: LibaoEntity) {
val total = libaoEntity.total
val available = libaoEntity.available
if (total != 0) {
val availablePercent = (available) / total.toFloat() * 100
val count = when {
availablePercent >= 1F -> {
availablePercent.toInt()
}
availablePercent == 0F -> {
0
}
else -> {
1
}
}
binding.remainingTv.text = "剩余${count}%"
binding.libaoSchedulePb.progress = count
}
}
}
class MoreViewHolder(var binding: ItemGameDetailMoreBinding) : RecyclerView.ViewHolder(binding.root)
class FooterViewHolder(var binding: RefreshFooterviewBinding) : RecyclerView.ViewHolder(binding.root)
companion object {
const val MORE = 0
const val LIBAO_ITEM = 1
const val FOOTER_ITEM = 2
const val LIBAO_REQUEST = 100
}
}

View File

@ -0,0 +1,75 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.content.Context
import android.view.View
import androidx.annotation.CallSuper
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.GameDetailData
abstract class BaseGameDetailItemViewHolder(val itemView: View, val downloadBtn: DownloadButton, val viewModel: GameDetailViewModel) : RecyclerView.ViewHolder(itemView) {
protected val context: Context = itemView.context
protected var itemData: GameDetailData? = null
protected val gameId
get() = viewModel.game?.id ?: ""
protected val gameName
get() = viewModel.game?.name ?: ""
protected val gameType
get() = viewModel.game?.categoryChinese ?: ""
protected val moduleType
get() = itemData?.typeChinese ?: ""
protected val sequence
get() = itemData?.position?.plus(1) ?: 0
protected val gameStatus
get() = getGameStatus(downloadBtn.buttonStyle)
protected val baseTrackData
get() = GameDetailModuleTrackData(
gameId = gameId,
gameName = gameName,
gameType = gameId,
gameStatus = gameStatus,
moduleType = moduleType,
sequence = sequence,
)
@CallSuper
open fun bindView(data: GameDetailData) {
itemData = data
}
data class GameDetailModuleTrackData(
var gameId: String = "",
var gameName: String = "",
var gameType: String = "",
var gameStatus: String = "",
var moduleType: String = "",
var moduleName: String = "",
var sequence: Int = 0,
var subModuleName: String = "",
var subSequence: Int = 0
)
companion object {
fun getGameStatus(buttonStyle: DownloadButton.ButtonStyle) = when (buttonStyle) {
DownloadButton.ButtonStyle.WAITING,
DownloadButton.ButtonStyle.UPDATING,
DownloadButton.ButtonStyle.PAUSE,
DownloadButton.ButtonStyle.FAILURE,
DownloadButton.ButtonStyle.DOWNLOADING_NORMAL,
DownloadButton.ButtonStyle.DOWNLOADING_PLUGIN -> "下载中"
DownloadButton.ButtonStyle.XAPK_UNZIPPING,
DownloadButton.ButtonStyle.XAPK_SUCCESS,
DownloadButton.ButtonStyle.XAPK_FAILURE,
DownloadButton.ButtonStyle.INSTALL_NORMAL,
DownloadButton.ButtonStyle.INSTALL_PLUGIN -> "待安装"
DownloadButton.ButtonStyle.LAUNCH_OR_OPEN -> "已安装"
DownloadButton.ButtonStyle.RESERVABLE -> "预约"
DownloadButton.ButtonStyle.RESERVED -> "已预约"
else -> "未下载"
}
}
}

View File

@ -0,0 +1,76 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.content.Context
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.updateLayoutParams
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.ItemGameDetailImageLinkBinding
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.GameDetailData
import com.gh.gamecenter.gamedetail.entity.GameDetailLink
class GameDetailAdvertisingImageItemViewHolder(
val binding: ItemGameDetailImageLinkBinding,
downloadBtn: DownloadButton,
viewModel: GameDetailViewModel
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val entity = data.linkAdvertising ?: return
bindImageItem(context, binding, entity, "游戏详情-广告推荐", baseTrackData) { gameStatus }
}
companion object {
fun bindImageItem(
context: Context,
binding: ItemGameDetailImageLinkBinding,
data: GameDetailLink,
entrance: String,
trackData: GameDetailModuleTrackData,
getGameStatus: () -> String
) {
binding.run {
if (data.img.isNotEmpty() && data.imgScale.isNotEmpty()) {
imageIv.updateLayoutParams<ConstraintLayout.LayoutParams> {
dimensionRatio = data.imgScale
}
maskView.updateLayoutParams<ConstraintLayout.LayoutParams> {
height = if (data.imgScale == "4:1") 40F.dip2px() else 60F.dip2px()
}
ImageUtils.display(imageIv, data.img)
}
iconIv.goneIf(data.componentIcon?.icon.isNullOrEmpty()) {
ImageUtils.display(iconIv, data.componentIcon?.icon)
}
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_aw_primary.toColor(context))
titleTv.text = data.title
linkTv.goneIf(data.text.isEmpty()) {
linkTv.text = data.text
linkTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
}
container.setOnClickListener { _ ->
data.link?.let {
DirectUtils.directToLinkPage(context, it, entrance, "")
SensorsBridge.trackGameDetailModuleClick(
trackData.gameId,
trackData.gameName,
trackData.gameType,
"组件内容",
trackData.moduleType,
trackData.moduleName,
trackData.sequence,
null,
null,
it.type,
it.link,
it.link,
getGameStatus()
)
}
}
}
}
}
}

View File

@ -0,0 +1,65 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.content.Context
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.ItemGameDetailLinkBinding
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.GameDetailData
import com.gh.gamecenter.gamedetail.entity.GameDetailLink
class GameDetailAdvertisingItemViewHolder(
val binding: ItemGameDetailLinkBinding,
downloadBtn: DownloadButton,
viewModel: GameDetailViewModel
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val entity = data.linkAdvertising ?: return
bindItem(context, binding, entity, "游戏详情-广告推荐", baseTrackData) { gameStatus }
}
companion object {
fun bindItem(
context: Context,
binding: ItemGameDetailLinkBinding,
data: GameDetailLink,
entrance: String,
trackData: GameDetailModuleTrackData,
getGameStatus: () -> String
) {
binding.run {
container.background = R.drawable.bg_shape_ui_container_1_radius_8_item_style.toDrawable(context)
iconIv.goneIf(data.componentIcon?.icon.isNullOrEmpty()) {
ImageUtils.display(iconIv, data.componentIcon?.icon)
}
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
titleTv.text = data.title
container.setOnClickListener { _ ->
data.link?.let {
DirectUtils.directToLinkPage(context, it, entrance, "")
SensorsBridge.trackGameDetailModuleClick(
trackData.gameId,
trackData.gameName,
trackData.gameType,
"组件内容",
trackData.moduleType,
trackData.moduleName,
trackData.sequence,
null,
null,
it.type,
it.link,
it.link,
getGameStatus()
)
}
}
}
}
}
}

View File

@ -0,0 +1,95 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.view.ViewGroup
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.view.VerticalItemDecoration
import com.gh.gamecenter.databinding.ItemGameDetailAnnouncementBinding
import com.gh.gamecenter.databinding.ItemGameDetailImageLinkBinding
import com.gh.gamecenter.databinding.ItemGameDetailLinkBinding
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.GameDetailData
import com.gh.gamecenter.gamedetail.entity.GameDetailLink
class GameDetailAnnouncementItemViewHolder(
val binding: ItemGameDetailAnnouncementBinding,
downloadBtn: DownloadButton,
viewModel: GameDetailViewModel
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val entity = data.linkAnnouncement ?: return
binding.run {
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
titleTv.text = entity.name
if (recyclerView.adapter !is GameDetailAnnouncementAdapter) {
recyclerView.isNestedScrollingEnabled = false
recyclerView.layoutManager = LinearLayoutManager(context)
recyclerView.adapter = GameDetailAnnouncementAdapter(entity.data)
recyclerView.addItemDecoration(VerticalItemDecoration(context, 8F, false, com.gh.gamecenter.common.R.color.transparent))
} else {
recyclerView.adapter?.run { notifyItemRangeChanged(0, itemCount) }
}
}
}
inner class GameDetailAnnouncementAdapter(val dataList: List<GameDetailLink>) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun getItemViewType(position: Int): Int {
val data = dataList[position]
return if (data.img.isEmpty()) ITEM_LINK else ITEM_IMAGE_LINK
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == ITEM_LINK) {
GameDetailAnnouncementItemViewHolder(parent.toBinding())
} else {
GameDetailAnnouncementImageItemViewHolder(parent.toBinding())
}
}
override fun getItemCount(): Int = dataList.size
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val data = dataList.getOrNull(position) ?: return
if (holder is GameDetailAnnouncementItemViewHolder) {
GameDetailAdvertisingItemViewHolder.bindItem(
context,
holder.binding,
data,
"游戏详情-资讯公告",
baseTrackData.apply {
moduleName = itemData?.linkAnnouncement?.name ?: ""
}) { gameStatus }
holder.binding.container.background = null
}
if (holder is GameDetailAnnouncementImageItemViewHolder) {
GameDetailAdvertisingImageItemViewHolder.bindImageItem(
context,
holder.binding,
data,
"游戏详情-资讯公告",
baseTrackData.apply {
moduleName = itemData?.linkAnnouncement?.name ?: ""
}
) { gameStatus }
}
}
}
inner class GameDetailAnnouncementItemViewHolder(val binding: ItemGameDetailLinkBinding) :
RecyclerView.ViewHolder(binding.root)
inner class GameDetailAnnouncementImageItemViewHolder(val binding: ItemGameDetailImageLinkBinding) :
RecyclerView.ViewHolder(binding.root)
companion object {
const val ITEM_LINK = 0
const val ITEM_IMAGE_LINK = 1
}
}

View File

@ -0,0 +1,135 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.text.Html
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.view.forEach
import androidx.core.view.isEmpty
import androidx.core.view.isVisible
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.ItemGameDetailBriefAwardBinding
import com.gh.gamecenter.databinding.ItemGameDetailBriefBinding
import com.gh.gamecenter.databinding.ItemGameDetailBriefTagBinding
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.dialog.GameDetailScrollableTextDialogFragment
import com.gh.gamecenter.gamedetail.entity.GameBrief
import com.gh.gamecenter.gamedetail.entity.GameDetailData
import com.gh.gamecenter.tag.TagsActivity
import splitties.systemservices.layoutInflater
class GameDetailBriefItemViewHolder(
val binding: ItemGameDetailBriefBinding,
downloadBtn: DownloadButton,
viewModel: GameDetailViewModel
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val entity = data.linkGameBrief ?: return
binding.run {
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
briefTv.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
divider.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_divider.toColor(context))
expandTv.background = R.drawable.bg_ui_surface_expand_gradient.toDrawable(context)
expandTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
titleTv.text = entity.name
tagScrollView.goneIf(entity.gameTags.isEmpty()) {
bindGamesTags(entity.gameTags)
}
briefTv.text = Html.fromHtml(entity.des)
briefTv.post {
expandTv.isVisible = briefTv.lineCount == 3 && briefTv.layout.getEllipsisCount(2) > 0
}
expandTv.setOnClickListener {
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
entity.name,
sequence,
gameStatus = gameStatus
)
GameDetailScrollableTextDialogFragment.show(
context,
viewModel.game?.id ?: "",
viewModel.game?.name ?: "",
"游戏信息",
entity.des
)
}
awardScrollView.goneIf(entity.awardData.isEmpty()) {
bindAwards(entity.awardData)
}
}
}
private fun bindGamesTags(gameTags: List<GameBrief.GameTag>) {
if (binding.tagContainer.isEmpty()) {
gameTags.forEachIndexed { index, gameTag ->
val tagBinding = ItemGameDetailBriefTagBinding.inflate(context.layoutInflater)
tagBinding.root.text = gameTag.name
tagBinding.root.setOnClickListener {
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
itemData?.linkGameBrief?.name,
sequence,
gameStatus = gameStatus
)
context.startActivity(
TagsActivity.getIntent(
context,
gameTag.name,
gameTag.name,
"游戏详情-游戏简介",
""
)
)
}
binding.tagContainer.addView(
tagBinding.root,
LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, 24F.dip2px()).apply {
leftMargin = if (index == 0) 0 else 4F.dip2px()
})
}
} else {
binding.tagContainer.forEach {
(it as? TextView)?.run {
setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
background = com.gh.gamecenter.common.R.drawable.bg_shape_ui_container_1_stroke_radius_6.toDrawable(context)
}
}
}
}
private fun bindAwards(awardList: List<GameBrief.AwardData>) {
if (binding.awardContainer.isEmpty()) {
awardList.forEachIndexed { index, awardData ->
val awardBinding = ItemGameDetailBriefAwardBinding.inflate(context.layoutInflater)
awardBinding.iconIv.display(awardData.icon)
awardBinding.nameTv.text = awardData.award
awardBinding.typeTv.text = awardData.awardType
awardBinding.root.setOnClickListener {
awardData.link?.let { link -> DirectUtils.directToLinkPage(context, link, "游戏详情-游戏简介", "") }
}
binding.awardContainer.addView(
awardBinding.root,
LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, 26F.dip2px()).apply {
leftMargin = if (index == 0) 0 else 24F.dip2px()
})
}
} else {
binding.awardContainer.forEach {
it.findViewById<TextView>(R.id.nameTv)?.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
it.findViewById<TextView>(R.id.typeTv)?.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
}
}
}
}

View File

@ -0,0 +1,403 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.graphics.drawable.ColorDrawable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import android.widget.LinearLayout
import android.widget.PopupWindow
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.*
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.SpanBuilder
import com.gh.gamecenter.databinding.ItemGameDetailRecyclerViewBinding
import com.gh.gamecenter.databinding.SubItemGameDetailCommentBinding
import com.gh.gamecenter.entity.RatingComment
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.GameDetailData
import com.gh.gamecenter.gamedetail.entity.GameDetailTabEntity
import com.gh.gamecenter.gamedetail.rating.RatingReplyActivity
import com.gh.gamecenter.gamedetail.rating.edit.RatingEditActivity
import com.gh.gamecenter.gamedetail.rating.logs.CommentLogsActivity
import com.gh.gamecenter.login.user.UserManager
import java.util.regex.Pattern
class GameDetailCommentItemViewHolder(
val binding: ItemGameDetailRecyclerViewBinding,
downloadBtn: DownloadButton,
viewModel: GameDetailViewModel
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val entity = data.linkComment ?: return
binding.run {
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
moreTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
moreTv.setDrawableEnd(com.gh.gamecenter.common.R.drawable.ic_auxiliary_arrow_right_12.toDrawable(context)?.apply {
colorFilter = PorterDuffColorFilter(com.gh.gamecenter.common.R.color.text_theme.toColor(context), PorterDuff.Mode.SRC_ATOP)
})
titleTv.text = entity.name
moreTv.isVisible = true
moreTv.text = "更多"
moreTv.setOnClickListener {
viewModel.performTabSelected(GameDetailTabEntity.TYPE_COMMENT)
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"右上角",
moduleType,
entity.name,
sequence,
gameStatus = gameStatus
)
}
if (recyclerView.adapter !is CommentItemAdapter) {
(recyclerView.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
recyclerView.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
recyclerView.adapter = CommentItemAdapter(entity.data)
} else {
recyclerView.adapter?.run { notifyItemRangeChanged(0, itemCount) }
}
}
}
inner class CommentItemAdapter(val dataList: ArrayList<RatingComment>) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val path = "游戏详情-玩家评论"
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
CommentItemViewHolder(parent.toBinding())
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val commentData = dataList.getOrNull(position) ?: return
var isChildLongClick = false
if (holder is CommentItemViewHolder) {
holder.binding.run {
root.background = R.drawable.bg_shape_f8_radius_8.toDrawable(context)
root.updateLayoutParams<MarginLayoutParams> {
leftMargin = if (position == 0) 16F.dip2px() else 8F.dip2px()
rightMargin = if (position == itemCount - 1) 16F.dip2px() else 0
}
userNameTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
tvBadgeName.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
commentTv.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
ipRegionTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
expandTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
moreIv.setImageResource(R.drawable.game_comment_more)
userIcon.display(commentData.user.border, commentData.user.icon, commentData.user.auth?.icon)
userNameTv.text = commentData.user.name
ratingStar.rating = commentData.star.toFloat()
if (commentData.user.badge != null) {
sdvUserBadge.visibility = View.VISIBLE
tvBadgeName.visibility = View.VISIBLE
ImageUtils.display(sdvUserBadge, commentData.user.badge?.icon)
tvBadgeName.text = commentData.user.badge?.name
} else {
sdvUserBadge.visibility = View.GONE
tvBadgeName.visibility = View.GONE
}
val p = Pattern.compile(RatingEditActivity.LABEL_REGEX)
val m = p.matcher(commentData.content)
if (m.find()) {
val contents =
TextHelper.getCommentLabelSpannableStringBuilder(commentData.content, com.gh.gamecenter.common.R.color.text_theme)
commentTv.setTextWithHighlightedTextWrappedInsideWrapper(
text = contents,
highlightedTextClickListener = TextHelper.DirectToWebViewHighlightedTextClick(
context,
"游戏详情-玩家评论"
)
)
} else {
commentTv.setTextWithHighlightedTextWrappedInsideWrapper(
text = commentData.content,
highlightedTextClickListener = TextHelper.DirectToWebViewHighlightedTextClick(
context,
"游戏详情-玩家评论"
)
)
}
commentTv.post {
expandTv.isVisible = commentTv.lineCount == 5 && commentTv.layout.getEllipsisCount(4) > 0
}
ipRegionTv.goneIf(!(commentData.source != null && commentData.source.region.isNotEmpty()))
ipRegionTv.text = " · ${commentData.source?.region}"
when {
commentData.isEditContent == null -> {
timeTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
timeTv.text = if (commentData.ignore) {
val s = "${NewsUtils.getFormattedTime(commentData.time)} 保护期评论不计入总分"
SpanBuilder(s).image(s.length - 12, s.length - 11, R.drawable.ic_ignore_rating_tips)
.color(context, s.length - 10, s.length, com.gh.gamecenter.common.R.color.text_secondary).build()
} else {
NewsUtils.getFormattedTime(commentData.time)
}
}
commentData.isEditContent!! -> {
timeTv.setTextColor(com.gh.gamecenter.common.R.color.text_F56614.toColor(context))
timeTv.text = if (commentData.ignore) {
"${NewsUtils.getFormattedTime(commentData.time)} 保护期间修改评论 >"
} else {
"${NewsUtils.getFormattedTime(commentData.time)} 已修改 >"
}
}
else -> {
timeTv.setTextColor(com.gh.gamecenter.common.R.color.text_F56614.toColor(context))
timeTv.text = if (commentData.ignore) {
"${NewsUtils.getFormattedTime(commentData.time)} 保护期间修改评论"
} else {
"${NewsUtils.getFormattedTime(commentData.time)} 已修改"
}
}
}
sdvUserBadge.setOnClickListener {
DialogUtils.showViewBadgeDialog(context, commentData.user.badge) {
DirectUtils.directToBadgeWall(
context,
commentData.user.id,
commentData.user.name,
commentData.user.icon
)
}
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
itemData?.linkComment?.name,
sequence,
subModuleName = "玩家评论列表",
supSequence = position + 1,
gameStatus = gameStatus
)
}
userIcon.setOnClickListener {
DirectUtils.directToHomeActivity(
context,
commentData.user.id,
viewModel.entrance,
path
)
NewLogUtils.logGameDetailCommentClick(
viewModel.game?.name ?: "",
viewModel.game?.id ?: "",
"个人主页"
)
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
itemData?.linkComment?.name,
sequence,
subModuleName = "玩家评论列表",
supSequence = position + 1,
gameStatus = gameStatus
)
}
userNameTv.setOnClickListener {
userIcon.performClick()
}
tvBadgeName.setOnClickListener { sdvUserBadge.performClick() }
commentTv.setOnLongClickListener(View.OnLongClickListener {
isChildLongClick = true
commentData.content.replace(RatingEditActivity.LABEL_REPLACE_REGEX.toRegex(), "")
.copyTextAndToast()
return@OnLongClickListener true
})
moreIv.setOnClickListener {
showMorePopWindow(it, commentData.user.id == UserManager.getInstance().userId) { text ->
when (text) {
"复制" -> {
commentData.content.replace(RatingEditActivity.LABEL_REPLACE_REGEX.toRegex(), "")
.copyTextAndToast()
}
"修改" -> {
val intent =
RatingEditActivity.getPatchIntent(context, viewModel.game!!, commentData)
SyncDataBetweenPageHelper.startActivityForResult(
context,
intent,
RATING_PATCH_REQUEST,
position
)
}
"投诉" -> {
context.ifLogin(BaseActivity.mergeEntranceAndPath(viewModel.entrance, path)) {
DialogUtils.showReportReasonDialog(
context,
Constants.REPORT_LIST.toList() as java.util.ArrayList<String>
) { reason, desc ->
SimpleRequestHelper.reportGameComment(
viewModel.game?.id ?: "",
commentData.id,
if (reason != "其他原因") reason else desc
)
}
}
}
"删除" -> {
DialogHelper.showDeleteGameCommentDialog(
context,
R.string.delete_game_comment.toResString()
) {
SimpleRequestHelper.deleteGameComment(
viewModel.game?.id ?: "",
commentData.id
) {
// 删除列表中的评论(如果当前列表有的话)
val index = dataList.indexOfFirst { item ->
item.id == commentData.id
}
if (index != -1) {
dataList.removeAt(index)
notifyItemRemoved(index)
}
}
}
}
}
}
}
timeTv.setOnClickListener {
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
itemData?.linkComment?.name,
sequence,
subModuleName = "玩家评论列表",
supSequence = position + 1,
gameStatus = gameStatus
)
if (commentData.isEditContent == null && commentData.ignore) {
DialogUtils.showStopServerExplanationDialog(
context,
if (viewModel.game?.commentDescription?.isNotEmpty() == true)
viewModel.game?.commentDescription else context.getString(R.string.rating_protection),
viewModel.game?.name
?: ""
)
} else if (commentData.isEditContent == true) {
val intent = CommentLogsActivity.getIntent(context, viewModel.game!!.id, commentData.id)
context.startActivity(intent)
}
}
expandTv.setOnClickListener { root.performClick() }
root.setOnClickListener {
if (isChildLongClick) {
isChildLongClick = false
return@setOnClickListener
}
val exposureSource = arrayListOf(
ExposureSource("游戏详情"),
ExposureSource("详情tab"),
ExposureSource("玩家评价"),
).toJson()
val intent = RatingReplyActivity.getIntent(
context = context,
gameId = viewModel.game?.id ?: "",
commentId = commentData.id,
exposureSource = exposureSource,
entrance = viewModel.entrance ?: "",
path = path
)
SyncDataBetweenPageHelper.startActivityForResult(
context,
intent,
RATING_REPLY_REQUEST,
position
)
NewLogUtils.logGameDetailCommentClick(
viewModel.game?.name ?: "",
viewModel.game?.id ?: "",
"评论内容"
)
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
itemData?.linkComment?.name,
sequence,
subModuleName = "玩家评论列表",
supSequence = position + 1,
gameStatus = gameStatus
)
}
}
}
}
private fun showMorePopWindow(v: View, isMyRating: Boolean, clickListener: (String) -> Unit) {
val contentList = if (isMyRating) arrayListOf("复制", "修改", "删除")
else arrayListOf("复制", "投诉")
val inflater = LayoutInflater.from(v.context)
val layout = inflater.inflate(com.gh.gamecenter.common.R.layout.layout_popup_container, null)
val popupWindow = PopupWindow(
layout,
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
popupWindow.apply {
setBackgroundDrawable(ColorDrawable(0))
isTouchable = true
isFocusable = true
isOutsideTouchable = true
}
val container = layout.findViewById<LinearLayout>(R.id.container)
for (text in contentList) {
val item = inflater.inflate(R.layout.layout_popup_option_item, container, false)
container.addView(item)
val hitText = item.findViewById<TextView>(R.id.hint_text)
hitText.text = text
item.setOnClickListener {
clickListener.invoke(text)
popupWindow.dismiss()
}
}
popupWindow.showAutoOrientation(v)
}
override fun getItemCount(): Int = dataList.size
}
inner class CommentItemViewHolder(val binding: SubItemGameDetailCommentBinding) :
RecyclerView.ViewHolder(binding.root)
companion object {
const val RATING_REPLY_REQUEST = 233
const val RATING_PATCH_REQUEST = 234
}
}

View File

@ -0,0 +1,249 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.graphics.Typeface
import android.text.Html
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.VerticalItemDecoration
import com.gh.gamecenter.databinding.*
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.GameDetailComprehensivePanelItem
import com.gh.gamecenter.gamedetail.entity.GameDetailComprehensivePanelItem.Companion.FUNCTION_TYPE_ONE_LINE_ONE_POINT
import com.gh.gamecenter.gamedetail.entity.GameDetailComprehensivePanelItem.Companion.SHOW_TYPE_PART
import com.gh.gamecenter.gamedetail.entity.GameDetailComprehensivePanelItem.ContentData
import com.gh.gamecenter.gamedetail.entity.GameDetailData
class GameDetailComprehensivePanelItemViewHolder(
val binding: ItemGameDetailComprehensivePanelBinding,
downloadBtn: DownloadButton,
viewModel: GameDetailViewModel
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val dataList = data.linkComprehensive ?: return
binding.run {
panelRv.background = com.gh.gamecenter.common.R.drawable.bg_shape_f8_radius_8.toDrawable(context)
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
if (panelRv.adapter !is ComprehensivePanelAdapter) {
(panelRv.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
panelRv.layoutManager = LinearLayoutManager(context)
panelRv.adapter = ComprehensivePanelAdapter(dataList)
} else {
panelRv.adapter?.run { notifyItemRangeChanged(0, itemCount) }
}
}
}
inner class ComprehensivePanelAdapter(val dataList: List<GameDetailComprehensivePanelItem>) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun getItemViewType(position: Int): Int {
val data = dataList[position]
return when (data.type) {
GameDetailComprehensivePanelItem.TYPE_FUNCTION -> ITEM_FUNCTION
GameDetailComprehensivePanelItem.TYPE_FAQ -> ITEM_FAQ
else -> ITEM_DECLARATION
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ITEM_FUNCTION -> ComprehensivePanelFunctionViewHolder(parent.toBinding())
ITEM_FAQ -> ComprehensivePanelFAQViewHolder(parent.toBinding())
else -> ComprehensivePanelDeclarationViewHolder(parent.toBinding())
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val data = dataList.getOrNull(position) ?: return
if (holder is ComprehensivePanelFunctionViewHolder) {
holder.binding.run {
titleTv.goneIf(data.title.isEmpty()) {
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
titleTv.text = data.title
}
val contentData = data.data ?: return
val showPart = data.functionType == FUNCTION_TYPE_ONE_LINE_ONE_POINT && data.showType == SHOW_TYPE_PART
if (recyclerView.adapter !is ComprehensivePanelFunctionAdapter) {
val spanCount = if (data.functionType == FUNCTION_TYPE_ONE_LINE_ONE_POINT) 1 else 2
val shrinkHeightDp = data.showRowNum * SHRANK_ONE_LINE_HEIGHT_DP
recyclerView.isNestedScrollingEnabled = false
(recyclerView.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
recyclerView.layoutManager = object : GridLayoutManager(context, spanCount) {
override fun canScrollVertically(): Boolean {
return false
}
}
recyclerView.adapter = ComprehensivePanelFunctionAdapter(data.title, contentData)
recyclerView.post {
expandTv.goneIf(!showPart || recyclerView.height <= shrinkHeightDp.dip2px()) {
var isExpand = false
expandTv.text = "展开"
recyclerView.updateLayoutParams<ConstraintLayout.LayoutParams> {
height = shrinkHeightDp.dip2px()
}
expandTv.setOnClickListener {
isExpand = !isExpand
recyclerView.updateLayoutParams<ConstraintLayout.LayoutParams> {
height = if (isExpand) {
ConstraintLayout.LayoutParams.WRAP_CONTENT
} else {
shrinkHeightDp.dip2px()
}
}
expandTv.text = if (isExpand) "收起" else "全部"
}
}
}
} else {
recyclerView.adapter?.run { notifyItemRangeChanged(0, itemCount) }
}
}
}
if (holder is ComprehensivePanelFAQViewHolder) {
holder.binding.run {
titleTv.goneIf(data.title.isEmpty()) {
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
titleTv.text = data.title
}
val contentData = data.data ?: return
if (recyclerView.adapter !is ComprehensivePanelFAQAdapter) {
recyclerView.isNestedScrollingEnabled = false
(recyclerView.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
recyclerView.layoutManager = LinearLayoutManager(context)
recyclerView.adapter = ComprehensivePanelFAQAdapter(data.title, contentData)
recyclerView.addItemDecoration(VerticalItemDecoration(context, 8F, false, com.gh.gamecenter.common.R.color.transparent))
} else {
recyclerView.adapter?.run { notifyItemRangeChanged(0, itemCount) }
}
}
}
if (holder is ComprehensivePanelDeclarationViewHolder) {
holder.binding.run {
titleTv.goneIf(data.title.isEmpty()) {
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
titleTv.text = data.title
}
declarationTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
declarationTv.text = Html.fromHtml(data.declaration)
}
}
}
override fun getItemCount(): Int = dataList.size
}
inner class ComprehensivePanelFunctionAdapter(private val subModuleName: String, val dataList: List<ContentData>) :
RecyclerView.Adapter<ComprehensivePanelFunctionItemViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ComprehensivePanelFunctionItemViewHolder =
ComprehensivePanelFunctionItemViewHolder(parent.toBinding())
override fun getItemCount(): Int = dataList.size
override fun onBindViewHolder(holder: ComprehensivePanelFunctionItemViewHolder, position: Int) {
val data = dataList.getOrNull(position) ?: return
holder.binding.numberIv.setImageResource(R.drawable.bg_game_detail_comprehensive_panel_function_number)
holder.binding.numberTv.run {
setTextColor(com.gh.gamecenter.common.R.color.text_aw_primary.toColor(context))
setTypeface(Typeface.createFromAsset(context.assets, Constants.DIN_FONT_PATH))
text = (position + 1).toString()
}
holder.binding.contentTv.run {
setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
text = data.text
}
holder.binding.root.setOnClickListener { _ ->
data.link?.let {
DirectUtils.directToLinkPage(context, it, "游戏详情-功能说明", "")
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
"功能说明",
sequence,
subModuleName,
position + 1,
it.type,
it.link,
it.text,
gameStatus
)
}
}
}
}
inner class ComprehensivePanelFAQAdapter(private val subModuleName: String, val dataList: List<ContentData>) :
RecyclerView.Adapter<ComprehensivePanelFAQItemViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ComprehensivePanelFAQItemViewHolder =
ComprehensivePanelFAQItemViewHolder(parent.toBinding())
override fun getItemCount(): Int = dataList.size
override fun onBindViewHolder(holder: ComprehensivePanelFAQItemViewHolder, position: Int) {
val data = dataList.getOrNull(position) ?: return
holder.binding.root.background = R.drawable.bg_shape_ui_surface_radius_4_item_style.toDrawable(context)
holder.binding.contentTv.run {
setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
text = data.text
}
holder.binding.arrowIv.goneIf(data.link == null) {
holder.binding.arrowIv.setImageResource(R.drawable.ic_auxiliary_arrow_right_8)
}
holder.binding.root.setOnClickListener { _ ->
data.link?.let {
DirectUtils.directToLinkPage(context, it, "游戏详情-功能说明", "")
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
"功能说明",
sequence,
subModuleName,
position + 1,
it.type,
it.link,
it.text,
gameStatus
)
}
}
}
}
inner class ComprehensivePanelFunctionViewHolder(val binding: ItemGameDetailComprehensivePanelRvBinding) :
RecyclerView.ViewHolder(binding.root)
inner class ComprehensivePanelFAQViewHolder(val binding: ItemGameDetailComprehensivePanelRvBinding) :
RecyclerView.ViewHolder(binding.root)
inner class ComprehensivePanelDeclarationViewHolder(val binding: ItemGameDetailComprehensivePanelDeclarationBinding) :
RecyclerView.ViewHolder(binding.root)
inner class ComprehensivePanelFunctionItemViewHolder(val binding: ItemGameDetailComprehensivePanelFunctionBinding) :
RecyclerView.ViewHolder(binding.root)
inner class ComprehensivePanelFAQItemViewHolder(val binding: ItemGameDetailComprehensivePanelFaqBinding) :
RecyclerView.ViewHolder(binding.root)
companion object {
const val ITEM_FUNCTION = 0
const val ITEM_FAQ = 1
const val ITEM_DECLARATION = 2
const val SHRANK_ONE_LINE_HEIGHT_DP = 24F
}
}

View File

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

View File

@ -0,0 +1,284 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.content.Context
import android.widget.ImageView
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.view.isVisible
import androidx.lifecycle.LifecycleOwner
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.common.constant.Config
import com.gh.common.util.DirectUtils
import com.gh.common.util.NewFlatLogUtils
import com.gh.gamecenter.ShellActivity
import com.gh.gamecenter.ShellActivity.Type
import com.gh.gamecenter.WebActivity
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.TextBannerView
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.databinding.ItemGameDetailContentCardSingleBinding
import com.gh.gamecenter.feature.entity.MeEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity.Companion.TYPE_ARCHIVE
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity.Companion.TYPE_BBS
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity.Companion.TYPE_GIFT
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity.Companion.TYPE_RELATED_VERSION
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity.Companion.TYPE_SERVER
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity.Companion.TYPE_TOOLKIT
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity.Companion.TYPE_ZONE
import com.gh.gamecenter.gamedetail.entity.GameDetailData
import com.gh.gamecenter.gamedetail.entity.GameDetailTabEntity
import com.gh.gamecenter.gamedetail.fuli.kaifu.ServersCalendarActivity
import com.gh.gamecenter.gamedetail.libao.LibaoListFragment
import com.gh.gamecenter.livedata.Event
import com.gh.gamecenter.newsdetail.NewsDetailActivity
import kotlin.math.abs
class GameDetailContentCardSingleItemViewHolder(
val binding: ItemGameDetailContentCardSingleBinding,
downloadBtn: DownloadButton,
viewModel: GameDetailViewModel,
private val lifecycleOwner: LifecycleOwner,
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val contentCardEntity = data.linkContentCard?.first() ?: return
binding.run {
bindContentCard(
context,
lifecycleOwner,
viewModel,
contentCardEntity,
container,
titleTv,
iconIv,
contentTv,
contentBannerView,
redDotTv,
newIv,
baseTrackData.apply { subSequence = 0 }
) { gameStatus }
}
}
companion object {
fun bindContentCard(
context: Context,
lifecycleOwner: LifecycleOwner,
viewModel: GameDetailViewModel,
contentCardEntity: ContentCardEntity,
containerView: ConstraintLayout,
titleTv: TextView,
iconIv: SimpleDraweeView,
contentTv: TextView,
contentBannerView: TextBannerView,
redDotTv: TextView,
newIv: ImageView,
trackData: GameDetailModuleTrackData,
getGameStatus: () -> String
) {
if (contentCardEntity.id == containerView.tag) {
if (contentBannerView.isVisible) contentBannerView.updateView()
containerView.background = com.gh.gamecenter.common.R.drawable.bg_shape_f8_radius_8.toDrawable(context)
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
contentTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
return
}
containerView.tag = contentCardEntity.id
titleTv.text = contentCardEntity.title
ImageUtils.display(iconIv, contentCardEntity.icon)
val showContentTv =
contentCardEntity.showDes && (contentCardEntity.des.isNotEmpty() || contentCardEntity.link.type == TYPE_GIFT)
contentTv.visibleIf(showContentTv) {
contentTv.text =
if (contentCardEntity.link.type == TYPE_GIFT && contentCardEntity.des.isEmpty()) "${contentCardEntity.libao?.total}个游戏礼包" else contentCardEntity.des
contentTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
}
val showContentBannerView =
contentCardEntity.showDes && contentCardEntity.link.type == TYPE_SERVER && contentCardEntity.server != null
contentBannerView.goneIf(!showContentBannerView) {
val server = contentCardEntity.server ?: return@goneIf
val nowTime = System.currentTimeMillis()
var timeDiff = 0L
var closestIndex = 0
val bannerTextDataList = server.calendar.mapIndexed { index, serverCalendarEntity ->
val diff = abs(nowTime - serverCalendarEntity.getTime() * 1000L)
if (diff < timeDiff || index == 0) {
timeDiff = diff
closestIndex = index
}
val serverTime =
if (TimeUtils.isToday(serverCalendarEntity.getTime()))
serverCalendarEntity.getFormatTime("今天 HH:mm")
else if (TimeUtils.isTomorrow(serverCalendarEntity.getTime()))
serverCalendarEntity.getFormatTime("明天 HH:mm")
else
serverCalendarEntity.getFormatTime("MM-dd HH:mm")
TextBannerView.BannerTextData("${serverTime ?: ""} ${serverCalendarEntity.type}")
}
contentBannerView.setDataList(bannerTextDataList, closestIndex)
contentBannerView.startBannerLoop()
}
redDotTv.goneIf(!((contentCardEntity.link.type == TYPE_SERVER && contentCardEntity.server?.total != 0) || contentCardEntity.link.type == TYPE_GIFT)) {
if ((contentCardEntity.link.type == TYPE_SERVER) && (contentCardEntity.server?.calendar?.isNotEmpty() == true))
redDotTv.text = contentCardEntity.server?.total.toString()
if ((contentCardEntity.link.type == TYPE_GIFT) && (contentCardEntity.libao != null))
redDotTv.text = contentCardEntity.libao?.total.toString()
}
val showNewTag = contentCardEntity.link.type == TYPE_ARCHIVE && contentCardEntity.archive != null && contentCardEntity.showNewTag
newIv.goneIf(!showNewTag)
containerView.setOnClickListener {
NewFlatLogUtils.logGameDetailGameContentCardClick(
contentCardEntity.title ?: "",
viewModel.game?.name ?: "",
viewModel.game?.id ?: "",
contentCardEntity.link.type ?: "",
contentCardEntity.link.link ?: "",
contentCardEntity.link.text ?: ""
)
SensorsBridge.trackGameDetailModuleClick(
gameId = trackData.gameId,
gameName = trackData.gameName,
gameType = trackData.gameType,
contentType = "组件内容",
moduleType = trackData.moduleType,
moduleName = trackData.moduleName,
sequence = trackData.sequence,
linkText = contentCardEntity.link.text ?: "",
linkType = contentCardEntity.link.type ?: "",
linkId = contentCardEntity.id,
gameStatus = getGameStatus()
)
val dialog = contentCardEntity.dialog
if (dialog != null) {// 展示内容卡片提示弹窗
DialogHelper.showDialog(
context = context,
title = dialog.title ?: "",
content = dialog.body ?: "",
confirmText = context.getString(com.gh.gamecenter.common.R.string.confirm),
cancelText = context.getString(com.gh.gamecenter.common.R.string.cancel),
confirmClickCallback = {
jumpToContentCardLink(context, contentCardEntity, viewModel)
}
)
} else {
jumpToContentCardLink(context, contentCardEntity, viewModel)
}
}
viewModel.contentCardClickedLiveData.observe(lifecycleOwner) {
if (it.peekContent() == contentCardEntity.link.type) {
it.getContentWithHandled()?.let { containerView.performClick() }
}
}
}
private fun jumpToContentCardLink(context: Context, contentCardEntity: ContentCardEntity, viewModel: GameDetailViewModel) {
val path = "游戏详情->内容卡片"
when (contentCardEntity.link.type) {
TYPE_GIFT,
TYPE_ARCHIVE -> {
val type = if (contentCardEntity.link.type == TYPE_GIFT) GameDetailTabEntity.TYPE_GIFT else GameDetailTabEntity.TYPE_ARCHIVE
val tabList = viewModel.gameDetailTabListLiveData.value?.data
if (tabList?.find { it.type == type } != null) {
viewModel.performTabSelected(type)
} else if (contentCardEntity.link.type == TYPE_GIFT) {
val bundle = LibaoListFragment.getBundle(
viewModel.game?.id ?: "",
viewModel.game?.name ?: "",
false
)
context.startActivity(ShellActivity.getIntent(context, Type.SIMPLE_LIBAO_LIST, bundle))
} else if (contentCardEntity.link.type == TYPE_ARCHIVE) {
DirectUtils.directToCloudArchive(context, viewModel.game?.id ?: "", viewModel.game?.name ?: "", "", "游戏详情页-内容卡片")
}
}
TYPE_SERVER -> {
if (viewModel.game != null && contentCardEntity.server != null) {
context.startActivity(
ServersCalendarActivity.getIntent(
context,
viewModel.game!!, contentCardEntity.server!!,
MeEntity()
)
)
}
}
TYPE_RELATED_VERSION -> {
viewModel.scrollToListPositionLiveData.postValue(Event(GameDetailData.TYPE_RELATED_GAME))
}
TYPE_ZONE -> {
if (contentCardEntity.zoneTab && viewModel.game?.zone != null && viewModel.game?.zone!!.style == "link") {
context.startActivity(
WebActivity.getIntent(
context,
viewModel.game?.zone!!.link,
true
)
)
}
}
TYPE_BBS -> {
val funcBbs = contentCardEntity.funcBbs
funcBbs?.let {
DirectUtils.directForumDetail(context, it.link, path)
}
}
TYPE_TOOLKIT -> {
if (contentCardEntity.toolkit.isNotEmpty()) {
contentCardEntity.toolkit.safelyGetInRelease(0)?.let {
val url = it.url
if (url != null && url.contains(Config.URL_ARTICLE)) {
val newsId = url.substring(url.lastIndexOf("/") + 1, url.length - 5) // 5: ".html"
val intent = NewsDetailActivity.getIntentById(context, newsId, path)
context.startActivity(intent)
} else {
context.startActivity(
WebActivity.getWebByCollectionTools(
context,
it,
false
)
)
}
}
}
}
else -> DirectUtils.directToLinkPage(
context,
contentCardEntity.link,
viewModel.entrance ?: "",
path,
ExposureEvent.createEvent(
null,
listOf(
ExposureSource("游戏详情", viewModel.game?.id ?: ""),
ExposureSource("内容卡片", contentCardEntity.id)
)
),
"游戏详情页-内容卡片"
)
}
}
}
}

View File

@ -0,0 +1,46 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import androidx.lifecycle.LifecycleOwner
import com.gh.gamecenter.databinding.ItemGameDetailContentCardTripleBinding
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.detail.viewholder.GameDetailContentCardDoubleItemViewHolder.Companion.bindContentCard
import com.gh.gamecenter.gamedetail.entity.GameDetailData
class GameDetailContentCardTripleItemViewHolder(
val binding: ItemGameDetailContentCardTripleBinding,
downloadBtn: DownloadButton,
viewModel: GameDetailViewModel,
private val lifecycleOwner: LifecycleOwner
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
data.linkContentCard?.let {
val getGameStatus = { gameStatus }
bindContentCard(
lifecycleOwner,
viewModel,
it.first(),
binding.firstCardContainer,
baseTrackData.apply { subSequence = 0 },
getGameStatus
)
bindContentCard(
lifecycleOwner,
viewModel,
it[1].apply { showDes = false },
binding.secondCardContainer,
baseTrackData.apply { subSequence = 1 },
getGameStatus
)
bindContentCard(
lifecycleOwner,
viewModel,
it[2].apply { showDes = false },
binding.thirdCardContainer,
baseTrackData.apply { subSequence = 2 },
getGameStatus
)
}
}
}

View File

@ -0,0 +1,143 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.content.Context
import androidx.lifecycle.LifecycleOwner
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.entity.PKEntity
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.toDrawable
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.databinding.ItemPkBinding
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.GameDetailData
class GameDetailContentRecommendItemViewHolder(
val binding: ItemPkBinding,
downloadBtn: DownloadButton,
viewModel: GameDetailViewModel,
private val lifecycleOwner: LifecycleOwner,
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
private var pkEntity: PKEntity? = null
override fun bindView(data: GameDetailData) {
super.bindView(data)
val link = data.linkContentRecommend ?: return
pkEntity = data.pkData ?: return
binding.run {
if (link.type == TYPE_PK) {
if (!pkView.isInitialized()) {
viewModel.pkVoteResultLiveData.observe(lifecycleOwner) {
if (it.peekContent().first == pkEntity?.id) {
val pair = it.getContentWithHandled()
if (pair != null && pkView.isInitialized()) {
pkView.vote(pair.second)
statisticsTv.text = "已有${pkEntity?.totalNum}人参与"
pkView.showResultWithAnimation()
}
}
}
} else {
pkView.updateView()
}
bindPKItem(context, this, link, pkEntity!!, onPositiveClickAction = positive@{
if (pkEntity == null) return@positive
SensorsBridge.trackPKClick(
GlobalActivityManager.getCurrentPageEntity().pageName,
GlobalActivityManager.getCurrentPageEntity().pageId,
pkEntity!!.id,
pkEntity!!.title,
pkEntity!!.option1.text,
pkEntity!!.link?.type ?: "",
pkEntity!!.link?.link ?: "",
pkEntity!!.link?.text ?: ""
)
viewModel.postPKVote(pkEntity!!.id, true)
}, onNegativeClickAction = negative@{
if (pkEntity == null) return@negative
SensorsBridge.trackPKClick(
GlobalActivityManager.getCurrentPageEntity().pageName,
GlobalActivityManager.getCurrentPageEntity().pageId,
pkEntity!!.id,
pkEntity!!.title,
pkEntity!!.option2.text,
pkEntity!!.link?.type ?: "",
pkEntity!!.link?.link ?: "",
pkEntity!!.link?.text ?: ""
)
viewModel.postPKVote(pkEntity!!.id, false)
}) {
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
pkEntity?.title,
sequence,
null,
null,
pkEntity?.link?.type,
pkEntity?.link?.link,
pkEntity?.link?.text,
gameStatus
)
}
}
}
}
companion object {
const val TYPE_PK = "pk"
const val TIME_PATTERN = "yyyy.MM.dd HH:mm:ss"
fun bindPKItem(
context: Context,
binding: ItemPkBinding,
link: LinkEntity,
pkEntity: PKEntity,
onPositiveClickAction: () -> Unit,
onNegativeClickAction: () -> Unit,
onLinkClickAction: () -> Unit
) {
binding.run {
container.background = R.drawable.bg_shape_f8_radius_8.toDrawable(context)
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
pkTitleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
endDateTv.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
statisticsTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
linkTv.background = com.gh.gamecenter.common.R.drawable.button_round_theme_alpha_10.toDrawable(context)
linkTv.setTextColor(com.gh.gamecenter.common.R.color.primary_theme.toColor(context))
titleTv.text = link.text
pkView.run {
setData(pkEntity)
onPositiveClick = onPositiveClickAction
onNegativeClick = onNegativeClickAction
}
pkTitleTv.text = pkEntity.title
val endDateText = if (pkEntity.isExpired) "" else ""
endDateTv.text = "${endDateText}${TimeUtils.getFormatTime(pkEntity.time.end, TIME_PATTERN)}结束"
statisticsTv.text = "已有${pkEntity.totalNum}人参与"
linkTv.goneIf(pkEntity.link == null) {
linkTv.setOnClickListener { _ ->
pkEntity.link?.let {
DirectUtils.directToLinkPage(
context,
it,
"内容推荐-PK",
""
)
}
onLinkClickAction.invoke()
}
}
}
}
}
}

View File

@ -0,0 +1,115 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.ItemGameDetailCustomColumnBinding
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.GameDetailCustomColumn.Companion.SHOW_DES_TYPE_ALL
import com.gh.gamecenter.gamedetail.entity.GameDetailCustomColumn.RightTopInfo.Companion.TYPE_BUTTON
import com.gh.gamecenter.gamedetail.entity.GameDetailCustomColumn.RightTopInfo.Companion.TYPE_TEXT
import com.gh.gamecenter.gamedetail.entity.GameDetailData
class GameDetailCustomColumnItemViewHolder(
val binding: ItemGameDetailCustomColumnBinding,
downloadBtn: DownloadButton,
viewModel: GameDetailViewModel
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val entity = data.linkCustomColumn ?: return
binding.run {
columnContainer.background = com.gh.gamecenter.common.R.drawable.bg_shape_f8_radius_8.toDrawable(context)
titleTv.goneIf(!entity.showName) {
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
titleTv.text = entity.name
}
imageIv.display(entity.img, true)
columnTitleContainer.goneIf(entity.titleInfo == null) {
gameIconIv.displayGameIcon(entity.titleInfo?.icon, entity.titleInfo?.iconSubscript, true, entity.titleInfo?.iconFloat)
columnTitleTv.goneIf(entity.titleInfo?.showTitle == false) {
columnTitleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
columnTitleTv.text = entity.titleInfo?.title
}
columnDesTv.text = entity.titleInfo?.text
columnDesTv.setTextColor(if (entity.titleInfo?.link == null) com.gh.gamecenter.common.R.color.text_tertiary.toColor(context) else com.gh.gamecenter.common.R.color.text_theme.toColor(context))
columnDesTv.setOnClickListener { _ ->
entity.titleInfo?.link?.let {
DirectUtils.directToLinkPage(context, it, "游戏详情-自定义栏目", "")
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
entity.name,
sequence,
null,
null,
it.type,
it.link,
it.text,
gameStatus
)
}
}
}
secondTitleContainer.goneIf(entity.secondTitleInfo == null) {
secondTitleIconIv.display(entity.secondTitleInfo?.icon, true)
secondTitleTv.goneIf(entity.secondTitleInfo?.showTitle == false) {
secondTitleTv.setTextColor(
entity.secondTitleInfo?.color?.hexStringToIntColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context)) ?: com.gh.gamecenter.common.R.color.text_primary.toColor(context)
)
secondTitleTv.text = entity.secondTitleInfo?.text
}
}
rightTopContainer.goneIf(entity.rightTopInfo == null) {
rightTopIconIv.goneIf(entity.rightTopInfo?.icon.isNullOrEmpty() || entity.rightTopInfo?.style == TYPE_BUTTON) {
ImageUtils.display(rightTopIconIv, entity.rightTopInfo?.icon)
}
rightTopButtonTv.goneIf(entity.rightTopInfo?.style == TYPE_TEXT) {
rightTopButtonTv.background = com.gh.gamecenter.common.R.drawable.download_button_normal_style.toDrawable(context)
rightTopButtonTv.text = entity.rightTopInfo?.text
}
rightTopTextTv.goneIf(entity.rightTopInfo?.style == TYPE_BUTTON) {
rightTopTextTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
rightTopTextTv.text = entity.rightTopInfo?.text
}
rightTopContainer.setOnClickListener {
entity.rightTopInfo?.link?.let {
DirectUtils.directToLinkPage(context, it, "游戏详情-自定义栏目", "")
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"右上角",
moduleType,
entity.name,
sequence,
null,
null,
it.type,
it.link,
it.text,
gameStatus
)
}
}
}
contentTv.run {
setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
shrinkLines = entity.showDesRowNum
isExpanded = viewModel.expandTextStatusSparseBooleanArray.get(data.position, entity.showDesType == SHOW_DES_TYPE_ALL)
setTextWithInterceptingInternalUrl(entity.tagDes)
onExpandCallback = {
viewModel.expandTextStatusSparseBooleanArray.put(data.position, true)
}
onShrinkCallback = {
viewModel.expandTextStatusSparseBooleanArray.put(data.position, false)
}
}
}
}
}

View File

@ -0,0 +1,54 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.text.Html
import androidx.core.view.isVisible
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.toDrawable
import com.gh.gamecenter.databinding.ItemGameDetailDeveloperWordBinding
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.dialog.GameDetailScrollableTextDialogFragment
import com.gh.gamecenter.gamedetail.entity.GameDetailData
class GameDetailDeveloperWordItemViewHolder(
val binding: ItemGameDetailDeveloperWordBinding,
downloadBtn: DownloadButton,
viewModel: GameDetailViewModel
) : com.gh.gamecenter.gamedetail.detail.viewholder.BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val entity = data.linkDeveloperWord ?: return
binding.run {
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
contentTv.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
titleTv.text = entity.name
contentTv.text = Html.fromHtml(entity.text)
contentTv.post {
expandTv.isVisible = contentTv.lineCount == 3 && contentTv.layout.getEllipsisCount(2) > 0
}
expandTv.background = R.drawable.bg_ui_surface_expand_gradient.toDrawable(context)
expandTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
expandTv.setOnClickListener {
GameDetailScrollableTextDialogFragment.show(
context,
viewModel.game?.id ?: "",
viewModel.game?.name ?: "",
entity.name,
entity.text
)
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
entity.name,
sequence,
gameStatus = gameStatus
)
}
}
}
}

View File

@ -0,0 +1,74 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.text.Html
import androidx.core.view.isVisible
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.ItemGameDetailDrawerBinding
import com.gh.gamecenter.databinding.SubItemGameDetailDrawerBinding
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.GameDetailData
import com.gh.gamecenter.gamedetail.entity.GameDetailLink
class GameDetailDrawerItemViewHolder(
val binding: ItemGameDetailDrawerBinding,
downloadBtn: DownloadButton,
viewModel: GameDetailViewModel
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val entity = data.linkDrawer ?: return
binding.drawer2Container.root.isVisible = entity.data.size > 1
binding.dividerContainer.isVisible = entity.data.size > 1
binding.dividerContainer.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_container_1.toColor(context))
binding.divider.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_divider.toColor(context))
binding.titleTv.goneIf(!entity.showName) {
binding.titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
binding.titleTv.text = entity.name
}
entity.data.getOrNull(0)?.let {
binding.drawer1Container.root.background = R.drawable.bg_game_detail_drawer_left_item_style.toDrawable(context)
bindSubView(binding.drawer1Container, it)
}
entity.data.getOrNull(1)?.let {
binding.drawer2Container.root.background = R.drawable.bg_game_detail_drawer_right_item_style.toDrawable(context)
bindSubView(binding.drawer2Container, it)
}
}
private fun bindSubView(subBinding: SubItemGameDetailDrawerBinding, data: GameDetailLink) {
subBinding.run {
iconIv.goneIf(data.componentIcon?.icon.isNullOrEmpty()) {
ImageUtils.display(iconIv, data.componentIcon?.icon)
}
contentTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
contentTv.text = Html.fromHtml(data.title)
arrowIv.goneIf(data.link == null) {
arrowIv.setImageResource(com.gh.gamecenter.common.R.drawable.ic_auxiliary_arrow_right_12)
}
root.setOnClickListener { _ ->
data.link?.let {
DirectUtils.directToLinkPage(context, it, "游戏详情-抽屉列表", "")
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
itemData?.linkDrawer?.name,
sequence,
null,
null,
it.type,
it.link,
it.text,
gameStatus
)
}
}
}
}
}

View File

@ -0,0 +1,74 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import com.gh.gamecenter.ShellActivity
import com.gh.gamecenter.ShellActivity.Type
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.ItemGameDetailRecyclerViewBinding
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.detail.GameLibaoAdapter
import com.gh.gamecenter.gamedetail.entity.GameDetailData
import com.gh.gamecenter.gamedetail.entity.GameDetailTabEntity
import com.gh.gamecenter.gamedetail.libao.LibaoListFragment
class GameDetailGiftItemViewHolder(
val binding: ItemGameDetailRecyclerViewBinding,
downloadBtn: DownloadButton,
viewModel: GameDetailViewModel
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val giftList = data.linkLibao ?: return
binding.run {
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
moreTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
moreTv.setDrawableEnd(com.gh.gamecenter.common.R.drawable.ic_auxiliary_arrow_right_12.toDrawable(context)?.apply {
colorFilter = PorterDuffColorFilter(com.gh.gamecenter.common.R.color.text_theme.toColor(context), PorterDuff.Mode.SRC_ATOP)
})
titleTv.text = "游戏礼包"
moreTv.goneIf(giftList.size <= 3) {
moreTv.text = "更多"
moreTv.setOnClickListener {
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"右上角",
moduleType,
"游戏礼包",
sequence,
gameStatus = gameStatus
)
val tabList = viewModel.gameDetailTabListLiveData.value?.data
if (tabList?.find { it.type == GameDetailTabEntity.TYPE_GIFT } != null) {
viewModel.performTabSelected(GameDetailTabEntity.TYPE_GIFT)
} else {
val bundle = LibaoListFragment.getBundle(
viewModel.game?.id ?: "",
viewModel.game?.name ?: "",
false
)
context.startActivity(ShellActivity.getIntent(context, Type.SIMPLE_LIBAO_LIST, bundle))
}
}
}
if (recyclerView.adapter !is GameLibaoAdapter) {
recyclerView.isNestedScrollingEnabled = false
(recyclerView.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
recyclerView.layoutManager = LinearLayoutManager(context)
recyclerView.adapter = GameLibaoAdapter(context, giftList, viewModel.game?.name ?: "", viewModel.game?.id ?: "")
} else {
(recyclerView.adapter as GameLibaoAdapter).run {
libaos = giftList
notifyItemRangeChanged(0, itemCount)
}
}
}
}
}

View File

@ -0,0 +1,256 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.content.Context
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.text.Html
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.DefaultUrlHandler
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.WebActivity
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.databinding.ItemGameDetailInfoBinding
import com.gh.gamecenter.databinding.SubItemGameDetailInfoBinding
import com.gh.gamecenter.feature.entity.GameInfo
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.dialog.GameInfoDialogFragment
import com.gh.gamecenter.gamedetail.dialog.GamePermissionDialogFragment
import com.gh.gamecenter.gamedetail.entity.GameDetailData
import com.gh.gamecenter.gamedetail.entity.GameDetailInfo
class GameDetailInfoItemViewHolder(
val binding: ItemGameDetailInfoBinding,
downloadBtn: DownloadButton,
viewModel: GameDetailViewModel
) :
BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val entity = data.linkInfo ?: return
binding.run {
container.background = R.drawable.bg_shape_f8_radius_8.toDrawable(context)
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
moreTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
moreTv.setDrawableEnd(com.gh.gamecenter.common.R.drawable.ic_auxiliary_arrow_right_12.toDrawable(context)?.apply {
colorFilter = PorterDuffColorFilter(com.gh.gamecenter.common.R.color.text_theme.toColor(context), PorterDuff.Mode.SRC_ATOP)
})
titleTv.text = entity.name
divider1.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_divider.toColor(context))
bindStaticInfo(context, entity, viewModel, privacyPolicyTv, permissionsTv, requestUpdateTv) {
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
entity.name,
sequence,
gameStatus = gameStatus
)
}
divider2.goneIf(entity.des.isEmpty()) {
divider2.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_divider.toColor(context))
}
desTv.goneIf(entity.des.isEmpty()) {
desTv.text = Html.fromHtml(entity.des)
}
moreTv.setOnClickListener {
GameInfoDialogFragment.show(context, viewModel.game, entity)
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"右上角",
moduleType,
entity.name,
sequence,
gameStatus = gameStatus
)
}
if (gameInfoRv.adapter !is InfoItemAdapter) {
val normalTypeKeys = normalTypeNameMap.keys
val showFirstList = entity.fields.filter { normalTypeKeys.contains(it.name) && it.isFirst }.sortedBy { it.order }
gameInfoRv.isNestedScrollingEnabled = false
(gameInfoRv.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
gameInfoRv.layoutManager = LinearLayoutManager(context)
gameInfoRv.adapter = InfoItemAdapter(showFirstList)
} else {
gameInfoRv.adapter?.run { notifyItemRangeChanged(0, itemCount) }
}
}
}
inner class InfoItemAdapter(val dataList: List<GameDetailInfo.InfoItem>) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
InfoItemViewHolder(parent.toBinding()) {
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
itemData?.linkInfo?.name,
sequence,
gameStatus = gameStatus
)
}
override fun getItemCount(): Int = dataList.size
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val data = dataList.getOrNull(position) ?: return
if (holder is InfoItemViewHolder) {
holder.bindInfoItem(context, data)
}
}
}
class InfoItemViewHolder(val binding: SubItemGameDetailInfoBinding, val onClickAction: (() -> Unit)? = null) : RecyclerView.ViewHolder(binding.root) {
fun bindInfoItem(context: Context, data: GameDetailInfo.InfoItem) {
binding.run {
nameTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
infoTv.setTextColor(if (data.name == TYPE_ICP) com.gh.gamecenter.common.R.color.text_theme.toColor(context) else com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
nameTv.text = normalTypeNameMap[data.name]
infoTv.text = when (data.name) {
TYPE_INTERNET -> if (data.value == "yes") "" else ""
TYPE_UPDATE_TIME -> data.value.toLongOrNull()?.let { TimeUtils.getFormatTime(it) }
else -> data.value
}
infoTv.setOnClickListener {
when (data.name) {
TYPE_ICP -> {
DirectUtils.directToExternalBrowser(context, context.getString(com.gh.gamecenter.common.R.string.icp_url))
}
}
onClickAction?.invoke()
}
}
}
}
companion object {
const val TYPE_PRIVACY_POLICY = "privacy_policy_url"
const val TYPE_PERMISSIONS = "permissions"
const val TYPE_REQUEST_UPDATE = "request_update_status"
const val TYPE_INTERNET = "internet_app"
const val TYPE_ICP = "ICP"
const val TYPE_DEVELOPER = "developer"
const val TYPE_PUBLISHER = "publisher"
const val TYPE_MANUFACTURER = "manufacturer"
const val TYPE_VERSION = "version"
const val TYPE_UPDATE_TIME = "update_time"
const val TYPE_RECOMMEND_AGE = "recommend_age"
const val TYPE_CREDIT_CODE = "credit_code"
const val TYPE_SIZE = "size"
// 固定信息类型
val staticTypeNameMap = mapOf(
TYPE_PRIVACY_POLICY to "隐私政策",
TYPE_PERMISSIONS to "权限及用途",
TYPE_REQUEST_UPDATE to "版本求更新"
)
val normalTypeNameMap = mapOf(
TYPE_INTERNET to "联网App",
TYPE_ICP to "ICP备案号",
TYPE_DEVELOPER to "开发者",
TYPE_PUBLISHER to "发行商",
TYPE_MANUFACTURER to "厂商",
TYPE_VERSION to "当前版本",
TYPE_UPDATE_TIME to "更新时间",
TYPE_RECOMMEND_AGE to "适龄等级",
TYPE_CREDIT_CODE to "统一社会信用代码",
TYPE_SIZE to "游戏大小"
)
fun bindStaticInfo(
context: Context,
entity: GameDetailInfo,
viewModel: GameDetailViewModel,
privacyPolicyTv: TextView,
permissionsTv: TextView,
requestUpdateTv: TextView,
onClickAction: (() -> Unit)? = null
) {
val privacyPolicyInfo = entity.fields.find { it.name == TYPE_PRIVACY_POLICY }
privacyPolicyTv.goneIf(privacyPolicyInfo == null) {
privacyPolicyTv.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
privacyPolicyTv.setDrawableEnd(R.drawable.ic_auxiliary_arrow_right_8.toDrawable(context)?.apply {
colorFilter = PorterDuffColorFilter(com.gh.gamecenter.common.R.color.text_secondary.toColor(context), PorterDuff.Mode.SRC_ATOP)
})
privacyPolicyTv.setOnClickListener {
privacyPolicyInfo?.value?.let {
if (!DefaultUrlHandler.transformNormalScheme(context, it, "隐私政策", "")) {
val intent = WebActivity.getIntent(context, it, "隐私政策", false, false)
context.startActivity(intent)
}
}
onClickAction?.invoke()
}
}
val permissionsInfo = entity.fields.find { it.name == TYPE_PERMISSIONS }
permissionsTv.goneIf(permissionsInfo == null) {
permissionsTv.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
permissionsTv.setDrawableEnd(R.drawable.ic_auxiliary_arrow_right_8.toDrawable(context)?.apply {
colorFilter = PorterDuffColorFilter(com.gh.gamecenter.common.R.color.text_secondary.toColor(context), PorterDuff.Mode.SRC_ATOP)
})
permissionsTv.setOnClickListener {
var version = ""
var manufacturerType = ""
var manufacturer = ""
var privacyPolicyUrl = ""
entity.fields.forEach {
when (it.name) {
TYPE_DEVELOPER, TYPE_PUBLISHER, TYPE_MANUFACTURER -> {
manufacturerType = it.name
manufacturer = it.value
}
TYPE_VERSION -> version = it.value
TYPE_PRIVACY_POLICY -> privacyPolicyUrl = it.value
}
}
val gameInfo = GameInfo(
version = version,
manufacturerType = manufacturerType,
manufacturer = manufacturer,
privacyPolicyUrl = privacyPolicyUrl,
permissions = permissionsInfo?.permissions
)
GamePermissionDialogFragment.show(context as AppCompatActivity, viewModel.game, gameInfo)
onClickAction?.invoke()
}
}
val requestUpdateInfo = entity.fields.find { it.name == TYPE_REQUEST_UPDATE }
requestUpdateTv.goneIf(requestUpdateInfo == null || requestUpdateInfo.value == "off") {
requestUpdateTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
requestUpdateTv.setDrawableEnd(R.drawable.ic_auxiliary_arrow_right_8.toDrawable(context)?.apply {
colorFilter = PorterDuffColorFilter(com.gh.gamecenter.common.R.color.text_theme.toColor(context), PorterDuff.Mode.SRC_ATOP)
})
requestUpdateTv.setOnClickListener {
DialogHelper.showDialog(
context, "版本求更新", "如果游戏上线了新版本,您可以提交申请,让小助手尽快更新版本喔!",
"提交申请", "取消", {
viewModel.sendSuggestion()
}, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)
)
onClickAction?.invoke()
}
}
}
}
}

View File

@ -0,0 +1,151 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.exposure.ExposureManager
import com.gh.common.util.DirectUtils
import com.gh.common.util.NewFlatLogUtils
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.divider.VerticalDividerItemDecoration
import com.gh.gamecenter.databinding.ItemGameDetailRecyclerViewBinding
import com.gh.gamecenter.entity.SubjectEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.game.horizontal.GameHorizontalAdapter
import com.gh.gamecenter.game.horizontal.GameHorizontalListType
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.GameDetailData
class GameDetailRecommendColumnItemViewHolder(
val binding: ItemGameDetailRecyclerViewBinding,
downloadBtn: DownloadButton,
viewModel: GameDetailViewModel
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val columnData = data.recommendColumn ?: return
binding.run {
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
moreTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
moreTv.setDrawableEnd(com.gh.gamecenter.common.R.drawable.ic_auxiliary_arrow_right_12.toDrawable(context)?.apply {
colorFilter = PorterDuffColorFilter(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context), PorterDuff.Mode.SRC_ATOP)
})
titleTv.text = columnData.columnTitle
moreTv.isEnabled = true
headPb.isVisible = false
moreTv.goneIf(columnData.displayHome.isEmpty()) {
moreTv.run {
text = columnData.displayHome
setDebouncedClickListener {
NewFlatLogUtils.logGameDetailColumnRecommendUpperRightClick(
viewModel.game?.name ?: "",
viewModel.game?.id ?: "",
columnData.displayHome,
columnData.columnTitle,
columnData.columnId,
columnData.moreLink?.text ?: "",
columnData.moreLink?.type ?: "",
columnData.moreLink?.link ?: ""
)
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"右上角",
moduleType,
columnData.columnTitle,
sequence,
gameStatus = gameStatus
)
SensorsBridge.trackColumnClick(
location = "游戏详情",
gameName = gameName,
gameId = gameId,
gameColumnName = columnData.columnTitle,
gameColumnId = columnData.columnId,
linkText = columnData.moreLink?.text ?: "",
linkType = columnData.moreLink?.type ?: "",
linkId = columnData.moreLink?.link ?: "",
text = "右上角",
buttonType = columnData.displayHome
)
if (columnData.displayHome == "换一批") {
headPb.isVisible = true
moreTv.isEnabled = false
viewModel.changeSubjectGame(columnData.columnId, columnData.gameCount) {
headPb.isVisible = false
moreTv.isEnabled = true
}
} else {
columnData.moreLink?.let {
DirectUtils.directToLinkPage(
context,
it,
viewModel.entrance ?: "",
"游戏详情[${viewModel.game?.name ?: ""}]:专题游戏单推荐"
)
}
}
}
}
}
if (recyclerView.adapter !is GameHorizontalAdapter) {
recyclerView.run {
isNestedScrollingEnabled = false
(itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
val subjectEntity = SubjectEntity().apply {
id = columnData.columnId
name = columnData.columnTitle
this.data = columnData.columnGames
}
val exposureEventList = arrayListOf<ExposureEvent>()
for ((index, game) in subjectEntity.data!!.withIndex()) {
game.sequence = index
val event = ExposureEvent.createEvent(
gameEntity = game,
source = listOf(
ExposureSource("游戏详情", viewModel.game?.name ?: ""),
ExposureSource("专题推荐", columnData.columnTitle)
)
)
exposureEventList.add(event)
ExposureManager.log(event)
}
val subjectAdapter =
GameHorizontalAdapter(
context,
subjectEntity,
GameHorizontalListType.GameDetailHorizontalType,
gameDetailTrackData = baseTrackData.apply {
moduleName = columnData.columnTitle
},
getGameStatus = { gameStatus })
subjectAdapter.gameName = viewModel.game?.name ?: ""
subjectAdapter.game = viewModel.game
subjectAdapter.gameId = viewModel.game?.id ?: ""
subjectAdapter.entrance = viewModel.entrance ?: ""
subjectAdapter.path = "专题游戏单推荐"
subjectAdapter.exposureEventList = exposureEventList
(itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
val itemDecoration = VerticalDividerItemDecoration.Builder(context)
.size(8F.dip2px())
.color(com.gh.gamecenter.common.R.color.transparent.toColor(context))
.build()
addItemDecoration(itemDecoration)
adapter = subjectAdapter
}
} else {
recyclerView.adapter?.run {
notifyItemRangeChanged(0, itemCount)
}
}
}
}
}

View File

@ -0,0 +1,75 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.DirectUtils
import com.gh.common.util.NewFlatLogUtils
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.setDrawableEnd
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.toDrawable
import com.gh.gamecenter.databinding.ItemGameDetailRecyclerViewBinding
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.detail.GameCollectionAdapter
import com.gh.gamecenter.gamedetail.entity.GameDetailData
class GameDetailRecommendGameCollectionItemViewHolder(
val binding: ItemGameDetailRecyclerViewBinding,
downloadBtn: DownloadButton,
viewModel: GameDetailViewModel
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val gameCollectionList = data.linkRecommendGameList ?: return
binding.run {
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
moreTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
moreTv.setDrawableEnd(com.gh.gamecenter.common.R.drawable.ic_auxiliary_arrow_right_12.toDrawable(context)?.apply {
colorFilter = PorterDuffColorFilter(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context), PorterDuff.Mode.SRC_ATOP)
})
titleTv.text = "游戏单推荐"
moreTv.isVisible = true
moreTv.text = "游戏单广场"
moreTv.setOnClickListener {
NewFlatLogUtils.logGameDetailGameListRecommendSquareJump(viewModel.game?.name ?: "", viewModel.game?.id ?: "")
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"右上角",
moduleType,
"游戏单推荐",
sequence,
gameStatus = gameStatus
)
DirectUtils.directToGameCollectionSquare(it.context, viewModel.entrance ?: "")
}
if (recyclerView.adapter !is GameCollectionAdapter) {
recyclerView.run {
isNestedScrollingEnabled = false
(itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
adapter = GameCollectionAdapter(
gameCollectionList,
viewModel.game?.id ?: "",
viewModel.game?.name ?: "",
viewModel.game,
viewModel.entrance ?: "",
"游戏详情[${viewModel.game?.name ?: ""}]:游戏单推荐",
listOf(ExposureSource("游戏详情", "${viewModel.game?.name ?: ""}+${viewModel.game?.id ?: ""}")), baseTrackData) { gameStatus }
}
} else {
recyclerView.adapter?.run {
notifyItemRangeChanged(0, itemCount)
}
}
}
}
}

View File

@ -0,0 +1,91 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.exposure.ExposureManager
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.setDrawableEnd
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.toDrawable
import com.gh.gamecenter.common.view.divider.VerticalDividerItemDecoration
import com.gh.gamecenter.databinding.ItemGameDetailRecyclerViewBinding
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.game.horizontal.GameHorizontalAdapter
import com.gh.gamecenter.game.horizontal.GameHorizontalListType
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.GameDetailData
class GameDetailRecommendGameItemViewHolder(
val binding: ItemGameDetailRecyclerViewBinding,
downloadBtn: DownloadButton,
viewModel: GameDetailViewModel
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val entity = data.linkEveryonePlaying ?: return
binding.run {
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
moreTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
moreTv.setDrawableEnd(com.gh.gamecenter.common.R.drawable.ic_auxiliary_arrow_right_12.toDrawable(context)?.apply {
colorFilter = PorterDuffColorFilter(com.gh.gamecenter.common.R.color.text_theme.toColor(context), PorterDuff.Mode.SRC_ATOP)
})
titleTv.text = "大家都在玩"
moreTv.isVisible = false
val subjectEntity = entity.recommendedGames ?: return
if (recyclerView.adapter !is GameHorizontalAdapter) {
recyclerView.run {
isNestedScrollingEnabled = false
(itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
val exposureEventList = arrayListOf<ExposureEvent>()
val itemDecoration = VerticalDividerItemDecoration.Builder(context)
.size(8F.dip2px())
.color(com.gh.gamecenter.common.R.color.transparent.toColor(context))
.build()
for ((index, game) in subjectEntity.data!!.withIndex()) {
game.sequence = index
val event = ExposureEvent.createEvent(
gameEntity = game,
source = listOf(
ExposureSource("游戏详情", viewModel.game?.name ?: ""),
ExposureSource("大家都在玩", game.recommendType)
)
)
exposureEventList.add(event)
ExposureManager.log(event)
}
layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
val subjectAdapter =
GameHorizontalAdapter(
context,
subjectEntity,
GameHorizontalListType.GameDetailHorizontalType,
false,
baseTrackData.apply { moduleName = "大家都在玩" }
) { gameStatus }
subjectAdapter.gameName = viewModel.game?.name ?: ""
subjectAdapter.game = viewModel.game
subjectAdapter.gameId = viewModel.game?.id ?: ""
subjectAdapter.entrance = viewModel.entrance ?: ""
subjectAdapter.path = "大家都在玩"
subjectAdapter.exposureEventList = exposureEventList
(itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
addItemDecoration(itemDecoration)
adapter = subjectAdapter
}
} else {
(recyclerView.adapter as GameHorizontalAdapter).run {
checkResetData(subjectEntity)
}
}
}
}
}

View File

@ -0,0 +1,51 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.display
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.databinding.ItemGameDetailRecommendImageBinding
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.GameDetailData
class GameDetailRecommendImageItemViewHolder(
val binding: ItemGameDetailRecommendImageBinding,
downloadBtn: DownloadButton,
viewModel: GameDetailViewModel
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val entity = data.linkImageRecommend ?: return
binding.run {
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
titleTv.text = entity.title
imageIv.display(entity.img)
root.setOnClickListener { _ ->
entity.link?.let {
DirectUtils.directToLinkPage(
context = context,
linkEntity = it,
entrance = viewModel.entrance ?: "",
path = "游戏详情"
)
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
entity.title,
sequence,
null,
null,
it.type,
it.link,
it.link,
gameStatus
)
}
}
}
}
}

View File

@ -0,0 +1,60 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.utils.setDrawableEnd
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.toDrawable
import com.gh.gamecenter.databinding.ItemGameDetailRecyclerViewBinding
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.game.vertical.SpanCountPagerSnapHelper
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.detail.GameDetailRelatedGameAdapter
import com.gh.gamecenter.gamedetail.entity.GameDetailData
class GameDetailRelatedGameItemViewHolder(
val binding: ItemGameDetailRecyclerViewBinding,
downloadBtn: DownloadButton,
viewModel: GameDetailViewModel
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val gameList = data.linkRelatedGame ?: return
binding.run {
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
moreTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
moreTv.setDrawableEnd(com.gh.gamecenter.common.R.drawable.ic_auxiliary_arrow_right_12.toDrawable(context)?.apply {
colorFilter = PorterDuffColorFilter(com.gh.gamecenter.common.R.color.text_theme.toColor(context), PorterDuff.Mode.SRC_ATOP)
})
titleTv.text = "相关游戏"
moreTv.isVisible = false
if (recyclerView.adapter !is GameDetailRelatedGameAdapter) {
recyclerView.run {
isNestedScrollingEnabled = false
(itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
if (gameList.size > 3) {
layoutManager = GridLayoutManager(context, 3, RecyclerView.HORIZONTAL, false)
clearOnScrollListeners()
(itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
onFlingListener = null
val snapHelper = SpanCountPagerSnapHelper(3, true)
snapHelper.attachToRecyclerView(this)
} else {
layoutManager = LinearLayoutManager(context)
}
adapter = GameDetailRelatedGameAdapter(context, viewModel.game, gameList, viewModel.entrance ?: "", baseTrackData) { gameStatus }
}
} else {
recyclerView.adapter?.run {
notifyItemRangeChanged(0, itemCount)
}
}
}
}
}

View File

@ -0,0 +1,186 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.view.Gravity
import android.view.View.MeasureSpec
import android.view.ViewGroup
import android.view.ViewTreeObserver
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.databinding.ItemGameDetailMoreBinding
import com.gh.gamecenter.databinding.ItemGameDetailRecyclerViewBinding
import com.gh.gamecenter.databinding.ItemGameDetailServerBinding
import com.gh.gamecenter.feature.entity.ServerCalendarEntity
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.detail.GameLatestServiceAdapter
import com.gh.gamecenter.gamedetail.entity.GameDetailData
import com.gh.gamecenter.gamedetail.fuli.kaifu.ServersCalendarActivity
class GameDetailServerItemViewHolder(
val binding: ItemGameDetailRecyclerViewBinding,
downloadBtn: DownloadButton,
viewModel: GameDetailViewModel
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val entity = data.linkServer ?: return
binding.run {
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
moreTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
moreTv.setDrawableEnd(com.gh.gamecenter.common.R.drawable.ic_auxiliary_arrow_right_12.toDrawable(context)?.apply {
colorFilter = PorterDuffColorFilter(com.gh.gamecenter.common.R.color.text_theme.toColor(context), PorterDuff.Mode.SRC_ATOP)
})
titleTv.text = "游戏开服"
moreTv.goneIf(entity.calendar.isEmpty()) {
moreTv.text = "更多"
moreTv.setOnClickListener {
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"右上角",
moduleType,
"游戏开服",
sequence,
gameStatus = gameStatus
)
val intent = ServersCalendarActivity.getIntent(
context,
viewModel.game!!,
entity,
null
)
context.startActivity(intent)
viewModel.game?.let {
NewLogUtils.logGameDetailOpenListClick(it.name ?: "", it.id, "更多")
}
}
}
if (entity.calendar.isNotEmpty()) {
if (recyclerView.adapter !is ServerItemAdapter) {
recyclerView.isNestedScrollingEnabled = false
(recyclerView.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
recyclerView.layoutManager = LinearLayoutManager(context)
recyclerView.adapter = ServerItemAdapter(entity.calendar)
} else {
recyclerView.adapter?.run { notifyItemRangeChanged(0, itemCount) }
}
}
}
}
inner class ServerItemAdapter(val dataList: ArrayList<ServerCalendarEntity>) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val showItemCount: Int = 3
private val maxItemCount: Int = 10
private var isExpand = false
private var expandEntities = arrayListOf<ServerCalendarEntity>()
private var shrinkEntities = arrayListOf<ServerCalendarEntity>()
init {
expandEntities.clear()
expandEntities.addAll(dataList.take(maxItemCount))
//举例当前时间为9点
//全部开服 展示的3条
//7、8、9、10 8、9、10
//7、8、9、10、11、12、13 10、11、12
//6、7、8、9 7、8、9
//获取未来最靠近当前时间的3条
shrinkEntities.clear()
val currentHour = TimeUtils.getCurrentHour()
dataList.forEach {
val hour = TimeUtils.getFormatTime(it.getTime(), "HH").toInt()
if (hour > currentHour && shrinkEntities.size < showItemCount) {
shrinkEntities.add(it)
}
}
//判断不足3条向前补齐
if (shrinkEntities.size < showItemCount) {
if (shrinkEntities.isEmpty()) {
shrinkEntities.addAll(dataList.takeLast(showItemCount))
} else {
val firstIndex = dataList.indexOf(shrinkEntities[0])
for (index in dataList.size - 1 downTo 0) {
if (index < firstIndex && shrinkEntities.size < showItemCount) {
shrinkEntities.add(0, dataList[index])
}
}
}
}
}
override fun getItemViewType(position: Int): Int {
return if (expandEntities.size > showItemCount) {
if (position == itemCount - 1) GameLatestServiceAdapter.MORE else GameLatestServiceAdapter.SERVICE_ITEM
} else {
GameLatestServiceAdapter.SERVICE_ITEM
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
if (viewType == ITEM_SERVER) ServerItemViewHolder(parent.toBinding()) else MoreViewHolder(parent.toBinding())
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is ServerItemViewHolder) {
val entity = if (expandEntities.size > showItemCount) {
if (isExpand) expandEntities.safelyGetInRelease(position) else
shrinkEntities.safelyGetInRelease(position)
} else {
expandEntities.safelyGetInRelease(position)
} ?: return
holder.binding.timeTv.setDrawableStart(R.drawable.ic_game_detail_server)
holder.binding.timeTv.text = TimeUtils.getFormatDate(entity.getTime())
holder.binding.serviceNameTv.text = "${entity.getNote()} ${entity.remark}"
holder.binding.serviceNameTv.viewTreeObserver.addOnGlobalLayoutListener(object :
ViewTreeObserver.OnGlobalLayoutListener {
override fun onGlobalLayout() {
holder.binding.serviceNameTv.viewTreeObserver.removeOnGlobalLayoutListener(this)
holder.binding.serviceNameTv.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
val measuredWidth = holder.binding.serviceNameTv.measuredWidth
val width = holder.binding.serviceNameTv.width
if (measuredWidth > width) {
holder.binding.serviceNameTv.isSelected = true
holder.binding.serviceNameTv.gravity = Gravity.LEFT
} else {
holder.binding.serviceNameTv.gravity = Gravity.RIGHT
}
}
})
}
if (holder is MoreViewHolder) {
holder.binding.arrowIv.rotation = if (isExpand) 180f else 0f
holder.itemView.setOnClickListener {
isExpand = !isExpand
notifyDataSetChanged()
viewModel.game?.let {
NewLogUtils.logGameDetailOpenListClick(it.name ?: "", it.id, "展开")
}
}
}
}
override fun getItemCount(): Int = if (expandEntities.size > showItemCount) {
if (isExpand) expandEntities.size + 1 else showItemCount + 1
} else expandEntities.size
}
inner class ServerItemViewHolder(val binding: ItemGameDetailServerBinding) :
RecyclerView.ViewHolder(binding.root)
inner class MoreViewHolder(var binding: ItemGameDetailMoreBinding) : RecyclerView.ViewHolder(binding.root)
companion object {
const val ITEM_MORE = 0
const val ITEM_SERVER = 1
}
}

View File

@ -0,0 +1,223 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.DataCollectionUtils
import com.gh.common.util.DirectUtils
import com.gh.common.util.NewsUtils
import com.gh.gamecenter.GameNewsActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.databinding.ItemGameDetailRecyclerViewBinding
import com.gh.gamecenter.databinding.ItemGameDetailStrategyBinding
import com.gh.gamecenter.databinding.ItemGameDetailStrategyFixedTopBinding
import com.gh.gamecenter.feature.entity.NewsEntity
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.ContentCardEntity
import com.gh.gamecenter.gamedetail.entity.GameDetailData
import com.gh.gamecenter.gamedetail.entity.GameDetailStrategy
import com.gh.gamecenter.gamedetail.entity.GameDetailTabEntity
import com.gh.gamecenter.newsdetail.NewsDetailActivity
class GameDetailStrategyItemViewHolder(
val binding: ItemGameDetailRecyclerViewBinding,
downloadBtn: DownloadButton,
viewModel: GameDetailViewModel
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val entity = data.linkGameGuide ?: return
binding.run {
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
moreTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
moreTv.setDrawableEnd(com.gh.gamecenter.common.R.drawable.ic_auxiliary_arrow_right_12.toDrawable(context)?.apply {
colorFilter = PorterDuffColorFilter(com.gh.gamecenter.common.R.color.text_theme.toColor(context), PorterDuff.Mode.SRC_ATOP)
})
titleTv.text = entity.name
if (entity.rightTop == "link") {
moreTv.text = "攻略专区"
moreTv.isVisible = true
moreTv.setOnClickListener {
val tabList = viewModel.gameDetailTabListLiveData.value?.data
if (tabList?.find { it.type == GameDetailTabEntity.TYPE_ZONE } != null) {
viewModel.performTabSelected(GameDetailTabEntity.TYPE_ZONE)
} else {
viewModel.performContentCardClicked(ContentCardEntity.TYPE_ZONE)
}
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"右上角",
moduleType,
entity.name,
sequence,
gameStatus = gameStatus
)
}
} else if (entity.rightTop == "more") {
moreTv.text = "更多"
moreTv.goneIf(entity.data.size < 3)
moreTv.setOnClickListener {
val intent = GameNewsActivity.getIntent(
context,
viewModel.game?.name,
viewModel.game?.id,
viewModel.entrance + "+(游戏详情[" + viewModel.game?.name + "]:新手攻略-全部)"
)
context.startActivity(intent)
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"右上角",
moduleType,
entity.name,
sequence,
gameStatus = gameStatus
)
}
}
if (recyclerView.adapter !is StrategyItemAdapter) {
(recyclerView.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
recyclerView.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
val dataList = if (entity.articleTop.isNotEmpty()) {
entity.articleTop.map { GameDetailStrategy.StrategyData(type = "article", linkArticle = it, isFixedTop = true) } + entity.data
} else {
entity.data
}
recyclerView.adapter = StrategyItemAdapter(dataList)
} else {
recyclerView.adapter?.run { notifyItemRangeChanged(0, itemCount) }
}
}
}
inner class StrategyItemAdapter(val dataList: List<GameDetailStrategy.StrategyData>) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun getItemViewType(position: Int): Int = if (dataList[position].isFixedTop) ITEM_FIXED_TOP else ITEM_NORMAL
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
if (viewType == ITEM_FIXED_TOP) FixedTopStrategyItemViewHolder(parent.toBinding()) else StrategyItemViewHolder(parent.toBinding())
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val strategyData = dataList.getOrNull(position) ?: return
val newsEntity = strategyData.linkArticle
val communityArticleEntity = strategyData.linkCommunityArticle
if (holder is StrategyItemViewHolder) {
holder.binding.run {
root.updateLayoutParams<MarginLayoutParams> {
leftMargin = if (position == 0) 16F.dip2px() else 8F.dip2px()
rightMargin = if (position == itemCount - 1) 16F.dip2px() else 0
}
root.background = R.drawable.bg_shape_f8_radius_8.toDrawable(context)
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
contentTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
timeTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
newsEntity?.let {
titleTv.text = it.title ?: ""
contentTv.text = it.intro ?: ""
timeTv.text =
if (TimeUtils.isToday(it.publishOn)) "今天" else TimeUtils.getFormatTime(it.publishOn)
root.setOnClickListener { _ ->
skipNewsDetail(it, position)
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
itemData?.linkGameGuide?.name,
sequence,
gameStatus = gameStatus
)
}
}
communityArticleEntity?.let {
titleTv.text = it.title
contentTv.text = it.content
timeTv.text =
if (TimeUtils.isToday(it.time?.create ?: 0L)) "今天" else TimeUtils.getFormatTime(it.time?.create ?: 0L)
root.setOnClickListener { _ ->
DirectUtils.directToCommunityArticle(context, it.community, it.id, viewModel.entrance, "游戏详情-游戏攻略")
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
itemData?.linkGameGuide?.name,
sequence,
gameStatus = gameStatus
)
}
}
}
}
if (holder is FixedTopStrategyItemViewHolder) {
holder.binding.run {
root.updateLayoutParams<MarginLayoutParams> {
leftMargin = if (position == 0) 16F.dip2px() else 8F.dip2px()
rightMargin = if (position == itemCount - 1) 16F.dip2px() else 0
}
root.background = R.drawable.bg_shape_f8_radius_8.toDrawable(context)
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
descTv.setTextColor(com.gh.gamecenter.common.R.color.text_tertiary.toColor(context))
newsEntity?.let {
ImageUtils.display(backgroundIv, it.thumb)
titleTv.text = it.title ?: ""
descTv.text = it.type ?: ""
root.setOnClickListener { _ ->
skipNewsDetail(it, position)
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
itemData?.linkGameGuide?.name,
sequence,
gameStatus = gameStatus
)
}
}
}
}
}
override fun getItemCount(): Int = dataList.size
private fun skipNewsDetail(article: NewsEntity, position: Int) {
DataCollectionUtils.uploadClick(context, "新手攻略", "游戏详情", article.title)
// 统计阅读量
NewsUtils.statNewsViews(article.id)
NewsDetailActivity.startNewsDetailActivity(
context, article,
viewModel.entrance + "+(游戏详情[" + viewModel.game?.name + "]:新手攻略-列表[" + (position + 1) + "])"
)
}
}
inner class StrategyItemViewHolder(val binding: ItemGameDetailStrategyBinding) :
RecyclerView.ViewHolder(binding.root)
inner class FixedTopStrategyItemViewHolder(val binding: ItemGameDetailStrategyFixedTopBinding) :
RecyclerView.ViewHolder(binding.root)
companion object {
const val ITEM_FIXED_TOP = 0
const val ITEM_NORMAL = 1
}
}

View File

@ -0,0 +1,83 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.text.Html
import androidx.core.view.isVisible
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.setDrawableEnd
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.toDrawable
import com.gh.gamecenter.databinding.ItemGameDetailUpdateBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.dialog.GameDetailScrollableTextDialogFragment
import com.gh.gamecenter.gamedetail.entity.GameDetailData
import com.gh.gamecenter.gamedetail.history.HistoryApkListActivity
class GameDetailUpdateItemViewHolder(
val binding: ItemGameDetailUpdateBinding,
downloadBtn: DownloadButton,
viewModel: GameDetailViewModel
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val entity = data.linkUpdate ?: return
binding.run {
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
contentTv.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(context))
historyTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
historyTv.setDrawableEnd(com.gh.gamecenter.common.R.drawable.ic_auxiliary_arrow_right_12.toDrawable(context)?.apply {
colorFilter = PorterDuffColorFilter(com.gh.gamecenter.common.R.color.text_theme.toColor(context), PorterDuff.Mode.SRC_ATOP)
})
historyTv.isVisible = entity.historyApkStatus == "on" && entity.historyApkCount >= 1
titleTv.text = entity.name
contentTv.text = Html.fromHtml(entity.updateDes)
contentTv.post {
expandTv.isVisible = contentTv.lineCount == 3 && contentTv.layout.getEllipsisCount(2) > 0
}
expandTv.background = R.drawable.bg_ui_surface_expand_gradient.toDrawable(context)
expandTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
expandTv.setOnClickListener {
GameDetailScrollableTextDialogFragment.show(
context,
viewModel.game?.id ?: "",
viewModel.game?.name ?: "",
entity.name,
entity.updateDes
)
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
entity.name,
sequence,
gameStatus = gameStatus
)
}
historyTv.setOnClickListener {
context.startActivity(
HistoryApkListActivity.getIntent(
context,
viewModel.game ?: GameEntity(),
viewModel.entrance ?: "",
"游戏详情-更新内容"
))
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"右上角",
moduleType,
entity.name,
sequence,
gameStatus = gameStatus
)
}
}
}
}

View File

@ -0,0 +1,111 @@
package com.gh.gamecenter.gamedetail.detail.viewholder
import android.graphics.PorterDuff
import android.graphics.PorterDuffColorFilter
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.DirectUtils
import com.gh.common.util.NewFlatLogUtils
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.ItemGameDetailRecyclerViewBinding
import com.gh.gamecenter.databinding.ItemGameDetailVideoBinding
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.GameDetailData
import com.gh.gamecenter.gamedetail.entity.GameDetailVideo
import com.gh.gamecenter.video.detail.VideoDetailContainerViewModel
class GameDetailVideoItemViewHolder(
val binding: ItemGameDetailRecyclerViewBinding,
downloadBtn: DownloadButton,
viewModel: GameDetailViewModel
) : BaseGameDetailItemViewHolder(binding.root, downloadBtn, viewModel) {
override fun bindView(data: GameDetailData) {
super.bindView(data)
val entity = data.linkGameVideo ?: return
binding.run {
titleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
moreTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
moreTv.setDrawableEnd(com.gh.gamecenter.common.R.drawable.ic_auxiliary_arrow_right_12.toDrawable(context)?.apply {
colorFilter = PorterDuffColorFilter(com.gh.gamecenter.common.R.color.text_theme.toColor(context), PorterDuff.Mode.SRC_ATOP)
})
titleTv.text = "游戏视频"
moreTv.goneIf(entity.count < 3) {
moreTv.text = "更多"
moreTv.setOnClickListener {
DirectUtils.directToGameVideo(context, viewModel.gameId ?: "", viewModel.entrance, "游戏详情")
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"右上角",
moduleType,
"游戏视频",
sequence,
gameStatus = gameStatus
)
}
}
if (recyclerView.adapter !is VideoItemAdapter) {
(recyclerView.itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
recyclerView.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
recyclerView.adapter = VideoItemAdapter(entity.video)
} else {
recyclerView.adapter?.run { notifyItemRangeChanged(0, itemCount) }
}
}
}
inner class VideoItemAdapter(val dataList: List<GameDetailVideo.Video>) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder =
VideoItemViewHolder(parent.toBinding())
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val video = dataList.getOrNull(position) ?: return
if (holder is VideoItemViewHolder) {
holder.binding.run {
root.updateLayoutParams<MarginLayoutParams> {
leftMargin = if (position == 0) 16F.dip2px() else 8F.dip2px()
rightMargin = if (position == itemCount - 1) 16F.dip2px() else 0
}
videoTitleTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(context))
videoTitleTv.text = video.title
ImageUtils.display(videoIv, video.poster)
root.setOnClickListener {
NewFlatLogUtils.logClickGameDetailVideoCategory(
"video",
video.id,
video.user?.id ?: ""
)
DirectUtils.directToVideoDetail(
context, video.id, VideoDetailContainerViewModel.Location.GAME_DETAIL.value,
false, viewModel.game?.id ?: "", viewModel.entrance, "游戏详情"
)
SensorsBridge.trackGameDetailModuleClick(
gameId,
gameName,
gameType,
"组件内容",
moduleType,
"游戏视频",
sequence,
gameStatus = gameStatus
)
}
}
}
}
override fun getItemCount(): Int = dataList.size
}
inner class VideoItemViewHolder(val binding: ItemGameDetailVideoBinding) :
RecyclerView.ViewHolder(binding.root)
}

View File

@ -8,37 +8,51 @@ import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.view.View
import android.view.ViewGroup
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.databinding.ItemGameDetailBigEventBinding
import com.gh.gamecenter.gamedetail.entity.BigEvent
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.viewholder.FooterViewHolder
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.databinding.ItemNewGameDetailBigEventBinding
import com.gh.gamecenter.feature.entity.BigEvent
import com.lightgame.adapter.BaseRecyclerAdapter
class GameBigEventAdapter(
val context: Context,
val bigEvents: List<BigEvent>,
context: Context,
private val bigEvents: List<BigEvent>,
val onLinkClick: (LinkEntity, Int) -> Unit
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return GameBigEventViewHolder(parent.toBinding())
) : BaseRecyclerAdapter<RecyclerView.ViewHolder>(context) {
override fun getItemViewType(position: Int): Int {
return if (position == itemCount - 1) {
ITEM_FOOTER
} else {
ITEM_CONTENT
}
}
override fun getItemCount(): Int = bigEvents.size
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == ITEM_CONTENT) {
NewGameBigEventViewHolder(parent.toBinding())
} else {
FooterViewHolder(mLayoutInflater.inflate(com.gh.gamecenter.common.R.layout.refresh_footerview, parent, false))
}
}
override fun getItemCount(): Int = if (bigEvents.isNotEmpty()) bigEvents.size + 1 else 0
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
val bigEvent = bigEvents[position]
if (holder is GameBigEventViewHolder) {
holder.binding.timeTv.text = TimeUtils.getFormatTime(bigEvent.time)
if (bigEvent.link == null) {
holder.binding.contentTv.text = bigEvent.content
if (holder is NewGameBigEventViewHolder) {
val bigEvent = bigEvents.getOrNull(position)
holder.binding.timeTv.text = TimeUtils.getFormatTime(bigEvent?.time ?: 0L)
if (bigEvent?.link == null) {
holder.binding.contentTv.text = bigEvent?.content
} else {
val content = "${bigEvent.content} ${bigEvent.link?.value}"
val builder = SpannableStringBuilder(content)
builder.setSpan(object : ClickableSpan() {
override fun updateDrawState(ds: TextPaint) {
ds.color = ContextCompat.getColor(context, com.gh.gamecenter.common.R.color.text_theme)
ds.color = com.gh.gamecenter.common.R.color.text_theme.toColor(mContext)
ds.isUnderlineText = true
}
@ -50,7 +64,20 @@ class GameBigEventAdapter(
holder.binding.contentTv.text = builder
}
}
if (holder is FooterViewHolder) {
holder.run {
loading.visibility = View.GONE
itemView.isClickable = false
hint.setText(com.gh.gamecenter.common.R.string.load_over_hint)
}
}
}
class GameBigEventViewHolder(var binding: ItemGameDetailBigEventBinding) : RecyclerView.ViewHolder(binding.root)
class NewGameBigEventViewHolder(var binding: ItemNewGameDetailBigEventBinding) :
RecyclerView.ViewHolder(binding.root)
companion object {
private const val ITEM_CONTENT = 0
private const val ITEM_FOOTER = 1
}
}

View File

@ -1,96 +0,0 @@
package com.gh.gamecenter.gamedetail.dialog
import android.app.Dialog
import android.content.Context
import android.graphics.Color
import android.graphics.drawable.ColorDrawable
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.LayoutInflater
import android.view.Window
import android.widget.TextView
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.TrackableDialog
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.view.VerticalItemDecoration
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.gamedetail.entity.BigEvent
class GameBigEventDialog(
context: Context,
val gameName: String,
val gameId: String,
val bigEvents: List<BigEvent>,
val onLinkClick: (LinkEntity, Int) -> Unit,
mEvent: String,
mKey: String,
mValue: String,
) : TrackableDialog(context, com.gh.gamecenter.common.R.style.GhAlertDialog, mEvent, mKey, mValue) {
private val mDelayLogRunnable = Runnable {
NewLogUtils.logGameDetailMajorEventView(gameName, gameId)
}
override fun onStart() {
super.onStart()
if (window != null) {
window!!.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
val params = window!!.attributes
params.width = context.resources.displayMetrics.widthPixels - DisplayUtils.dip2px(40f)
window!!.attributes = params
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
requestWindowFeature(Window.FEATURE_NO_TITLE)
val contentView = LayoutInflater.from(context).inflate(R.layout.dialog_game_big_event, null)
setContentView(contentView)
val bigEventRv = contentView.findViewById<RecyclerView>(R.id.bigEventRv)
bigEventRv.adapter = GameBigEventAdapter(context, bigEvents, onLinkClick)
bigEventRv.layoutManager = LinearLayoutManager(context)
bigEventRv.addItemDecoration(VerticalItemDecoration(context, 19F, false, com.gh.gamecenter.common.R.color.ui_surface))
val closeTv = contentView.findViewById<TextView>(R.id.dialog_close)
closeTv.setOnClickListener {
dismiss()
}
setOnCancelListener {
mHandler.removeCallbacks(mDelayLogRunnable)
}
mHandler.postDelayed(mDelayLogRunnable, 3000)
}
companion object {
private val mHandler = Handler(Looper.getMainLooper())
fun showGameBigEventDialog(
context: Context,
gameName: String,
gameId: String,
bigEvents: List<BigEvent>,
onLinkClick: (LinkEntity, Int) -> Unit
): Dialog {
return GameBigEventDialog(
context,
gameName,
gameId,
bigEvents,
onLinkClick,
"游戏大事件",
"弹窗",
gameName
).apply {
show()
}
}
}
}

View File

@ -0,0 +1,76 @@
package com.gh.gamecenter.gamedetail.dialog
import android.content.Context
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.common.base.fragment.BaseBottomDialogFragment
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.databinding.DialogGameDetailRecyclerViewBinding
import com.gh.gamecenter.feature.entity.BigEvent
import com.lightgame.utils.AppManager
class GameBigEventDialogFragment(
val gameName: String,
val gameId: String,
private val bigEvents: List<BigEvent>,
private val onLinkClick: (LinkEntity, Int) -> Unit,
): BaseBottomDialogFragment<DialogGameDetailRecyclerViewBinding>() {
private val adapter by lazy { GameBigEventAdapter(requireContext(), bigEvents, onLinkClick) }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mBinding.titleTv.text = "游戏大事件"
mBinding.closeIv.setOnClickListener {
dismissAllowingStateLoss()
}
mBinding.recyclerView.run {
layoutManager = LinearLayoutManager(requireContext())
adapter = this@GameBigEventDialogFragment.adapter
(itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
}
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
adapter.notifyItemRangeChanged(0, adapter.itemCount)
}
companion object {
@JvmStatic
fun show(context: Context?, gameId: String, gameName: String, bigEvents: List<BigEvent>, onLinkClick: (LinkEntity, Int) -> Unit) {
val fragmentActivity: FragmentActivity = if (context is FragmentActivity) {
context
} else if (BuildConfig.DEBUG) {
throw IllegalStateException("GameBigEventDialogFragment context must be FragmentActivity")
} else {
val currentActivity = AppManager.getInstance().currentActivity()
if (currentActivity is FragmentActivity) {
currentActivity
} else {
throw IllegalStateException("current activity context must be FragmentActivity")
}
}
// 防止重复弹出
if (hasDialogInCurrentActivity(fragmentActivity)) return
val dialogFragment = GameBigEventDialogFragment(gameId, gameName, bigEvents, onLinkClick)
dialogFragment.show(fragmentActivity.supportFragmentManager, GameBigEventDialogFragment::class.java.name)
}
private fun hasDialogInCurrentActivity(fragmentActivity: FragmentActivity): Boolean {
val fragments: List<Fragment> = fragmentActivity.supportFragmentManager.fragments
fragments.forEach { fragment ->
if (fragment is GameBigEventDialogFragment) return true
}
return false
}
}
}

View File

@ -0,0 +1,62 @@
package com.gh.gamecenter.gamedetail.dialog
import android.content.Context
import android.os.Bundle
import android.text.Html
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.common.base.fragment.BaseBottomDialogFragment
import com.gh.gamecenter.databinding.DialogGameDetailScrollableTextBinding
import com.lightgame.utils.AppManager
class GameDetailScrollableTextDialogFragment(
val gameName: String,
val gameId: String,
private val title: String,
private val content: String,
): BaseBottomDialogFragment<DialogGameDetailScrollableTextBinding>() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mBinding.closeIv.setOnClickListener {
dismissAllowingStateLoss()
}
mBinding.titleTv.text = title
mBinding.contentTv.text = Html.fromHtml(content)
}
companion object {
@JvmStatic
fun show(context: Context?, gameId: String, gameName: String, title: String, content: String) {
val fragmentActivity: FragmentActivity = if (context is FragmentActivity) {
context
} else if (BuildConfig.DEBUG) {
throw IllegalStateException("GameDetailScrollableTextDialogFragment context must be FragmentActivity")
} else {
val currentActivity = AppManager.getInstance().currentActivity()
if (currentActivity is FragmentActivity) {
currentActivity
} else {
throw IllegalStateException("current activity context must be FragmentActivity")
}
}
// 防止重复弹出
if (hasDialogInCurrentActivity(fragmentActivity)) return
val dialogFragment = GameDetailScrollableTextDialogFragment(gameId, gameName, title, content)
dialogFragment.show(fragmentActivity.supportFragmentManager, GameDetailScrollableTextDialogFragment::class.java.name)
}
private fun hasDialogInCurrentActivity(fragmentActivity: FragmentActivity): Boolean {
val fragments: List<Fragment> = fragmentActivity.supportFragmentManager.fragments
fragments.forEach { fragment ->
if (fragment is GameDetailScrollableTextDialogFragment) return true
}
return false
}
}
}

View File

@ -0,0 +1,44 @@
package com.gh.gamecenter.gamedetail.dialog
import android.content.Context
import android.view.ViewGroup
import android.view.ViewGroup.MarginLayoutParams
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.databinding.ItemGameDetailFunctionBinding
import com.gh.gamecenter.gamedetail.entity.GameDetailInfoTag
import com.lightgame.adapter.BaseRecyclerAdapter
class GameFunctionAdapter(
context: Context,
private val infoTags: List<GameDetailInfoTag.InfoTag>
) : BaseRecyclerAdapter<RecyclerView.ViewHolder>(context) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return GameFunctionItemViewHolder(parent.toBinding())
}
override fun getItemCount(): Int = infoTags.size
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is GameFunctionItemViewHolder) {
holder.binding.root.updateLayoutParams<MarginLayoutParams> {
setMargins(0, if (position == 0) 8F.dip2px() else 20F.dip2px(), 0, 0)
}
val infoTag = infoTags.getOrNull(position)
ImageUtils.display(holder.binding.iconIv, infoTag?.icon)
holder.binding.nameTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(mContext))
holder.binding.desTv.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(mContext))
holder.binding.nameTv.text = infoTag?.name
holder.binding.desTv.text = infoTag?.des
}
}
class GameFunctionItemViewHolder(var binding: ItemGameDetailFunctionBinding) :
RecyclerView.ViewHolder(binding.root)
}

View File

@ -0,0 +1,74 @@
package com.gh.gamecenter.gamedetail.dialog
import android.content.Context
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.common.base.fragment.BaseBottomDialogFragment
import com.gh.gamecenter.databinding.DialogGameDetailRecyclerViewBinding
import com.gh.gamecenter.gamedetail.entity.GameDetailInfoTag
import com.lightgame.utils.AppManager
class GameFunctionDialogFragment(
val gameName: String,
val gameId: String,
private val infoTags: List<GameDetailInfoTag.InfoTag>
): BaseBottomDialogFragment<DialogGameDetailRecyclerViewBinding>() {
private val adapter by lazy { GameFunctionAdapter(requireContext(), infoTags) }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mBinding.titleTv.text = "功能说明"
mBinding.closeIv.setOnClickListener {
dismissAllowingStateLoss()
}
mBinding.recyclerView.run {
layoutManager = LinearLayoutManager(requireContext())
adapter = this@GameFunctionDialogFragment.adapter
(itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
}
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
adapter.notifyItemRangeChanged(0, adapter.itemCount)
}
companion object {
@JvmStatic
fun show(context: Context?, gameId: String, gameName: String, infoTags: List<GameDetailInfoTag.InfoTag>) {
val fragmentActivity: FragmentActivity = if (context is FragmentActivity) {
context
} else if (BuildConfig.DEBUG) {
throw IllegalStateException("GameFunctionDialogFragment context must be FragmentActivity")
} else {
val currentActivity = AppManager.getInstance().currentActivity()
if (currentActivity is FragmentActivity) {
currentActivity
} else {
throw IllegalStateException("current activity context must be FragmentActivity")
}
}
// 防止重复弹出
if (hasDialogInCurrentActivity(fragmentActivity)) return
val dialogFragment = GameFunctionDialogFragment(gameId, gameName, infoTags)
dialogFragment.show(fragmentActivity.supportFragmentManager, GameFunctionDialogFragment::class.java.name)
}
private fun hasDialogInCurrentActivity(fragmentActivity: FragmentActivity): Boolean {
val fragments: List<Fragment> = fragmentActivity.supportFragmentManager.fragments
fragments.forEach { fragment ->
if (fragment is GameFunctionDialogFragment) return true
}
return false
}
}
}

View File

@ -0,0 +1,58 @@
package com.gh.gamecenter.gamedetail.dialog
import android.content.Context
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.databinding.ItemDialogGameInfoOtherBinding
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.detail.viewholder.GameDetailInfoItemViewHolder
import com.gh.gamecenter.gamedetail.entity.GameDetailInfo
import com.gh.gamecenter.gamedetail.entity.GameDetailInfo.InfoItem
import com.lightgame.adapter.BaseRecyclerAdapter
class GameInfoAdapter(
context: Context,
private val data: GameDetailInfo,
private val infoList: List<InfoItem>,
private val viewModel: GameDetailViewModel
) : BaseRecyclerAdapter<RecyclerView.ViewHolder>(context) {
override fun getItemViewType(position: Int): Int = if (position == itemCount - 1) TYPE_OTHER else TYPE_INFO
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return if (viewType == TYPE_INFO) {
GameDetailInfoItemViewHolder.InfoItemViewHolder(parent.toBinding())
} else {
GameInfoDialogOtherViewHolder(parent.toBinding())
}
}
override fun getItemCount(): Int = infoList.size + 1
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is GameDetailInfoItemViewHolder.InfoItemViewHolder) {
val info = infoList.getOrNull(position) ?: return
holder.bindInfoItem(mContext, info)
}
if (holder is GameInfoDialogOtherViewHolder) {
holder.binding.run {
GameDetailInfoItemViewHolder.bindStaticInfo(
mContext,
data,
viewModel,
privacyPolicyTv,
permissionsTv,
requestUpdateTv
)
}
}
}
class GameInfoDialogOtherViewHolder(val binding: ItemDialogGameInfoOtherBinding): RecyclerView.ViewHolder(binding.root)
companion object {
const val TYPE_INFO = 0
const val TYPE_OTHER = 0
}
}

View File

@ -0,0 +1,90 @@
package com.gh.gamecenter.gamedetail.dialog
import android.content.Context
import android.os.Bundle
import android.view.View
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.common.base.fragment.BaseBottomDialogFragment
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.viewModelProviderFromParent
import com.gh.gamecenter.databinding.DialogGameDetailRecyclerViewBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.detail.viewholder.GameDetailInfoItemViewHolder.Companion.normalTypeNameMap
import com.gh.gamecenter.gamedetail.entity.GameDetailInfo
import com.halo.assistant.HaloApp
import com.lightgame.utils.AppManager
class GameInfoDialogFragment(
val gameEntity: GameEntity?,
private val data: GameDetailInfo,
): BaseBottomDialogFragment<DialogGameDetailRecyclerViewBinding>() {
private val viewModel: GameDetailViewModel by lazy {
val factory = GameDetailViewModel.Factory(
HaloApp.getInstance().application,
gameEntity?.id,
gameEntity
)
viewModelProviderFromParent(factory, gameEntity?.id ?: "")
}
private val adapter by lazy {
val normalTypeKeys = normalTypeNameMap.keys
val infoList = data.fields.filter { normalTypeKeys.contains(it.name) }.sortedBy { it.order }
GameInfoAdapter(requireContext(), data, infoList, viewModel)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mBinding.titleTv.text = data.name
mBinding.closeIv.setOnClickListener {
dismissAllowingStateLoss()
}
mBinding.recyclerView.run {
setPadding(16F.dip2px(), 16F.dip2px(), 16F.dip2px(), 16F.dip2px())
layoutManager = LinearLayoutManager(requireContext())
adapter = this@GameInfoDialogFragment.adapter
(itemAnimator as DefaultItemAnimator).supportsChangeAnimations = false
}
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
adapter.notifyItemRangeChanged(0, adapter.itemCount)
}
companion object {
@JvmStatic
fun show(context: Context?, gameEntity: GameEntity?, data: GameDetailInfo) {
val fragmentActivity: FragmentActivity = if (context is FragmentActivity) {
context
} else if (BuildConfig.DEBUG) {
throw IllegalStateException("GameInfoDialogFragment context must be FragmentActivity")
} else {
val currentActivity = AppManager.getInstance().currentActivity()
if (currentActivity is FragmentActivity) {
currentActivity
} else {
throw IllegalStateException("current activity context must be FragmentActivity")
}
}
// 防止重复弹出
if (hasDialogInCurrentActivity(fragmentActivity)) return
val dialogFragment = GameInfoDialogFragment(gameEntity, data)
dialogFragment.show(fragmentActivity.supportFragmentManager, GameInfoDialogFragment::class.java.name)
}
private fun hasDialogInCurrentActivity(fragmentActivity: FragmentActivity): Boolean {
val fragments: List<Fragment> = fragmentActivity.supportFragmentManager.fragments
fragments.forEach { fragment ->
if (fragment is GameInfoDialogFragment) return true
}
return false
}
}
}

View File

@ -12,10 +12,8 @@ import androidx.fragment.app.FragmentActivity
import androidx.recyclerview.widget.LinearLayoutManager
import com.gh.common.util.NewLogUtils
import com.gh.download.dialog.DownloadDialog
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.base.fragment.BaseDialogFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.view.divider.HorizontalDividerItemDecoration
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.core.utils.MtaHelper
@ -82,21 +80,6 @@ class GameTagsDialog : BaseDialogFragment() {
tag.name, tag.name, "", "游戏介绍"
)
)
SensorsBridge.trackGameDetailPageGameTagClick(
gameId = mGameId,
gameName = mGameName,
pageName = GlobalActivityManager.getCurrentPageEntity().pageName,
pageId = GlobalActivityManager.getCurrentPageEntity().pageId,
pageBusinessId = GlobalActivityManager.getCurrentPageEntity().pageBusinessId,
lastPageName = GlobalActivityManager.getLastPageEntity().pageName,
lastPageId = GlobalActivityManager.getLastPageEntity().pageId,
lastPageBusinessId = GlobalActivityManager.getLastPageEntity().pageBusinessId,
downloadStatus = mDownloadStatus,
gameType = mGameType,
position = position,
gameTag = listOf(tag.name),
gameTagId = tag.id,
)
NewLogUtils.logGameDetailTagClick(mGameId, mGameName, tag.id, tag.name, tag.isTop)
MtaHelper.onEvent("游戏标签弹窗", "进入标签", "${mGameName}+${tag.name}")
}

View File

@ -4,11 +4,7 @@ import android.app.Dialog
import android.content.DialogInterface
import android.content.Intent
import android.os.Bundle
import android.view.Gravity
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.WindowManager
import android.view.*
import android.widget.ImageView
import android.widget.TextView
import androidx.lifecycle.ViewModelProvider
@ -20,23 +16,26 @@ import com.gh.download.DownloadManager
import com.gh.gamecenter.R
import com.gh.gamecenter.ShellActivity
import com.gh.gamecenter.ShellActivity.Type
import com.gh.gamecenter.databinding.FragmentDialogSpecialDownloadBinding
import com.gh.gamecenter.common.base.fragment.BaseDraggableDialogFragment
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.asVGame
import com.gh.gamecenter.common.utils.viewModelProviderFromParent
import com.gh.gamecenter.core.utils.SpeedUtils
import com.gh.gamecenter.databinding.FragmentDialogSpecialDownloadBinding
import com.gh.gamecenter.feature.entity.GameDetailServer
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.LibaoEntity
import com.gh.gamecenter.feature.entity.MeEntity
import com.gh.gamecenter.feature.view.TrapezoidDownloadButton
import com.gh.gamecenter.gamedetail.LibaoListFragment
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.GameDetailTabEntity
import com.gh.gamecenter.gamedetail.fuli.kaifu.ServersCalendarActivity
import com.gh.gamecenter.gamedetail.libao.LibaoListFragment
import com.gh.gamecenter.manager.PackagesManager
import com.gh.ndownload.NDownloadService
import com.gh.ndownload.suspendwindow.utils.NDownloadSuspendWindowHelper
import com.halo.assistant.HaloApp
import com.lightgame.download.DataWatcher
import com.lightgame.download.DownloadConfig
import com.lightgame.download.DownloadEntity
@ -51,6 +50,7 @@ class SpecialDownloadDialogFragment : BaseDraggableDialogFragment() {
private var viewModel: SpecialDownloadDialogViewModel? = null
private var visibilityViewModel: SpecialDownloadVisibilityViewModel? = null
private var gameDetailViewModel: GameDetailViewModel? = null
private var installedVersionMisMatched = false
@ -139,6 +139,14 @@ class SpecialDownloadDialogFragment : BaseDraggableDialogFragment() {
super.onViewCreated(view, savedInstanceState)
boundedGameEntity = arguments?.getParcelable(KEY_GAME_ENTITY)
val factory = GameDetailViewModel.Factory(
HaloApp.getInstance().application,
boundedGameEntity?.id,
boundedGameEntity
)
gameDetailViewModel = viewModelProviderFromParent(factory, boundedGameEntity?.id ?: "")
val installHint = arguments?.getParcelable(KEY_INSTALL_HINT) ?: LinkEntity()
binding.collapseIv.setOnClickListener {
@ -293,12 +301,17 @@ class SpecialDownloadDialogFragment : BaseDraggableDialogFragment() {
titleIv.setImageResource(R.drawable.ic_special_download_text_gift)
hintTv.setText(getString(R.string.special_download_tips_collect_immediately))
subTipsView.setOnClickListener {
val bundle = LibaoListFragment.getBundle(
boundedGameEntity?.id ?: "",
boundedGameEntity?.name ?: "",
content.libaoList
)
startActivity(ShellActivity.getIntent(requireContext(), Type.SIMPLE_LIBAO_LIST, bundle))
val tabList = gameDetailViewModel?.gameDetailTabListLiveData?.value?.data
if (tabList?.find { it.type == GameDetailTabEntity.TYPE_GIFT } != null) {
gameDetailViewModel?.performTabSelected(GameDetailTabEntity.TYPE_GIFT)
} else {
val bundle = LibaoListFragment.getBundle(
boundedGameEntity?.id ?: "",
boundedGameEntity?.name ?: "",
false
)
startActivity(ShellActivity.getIntent(requireContext(), Type.SIMPLE_LIBAO_LIST, bundle))
}
trackEvent(boundedGameEntity!!, "游戏礼包", true)
}
}

View File

@ -1,11 +1,8 @@
package com.gh.gamecenter.gamedetail.entity
import androidx.annotation.Keep
import com.gh.gamecenter.common.entity.CommunityEntity
import com.gh.gamecenter.common.entity.Display
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.entity.ToolBoxEntity
import com.gh.gamecenter.entity.*
import com.gh.gamecenter.feature.entity.GameDetailServer
import com.gh.gamecenter.feature.entity.LibaoEntity
import com.google.gson.annotations.SerializedName
@ -14,39 +11,11 @@ import com.google.gson.annotations.SerializedName
class ContentCardEntity(
@SerializedName("_id")
var id: String = "",
var name: String? = "", // tag
var title: String? = "",
var image: String? = "",
@SerializedName("target", alternate = ["link", "link_id"])
var link: String? = "",
@SerializedName("type", alternate = ["link_type"])
var type: String? = "",
var icon: String = "",
@SerializedName("game_icon")
var gameIcon: String? = "",
@SerializedName("game_icon_subscript")
var gameIconSubscript: String? = "",
var text: String? = "",
@SerializedName("link_text")
var linkText: String? = "",//游戏详情弹窗,兼容旧版本用
var value: String? = "",
@SerializedName("community_id")
var communityId: String? = "",
@SerializedName("link_community", alternate = ["community"])
var community: CommunityEntity? = CommunityEntity(),
var display: Display? = null, // 板块
@SerializedName("close_button")
var closeButton: String = "open",//用户判断h5游戏关闭按钮是否显示hide(隐藏)、open(开启)
@SerializedName("button_link")
var buttonLink: Boolean = false,
@SerializedName("activity_id")
var activityId: String = "",
var style: String = "",
var des: String = "",
var link: LinkEntity = LinkEntity(), // 其中功能类func_server游戏开服、func_libao游戏礼包、func_related_version相关版本、func_zone专区、func_tool_kit工具、func_bbs论坛、func_archive云存档
var server: GameDetailServer? = null,
@SerializedName("mirror_server")
var mirrorServer: GameDetailServer? = null,
var libao: LibaoEntity? = null,
@SerializedName("zone_tab")
var zoneTab: Boolean = false,
@ -55,7 +24,11 @@ class ContentCardEntity(
var toolkit: ArrayList<ToolBoxEntity> = ArrayList(),
@SerializedName("func_bbs")
val funcBbs: LinkEntity? = null,
val dialog: Dialog? = null
val archive: Archive? = null,
val dialog: Dialog? = null,
// 本地字段
var showDes: Boolean = true,
var showNewTag: Boolean = false,
) {
@Keep
@ -66,26 +39,21 @@ class ContentCardEntity(
val body: String? = ""
)
fun toLinkEntity(): LinkEntity {
return LinkEntity(
name = name,
title = title,
image = image,
link = link,
type = type,
icon = icon,
gameIcon = gameIcon,
gameIconSubscript = gameIconSubscript,
text = text,
linkText = linkText,
value = value,
communityId = communityId,
community = community,
display = display,
closeButton = closeButton,
buttonLink = buttonLink,
activityId = activityId,
style = style
)
@Keep
class Archive(
@SerializedName("_id")
val id: String = "",
val name: String = "",
val time: Long = 0L
)
companion object {
const val TYPE_GIFT = "func_libao"
const val TYPE_SERVER = "func_server"
const val TYPE_RELATED_VERSION = "func_related_version"
const val TYPE_ZONE = "func_zone"
const val TYPE_BBS = "func_bbs"
const val TYPE_TOOLKIT = "func_tool_kit"
const val TYPE_ARCHIVE = "func_archive"
}
}

View File

@ -0,0 +1,10 @@
package com.gh.gamecenter.gamedetail.entity
data class CoverEntity(
var index: Int = -1,
var isDefault: Boolean = false,
var tabName: String = "",
var video: CoverTabEntity.Video? = null,
var gallery: CoverTabEntity.Gallery? = null,
var coverTabEntity: CoverTabEntity? = null
)

View File

@ -1,212 +0,0 @@
package com.gh.gamecenter.gamedetail.entity
import android.os.Parcelable
import androidx.annotation.Keep
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.entity.*
import com.gh.gamecenter.feature.entity.*
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
@Keep
class DetailEntity(
var type: String = "",
@SerializedName("new_notice")
var noticeList: ArrayList<LinkEntity>? = null,
var des: String? = null,// 介绍文案
var gallery: ArrayList<String>? = null,
@SerializedName("count")
var videoCount: Int = 0,//视频数量
var video: ArrayList<Video>? = null,
var comment: ArrayList<RatingComment>? = null,
var info: GameInfo? = null,
var update: UpdateContent? = null,
var libao: ArrayList<LibaoEntity>? = null,
@SerializedName("related_version")
var relatedVersion: ArrayList<RelatedVersion>? = null,
var server: GameDetailServer? = null,
@SerializedName("image_recommend")
var imageRecommend: LinkEntity? = null,
@SerializedName("custom_column")
var customColumn: CustomColumn? = null,
@SerializedName("related_game")
var relatedGames: ArrayList<GameDetailRelatedGame>? = null,//原推荐游戏
@SerializedName("download_game")
var downloadGames: ArrayList<String>? = null,// 下载推荐
@SerializedName("install_game")
var installGames: ArrayList<String>? = null,// 安装推荐
var recommendedGames: SubjectEntity? = null,
var article: ArrayList<NewsEntity>? = null,
@SerializedName("article_top")
var articleTop: ArrayList<NewsEntity>? = null,
@SerializedName("recommend_game_list")
var recommendGameList: ArrayList<GameDetailRecommendGameEntity>? = null,
//专题游戏单推荐
@SerializedName("column_id")
var columnId: String = "",
@SerializedName("title")
var columnTitle: String = "",
@SerializedName("display.home")
var displayHome: String = "",//右上角文案
@SerializedName("size.index")
var gameCount: Int = 0,//显示多少条数
@SerializedName("display.more_link")
var moreLink: LinkEntity? = null,
@SerializedName("column_game")
var columnGames: ArrayList<GameEntity>? = null,
// 仅用于镜像
@SerializedName(value = "new_tag_style")
var tagStyle: ArrayList<TagStyleEntity> = ArrayList(),
// 仅用于镜像
var apk: ArrayList<ApkEntity>? = ArrayList(),
// 仅用于镜像
@SerializedName("custom_columns")
var customColumnList: ArrayList<CustomColumn>? = null,
// 仅用于镜像
var manufacturer: String? = null,
// 仅用于镜像
@SerializedName("privacy_policy_url")
var privacyPolicyUrl: String? = null,
// 仅用于镜像
@SerializedName("manufacturer_type")
var manufacturerType: String = "",
// 仅用于镜像
var publisher: String = "",
// 仅用于镜像
var developer: String = "",
// 仅用于镜像
var supplier: String = "",
// 仅用于镜像
@SerializedName("credit_code")
var creditCode: String = "",
// 仅用于镜像
@SerializedName("update_des")
var updateDes: String = "",
// 仅用于镜像
@SerializedName("update_time")
var updateTime: Long = 0,
// 仅用于镜像
@SerializedName("content_card_status")
var contentCardStatus: String = "off", //on生效/off不生效
@SerializedName("detail_dialogs")
var detailDialogs: ArrayList<GameEntity.Dialog> = arrayListOf(),
// 是否与前一个 item 连成整体,是否与后一个 item 连成整体
var shouldBoundWithPreviousItem: Boolean = false,
var shouldBoundWithNextItem: Boolean = false,
// 上下 padding
var paddingTop: Int = 0,
var paddingBottom: Int = 0
) {
enum class Type(val value: String) {
IMAGE_GALLERY("gallery"),
VIDEOS("video"),
COMMENTS("comment"),
GAME_INFO("info"),
UPDATE_CONTENT("update"),
LATEST_SERVER("server"),
RELATED_VERSION("related_version"),
IMAGE("image_recommend"),
LIBAO("libao"),
INFO_GUIDE("article"),
RECOMMENDED_GAMES("related_game"),
CUSTOM_COLUMN("custom_column"),
DES("des"),
NOTICE("notice"),
RECOMMEND_GAME_LIST("recommend_game_list"),
COLUMN_RECOMMEND("column_recommend"),
}
}
@Keep
class CustomColumn(
@SerializedName("_id")
var id: String = "",
var name: String = "",
@SerializedName("show_name")
var showName: Boolean? = true,
@SerializedName("name_icon")
var nameIcon: String? = "",
@SerializedName("name_link")
var nameLink: LinkEntity? = null,
var link: LinkEntity? = null,
var order: Long? = 0, // 权重
var title: Title? = null,
var des: String? = "",
@SerializedName("tag_des")
var desFull: String? = null,
@SerializedName("tag_des_brief")
var desBrief: String? = "",
@SerializedName("show_des_type")
var showDesType: String? = "",
@SerializedName("show_des_row_num")
var showDesRowNum: Int? = 3,
@SerializedName("show_info_tag")
var showInfoTag: Boolean? = false,
@SerializedName("show_info_tag_des")
var showInfoTagDes: Boolean? = false,
@SerializedName("show_info_tag_des_type")
var showInfoTagDesType: String? = "", // first_expand (用于默认展开), collapse, expand
@SerializedName("info_tag")
var infoTag: List<TagEntity>? = listOf(),
var time: Long? = 0,
var isHtmlDes: Boolean? = true,
// 是否显示自定义栏目的提示浮窗 (本地字段)
var showExpandTagsHint: Boolean? = false
)
@Keep
class Title(var icon: String, var value: String, var color: String)
@Keep
class UpdateContent(
@SerializedName("history_apk_count")
val historyApkCount: Int = 0,
@SerializedName("history_apk_status")
var historyApkStatus: String = "",
@SerializedName("update_des")
var updateDes: String = ""
)
@Keep
@Parcelize
class Video(
@SerializedName("_id", alternate = ["video_id"])
var videoId: String = "",
@SerializedName(
"new_video_count",
alternate = ["video_count"]
) // 选用 JSON 最后一个值作为 videoCount 的值
var videoCount: Int = 0,
var poster: String = "",
var title: String = "",
var url: String = "",
var user: User? = null,
var vote: Int = 0
) : Parcelable
@Keep
@Parcelize
class RelatedVersion(
var first: Boolean = false,
var last: Boolean = false,
var game: GameEntity? = null,
@SerializedName("game_id")
var gameId: String = "",
@SerializedName("game_name")
private var mGameName: String = "",
@SerializedName("game_icon")
var gameIcon: String = "",
@SerializedName(value = "game_tag", alternate = arrayOf("new_game_tag"))
var gameTags: ArrayList<TagStyleEntity> = ArrayList(),
var index: Int
) : Parcelable {
@IgnoredOnParcel
val gameName: String
get() = mGameName.removeSuffix(".")
}

View File

@ -0,0 +1,462 @@
package com.gh.gamecenter.gamedetail.entity
import com.gh.gamecenter.common.entity.IconFloat
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.entity.PKEntity
import com.gh.gamecenter.entity.GameDetailRecommendGameEntity
import com.gh.gamecenter.entity.GameDetailRelatedGame
import com.gh.gamecenter.entity.RatingComment
import com.gh.gamecenter.entity.SubjectEntity
import com.gh.gamecenter.feature.entity.*
import com.google.gson.annotations.SerializedName
data class GameDetailData(
var position: Int = -1, // 本地字段
var type: String = "",
val order: Order = Order(),
@SerializedName("link_video_img_area")
val linkVideoImgArea: List<CoverTabEntity>? = null,
@SerializedName("link_basic_info")
val linkBasicInfo: GameEntity? = null,
@SerializedName("link_data_info")
val linkDataInfo: List<GameDetailDataInfo>? = null,
@SerializedName("link_info_tag")
val linkInfoTag: GameDetailInfoTag? = null,
@SerializedName("link_content_card")
val linkContentCard: List<ContentCardEntity>? = null,
@SerializedName("link_advertising")
val linkAdvertising: GameDetailLink? = null,
@SerializedName("link_comprehensive")
val linkComprehensive: List<GameDetailComprehensivePanelItem>? = null,
@SerializedName("link_drawer")
val linkDrawer: GameDetailLinksItem? = null,
@SerializedName("link_announcement")
val linkAnnouncement: GameDetailLinksItem? = null,
@SerializedName("link_game_brief")
val linkGameBrief: GameBrief? = null,
@SerializedName("link_developer_word")
val linkDeveloperWord: DeveloperWord? = null,
@SerializedName("link_update")
val linkUpdate: GameDetailUpdateContent? = null,
@SerializedName("link_custom_column")
val linkCustomColumn: GameDetailCustomColumn? = null,
@SerializedName("link_comment")
val linkComment: GameDetailComment? = null,
@SerializedName("link_info")
val linkInfo: GameDetailInfo? = null,
@SerializedName("link_content_recommend")
val linkContentRecommend: LinkEntity? = null,
var pkData: PKEntity? = null,
@SerializedName("link_game_video")
val linkGameVideo: GameDetailVideo? = null,
@SerializedName("link_game_guide")
val linkGameGuide: GameDetailStrategy? = null,
@SerializedName("link_server")
val linkServer: GameDetailServer? = null,
@SerializedName("link_libao")
var linkLibao: ArrayList<LibaoEntity>? = null,
@SerializedName("link_related_game")
val linkRelatedGame: ArrayList<GameEntity>? = null,
@SerializedName("link_image_recommend")
val linkImageRecommend: GameDetailRecommendImage? = null,
@SerializedName("link_everyone_playing")
val linkEveryonePlaying: GameDetailRecommendGame? = null,
@SerializedName("link_recommend_game_list")
val linkRecommendGameList: ArrayList<GameDetailRecommendGameEntity>? = null,
@SerializedName("link_column_recommend")
val linkColumnRecommend: List<GameDetailRecommendColumn>? = null,
val recommendColumn: GameDetailRecommendColumn? = null,
) {
val typeChinese
get() = when (type) {
TYPE_VIDEO_IMG_AREA -> "视频/图片区域"
TYPE_BASIC_INFO -> "头部信息"
TYPE_DATA_INFO -> "数据信息栏"
TYPE_INFO_TAG -> "功能标签"
TYPE_CONTENT_CARD -> "内容卡片"
TYPE_ADVERTISING -> "广告推荐"
TYPE_COMPREHENSIVE -> "综合面板"
TYPE_CUSTOM_COLUMN -> "自定义栏目"
TYPE_DRAWER -> "抽屉列表"
TYPE_ANNOUNCEMENT -> "资讯公告"
TYPE_GAME_BRIEF -> "游戏简介"
TYPE_DEVELOPER_WORD -> "开发者的话"
TYPE_UPDATE -> "更新内容"
TYPE_COMMENT -> "玩家评论"
TYPE_INFO -> "详情信息"
TYPE_CONTENT_RECOMMEND -> "内容推荐PK组件"
TYPE_GAME_VIDEO -> "游戏视频"
TYPE_GAME_GUIDE -> "游戏攻略"
TYPE_SERVER -> "游戏开服"
TYPE_LIBAO -> "游戏礼包"
TYPE_RELATED_GAME -> "相关游戏"
TYPE_IMAGE_RECOMMEND -> "图片推荐"
TYPE_EVERYONE_PLAYING -> "大家都在玩"
TYPE_RECOMMEND_GAME_LIST -> "游戏单推荐"
TYPE_COLUMN_RECOMMEND -> "专题推荐"
else -> ""
}
fun areItemsTheSame(other: GameDetailData) = type == other.type
fun areContentsTheSame(other: GameDetailData) = type == other.type && this == other
companion object {
const val TYPE_VIDEO_IMG_AREA = "video_img_area"
const val TYPE_BASIC_INFO = "basic_info"
const val TYPE_DATA_INFO = "data_info"
const val TYPE_INFO_TAG = "info_tag"
const val TYPE_CONTENT_CARD = "content_card"
const val TYPE_ADVERTISING = "advertising"
const val TYPE_COMPREHENSIVE = "comprehensive"
const val TYPE_CUSTOM_COLUMN = "custom_column"
const val TYPE_DRAWER = "drawer"
const val TYPE_ANNOUNCEMENT = "announcement"
const val TYPE_GAME_BRIEF = "game_brief"
const val TYPE_DEVELOPER_WORD = "developer_word"
const val TYPE_UPDATE = "update"
const val TYPE_COMMENT = "comment"
const val TYPE_INFO = "info"
const val TYPE_CONTENT_RECOMMEND = "content_recommend"
const val TYPE_GAME_VIDEO = "game_video"
const val TYPE_GAME_GUIDE = "game_guide"
const val TYPE_SERVER = "server"
const val TYPE_LIBAO = "libao"
const val TYPE_RELATED_GAME = "related_game"
const val TYPE_IMAGE_RECOMMEND = "image_recommend"
const val TYPE_EVERYONE_PLAYING = "everyone_playing"
const val TYPE_RECOMMEND_GAME_LIST = "recommend_game_list"
const val TYPE_COLUMN_RECOMMEND = "column_recommend"
}
}
data class Order(
@SerializedName("before_download")
val beforeDownload: Int = -1, // 下载前排序
val installed: Int = -1, // 已安装排序
val updateable: Int = -1, // 可更新排序
)
data class CoverTabEntity(
@SerializedName("_id")
var id: String = "",
@SerializedName("game_id")
var gameId: String = "",
var name: String = "",
var type: String = "", // tab显示内容(图集gallery、视频video)
var select: Boolean = false, // 是否默认选中
@SerializedName("video_link")
var video: Video? = null,
@SerializedName("gallery_info")
var gallery: List<Gallery>? = null,
) {
data class Gallery(
var url: String = "",
var width: String = "",
var height: String = "",
)
data class Video(
@SerializedName("_id", alternate = ["video_id"])
var videoId: String = "",
var poster: String = "",
var title: String = "",
var url: String = "",
)
}
data class GameDetailDataInfo(
val name: String = "", // 字段ranking榜单排名、size游戏大小、recommend_age适龄提示、language语言、internet_app需要联网、real_name游戏内实名
@SerializedName("name_cn")
val nameCn: String = "", // 字段中文
val value: String = "", // 字段值
val ranking: Ranking? = null, // 榜单排名
val size: String = "", // 游戏大小
@SerializedName("recommend_age")
val recommendAge: String = "", // 适龄等级
val language: String = "", // 语言
@SerializedName("internet_app")
val internetApp: String = "", // 是否联网
@SerializedName("real_name")
val realName: RealName? = null // 实名
) {
data class Ranking(
@SerializedName("collection_id")
val collectionId: String = "", // 专题合集ID
@SerializedName("collection_name")
val collectionName: String = "", // 专题合集名称
@SerializedName("column_id")
val columnId: String = "", // 专题ID
@SerializedName("column_name")
val columnName: String = "", // 专题名称
val no: Int = -1 // 名次
)
data class RealName(
val state: String = "", // 是否需要游戏内实名
@SerializedName("certification_screenshot")
val certificationScreenshot: String = "" // 实名认证截图
)
}
data class GameDetailInfoTag(
@SerializedName("info_tags")
val infoTags: List<InfoTag> = listOf(), // 功能标签
@SerializedName("request_speed_status")
val requestSpeedStatus: String = "" // 求加速状态, on/off
) {
data class InfoTag(
val name: String = "", // 功能名称
val des: String = "", // 功能说明
val icon: String = "", // icon
val color: String = "" // 颜色
)
}
data class GameDetailLink(
val title: String = "", // 标题
@SerializedName("component_icon")
val componentIcon: ComponentIcon? = null, // 组件图标
val img: String = "", // 图片
@SerializedName("img_scale")
val imgScale: String = "", // 图片比例,2:1、4:1
val text: String = "", // 跳转文案
val link: LinkEntity? = null // 跳转链接
) {
data class ComponentIcon(
@SerializedName("_id")
val id: String = "",
val name: String = "",
val icon: String = ""
)
}
data class GameDetailComprehensivePanelItem(
val title: String = "", // 标题
val type: String = "", // 板块类型function功能列表、FAQ常见问题、declaration声明信息
@SerializedName("show_title")
val showTitle: Boolean = true, // 是否显示此标题true显示、false隐藏
@SerializedName("function_type")
val functionType: String = "", // 功能列表类型type=functionone_line_one_point一行一分点、one_line_two_points一行两分点
@SerializedName("show_type")
val showType: String = "", // function_type=one_line_one_point, 一行一分点-显示内容part部分展开、all展开全部
@SerializedName("show_row_num")
val showRowNum: Int = 0, // show_type=part一行一分点-显示内容-部分展开,填写展示分点数
val data: List<ContentData>? = null, // 功能列表/常见问题 内容数据
val declaration: String = "" // type=declaration时才有此字段数据
) {
data class ContentData(
val text: String = "", // 文案/分点内容
val link: LinkEntity? = null // 跳转链接type=FAQ时填写
)
companion object {
const val TYPE_FUNCTION = "function"
const val TYPE_FAQ = "FAQ"
const val TYPE_DECLARATION = "declaration"
const val FUNCTION_TYPE_ONE_LINE_ONE_POINT = "one_line_one_point"
const val FUNCTION_TYPE_ONE_LINE_TWO_POINTS = "one_line_two_points"
const val SHOW_TYPE_PART = "part"
const val SHOW_TYPE_ALL = "all"
}
}
data class GameDetailLinksItem(
val name: String = "", // 标题
@SerializedName("show_name")
val showName: Boolean = true, // app是否显示组件标题true显示、false隐藏
val data: List<GameDetailLink> = listOf()
)
data class GameBrief(
val name: String = "", // 组件标题
@SerializedName("game_tags")
val gameTags: List<GameTag> = listOf(), // 游戏标签
val des: String = "", // 游戏介绍
@SerializedName("award_data")
val awardData: List<AwardData> = listOf() // 奖项数据
) {
data class GameTag(
@SerializedName("_id")
val id: String = "",
@SerializedName("_seq")
val seq: Int = 0,
val name: String = "", // 功能名称
val color: String = "", // 标签颜色
val background: String = "", // 标签背景
val column: String = "", // 排行榜专题标签颜色
@SerializedName("is_top")
val isTop: Boolean = false
)
data class AwardData(
val icon: String = "", // 图标
val award: String = "", // 奖项名称
@SerializedName("award_type")
val awardType: String = "", // 奖项类别
val link: LinkEntity? = null // 通用链接
)
}
data class GameDetailCustomColumn(
val name: String = "", // 组件标题
@SerializedName("show_name")
val showName: Boolean = true, // 是否展示组件标题
val img: String = "", // 图片
@SerializedName("title_info")
val titleInfo: TitleInfo? = null, // 栏目标题数据
@SerializedName("right_top_info")
val rightTopInfo: RightTopInfo? = null, // 右上角数据
@SerializedName("second_title_info")
val secondTitleInfo: SecondTitleInfo? = null, // 二级标题数据
@SerializedName("show_des_type")
val showDesType: String = "", // 显示内容part控制行数、all展开全部
@SerializedName("show_des_row_num")
val showDesRowNum: Int = 0, // 控制行数
@SerializedName("tag_des")
val tagDes: String = "" // 正文说明
) {
data class TitleInfo(
val icon: String = "", // 栏目标题前图标
@SerializedName("icon_subscript")
val iconSubscript: String = "", // 栏目标题前图标
@SerializedName("icon_float")
var iconFloat: IconFloat? = null,
val title: String = "", // 栏目标题
@SerializedName("show_title")
val showTitle: Boolean = true, // 是否显示栏目标题
val text: String = "", // 栏目标题后文案
val link: LinkEntity? = null // 标题后文案跳转链接
)
data class RightTopInfo(
val icon: String = "", // 右上角图标
val style: String = "", // 文案样式text文本、button按钮
val text: String = "", // 右上角文案
val link: LinkEntity? = null // 右上角文案跳转链接
) {
companion object {
const val TYPE_TEXT = "text"
const val TYPE_BUTTON = "button"
}
}
data class SecondTitleInfo(
val icon: String = "", // 二级标题前图标
@SerializedName("show_title")
val showTitle: Boolean = true, // 是否显示二级标题
val text: String = "", // 二级标题内容
val color: String = "" // 二级标题颜色
)
companion object {
const val SHOW_DES_TYPE_PART = "part"
const val SHOW_DES_TYPE_ALL = "all"
}
}
data class DeveloperWord(
val name: String = "",
val text: String = "",
)
data class GameDetailUpdateContent(
val name: String = "",
@SerializedName("history_apk_count")
val historyApkCount: Int = 0,
@SerializedName("history_apk_status")
var historyApkStatus: String = "",
@SerializedName("update_des")
var updateDes: String = ""
)
data class GameDetailComment(
val name: String = "",
val data: ArrayList<RatingComment> = arrayListOf()
)
data class GameDetailInfo(
val name: String = "",
var fields: List<InfoItem> = listOf(),
val des: String = ""
) {
data class InfoItem(
val name: String = "",
val value: String = "",
@SerializedName("is_first")
val isFirst: Boolean = true, // 是否显示在一级页面
val order: Int = -1, // 排序
val permissions: List<Permission>? = null
)
}
data class GameDetailVideo(
val video: List<Video> = listOf(), // 视频列表
val count: Int = 0 // 视频数量
) {
data class Video(
@SerializedName("_id")
val id: String = "",
val poster: String = "", // 封面图
val title: String = "",
val url: String = "",
val user: User? = null,
val vote: Int = 0 // 点赞数
)
}
data class GameDetailStrategy(
val name: String = "", // 组件标题
@SerializedName("right_top")
val rightTop: String = "", // 右上角文案more更多、link攻略链接
@SerializedName("article_types")
val articleTypes: List<String> = listOf(), // 游戏文章类型
@SerializedName("article_top")
val articleTop: List<NewsEntity> = listOf(), // 置顶文章
val data: List<StrategyData> = listOf() // 文章数据
) {
data class StrategyData(
val type: String = "", // 文章类型
@SerializedName("link_article")
val linkArticle: NewsEntity? = null, // 游戏文章
@SerializedName("link_community_article")
val linkCommunityArticle: ArticleEntity? = null, // 社区文章
val isFixedTop: Boolean = false // 本地字段,是否置顶
)
}
data class GameDetailRecommendImage(
val title: String = "",
val img: String = "",
val link: LinkEntity? = null
)
data class GameDetailRecommendGame(
@SerializedName("related_game")
var relatedGames: ArrayList<GameDetailRelatedGame>? = null,//原推荐游戏
@SerializedName("download_game")
var downloadGames: ArrayList<String>? = null,// 下载推荐
@SerializedName("install_game")
var installGames: ArrayList<String>? = null,// 安装推荐
var recommendedGames: SubjectEntity? = null,
)
data class GameDetailRecommendColumn(
@SerializedName("column_id")
var columnId: String = "",
@SerializedName("title")
var columnTitle: String = "",
@SerializedName("display.home")
var displayHome: String = "",//右上角文案
@SerializedName("size.index")
var gameCount: Int = 0,//显示多少条数
@SerializedName("display.more_link")
var moreLink: LinkEntity? = null,
@SerializedName("column_game")
var columnGames: ArrayList<GameEntity>? = null,
)

View File

@ -0,0 +1,27 @@
package com.gh.gamecenter.gamedetail.entity
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.SimpleGame
import com.google.gson.annotations.SerializedName
class GameDetailSetting(
@SerializedName("download_position_link")
val downloadPositionLink: LinkEntity? = null, // 触发下载定位
@SerializedName("update_download_position_link")
val updateDownloadPositionLink: LinkEntity? = null, // 触发更新下载定位
@SerializedName("_seq")
val shortId: String = "",
@SerializedName("appointment_switch_status")
var appointmentSwitchStatus: Boolean = false,
@SerializedName("smooth_relation_game")
var smoothRelatedGame: SimpleGame? = null,
@SerializedName("detail_dialogs")
var detailDialogs: ArrayList<GameEntity.Dialog> = arrayListOf(),
val item: List<GameDetailItemSetting> = listOf() // 组件设置
) {
class GameDetailItemSetting(
val type: String = "",
val order: Order = Order(),
)
}

View File

@ -0,0 +1,29 @@
package com.gh.gamecenter.gamedetail.entity
import androidx.annotation.Keep
import com.gh.gamecenter.common.entity.LinkEntity
import com.google.gson.annotations.SerializedName
@Keep
data class GameDetailTabEntity(
@SerializedName("_id")
var id: String = "",
var name: String = "",
var type: String = "", // tab类型game_detail详情、comment评论、archive云存档、bbs论坛、zone专区、webweb链接、libao礼包、custom_page自定义页面
@SerializedName("archive_config_url")
var archiveConfigUrl: String = "", // type=archive时才有此云存档配置
var link: LinkEntity? = null, // 只有论坛、web链接、自定义页面才有此字段数据
@SerializedName("default_data")
var defaultData: List<GameDetailData>? = null, // type=game_detail时才有默认数据
) {
companion object {
const val TYPE_DETAIL = "game_detail"
const val TYPE_COMMENT = "comment"
const val TYPE_ARCHIVE = "archive"
const val TYPE_BBS = "bbs"
const val TYPE_ZONE = "zone"
const val TYPE_WEB = "web"
const val TYPE_GIFT = "libao"
const val TYPE_CUSTOM_PAGE = "custom_page"
}
}

View File

@ -1,12 +0,0 @@
package com.gh.gamecenter.gamedetail.entity
import androidx.annotation.Keep
@Keep
class GameInfoItemData(
var info: String = "",
var title: String = "",
var actionStr: String = "",
var action2Str: String = "",
var key: String = ""
)

View File

@ -1,98 +0,0 @@
package com.gh.gamecenter.gamedetail.entity
import androidx.annotation.Keep
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.entity.*
import com.gh.gamecenter.feature.entity.*
import com.google.gson.annotations.SerializedName
@Keep
class NewGameDetailEntity(
@SerializedName("show_comment")
var showComment: Boolean = false,//游戏评论开关
@SerializedName("direct_comment")
var directComment: Boolean = false,//安装游戏才能评论
var star: Float = 0f,
@SerializedName("comment_count")
var commentCount: Int = 0,
var ranking: Ranking? = null,// 游戏榜单排名,无排名时,字段不存在
@SerializedName("top_video")
var topVideo: Video? = null,
@SerializedName("me")
var me: MeEntity? = null,
var event: BigEvent? = null,//游戏大事件
@SerializedName("detail_dialogs")
var detailDialogs: ArrayList<GameEntity.Dialog> = arrayListOf(),
@SerializedName("new_tag_style")
var tagStyle: ArrayList<TagStyleEntity> = ArrayList(),
@SerializedName("detail_tab")
var detailEntity: ArrayList<DetailEntity> = ArrayList(),
@SerializedName("zone_tab")
var zone: ZoneEntity? = null,
@SerializedName("appointment_switch_status")
var appointmentSwitchStatus: Boolean = false,
@SerializedName("recommend_age")
var recommendAge: String = "",//适龄等级
@SerializedName("_seq")
val shortId: String = "",
@SerializedName("mirror_status")
var mirrorStatus: String? = "",
@SerializedName("mirror_data")
var mirrorData: DetailEntity? = null,
@SerializedName("mirror_status2")
var mirrorStatus2: String? = "",
@SerializedName("mirror_data2")
var mirrorData2: DetailEntity? = null,
@SerializedName("bbs_tab")
var bbsTab: LinkEntity? = null,
@SerializedName("certification_tag")
var certificateTag: Screenshot? = null,
@SerializedName("content_card")
var contentCard: ArrayList<ContentCardEntity> = ArrayList(),
@SerializedName("smooth_relation_game")
var smoothRelatedGame: SimpleGame? = null,
@SerializedName("show_archive")
var showArchive: Boolean = false, //云存档开关
@SerializedName("archive_tab")
var archiveTab: ArchiveTab = ArchiveTab(),
@SerializedName("new_notice")
var newNotice: ArrayList<LinkEntity>? = null,
) {
fun isShowContentCard(gameEntity: GameEntity?): Boolean {
return contentCard.size > 1 && (gameEntity?.shouldUseMirrorInfo() == false || (gameEntity?.shouldUseMirrorInfo() == true && mirrorData?.contentCardStatus == "on"))
}
}
@Keep
class Ranking(
@SerializedName("collection_id")
var collectionId: String = "",//合集id
@SerializedName("collection_name")
var collectionName: String = "",//合集名称
@SerializedName("column_id")
var columnId: String = "",//专题id
@SerializedName("column_name")
var columnName: String = "",// 专题标题
var no: String = ""// 排名名次
)
@Keep
class BigEvent(
var time: Long = 0,
var content: String = "",
@SerializedName("high_light")
var highLight: Boolean = false,
var link: LinkEntity? = null
)
@Keep
class Screenshot(var screenshot: String = "")
@Keep
class ArchiveTab(@SerializedName("config_url") var configUrl: String = "")

View File

@ -2,8 +2,6 @@ package com.gh.gamecenter.gamedetail.fuli
import android.content.Intent
import android.view.View
import androidx.core.content.ContextCompat
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.ethanhua.skeleton.Skeleton
@ -14,13 +12,16 @@ import com.gh.gamecenter.common.base.fragment.LazyFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.eventbus.EBReuse
import com.gh.gamecenter.common.mvvm.Status
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.utils.observeNonNull
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.viewModelProvider
import com.gh.gamecenter.common.utils.viewModelProviderFromParent
import com.gh.gamecenter.core.iinterface.IScrollable
import com.gh.gamecenter.databinding.FragmentFuliBinding
import com.gh.gamecenter.eventbus.EBTypeChange
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.gamedetail.GameDetailFragment
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.GameDetailWrapperFragment
import com.halo.assistant.HaloApp
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
@ -73,11 +74,11 @@ class FuLiFragment : LazyFragment(), IScrollable {
mFuLiViewModel?.updateGameEntity(gameEntity)
}
mGameDetailViewModel?.gameDetailLiveData?.value?.data?.zone?.let {
mGameDetailViewModel?.game?.zone?.let {
mFuLiViewModel?.updateUnifiedGameDetailEntity(it)
}
mFuLiViewModel?.fuFiDataLD?.observe(this, Observer {
mFuLiViewModel?.fuFiDataLD?.observe(this) {
mViewSkeletonScreen.hide()
mBinding?.loadingContainer?.reuseLlLoading?.visibility = View.GONE
@ -86,19 +87,16 @@ class FuLiFragment : LazyFragment(), IScrollable {
mAdapter?.notifyDataSetChanged()
} else {
mBinding?.containerRl?.setBackgroundColor(
ContextCompat.getColor(
requireContext(),
com.gh.gamecenter.common.R.color.ui_surface
com.gh.gamecenter.common.R.color.ui_surface.toColor(
requireContext()
)
)
mBinding?.reuseNoneData?.root?.visibility = View.VISIBLE
}
})
}
mGameDetailViewModel?.unifiedGameDetailWithUserRelatedInfoForChildLiveData?.observeNonNull(this) { entity ->
entity.zone?.let {
mFuLiViewModel?.updateUnifiedGameDetailEntity(it)
}
mGameDetailViewModel?.zoneLiveData?.observeNonNull(this) {
mFuLiViewModel?.updateUnifiedGameDetailEntity(it)
}
}
@ -127,7 +125,7 @@ class FuLiFragment : LazyFragment(), IScrollable {
.show()
mAdapter = FuLiAdapter(requireContext(), mFuLiViewModel, mEntrance)
mBinding?.fmFuliRv?.adapter = mAdapter
layoutManager = object : androidx.recyclerview.widget.LinearLayoutManager(context) {
layoutManager = object : LinearLayoutManager(context) {
override fun canScrollVertically(): Boolean {
return isCanScroll
}
@ -137,7 +135,7 @@ class FuLiFragment : LazyFragment(), IScrollable {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
EventBus.getDefault().post(EBTypeChange(GameDetailFragment.EB_SCROLLING, 0))
EventBus.getDefault().post(EBTypeChange(GameDetailWrapperFragment.EB_SCROLLING, 0))
}
}
})

View File

@ -3,10 +3,10 @@ package com.gh.gamecenter.gamedetail.fuli
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.gh.gamecenter.common.entity.CommunityEntity
import com.gh.gamecenter.common.mvvm.Resource
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.ZoneContentEntity
import com.gh.gamecenter.feature.entity.ZoneEntity
import com.gh.gamecenter.common.mvvm.Resource
class FuLiViewModel : ViewModel() {
@ -21,7 +21,7 @@ class FuLiViewModel : ViewModel() {
fun updateUnifiedGameDetailEntity(zoneEntity: ZoneEntity) {
if (zoneEntity.style == "default") {
if (!zoneEntity.content.isNullOrEmpty()) {
if (zoneEntity.content.isNotEmpty()) {
fuFiDataLD.postValue(Resource.success(zoneEntity.content))
} else {//无内容
fuFiDataLD.postValue(Resource.error(null))

View File

@ -1,34 +1,31 @@
package com.gh.gamecenter.gamedetail.history
import android.content.Context
import android.util.SparseBooleanArray
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.g00fy2.versioncompare.Version
import com.gh.gamecenter.common.constant.ItemViewType
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.common.exposure.IExposable
import com.gh.common.util.*
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.common.util.DownloadItemUtils
import com.gh.common.util.PackageUtils
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.baselist.ListAdapter
import com.gh.gamecenter.common.constant.ItemViewType
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.utils.DialogHelper
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.common.utils.getMetaExtra
import com.gh.gamecenter.common.utils.setRootBackgroundColor
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.databinding.ItemHistoryApkBinding
import com.gh.gamecenter.feature.entity.ApkEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.view.DownloadButton
import com.gh.gamecenter.gamedetail.dialog.GameDetailScrollableTextDialogFragment
import com.gh.gamecenter.manager.PackagesManager
import com.lightgame.download.DownloadEntity
import com.lightgame.download.DownloadStatus
@ -42,10 +39,6 @@ class HistoryApkListAdapter(
private val mBasicExposureSource by lazy { ExposureSource("历史版本", "") }
private var mExpandSparseBooleanArray = SparseBooleanArray()
private var mDescExpandedMarginRight = -1
private var mDescShrankMarginRight = -1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val view: View
return when (viewType) {
@ -62,25 +55,26 @@ class HistoryApkListAdapter(
is HistoryApkViewHolder -> {
val gameEntity = mEntityList[position]
val apkEntity = gameEntity.getApk().first()
val maxDesLines = if (mExpandSparseBooleanArray.get(holder.adapterPosition)) Int.MAX_VALUE else 3
val exposureEvent =
ExposureEvent.createEvent(gameEntity = gameEntity, source = listOf(mBasicExposureSource))
holder.binding.root.setRootBackgroundColor(com.gh.gamecenter.common.R.color.ui_surface)
holder.binding.expandTv.background = R.drawable.bg_ui_surface_expand_gradient.toDrawable(mContext)
holder.binding.expandTv.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(mContext))
holder.binding.updateDescTv.text = apkEntity.updateDesc
holder.binding.updateDescTv.post {
holder.binding.expandTv.isVisible = holder.binding.updateDescTv.lineCount == 3 && holder.binding.updateDescTv.layout.getEllipsisCount(2) > 0
}
holder.binding.versionTv.text = "版本${apkEntity.version}"
holder.binding.updateDescTv.setExpandMaxLines(maxDesLines)
holder.binding.updateDescTv.setIsExpanded(maxDesLines == Int.MAX_VALUE)
holder.binding.releaseDateTv.text = TimeUtils.getFormatTime(apkEntity.updateTime)
holder.binding.updateDescTv.setExpandCallback {
mExpandSparseBooleanArray.put(holder.adapterPosition, true)
updateDescMarginRight(
holder.binding.updateDescTv,
mExpandSparseBooleanArray.get(holder.adapterPosition),
holder.binding.downloadBtn.visibility == View.VISIBLE
holder.binding.expandTv.setOnClickListener {
GameDetailScrollableTextDialogFragment.show(
mContext,
gameEntity.id,
gameEntity.name ?: "",
"更新说明",
apkEntity.updateDesc
)
MtaHelper.onEvent("历史版本", "展开", "${mViewModel.game?.name}+${apkEntity.version}")
}
holder.binding.downloadBtn.run {
setOnTouchListener { _, event ->
@ -155,34 +149,10 @@ class HistoryApkListAdapter(
text = downloadText
}
}
updateDescMarginRight(
holder.binding.updateDescTv,
mExpandSparseBooleanArray.get(holder.adapterPosition),
holder.binding.downloadBtn.visibility == View.VISIBLE
)
}
}
}
// damn, 设计师说要存在下载按钮和不存在下载按钮时有不一样的收起/展开效果
private fun updateDescMarginRight(descTv: TextView, isExpanded: Boolean, isDownloadBtnVisible: Boolean) {
if (mDescExpandedMarginRight == -1) {
mDescExpandedMarginRight = 0
mDescShrankMarginRight =
mContext.resources.getDimension(com.gh.gamecenter.common.R.dimen.history_apk_desc_shrank_margin_right).toInt()
}
val constraintSet = ConstraintSet()
constraintSet.clone(descTv.parent as ConstraintLayout)
if (!isDownloadBtnVisible || isExpanded) {
constraintSet.setMargin(descTv.id, ConstraintSet.RIGHT, mDescExpandedMarginRight)
} else {
constraintSet.setMargin(descTv.id, ConstraintSet.RIGHT, mDescShrankMarginRight)
}
constraintSet.applyTo(descTv.parent as ConstraintLayout)
}
private fun souldShowDowngradeDialog(apkEntity: ApkEntity, btnText: CharSequence): Boolean {
when (btnText) {
"下载" -> MtaHelper.onEvent("历史版本", "下载", "${mViewModel.game?.name}_${apkEntity.version}")

View File

@ -0,0 +1,43 @@
package com.gh.gamecenter.gamedetail.libao
import android.content.Context
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.baselist.ListAdapter
import com.gh.gamecenter.common.constant.ItemViewType
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.viewholder.FooterViewHolder
import com.gh.gamecenter.feature.entity.LibaoEntity
import com.gh.gamecenter.gamedetail.detail.GameLibaoAdapter.LibaoViewHolder
class LibaoListAdapter(
context: Context,
private val viewModel: LibaoListViewModel,
private val gameId: String,
private val gameName: String
) : ListAdapter<LibaoEntity>(context) {
override fun getItemCount() =
if (mEntityList.isNullOrEmpty()) 0 else mEntityList.size + FOOTER_ITEM_COUNT
override fun getItemViewType(position: Int) =
when (position) {
itemCount - 1 -> ItemViewType.ITEM_FOOTER
else -> ItemViewType.ITEM_BODY
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
ItemViewType.ITEM_BODY -> LibaoViewHolder(parent.toBinding())
else -> FooterViewHolder(mLayoutInflater.inflate(com.gh.gamecenter.common.R.layout.refresh_footerview, parent, false))
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is LibaoViewHolder -> {
val entity = mEntityList[position]
holder.bindItem(mContext, this, gameId, gameName, entity)
}
is FooterViewHolder -> holder.initFooterViewHolder(viewModel, mIsLoading, mIsNetworkError, mIsOver)
}
}
}

View File

@ -0,0 +1,92 @@
package com.gh.gamecenter.gamedetail.libao
import android.os.Bundle
import android.view.View
import android.widget.RelativeLayout
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.baselist.LazyListFragment
import com.gh.gamecenter.common.baselist.ListAdapter
import com.gh.gamecenter.common.json.json
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.marqueeOnce
import com.gh.gamecenter.common.utils.viewModelProvider
import com.gh.gamecenter.feature.entity.LibaoEntity
class LibaoListFragment : LazyListFragment<LibaoEntity, LibaoListViewModel>() {
private var gameId = ""
private var gameName = ""
private var hideToolbar = false
private var adapter: LibaoListAdapter? = null
override fun getRealLayoutId() = R.layout.fragment_libao_list
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
gameId = arguments?.getString(ARG_GAME_ID) ?: ""
gameName = arguments?.getString(ARG_GAME_NAME) ?: ""
hideToolbar = arguments?.getBoolean(HIDE_TOOLBAR, false) ?: false
}
override fun initRealView() {
super.initRealView()
val toolbarContainer = mCachedView.findViewById<RelativeLayout>(R.id.reuseToolbar)
val toolbarTitleTv = toolbarContainer.findViewById<TextView>(R.id.normal_title)
val toolbarBackContainer = toolbarContainer.findViewById<View>(com.gh.gamecenter.selector.R.id.backContainer)
toolbarBackContainer.setOnClickListener { requireActivity().onBackPressed() }
toolbarContainer.goneIf(hideToolbar) {
toolbarTitleTv.text = "${gameName}-礼包"
toolbarTitleTv.marqueeOnce()
}
}
override fun onFragmentFirstVisible() {
super.onFragmentFirstVisible()
SensorsBridge.trackEvent("GameGiftPageView", json {
"game_id" to gameId
"game_name" to gameName
"last_page_name" to GlobalActivityManager.getLastPageEntity().pageName
"last_page_id" to GlobalActivityManager.getLastPageEntity().pageId
})
}
override fun provideListAdapter(): ListAdapter<*> =
adapter ?: LibaoListAdapter(
requireContext(),
mListViewModel ?: provideListViewModel(),
gameId,
gameName
).apply { adapter = this }
override fun provideListViewModel(): LibaoListViewModel =
viewModelProvider<LibaoListViewModel>(LibaoListViewModel.Factory(arguments?.getString(ARG_GAME_ID) ?: ""))
override fun getItemDecoration(): RecyclerView.ItemDecoration? = null
companion object {
private const val ARG_GAME_ID = "game_id"
private const val ARG_GAME_NAME = "game_name"
private const val HIDE_TOOLBAR = "hide_toolbar"
fun getBundle(gameId: String, gameName: String, hideToolbar: Boolean) = Bundle().apply {
putString(ARG_GAME_ID, gameId)
putString(ARG_GAME_NAME, gameName)
putBoolean(HIDE_TOOLBAR, hideToolbar)
}
fun newInstance(bundle: Bundle?) = LibaoListFragment().apply {
if (bundle != null) {
arguments = bundle
}
}
}
}

View File

@ -0,0 +1,72 @@
package com.gh.gamecenter.gamedetail.libao
import android.annotation.SuppressLint
import android.app.Application
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.gh.common.util.LibaoUtils
import com.gh.gamecenter.common.baselist.ListViewModel
import com.gh.gamecenter.common.utils.observableToMain
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.core.utils.UrlFilterUtils
import com.gh.gamecenter.feature.entity.LibaoEntity
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.retrofit.RetrofitManager
import com.halo.assistant.HaloApp
import com.lightgame.utils.Utils
import io.reactivex.Observable
import io.reactivex.Single
class LibaoListViewModel(application: Application, private val gameId: String) :
ListViewModel<LibaoEntity, LibaoEntity>(application) {
private val api = RetrofitManager.getInstance().api
override fun provideDataObservable(page: Int): Observable<MutableList<LibaoEntity>>? = null
@SuppressLint("CheckResult")
override fun provideDataSingle(page: Int): Single<MutableList<LibaoEntity>> {
return Single.create { emitter ->
api.getGameLibaoList(gameId, page)
.compose(singleToMain())
.subscribe({ libaoList ->
if (libaoList.isEmpty()) {
emitter.onSuccess(libaoList)
} else {
val builder = StringBuilder()
var i = 0
val size = libaoList.size
while (i < size) {
builder.append(libaoList[i].id)
builder.append("-")
i++
}
builder.deleteCharAt(builder.length - 1)
val ids = builder.toString()
api.getLibaoStatus(UrlFilterUtils.getFilterQuery("libao_ids", ids))
.compose(observableToMain())
.subscribe({
LibaoUtils.initLiBaoEntity(it, libaoList)
GameDetailViewModel.sortLibaoList(libaoList)
emitter.onSuccess(libaoList)
}, {
Utils.toast(getApplication(), "获取礼包状态失败")
emitter.onSuccess(libaoList)
})
}
}, {
emitter.onError(it)
})
}
}
override fun mergeResultLiveData() {
mResultLiveData.addSource(mListLiveData) { list ->
mResultLiveData.postValue(list)
}
}
class Factory(private val gameId: String) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return LibaoListViewModel(HaloApp.getInstance().application, gameId) as T
}
}
}

View File

@ -25,11 +25,11 @@ import com.gh.gamecenter.common.utils.toPx
import com.gh.gamecenter.core.iinterface.IScrollable
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.SpanBuilder
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.entity.RatingComment
import com.gh.gamecenter.eventbus.EBStar
import com.gh.gamecenter.eventbus.EBTypeChange
import com.gh.gamecenter.gamedetail.GameDetailFragment
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.gamedetail.GameDetailWrapperFragment
import com.halo.assistant.HaloApp
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
@ -161,7 +161,7 @@ class RatingFragment : LazyListFragment<RatingComment, RatingViewModel>(), IScro
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
EventBus.getDefault().post(EBTypeChange(GameDetailFragment.EB_SCROLLING, 0))
EventBus.getDefault().post(EBTypeChange(GameDetailWrapperFragment.EB_SCROLLING, 0))
}
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
var position = mLayoutManager.findLastCompletelyVisibleItemPosition()

View File

@ -0,0 +1,133 @@
package com.gh.gamecenter.gamedetail.video
import android.graphics.Rect
import android.os.Handler
import android.os.Looper
import android.view.View
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.NetworkUtils
import com.gh.gamecenter.core.utils.MD5Utils
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.home.video.ScrollCalculatorHelper
import com.gh.gamecenter.video.detail.CustomManager
import com.halo.assistant.HaloApp
import com.lightgame.utils.Utils
class GameDetailScrollCalculatorHelper(val listRv: RecyclerView, private val playId: Int, private val rangeTop: Int) {
private var firstVisible = -1
private var lastVisible = 0
private var runnable: PlayRunnable? = null
private val playHandler = Handler(Looper.getMainLooper())
var currentPlayer: TopVideoView? = null
fun onScrollStateChanged(firstVisibleItem: Int, lastVisibleItem: Int, scrollState: Int) {
if (scrollState == RecyclerView.SCROLL_STATE_IDLE) {
firstVisible = firstVisibleItem
lastVisible = lastVisibleItem
stopIfNeeded()
playVideo(listRv)
}
}
//判断player是否划出了屏幕划出了屏幕则需要释放
private fun stopIfNeeded() {
if (currentPlayer != null) {
//保存进度
val currentScheduler = currentPlayer?.currentPositionWhenPlaying?.toLong() ?: 0L
ScrollCalculatorHelper.savePlaySchedule(MD5Utils.getContentMD5(currentPlayer?.getUrl()), currentScheduler)
currentPlayer?.initUIState()
// currentPlayer?.resetDetailMask()
currentPlayer = null
runnable?.let { playHandler.removeCallbacks(it) }
}
}
fun release() {
if (currentPlayer != null) {
val currentScheduler = currentPlayer?.currentPositionWhenPlaying?.toLong() ?: 0L
ScrollCalculatorHelper.savePlaySchedule(MD5Utils.getContentMD5(currentPlayer?.getUrl()), currentScheduler)
CustomManager.releaseAllVideos(currentPlayer?.getKey())
runnable?.let { playHandler.removeCallbacks(it) }
// currentPlayer?.resetDetailMask()
currentPlayer = null
}
}
private fun playVideo(view: RecyclerView?) {
if (view == null) return
val layoutManager = view.layoutManager
var gsyBaseVideoPlayer: TopVideoView
for (i in firstVisible until lastVisible + 1) {
if (layoutManager == null) return
val child = listRv.findViewHolderForAdapterPosition(i)?.itemView
val player: View? = child?.findViewById(playId)
if (player == null || player.visibility != View.VISIBLE || player !is TopVideoView) continue
val rect = Rect()
player.getLocalVisibleRect(rect)
val width = player.width
val height = player.height
if (rect.left == 0 && rect.right == width && rect.top == 0 && rect.bottom == height) {
gsyBaseVideoPlayer = player
if (runnable != null) {
playHandler.removeCallbacks(runnable!!)
runnable = null
}
if (currentPlayer == gsyBaseVideoPlayer) return
val screenPosition = IntArray(2)
gsyBaseVideoPlayer.getLocationInWindow(screenPosition)
val rangePosition = screenPosition[1]
if (rangePosition >= rangeTop) {
runnable?.let { playHandler.removeCallbacks(it) }
runnable = PlayRunnable(gsyBaseVideoPlayer)
if (currentPlayer != null) {
currentPlayer?.initUIState()
// currentPlayer?.resetDetailMask()
}
//降低频率
playHandler.postDelayed(runnable!!, 100)
break
}
}
}
}
private inner class PlayRunnable(var videoView: TopVideoView?) : Runnable {
override fun run() {
if (videoView != null && !videoView!!.isInPlayingState) {
val videoOption =
SPUtils.getString(Constants.SP_HOME_OR_DETAIL_VIDEO_OPTION, Constants.VIDEO_OPTION_WIFI)
?: Constants.VIDEO_OPTION_WIFI
when (videoOption) {
Constants.VIDEO_OPTION_ALL -> {
startPlayLogic(videoView)
}
Constants.VIDEO_OPTION_WIFI -> {
if (NetworkUtils.isWifiConnected(HaloApp.getInstance().application)) {
startPlayLogic(videoView)
}
}
else -> {
//do nothing
}
}
}
}
}
private fun startPlayLogic(videoView: TopVideoView?) {
Utils.log("GameDetailScrollCalculatorHelper", "startPlayLogic $videoView")
videoView?.startPlayLogic(true)
currentPlayer = videoView
}
}

View File

@ -8,9 +8,9 @@ import android.view.*
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.SeekBar
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import com.gh.common.util.LogUtils
import com.gh.download.cache.ExoCacheManager
import com.gh.gamecenter.R
@ -20,15 +20,14 @@ import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.core.runOnUiThread
import com.gh.gamecenter.core.utils.MD5Utils
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.StringUtils
import com.gh.gamecenter.gamedetail.GameDetailViewModel
import com.gh.gamecenter.gamedetail.entity.Video
import com.gh.gamecenter.gamedetail.entity.CoverTabEntity
import com.gh.gamecenter.home.video.ScrollCalculatorHelper
import com.gh.gamecenter.video.detail.CustomManager
import com.lightgame.utils.Utils
import com.shuyu.gsyvideoplayer.utils.CommonUtil
import com.shuyu.gsyvideoplayer.utils.Debuger
import com.shuyu.gsyvideoplayer.utils.GSYVideoType
import com.shuyu.gsyvideoplayer.video.StandardGSYVideoPlayer
import com.shuyu.gsyvideoplayer.video.base.GSYVideoView
import com.shuyu.gsyvideoplayer.video.base.GSYVideoViewBridge
@ -42,7 +41,7 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS
private var mVolumeObserver: VolumeObserver? = null
var gameName = ""
var video: Video? = null
var video: CoverTabEntity.Video? = null
var viewModel: GameDetailViewModel? = null
var uuid = UUID.randomUUID().toString()
private var mMuteDisposable: Disposable? = null
@ -50,9 +49,6 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS
private var mIsAutoPlay = false
private var mLastGetContentLengthTime = 0L
val combinedTitleAndId: String
get() = StringUtils.combineTwoString(video?.title, video?.videoId)
init {
post {
gestureDetector =
@ -89,11 +85,11 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS
}
setBackFromFullScreenListener {
if (it.id == R.id.fullscreen) {
MtaHelper.onEvent("游戏详情_顶部视频", "${getMtaKeyPrefix()}-退出全屏", combinedTitleAndId)
} else if (it.id == R.id.back) {
MtaHelper.onEvent("游戏详情_顶部视频", "${getMtaKeyPrefix()}-点击返回", combinedTitleAndId)
}
// if (it.id == R.id.fullscreen) {
// MtaHelper.onEvent("游戏详情_顶部视频", "${getMtaKeyPrefix()}-退出全屏", combinedTitleAndId)
// } else if (it.id == R.id.back) {
// MtaHelper.onEvent("游戏详情_顶部视频", "${getMtaKeyPrefix()}-点击返回", combinedTitleAndId)
// }
clearFullscreenLayout()
}
@ -112,39 +108,34 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS
//这个必须配置最上面的构造才能生效
override fun getLayoutId(): Int {
return R.layout.layout_game_detail_video_portrait
return R.layout.layout_new_game_detail_video_portrait
}
fun observeVolume(fragment: Fragment?) {
fragment?.context?.applicationContext?.contentResolver?.registerContentObserver(
android.provider.Settings.System.CONTENT_URI, true, mVolumeObserver!!
)
}
fragment?.fragmentManager?.registerFragmentLifecycleCallbacks(
object : FragmentManager.FragmentLifecycleCallbacks() {
override fun onFragmentPaused(fm: FragmentManager, f: Fragment) {
if (f === fragment) {
fragment.context?.applicationContext?.contentResolver?.unregisterContentObserver(mVolumeObserver!!)
fragment.fragmentManager?.unregisterFragmentLifecycleCallbacks(this)
}
}
}, false
)
fun unObserveVolume(activity: AppCompatActivity) {
activity.contentResolver?.unregisterContentObserver(mVolumeObserver!!)
}
fun startPlayLogic(isAutoPlay: Boolean) {
mIsAutoPlay = isAutoPlay
violenceUpdateMuteStatus()
if (isAutoPlay) {
MtaHelper.onEvent("游戏详情_顶部视频", "视频播放方式", "自动播放")
MtaHelper.onEvent("游戏详情_顶部视频", "顶部视频-自动播放", combinedTitleAndId)
} else {
MtaHelper.onEvent("游戏详情_顶部视频", "视频播放方式", "手动播放")
}
// if (isAutoPlay) {
// MtaHelper.onEvent("游戏详情_顶部视频", "视频播放方式", "自动播放")
// MtaHelper.onEvent("游戏详情_顶部视频", "顶部视频-自动播放", combinedTitleAndId)
// } else {
// MtaHelper.onEvent("游戏详情_顶部视频", "视频播放方式", "手动播放")
// }
if (isAutoPlay) {
val seekTime = ScrollCalculatorHelper.getPlaySchedule(MD5Utils.getContentMD5(video?.url))
seekOnStart = seekTime
mTouchingProgressBar = false
}
GSYVideoType.setShowType(GSYVideoType.SCREEN_TYPE_FULL)
startPlayLogic()
}
@ -162,6 +153,12 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow()
disposableTimer()
unObserveVolume(context as AppCompatActivity)
}
fun disposableTimer() {
if (mMuteDisposable != null && !mMuteDisposable!!.isDisposed) {
mMuteDisposable?.dispose()
@ -192,7 +189,7 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS
if (isManual) {
Utils.toast(context, "当前处于静音状态")
uploadVideoStreamingPlaying("点击静音")
MtaHelper.onEvent("游戏详情_顶部视频", "${getMtaKeyPrefix()}-点击静音", combinedTitleAndId)
// MtaHelper.onEvent("游戏详情_顶部视频", "${getMtaKeyPrefix()}-点击静音", combinedTitleAndId)
}
}
@ -207,13 +204,13 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS
)
if (isManual) {
uploadVideoStreamingPlaying("取消静音")
MtaHelper.onEvent("游戏详情_顶部视频", "${getMtaKeyPrefix()}-解除静音", combinedTitleAndId)
// MtaHelper.onEvent("游戏详情_顶部视频", "${getMtaKeyPrefix()}-解除静音", combinedTitleAndId)
}
}
override fun onSeekComplete() {
super.onSeekComplete()
MtaHelper.onEvent("游戏详情_顶部视频", "${getMtaKeyPrefix()}-拖动进度条", combinedTitleAndId)
// MtaHelper.onEvent("游戏详情_顶部视频", "${getMtaKeyPrefix()}-拖动进度条", combinedTitleAndId)
}
// 重载以减少横竖屏切换的时间
@ -262,12 +259,16 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS
return uuid
}
fun getUrl(): String {
return mUrl
}
override fun onAutoCompletion() {
// 这个方法在内核被释放时也会被回调,所以可以用来统计播放量和播放时长
val playedTime = (gsyVideoManager.currentPosition / 1000).toInt()
MtaHelper.onEventWithTime("视频播放量_按位置", playedTime, "游戏详情-顶部视频", combinedTitleAndId)
MtaHelper.onEventWithTime("视频播放量_游戏加位置", playedTime, gameName, "游戏详情-顶部视频")
// MtaHelper.onEventWithTime("视频播放量_按位置", playedTime, "游戏详情-顶部视频", combinedTitleAndId)
// MtaHelper.onEventWithTime("视频播放量_游戏加位置", playedTime, gameName, "游戏详情-顶部视频")
//播放完成后判断是否已缓冲完毕,没有完成显示播放错误状态
if (mBufferPoint != 0 && mBufferPoint != 100 && isShown) {
@ -353,7 +354,7 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS
findViewById<LinearLayout>(R.id.replayContainer)?.visibility = View.VISIBLE
mTopContainer.visibility = View.VISIBLE
findViewById<ImageView>(R.id.replayIv).setOnClickListener {
MtaHelper.onEvent("游戏详情_顶部视频", "${getMtaKeyPrefix()}-点击重新播放", combinedTitleAndId)
// MtaHelper.onEvent("游戏详情_顶部视频", "${getMtaKeyPrefix()}-点击重新播放", combinedTitleAndId)
startButton.performClick()
violenceUpdateMuteStatus()
uploadVideoStreamingPlaying("重新播放")
@ -390,13 +391,6 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS
return R.drawable.ic_game_detail_exit_full_screen
}
override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
if (!isIfCurrentIsFullscreen) {
parent.requestDisallowInterceptTouchEvent(true)
}
return super.onInterceptTouchEvent(ev)
}
/******************* 下方两个重载方法,在播放开始前不屏蔽封面,不需要可屏蔽 ********************/
override fun onSurfaceUpdated(surface: Surface) {
@ -419,21 +413,25 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS
override fun changeUiToNormal() {
super.changeUiToNormal()
findViewById<LinearLayout>(R.id.errorContainer)?.visibility = View.GONE
findViewById<LinearLayout>(R.id.loadingContainer)?.visibility = View.GONE
}
override fun changeUiToPreparingShow() {
super.changeUiToPreparingShow()
findViewById<LinearLayout>(R.id.errorContainer)?.visibility = View.GONE
findViewById<LinearLayout>(R.id.loadingContainer)?.visibility = View.VISIBLE
}
override fun changeUiToPlayingShow() {
super.changeUiToPlayingShow()
findViewById<LinearLayout>(R.id.errorContainer)?.visibility = View.GONE
findViewById<LinearLayout>(R.id.loadingContainer)?.visibility = View.GONE
}
override fun changeUiToPauseShow() {
super.changeUiToPauseShow()
findViewById<LinearLayout>(R.id.errorContainer)?.visibility = View.GONE
findViewById<LinearLayout>(R.id.loadingContainer)?.visibility = View.GONE
}
fun showFullPauseBitmap() {
@ -450,12 +448,39 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS
override fun changeUiToCompleteShow() {
super.changeUiToCompleteShow()
findViewById<LinearLayout>(R.id.errorContainer)?.visibility = View.GONE
findViewById<LinearLayout>(R.id.loadingContainer)?.visibility = View.GONE
}
override fun changeUiToError() {
super.changeUiToError()
setViewShowState(mStartButton, View.INVISIBLE)
findViewById<LinearLayout>(R.id.errorContainer)?.visibility = View.VISIBLE
findViewById<LinearLayout>(R.id.loadingContainer)?.visibility = View.GONE
}
override fun changeUiToPrepareingClear() {
super.changeUiToPrepareingClear()
findViewById<LinearLayout>(R.id.loadingContainer)?.visibility = View.GONE
}
override fun changeUiToPlayingBufferingShow() {
super.changeUiToPlayingBufferingShow()
findViewById<LinearLayout>(R.id.loadingContainer)?.visibility = View.VISIBLE
}
override fun changeUiToPlayingBufferingClear() {
super.changeUiToPlayingBufferingClear()
findViewById<LinearLayout>(R.id.loadingContainer)?.visibility = View.VISIBLE
}
override fun changeUiToClear() {
super.changeUiToClear()
findViewById<LinearLayout>(R.id.loadingContainer)?.visibility = View.GONE
}
override fun changeUiToCompleteClear() {
super.changeUiToCompleteClear()
findViewById<LinearLayout>(R.id.loadingContainer)?.visibility = View.GONE
}
override fun netWorkErrorLogic() {
@ -497,11 +522,11 @@ class TopVideoView @JvmOverloads constructor(context: Context, attrs: AttributeS
override fun onClick(v: View) {
when (v.id) {
R.id.start -> {
if (currentState == GSYVideoView.CURRENT_STATE_PLAYING) {
MtaHelper.onEvent("游戏详情_顶部视频", "${getMtaKeyPrefix()}-点击暂停", combinedTitleAndId)
} else {
MtaHelper.onEvent("游戏详情_顶部视频", "${getMtaKeyPrefix()}-点击播放", combinedTitleAndId)
}
// if (currentState == GSYVideoView.CURRENT_STATE_PLAYING) {
// MtaHelper.onEvent("游戏详情_顶部视频", "${getMtaKeyPrefix()}-点击暂停", combinedTitleAndId)
// } else {
// MtaHelper.onEvent("游戏详情_顶部视频", "${getMtaKeyPrefix()}-点击播放", combinedTitleAndId)
// }
super.onClick(v)
}

View File

@ -101,6 +101,7 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable {
private var bottomTabId = ""
private var bottomTabName = ""
private var tabIndex = -1
private var showFloatingWindow = true
private lateinit var pageLocation: PageLocation
@ -131,6 +132,7 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable {
bottomTabId = arguments?.getString(EntranceConsts.KEY_BOTTOM_TAB_ID, "") ?: ""
bottomTabName = arguments?.getString(EntranceConsts.KEY_BOTTOM_TAB_NAME, "") ?: ""
tabIndex = arguments?.getInt(EntranceConsts.KEY_TAB_INDEX, -1) ?: -1
showFloatingWindow = arguments?.getBoolean(EntranceConsts.KEY_SHOW_FLOATING_WINDOW, true) ?: true
val tabName = arguments?.getString(EntranceConsts.KEY_TAB_NAME, "") ?: ""
val multiTabNavId = arguments?.getString(EntranceConsts.KEY_MULTI_TAB_NAV_ID, "") ?: ""
val multiTabNavName = arguments?.getString(EntranceConsts.KEY_MULTI_TAB_NAV_NAME, "") ?: ""
@ -555,37 +557,41 @@ class CustomPageFragment : LazyFragment(), ISmartRefreshContent, IScrollable {
}
private fun buildPriorityChain() {
val floatingWindowHandler = CustomFloatingWindowHandler(23)
val videoHandler = VideoHandler(24, scrollCalculatorHelper)
priorityChain.addHandler(pullDownPushHandler)
priorityChain.addHandler(floatingWindowHandler)
if (showFloatingWindow) {
val floatingWindowHandler = CustomFloatingWindowHandler(23)
priorityChain.addHandler(floatingWindowHandler)
viewModel.floatingWindows.observe(viewLifecycleOwner, EventObserver {
floatingWindowHandler.setData(it)
})
floatingWindowHandler.showFloatingAction.observe(viewLifecycleOwner, EventObserver {
binding.floatingView.setData(
it,
pageLocation,
viewModel.pageConfigure.exposureSourceList,
floatingWindowHandler
)
floatViewManager.show(binding.floatingView)
})
binding.floatingView.setExpandListener { entity, hasAnimation ->
val dialog = CustomWelcomeDialogFragment.getInstance(entity, true, this, hasAnimation, pageLocation)
dialog.setDismissListener {
floatViewManager.resume()
floatingWindowHandler.dismiss()
}
dialog.show(childFragmentManager, CustomWelcomeDialogFragment.TAG)
}
}
priorityChain.addHandler(videoHandler)
(parentFragment as? BaseTabWrapperFragment)?.addTabGuideHandlerIfExists(priorityChain)
viewModel.floatingWindows.observe(viewLifecycleOwner, EventObserver {
floatingWindowHandler.setData(it)
})
floatingWindowHandler.showFloatingAction.observe(viewLifecycleOwner, EventObserver {
binding.floatingView.setData(
it,
pageLocation,
viewModel.pageConfigure.exposureSourceList,
floatingWindowHandler
)
floatViewManager.show(binding.floatingView)
})
binding.floatingView.setExpandListener { entity, hasAnimation ->
val dialog = CustomWelcomeDialogFragment.getInstance(entity, true, this, hasAnimation, pageLocation)
dialog.setDismissListener {
floatViewManager.resume()
floatingWindowHandler.dismiss()
}
dialog.show(childFragmentManager, CustomWelcomeDialogFragment.TAG)
}
if (superiorChain != null) {
superiorChain?.registerInferiorChain(priorityChain)
} else {

View File

@ -1,5 +1,6 @@
package com.gh.gamecenter.home.custom
import android.annotation.SuppressLint
import android.app.Application
import androidx.collection.ArrayMap
import androidx.lifecycle.AndroidViewModel
@ -10,9 +11,12 @@ import com.gh.common.util.GameUtils
import com.gh.common.util.NewFlatLogUtils
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.entity.ErrorEntity
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.common.utils.toObject
import com.gh.gamecenter.core.utils.RandomUtils
import com.gh.gamecenter.entity.PullDownPush
import com.gh.gamecenter.entity.RatingComment
@ -105,6 +109,8 @@ class CustomPageViewModel(
val pageLocation: PageLocation
get() = pageTracker.pageLocation
val pkVoteResultLiveData = MutableLiveData<Event<Pair<String, Boolean>>>()
fun init(
pageConfigure: PageConfigure,
searchToolbarTabWrapperViewModel: SearchToolbarTabWrapperViewModel?,
@ -746,6 +752,25 @@ class CustomPageViewModel(
_gameListSquareDestination.value = Event(item)
}
@SuppressLint("CheckResult")
fun postPKVote(pkId: String, isPositive: Boolean) {
repository.postPKVote(pkId, isPositive)
.compose(singleToMain())
.subscribe({
pkVoteResultLiveData.postValue(Event(Pair(pkId, isPositive)))
}, {
if (it is HttpException) {
val errorString = it.response().errorBody()?.string()
val errorEntity = errorString?.toObject<ErrorEntity>()
if (!errorEntity?.toast.isNullOrEmpty()) {
Utils.toast(getApplication(), errorEntity?.toast)
}
} else {
Utils.toast(getApplication(), "投票失败")
}
})
}
fun hideNotificationItem(id: String, itemId: String) {
repository.hideNotificationItem(id, itemId)
}

View File

@ -1,203 +0,0 @@
package com.gh.gamecenter.home.custom.adapter
import android.content.Context
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.*
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.adapter.viewholder.GameViewHolder
import com.gh.gamecenter.common.base.GlobalActivityManager
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.StringUtils
import com.gh.gamecenter.entity.SubjectEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.game.horizontal.GameHorizontalSimpleItemViewHolder
import com.gh.gamecenter.home.PageConfigure
import com.gh.gamecenter.home.custom.model.CustomSubjectItem
import com.gh.gamecenter.home.custom.viewholder.CustomGameHorizontalItemViewHolder
import com.gh.gamecenter.feature.minigame.MiniGameItemHelper
import com.lightgame.download.DownloadEntity
class CustomGameHorizontalAdapter(
context: Context,
private val pageConfigure: PageConfigure
) : CustomBaseChildAdapter<GameEntity, CustomGameHorizontalItemViewHolder>(context) {
var gameName = ""
var gameId = ""
var game: GameEntity? = null
var path = ""
var exposureEventList: ArrayList<ExposureEvent>? = null
private val mDefaultHorizontalPadding by lazy { com.gh.gamecenter.common.R.dimen.game_detail_item_horizontal_padding.toPx() }
private lateinit var _data: CustomSubjectItem
private val subjectEntity: SubjectEntity
get() = _data.data
private val type: GameHorizontalListType
get() = if (subjectEntity.isMiniGame) {
GameHorizontalListType.MiniGameSubjectHorizontalType
} else {
GameHorizontalListType.SubjectHorizontalType
}
fun setData(data: CustomSubjectItem) {
_data = data
submitList(data.data.data)
}
override fun getKey(t: GameEntity): String {
return t.id
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) =
CustomGameHorizontalItemViewHolder(parent.toBinding())
override fun getItemCount(): Int {
val size = dataList.size
return when (type) {
GameHorizontalListType.GameDetailHorizontalType -> {
size
}
GameHorizontalListType.MiniGameSubjectHorizontalType -> {
dataList.size
}
else -> when {
size < 4 -> size
size < 8 -> 4
else -> 8
}
}
}
override fun onBindViewHolder(holder: CustomGameHorizontalItemViewHolder, position: Int) {
val params = holder.binding.root.layoutParams as RecyclerView.LayoutParams
params.width =
if (gameName.isNotBlank()) RecyclerView.LayoutParams.WRAP_CONTENT else RecyclerView.LayoutParams.MATCH_PARENT
if (type == GameHorizontalListType.GameDetailHorizontalType) {
params.leftMargin = if (position == 0) mDefaultHorizontalPadding else 8F.dip2px()
params.rightMargin = if (position == itemCount - 1) mDefaultHorizontalPadding else 0F.dip2px()
}
holder.binding.root.layoutParams = params
val gameEntity = getItem(position)
holder.binding.simpleGameContainer.run {
gameIcon.displayGameIcon(gameEntity)
GameHorizontalSimpleItemViewHolder.setHorizontalNameAndGravity(gameName, gameEntity.name)
downloadBtn.goneIf(!subjectEntity.showDownload)
gameName.maxLines = if (subjectEntity.showDownload) 1 else 2
}
holder.bindGameHorizontalItem(gameEntity, subjectEntity, false, false)
val entranceResult: String
val locationResult: String
if (type == GameHorizontalListType.GameDetailHorizontalType) {
entranceResult = StringUtils.buildString(
pageConfigure.entrance,
"+(",
"游戏详情",
"[",
gameName,
"]:${path}[",
(position + 1).toString(),
"])"
)
locationResult = StringUtils.buildString("游戏详情-", gameName, "-${path}", ":", gameEntity.name)
} else {
entranceResult =
StringUtils.buildString("(游戏-专题:", subjectEntity.name, "-列表[", (position + 1).toString(), "])")
locationResult = StringUtils.buildString("游戏-专题-", subjectEntity.name, ":", gameEntity.name)
}
holder.itemView.setOnClickListener {
if (type == GameHorizontalListType.GameDetailHorizontalType) {
DataCollectionUtils.uploadClick(context, path, "游戏详情", gameEntity.name)
NewLogUtils.logGameDetailPopularClick(gameName, gameId, "game", gameEntity.name ?: "")
SensorsBridge.trackGameDetailPagePopularClick(
gameId = gameId,
gameName = gameName,
pageName = GlobalActivityManager.getCurrentPageEntity().pageName,
pageId = GlobalActivityManager.getCurrentPageEntity().pageId,
pageBusinessId = GlobalActivityManager.getCurrentPageEntity().pageBusinessId,
lastPageName = GlobalActivityManager.getLastPageEntity().pageName,
lastPageId = GlobalActivityManager.getLastPageEntity().pageId,
lastPageBusinessId = GlobalActivityManager.getLastPageEntity().pageBusinessId,
downloadStatus = game?.downloadStatusChinese ?: "",
gameType = game?.categoryChinese ?: "",
clickGameType = gameEntity.categoryChinese,
clickGameName = gameEntity.name ?: "",
clickGameId = gameEntity.id
)
}
if (gameEntity.isMiniGame()) {
MiniGameItemHelper.launchMiniGame(gameEntity)
} else {
GameDetailActivity.startGameDetailActivity(
context,
gameEntity.id,
entranceResult,
exposureEventList?.get(position)
)
}
}
if (subjectEntity.showDownload && type != GameHorizontalListType.GameDetailHorizontalType) {
DownloadItemUtils.setOnClickListener(
context,
holder.binding.simpleGameContainer.downloadBtn,
gameEntity,
position,
this,
entranceResult,
location = locationResult,
traceEvent = if (position < (exposureEventList?.size ?: 0)) exposureEventList!![position] else null
)
DownloadItemUtils.updateItem(
context,
gameEntity,
GameViewHolder(holder.binding.simpleGameContainer.root).apply {
gameDownloadBtn = holder.binding.simpleGameContainer.downloadBtn
gameDownloadTips = holder.binding.simpleGameContainer.downloadTipsLottie
multiVersionDownloadTv = holder.binding.simpleGameContainer.multiVersionDownloadTv
},
)
}
}
fun notifyItemByDownload(downloadEntity: DownloadEntity?) {
if (downloadEntity == null) {
notifyDataSetChanged()
} else {
subjectEntity.data?.forEachIndexed { position, gameEntity ->
if (downloadEntity.gameId == gameEntity.id) {
notifyItemChanged(position)
return
}
}
}
}
fun notifyChildItem(packageName: String) {
subjectEntity.data?.forEachIndexed { position, gameEntity ->
gameEntity.getApk().forEach { apkEntity ->
if (apkEntity.packageName == packageName) {
notifyItemChanged(position)
return
}
}
}
}
}
sealed class GameHorizontalListType {
object GameDetailHorizontalType : GameHorizontalListType()
object SubjectHorizontalType : GameHorizontalListType()
object MiniGameSubjectHorizontalType : GameHorizontalListType()
}

View File

@ -50,6 +50,7 @@ import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_MINI_GAME_RECENT_PLAY
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_NAVIGATION
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_NEW_GAME_TEST
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_PK
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_PLUGIN
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_RANK_COLLECTION
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.CUSTOM_PAGE_ITEM_TYPE_RECENT_PLAY
@ -274,6 +275,8 @@ class CustomPageAdapter(
CUSTOM_PAGE_ITEM_TYPE_FOOTER -> CustomFooterViewHolder(viewModel, parent.toBinding())
CUSTOM_PAGE_ITEM_TYPE_PK -> CustomPKItemViewHolder(viewModel, lifecycleOwner, parent.toBinding())
else -> InvalidViewHolder(viewModel, parent.toBinding())
}

View File

@ -4,9 +4,10 @@ import com.gh.common.filter.RegionSettingHelper
import com.gh.gamecenter.common.entity.CommunityEntity
import com.gh.gamecenter.common.entity.Display
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.entity.PKEntity
import com.gh.gamecenter.entity.*
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.FloatingWindowEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.IgnoredOnParcel
@ -78,7 +79,10 @@ class CustomPageData(
@SerializedName("link_common_collection")
val linkCommonCollection: CommonContentCollection? = null,
@SerializedName("link_qq_game_recently_played")
val linkQqGameRecentlyPlayed: LinkQqGameRecentlyPlayed? = null
val linkQqGameRecentlyPlayed: LinkQqGameRecentlyPlayed? = null,
@SerializedName("link_pk")
val linkPK: LinkEntity? = null,
var pkData: PKEntity? = null,
) {
// 游戏专题
val gameSubjectEntity: SubjectEntity?

View File

@ -2,6 +2,7 @@ package com.gh.gamecenter.home.custom.model
import com.gh.common.util.ViewPagerFragmentHelper
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.entity.PKEntity
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.core.utils.TimeUtils
import com.gh.gamecenter.entity.*
@ -170,6 +171,7 @@ abstract class CustomPageItem(
const val CUSTOM_PAGE_ITEM_TYPE_MINI_GAME_RECENT_PLAY = 35
const val CUSTOM_PAGE_ITEM_TYPE_SINGLE_GAME_CARD = 36
const val CUSTOM_PAGE_ITEM_TYPE_DOUBLE_GAME_WELFARE_CARD = 37
const val CUSTOM_PAGE_ITEM_TYPE_PK = 38
// 专题样式 to itemType
val subjectTypeMap: HashMap<String, Int> = hashMapOf(
@ -849,6 +851,26 @@ data class CustomSplitCommonContentCollectionItem(
}
}
// PK
data class CustomPKItem(
private val _link: LinkEntity,
val data: PKEntity,
private val _position: Int,
private val _componentPosition: Int
) : CustomPageItem(_link, _position, _componentPosition) {
override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_PK
override fun doAreContentsTheSames(other: CustomPageItem): Boolean {
return other is CustomPKItem
&& link == other.link
&& data == other.data
&& position == other.position
&& componentPosition == other.componentPosition
}
}
object CustomFooterItem : CustomPageItem(LinkEntity(), -1, -1) {
override val itemType: Int
get() = CUSTOM_PAGE_ITEM_TYPE_FOOTER

View File

@ -1,17 +1,23 @@
package com.gh.gamecenter.home.custom.model
import android.annotation.SuppressLint
import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.entity.PKEntity
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.common.utils.toRequestBody
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.entity.PullDownPush
import com.gh.gamecenter.entity.SubjectEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.FloatingWindowEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.retrofit.service.ApiService
import com.gh.gamecenter.wrapper.MainWrapperRepository
import com.halo.assistant.HaloApp
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.SingleEmitter
import okhttp3.ResponseBody
class CustomPageRemoteDataSource(
private val api: ApiService,
@ -26,17 +32,52 @@ class CustomPageRemoteDataSource(
return newApi.loadPullDownPush(pageId, BuildConfig.VERSION_NAME, HaloApp.getInstance().channel)
}
@SuppressLint("CheckResult")
fun loadCustomPageData(pageId: String, page: Int, forceLoad: Boolean = false): Single<CustomPageData> {
return if (page == 1 &&
pageId == mainWrapperRepository.defaultCustomPageId &&
mainWrapperRepository.customPageLiveData.value != null &&
!forceLoad
) {
Single.create {
it.onSuccess(mainWrapperRepository.customPageLiveData.value!!)
return Single.create { emitter ->
val customPageData = mainWrapperRepository.customPageLiveData.value
if (page == 1 &&
pageId == mainWrapperRepository.defaultCustomPageId &&
customPageData != null &&
!forceLoad
) {
loadPKDataIfExists(customPageData, emitter)
} else {
newApi.getCustomPageData(pageId, page)
.compose(singleToMain())
.subscribe({
loadPKDataIfExists(it, emitter)
}, {
emitter.onError(it)
})
}
}
}
@SuppressLint("CheckResult")
private fun loadPKDataIfExists(customPageData: CustomPageData, emitter: SingleEmitter<CustomPageData>) {
val pkIdList = arrayListOf<String>()
customPageData.customsComponents.forEach { data ->
if (data.linkPK != null) {
data.linkPK.link?.let { id -> pkIdList.add(id) }
}
}
if (pkIdList.isNotEmpty()) {
loadPKData(pkIdList)
.compose(singleToMain())
.subscribe({ pkList ->
pkList.forEach { pkEntity ->
val data = customPageData.customsComponents.find { it.linkPK?.link == pkEntity.id }
if (data != null) {
data.pkData = pkEntity
}
}
emitter.onSuccess(customPageData)
}, {
emitter.onSuccess(customPageData)
})
} else {
newApi.getCustomPageData(pageId, page)
emitter.onSuccess(customPageData)
}
}
@ -65,4 +106,18 @@ class CustomPageRemoteDataSource(
return api.getDiscoveryGames(1, paramsMap)
.map { it.games }
}
fun loadPKData(pkIdList: List<String>): Single<List<PKEntity>> {
val data = mapOf(
"pk_ids" to pkIdList
)
return newApi.getPKList(data.toRequestBody())
}
fun postPKVote(pkId: String, isPositive: Boolean): Single<ResponseBody> {
val data = mapOf(
"result" to if (isPositive) "option1" else "option2"
)
return newApi.postPK(pkId, data.toRequestBody())
}
}

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