diff --git a/app/build.gradle b/app/build.gradle index 86f16ccd03..ebef876050 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -503,6 +503,8 @@ andResGuard { "R.id.vote", "R.id.watermark_hint", "R.id.watermark_sb", + "R.id.bottomShareIv", + "R.id.bottomShareTv", ] compressFilePattern = [ "*.png", diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 7e4ec48515..970b3d2e58 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -45,7 +45,7 @@ com.google.android.exoplayer2, tv.danmaku.ijk.media.exo2, pl.droidsonroids.gif, - com.lzf.easyfloat"/> + com.lzf.easyfloat" /> + android:screenOrientation="portrait" /> + + + + + + + + + + + + + + + + + + + : ToolBarAct mRichEditor.insertCustomStyleLink(insertData) } } + INSERT_GAME_COLLECTION_CODE -> { + val gameCollectionEntity = data?.getParcelableExtra(GamesCollectionEntity::class.java.simpleName) + if (gameCollectionEntity != null) { + mRichEditor.focusEditor() + insertData = EditorInsertEntity.transform(gameCollectionEntity) + mRichEditor.insertCustomStyleLink(insertData) + } + } REQUEST_CODE_IMAGE -> { if (data != null) mViewModel.uploadPic(data) } @@ -248,7 +254,7 @@ abstract class BaseRichEditorActivity : ToolBarAct R.id.editor_paragraph_h4, R.id.editor_font_container, R.id.editor_paragraph_container, R.id.editor_paragraph_quote, R.id.editor_link_answer, R.id.editor_link_article, R.id.editor_link_game, R.id.editor_link_video, R.id.uploadVideoGuideClose, - R.id.originalTipsClose + R.id.originalTipsClose, R.id.editor_link_game_collection ) fun onRichClick(view: View) { when (view.id) { @@ -354,6 +360,12 @@ abstract class BaseRichEditorActivity : ToolBarAct INSERT_GAME_CODE ) } + R.id.editor_link_game_collection -> { + startActivityForResult( + InsertGameCollectionWrapperActivity.getIntent(this), + INSERT_GAME_COLLECTION_CODE + ) + } R.id.editor_link_video -> { chooseVideo() } @@ -714,6 +726,7 @@ abstract class BaseRichEditorActivity : ToolBarAct const val INSERT_ANSWER_CODE = 411 const val INSERT_ARTICLE_CODE = 412 const val INSERT_GAME_CODE = 413 + const val INSERT_GAME_COLLECTION_CODE = 414 const val MAX_INPUT_TEXT_NUM = 10000 const val MAX_MEDIA_COUNT = 20 diff --git a/app/src/main/java/com/gh/common/DefaultUrlHandler.kt b/app/src/main/java/com/gh/common/DefaultUrlHandler.kt index c42d1a4cae..f5816300a2 100644 --- a/app/src/main/java/com/gh/common/DefaultUrlHandler.kt +++ b/app/src/main/java/com/gh/common/DefaultUrlHandler.kt @@ -340,6 +340,11 @@ object DefaultUrlHandler { val position = uri.getQueryParameter("position") ?: "" DirectUtils.directToHelpAndFeedback(context, position.toInt()) } + + EntranceUtils.HOST_GAME_COLLECTION_DETAIL -> { + DirectUtils.directToGameCollectionDetail(context, id, entrance) + } + else -> DialogHelper.showUpgradeDialog(context) } return true diff --git a/app/src/main/java/com/gh/common/constant/Constants.java b/app/src/main/java/com/gh/common/constant/Constants.java index d88fe7798d..4f2135007c 100644 --- a/app/src/main/java/com/gh/common/constant/Constants.java +++ b/app/src/main/java/com/gh/common/constant/Constants.java @@ -234,6 +234,8 @@ public class Constants { public static final String SP_SHOULD_SHOW_GAME_DETAIL_INSTALL_GUIDE = "should_show_game_detail_install_guide"; // 儿童/青少年模式 public static final String SP_TEENAGER_MODE = "teenager_mode"; + // 我的游戏引导 + public static final String SP_MY_GAME_GUIDE = "my_game_guide"; //手机号码匹配规则 public static final String REGEX_MOBILE = "^((13[0-9])|(15[^4,\\D])|(18[0,5-9]))\\d{8}$"; @@ -344,12 +346,19 @@ public class Constants { public static final String ACTIVITY_DETAIL_ADDRESS_DEV = "https://static-web.ghzs.com/ghzs_activity_dev/common.html?from=ghzs"; public static final String ACTIVITY_DETAIL_ADDRESS = "https://static-web.ghzs.com/ghzs_activity_prod/common.html?from=ghzs"; + // 游戏单详情分享链接 + public static final String GAME_COLLECTION_SHARE_ADDRESS_DEV = "https://dev-and-static.ghzs.com/web/game_collection/index.html#/?from=ghzs"; + public static final String GAME_COLLECTION_SHARE_ADDRESS = "https://and-static.ghzs.com/web/game_collection/index.html#/?from=ghzs"; + // 青少年模式找回密码 public static final String TEEN_MODE_RESET_PASSWORD = "https://resource.ghzs.com/page/privacy_policies/help_password.html"; // 儿童/青少年使用须知 public static final String TEEN_MODE_RULE = "https://resource.ghzs.com/page/privacy_policies/teenager_privacy.html"; + //游戏单管理规范 + public static final String GAME_COLLECTION_RULE = "https://resource.ghzs.com/page/privacy_policies/game_collection.html"; + //最少需要多少数据才能上传 public static final int DATA_AMOUNT = 20; diff --git a/app/src/main/java/com/gh/common/constant/ItemViewType.java b/app/src/main/java/com/gh/common/constant/ItemViewType.java index 57a3dab273..9408d2a5e9 100644 --- a/app/src/main/java/com/gh/common/constant/ItemViewType.java +++ b/app/src/main/java/com/gh/common/constant/ItemViewType.java @@ -37,6 +37,7 @@ public class ItemViewType { public static final int BLANK_DIVIDER = 29; // 空白补充区域 public static final int COMMON_LINK_COLLECTION = 30; // 通用链接合集 public static final int RANK_COLLECTION = 31; // 排行榜样式专题合集 + public static final int GAME_COLLECTION_ITEM = 32; // 游戏单 /** * 普通列表 diff --git a/app/src/main/java/com/gh/common/history/HistoryDatabase.kt b/app/src/main/java/com/gh/common/history/HistoryDatabase.kt index 0dd544fd51..477dabf9a5 100644 --- a/app/src/main/java/com/gh/common/history/HistoryDatabase.kt +++ b/app/src/main/java/com/gh/common/history/HistoryDatabase.kt @@ -6,6 +6,7 @@ import androidx.room.RoomDatabase 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.HistoryGameEntity import com.gh.gamecenter.entity.MyVideoEntity import com.gh.gamecenter.entity.NewsEntity @@ -15,7 +16,7 @@ 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], version = 9, exportSchema = false) +@Database(entities = [AnswerEntity::class, ArticleEntity::class, NewsEntity::class, HistoryGameEntity::class, MyVideoEntity::class, GamesCollectionEntity::class], version = 10, exportSchema = false) @TypeConverters(CountConverter::class, CommunityConverter::class, TimeConverter::class, @@ -28,7 +29,10 @@ import com.halo.assistant.HaloApp UserConverter::class, ImageInfoConverter::class, VideoInfoConverter::class, - QuestionsConverter::class) + QuestionsConverter::class, + MeConverter::class, + SimpleGameListConverter::class, + TagInfoListConverter::class) abstract class HistoryDatabase : RoomDatabase() { @@ -37,6 +41,7 @@ abstract class HistoryDatabase : RoomDatabase() { abstract fun newsDao(): NewsHistoryDao abstract fun gameDao(): GameDao abstract fun videoHistoryDao(): VideoHistoryDao + abstract fun gamesCollectionDao(): GamesCollectionDao companion object { @@ -101,6 +106,13 @@ abstract class HistoryDatabase : RoomDatabase() { } } + val MIGRATION_9_10: Migration = object : Migration(9, 10) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL("CREATE TABLE GamesCollectionEntity (id TEXT NOT NULL PRIMARY KEY, tags TEXT DEFAULT '', games TEXT DEFAULT '', title TEXT NOT NULL DEFAULT '', intro TEXT NOT NULL DEFAULT '', cover TEXT NOT NULL DEFAULT '', display TEXT NOT NULL DEFAULT '', stamp TEXT NOT NULL DEFAULT '', count TEXT NOT NULL DEFAULT '', user TEXT NOT NULL DEFAULT '', me TEXT NOT NULL DEFAULT '', orderTag INTEGER NOT NULL DEFAULT 0)" + ) + } + } + val instance by lazy { Room.databaseBuilder(HaloApp.getInstance().application, HistoryDatabase::class.java, "USER_TRACK_HISTORY_DATABASE") .addMigrations(MIGRATION_2_3) @@ -110,6 +122,7 @@ abstract class HistoryDatabase : RoomDatabase() { .addMigrations(MIGRATION_6_7) .addMigrations(MIGRATION_7_8) .addMigrations(MIGRATION_8_9) + .addMigrations(MIGRATION_9_10) .build() } } diff --git a/app/src/main/java/com/gh/common/history/HistoryHelper.kt b/app/src/main/java/com/gh/common/history/HistoryHelper.kt index 9c096fd90f..b428e7c1c7 100644 --- a/app/src/main/java/com/gh/common/history/HistoryHelper.kt +++ b/app/src/main/java/com/gh/common/history/HistoryHelper.kt @@ -24,6 +24,11 @@ object HistoryHelper { runOnIoThread { tryCatchInRelease { HistoryDatabase.instance.articleDao().addArticle(articleEntity) } } } + fun insertGamesCollectionEntity(gamesCollectionDetailEntity: GamesCollectionDetailEntity) { + val gamesCollectionEntity = convertGamesCollectionDetailToGamesCollection(gamesCollectionDetailEntity) + runOnIoThread { tryCatchInRelease { HistoryDatabase.instance.gamesCollectionDao().addGamesCollection(gamesCollectionEntity) } } + } + @JvmStatic fun insertGameEntity(gameEntity: GameEntity) { val historyGameEntity = convertGameEntityToHistoryGameEntity(gameEntity) @@ -145,4 +150,29 @@ object HistoryHelper { return answerEntity } + private fun convertGamesCollectionDetailToGamesCollection(gamesCollectionDetailEntity: GamesCollectionDetailEntity): GamesCollectionEntity { + val gamesCollectionEntity = GamesCollectionEntity() + + gamesCollectionEntity.id = gamesCollectionDetailEntity.id + gamesCollectionEntity.tags = gamesCollectionDetailEntity.tags + gamesCollectionEntity.games = ArrayList(gamesCollectionDetailEntity.games?.take(3)?.map { it.toSimpleGame() }) + gamesCollectionEntity.title = gamesCollectionDetailEntity.title + gamesCollectionEntity.intro = gamesCollectionDetailEntity.intro + gamesCollectionEntity.cover = gamesCollectionDetailEntity.cover + gamesCollectionEntity.display = gamesCollectionDetailEntity.display + gamesCollectionEntity.orderTag = System.currentTimeMillis() + gamesCollectionEntity.stamp = gamesCollectionDetailEntity.stamp + gamesCollectionEntity.count = gamesCollectionDetailEntity.count + gamesCollectionDetailEntity.user?.run { + gamesCollectionEntity.user = User( + id = id ?: "", + name = name ?: "", + icon = icon ?: "", + badge = badge) + } + gamesCollectionEntity.me = gamesCollectionDetailEntity.me + + return gamesCollectionEntity + } + } \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/util/CommentHelper.kt b/app/src/main/java/com/gh/common/util/CommentHelper.kt index 9c08dbc093..f36fbc471c 100644 --- a/app/src/main/java/com/gh/common/util/CommentHelper.kt +++ b/app/src/main/java/com/gh/common/util/CommentHelper.kt @@ -107,6 +107,20 @@ object CommentHelper { ) } + fun showGameCollectionCommentOption( + view: View, + commentEntity: CommentEntity, + listener: OnCommentOptionClickListener? + ) { + showCommentOptions( + view = view, + commentEntity = commentEntity, + showConversation = false, + ignoreModerator = true, + listener = listener + ) + } + private fun showCommentOptions( view: View, commentEntity: CommentEntity, diff --git a/app/src/main/java/com/gh/common/util/DirectUtils.kt b/app/src/main/java/com/gh/common/util/DirectUtils.kt index 4d69ca035b..b895a35fa1 100644 --- a/app/src/main/java/com/gh/common/util/DirectUtils.kt +++ b/app/src/main/java/com/gh/common/util/DirectUtils.kt @@ -35,10 +35,12 @@ import com.gh.gamecenter.fragment.MainWrapperFragment import com.gh.gamecenter.game.columncollection.detail.ColumnCollectionDetailActivity import com.gh.gamecenter.game.commoncollection.detail.CommonCollectionDetailActivity import com.gh.gamecenter.game.upload.GameSubmissionActivity +import com.gh.gamecenter.gamecollection.detail.GameCollectionDetailActivity import com.gh.gamecenter.gamedetail.GameDetailFragment import com.gh.gamecenter.gamedetail.fuli.kaifu.ServersCalendarActivity import com.gh.gamecenter.gamedetail.history.HistoryApkListActivity import com.gh.gamecenter.gamedetail.rating.RatingReplyActivity +import com.gh.gamecenter.gamecollection.square.GameCollectionSquareActivity import com.gh.gamecenter.manager.UserManager import com.gh.gamecenter.mygame.PlayedGameActivity import com.gh.gamecenter.personalhome.UserHomeActivity @@ -353,6 +355,8 @@ object DirectUtils { //"h5_game_center" -> directLetoGameCenter(context) + "game_list" -> directToGameCollectionSquare(context, entrance, "", "", "") + "" -> { // do nothing } @@ -1642,4 +1646,31 @@ object DirectUtils { bundle.putString(KEY_COLLECTION_ID, collectionId) jumpActivity(context, bundle) } + + /** + * 跳转至游戏单广场 + */ + @JvmStatic + fun directToGameCollectionSquare(context: Context, entrance: String = "", forumName: String = "", gameCollectionTitle: String = "", gameCollectionId: String = "") { + val bundle = Bundle() + bundle.putString(KEY_TO, GameCollectionSquareActivity::class.java.name) + bundle.putString(KEY_ENTRANCE, entrance) + bundle.putString(KEY_FORUM_NAME, forumName) + bundle.putString(KEY_GAME_COLLECTION_TITLE, gameCollectionTitle) + bundle.putString(KEY_GAME_COLLECTION_ID, gameCollectionId) + jumpActivity(context, bundle) + } + + /** + * 跳转至游戏单详情 + */ + @JvmStatic + fun directToGameCollectionDetail(context: Context, id: String, entrance: String? = null) { + if (id.isEmpty()) return + val bundle = Bundle() + bundle.putString(KEY_ENTRANCE, entrance ?: ENTRANCE_BROWSER) + bundle.putString(KEY_TO, GameCollectionDetailActivity::class.java.name) + bundle.putString(KEY_GAME_COLLECTION_ID, id) + jumpActivity(context, bundle) + } } \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/util/EntranceUtils.java b/app/src/main/java/com/gh/common/util/EntranceUtils.java index 6a061d2211..cdb3660999 100644 --- a/app/src/main/java/com/gh/common/util/EntranceUtils.java +++ b/app/src/main/java/com/gh/common/util/EntranceUtils.java @@ -94,6 +94,7 @@ public class EntranceUtils { public static final String HOST_GAME_RATING_DETAIL = "game_rating_detail"; public static final String HOST_HELP_AND_FEEDBACK = "help_and_feedback"; public static final String HOST_LAUNCH_SIMULATOR_GAME = "launch_simulator_game"; + public static final String HOST_GAME_COLLECTION_DETAIL = "game_collection_detail"; public static final String KEY_DATA = "data"; public static final String KEY_MESSAGE = "message"; public static final String KEY_MESSAGE_ID = "message_id"; @@ -251,6 +252,11 @@ public class EntranceUtils { public static final String KEY_PARENT_TAG = "parent_tag"; public static final String KEY_BLOCK_ID = "block_id"; public static final String KEY_BLOCK_NAME = "block_name"; + public static final String KEY_INSERT_GAME_COLLECTION = "insert_game_collection"; + public static final String KEY_IS_FROM_SQUARE = "is_from_square"; + public static final String KEY_FORUM_NAME = "forum_name";//版块名称 + public static final String KEY_GAME_COLLECTION_TITLE = "game_collection_title";//游戏单标题 + public static final String KEY_GAME_COLLECTION_ID = "game_collection_id";//游戏单ID public static void jumpActivity(Context context, Bundle bundle) { bundle.putBoolean(KEY_REQUIRE_REDIRECT, true); diff --git a/app/src/main/java/com/gh/common/util/ErrorHelper.kt b/app/src/main/java/com/gh/common/util/ErrorHelper.kt index 2a9ed4927c..8190457165 100644 --- a/app/src/main/java/com/gh/common/util/ErrorHelper.kt +++ b/app/src/main/java/com/gh/common/util/ErrorHelper.kt @@ -2,6 +2,7 @@ package com.gh.common.util import android.content.Context import com.gh.common.constant.Config +import com.gh.common.constant.Constants import com.gh.gamecenter.R import com.gh.gamecenter.WebActivity import com.gh.gamecenter.entity.ErrorEntity @@ -105,6 +106,9 @@ object ErrorHelper { 403057, 403068 -> handleErrorWithCommentBannedDialog(context, errorEntity) + 403200, + 403201, + 403202 -> handleErrorWithGameCollectionBannedDialog(context, errorEntity) 403001 -> Utils.toast(context, "标签名称太长了") 403002 -> Utils.toast(context, "已经被邀请了") @@ -161,6 +165,30 @@ object ErrorHelper { } } + private fun handleErrorWithGameCollectionBannedDialog(context: Context, errorEntity: ErrorEntity?) { + val bannedType = if (errorEntity?.data?.alwaysBlock == true) { + "" + } else { + "(非永久)" + } + val dialogContext = DialogUtils.checkDialogContext(context) + DialogHelper.showDialog( + dialogContext, + "提示", + "你因违反《游戏单管理规范》,已被禁言$bannedType,如有疑问,请联系客服(QQ:${Config.getSettings()?.support?.qq})", + "去看看", "关闭", { + dialogContext.startActivity( + WebActivity.getWebIntent( + dialogContext, + "游戏单管理规范", + Constants.GAME_COLLECTION_RULE + ) + ) + }, + extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true) + ) + } + private fun handleErrorWithCommentBannedDialog(context: Context, errorEntity: ErrorEntity?) { val bannedType = if (errorEntity?.data?.alwaysBlock == true) { "" @@ -173,7 +201,13 @@ object ErrorHelper { "提示", "你因违反《光环助手评论规则》,已被禁言$bannedType,如有疑问,请联系客服(QQ:${Config.getSettings()?.support?.qq})", "去看看", "关闭", { - dialogContext.startActivity(WebActivity.getWebIntent(dialogContext, dialogContext.getString(R.string.comment_rules_title), dialogContext.getString(R.string.comment_rules_url))) + dialogContext.startActivity( + WebActivity.getWebIntent( + dialogContext, + dialogContext.getString(R.string.comment_rules_title), + dialogContext.getString(R.string.comment_rules_url) + ) + ) }, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true) ) @@ -189,9 +223,15 @@ object ErrorHelper { DialogHelper.showDialog( dialogContext, "提示", - "你因违反《问答版块规则》,已被禁言$bannedType,如有疑问,请联系客服(QQ:1562479331)", + "你因违反《问答版块规则》,已被禁言$bannedType,如有疑问,请联系客服(QQ:${Config.getSettings()?.support?.qq})", "去看看", "关闭", { - dialogContext.startActivity(WebActivity.getWebIntent(dialogContext, dialogContext.getString(R.string.community_rule_title), dialogContext.getString(R.string.community_rule_url))) + dialogContext.startActivity( + WebActivity.getWebIntent( + dialogContext, + dialogContext.getString(R.string.community_rule_title), + dialogContext.getString(R.string.community_rule_url) + ) + ) }, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true) ) diff --git a/app/src/main/java/com/gh/common/util/NewLogUtils.kt b/app/src/main/java/com/gh/common/util/NewLogUtils.kt index cb03b1dc8a..1ceb71e44b 100644 --- a/app/src/main/java/com/gh/common/util/NewLogUtils.kt +++ b/app/src/main/java/com/gh/common/util/NewLogUtils.kt @@ -1615,4 +1615,169 @@ object NewLogUtils { } log(json, "event", false) } + + //进入我的游戏单 + fun logEnterMyGameCollection() { + val json = json { + "event" to "enter_game_collect_self_location" + "timestamp" to System.currentTimeMillis() / 1000 + parseAndPutMeta().invoke(this) + } + log(json, "event", false) + } + + //进入创建游戏单 + fun logEnterGameCollectionEdit(entrance: String) { + val json = json { + "event" to "enter_game_collect_create_location" + "entrance" to entrance + "timestamp" to System.currentTimeMillis() / 1000 + parseAndPutMeta().invoke(this) + } + log(json, "event", false) + } + + //进入游戏单广场 + fun logEnterGameCollectionSquare(entrance: String, forumName: String = "", title: String = "", id: String = "") { + val json = json { + "event" to "enter_game_collect_square" + "entrance" to entrance + "forum_name" to forumName + "game_collect_title" to title + "game_collect_id" to id + "timestamp" to System.currentTimeMillis() / 1000 + parseAndPutMeta().invoke(this) + } + log(json, "event", false) + } + + //进入选择标签 + fun logEnterGameCollectionTag() { + val json = json { + "event" to "enter_game_collect_tag_location" + "timestamp" to System.currentTimeMillis() / 1000 + parseAndPutMeta().invoke(this) + } + log(json, "event", false) + } + + //筛选游戏单标签 + fun logFilterGameCollectionTag(tagCategory: String, tagName: String) { + val json = json { + "event" to "filter_game_collect_tag" + "filter_tag_category" to tagCategory + "filter_tag_name" to tagName + "timestamp" to System.currentTimeMillis() / 1000 + parseAndPutMeta().invoke(this) + } + log(json, "event", false) + } + + //点击安利墙卡片 + fun logClickGameCollectionAmway() { + val json = json { + "event" to "click_game_collect_recommend_card" + "timestamp" to System.currentTimeMillis() / 1000 + parseAndPutMeta().invoke(this) + } + log(json, "event", false) + } + + //进入安利墙 + fun logEnterGameCollectionAmway() { + val json = json { + "event" to "enter_game_collect_recommend_card" + "timestamp" to System.currentTimeMillis() / 1000 + parseAndPutMeta().invoke(this) + } + log(json, "event", false) + } + + //点击卡片的游戏icon + fun logClickGameCollectionGameIcon(title: String, id: String, gameName: String, gameId: String) { + val json = json { + "event" to "click_game_collect_recommend_card_icon" + "game_collect_title" to title + "game_collect_id" to id + "game_name" to gameName + "game_id" to gameId + "timestamp" to System.currentTimeMillis() / 1000 + parseAndPutMeta().invoke(this) + } + log(json, "event", false) + } + + //点击卡片的作者信息 + fun logClickGameCollectionAuthor(title: String, id: String) { + val json = json { + "event" to "click_game_collect_recommend_card_author" + "game_collect_title" to title + "game_collect_id" to id + "timestamp" to System.currentTimeMillis() / 1000 + parseAndPutMeta().invoke(this) + } + log(json, "event", false) + } + + //点击进入游戏单 + fun logEnterGameCollectionDetail(title: String, id: String) { + val json = json { + "event" to "enter_game_collect_detail" + "game_collect_title" to title + "game_collect_id" to id + "timestamp" to System.currentTimeMillis() / 1000 + parseAndPutMeta().invoke(this) + } + log(json, "event", false) + } + + //浏览游戏单/游戏单详情相关点击 + @JvmStatic + fun logViewOrClickGameCollectionDetail( + event: String, + title: String, + id: String, + shareType: String = "" + ) { + val json = json { + "event" to event + "game_collect_title" to title + "game_collect_id" to id + if (shareType.isNotEmpty()) { + "share_type" to shareType + } + "timestamp" to System.currentTimeMillis() / 1000 + parseAndPutMeta().invoke(this) + } + log(json, "event", false) + } + + //游戏单视频相关 + @JvmStatic + fun logGameCollectionVideoEvent( + event: String, + title: String, + id: String, + progress: Double, + playTime: Int, + playAction: String = "", + stopAction: String = "", + ) { + val json = json { + "event" to event + "game_collect_title" to title + "game_collect_id" to id + "progress" to progress + "play_time" to playTime + if (playAction.isNotEmpty()) { + "play_action" to playAction + } + if (stopAction.isNotEmpty()) { + "stop_action" to stopAction + } + "timestamp" to System.currentTimeMillis() / 1000 + parseAndPutMeta().invoke(this) + } + log(json, "event", false) + } } \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/util/PatternUtils.java b/app/src/main/java/com/gh/common/util/PatternUtils.java index 4459437029..8beb323628 100644 --- a/app/src/main/java/com/gh/common/util/PatternUtils.java +++ b/app/src/main/java/com/gh/common/util/PatternUtils.java @@ -55,4 +55,24 @@ public class PatternUtils { return newText; } + /** + * 判断字符串中是否有连续2个以上的换行 + */ + public static boolean isHasWrap(String text){ + String pattern = "[\\n]{2,}"; + Regex regex = new Regex(pattern); + return regex.find(text, 0) != null; + } + + /** + * 替换字符串中连续2个以上的换行为一个换行 + */ + public static String replaceWrap(String text) { + String pattern = "[\\n]{2,}"; + String newText = text; + if (isHasSpace(text)) { + newText = text.replaceAll(pattern, "\n"); + } + return newText; + } } diff --git a/app/src/main/java/com/gh/common/util/PostCommentUtils.java b/app/src/main/java/com/gh/common/util/PostCommentUtils.java index dec59ec097..7001a34262 100644 --- a/app/src/main/java/com/gh/common/util/PostCommentUtils.java +++ b/app/src/main/java/com/gh/common/util/PostCommentUtils.java @@ -284,6 +284,29 @@ public class PostCommentUtils { }); } + public static void reportGameCollectionComment(final Context context, + final String gameCollectionId, + final String commentId, + final String reportData, + final PostCommentListener listener) { + RequestBody body = RequestBody.create(MediaType.parse("application/json"), reportData); + RetrofitManager.getInstance(context).getApi() + .reportGameCollectionComment(gameCollectionId, commentId, body) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(new Response() { + @Override + public void onResponse(ResponseBody response) { + listener.postSuccess(null); + } + + @Override + public void onFailure(HttpException e) { + listener.postFailed(e); + } + }); + } + public interface PostCommentListener { void postSuccess(JSONObject response); diff --git a/app/src/main/java/com/gh/common/util/ShareUtils.java b/app/src/main/java/com/gh/common/util/ShareUtils.java index 875cee32c8..b7aebcfc82 100644 --- a/app/src/main/java/com/gh/common/util/ShareUtils.java +++ b/app/src/main/java/com/gh/common/util/ShareUtils.java @@ -87,7 +87,8 @@ public class ShareUtils { web("web链接"), userHome("个人主页"), qaDetail("QA内容详情"), - inviteFriends("邀请好友"); + inviteFriends("邀请好友"), + gameCollection("游戏单"); private String name; @@ -148,6 +149,19 @@ public class ShareUtils { ShareUtils.shareEntrance == ShareEntrance.communityArticle || ShareUtils.shareEntrance == ShareUtils.ShareEntrance.video) { NewLogUtils.logShareResult(true); + } else if (ShareUtils.shareEntrance == ShareEntrance.gameCollection) { + String shareType; + if (mShareType == ShareType.qq) { + shareType = "QQ好友"; + } else { + shareType = "QQ空间"; + } + NewLogUtils.logViewOrClickGameCollectionDetail( + "click_game_collect_detail_favorite_success", + shareEntity.getShareTitle(), + resourceId, + shareType + ); } } diff --git a/app/src/main/java/com/gh/common/util/SingletonHolder.kt b/app/src/main/java/com/gh/common/util/SingletonHolder.kt new file mode 100644 index 0000000000..aead75f1e2 --- /dev/null +++ b/app/src/main/java/com/gh/common/util/SingletonHolder.kt @@ -0,0 +1,26 @@ +package com.gh.common.util + +open class SingletonHolder(creator: () -> T) { + private var creator: (() -> T)? = creator + + @Volatile + private var instance: T? = null + + fun getInstance(): T { + val obj = instance + if (obj != null) { + return obj + } + return synchronized(this) { + val obj1 = instance + if (obj1 != null) { + obj1 + } else { + val created = creator!!() + instance = created + creator = null + created + } + } + } +} diff --git a/app/src/main/java/com/gh/common/view/StackRecyclerView.kt b/app/src/main/java/com/gh/common/view/StackRecyclerView.kt new file mode 100644 index 0000000000..d15b5aa20a --- /dev/null +++ b/app/src/main/java/com/gh/common/view/StackRecyclerView.kt @@ -0,0 +1,37 @@ +package com.gh.common.view + +import android.content.Context +import android.util.AttributeSet +import android.view.MotionEvent +import androidx.recyclerview.widget.RecyclerView +import kotlin.math.abs + +class StackRecyclerView : RecyclerView { + + private var posX: Float = 0.0F + private var posY: Float = 0.0F + + constructor(context: Context) : super(context) + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) + + override fun dispatchTouchEvent(ev: MotionEvent): Boolean { + if (ev.action == MotionEvent.ACTION_DOWN) { + posX = ev.x + posY = ev.y + } else if (ev.action == MotionEvent.ACTION_MOVE) { + if (posY - ev.y > abs(posX - ev.x) || ev.y - posY > abs(posX - ev.x)) { + parent.requestDisallowInterceptTouchEvent(false) + } else { + parent.requestDisallowInterceptTouchEvent(true) + } + } + return super.dispatchTouchEvent(ev) + } +} diff --git a/app/src/main/java/com/gh/common/view/stacklayoutmanager/StackAnimation.kt b/app/src/main/java/com/gh/common/view/stacklayoutmanager/StackAnimation.kt new file mode 100644 index 0000000000..921b7ce8aa --- /dev/null +++ b/app/src/main/java/com/gh/common/view/stacklayoutmanager/StackAnimation.kt @@ -0,0 +1,22 @@ +package com.gh.common.view.stacklayoutmanager + +import android.view.View +import com.gh.common.view.stacklayoutmanager.StackLayoutManager.ScrollOrientation + +abstract class StackAnimation(scrollOrientation: ScrollOrientation, visibleCount: Int) { + + protected val mScrollOrientation = scrollOrientation + protected var mVisibleCount = visibleCount + + internal fun setVisibleCount(visibleCount: Int) { + mVisibleCount = visibleCount + } + + /** + * 外部回调,用来做动画. + * @param firstMovePercent 第一个可视 item 移动的百分比,当即将完全移出屏幕的时候 firstMovePercent无限接近1. + * @param itemView 当前的 itemView. + * @param position 当前 itemView 对应的位置,position = 0 until visibleCount. + */ + abstract fun doAnimation(firstMovePercent: Float, itemView: View, position: Int) +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/view/stacklayoutmanager/StackLayout.kt b/app/src/main/java/com/gh/common/view/stacklayoutmanager/StackLayout.kt new file mode 100644 index 0000000000..a946350b12 --- /dev/null +++ b/app/src/main/java/com/gh/common/view/stacklayoutmanager/StackLayout.kt @@ -0,0 +1,34 @@ +package com.gh.common.view.stacklayoutmanager + +import android.view.View + +abstract class StackLayout(scrollOrientation: StackLayoutManager.ScrollOrientation, + visibleCount: Int, + perItemOffset: Int) { + + protected val mScrollOrientation = scrollOrientation + protected var mVisibleCount = visibleCount + protected var mPerItemOffset = perItemOffset + + internal fun setItemOffset(offset: Int) { + mPerItemOffset = offset + } + + internal fun getItemOffset(): Int { + return mPerItemOffset + } + + /** + * 外部回调,用来做布局. + * @param firstMovePercent 第一个可视 item 移动的百分比,当即将完全移出屏幕的时候 firstMovePercent无限接近1. + * @param itemView 当前的 itemView. + * @param position 当前 itemView 对应的位置,position = 0 until visibleCount. + */ + abstract fun doLayout(stackLayoutManager: StackLayoutManager, + scrollOffset: Int, + firstMovePercent: Float, + itemView: View, + position: Int) + + abstract fun requestLayout() +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/common/view/stacklayoutmanager/StackLayoutManager.kt b/app/src/main/java/com/gh/common/view/stacklayoutmanager/StackLayoutManager.kt new file mode 100644 index 0000000000..de14f8e396 --- /dev/null +++ b/app/src/main/java/com/gh/common/view/stacklayoutmanager/StackLayoutManager.kt @@ -0,0 +1,544 @@ +package com.gh.common.view.stacklayoutmanager + +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import androidx.recyclerview.widget.RecyclerView.* +import com.gh.common.util.DisplayUtils +import com.gh.common.util.dip2px +import com.lightgame.utils.Utils +import kotlin.math.ceil +import kotlin.math.floor + +class StackLayoutManager(scrollOrientation: ScrollOrientation, + visibleCount: Int, + animation: Class, + layout: Class) : RecyclerView.LayoutManager() { + private enum class FlingOrientation{NONE, LEFT_TO_RIGHT, RIGHT_TO_LEFT, TOP_TO_BOTTOM, BOTTOM_TO_TOP} + + enum class ScrollOrientation{LEFT_TO_RIGHT, RIGHT_TO_LEFT, TOP_TO_BOTTOM, BOTTOM_TO_TOP} + + private var mVisibleItemCount = visibleCount + + private var mScrollOrientation = scrollOrientation + + private var mScrollOffset: Int + + var isFlinging = false + + private lateinit var mOnScrollListener: OnScrollListener + private lateinit var mOnFlingListener: OnFlingListener + + //做动画的组件,支持自定义 + private var mAnimation: StackAnimation? = null + //做布局的组件,支持自定义 + private var mLayout: StackLayout? = null + + //是否是翻页效果 + private var mPagerMode = true + + //触发翻页效果的最低 Fling速度 + private var mPagerFlingVelocity = 0 + + //标志当前滚动是否是调用scrollToCenter之后触发的滚动 + private var mFixScrolling = false + + //fling的方向,用来判断是前翻还是后翻 + private var mFlingOrientation = FlingOrientation.NONE + + //当前所处item对应的位置 + private var itemPosition = 0 + + //判断item位置是否发生了改变 + private var isItemPositionChanged = false + + //item 位置发生改变的回调 + private var itemChangedListener: ItemChangedListener? = null + + interface ItemChangedListener { + fun onItemChanged(position: Int) + } + + /** + * 设置是否为ViewPager 式翻页模式. + *

+ * 当设置为 true 的时候,可以配合[StackLayoutManager.setPagerFlingVelocity]设置触发翻页的最小速度. + * @param isPagerMode 这个值默认是 false,当设置为 true 的时候,会有 viewPager 翻页效果. + */ + fun setPagerMode(isPagerMode: Boolean) { + mPagerMode = isPagerMode + } + + /** + * @return 当前是否为ViewPager翻页模式. + */ + fun getPagerMode(): Boolean { + return mPagerMode + } + + /** + * 设置触发ViewPager翻页效果的最小速度. + *

+ * 该值仅在 [StackLayoutManager.getPagerMode] == true的时候有效. + * @param velocity 默认值是2000. + */ + fun setPagerFlingVelocity(@androidx.annotation.IntRange(from = 0, to = Int.MAX_VALUE.toLong()) velocity: Int) { + mPagerFlingVelocity = Int.MAX_VALUE.coerceAtMost(0.coerceAtLeast(velocity)) + } + + /** + * @return 当前触发翻页的最小 fling 速度. + */ + fun getPagerFlingVelocity(): Int { + return mPagerFlingVelocity + } + + /** + * 设置recyclerView 静止时候可见的itemView 个数. + * @param count 可见 itemView,默认为3 + */ + fun setVisibleItemCount(@androidx.annotation.IntRange(from = 1, to = Long.MAX_VALUE)count: Int) { + mVisibleItemCount = (itemCount - 1).coerceAtMost(1.coerceAtLeast(count)) + mAnimation?.setVisibleCount(mVisibleItemCount) + } + + /** + * 获取recyclerView 静止时候可见的itemView 个数. + * @return 静止时候可见的itemView 个数,默认为3. + */ + fun getVisibleItemCount(): Int { + return mVisibleItemCount + } + + /** + * 设置 item 偏移值,即第 i 个 item 相对于 第 i-1个 item 在水平方向的偏移值,默认是40px. + * @param offset 每个 item 相对于前一个的偏移值. + */ + fun setItemOffset(offset: Int) { + mLayout?.setItemOffset(offset) + } + + /** + * 获取每个 item 相对于前一个的水平偏移值. + * @return 每个 item 相对于前一个的水平偏移值. + */ + fun getItemOffset(): Int { + return if (mLayout == null) { + 0 + } else { + mLayout!!.getItemOffset() + } + } + + /** + * 设置item 移动动画. + * @param animation item 移动动画. + */ + fun setAnimation(animation: StackAnimation) { + mAnimation = animation + } + + /** + * 获取 item 移动动画. + * @return item 移动动画. + */ + fun getAnimation(): StackAnimation? { + return mAnimation + } + + /** + * 获取StackLayoutManager 的滚动方向. + * @return StackLayoutManager 的滚动方向. + */ + fun getScrollOrientation(): ScrollOrientation { + return mScrollOrientation + } + + /** + * 返回第一个可见 itemView 的位置. + * @return 返回第一个可见 itemView 的位置. + */ + fun getFirstVisibleItemPosition(): Int { + if (width == 0 || height == 0) { + return 0 + } + return when(mScrollOrientation) { + ScrollOrientation.RIGHT_TO_LEFT -> floor((mScrollOffset * 1.0 / width)).toInt() + ScrollOrientation.LEFT_TO_RIGHT -> itemCount - 1 - ceil((mScrollOffset * 1.0 / width)).toInt() + ScrollOrientation.BOTTOM_TO_TOP -> floor((mScrollOffset * 1.0 / height)).toInt() + ScrollOrientation.TOP_TO_BOTTOM -> itemCount - 1 - ceil((mScrollOffset * 1.0 / height)).toInt() + } + } + + /** + * 设置 item 位置改变时触发的回调 + */ + fun setItemChangedListener(listener: ItemChangedListener) { + itemChangedListener = listener + } + +// constructor(scrollOrientation: ScrollOrientation) : this(scrollOrientation, 3, DefaultAnimation::class.java, DefaultLayout::class.java) +// +// constructor(scrollOrientation: ScrollOrientation, visibleCount: Int) : this(scrollOrientation, visibleCount, DefaultAnimation::class.java, DefaultLayout::class.java) + +// constructor() : this(ScrollOrientation.RIGHT_TO_LEFT) + + init { + mScrollOffset = when(mScrollOrientation) { + ScrollOrientation.RIGHT_TO_LEFT, ScrollOrientation.BOTTOM_TO_TOP -> 0 + else -> Int.MAX_VALUE + } + + if (StackAnimation::class.java.isAssignableFrom(animation)) { + try { + val cla = animation.getDeclaredConstructor(ScrollOrientation::class.java, Int::class.javaPrimitiveType) + mAnimation = cla.newInstance(scrollOrientation, visibleCount) as StackAnimation + } catch (e: Exception) { + e.printStackTrace() + } + } + if (StackLayout::class.java.isAssignableFrom(layout)) { + try { + val cla = layout.getDeclaredConstructor(ScrollOrientation::class.java, Int::class.javaPrimitiveType, Int::class.javaPrimitiveType) + mLayout = cla.newInstance(scrollOrientation, visibleCount, 30) as StackLayout + } catch (e: Exception) { + e.printStackTrace() + } + } + } + + override fun generateDefaultLayoutParams(): LayoutParams { + return LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT) + } + + override fun onAttachedToWindow(view: RecyclerView) { + super.onAttachedToWindow(view) + mOnFlingListener = object : OnFlingListener() { + override fun onFling(velocityX: Int, velocityY: Int): Boolean { + if (mPagerMode) { + when(mScrollOrientation) { + ScrollOrientation.RIGHT_TO_LEFT, ScrollOrientation.LEFT_TO_RIGHT -> { + mFlingOrientation = when { + velocityX > mPagerFlingVelocity -> FlingOrientation.RIGHT_TO_LEFT + velocityX < -mPagerFlingVelocity -> FlingOrientation.LEFT_TO_RIGHT + else -> FlingOrientation.NONE + } + if (mScrollOffset in 1 until width * (itemCount - 1)) { //边界不需要滚动 + mFixScrolling = true + } + } + else -> { + mFlingOrientation = when { + velocityY > mPagerFlingVelocity -> FlingOrientation.BOTTOM_TO_TOP + velocityY < -mPagerFlingVelocity -> FlingOrientation.TOP_TO_BOTTOM + else -> FlingOrientation.NONE + } + if (mScrollOffset in 1 until width * (itemCount - 1)) { //边界不需要滚动 + mFixScrolling = true + } + } + } + calculateAndScrollToTarget(view) + } + else { + Utils.log("$velocityX $velocityY") + } + return mPagerMode + } + } + view.onFlingListener = mOnFlingListener + + mOnScrollListener = object : OnScrollListener() { + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + if (newState == SCROLL_STATE_IDLE) { + isFlinging = false + if (!mFixScrolling) { + mFixScrolling = true + calculateAndScrollToTarget(view) + } else { + //表示此次 IDLE 是由修正位置结束触发的 + mFixScrolling = false + } + } else if (newState == SCROLL_STATE_DRAGGING) { + mFixScrolling = false + isFlinging = false + } else if (newState == SCROLL_STATE_SETTLING) { + isFlinging = true + } + } + } + view.addOnScrollListener(mOnScrollListener) + } + + override fun onDetachedFromWindow(view: RecyclerView?, recycler: RecyclerView.Recycler?) { + super.onDetachedFromWindow(view, recycler) + if (view?.onFlingListener == mOnFlingListener) { + view.onFlingListener = null + } + view?.removeOnScrollListener(mOnScrollListener) + } + + override fun canScrollHorizontally(): Boolean { + if (itemCount == 0) { + return false + } + return when (mScrollOrientation) { + ScrollOrientation.LEFT_TO_RIGHT, ScrollOrientation.RIGHT_TO_LEFT -> true + else -> false + } + } + + override fun canScrollVertically(): Boolean { + if (itemCount == 0) { + return false + } + return when (mScrollOrientation) { + ScrollOrientation.TOP_TO_BOTTOM, ScrollOrientation.BOTTOM_TO_TOP -> true + else -> false + } + } + + override fun onLayoutChildren(recycler: RecyclerView.Recycler, state: State) { + + mLayout?.requestLayout() + + removeAndRecycleAllViews(recycler) + + if (itemCount > 0) { + mScrollOffset = getValidOffset(mScrollOffset) + loadItemView(recycler) + } + } + + override fun scrollHorizontallyBy(dx: Int, recycler: RecyclerView.Recycler, state: State): Int { + return handleScrollBy(dx, recycler) + } + + override fun scrollVerticallyBy(dy: Int, recycler: RecyclerView.Recycler, state: State?): Int { + return handleScrollBy(dy, recycler) + } + + override fun scrollToPosition(position: Int) { + if (position < 0 || position >= itemCount) { + throw ArrayIndexOutOfBoundsException("$position is out of bound [0..$itemCount-1]") + } + if (position < 0) { + throw ArrayIndexOutOfBoundsException("$position is out of bound [0..$itemCount-1]") + } + mScrollOffset = getPositionOffset(position) + requestLayout() + } + + override fun smoothScrollToPosition(recyclerView: RecyclerView, state: State?, position: Int) { + if (position < 0 || position >= itemCount) { + throw ArrayIndexOutOfBoundsException("$position is out of bound [0..$itemCount-1]") + } + if (position < 0) { + throw ArrayIndexOutOfBoundsException("$position is out of bound [0..$itemCount-1]") + } + mFixScrolling = true + scrollToCenter(position, recyclerView, true) + } + + private fun updatePositionRecordAndNotify(position: Int) { + if (itemChangedListener == null) { + return + } + if (position != itemPosition) { + isItemPositionChanged = true + itemPosition = position + itemChangedListener?.onItemChanged(itemPosition) + } else { + isItemPositionChanged = false + } + } + + private fun handleScrollBy(offset: Int, recycler: RecyclerView.Recycler): Int { + //期望值,不得超过最大最小值,所以期望值不一定等于实际值 + val expectOffset = mScrollOffset + offset + //实际值 + mScrollOffset = getValidOffset(expectOffset) + + //实际偏移,超过最大最小值之后的偏移都应该是0,该值作为返回值,否则在极限位置进行滚动的时候不会出现弹性阴影 + val exactMove = mScrollOffset - expectOffset + offset + + if (exactMove == 0) { + //itemViews 位置都不会改变,直接 return + return 0 + } + + detachAndScrapAttachedViews(recycler) + + loadItemView(recycler) + + return offset + } + + private fun loadItemView(recycler: RecyclerView.Recycler) { + val firstVisiblePosition = getFirstVisibleItemPosition() + val lastVisiblePosition = getLastVisibleItemPosition() + + //位移百分比 + val movePercent = getFirstVisibleItemMovePercent() + + for (i in lastVisiblePosition downTo firstVisiblePosition) { + val view = recycler.getViewForPosition(i) + view.layoutParams = view.layoutParams.apply { width = DisplayUtils.getScreenWidth() - 50F.dip2px() } + //添加到recycleView 中 + addView(view) + //测量 + measureChild(view, 0, 0) + //布局 + mLayout?.doLayout(this, mScrollOffset, movePercent, view, i - firstVisiblePosition) + //做动画 + mAnimation?.doAnimation(movePercent, view, i - firstVisiblePosition) + } + + //尝试更新当前item的位置并通知外界 + updatePositionRecordAndNotify(firstVisiblePosition) + + //重用 + if (firstVisiblePosition - 1 >= 0) { + val view = recycler.getViewForPosition(firstVisiblePosition - 1) + resetViewAnimateProperty(view) + removeAndRecycleView(view, recycler) + } + if (lastVisiblePosition + 1 < itemCount) { + val view = recycler.getViewForPosition(lastVisiblePosition + 1) + resetViewAnimateProperty(view) + removeAndRecycleView(view, recycler) + } + } + + private fun resetViewAnimateProperty(view: View) { + view.rotationY = 0f + view.rotationX = 0f + view.scaleX = 1f + view.scaleY = 1f + view.alpha = 1f + } + + private fun calculateAndScrollToTarget(view: RecyclerView) { + val targetPosition = calculateCenterPosition(getFirstVisibleItemPosition()) + scrollToCenter(targetPosition, view, true) + } + + private fun scrollToCenter(targetPosition: Int, recyclerView: RecyclerView, animation: Boolean) { + val targetOffset = getPositionOffset(targetPosition) + when(mScrollOrientation) { + ScrollOrientation.LEFT_TO_RIGHT, ScrollOrientation.RIGHT_TO_LEFT -> { + if (animation) { + recyclerView.smoothScrollBy(targetOffset - mScrollOffset, 0) + } else { + recyclerView.scrollBy(targetOffset - mScrollOffset, 0) + } + } + else -> { + if (animation) { + recyclerView.smoothScrollBy(0, targetOffset - mScrollOffset) + } else { + recyclerView.scrollBy(0, targetOffset - mScrollOffset) + } + } + } + } + + private fun getValidOffset(expectOffset: Int): Int { + val offset = (width * (itemCount - 1)).coerceAtMost(expectOffset).coerceAtLeast(0) + return when(mScrollOrientation) { + ScrollOrientation.RIGHT_TO_LEFT, ScrollOrientation.LEFT_TO_RIGHT -> offset + else -> (height * (itemCount - 1)).coerceAtMost(expectOffset).coerceAtLeast(0) + } + } + + private fun getPositionOffset(position: Int): Int { + return when(mScrollOrientation) { + ScrollOrientation.RIGHT_TO_LEFT -> position * width + ScrollOrientation.LEFT_TO_RIGHT -> (itemCount - 1 - position) * width + ScrollOrientation.BOTTOM_TO_TOP -> position * height + ScrollOrientation.TOP_TO_BOTTOM -> (itemCount - 1 - position) * height + } + } + + private fun getLastVisibleItemPosition(): Int { + val firstVisiblePosition = getFirstVisibleItemPosition() + return if (firstVisiblePosition + mVisibleItemCount > itemCount - 1) { + itemCount - 1 + } else { + firstVisiblePosition + mVisibleItemCount + } + } + + private fun getFirstVisibleItemMovePercent(): Float { + if (width == 0 || height == 0) { + return 0f + } + return when (mScrollOrientation) { + ScrollOrientation.RIGHT_TO_LEFT -> (mScrollOffset % width) * 1.0f / width + ScrollOrientation.LEFT_TO_RIGHT -> { + val targetPercent = 1 - (mScrollOffset % width) * 1.0f / width + return if (targetPercent == 1f) { + 0f + } else { + targetPercent + } + } + ScrollOrientation.BOTTOM_TO_TOP -> (mScrollOffset % height) * 1.0f / height + ScrollOrientation.TOP_TO_BOTTOM -> { + val targetPercent = 1 - (mScrollOffset % height) * 1.0f / height + return if (targetPercent == 1f) { + 0f + } else { + targetPercent + } + } + } + } + + private fun calculateCenterPosition(position: Int): Int { + //当是 Fling 触发的时候 + val triggerOrientation = mFlingOrientation + mFlingOrientation = FlingOrientation.NONE + when(mScrollOrientation) { + ScrollOrientation.RIGHT_TO_LEFT -> { + if (triggerOrientation == FlingOrientation.RIGHT_TO_LEFT) { + return position + 1 + } else if (triggerOrientation == FlingOrientation.LEFT_TO_RIGHT) { + return position + } + } + ScrollOrientation.LEFT_TO_RIGHT -> { + if (triggerOrientation == FlingOrientation.LEFT_TO_RIGHT) { + return position + 1 + } else if (triggerOrientation == FlingOrientation.RIGHT_TO_LEFT) { + return position + } + } + ScrollOrientation.BOTTOM_TO_TOP -> { + if (triggerOrientation == FlingOrientation.BOTTOM_TO_TOP) { + return position + 1 + } else if (triggerOrientation == FlingOrientation.TOP_TO_BOTTOM) { + return position + } + } + ScrollOrientation.TOP_TO_BOTTOM -> { + if (triggerOrientation == FlingOrientation.TOP_TO_BOTTOM) { + return position + 1 + } else if (triggerOrientation == FlingOrientation.BOTTOM_TO_TOP) { + return position + } + } + } + + //当不是 fling 触发的时候 + val percent = getFirstVisibleItemMovePercent() + //向左移动超过50% position(firstVisibleItemPosition)++ + //否 position不变 + return if (percent < 0.5) { + position + } else { + position + 1 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/CropImageActivity.java b/app/src/main/java/com/gh/gamecenter/CropImageActivity.java index 09ca79c334..5df6096cd0 100644 --- a/app/src/main/java/com/gh/gamecenter/CropImageActivity.java +++ b/app/src/main/java/com/gh/gamecenter/CropImageActivity.java @@ -8,6 +8,7 @@ import android.os.Bundle; import android.view.MenuItem; import android.view.View; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.TextView; import com.gh.base.ToolBarActivity; @@ -20,6 +21,10 @@ import java.io.File; import java.lang.ref.SoftReference; import androidx.annotation.NonNull; +import androidx.core.view.OnApplyWindowInsetsListener; +import androidx.core.view.ViewCompat; +import androidx.core.view.WindowInsetsCompat; + import butterknife.BindView; public class CropImageActivity extends ToolBarActivity { @@ -37,11 +42,16 @@ public class CropImageActivity extends ToolBarActivity { @NonNull public static Intent getIntent(Context context, String picturePath, float cropRatio, String entrance) { + return getIntent(context, picturePath, cropRatio, true, entrance); + } + + @NonNull + public static Intent getIntent(Context context, String picturePath, float cropRatio, boolean isBlackTheme, String entrance) { Intent intent = new Intent(context, CropImageActivity.class); intent.putExtra(EntranceUtils.KEY_PATH, picturePath); intent.putExtra(EntranceUtils.KEY_ENTRANCE, entrance); intent.putExtra(EntranceUtils.KEY_IMAGE_CROP_RATIO, cropRatio); - intent.putExtra(EntranceUtils.KEY_BLACK_THEME, true); + intent.putExtra(EntranceUtils.KEY_BLACK_THEME, isBlackTheme); return intent; } @@ -62,12 +72,13 @@ public class CropImageActivity extends ToolBarActivity { setToolbarMenu(R.menu.menu_positive); MenuItem menuItem = getMenuItem(R.id.layout_menu_positive); TextView menuButton = menuItem.getActionView().findViewById(R.id.menu_answer_post); - menuButton.setTextColor(getResources().getColor(mBlackTheme ? R.color.theme_font : R.color.title)); + menuButton.setTextColor(getResources().getColor(R.color.theme_font)); float ratio = getIntent().getFloatExtra(EntranceUtils.KEY_IMAGE_CROP_RATIO, 1F); mCropImageCustom.setCropRatio(ratio); - DisplayUtils.transparentStatusBar(this); + DisplayUtils.setLightStatusBar(this, !mBlackTheme); + DisplayUtils.setStatusBarColor(this, R.color.transparent, !mBlackTheme); } @Override diff --git a/app/src/main/java/com/gh/gamecenter/SkipActivity.java b/app/src/main/java/com/gh/gamecenter/SkipActivity.java index 56148a1080..bb112c004e 100644 --- a/app/src/main/java/com/gh/gamecenter/SkipActivity.java +++ b/app/src/main/java/com/gh/gamecenter/SkipActivity.java @@ -36,6 +36,7 @@ import static com.gh.common.util.EntranceUtils.HOST_COMMUNITY_COLUMN_DETAIL; import static com.gh.common.util.EntranceUtils.HOST_COMMUNITY_QUESTION_LABEL_DETAIL; import static com.gh.common.util.EntranceUtils.HOST_DOWNLOAD; import static com.gh.common.util.EntranceUtils.HOST_GAME; +import static com.gh.common.util.EntranceUtils.HOST_GAME_COLLECTION_DETAIL; import static com.gh.common.util.EntranceUtils.HOST_LIBAO; import static com.gh.common.util.EntranceUtils.HOST_QQ; import static com.gh.common.util.EntranceUtils.HOST_QQ_GROUP; @@ -390,6 +391,9 @@ public class SkipActivity extends BaseActivity { position = uri.getQueryParameter("position"); DirectUtils.directToHelpAndFeedback(this, TextUtils.isEmpty(position) ? 0 : Integer.parseInt(position)); break; + case HOST_GAME_COLLECTION_DETAIL: + DirectUtils.directToGameCollectionDetail(this, path, ENTRANCE_BROWSER); + break; default: EntranceUtils.jumpActivity(this, new Bundle()); // 跳转至首页 return; diff --git a/app/src/main/java/com/gh/gamecenter/WeiBoShareActivity.java b/app/src/main/java/com/gh/gamecenter/WeiBoShareActivity.java index fab00a3394..b78dde982f 100644 --- a/app/src/main/java/com/gh/gamecenter/WeiBoShareActivity.java +++ b/app/src/main/java/com/gh/gamecenter/WeiBoShareActivity.java @@ -272,6 +272,13 @@ public class WeiBoShareActivity extends Activity implements WbShareCallback { ShareUtils.shareEntrance == ShareUtils.ShareEntrance.communityArticle || ShareUtils.shareEntrance == ShareUtils.ShareEntrance.video) { NewLogUtils.logShareResult(true); + } else if (ShareUtils.shareEntrance == ShareUtils.ShareEntrance.gameCollection) { + NewLogUtils.logViewOrClickGameCollectionDetail( + "click_game_collect_detail_favorite_success", + ShareUtils.shareEntity.getShareTitle(), + ShareUtils.resourceId, + "新浪微博" + ); } } else { IntegralLogHelper.INSTANCE.logInviteResult("成功", "微博"); diff --git a/app/src/main/java/com/gh/gamecenter/adapter/viewholder/GameCollectionItemViewHolder.kt b/app/src/main/java/com/gh/gamecenter/adapter/viewholder/GameCollectionItemViewHolder.kt new file mode 100644 index 0000000000..24ebf431eb --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/adapter/viewholder/GameCollectionItemViewHolder.kt @@ -0,0 +1,6 @@ +package com.gh.gamecenter.adapter.viewholder + +import com.gh.base.BaseRecyclerViewHolder +import com.gh.gamecenter.databinding.GameCollectionItemBinding + +class GameCollectionItemViewHolder(var binding: GameCollectionItemBinding) : BaseRecyclerViewHolder(binding.root) diff --git a/app/src/main/java/com/gh/gamecenter/adapter/viewholder/GameHeadViewHolder.kt b/app/src/main/java/com/gh/gamecenter/adapter/viewholder/GameHeadViewHolder.kt index f80943c444..96fa28c90c 100644 --- a/app/src/main/java/com/gh/gamecenter/adapter/viewholder/GameHeadViewHolder.kt +++ b/app/src/main/java/com/gh/gamecenter/adapter/viewholder/GameHeadViewHolder.kt @@ -48,7 +48,8 @@ class GameHeadViewHolder(var binding: GameHeadItemBinding) : && subject.data != null && subject.data!!.size >= subject.more ?: 0 && subject.type != "column_collection" - && subject.type != "gallery_slide") { + && subject.type != "gallery_slide" + && subject.type != "game_list_collection") { binding.headMore.visibility = View.GONE } else if (subject.home == "hide"){ binding.headMore.visibility = View.GONE diff --git a/app/src/main/java/com/gh/gamecenter/collection/CollectionWrapperFragment.java b/app/src/main/java/com/gh/gamecenter/collection/CollectionWrapperFragment.java index b0b4fcaae2..2df04ac545 100644 --- a/app/src/main/java/com/gh/gamecenter/collection/CollectionWrapperFragment.java +++ b/app/src/main/java/com/gh/gamecenter/collection/CollectionWrapperFragment.java @@ -3,8 +3,10 @@ package com.gh.gamecenter.collection; import android.os.Bundle; import com.gh.base.fragment.BaseFragment_TabLayout; +import com.gh.common.util.EntranceUtils; import com.gh.common.util.MtaHelper; import com.gh.gamecenter.R; +import com.gh.gamecenter.manager.UserManager; import java.util.List; @@ -29,27 +31,41 @@ public class CollectionWrapperFragment extends BaseFragment_TabLayout { return fragment; } + @Override + protected int getLayoutId() { + return R.layout.fragment_no_padding_tablayout_viewpager; + } + @Override protected void initTabTitleList(List tabTitleList) { tabTitleList.add(getString(R.string.answer)); tabTitleList.add(getString(R.string.collection_article)); + tabTitleList.add(getString(R.string.video)); + tabTitleList.add(getString(R.string.game_collection)); tabTitleList.add(getString(R.string.collection_toolkit)); tabTitleList.add(getString(R.string.collection_info)); - tabTitleList.add(getString(R.string.video)); } @Override protected void initFragmentList(List fragments) { fragments.add(new AnswerFragment().with(getArguments())); fragments.add(new CommunityArticleFragment().with(getArguments())); - fragments.add(new ToolsFragment().with(getArguments())); - fragments.add(new ArticleFragment().with(getArguments())); Bundle arguments = getArguments(); if (arguments != null) arguments.putString("videoStyle", VideoFragment.VideoStyle.COLLECT.getValue()); fragments.add(new VideoFragment().with(arguments)); + Bundle gameCollectionArguments = getArguments(); + if (gameCollectionArguments != null) { + gameCollectionArguments.putString(EntranceUtils.KEY_USER_ID, UserManager.getInstance().getUserId()); + gameCollectionArguments.putString(EntranceUtils.KEY_TYPE, GamesCollectionFragment.TYPE_COLLECT); + } + fragments.add(new GamesCollectionFragment().with(arguments)); + + fragments.add(new ToolsFragment().with(getArguments())); + fragments.add(new ArticleFragment().with(getArguments())); + } @Override diff --git a/app/src/main/java/com/gh/gamecenter/collection/GamesCollectionAdapter.kt b/app/src/main/java/com/gh/gamecenter/collection/GamesCollectionAdapter.kt new file mode 100644 index 0000000000..6b613f72a7 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/collection/GamesCollectionAdapter.kt @@ -0,0 +1,268 @@ +package com.gh.gamecenter.collection + +import android.app.Activity +import android.content.Context +import android.content.Intent +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.appcompat.app.AppCompatActivity +import androidx.databinding.DataBindingUtil +import androidx.recyclerview.widget.RecyclerView +import com.gh.common.constant.ItemViewType +import com.gh.common.util.* +import com.gh.gamecenter.GameDetailActivity +import com.gh.gamecenter.R +import com.gh.gamecenter.adapter.viewholder.FooterViewHolder +import com.gh.gamecenter.adapter.viewholder.GameCollectionItemViewHolder +import com.gh.gamecenter.baselist.ListAdapter +import com.gh.gamecenter.collection.GamesCollectionFragment.Companion.TYPE_USER +import com.gh.gamecenter.entity.GamesCollectionEntity +import com.gh.gamecenter.gamecollection.detail.GameCollectionDetailActivity +import com.gh.gamecenter.gamecollection.publish.GameCollectionEditActivity +import com.gh.gamecenter.manager.UserManager + +class GamesCollectionAdapter( + context: Context, + private val mViewModel: GamesCollectionViewModel +) : ListAdapter(context) { + + override fun getItemCount(): Int { + return if (mEntityList == null || mEntityList.isEmpty()) return 0 else mEntityList.size + 1 + } + + override fun getItemViewType(position: Int): Int { + return if (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 -> GameCollectionItemViewHolder( + DataBindingUtil.inflate( + mLayoutInflater, + R.layout.game_collection_item, + parent, + false + ) + ) + + else -> FooterViewHolder(mLayoutInflater.inflate(R.layout.refresh_footerview, parent, false)) + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is GameCollectionItemViewHolder -> { + val itemEntity = mEntityList[position] + holder.binding.run { + entity = itemEntity + executePendingBindings() + + moreIv.goneIf(itemEntity.user?.id != UserManager.getInstance().userId) + + when (itemEntity.stamp) { + "special_choice" -> { + tagIv.setBackgroundResource(R.drawable.ic_chosen) + } + "official" -> { + tagIv.setBackgroundResource(R.drawable.ic_official) + } + } + + when { + mViewModel.mIsInsertGameCollection -> { + userIcon.visibility = View.VISIBLE + userName.visibility = View.VISIBLE + timeTv.visibility = View.GONE + myselfTag.visibility = View.GONE + } + + mViewModel.type == TYPE_USER && itemEntity.display == "self_only" -> { + userIcon.visibility = View.GONE + userName.visibility = View.GONE + timeTv.visibility = View.GONE + myselfTag.visibility = View.VISIBLE + } + + mViewModel.type == TYPE_USER && itemEntity.display != "self_only"-> { + userIcon.visibility = View.GONE + userName.visibility = View.GONE + timeTv.visibility = View.VISIBLE + myselfTag.visibility = View.GONE + } + + else -> { + userIcon.visibility = View.VISIBLE + userName.visibility = View.VISIBLE + timeTv.visibility = View.GONE + myselfTag.visibility = View.GONE + } + } + + moreIv.setOnClickListener { + showOptionPopupWindow(moreIv, itemEntity) + } + + myselfTag.setOnClickListener { + DialogHelper.showDialog(mContext, "仅自己可见", "游戏单开启“仅自己可见”后,将不会对其他用户展示,您可以通过关闭仅自己可见或者投稿分享游戏单", "我知道了", "", { + + }, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)) + } + + gameOne.setOnClickListener { + GameDetailActivity.startGameDetailActivity(mContext, itemEntity.games?.safelyGetInRelease(0)?.id, "") + } + gameTwo.setOnClickListener { + GameDetailActivity.startGameDetailActivity(mContext, itemEntity.games?.safelyGetInRelease(1)?.id, "") + } + gameThree.setOnClickListener { + GameDetailActivity.startGameDetailActivity(mContext, itemEntity.games?.safelyGetInRelease(2)?.id, "") + } + + userIcon.setOnClickListener { + val path = when (mViewModel.type) { + GamesCollectionFragment.TYPE_COLLECT -> "我的收藏-游戏单" + GamesCollectionFragment.TYPE_HISTORY -> "浏览记录-游戏单" + GamesCollectionFragment.TYPE_USER -> "个人主页-游戏单" + else -> "" + } + DirectUtils.directToHomeActivity(mContext, itemEntity.user?.id, "", path) + } + userName.setOnClickListener { userIcon.performClick() } + + root.setOnClickListener { + if (mViewModel.mIsInsertGameCollection) { + val entity = GamesCollectionEntity().apply { + id = itemEntity.id + title = itemEntity.title + intro = itemEntity.intro + } + val intent = Intent().apply { + putExtra(GamesCollectionEntity::class.java.simpleName, entity) + } + (mContext as AppCompatActivity).setResult(Activity.RESULT_OK, intent) + (mContext as AppCompatActivity).finish() + } else { + NewLogUtils.logEnterGameCollectionDetail(itemEntity.title, itemEntity.id) + mContext.startActivity(GameCollectionDetailActivity.getIntent(mContext, itemEntity.id)) + } + } + + commentCountContainer.setOnClickListener { + NewLogUtils.logEnterGameCollectionDetail(itemEntity.title, itemEntity.id) + mContext.startActivity( + GameCollectionDetailActivity.getIntent( + mContext, + itemEntity.id, + isScrollToCommentArea = true + ) + ) + } + + voteCountContainer.setOnClickListener { + debounceActionWithInterval(R.id.vote_count_container, 1000) { + mViewModel.postVoteGameCollection(itemEntity.id, !voteIcon.isChecked) { + if (!voteIcon.isChecked) { + voteCount.setTextColor(R.color.theme_font.toColor()) + voteIcon.isChecked = true + voteIcon.visibility = View.GONE + voteAnimation.visibility = View.VISIBLE + voteAnimation.setAnimation("lottie/community_vote.json") + voteAnimation.playAnimation() + voteAnimation.doOnAnimationEnd { + voteAnimation.visibility = View.GONE + voteIcon.visibility = View.VISIBLE + } + + itemEntity.me?.vote = true + itemEntity.count?.run { + vote++ + voteCount.text = vote.toString() + } + } else { + voteIcon.isChecked = false + itemEntity.me?.vote = false + itemEntity.count?.run { + vote-- + if (vote < 0) vote = 0 + voteCount.setTextColor(R.color.text_999999.toColor()) + voteCount.text = vote.toString() + } + } + } + } + } + } + } + + is FooterViewHolder -> holder.initFooterViewHolder(mViewModel, mIsLoading, mIsNetworkError, mIsOver) + } + } + + private fun showOptionPopupWindow(view: View, entity: GamesCollectionEntity) { + val contentList = arrayListOf("编辑", "投稿", "删除") + + val inflater = LayoutInflater.from(mContext) + val layout = inflater.inflate(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(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(R.id.hint_text) + hitText.text = text + + item.setOnClickListener { + dealOption(text, entity) + popupWindow.dismiss() + } + } + popupWindow.showAutoOrientation(view) + } + + private fun dealOption(content: String, entity: GamesCollectionEntity) { + when (content) { + "编辑" -> { + mContext.startActivity(GameCollectionEditActivity.getIntent(mContext, entity)) + } + + "投稿" -> { + if ((entity.count?.game ?: 0) >= 8) { + DialogHelper.showDialog(mContext, "温馨提示", "投稿通过后,将自动关闭“仅自己可见”,所有用户都能浏览到游戏单,确定投稿?", "确定", "取消", { + mViewModel.publishGameCollection(entity) + }, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)) + } else { + DialogHelper.showDialog(mContext, "温馨提示", "游戏单需要收录至少8个游戏,才可以投稿至游戏单广场哦~", "添加游戏", "我知道了", { + mContext.startActivity(GameCollectionEditActivity.getIntent(mContext, entity)) + }, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)) + } + } + + "删除" -> { + DialogHelper.showDialog(mContext, "温馨提示", "游戏单删除后将无法恢复,是否确认删除", "确定", "取消", { + mViewModel.deleteGameCollection(entity) + }, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/collection/GamesCollectionFragment.kt b/app/src/main/java/com/gh/gamecenter/collection/GamesCollectionFragment.kt new file mode 100644 index 0000000000..bab8713915 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/collection/GamesCollectionFragment.kt @@ -0,0 +1,80 @@ +package com.gh.gamecenter.collection + +import android.os.Bundle +import android.view.View +import androidx.core.content.ContextCompat +import com.gh.common.util.* +import com.gh.common.util.EntranceUtils.* +import com.gh.common.view.SpacingItemDecoration +import com.gh.gamecenter.R +import com.gh.gamecenter.baselist.ListFragment +import com.gh.gamecenter.baselist.LoadType +import com.gh.gamecenter.entity.GamesCollectionEntity + +class GamesCollectionFragment : ListFragment() { + + private var mUserId = "" + private var mType = "" + private var mIsInsertGameCollection = false + private var mAdapter: GamesCollectionAdapter? = null + + override fun provideListViewModel() = viewModelProvider( + GamesCollectionViewModel.Factory(mUserId, mType, mIsInsertGameCollection) + ) + + override fun provideListAdapter() = mAdapter ?: GamesCollectionAdapter(requireContext(), mListViewModel).apply { mAdapter = this } + + override fun getItemDecoration() = SpacingItemDecoration(notDecorateTheFirstItem = mType == TYPE_USER, top = 16F.dip2px()) + + override fun onCreate(savedInstanceState: Bundle?) { + mUserId = arguments?.getString(KEY_USER_ID, "") ?: "" + mType = arguments?.getString(KEY_TYPE, "") ?: "" + mIsInsertGameCollection = arguments?.getBoolean(KEY_INSERT_GAME_COLLECTION, false) ?: false + super.onCreate(savedInstanceState) + mListRv?.run { + overScrollMode = View.OVER_SCROLL_NEVER + if (!mIsInsertGameCollection && mType == TYPE_USER) setBackgroundColor(R.color.white.toColor()) + } + + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + mListViewModel.deleteLiveData.observe(viewLifecycleOwner) { + val index = mAdapter?.entityList?.indexOf(it) ?: -1 + mAdapter?.entityList?.removeAt(index) + if (mAdapter?.entityList?.isNullOrEmpty() == true) { + onLoadEmpty() + } else { + mAdapter?.notifyItemRemoved(index) + } + + toast("删除成功") + } + + mListViewModel.publishLiveData.observe(viewLifecycleOwner) { + mListViewModel.load(LoadType.REFRESH) + } + } + + override fun onLoadEmpty() { + super.onLoadEmpty() + // RecyclerView 被隐藏的话会导致不能 AppBar 不能滑动 + if (mType == TYPE_USER) mListRv.visibility = View.VISIBLE + } + + override fun onLoadError() { + super.onLoadError() + if (mType == TYPE_USER) { + mListRv.visibility = View.VISIBLE + mReuseNoConn?.setBackgroundColor(ContextCompat.getColor(requireContext(), R.color.background)) + } + } + + companion object { + const val TYPE_HISTORY = "history" + const val TYPE_COLLECT = "collect" + const val TYPE_USER = "user" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/collection/GamesCollectionViewModel.kt b/app/src/main/java/com/gh/gamecenter/collection/GamesCollectionViewModel.kt new file mode 100644 index 0000000000..6a0d9dec1f --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/collection/GamesCollectionViewModel.kt @@ -0,0 +1,128 @@ +package com.gh.gamecenter.collection + +import android.annotation.SuppressLint +import android.app.Application +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import com.gh.common.history.HistoryDatabase +import com.gh.common.util.ErrorHelper +import com.gh.common.util.ToastUtils +import com.gh.common.util.observableToMain +import com.gh.common.util.singleToMain +import com.gh.gamecenter.baselist.ListViewModel +import com.gh.gamecenter.collection.GamesCollectionFragment.Companion.TYPE_COLLECT +import com.gh.gamecenter.collection.GamesCollectionFragment.Companion.TYPE_HISTORY +import com.gh.gamecenter.entity.GamesCollectionEntity +import com.gh.gamecenter.retrofit.BiResponse +import com.gh.gamecenter.retrofit.Response +import com.gh.gamecenter.retrofit.RetrofitManager +import com.halo.assistant.HaloApp +import io.reactivex.Single +import okhttp3.ResponseBody +import retrofit2.HttpException + +class GamesCollectionViewModel(application: Application, var userId: String, var type: String, val mIsInsertGameCollection: Boolean = false) : + ListViewModel(application) { + + private val mApi = RetrofitManager.getInstance(getApplication()).api + val deleteLiveData = MutableLiveData() + val publishLiveData = MutableLiveData() + + override fun provideDataObservable(page: Int) = null + + override fun provideDataSingle(page: Int): Single> { + return when (type) { + TYPE_COLLECT -> mApi.getFavoriteGameCollectionList(userId) + + TYPE_HISTORY -> { + if (page > 5) { + Single.create { it.onSuccess(arrayListOf()) } + } else { + HistoryDatabase.instance.gamesCollectionDao().getGamesCollectionWithOffset(20, (page - 1) * 20) + } + } + + else -> { + val map = if (mIsInsertGameCollection) { + hashMapOf("filter" to "display") + } else mapOf() + mApi.getUserGameCollectionList(userId, map) + } + } + } + + override fun mergeResultLiveData() { + mResultLiveData.addSource(mListLiveData) { + mResultLiveData.postValue(it) + } + } + + fun deleteGameCollection(entity: GamesCollectionEntity) { + mApi.deleteGameCollection(entity.id) + .compose(observableToMain()) + .subscribe(object : Response() { + override fun onResponse(response: ResponseBody?) { + super.onResponse(response) + deleteLiveData.postValue(entity) + } + + override fun onFailure(e: HttpException?) { + super.onFailure(e) + ToastUtils.showToast("删除失败") + } + }) + } + + fun publishGameCollection(entity: GamesCollectionEntity){ + mApi.deleteGameCollection(entity.id) + .compose(observableToMain()) + .subscribe(object : Response() { + override fun onResponse(response: ResponseBody?) { + super.onResponse(response) + publishLiveData.postValue(entity) + } + + override fun onFailure(e: HttpException?) { + super.onFailure(e) + ToastUtils.showToast("投稿失败") + } + }) + } + + @SuppressLint("CheckResult") + fun postVoteGameCollection(gameCollectionId: String, isVote: Boolean, successCallback: (() -> Unit)) { + val single = if (isVote) { + mApi.voteGameCollection(gameCollectionId) + } else { + mApi.unVoteGameCollection(gameCollectionId) + } + single.compose(singleToMain()) + .subscribe(object : BiResponse() { + override fun onSuccess(data: ResponseBody) { + ToastUtils.toast(if (isVote) "点赞成功" else "取消点赞") + successCallback.invoke() + } + + override fun onFailure(exception: Exception) { + super.onFailure(exception) + + if (exception is HttpException) { + ErrorHelper.handleError( + getApplication(), + exception.response()?.errorBody()?.string() + ) + } + } + }) + } + + + class Factory(private val mUserId: String, private val mType: String, private val mIsInsertGameCollection: Boolean) : + ViewModelProvider.NewInstanceFactory() { + override fun create(modelClass: Class): T { + return GamesCollectionViewModel(HaloApp.getInstance().application, mUserId, mType, mIsInsertGameCollection) as T + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/entity/GameCollectionDraft.kt b/app/src/main/java/com/gh/gamecenter/entity/GameCollectionDraft.kt new file mode 100644 index 0000000000..aae0ec76fa --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/entity/GameCollectionDraft.kt @@ -0,0 +1,45 @@ +package com.gh.gamecenter.entity + +import android.os.Parcelable +import androidx.room.Entity +import androidx.room.Ignore +import androidx.room.PrimaryKey +import androidx.room.TypeConverters +import com.gh.gamecenter.R +import com.gh.gamecenter.qa.entity.Count +import com.gh.gamecenter.qa.entity.TimeEntity +import com.gh.gamecenter.room.converter.SimpleGameListConverter +import com.gh.gamecenter.room.converter.TagInfoListConverter +import com.google.gson.annotations.SerializedName +import kotlinx.android.parcel.Parcelize + +@Entity +@Parcelize +class GameCollectionDraft( + @Ignore + @SerializedName("_id") + var id: String = "", + @PrimaryKey + var primaryKey: String = "", // db key + @TypeConverters(TagInfoListConverter::class) + var tags: ArrayList? = null, + @TypeConverters(SimpleGameListConverter::class) + var games: ArrayList? = null, + var title: String = "", + var intro: String = "", + var cover: String = "", + var display: String = "",//self_only: 仅自己可见 +) : Parcelable { + + fun convertGameCollectionEntity(): GamesCollectionEntity { + val entity = GamesCollectionEntity() + entity.id = id + entity.tags = tags + entity.games = games + entity.title = title + entity.intro = intro + entity.cover = cover + entity.display = display + return entity + } +} diff --git a/app/src/main/java/com/gh/gamecenter/entity/GameCollectionTagEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/GameCollectionTagEntity.kt new file mode 100644 index 0000000000..693dc99b9e --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/entity/GameCollectionTagEntity.kt @@ -0,0 +1,14 @@ +package com.gh.gamecenter.entity + +import android.os.Parcelable +import com.google.gson.annotations.SerializedName +import kotlinx.android.parcel.Parcelize + +@Parcelize +data class GameCollectionTagEntity( + @SerializedName("_id") + val categoryId: String = "", + @SerializedName("name") + val categoryName: String = "", + val tags: List = ArrayList() +) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/entity/GameEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/GameEntity.kt index d9ef421c6f..12f088d9d9 100644 --- a/app/src/main/java/com/gh/gamecenter/entity/GameEntity.kt +++ b/app/src/main/java/com/gh/gamecenter/entity/GameEntity.kt @@ -252,6 +252,13 @@ data class GameEntity( @SerializedName("bbs_id") var bbsId: String = "", + //游戏单推荐分数 + @SerializedName("recommend_star") + var recommendStar: Int = 5, + //游戏单推荐理由 + @SerializedName("recommend_text") + var recommendText: String = "", + // 本地字段,使用镜像信息 var useMirrorInfo: Boolean = false, // 本地字段,曝光用 @@ -549,6 +556,20 @@ data class GameEntity( && (useMirrorInfo || RegionSettingHelper.shouldThisGameDisplayMirrorInfo(id))) } + fun toSimpleGame(): SimpleGame { + val simpleGame = SimpleGame() + simpleGame.id = id + simpleGame.mName = mName + simpleGame.nameSuffix = nameSuffix + simpleGame.mIcon = mIcon + simpleGame.mRawIcon = mRawIcon + simpleGame.active = active + simpleGame.iconSubscript = iconSubscript + simpleGame.recommendStar = recommendStar + simpleGame.recommendText = recommendText + return simpleGame + } + fun clone(): GameEntity { val gameEntity = GameEntity() gameEntity.id = id @@ -762,12 +783,22 @@ data class SimpleGame( @SerializedName("name_suffix") var nameSuffix: String? = null, @SerializedName("icon") - private var mIcon: String? = null, + var mIcon: String? = null, @SerializedName("ori_icon") - private var mRawIcon: String? = null, + var mRawIcon: String? = null, @SerializedName("icon_subscript") var iconSubscript: String? = null, - var active: Boolean = false + var active: Boolean = false, + //游戏单推荐分数 + @SerializedName("recommend_star") + var recommendStar: Int = 5, + @SerializedName("mirror_status") + var mirrorStatus: String? = "", + @SerializedName("mirror_data") + var mirrorData: SimpleGame? = null, + //游戏单推荐理由 + @SerializedName("recommend_text") + var recommendText: String = "", ) : Parcelable { @IgnoredOnParcel @@ -788,6 +819,10 @@ data class SimpleGame( gameEntity.icon = mIcon gameEntity.rawIcon = mRawIcon gameEntity.iconSubscript = iconSubscript + gameEntity.mirrorStatus = mirrorStatus + gameEntity.mirrorData = mirrorData?.toGameEntity() + gameEntity.recommendStar = recommendStar + gameEntity.recommendText = recommendText return gameEntity } } diff --git a/app/src/main/java/com/gh/gamecenter/entity/GamesCollectionDetailEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/GamesCollectionDetailEntity.kt new file mode 100644 index 0000000000..a9abc1cf49 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/entity/GamesCollectionDetailEntity.kt @@ -0,0 +1,38 @@ +package com.gh.gamecenter.entity + +import android.os.Parcelable +import com.gh.gamecenter.qa.entity.Count +import com.gh.gamecenter.qa.entity.TimeEntity +import com.google.gson.annotations.SerializedName +import kotlinx.android.parcel.Parcelize + +@Parcelize +class GamesCollectionDetailEntity( + @SerializedName("_id") + var id: String = "", + var tags: ArrayList? = null, + var games: ArrayList? = null, + var title: String = "", + var intro: String = "", + var cover: String = "", + var video: Video? = null, + var display: String = "",//self_only: 仅自己可见 + var status: String = "",// draft/pending/pass/failed + var time: TimeEntity? = null, + var stamp: String = "",//special_choice: 精选 official: 官方 + var count: Count? = null, + var user: UserEntity? = null, + var me: MeEntity? = null, +) : Parcelable { + + @Parcelize + data class Video( + var active: Boolean = false, + var format: String = "", + var title: String = "", + var size: Long = 0, + var url: String = "", + var poster: String = "", + var time: Long = 0 + ) : Parcelable +} diff --git a/app/src/main/java/com/gh/gamecenter/entity/GamesCollectionEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/GamesCollectionEntity.kt new file mode 100644 index 0000000000..f72c1747e7 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/entity/GamesCollectionEntity.kt @@ -0,0 +1,52 @@ +package com.gh.gamecenter.entity + +import android.os.Parcelable +import androidx.room.Entity +import androidx.room.Ignore +import androidx.room.PrimaryKey +import androidx.room.TypeConverters +import com.gh.gamecenter.R +import com.gh.gamecenter.qa.entity.Count +import com.gh.gamecenter.qa.entity.TimeEntity +import com.gh.gamecenter.room.converter.* +import com.google.gson.annotations.SerializedName +import kotlinx.android.parcel.Parcelize + +@Entity +@Parcelize +class GamesCollectionEntity( + @PrimaryKey + @SerializedName("_id") + var id: String = "", + @TypeConverters(TagInfoListConverter::class) + var tags: ArrayList? = null, + @TypeConverters(SimpleGameListConverter::class) + var games: ArrayList? = null, + var title: String = "", + var intro: String = "", + var cover: String = "", + var display: String = "",//self_only: 仅自己可见 + var stamp: String = "",//special_choice: 精选 offical: 官方 + @TypeConverters(CountConverter::class) + var count: Count? = null, + @TypeConverters(UserConverter::class) + var user: User? = null, + @TypeConverters(MeConverter::class) + var me: MeEntity? = null, + var orderTag: Long = 0, + @Ignore + var time: TimeEntity? = null, + @Ignore + var status: String = "",// draft/pending/pass/failed +) : Parcelable { + + fun getStatusLabelRes(): Int { + return when { + display == "self_only" && status == "draft" -> R.drawable.ic_game_collection_private + status == "draft" -> R.drawable.ic_game_collection_draft + status == "pending" -> R.drawable.ic_game_collection_pending + status == "failed" -> R.drawable.ic_game_collection_fail + else -> -1 + } + } +} diff --git a/app/src/main/java/com/gh/gamecenter/entity/HomeContent.kt b/app/src/main/java/com/gh/gamecenter/entity/HomeContent.kt index 9a57462f34..cb7ece6001 100644 --- a/app/src/main/java/com/gh/gamecenter/entity/HomeContent.kt +++ b/app/src/main/java/com/gh/gamecenter/entity/HomeContent.kt @@ -13,6 +13,8 @@ data class HomeContent( val linkGame: GameEntity? = null, @SerializedName("link_column") val linkColumn: SubjectEntity? = null, + @SerializedName("game_list") + val linkGameCollection: List? = null, @SerializedName("link_top_game_comment") val linkTopGameComment: List? = null, @SerializedName("display_content") diff --git a/app/src/main/java/com/gh/gamecenter/entity/MeEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/MeEntity.kt index e9ac7b7617..a7f9598108 100644 --- a/app/src/main/java/com/gh/gamecenter/entity/MeEntity.kt +++ b/app/src/main/java/com/gh/gamecenter/entity/MeEntity.kt @@ -25,8 +25,8 @@ class MeEntity(@SerializedName("is_community_voted") @SerializedName("is_answer_opposed") var isAnswerOpposed: Boolean = false, // 是否已经踩过回答 - @SerializedName(value = "is_answer_own", alternate = ["is_community_article_own", "is_question_own", "is_video_own"]) - var isContentOwner: Boolean = false, // 是否是当前内容(回答/社区文章/问题/视频)的拥有者 + @SerializedName(value = "is_answer_own", alternate = ["is_community_article_own", "is_question_own", "is_video_own", "is_game_list_own"]) + var isContentOwner: Boolean = false, // 是否是当前内容(回答/社区文章/问题/视频/游戏单)的拥有者 @SerializedName("is_answer_favorite") var isAnswerFavorite: Boolean = false, @@ -62,7 +62,7 @@ class MeEntity(@SerializedName("is_community_voted") var isCommentOwner: Boolean = false, // 是否是当前评论的拥有者 @SyncPage(syncNames = [SyncFieldConstants.ARTICLE_COMMENT_VOTE]) - @SerializedName("is_comment_voted", alternate = ["is_answer_comment_voted", "is_video_comment_voted", "is_community_article_comment_voted", "is_question_comment_voted"]) + @SerializedName("is_comment_voted", alternate = ["is_answer_comment_voted", "is_video_comment_voted", "is_community_article_comment_voted", "is_question_comment_voted", "is_game_list_voted"]) var isCommentVoted: Boolean = false, // 是否已经点赞过当前评论 @SerializedName("is_version_requested") @@ -116,7 +116,9 @@ class MeEntity(@SerializedName("is_community_voted") var questionDraft: QuestionDraftEntity? = null,//问题详情可能返回草稿 @SerializedName("is_follow_bbs") - var isFollowForum: Boolean = false + var isFollowForum: Boolean = false, + + var vote: Boolean = false ) : Parcelable @Parcelize diff --git a/app/src/main/java/com/gh/gamecenter/entity/MessageEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/MessageEntity.kt index e9b4d33af9..d1f03036fb 100644 --- a/app/src/main/java/com/gh/gamecenter/entity/MessageEntity.kt +++ b/app/src/main/java/com/gh/gamecenter/entity/MessageEntity.kt @@ -24,6 +24,9 @@ class MessageEntity { var answer: Answer = Answer() + @SerializedName("game_list") + var gameList: GameList = GameList() + var type: String = "" var read: Boolean = true // true:已读,false:未读 @@ -60,7 +63,7 @@ class MessageEntity { title = parcel.readString() thumb = parcel.readString() communityId = parcel.readString() - images = parcel.createStringArrayList()?: arrayListOf() + images = parcel.createStringArrayList() ?: arrayListOf() } override fun writeToParcel(parcel: Parcel, flags: Int) { @@ -102,7 +105,7 @@ class MessageEntity { constructor(parcel: Parcel) : this() { id = parcel.readString() content = parcel.readString() - images = parcel.createStringArrayList()?: arrayListOf() + images = parcel.createStringArrayList() ?: arrayListOf() } override fun writeToParcel(parcel: Parcel, flags: Int) { @@ -170,7 +173,7 @@ class MessageEntity { @SerializedName("_id") var id: String? = null - @SerializedName("top_id") + @SerializedName("top_id", alternate = ["parent_id"]) var topId: String? = null var content: String? = null @@ -216,4 +219,11 @@ class MessageEntity { var urlComment: String? = null } + class GameList { + @SerializedName("_id") + var id: String = "" + var cover: String = "" + var title: String = "" + } + } diff --git a/app/src/main/java/com/gh/gamecenter/entity/ForumShareEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/NormalShareEntity.kt similarity index 93% rename from app/src/main/java/com/gh/gamecenter/entity/ForumShareEntity.kt rename to app/src/main/java/com/gh/gamecenter/entity/NormalShareEntity.kt index fda128ebcb..0f5f4c4b2b 100644 --- a/app/src/main/java/com/gh/gamecenter/entity/ForumShareEntity.kt +++ b/app/src/main/java/com/gh/gamecenter/entity/NormalShareEntity.kt @@ -5,7 +5,7 @@ import com.gh.common.util.ShareUtils import kotlinx.android.parcel.Parcelize @Parcelize -data class ForumShareEntity( +data class NormalShareEntity( var id: String = "", var shareUrl: String = "", var shareIcon: String = "", diff --git a/app/src/main/java/com/gh/gamecenter/entity/PersonalEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/PersonalEntity.kt index 95755b0233..bc6cc27ee8 100644 --- a/app/src/main/java/com/gh/gamecenter/entity/PersonalEntity.kt +++ b/app/src/main/java/com/gh/gamecenter/entity/PersonalEntity.kt @@ -40,6 +40,8 @@ data class PersonalEntity( @SerializedName("game_comment") val gameComment: Int = 0, val video: Int = 0, + @SerializedName("game_list") + val gameList: Int = 0, @SerializedName("today_visit") val todayVisit: Int? = 0) : Parcelable { diff --git a/app/src/main/java/com/gh/gamecenter/entity/SubjectEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/SubjectEntity.kt index ea5538ddd5..6fa025e7b1 100644 --- a/app/src/main/java/com/gh/gamecenter/entity/SubjectEntity.kt +++ b/app/src/main/java/com/gh/gamecenter/entity/SubjectEntity.kt @@ -30,6 +30,9 @@ data class SubjectEntity( @SerializedName("common_collection_content") var commonCollectionList: MutableList? = null, + @SerializedName("game_list_collection") + var gameListCollection: List? = null, + @SerializedName("show_name") var showName: Boolean = true, // 是否显示“专题名字”,true、false @SerializedName("show_suffix") diff --git a/app/src/main/java/com/gh/gamecenter/entity/TagInfoEntity.kt b/app/src/main/java/com/gh/gamecenter/entity/TagInfoEntity.kt new file mode 100644 index 0000000000..f4c3e67bb0 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/entity/TagInfoEntity.kt @@ -0,0 +1,12 @@ +package com.gh.gamecenter.entity + +import android.os.Parcelable +import com.google.gson.annotations.SerializedName +import kotlinx.android.parcel.Parcelize + +@Parcelize +data class TagInfoEntity( + @SerializedName("_id") + var id: String = "", + var name: String = "" +) : Parcelable diff --git a/app/src/main/java/com/gh/gamecenter/fragment/HomeSearchToolWrapperFragment.kt b/app/src/main/java/com/gh/gamecenter/fragment/HomeSearchToolWrapperFragment.kt index 8164293b66..f0ad72c3e2 100644 --- a/app/src/main/java/com/gh/gamecenter/fragment/HomeSearchToolWrapperFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/fragment/HomeSearchToolWrapperFragment.kt @@ -26,6 +26,7 @@ import com.gh.gamecenter.entity.SubjectRecommendEntity import com.gh.gamecenter.game.GameFragment import com.gh.gamecenter.game.columncollection.detail.ColumnCollectionDetailFragment import com.gh.gamecenter.game.commoncollection.detail.CommonCollectionDetailFragment +import com.gh.gamecenter.gamecollection.square.GameCollectionSquareFragment import com.gh.gamecenter.home.HomeFragment import com.gh.gamecenter.servers.GameServersPublishFragment import com.gh.gamecenter.servers.GameServersTestFragment @@ -384,7 +385,7 @@ class HomeSearchToolWrapperFragment : SearchToolWrapperFragment() { private fun generateFragments(tabList: ArrayList): ArrayList { val fragmentList = arrayListOf() - for (tab in tabList) { + for ((index, tab) in tabList.withIndex()) { val fragment = when (tab.type) { "home" -> HomeFragment().with(Bundle()) "top_game_comment" -> AmwayFragment().with(Bundle()) @@ -392,6 +393,11 @@ class HomeSearchToolWrapperFragment : SearchToolWrapperFragment() { putParcelable(EntranceUtils.KEY_BLOCK_DATA, tab) }) "server" -> GameServersPublishFragment().with(Bundle()) + "game_list" -> GameCollectionSquareFragment().with(Bundle().apply { + putString(EntranceUtils.KEY_ENTRANCE, "顶部tab") + putInt(EntranceUtils.KEY_TAB_INDEX, index) + putString(EntranceUtils.KEY_NAME, tab.name) + }) "column_test" -> GameServersTestFragment().with(Bundle().apply { putString(GameServersTestFragment.TEST_COLUMN_ID, tab.link) }) diff --git a/app/src/main/java/com/gh/gamecenter/game/GameFragmentAdapter.kt b/app/src/main/java/com/gh/gamecenter/game/GameFragmentAdapter.kt index fdafb07946..83ae9a52b2 100644 --- a/app/src/main/java/com/gh/gamecenter/game/GameFragmentAdapter.kt +++ b/app/src/main/java/com/gh/gamecenter/game/GameFragmentAdapter.kt @@ -49,6 +49,7 @@ import com.gh.gamecenter.game.vertical.GameVerticalAdapter import com.gh.gamecenter.game.vertical.GameVerticalSlideViewHolder import com.gh.gamecenter.game.vertical.OnPagerSnapScrollListener import com.gh.gamecenter.home.BlankDividerViewHolder +import com.gh.gamecenter.home.gamecollection.HomeGameCollectionViewHolder import com.gh.gamecenter.servers.GameServersActivity import com.gh.gamecenter.subject.SubjectActivity import com.lightgame.adapter.BaseRecyclerAdapter @@ -102,6 +103,8 @@ class GameFragmentAdapter( if (itemData.gallerySlide != null) return ItemViewType.GALLERY_SLIDE if (itemData.blankDivider != null) return ItemViewType.BLANK_DIVIDER if (itemData.rankCollection != null) return ItemViewType.RANK_COLLECTION + if (itemData.gameCollection != null) return ItemViewType.GAME_COLLECTION_ITEM + return ItemViewType.LOADING } @@ -182,6 +185,9 @@ class GameFragmentAdapter( ItemViewType.RANK_COLLECTION -> { RankCollectionViewHolder(RankCollectionListBinding.bind(mLayoutInflater.inflate(R.layout.rank_collection_list, parent, false))) } + ItemViewType.GAME_COLLECTION_ITEM -> { + HomeGameCollectionViewHolder(HomeGameCollectionItemBinding.bind(mLayoutInflater.inflate(R.layout.home_game_collection_item, parent, false))) + } else -> GameItemViewHolder(GameItemBinding.bind(mLayoutInflater.inflate(R.layout.game_item, parent, false))) } } @@ -203,6 +209,7 @@ class GameFragmentAdapter( is BlankDividerViewHolder -> holder.bindView(mItemDataList[position].blankDivider!!) is CommonCollectionViewHolder -> bindCommonCollection(holder, position) is RankCollectionViewHolder -> bindRankCollection(holder, position) + is HomeGameCollectionViewHolder -> bindGameCollection(holder, position) } } @@ -830,6 +837,9 @@ class GameFragmentAdapter( blockData?.name ?: "" ) } + "game_list_collection" -> { + DirectUtils.directToGameCollectionSquare(mContext, "版块内容列表", column.name ?: "") + } else -> { if (column.indexRightTopLink != null) { val link = column.indexRightTopLink!! @@ -859,6 +869,45 @@ class GameFragmentAdapter( } } + private fun bindGameCollection(holder: HomeGameCollectionViewHolder, position: Int) { + val gameItemData = mItemDataList[position] + val gameCollectionItemDataList = gameItemData.gameCollection?: listOf() + + val exposureList = arrayListOf() + for (gameCollectionItemData in gameCollectionItemDataList) { + runOnIoThread(true) { + val gameCollection = gameCollectionItemData.gameCollectionItem + val gameCollectionSource = listOf(ExposureSource("游戏单", "${gameCollection?.title} + ${gameCollection?.id}")) + val gameExposureList = arrayListOf() + gameCollection?.games?.get(0)?.let { + gameExposureList.add(ExposureEvent.createEventWithSourceConcat( + gameEntity = it.toGameEntity().apply { outerSequence = position; sequence = gameCollectionItemData.gameStartPosition + 1 }, + basicSource = mBasicExposureSource, + source = gameCollectionSource + )) + } + gameCollection?.games?.get(1)?.let { + gameExposureList.add(ExposureEvent.createEventWithSourceConcat( + gameEntity = it.toGameEntity().apply { outerSequence = position; sequence = gameCollectionItemData.gameStartPosition + 2 }, + basicSource = mBasicExposureSource, + source = gameCollectionSource + )) + } + gameCollection?.games?.get(2)?.let { + gameExposureList.add(ExposureEvent.createEventWithSourceConcat( + gameEntity = it.toGameEntity().apply { outerSequence = position; sequence = gameCollectionItemData.gameStartPosition + 3 }, + basicSource = mBasicExposureSource, + source = gameCollectionSource + )) + } + gameCollectionItemData.exposureEventList = gameExposureList + exposureList.addAll(gameExposureList) + } + } + gameItemData.exposureEventList = exposureList + holder.bindGameCollectionList(gameCollectionItemDataList, "版块内容列表") + } + override fun getItemCount(): Int { return if (mItemDataList.size > 0) mItemDataList.size + 1 else mItemDataList.size } diff --git a/app/src/main/java/com/gh/gamecenter/game/GameViewModel.kt b/app/src/main/java/com/gh/gamecenter/game/GameViewModel.kt index 6e7ed1e383..70ff0fd055 100644 --- a/app/src/main/java/com/gh/gamecenter/game/GameViewModel.kt +++ b/app/src/main/java/com/gh/gamecenter/game/GameViewModel.kt @@ -14,6 +14,7 @@ import com.gh.gamecenter.baselist.LoadStatus import com.gh.gamecenter.entity.* import com.gh.gamecenter.game.data.GameItemData import com.gh.gamecenter.game.data.GameSubjectData +import com.gh.gamecenter.gamecollection.square.GameCollectionListItemData import com.gh.gamecenter.home.BlankDividerViewHolder import com.gh.gamecenter.retrofit.BiResponse import com.gh.gamecenter.retrofit.Response @@ -650,6 +651,23 @@ class GameViewModel(application: Application, var blockData: SubjectRecommendEnt } } + if (subjectEntity.type == "game_list_collection") { + val gameCollectionItem = GameItemData() + val itemDataList = arrayListOf().apply { + if (!subjectEntity.gameListCollection.isNullOrEmpty()) { + var position = 0 + for (item in subjectEntity.gameListCollection!!) { + add(GameCollectionListItemData(gameCollectionItem = item, gameStartPosition = position)) + position += if (item.count?.game!! > 2) 3 else item.count?.game ?: 0 + } + } + } + gameCollectionItem.gameCollection = itemDataList + appendAdditionalInfoToSubjectGame(subjectEntity, index) + mItemDataListCache.add(gameCollectionItem) + continue + } + appendAdditionalInfoToSubjectGame(subjectEntity, index) } diff --git a/app/src/main/java/com/gh/gamecenter/game/data/GameItemData.kt b/app/src/main/java/com/gh/gamecenter/game/data/GameItemData.kt index 26063dc792..7b6bfcfb11 100644 --- a/app/src/main/java/com/gh/gamecenter/game/data/GameItemData.kt +++ b/app/src/main/java/com/gh/gamecenter/game/data/GameItemData.kt @@ -5,6 +5,7 @@ import com.gh.gamecenter.entity.GameEntity import com.gh.gamecenter.entity.LinkEntity import com.gh.gamecenter.entity.SubjectEntity import com.gh.gamecenter.entity.SubjectRecommendEntity +import com.gh.gamecenter.gamecollection.square.GameCollectionListItemData class GameItemData { var game: GameEntity? = null @@ -24,6 +25,8 @@ class GameItemData { var rankCollection: SubjectEntity? = null + var gameCollection: List? = null + var blankDivider: Float? = null // 空白的空间补全item var exposureEvent: ExposureEvent? = null diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/choose/AddGamesActivity.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/choose/AddGamesActivity.kt new file mode 100644 index 0000000000..09a979b0d0 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/choose/AddGamesActivity.kt @@ -0,0 +1,19 @@ +package com.gh.gamecenter.gamecollection.choose + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import com.gh.gamecenter.NormalActivity + +class AddGamesActivity : NormalActivity() { + + override fun provideNormalIntent(): Intent { + return getTargetIntent(this, AddGamesActivity::class.java, AddGamesFragment::class.java) + } + + companion object { + fun getIntent(context: Context): Intent { + return getTargetIntent(context, AddGamesActivity::class.java, AddGamesFragment::class.java) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/choose/AddGamesFragment.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/choose/AddGamesFragment.kt new file mode 100644 index 0000000000..b994ba2dfa --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/choose/AddGamesFragment.kt @@ -0,0 +1,19 @@ +package com.gh.gamecenter.gamecollection.choose + +import androidx.core.os.bundleOf +import androidx.fragment.app.Fragment +import com.gh.base.fragment.BaseLazyTabFragment +import com.gh.common.util.EntranceUtils + +class AddGamesFragment : BaseLazyTabFragment() { + + override fun initFragmentList(fragments: MutableList) { + fragments.add(AddSearchGameFragment().apply { bundleOf(EntranceUtils.KEY_NAVIGATION_TITLE to "添加游戏") }) + fragments.add(AddUserPlayedGameFragment()) + } + + override fun initTabTitleList(tabTitleList: MutableList) { + tabTitleList.add("搜索游戏") + tabTitleList.add("玩过游戏") + } +} diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/choose/AddSearchAndPlayedGameAdapter.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/choose/AddSearchAndPlayedGameAdapter.kt new file mode 100644 index 0000000000..3864f12d74 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/choose/AddSearchAndPlayedGameAdapter.kt @@ -0,0 +1,44 @@ +package com.gh.gamecenter.gamecollection.choose + +import android.content.Context +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.gh.common.util.ToastUtils +import com.gh.common.util.toColor +import com.gh.common.util.toDrawable +import com.gh.gamecenter.R +import com.gh.gamecenter.game.GameItemViewHolder +import com.gh.gamecenter.qa.editor.GameAdapter + +class AddSearchAndPlayedGameAdapter(context: Context, val mChooseGamesViewModel: ChooseGamesViewModel) : GameAdapter(context) { + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + if (holder is GameItemViewHolder) { + val entity = mEntityList[position] + val isSelected = mChooseGamesViewModel.chooseGamesLiveData.value?.find { it.id == entity.id } != null + holder.binding.game = entity + holder.binding.downloadBtn.text = if (isSelected) "删除" else "添加" + holder.binding.downloadBtn.background = + if (isSelected) R.drawable.bg_shape_f5_radius_999.toDrawable() else R.drawable.download_button_normal_style.toDrawable() + holder.binding.downloadBtn.setTextColor(if (isSelected) R.color.text_999999.toColor() else R.color.white.toColor()) + + holder.binding.downloadBtn.setOnClickListener { + val chooseGameList = mChooseGamesViewModel.chooseGamesLiveData.value ?: arrayListOf() + if (isSelected) { + chooseGameList.remove(chooseGameList.find { it.id == entity.id }) + ToastUtils.showToast("游戏已移除") + } else { + if (chooseGameList.size >= 100) { + ToastUtils.showToast("已添加游戏到达上限") + return@setOnClickListener + } + chooseGameList.add(entity) + ToastUtils.showToast("游戏已添加") + } + notifyItemChanged(position) + mChooseGamesViewModel.chooseGamesLiveData.postValue(chooseGameList) + } + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/choose/AddSearchGameFragment.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/choose/AddSearchGameFragment.kt new file mode 100644 index 0000000000..a1799ffd54 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/choose/AddSearchGameFragment.kt @@ -0,0 +1,59 @@ +package com.gh.gamecenter.gamecollection.choose + +import android.os.Bundle +import android.view.View +import androidx.core.widget.doOnTextChanged +import androidx.recyclerview.widget.RecyclerView +import com.gh.common.util.goneIf +import com.gh.common.util.viewModelProvider +import com.gh.gamecenter.R +import com.gh.gamecenter.SuggestionActivity +import com.gh.gamecenter.databinding.FragmentSearchGameBinding +import com.gh.gamecenter.qa.editor.GameAdapter +import com.gh.gamecenter.qa.editor.GameFragment +import com.gh.gamecenter.suggest.SuggestType + +class AddSearchGameFragment : GameFragment() { + + private lateinit var mBinding: FragmentSearchGameBinding + private lateinit var mChooseGamesViewModel: ChooseGamesViewModel + + override fun getLayoutId(): Int = 0 + + override fun getInflatedLayout(): View { + return FragmentSearchGameBinding.inflate(layoutInflater, null, false).apply { + mBinding = this + }.root + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + mChooseGamesViewModel = viewModelProvider(ChooseGamesViewModel.Factory()) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setNavigationTitle("添加游戏") + noneText.text = "没有找到相关游戏,换个搜索词试试?" + searchEt.doOnTextChanged { text, _, _, _ -> + mBinding.promptTv.goneIf(!text.isNullOrEmpty()) + } + + mBinding.applyGameTv.setOnClickListener { + SuggestionActivity.startSuggestionActivity(requireContext(), SuggestType.gameCollect, "【游戏单添加游戏】") + } + } + + override fun getItemDecoration(): RecyclerView.ItemDecoration? { + return null + } + + override fun provideListAdapter(): GameAdapter? { + if (mAdapter == null) { + mAdapter = AddSearchAndPlayedGameAdapter(requireContext(), mChooseGamesViewModel) + } + return mAdapter + } + + override fun isAutoShowKeyboard(): Boolean = false +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/choose/AddUserPlayedGameFragment.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/choose/AddUserPlayedGameFragment.kt new file mode 100644 index 0000000000..c2801c2e23 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/choose/AddUserPlayedGameFragment.kt @@ -0,0 +1,51 @@ +package com.gh.gamecenter.gamecollection.choose + +import android.os.Bundle +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.gh.common.util.EntranceUtils +import com.gh.common.util.toColor +import com.gh.common.util.viewModelProvider +import com.gh.gamecenter.R +import com.gh.gamecenter.baselist.ListAdapter +import com.gh.gamecenter.baselist.ListFragment +import com.gh.gamecenter.entity.GameEntity +import com.gh.gamecenter.manager.UserManager +import com.gh.gamecenter.mygame.PlayedGameViewModel +import com.gh.gamecenter.qa.editor.GameAdapter + +class AddUserPlayedGameFragment : ListFragment() { + + private var mAdapter: GameAdapter? = null + private lateinit var mViewModel: PlayedGameViewModel + private lateinit var mChooseGamesViewModel: ChooseGamesViewModel + + override fun getLayoutId(): Int = R.layout.fragment_add_user_played_game + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + mChooseGamesViewModel = viewModelProvider(ChooseGamesViewModel.Factory()) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + mCachedView.setBackgroundColor(R.color.white.toColor()) + } + + override fun provideListAdapter(): ListAdapter { + return mAdapter ?: AddSearchAndPlayedGameAdapter(requireContext(), mChooseGamesViewModel).apply { + mAdapter = this + } + } + + override fun getItemDecoration(): RecyclerView.ItemDecoration? { + return null + } + + override fun provideListViewModel(): PlayedGameViewModel { + val userId = arguments?.getString(EntranceUtils.KEY_USER_ID) + ?: UserManager.getInstance().userId + mViewModel = viewModelProvider(PlayedGameViewModel.Factory(userId, true)) + return mViewModel + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/choose/ChooseGamesActivity.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/choose/ChooseGamesActivity.kt new file mode 100644 index 0000000000..0769fa62a5 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/choose/ChooseGamesActivity.kt @@ -0,0 +1,23 @@ +package com.gh.gamecenter.gamecollection.choose + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import com.gh.gamecenter.NormalActivity + +class ChooseGamesActivity : NormalActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setNavigationTitle("选择游戏") + } + + override fun provideNormalIntent(): Intent { + return getTargetIntent(this, ChooseGamesActivity::class.java, ChooseGamesFragment::class.java) + } + + companion object { + fun getIntent(context: Context): Intent { + return getTargetIntent(context, ChooseGamesActivity::class.java, ChooseGamesFragment::class.java) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/choose/ChooseGamesAdapter.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/choose/ChooseGamesAdapter.kt new file mode 100644 index 0000000000..5deb3cccf1 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/choose/ChooseGamesAdapter.kt @@ -0,0 +1,93 @@ +package com.gh.gamecenter.gamecollection.choose + +import android.annotation.SuppressLint +import android.content.Context +import android.view.MotionEvent +import android.view.ViewGroup +import androidx.core.widget.doOnTextChanged +import androidx.recyclerview.widget.RecyclerView +import com.gh.base.BaseRecyclerViewHolder +import com.gh.common.util.PatternUtils +import com.gh.common.util.TextHelper +import com.gh.common.util.consume +import com.gh.common.util.toBinding +import com.gh.gamecenter.baselist.ListAdapter +import com.gh.gamecenter.databinding.ItemChooseGamesBinding +import com.gh.gamecenter.entity.GameEntity +import com.lightgame.adapter.BaseRecyclerAdapter + +class ChooseGamesAdapter(context: Context, val dragListener: ItemDragListener) : + ListAdapter(context) { + + + public override fun setListData(updateData: MutableList?) { + super.setListData(updateData) + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return ChooseGamesViewHolder(parent.toBinding()) + } + + override fun areItemsTheSame(oldItem: GameEntity?, newItem: GameEntity?): Boolean { + return oldItem == newItem + } + + override fun areContentsTheSame(oldItem: GameEntity?, newItem: GameEntity?): Boolean { + return oldItem?.id == newItem?.id + } + + @SuppressLint("ClickableViewAccessibility") + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + if (holder is ChooseGamesViewHolder) { + val gameEntity = mEntityList[position] + holder.binding.recommendReasonEt.run { + doOnTextChanged { text, start, _, _ -> + if(PatternUtils.isHasWrap(text.toString())){ + setText(PatternUtils.replaceWrap(text.toString())) + setSelection(start) + return@doOnTextChanged + } + if (PatternUtils.isHasSpace(text.toString())) { + setText(PatternUtils.replaceSpace(text.toString())) + setSelection(start) + return@doOnTextChanged + } + } + } + holder.binding.gameNameTv.text = gameEntity.name + holder.binding.gameIcon.displayGameIcon(gameEntity) + holder.binding.recommendReasonEt.setText(gameEntity.recommendText) + holder.binding.ratingScore.rating = gameEntity.recommendStar.toFloat() + holder.binding.recommendReasonEt.filters = arrayOf(TextHelper.getFilter(45, "最多输入45个字")) + holder.binding.recommendReasonEt.doOnTextChanged { text, _, _, _ -> + gameEntity.recommendText = text.toString() + } + holder.binding.ratingScore.setOnRatingChangeListener { _, rating -> + gameEntity.recommendStar = rating.toInt() + } + holder.binding.deleteIv.setOnClickListener { + dragListener.deleteItem(gameEntity) + } + holder.binding.dragView.setOnTouchListener { _, event -> + consume { + if (event.action == MotionEvent.ACTION_DOWN) { + dragListener.startDragItem(holder) + } + } + } + holder.binding.topView.setOnClickListener { + dragListener.setToTop(holder) + } + } + } + + override fun getItemCount(): Int = mEntityList.size + + class ChooseGamesViewHolder(val binding: ItemChooseGamesBinding) : BaseRecyclerViewHolder(binding.root) + + interface ItemDragListener { + fun startDragItem(holder: RecyclerView.ViewHolder) + fun setToTop(holder: RecyclerView.ViewHolder) + fun deleteItem(entity: GameEntity) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/choose/ChooseGamesFragment.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/choose/ChooseGamesFragment.kt new file mode 100644 index 0000000000..ee4cf9d765 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/choose/ChooseGamesFragment.kt @@ -0,0 +1,141 @@ +package com.gh.gamecenter.gamecollection.choose + +import android.os.Bundle +import android.view.MenuItem +import android.view.View +import androidx.recyclerview.widget.ItemTouchHelper +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.gh.common.util.DialogHelper +import com.gh.common.util.goneIf +import com.gh.common.util.viewModelProvider +import com.gh.gamecenter.R +import com.gh.gamecenter.databinding.FragmentChooseGamesBinding +import com.gh.gamecenter.entity.GameEntity +import com.gh.gamecenter.normal.NormalFragment +import java.lang.ref.WeakReference +import java.util.* + +class ChooseGamesFragment : NormalFragment(), ChooseGamesAdapter.ItemDragListener { + + private lateinit var mBinding: FragmentChooseGamesBinding + private lateinit var mViewModel: ChooseGamesViewModel + private lateinit var mAdapter: ChooseGamesAdapter + + private val mItemTouchCallback = CustomItemTouchHelper(this) + private val mItemTouchHelper = ItemTouchHelper(mItemTouchCallback) + + override fun getLayoutId(): Int = 0 + + override fun getInflatedLayout(): View { + return FragmentChooseGamesBinding.inflate(layoutInflater, null, false).apply { + mBinding = this + }.root + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + initMenu(R.menu.menu_save) + mViewModel = viewModelProvider(ChooseGamesViewModel.Factory()) + } + + override fun onMenuItemClick(menuItem: MenuItem?) { + super.onMenuItemClick(menuItem) + menuItem?.run { + if (itemId == R.id.layout_menu_save) { + requireActivity().finish() + } + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + mViewModel.chooseGamesLiveData.observe(viewLifecycleOwner) { + mBinding.addGamesTv.goneIf(it.isNotEmpty()) + mBinding.gamesRv.goneIf(it.isEmpty()) + mAdapter.setListData(it) + mBinding.gameCountTv.text = "已收录${it.size}款游戏" + } + mBinding.gamesRv.apply { + layoutManager = LinearLayoutManager(requireContext()) + adapter = ChooseGamesAdapter(requireContext(), this@ChooseGamesFragment).apply { + mAdapter = this + } + mItemTouchHelper.attachToRecyclerView(this) + } + + mBinding.addGamesButton.setOnClickListener { + requireContext().startActivity(AddGamesActivity.getIntent(requireContext())) + } + + mBinding.addGamesTv.setOnClickListener { mBinding.addGamesButton.performClick() } + + } + + override fun startDragItem(holder: RecyclerView.ViewHolder) { + mItemTouchHelper.startDrag(holder) + } + + override fun setToTop(holder: RecyclerView.ViewHolder) { + val holderPosition = holder.bindingAdapterPosition + for (i in holderPosition downTo 1) { + mAdapter.notifyItemMoved(i, i - 1) + Collections.swap(mAdapter.entityList, i, i - 1) + val games = mViewModel.chooseGamesLiveData.value ?: arrayListOf() + Collections.swap(games, i, i - 1) + } + } + + override fun deleteItem(entity: GameEntity) { + val chooseGames = mViewModel.chooseGamesLiveData.value + chooseGames?.remove(entity) + mViewModel.chooseGamesLiveData.postValue(chooseGames) + } + + override fun onBackPressed(): Boolean { + val games = mViewModel.chooseGamesLiveData.value ?: arrayListOf() + if (games.isNotEmpty()) { + DialogHelper.showDialog(requireContext(), "提示", "是否保存本次选择的游戏", "保存", "取消", { + requireActivity().finish() + }, { + mViewModel.chooseGamesLiveData.postValue(arrayListOf()) + requireActivity().finish() + }, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)) + return true + } + return super.onBackPressed() + } + + class CustomItemTouchHelper(val fragment: ChooseGamesFragment) : ItemTouchHelper.Callback() { + + var fragmentReference: WeakReference = WeakReference(fragment) + + override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int { + return makeMovementFlags(ItemTouchHelper.UP or ItemTouchHelper.DOWN, 0) + } + + override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean { + val fragment = fragmentReference.get() + fragment?.run { + mAdapter.notifyItemMoved(viewHolder.bindingAdapterPosition, target.bindingAdapterPosition) + Collections.swap(mAdapter.entityList, viewHolder.bindingAdapterPosition, target.bindingAdapterPosition) + val games = mViewModel.chooseGamesLiveData.value ?: arrayListOf() + Collections.swap(games, viewHolder.bindingAdapterPosition, target.bindingAdapterPosition) + } + return true + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + + } + + override fun canDropOver(recyclerView: RecyclerView, current: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean { + return true + } + + override fun isLongPressDragEnabled(): Boolean { + return false + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/choose/ChooseGamesRepository.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/choose/ChooseGamesRepository.kt new file mode 100644 index 0000000000..4ccdd3d248 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/choose/ChooseGamesRepository.kt @@ -0,0 +1,12 @@ +package com.gh.gamecenter.gamecollection.choose + +import androidx.lifecycle.MutableLiveData +import com.gh.common.util.SingletonHolder +import com.gh.gamecenter.entity.GameEntity + +class ChooseGamesRepository private constructor() { + + val chooseGamesLiveData: MutableLiveData> = MutableLiveData() + + companion object : SingletonHolder(::ChooseGamesRepository) +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/choose/ChooseGamesViewModel.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/choose/ChooseGamesViewModel.kt new file mode 100644 index 0000000000..3822a7e5d1 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/choose/ChooseGamesViewModel.kt @@ -0,0 +1,21 @@ +package com.gh.gamecenter.gamecollection.choose + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import com.halo.assistant.HaloApp + +class ChooseGamesViewModel(application: Application, repository: ChooseGamesRepository) : AndroidViewModel(application) { + + val chooseGamesLiveData = repository.chooseGamesLiveData + + class Factory : ViewModelProvider.NewInstanceFactory() { + override fun create(modelClass: Class): T { + return ChooseGamesViewModel( + HaloApp.getInstance().application, + ChooseGamesRepository.getInstance() + ) as T + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/detail/GameCollectionDetailActivity.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/detail/GameCollectionDetailActivity.kt new file mode 100644 index 0000000000..8298502af8 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/detail/GameCollectionDetailActivity.kt @@ -0,0 +1,38 @@ +package com.gh.gamecenter.gamecollection.detail + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import com.gh.common.util.DisplayUtils +import com.gh.common.util.EntranceUtils +import com.gh.gamecenter.NormalActivity +import com.gh.gamecenter.R + +class GameCollectionDetailActivity : NormalActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + hideToolbar(true) + DisplayUtils.setStatusBarColor(this, R.color.transparent, false) + } + + override fun provideNormalIntent(): Intent { + return getTargetIntent(this, GameCollectionDetailActivity::class.java, GameCollectionDetailFragment::class.java) + } + + companion object { + @JvmStatic + fun getIntent(context: Context, gameCollectionId: String, isFromSquare: Boolean = false): Intent { + return getIntent(context, gameCollectionId, isFromSquare, false) + } + + @JvmStatic + fun getIntent(context: Context, gameCollectionId: String, isFromSquare: Boolean = false, isScrollToCommentArea: Boolean = false): Intent { + val bundle = Bundle() + bundle.putString(EntranceUtils.KEY_GAME_COLLECTION_ID, gameCollectionId) + bundle.putBoolean(EntranceUtils.KEY_IS_FROM_SQUARE, isFromSquare) + bundle.putBoolean(EntranceUtils.KEY_SCROLL_TO_COMMENT_AREA, isScrollToCommentArea) + return getTargetIntent(context, GameCollectionDetailActivity::class.java, GameCollectionDetailFragment::class.java, bundle) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/detail/GameCollectionDetailAdapter.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/detail/GameCollectionDetailAdapter.kt new file mode 100644 index 0000000000..975dae3ae2 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/detail/GameCollectionDetailAdapter.kt @@ -0,0 +1,552 @@ +package com.gh.gamecenter.gamecollection.detail + +import android.content.Context +import android.text.SpannableStringBuilder +import android.text.TextUtils +import android.util.SparseArray +import android.view.View +import android.view.ViewGroup +import androidx.constraintlayout.widget.ConstraintSet +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.gh.common.exposure.ExposureEvent +import com.gh.common.exposure.ExposureSource +import com.gh.common.exposure.ExposureType +import com.gh.common.exposure.IExposable +import com.gh.common.syncpage.SyncDataEntity +import com.gh.common.syncpage.SyncFieldConstants +import com.gh.common.syncpage.SyncPageRepository +import com.gh.common.util.* +import com.gh.common.view.GridSpacingItemColorDecoration +import com.gh.gamecenter.GameDetailActivity +import com.gh.gamecenter.R +import com.gh.gamecenter.databinding.GameCollectionGameItemBinding +import com.gh.gamecenter.databinding.ItemArticleDetailCommentBinding +import com.gh.gamecenter.entity.CommentEntity +import com.gh.gamecenter.entity.GameEntity +import com.gh.gamecenter.eventbus.EBDownloadStatus +import com.gh.gamecenter.gamecollection.detail.conversation.GameCollectionCommentConversationAdapter +import com.gh.gamecenter.qa.article.detail.CommentItemData +import com.gh.gamecenter.qa.comment.CommentActivity +import com.gh.gamecenter.qa.comment.CommentPictureAdapter +import com.gh.gamecenter.qa.comment.OnCommentOptionClickListener +import com.gh.gamecenter.qa.comment.base.BaseCommentAdapter +import com.lightgame.download.DownloadEntity +import java.util.ArrayList +import java.util.HashMap + +open class GameCollectionDetailAdapter( + context: Context, + private val type: AdapterType, + private val mEntrance: String, + val mViewModel: GameCollectionDetailViewModel, + commentClosure: ((CommentEntity) -> Unit)? = null +) : BaseCommentAdapter(context, mViewModel, type, mEntrance, commentClosure), IExposable { + + val positionAndPackageMap = HashMap() + private var mExposureEventArray: SparseArray? = null + + override fun setListData(updateData: MutableList?) { + mExposureEventArray = SparseArray(updateData?.size ?: 0) + // 记录游戏位置 + if (updateData != null) { + for (i in 0 until updateData.size) { + val gameEntity = updateData[i].game + if (gameEntity != null) { + var packages = gameEntity.id + for (apkEntity in gameEntity.getApk()) { + packages += apkEntity.packageName + } + gameEntity.sequence = i + positionAndPackageMap[packages + i] = i + } + } + } + super.setListData(updateData) + } + + override fun getItemViewType(position: Int): Int { + val item = mEntityList[position] + return when { + item.game != null -> ITEM_GAME + item.gameEmpty != null -> ITEM_GAME_EMPTY + item.divider != null -> ITEM_DIVIDER + else -> super.getItemViewType(position) + } + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return when (viewType) { + ITEM_GAME -> GameCollectionGameItemViewHolder( + GameCollectionGameItemBinding.bind( + mLayoutInflater.inflate( + R.layout.game_collection_game_item, + parent, + false + ) + ) + ) + + ITEM_GAME_EMPTY -> GameCollectionGameEmptyViewHolder( + mLayoutInflater.inflate( + R.layout.game_collection_detail_none_game_item, + parent, + false + ) + ) + + ITEM_DIVIDER -> GameCollectionDividerViewHolder( + mLayoutInflater.inflate( + R.layout.game_collection_detail_divider_item, + parent, + false + ) + ) + + else -> super.onCreateViewHolder(parent, viewType) + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is CommentFilterViewHolder -> { + holder.bindView(gameCollection = true) + } + + is GameCollectionGameItemViewHolder -> { + holder.bindView(mEntityList[position].game!!, position) + } + + is CommentItemViewHolder -> { + bindComment(holder.binding, mEntityList[position].commentNormal!!) { deleteCommentEntity -> + val findEntity = + mEntityList.find { it.commentNormal != null && it.commentNormal?.id == deleteCommentEntity.id } + val index = mEntityList.indexOf(findEntity) + mEntityList.remove(findEntity) + notifyItemRemoved(index) + + //刷新 ITEM_FILTER + mViewModel.commentCount -= 1 + if (this !is GameCollectionCommentConversationAdapter) { + notifyItemChanged(mViewModel.filterPos) + } + + mEntityList[0].commentTop?.reply = (mEntityList[0].commentTop?.reply + ?: 0) - 1 + SyncPageRepository.postSyncData( + SyncDataEntity( + mEntityList[0].commentTop?.id + ?: "", + SyncFieldConstants.ARTICLE_COMMENT_REPLY_COUNT, + mEntityList[0].commentTop?.reply, + checkFieldEntity = true + ) + ) + //刷新评论详情头部 +// notifyItemChanged(0) + } + } + + is CommentFooterViewHolder -> { + holder.bindView(mIsLoading, mIsNetworkError, mIsOver, R.string.game_collection_load_over_hint) + } + + else -> super.onBindViewHolder(holder, position) + } + } + + fun bindComment( + binding: ItemArticleDetailCommentBinding, + comment: CommentEntity, + deleteCallBack: ((comment: CommentEntity) -> Unit)? = null + ) { + bindNormalComment(binding, comment, deleteCallBack) + binding.run { + if (type == AdapterType.COMMENT) { + // 游戏单详情页面用的样式 + updateSubComment(this, comment) + + floorHintTv.visibility = View.INVISIBLE + bottomDivider.visibility = View.VISIBLE + + root.setOnClickListener { + mContext.startActivity( + CommentActivity.getGameCollectionCommentReplyIntent( + mContext, + mViewModel.gameCollectionId, + comment.id ?: "", + mViewModel.commentCount, + comment + ) + ) + } + + commentCountTv.setOnClickListener { + mContext.startActivity( + CommentActivity.getGameCollectionCommentDetailIntent( + mContext, + comment.id ?: "", + mViewModel.gameCollectionId, + false, + mEntrance, + "游戏单详情" + ) + ) + } + + contentTv.text = comment.content + + commentCountTv.text = mViewModel.getCommentText(comment.reply, "回复") + } else { + // 评论详情用的样式 + floorHintTv.text = CommentUtils.getCommentTime(comment.time) + commentCountTv.setOnClickListener { commentClosure?.invoke(comment) } + commentCountTv.setCompoundDrawables(null, null, null, null) + commentCountTv.text = "回复" + likeCountTv.text = mViewModel.getLikeText(comment.vote, "") + root.setOnClickListener { binding.commentCountTv.performClick() } + + if (comment.parentUser != null && !TextUtils.isEmpty(comment.parentUser!!.id) ) { + val prefix = "回复" + val colon = " :" + val parentUserName = " ${comment.parentUser?.name} " + + val prefixSpan = SpanBuilder(prefix).color( + binding.root.context, + 0, + prefix.length, + R.color.text_999999 + ).build() + val parentUserNameSpan = SpanBuilder(parentUserName) + .bold(0, parentUserName.length) + .click(0, parentUserName.length, R.color.text_666666) { + DirectUtils.directToHomeActivity( + binding.root.context, + comment.user.id, + 1, + mEntrance, + PATH_ARTICLE_DETAIL_COMMENT + ) + MtaHelper.onEvent("帖子详情", "引用回复区域", "用户名字") + }.build() + val colonSpan = SpanBuilder(colon).color( + binding.root.context, + 0, + colon.length, + R.color.text_999999 + ).build() + val authorSpan = if (comment.parentUser?.me?.isCommentOwner == true) { + SpanBuilder("作者").image(0, "作者".length, R.drawable.ic_hint_author).build() + } else { + "" + } + + contentTv.text = SpannableStringBuilder() + .append(prefixSpan) + .append(parentUserNameSpan) + .append(authorSpan) + .append(colonSpan) + .append(comment.content) + } else { + contentTv.text = comment.content + } + contentTv.maxLines = Int.MAX_VALUE + } + } + } + + fun bindNormalComment( + binding: ItemArticleDetailCommentBinding, + comment: CommentEntity, + deleteCallBack: ((comment: CommentEntity) -> Unit)? + ) { + binding.run { + if (!comment.images.isNullOrEmpty()) { + if (commentPictureRv.adapter == null) { + commentPictureRv.apply { + layoutManager = GridLayoutManager(context, 3) + adapter = CommentPictureAdapter(context, comment.images!!, "") + if (itemDecorationCount == 0) { + addItemDecoration(GridSpacingItemColorDecoration(context, 2, R.color.white)) + } + visibility = View.VISIBLE + } + } else { + (commentPictureRv.adapter as CommentPictureAdapter).checkResetData(comment.images!!) + } + } else { + binding.commentPictureRv.visibility = View.GONE + } + this.comment = comment + contentTv.setExpandMaxLines(if (comment.isExpand) Int.MAX_VALUE else 4) + contentTv.setIsExpanded(comment.isExpand) + collapseTv.goneIf(!comment.isExpand) + contentTv.setExpandCallback { + comment.isExpand = true + collapseTv.visibility = View.VISIBLE + this.comment = comment + } + collapseTv.setOnClickListener { + comment.isExpand = false + collapseTv.visibility = View.GONE + contentTv.setExpandMaxLines(4) + contentTv.setIsExpanded(false) + this.comment = comment + } + userIconIv.display( + comment.user.border, + comment.user.icon, + comment.user.auth?.icon + ) + + userIconIv.setOnClickListener { + DirectUtils.directToHomeActivity( + mContext, + comment.user.id, + "", + "游戏单详情-评论" + ) + } + + userNameTv.setOnClickListener { + DirectUtils.directToHomeActivity( + mContext, + comment.user.id, + "", + "游戏单详情-评论" + ) + } + + likeCountTv.text = mViewModel.getLikeText(comment.vote) + + if (comment.me?.isCommentVoted == true) { + likeCountTv.setCompoundDrawablesWithIntrinsicBounds( + R.drawable.comment_vote_select, + 0, + 0, + 0 + ) + } else { + likeCountTv.setCompoundDrawablesWithIntrinsicBounds( + R.drawable.comment_vote_unselect, + 0, + 0, + 0 + ) + } + + likeCountTv.setDebouncedClickListener { + likeCountTv.context.ifLogin("游戏单详情-评论-点赞") { + mViewModel.postVoteGameCollectionComment(comment) { + likeCountTv.setCompoundDrawablesWithIntrinsicBounds( + if (comment.me?.isCommentVoted == true) R.drawable.comment_vote_select else R.drawable.comment_vote_unselect, + 0, + 0, + 0 + ) + likeCountTv.text = mViewModel.getLikeText(comment.vote) + } + } + } + + badgeTv.setOnClickListener { + DialogUtils.showViewBadgeDialog(mContext, comment.user.badge) { + DirectUtils.directToBadgeWall( + mContext, + comment.user.id, + comment.user.name, + comment.user.icon + ) + } + } + + badgeIv.setOnClickListener { badgeTv.performClick() } + + moreIv.setOnClickListener { + CommentHelper.showGameCollectionCommentOption( + it, + comment, + object : OnCommentOptionClickListener { + override fun onCommentOptionClick(entity: CommentEntity, option: String) { + when (option) { + "删除评论" -> { + DialogHelper.showDialog( + binding.moreIv.context, + "提示", + "删除评论后,评论下所有的回复都将被删除", + "删除", + "取消", + { + mViewModel.deleteComment(comment) { + deleteCallBack?.invoke(comment) + } + }, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true) + ) + } + } + } + } + ) + } + } + } + + private fun updateSubComment( + binding: ItemArticleDetailCommentBinding, + comment: CommentEntity, + ) { + val subCommentList = comment.subCommentList + val ownerUserId = mViewModel.ownerUserId + binding.moreSubCommentBtn.goneIf(comment.reply < 3) + binding.moreSubCommentBtn.text = "查看全部${comment.reply}条回复" + binding.subCommentContainer.goneIf(subCommentList.isNullOrEmpty()) + binding.firstSubCommentTv.goneIf(subCommentList?.firstOrNull() == null) + binding.secondSubCommentTv.goneIf(subCommentList?.secondOrNull() == null) + binding.subCommentContainer.setRoundedColorBackground(R.color.text_F5F5F5, 5F) + + subCommentList?.firstOrNull()?.let { + binding.firstSubCommentTv.text = getSubCommentSpanned( + it.user.name, + if (it.user.id == ownerUserId) "作者" else "", + it.content + ) + } + subCommentList?.secondOrNull()?.let { + binding.secondSubCommentTv.text = getSubCommentSpanned( + it.user.name, + if (it.user.id == ownerUserId) "作者" else "", + it.content + ) + } + binding.subCommentContainer.setOnClickListener { + mContext.startActivity( + CommentActivity.getGameCollectionCommentDetailIntent( + mContext, + comment.id ?: "", + mViewModel.gameCollectionId, + false, + mEntrance, + "游戏单详情" + ) + ) + } + } + + private fun getSubCommentSpanned( + name: String?, + author: String?, + content: String? + ): SpannableStringBuilder { + val finalAuthor = author ?: "" + val finalName = "$name " + val colon = " :" + + val nameSpan = + SpanBuilder(finalName).color(0, finalName.length, R.color.theme_font).build() + val authorSpan = if (finalAuthor.isNotEmpty()) SpanBuilder(finalAuthor).image( + 0, + finalAuthor.length, + R.drawable.ic_hint_author + ).build() else "" + val colonSpan = SpanBuilder(colon).color(0, colon.length, R.color.theme_font).build() + return SpannableStringBuilder().append(nameSpan).append(authorSpan).append(colonSpan) + .append(content) + } + + fun notifyItemByDownload(download: DownloadEntity) { + for (key in positionAndPackageMap.keys) { + if (key.contains(download.packageName) && key.contains(download.gameId)) { + val position = positionAndPackageMap[key] + if (position != null && mEntityList != null && position < mEntityList.size && mEntityList[position].game != null) { + mEntityList[position].game!!.getEntryMap()[download.platform] = download + notifyItemChanged(position) + } + } + } + } + + fun notifyItemAndRemoveDownload(status: EBDownloadStatus) { + for (key in positionAndPackageMap.keys) { + if (key.contains(status.packageName) && key.contains(status.gameId)) { + val position = positionAndPackageMap[key] + if (position != null && mEntityList != null && position < mEntityList.size && mEntityList[position].game != null) { + mEntityList[position].game!!.getEntryMap().remove(status.platform) + notifyItemChanged(position) + } + } + } + } + + inner class GameCollectionGameEmptyViewHolder(view: View): RecyclerView.ViewHolder(view) + + inner class GameCollectionDividerViewHolder(view: View): RecyclerView.ViewHolder(view) + + inner class GameCollectionGameItemViewHolder(val binding: GameCollectionGameItemBinding): RecyclerView.ViewHolder(binding.root) { + + fun bindView(gameEntity: GameEntity, position: Int) { + binding.run { + game = gameEntity + executePendingBindings() + + val exposureSources = ArrayList().apply { + add(ExposureSource("首页搜索", "${mViewModel.gameCollectionTitle}+${mViewModel.gameCollectionId}")) + } + val exposureEvent = ExposureEvent.createEvent( + gameEntity, + exposureSources, + null, + ExposureType.EXPOSURE + ) + mExposureEventArray!!.put(position, exposureEvent) + + recommendTv.post { + if (!TextUtils.isEmpty(gameEntity.recommendText)) { + ConstraintSet().apply { + clone(container) + clear(recommendTv.id, ConstraintSet.BOTTOM) + if (recommendTv.lineCount == 1) { + recommendTv.setPadding(0, 0, 0, 0) + connect(recommendTv.id, ConstraintSet.BOTTOM, recommendPref.id, ConstraintSet.BOTTOM) + } else { + recommendTv.setPadding(0, 0, 0, 8F.dip2px()) + connect(recommendTv.id, ConstraintSet.BOTTOM, container.id, ConstraintSet.BOTTOM) + } + applyTo(container) + } + } + } + + root.setOnClickListener { + GameDetailActivity.startGameDetailActivity( + mContext, + gameEntity.id, + mEntrance, + exposureEvent + ) + } + + DownloadItemUtils.setOnClickListener( + mContext, + binding.downloadBtn, + gameEntity, + position, + this@GameCollectionDetailAdapter, + mEntrance, + "游戏单详情-游戏列表:${gameEntity.name}", + exposureEvent) + + DownloadItemUtils.updateDownloadButton(mContext, downloadBtn, gameEntity, false) + } + } + } + + override fun getEventByPosition(pos: Int): ExposureEvent? = mExposureEventArray!!.get(pos) + + override fun getEventListByPosition(pos: Int): List? = null + + companion object { + const val ITEM_GAME = 900 + const val ITEM_GAME_EMPTY = 901 + const val ITEM_DIVIDER = 902 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/detail/GameCollectionDetailFragment.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/detail/GameCollectionDetailFragment.kt new file mode 100644 index 0000000000..8d49061d53 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/detail/GameCollectionDetailFragment.kt @@ -0,0 +1,883 @@ +package com.gh.gamecenter.gamecollection.detail + +import android.app.Activity +import android.content.Intent +import android.graphics.Color +import android.os.Bundle +import android.view.View +import android.view.ViewGroup +import android.view.ViewTreeObserver +import androidx.annotation.ColorRes +import androidx.appcompat.app.AppCompatActivity +import androidx.lifecycle.Lifecycle +import androidx.recyclerview.widget.RecyclerView +import butterknife.OnClick +import com.gh.common.constant.Constants +import com.gh.common.exposure.ExposureListener +import com.gh.common.history.HistoryHelper +import com.gh.common.util.* +import com.gh.common.xapk.XapkInstaller +import com.gh.common.xapk.XapkUnzipStatus +import com.gh.download.DownloadManager +import com.gh.gamecenter.R +import com.gh.gamecenter.baselist.ListFragment +import com.gh.gamecenter.baselist.LoadType +import com.gh.gamecenter.databinding.FragmentGameCollectionDetailBinding +import com.gh.gamecenter.databinding.LayoutGameCollectionTagBinding +import com.gh.gamecenter.entity.* +import com.gh.gamecenter.eventbus.EBShare +import com.gh.gamecenter.eventbus.EBUserFollow +import com.gh.gamecenter.gamedetail.GameDetailFragment +import com.gh.gamecenter.home.video.ScrollCalculatorHelper +import com.gh.gamecenter.manager.UserManager +import com.gh.gamecenter.qa.article.detail.CommentItemData +import com.gh.gamecenter.qa.comment.CommentActivity +import com.gh.gamecenter.qa.comment.base.BaseCommentAdapter +import com.gh.gamecenter.qa.comment.base.BaseCommentViewModel +import com.gh.gamecenter.setting.VideoSettingFragment +import com.gh.gamecenter.video.detail.CustomManager +import com.google.android.material.appbar.AppBarLayout +import com.lightgame.download.DataWatcher +import com.lightgame.download.DownloadEntity +import com.shuyu.gsyvideoplayer.builder.GSYVideoOptionBuilder +import com.shuyu.gsyvideoplayer.listener.GSYSampleCallBack +import com.shuyu.gsyvideoplayer.utils.OrientationUtils +import com.shuyu.gsyvideoplayer.video.base.GSYVideoView +import kotlinx.android.synthetic.main.item_article_detail_comment.view.* +import kotlinx.android.synthetic.main.piece_game_detail_video.* +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode +import kotlin.math.abs + +class GameCollectionDetailFragment : + ListFragment() { + + private var mBinding: FragmentGameCollectionDetailBinding? = null + private var mAdapter: GameCollectionDetailAdapter? = null + private var mEntity: GamesCollectionDetailEntity? = null + private var mGameCollectionId = "" + private var mGameCollectionTitle = "" + private var mScrollToCommentArea = false + private var mFromSquare = false + private var mIsLight = false + private var mIsPauseTopVideo = false + private var mOrientationUtils: OrientationUtils? = null + private val mDataWatcher = object : DataWatcher() { + override fun onDataChanged(downloadEntity: DownloadEntity) { + mAdapter?.notifyItemByDownload(downloadEntity) + + if (downloadEntity.meta[XapkInstaller.XAPK_UNZIP_STATUS] == XapkUnzipStatus.FAILURE.name) { + showUnzipFailureDialog(downloadEntity) + } + } + } + + override fun getLayoutId() = 0 + + override fun getInflatedLayout() = + FragmentGameCollectionDetailBinding.inflate(layoutInflater).apply { mBinding = this }.root + + override fun provideListViewModel() = + viewModelProvider(GameCollectionDetailViewModel.Factory(mGameCollectionId)) + + override fun provideListAdapter() = + mAdapter ?: GameCollectionDetailAdapter(requireContext(), BaseCommentAdapter.AdapterType.COMMENT, mEntrance, mListViewModel).apply { mAdapter = this } + + override fun getItemDecoration() = null + + override fun onCreate(savedInstanceState: Bundle?) { + mGameCollectionId = arguments?.getString(EntranceUtils.KEY_GAME_COLLECTION_ID) ?: "" + mFromSquare = arguments?.getBoolean(EntranceUtils.KEY_IS_FROM_SQUARE, false) ?: false + mScrollToCommentArea = arguments?.getBoolean(EntranceUtils.KEY_SCROLL_TO_COMMENT_AREA, false) ?: false + super.onCreate(savedInstanceState) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + initView() + initObserver() + mListViewModel.getGameCollectionDetail() + + mListRv.addOnScrollListener(ExposureListener(this, provideListAdapter())) + } + + private fun initView() { + mBinding?.run { + inputContainer.run { + bottomShareGroup.visibility = View.VISIBLE + replyTv.setBackgroundResource(R.drawable.button_round_f5f5f5) + replyTv.text = "说点什么吧" + } + fixedTopFilterView.run { + (root.layoutParams as ViewGroup.MarginLayoutParams).apply { + topMargin = DisplayUtils.getStatusBarHeight(resources) + 48F.dip2px() + root.layoutParams = this + } + filterLatestTv.setOnClickListener { + getFilterVH()?.binding?.filterLatestTv?.performClick() + updateFilterView() + } + filterOldestTv.setOnClickListener { + getFilterVH()?.binding?.filterOldestTv?.performClick() + updateFilterView() + } + commentHintTv.text = "游戏单评论" + filterLatestTv.text = "最新" + filterOldestTv.text = "最早" + } + } + } + + private fun initObserver() { + mListViewModel.loadResultLiveData.observeNonNull(this) { + when (it) { + BaseCommentViewModel.LoadResult.SUCCESS -> { + mEntity = mListViewModel.gameCollectionDetail + mGameCollectionTitle = mListViewModel.gameCollectionDetail?.title ?: "" + + postDelayedRunnable({ + tryCatchInRelease { + logEvent("view_game_collect_detail") + } + }, 2000) + + updateView() + initViewListener() + } + + else -> { + if (it == BaseCommentViewModel.LoadResult.DELETED) { + mReuseNoConn?.visibility = View.GONE + mReuseNoData?.visibility = View.VISIBLE + toast(R.string.content_delete_toast) + } else { + mReuseNoConn?.visibility = View.VISIBLE + mReuseNoData?.visibility = View.GONE + mReuseNoConn?.setOnClickListener { + mListViewModel.getGameCollectionDetail() + mReuseNoConn?.visibility = View.GONE + mBinding?.root?.setBackgroundColor(Color.WHITE) + } + } + + DisplayUtils.setStatusBarColor(requireActivity(), R.color.white, true) + mBinding?.toolbarContainer?.setBackgroundColor(Color.WHITE) + mBinding?.backIv?.setImageResource(R.drawable.ic_bar_back) + + mListLoading?.visibility = View.GONE + mBinding?.inputContainer?.bottomContainer?.visibility = View.GONE + mBinding?.bottomShadowView?.visibility = View.GONE + mBinding?.root?.setBackgroundColor(Color.TRANSPARENT) + } + } + } + + mListViewModel.followLiveData.observeNonNull(this) { + updateFollowView() + } + + mListViewModel.favoriteLiveData.observeNonNull(this) { + updateStartView() + } + + mListViewModel.likeLiveData.observeNonNull(this) { + updateLikeView() + } + + mListViewModel.shareLiveData.observeNonNull(this) { + updateShareView() + } + } + + private fun initViewListener() { + mBinding?.appbar?.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset -> + if (!isAdded) return@OnOffsetChangedListener + + val absVerticalOffset = abs(verticalOffset) + mListRefresh?.isEnabled = absVerticalOffset <= 2 + + mBinding?.run { + val bgOffset = if (mListViewModel.displayTopVideo) { + videoItem.player.bottom - DisplayUtils.getStatusBarHeight(resources) - 48F.dip2px() + } else { + imageItem.root.bottom - DisplayUtils.getStatusBarHeight(resources) - 48F.dip2px() + } + mIsLight = absVerticalOffset < bgOffset + updateFollowView() + updateToolbar() + + if (mListViewModel.displayTopVideo) { + if (absVerticalOffset == appBarLayout.totalScrollRange + && videoItem.player.currentState == GSYVideoView.CURRENT_STATE_PLAYING + ) { + pauseVideo() + mIsPauseTopVideo = true + } else if (mIsPauseTopVideo + && absVerticalOffset == 0 + && videoItem.player.currentState == GSYVideoView.CURRENT_STATE_PAUSE + ) { + resumeVideo() + mIsPauseTopVideo = false + } + } + } + }) + + mListRv?.addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + val firstCompletelyVisiblePosition = mLayoutManager.findFirstCompletelyVisibleItemPosition() + if (RecyclerView.NO_POSITION == firstCompletelyVisiblePosition) return + + val filterView = mLayoutManager.findViewByPosition(mListViewModel.filterPos) + + if (firstCompletelyVisiblePosition >= mListViewModel.filterPos + 1 && filterView == null) { + mBinding?.fixedTopFilterView?.root?.visibility = View.VISIBLE + updateFilterView() + } else { + filterView?.let { + if (it.top <= 0 && mBinding?.fixedTopFilterView?.root?.visibility == View.GONE) { + mBinding?.fixedTopFilterView?.root?.visibility = View.VISIBLE + updateFilterView() + } else if (it.top > 0 && mBinding?.fixedTopFilterView?.root?.visibility == View.VISIBLE) { + mBinding?.fixedTopFilterView?.root?.visibility = View.GONE + } + } + } + } + }) + } + + private fun updateToolbar() { + mBinding?.run { + if (mIsLight) { + DisplayUtils.setStatusBarColor(requireActivity(), R.color.transparent, false) + toolbarContainer.setBackgroundColor(Color.TRANSPARENT) + backIv.setImageResource(R.drawable.ic_bar_back_light) + squareIv.setImageResource(R.drawable.ic_game_collection_square_light) + toolbarUserContainer.visibility = View.GONE + if (!mListViewModel.displayTopVideo) { + toolbarLightUserContainer.visibility = View.VISIBLE + } + } else { + DisplayUtils.setStatusBarColor(requireActivity(), R.color.white, true) + toolbarContainer.setBackgroundColor(Color.WHITE) + backIv.setImageResource(R.drawable.ic_bar_back) + squareIv.setImageResource(R.drawable.ic_game_collection_square) + toolbarUserContainer.visibility = View.VISIBLE + if (!mListViewModel.displayTopVideo) { + toolbarLightUserContainer.visibility = View.GONE + } + } + } + } + + private fun updateView() { + mReuseNoConn?.visibility = View.GONE + mListLoading?.visibility = View.GONE + + scrollToComment() + + initTopView() + updateFollowView() + updateStartView() + updateLikeView() + updateShareView() + + mReuseNoConn?.setOnClickListener { + mReuseNoConn?.visibility = View.GONE + mListLoading?.visibility = View.VISIBLE + mListViewModel.getGameCollectionDetail() + } + } + + private fun scrollToComment() { + if (mScrollToCommentArea) { + val listener = object : ViewTreeObserver.OnGlobalLayoutListener { + override fun onGlobalLayout() { + mBinding?.appbar?.setExpanded(false) + mLayoutManager?.scrollToPositionWithOffset(mListViewModel?.filterPos ?: 0, 0) + if (mListViewModel?.filterPos ?: 0 >= mLayoutManager?.findFirstVisibleItemPosition() ?: 0 + && mListViewModel?.filterPos ?: 0 <= mLayoutManager?.findLastVisibleItemPosition() ?: 0) { + mListRv?.viewTreeObserver?.removeOnGlobalLayoutListener(this) + } + } + } + mListRv?.viewTreeObserver?.addOnGlobalLayoutListener(listener) + mListRv.postDelayed({ + tryCatchInRelease { + mListRv?.viewTreeObserver?.removeOnGlobalLayoutListener(listener) + } + }, 2000) + } + } + + private fun initTopView() { + mBinding?.run { + mBinding?.run { + entity = mEntity + executePendingBindings() + squareIv.goneIf(mFromSquare) + } + + if (!mListViewModel.displayTopVideo) { + imageItem.root.visibility = View.VISIBLE + videoItem.root.visibility = View.GONE + initImageTypeView() + } else { + imageItem.root.visibility = View.GONE + videoItem.root.visibility = View.VISIBLE + initVideoTypeView() + } + } + } + + private fun initImageTypeView() { + mBinding?.imageItem?.run { + mEntity?.run { + entity = this + executePendingBindings() + + desTv.text = "游戏单简介:${intro}" + + when (stamp) { + "special_choice" -> { + tagIv.setBackgroundResource(R.drawable.ic_chosen_big) + } + "official" -> { + tagIv.setBackgroundResource(R.drawable.ic_official_big) + } + } + + if (!UserManager.getInstance().isLoggedIn) { + tags?.forEachIndexed { index, tag -> + tagList.addView( + getTagView( + tag.name, + index == tags!!.size - 1, + R.color.white, + R.color.white_alpha_20 + ) + ) + } + } + } + } + } + + private fun initVideoTypeView() { + mBinding?.videoItem?.run { + mEntity?.run { + entity = this + executePendingBindings() + + desTv.text = "游戏单简介:${intro}" + + when (stamp) { + "special_choice" -> { + tagIv.setBackgroundResource(R.drawable.ic_chosen_big) + } + "official" -> { + tagIv.setBackgroundResource(R.drawable.ic_official_big) + } + } + + if (!UserManager.getInstance().isLoggedIn) { + tags?.forEachIndexed { index, tag -> + tagList.addView( + getTagView( + tag.name, + index == tags!!.size - 1, + R.color.theme, + R.color.theme_alpha_20 + ) + ) + } + } + + userIcon.display(user?.border, user?.icon, user?.auth?.icon) + + setUpTopVideo(video!!) + } + } + } + + private fun getTagView( + content: String, + isLast: Boolean, + @ColorRes tvColorRes: Int, + @ColorRes dividerColorRes: Int + ): View { + return LayoutGameCollectionTagBinding.inflate(layoutInflater).apply { + divider.goneIf(isLast) + divider.setBackgroundColor(dividerColorRes.toColor()) + contentTv.text = content + contentTv.setTextColor(tvColorRes.toColor()) + }.root + } + + private fun updateFollowView() { + if (!mListViewModel.displayTopVideo) { + updateImageItemFollowView() + } else { + updateVideoItemFollowView() + } + } + + private fun updateImageItemFollowView() { + mBinding?.toolbarFollowTv?.run { + val isFollow = mEntity?.me?.isFollower ?: false + visibility = View.VISIBLE + text = if (isFollow) "已关注" else "关注" + if (mIsLight) { + setBackgroundResource(if (isFollow) R.drawable.button_round_black_alpha_10 else R.drawable.button_round_black_alpha_30) + setTextColor(if (isFollow) R.color.white_alpha_60.toColor() else R.color.white.toColor()) + } else { + setBackgroundResource(if (isFollow) R.drawable.button_round_f5f5f5 else R.drawable.button_round_1a2496ff) + setTextColor(if (isFollow) R.color.text_999999.toColor() else R.color.theme.toColor()) + } + } + } + + private fun updateVideoItemFollowView() { + val isFollow = mEntity?.me?.isFollower ?: false + mBinding?.videoItem?.videoItemFollowTv?.run { + text = if (isFollow) "已关注" else "关注" + setBackgroundResource(if (isFollow) R.drawable.button_round_f5f5f5 else R.drawable.button_round_1a2496ff) + setTextColor(if (isFollow) R.color.text_999999.toColor() else R.color.theme.toColor()) + } + mBinding?.toolbarFollowTv?.run { + if (mIsLight) { + visibility = View.GONE + } else { + visibility = View.VISIBLE + text = if (isFollow) "已关注" else "关注" + setBackgroundResource(if (isFollow) R.drawable.button_round_f5f5f5 else R.drawable.button_round_1a2496ff) + setTextColor(if (isFollow) R.color.text_999999.toColor() else R.color.theme.toColor()) + } + } + } + + private fun updateStartView() { + mBinding?.inputContainer?.run { + bottomStarTv.text = mListViewModel.getStarText() + if (mEntity?.me?.isFavorite == true) { + bottomStarIv.setImageResource(R.drawable.ic_article_detail_stared_bottom_bar) + } else { + bottomStarIv.setImageResource(R.drawable.ic_article_detail_star_bottom_bar) + } + } + } + + private fun updateLikeView() { + mBinding?.inputContainer?.run { + bottomLikeTv.text = mListViewModel.getLikeText(mEntity?.count?.vote ?: 0) + if (mEntity?.me?.vote == true) { + bottomLikeIv.setImageResource(R.drawable.ic_article_detail_liked_bottom_bar) + } else { + bottomLikeIv.setImageResource(R.drawable.ic_article_detail_like_bottom_bar) + } + } + } + + private fun updateShareView() { + mBinding?.inputContainer?.run { + bottomShareTv.text = mListViewModel.getShareText() + } + } + + private fun setUpTopVideo(video: GamesCollectionDetailEntity.Video) { + mBinding?.videoItem?.player?.run { + mOrientationUtils = OrientationUtils(requireActivity(), this) + mOrientationUtils?.isEnable = false + + GSYVideoOptionBuilder() + .setIsTouchWigetFull(false) + .setIsTouchWiget(false) + .setRotateViewAuto(false) + .setShowFullAnimation(false) + .setSeekRatio(1f) + .setUrl(video.url) + .setCacheWithPlay(true) + .setVideoAllCallBack(object : GSYSampleCallBack() { + override fun onQuitFullscreen(url: String?, vararg objects: Any) { + mOrientationUtils?.backToProtVideo() + logVideoEvent("video_game_collect_detail_full_cancel") + } + }) + .build(this) + + gameCollectionId = mGameCollectionId + gameCollectionTitle = mGameCollectionTitle + viewModel = mListViewModel + this.video = video + updateThumb(video.poster) + + //val trafficVideo = PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SettingsFragment.TRAFFIC_VIDEO_SP_KEY, false) + val videoOption = SPUtils.getString( + Constants.SP_HOME_OR_DETAIL_VIDEO_OPTION, + VideoSettingFragment.VIDEO_OPTION_WIFI + ) + ?: VideoSettingFragment.VIDEO_OPTION_WIFI + when (videoOption) { + VideoSettingFragment.VIDEO_OPTION_ALL -> { + startPlayLogic(isAutoPlay = true) + } + + VideoSettingFragment.VIDEO_OPTION_WIFI -> { + if (NetworkUtils.isWifiConnected(requireContext()) /*|| !trafficVideo*/) { + if (mListViewModel.isTopVideoPartlyCached(video.url)) { + startPlayLogic(isAutoPlay = true) + } else { + // 未有缓存时,为避免影响页面加载,延迟自动播放视频 + postDelayedRunnable({ + if (activity != null && activity?.isFinishing != true) { + startPlayLogic(isAutoPlay = true) + } + }, GameDetailFragment.INITIAL_DELAY) + } + } + } + + else -> { + } + } + + fullscreenButton.setOnClickListener { + val horizontalVideoView = + startWindowFullscreen(requireContext(), true, true) as? GameCollectionVideoView + if (horizontalVideoView == null) { + toastInInternalRelease("全屏失败,请向技术人员提供具体的操作步骤") + return@setOnClickListener + } + mOrientationUtils?.resolveByClick() + horizontalVideoView.uuid = uuid + horizontalVideoView.viewModel = mListViewModel + horizontalVideoView.video = video + horizontalVideoView.updateThumb(video.poster) + horizontalVideoView.violenceUpdateMuteStatus() + logVideoEvent("video_game_collect_detail_full") + } + + observeVolume(this@GameCollectionDetailFragment) + } + } + + override fun onLoadRefresh() { +// showSkeleton(true) + } + + override fun onLoadDone() { +// showSkeleton(false) + mReuseNoConn?.visibility = View.GONE + mReuseNoData?.visibility = View.GONE + mListLoading?.visibility = View.GONE + + mListRv.visibility = View.VISIBLE + hideRefreshingLayout() + + mListRv.postDelayed({ + if (provideListAdapter().itemCount < theNumberNeededToFillAScreen()) { + autoLoadMore() + } + }, autoLoadMoreDelay.toLong()) + } + + override fun onLoadEmpty() { +// showSkeleton(false) + mListRv.visibility = View.VISIBLE + } + + override fun onLoadError() { + super.onLoadError() + mListRv.visibility = View.VISIBLE + } + + override fun onResume() { + if (isEverPause && mAdapter != null) mAdapter?.notifyDataSetChanged() + super.onResume() + DownloadManager.getInstance(context).addObserver(mDataWatcher) + + if (!mIsPauseTopVideo) { + resumeVideo() + } + } + + override fun onPause() { + super.onPause() + DownloadManager.getInstance(context).removeObserver(mDataWatcher) + + pauseVideo() + if (mListViewModel.displayTopVideo) { + mBinding?.videoItem?.player?.run { + val currentPosition = getCurrentPosition() + val topVideo = mEntity?.video + if (topVideo != null) { + ScrollCalculatorHelper.savePlaySchedule( + MD5Utils.getContentMD5(topVideo.url), + currentPosition + ) + } + } + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + + if (requestCode == CommentActivity.REQUEST_CODE && resultCode == Activity.RESULT_OK) { + val commentCount = data?.getIntExtra(CommentActivity.COMMENT_COUNT, 0) + if (commentCount != 0 && commentCount != null) { + mEntity?.count?.comment = commentCount + mBinding?.inputContainer?.bottomCommentTv?.text = mListViewModel.getCommentText( + mEntity?.count?.comment + ?: 0, "评论" + ) + updateFilterView() + mListViewModel.load(LoadType.REFRESH) + } + } + } + + override fun onDestroyView() { + super.onDestroyView() + mEntity?.run { + if (status == "pass" && display != "self_only") { + HistoryHelper.insertGamesCollectionEntity(this) + } + } + } + + override fun onBackPressed(): Boolean { + mOrientationUtils?.backToProtVideo() + + if (CustomManager.backFromWindowFull(requireActivity(), mBinding?.videoItem?.player?.getKey())) { + return true + } + + return super.onBackPressed() + } + + override fun onDestroy() { + super.onDestroy() + releaseVideo() + } + + private fun pauseVideo() { + if (mListViewModel.displayTopVideo) { + mBinding?.videoItem?.player?.onVideoPause() + } + } + + private fun resumeVideo() { + if (mListViewModel.displayTopVideo) { + mBinding?.videoItem?.player?.onVideoResume() + } + } + + private fun releaseVideo() { + if (mListViewModel.displayTopVideo) { + mBinding?.videoItem?.player?.release() + mBinding?.videoItem?.player?.disposableTimer() + } + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onUserFollow(change: EBUserFollow) { + mEntity?.me?.isFollower = change.isFollow + updateFollowView() + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onEventMainThread(share: EBShare?) { + postDelayedRunnable({ + tryCatchInRelease { + if (share != null && share.shareEntrance == ShareUtils.ShareEntrance.gameCollection && isAdded) { + mListViewModel.postShareGameCollection() + } + } + }, 200) + } + + @OnClick(R.id.backIv, R.id.desContainer, R.id.videoDesContainer, R.id.toolbarLightUserContainer, R.id.toolbarUserContainer, + R.id.videoItemUserContainer, R.id.toolbarFollowTv, R.id.videoItemFollowTv, R.id.squareIv, R.id.bottomStarIv, + R.id.bottomStarTv, R.id.bottomLikeIv, R.id.bottomLikeTv, R.id.bottomShareIv, R.id.bottomShareTv, R.id.replyTv, + R.id.bottomCommentIv, R.id.bottomCommentTv) + override fun onClick(v: View) { + when (v.id) { + R.id.backIv -> requireActivity().finish() + + R.id.desContainer, + R.id.videoDesContainer -> { + logEvent("click_game_collect_detail_product") + startActivity(GameCollectionPosterActivity.getIntent(requireContext(), mEntity ?: GamesCollectionDetailEntity())) + } + + R.id.toolbarLightUserContainer, + R.id.toolbarUserContainer, + R.id.videoItemUserContainer -> { + DirectUtils.directToHomeActivity(requireContext(), mEntity?.user?.id, "", "游戏单详情-导航栏") + } + + R.id.toolbarFollowTv, + R.id.videoItemFollowTv -> { + ifLogin("游戏单详情") { + if (mEntity?.me?.isFollower == false) { + logEvent("click_game_collect_detail_follow") + mListViewModel?.followingCommand(mEntity?.user?.id ?: "", true) + } else { + DialogHelper.showDialog( + requireContext(), + "取消关注", + "确定要取消关注 ${mEntity?.user?.name} 吗?", + "确定取消", + "暂不取消", + { + logEvent("click_game_collect_detail_follow_cancel") + mListViewModel?.followingCommand(mEntity?.user?.id ?: "", false) + }, + extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true) + ) + } + } + } + + R.id.squareIv -> { + DirectUtils.directToGameCollectionSquare( + requireContext(), + "游戏单详情", + "", + mGameCollectionTitle, + mGameCollectionId + ) + } + + R.id.replyTv -> { + if (isSelfOnly()) return + debounceActionWithInterval(v.id) { + startCommentActivity() + } + } + + R.id.bottomCommentIv, + R.id.bottomCommentTv -> { + mListRv.post { + mBinding?.appbar?.setExpanded(false) + mLayoutManager.scrollToPositionWithOffset(mListViewModel?.filterPos ?: 0, 0) + } + } + + R.id.bottomStarIv, + R.id.bottomStarTv -> { + if (isSelfOnly()) return + debounceActionWithInterval(v.id) { + ifLogin("游戏单详情") { + if (mEntity?.me?.isFavorite == true) { + logEvent("click_game_collect_detail_favorite_cancel") + } else { + logEvent("click_game_collect_detail_favorite") + } + mListViewModel?.postFavoriteGameCollection() + } + } + } + + R.id.bottomLikeIv, + R.id.bottomLikeTv -> { + if (isSelfOnly()) return + debounceActionWithInterval(v.id) { + ifLogin("游戏单详情") { + mListViewModel?.postVoteGameCollection() + } + } + } + + R.id.bottomShareIv, + R.id.bottomShareTv -> { + if (isSelfOnly()) return + showMoreItemDialog() + } + } + } + + private fun updateFilterView() { + mBinding?.fixedTopFilterView?.run { + getFilterVH()?.binding?.commentHintCountTv?.text = "${provideListViewModel().commentCount}" + + filterLatestTv.setTextColor(getFilterVH()?.binding?.filterLatestTv?.textColors) + filterOldestTv.setTextColor(getFilterVH()?.binding?.filterOldestTv?.textColors) + commentHintTv.text = getFilterVH()?.binding?.commentHintTv?.text + commentHintCountTv.text = getFilterVH()?.binding?.commentHintCountTv?.text + } + } + + private fun getFilterVH(): BaseCommentAdapter.CommentFilterViewHolder? { + return (provideListAdapter() as BaseCommentAdapter).filterVH + } + + private fun isSelfOnly(): Boolean { + val isSelfOnly = mEntity?.display == "self_only" + if (isSelfOnly) toast("游戏单为仅自己可见状态") + return isSelfOnly + } + + private fun showMoreItemDialog() { + if (!lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) return + mEntity?.run { + GameCollectionShareDialog.showMoreDialog( + requireActivity() as AppCompatActivity, + getShareEntity(this) + ) + } + } + + private fun startCommentActivity() { + mEntity?.run { + val intent = CommentActivity.getGameCollectionCommentIntent( + requireContext(), + id, + count?.comment ?: 0 + ) + startActivityForResult(intent, CommentActivity.REQUEST_CODE) + } + } + + private fun getShareEntity(entity: GamesCollectionDetailEntity): NormalShareEntity { + val shareUrl = if (isPublishEnv()) { + Constants.GAME_COLLECTION_SHARE_ADDRESS + } else { + Constants.GAME_COLLECTION_SHARE_ADDRESS_DEV + } + "&id=${entity.id}" + return NormalShareEntity( + id = entity.id, + shareUrl = shareUrl, + shareIcon = entity.cover, + shareTitle = entity.title, + shareSummary = entity.intro, + shareEntrance = ShareUtils.ShareEntrance.gameCollection + ) + } + + fun showUnzipFailureDialog(downloadEntity: DownloadEntity) { + val data = mAdapter?.positionAndPackageMap ?: return + for (gameAndPosition in data) { + if (gameAndPosition.key.contains(downloadEntity.packageName)) { + val targetView = mLayoutManager.findViewByPosition(gameAndPosition.value) + if (targetView != null) { + DialogUtils.showUnzipFailureDialog(requireContext(), downloadEntity) + return + } + } + } + } + + fun logEvent(event: String, shareType: String = "") { + NewLogUtils.logViewOrClickGameCollectionDetail( + event, + mGameCollectionTitle, + mGameCollectionId, + shareType + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/detail/GameCollectionDetailViewModel.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/detail/GameCollectionDetailViewModel.kt new file mode 100644 index 0000000000..706f20ec94 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/detail/GameCollectionDetailViewModel.kt @@ -0,0 +1,340 @@ +package com.gh.gamecenter.gamecollection.detail + +import android.annotation.SuppressLint +import android.app.Application +import android.net.Uri +import android.os.Build +import android.text.TextUtils +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import com.gh.common.constant.Constants +import com.gh.common.util.* +import com.gh.gamecenter.R +import com.gh.gamecenter.baselist.LoadStatus +import com.gh.gamecenter.entity.CommentEntity +import com.gh.gamecenter.entity.GamesCollectionDetailEntity +import com.gh.gamecenter.eventbus.EBUserFollow +import com.gh.gamecenter.manager.UserManager +import com.gh.gamecenter.qa.article.detail.CommentItemData +import com.gh.gamecenter.qa.comment.base.BaseCommentViewModel +import com.gh.gamecenter.retrofit.BiResponse +import com.gh.gamecenter.retrofit.Response +import com.gh.gamecenter.retrofit.RetrofitManager +import com.halo.assistant.HaloApp +import com.lightgame.utils.Utils +import io.reactivex.Single +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import okhttp3.ResponseBody +import org.greenrobot.eventbus.EventBus +import org.json.JSONObject +import retrofit2.HttpException +import tv.danmaku.ijk.media.exo2.ExoSourceManager + +open class GameCollectionDetailViewModel(application: Application, + var gameCollectionId: String) : + BaseCommentViewModel(application, "", "", "", "") { + + var followLiveData = MutableLiveData() + val favoriteLiveData = MutableLiveData() + val likeLiveData = MutableLiveData() + val shareLiveData = MutableLiveData() + var gameCollectionDetail: GamesCollectionDetailEntity? = null + var gameCollectionTitle = "" + var ownerUserId = "" + var filterPos = 0 + var displayTopVideo: Boolean = false + var videoIsMuted = SPUtils.getBoolean(Constants.SP_VIDEO_PLAY_MUTE, true) + + @SuppressLint("CheckResult") + fun getGameCollectionDetail() { + mApi.getGameCollectionDetail(gameCollectionId) + .compose(singleToMain()) + .subscribe(object : BiResponse() { + override fun onSuccess(data: GamesCollectionDetailEntity) { + // 4.4以下设备不显示顶部视频 + displayTopVideo = data.video != null + && Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT + gameCollectionDetail = data + gameCollectionTitle = data.title + commentCount = data.count?.comment ?: 0 + ownerUserId = data.user?.id ?: "" + filterPos = (data.games?.size ?: 1) + 1 + loadResultLiveData.postValue(LoadResult.SUCCESS) + mergeData(mListLiveData.value) + } + + override fun onFailure(exception: Exception) { + super.onFailure(exception) + if (exception is HttpException) { + if (exception.code().toString().startsWith("404")) { + loadResultLiveData.postValue(LoadResult.DELETED) + } else { + loadResultLiveData.postValue(LoadResult.NETWORK_ERROR) + } + } + } + }) + } + + override fun provideDataObservable(page: Int) = null + + override fun provideDataSingle(page: Int): Single> { + val sortType = if (currentSortType == SortType.LATEST) "earliest" else "latest" + return mApi.getGameCollectionComments(gameCollectionId, page, sortType) + } + + override fun mergeResultLiveData() { + mResultLiveData.addSource(mListLiveData) { mergeData(it) } + } + + fun mergeData(list: List?) { + gameCollectionDetail?.run { + val itemDataList = arrayListOf().apply { + if (games?.isEmpty() == true) { + add(CommentItemData(gameEmpty = true)) + } else { + val gameLastIndex = games!!.size - 1 + if (mResultLiveData.value?.get(0)?.game != null + && mResultLiveData.value?.get(gameLastIndex)?.game != null) { + for (i in 0..gameLastIndex) { + add(mResultLiveData.value!![i]) + } + } else { + games?.forEach { + add(CommentItemData(game = it)) + } + } + } + + add(CommentItemData(divider = true)) + add(CommentItemData(filter = true)) + + if (list.isNullOrEmpty() && mLoadStatusLiveData.value == LoadStatus.INIT_EMPTY) { + add(CommentItemData(errorEmpty = true)) + } else if (list.isNullOrEmpty() && mLoadStatusLiveData.value == LoadStatus.INIT_FAILED) { + add(CommentItemData(errorConnection = true)) + } else { + list?.forEach { + add(CommentItemData(commentNormal = it)) + } + add(CommentItemData(footer = true)) + } + } + + mResultLiveData.postValue(itemDataList) + } + } + + fun followingCommand(userId: String, isFollow: Boolean) { + val observable = if (isFollow) { + RetrofitManager.getInstance(getApplication()).api.postFollowing(userId) + } else { + RetrofitManager.getInstance(getApplication()).api.deleteFollowing(userId) + } + observable + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : Response() { + override fun onResponse(response: ResponseBody?) { + super.onResponse(response) + if (isFollow) { + Utils.toast(getApplication(), R.string.concern_success) + } else { + Utils.toast(getApplication(), R.string.concern_cancel) + } + gameCollectionDetail?.me?.isFollower = + gameCollectionDetail?.me?.isFollower != true + followLiveData.postValue(isFollow) + EventBus.getDefault().post(EBUserFollow(userId, isFollow)) + } + + override fun onFailure(e: HttpException?) { + super.onFailure(e) + Utils.toast(getApplication(), R.string.loading_failed_hint) + } + }) + } + + @SuppressLint("CheckResult") + fun postFavoriteGameCollection() { + if (gameCollectionDetail == null) return + val single = if (gameCollectionDetail?.me?.isFavorite == true) { + mApi.deleteFavoriteGameCollection(UserManager.getInstance().userId, gameCollectionId) + } else { + mApi.favoriteGameCollection(UserManager.getInstance().userId, gameCollectionId) + } + single.compose(singleToMain()) + .subscribe(object : BiResponse() { + override fun onSuccess(data: ResponseBody) { + if (gameCollectionDetail?.me?.isFavorite == true) { + gameCollectionDetail!!.count!!.favorite-- + ToastUtils.showToast("取消收藏") + } else { + gameCollectionDetail!!.count!!.favorite++ + ToastUtils.showToast("收藏成功") + } + gameCollectionDetail?.me?.isFavorite = + gameCollectionDetail?.me?.isFavorite != true + favoriteLiveData.postValue(true) + } + + override fun onFailure(exception: Exception) { + super.onFailure(exception) + + if (exception is HttpException) { + ErrorHelper.handleError( + getApplication(), + exception.response()?.errorBody()?.string() + ) + } + } + }) + } + + @SuppressLint("CheckResult") + fun postVoteGameCollection() { + if (gameCollectionDetail == null) return + val single = if (gameCollectionDetail?.me?.vote == true) { + mApi.unVoteGameCollection(gameCollectionId) + } else { + mApi.voteGameCollection(gameCollectionId) + } + single.compose(singleToMain()) + .subscribe(object : BiResponse() { + override fun onSuccess(data: ResponseBody) { + if (gameCollectionDetail?.me?.vote == true) { + gameCollectionDetail!!.count!!.vote-- + ToastUtils.showToast("取消点赞") + } else { + gameCollectionDetail!!.count!!.vote++ + ToastUtils.showToast("点赞成功") + } + gameCollectionDetail?.me?.vote = + gameCollectionDetail?.me?.vote != true + likeLiveData.postValue(true) + } + + override fun onFailure(exception: Exception) { + super.onFailure(exception) + + if (exception is HttpException) { + ErrorHelper.handleError( + getApplication(), + exception.response()?.errorBody()?.string() + ) + } + } + }) + } + @SuppressLint("CheckResult") + fun postShareGameCollection() { + if (gameCollectionDetail == null) return + mApi.shareGameCollection(gameCollectionId) + .compose(singleToMain()) + .subscribe(object : BiResponse() { + override fun onSuccess(data: ResponseBody) { + gameCollectionDetail!!.count!!.share++ + shareLiveData.postValue(true) + } + + override fun onFailure(exception: Exception) { + super.onFailure(exception) + + if (exception is HttpException) { + ErrorHelper.handleError( + getApplication(), + exception.response()?.errorBody()?.string() + ) + } + } + }) + } + + + @SuppressLint("CheckResult") + fun postVoteGameCollectionComment(comment: CommentEntity, successCallback: (() -> Unit)) { + mApi.voteGameCollectionComment(gameCollectionId, comment.id) + .compose(singleToMain()) + .subscribe(object : BiResponse() { + override fun onSuccess(data: ResponseBody) { + if (comment.me?.isCommentVoted == true) { + comment.vote-- + ToastUtils.showToast("取消点赞") + } else { + comment.vote++ + ToastUtils.showToast("点赞成功") + } + comment.me?.isCommentVoted = !(comment.me?.isCommentVoted ?: false) + successCallback.invoke() + } + + override fun onFailure(exception: Exception) { + super.onFailure(exception) + + if (exception is HttpException) { + ErrorHelper.handleError( + getApplication(), + exception.response()?.errorBody()?.string() + ) + } + } + }) + } + + @SuppressLint("CheckResult") + override fun deleteComment(entity: CommentEntity, callback: () -> Unit) { + mApi.deleteGameCollectionComment(gameCollectionId, entity.id) + .compose(observableToMain()) + .subscribe(object : Response() { + override fun onResponse(response: ResponseBody?) { + hideCommentSuccess() + callback.invoke() + } + + override fun onFailure(e: HttpException?) { + super.onFailure(e) + e?.let { httpException -> + if (httpException.code() == 403) { + val string = e.response().errorBody()?.string() ?: "" + val errorJson = JSONObject(string) + val errorCode = errorJson.getInt("code") + if (errorCode == 403059) { + Utils.toast(getApplication(), "权限错误,请刷新后重试") + return + } else { + Utils.toast(getApplication(), e.message()) + } + } + } + } + }) + } + + fun getStarText(): String { + return if (gameCollectionDetail?.count?.favorite == 0) "收藏" else "${gameCollectionDetail?.count?.favorite}" + } + + fun getShareText(): String { + return if (gameCollectionDetail?.count?.share == 0) "分享" else "${gameCollectionDetail?.count?.share}" + } + + fun isTopVideoPartlyCached(topVideoUrl: String): Boolean { + val cache = ExoSourceManager.getCacheSingleInstance(HaloApp.getInstance().application, null) + val key = Uri.parse(topVideoUrl).toString() + return if (!TextUtils.isEmpty(key)) { + val cachedSpans = cache.getCachedSpans(key) + cachedSpans.size != 0 + } else { + false + } + } + + class Factory(private val gameCollectionId: String) : ViewModelProvider.NewInstanceFactory() { + + override fun create(modelClass: Class): T { + return GameCollectionDetailViewModel(HaloApp.getInstance().application, gameCollectionId) as T + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/detail/GameCollectionPosterActivity.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/detail/GameCollectionPosterActivity.kt new file mode 100644 index 0000000000..491c030e9c --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/detail/GameCollectionPosterActivity.kt @@ -0,0 +1,28 @@ +package com.gh.gamecenter.gamecollection.detail + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import com.gh.common.util.DisplayUtils +import com.gh.common.util.EntranceUtils +import com.gh.gamecenter.NormalActivity +import com.gh.gamecenter.R +import com.gh.gamecenter.entity.GamesCollectionDetailEntity + +class GameCollectionPosterActivity : NormalActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + hideToolbar(true) + DisplayUtils.setStatusBarColor(this, R.color.transparent, false) + } + + companion object { + @JvmStatic + fun getIntent(context: Context, entity: GamesCollectionDetailEntity): Intent { + val bundle = Bundle() + bundle.putParcelable(EntranceUtils.KEY_DATA, entity) + return getTargetIntent(context, GameCollectionPosterActivity::class.java, GameCollectionPosterFragment::class.java, bundle) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/detail/GameCollectionPosterFragment.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/detail/GameCollectionPosterFragment.kt new file mode 100644 index 0000000000..ca56229379 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/detail/GameCollectionPosterFragment.kt @@ -0,0 +1,119 @@ +package com.gh.gamecenter.gamecollection.detail + +import android.graphics.Bitmap +import android.os.Build +import android.os.Bundle +import android.view.View +import androidx.annotation.RequiresApi +import butterknife.OnClick +import com.gh.common.runOnUiThread +import com.gh.common.util.* +import com.gh.gamecenter.R +import com.gh.gamecenter.databinding.FragmentGameCollectionPosterBinding +import com.gh.gamecenter.databinding.LayoutGameCollectionTagBinding +import com.gh.gamecenter.entity.GamesCollectionDetailEntity +import com.gh.gamecenter.normal.NormalFragment + +class GameCollectionPosterFragment : NormalFragment() { + + private var mBinding: FragmentGameCollectionPosterBinding? = null + private var mViewModel: GameCollectionPosterViewModel? = null + private var mEntity = GamesCollectionDetailEntity() + + override fun getLayoutId() = 0 + + override fun getInflatedLayout() = + FragmentGameCollectionPosterBinding.inflate(layoutInflater).apply { mBinding = this }.root + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + mViewModel = viewModelProvider() + mEntity = arguments?.getParcelable(EntranceUtils.KEY_DATA) ?: GamesCollectionDetailEntity() + + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + mViewModel?.followLiveData?.observeNonNull(viewLifecycleOwner) { + setFollowStatus(it) + } + + mBinding?.apply { + mEntity.apply { + entity = this + executePendingBindings() + + ImageUtils.getBitmap(cover, object : BiCallback { + @RequiresApi(Build.VERSION_CODES.JELLY_BEAN_MR1) + override fun onFirst(first: Bitmap) { + val blurBitmap = BitmapUtils.getBlurBitmap(requireContext(), first, 16) + runOnUiThread { + posterBg.setImageBitmap(blurBitmap) + } + } + + override fun onSecond(second: Boolean) {} + }) + + userIcon.display(user?.border, user?.icon, user?.auth?.icon) + + tags?.forEachIndexed { index, tag -> + tagContainer.addView(getTagView(tag.name, index == tags!!.size - 1)) + } + + setFollowStatus(me?.isFollower == true) + } + } + } + + private fun setFollowStatus(isFollow: Boolean) { + mBinding?.followTv?.run { + setBackgroundResource(if (isFollow) R.drawable.button_round_white_alpha_10 else R.drawable.button_round_white_alpha_20) + setTextColor(if (isFollow) R.color.white_alpha_60.toColor() else R.color.white.toColor()) + text = if (isFollow) "已关注" else "关注" + } + } + + private fun getTagView(content: String, isLast: Boolean): View { + return LayoutGameCollectionTagBinding.inflate(layoutInflater).apply { + root.setPadding(0, 12F.dip2px(), 0, 0) + divider.goneIf(isLast) + contentTv.text = content + }.root + } + + @OnClick(R.id.backIv, R.id.userIcon, R.id.userName, R.id.followTv) + override fun onClick(view: View) { + when (view.id) { + R.id.backIv -> requireActivity().finish() + + R.id.userIcon -> { + DirectUtils.directToHomeActivity(requireContext(), mEntity.user?.id, "", "游戏单详情-封面页") + } + + R.id.userName -> { + DirectUtils.directToHomeActivity(requireContext(), mEntity.user?.id, "", "游戏单详情-封面页") + } + + R.id.followTv -> { + ifLogin("游戏单详情-封面页") { + if (mBinding?.followTv?.text == "关注") { + mViewModel?.followingCommand(mEntity.user?.id ?: "", true) + } else { + DialogHelper.showDialog( + requireContext(), + "取消关注", + "确定要取消关注 ${mEntity.user?.name} 吗?", + "确定取消", + "暂不取消", + { mViewModel?.followingCommand(mEntity.user?.id ?: "", false) }, + extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true) + ) + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/detail/GameCollectionPosterViewModel.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/detail/GameCollectionPosterViewModel.kt new file mode 100644 index 0000000000..bc37e692d1 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/detail/GameCollectionPosterViewModel.kt @@ -0,0 +1,48 @@ +package com.gh.gamecenter.gamecollection.detail + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.MutableLiveData +import com.gh.gamecenter.R +import com.gh.gamecenter.eventbus.EBUserFollow +import com.gh.gamecenter.retrofit.Response +import com.gh.gamecenter.retrofit.RetrofitManager +import com.lightgame.utils.Utils +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import okhttp3.ResponseBody +import org.greenrobot.eventbus.EventBus +import retrofit2.HttpException + +class GameCollectionPosterViewModel(application: Application) : AndroidViewModel(application) { + + var followLiveData = MutableLiveData() + + fun followingCommand(userId: String, isFollow: Boolean) { + val observable = if (isFollow) { + RetrofitManager.getInstance(getApplication()).api.postFollowing(userId) + } else { + RetrofitManager.getInstance(getApplication()).api.deleteFollowing(userId) + } + observable + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : Response() { + override fun onResponse(response: ResponseBody?) { + super.onResponse(response) + if (isFollow) { + Utils.toast(getApplication(), R.string.concern_success) + } else { + Utils.toast(getApplication(), R.string.concern_cancel) + } + followLiveData.postValue(isFollow) + EventBus.getDefault().post(EBUserFollow(userId, isFollow)) + } + + override fun onFailure(e: HttpException?) { + super.onFailure(e) + Utils.toast(getApplication(), R.string.loading_failed_hint) + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/detail/GameCollectionShareDialog.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/detail/GameCollectionShareDialog.kt new file mode 100644 index 0000000000..f89eeca996 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/detail/GameCollectionShareDialog.kt @@ -0,0 +1,131 @@ +package com.gh.gamecenter.gamecollection.detail + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.app.AppCompatActivity +import com.gh.common.dialog.BaseDraggableDialogFragment +import com.gh.common.util.* +import com.gh.gamecenter.databinding.DialogGameCollectionShareBinding +import com.gh.gamecenter.entity.NormalShareEntity +import com.gh.gamecenter.eventbus.EBShare +import org.greenrobot.eventbus.EventBus + +class GameCollectionShareDialog : BaseDraggableDialogFragment() { + + private lateinit var mBinding: DialogGameCollectionShareBinding + private var mShareEntity: NormalShareEntity? = null + private var mShareUtils: ShareUtils? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + mShareEntity = requireArguments().getParcelable(KEY_SHARE) + mShareUtils = getShareUtils() + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + return DialogGameCollectionShareBinding.inflate(inflater, container, false) + .apply { mBinding = this }.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + mBinding.run { + wechatItem.setOnClickListener { + logEvent("微信") + mShareUtils?.wechatShare() + } + + wechatMomentsItem.setOnClickListener { + logEvent("朋友圈") + mShareUtils?.wechatMomentsShare() + } + + qqItem.setOnClickListener { + logEvent("QQ好友") + mShareUtils?.qqShare() + } + + qqZoneItem.setOnClickListener { + logEvent("QQ空间") + mShareUtils?.qZoneShare() + } + + weiboItem.setOnClickListener { + logEvent("新浪微博") + mShareUtils?.sinaWeiboShare() + } + + smsItem.setOnClickListener { + logEvent("短信") + EventBus.getDefault().post(EBShare(ShareUtils.shareEntrance)) + mShareUtils?.shortMessageShare() + } + + copyItem.setOnClickListener { + logEvent("复制链接") + EventBus.getDefault().post(EBShare(ShareUtils.shareEntrance)) + mShareUtils?.copyLink(mShareUtils?.shareUrl ?: "") + } + + cancelTv.setOnClickListener { + dismissAllowingStateLoss() + } + } + } + + private fun getShareUtils(): ShareUtils { + val shareUtils = ShareUtils.getInstance(requireContext()) + mShareEntity?.run { + shareUtils.shareParamsDetail( + requireActivity(), + shareUrl, + shareIcon, + shareTitle, + shareSummary, + shareEntrance, + id, + null + ) + } + return shareUtils + } + + private fun logEvent(shareType: String) { + NewLogUtils.logViewOrClickGameCollectionDetail( + "click_game_collect_detail_share", + mShareEntity?.shareTitle ?: "", + mShareEntity?.id ?: "", + shareType + ) + } + + override fun getRootView(): View = mBinding.root + override fun getDragCloseView(): View = mBinding.dragClose + + companion object { + const val KEY_SHARE = "share" + const val REQUEST_CODE = 1105 + + @JvmStatic + fun showMoreDialog( + activity: AppCompatActivity, + share: NormalShareEntity, + ) { + GameCollectionShareDialog().apply { + arguments = Bundle().apply { + putParcelable(KEY_SHARE, share) + } + }.show( + activity.supportFragmentManager, + GameCollectionShareDialog::class.java.name + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/detail/GameCollectionVideoView.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/detail/GameCollectionVideoView.kt new file mode 100644 index 0000000000..cbe674df92 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/detail/GameCollectionVideoView.kt @@ -0,0 +1,430 @@ +package com.gh.gamecenter.gamecollection.detail + +import android.content.Context +import android.util.AttributeSet +import android.view.GestureDetector +import android.view.MotionEvent +import android.view.Surface +import android.view.View +import android.widget.ImageView +import android.widget.SeekBar +import androidx.core.content.ContextCompat +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import com.gh.common.observer.MuteCallback +import com.gh.common.observer.VolumeObserver +import com.gh.common.runOnIoThread +import com.gh.common.util.* +import com.gh.gamecenter.R +import com.gh.gamecenter.entity.GamesCollectionDetailEntity +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.video.StandardGSYVideoPlayer +import com.shuyu.gsyvideoplayer.video.base.GSYVideoView +import com.shuyu.gsyvideoplayer.video.base.GSYVideoViewBridge +import com.squareup.picasso.Picasso +import io.reactivex.disposables.Disposable +import kotlinx.android.synthetic.main.layout_game_detail_video_portrait.view.* +import kotlinx.android.synthetic.main.piece_video_control.view.* +import kotlinx.android.synthetic.main.piece_video_replay.view.* +import java.text.DecimalFormat +import java.util.* + +class GameCollectionVideoView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : StandardGSYVideoPlayer(context, attrs) { + + private var mMuteCallback: MuteCallback + private var mVolumeObserver: VolumeObserver + + var gameName = "" + var gameCollectionId = "" + var gameCollectionTitle = "" + var video: GamesCollectionDetailEntity.Video? = null + var viewModel: GameCollectionDetailViewModel? = null + var uuid = UUID.randomUUID().toString() + private var mMuteDisposable: Disposable? = null + private var mIsAutoPlay = false + private var mIsManualStop = false + + init { + post { + gestureDetector = GestureDetector(getContext().applicationContext, object : GestureDetector.SimpleOnGestureListener() { + override fun onSingleTapConfirmed(e: MotionEvent): Boolean { + if (!mChangePosition && !mChangeVolume && !mBrightness) { + onClickUiToggle(e) + } + return super.onSingleTapConfirmed(e) + } + }) + + if (mIfCurrentIsFullscreen) { + showBackBtn() + } else { + hideBackBtn() + } + + volume.setOnClickListener { toggleMute() } + } + + mMuteCallback = object : MuteCallback { + override fun onMute(isMute: Boolean) { + if (isMute) { + mute() + } else { + unMute() + } + } + } + + mVolumeObserver = VolumeObserver(mMuteCallback) + + setBackFromFullScreenListener { + clearFullscreenLayout() + } + + errorBtn?.setOnClickListener { + debounceActionWithInterval(errorBtn.id, 1000) { + if (!com.shuyu.gsyvideoplayer.utils.NetworkUtils.isAvailable(mContext)) { + Utils.toast(context, "网络异常,请检查手机网络状态") + setViewShowState(mStartButton, View.INVISIBLE) + errorContainer.visibility = View.VISIBLE + return@debounceActionWithInterval + } + startPlayLogic(false) + } + } + } + + //这个必须配置最上面的构造才能生效 + override fun getLayoutId(): Int { + return R.layout.layout_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 startPlayLogic(isAutoPlay: Boolean) { + mIsAutoPlay = isAutoPlay + violenceUpdateMuteStatus() + logVideoEvent("video_game_collect_detail_play", if (isAutoPlay) "自动播放" else "手动播放") + if (isAutoPlay) { + val seekTime = ScrollCalculatorHelper.getPlaySchedule(MD5Utils.getContentMD5(video?.url)) + seekOnStart = seekTime + } + startPlayLogic() + } + + fun violenceUpdateMuteStatus() { + if (mMuteDisposable != null) { + mMuteDisposable?.dispose() + mMuteDisposable = null + } + mMuteDisposable = rxTimer(25) { + if (it >= 400) { + mMuteDisposable?.dispose() + mMuteDisposable = null + } + updateMuteStatus() + } + } + + fun disposableTimer() { + if (mMuteDisposable != null && !mMuteDisposable!!.isDisposed) { + mMuteDisposable?.dispose() + mMuteDisposable = null + } + } + + private fun toggleMute() { + if (viewModel?.videoIsMuted == true) { + unMute(true) + } else { + mute(true) + } + } + + fun updateMuteStatus() { + if (viewModel?.videoIsMuted == true) { + mute() + } else { + unMute() + } + } + + private fun mute(isManual: Boolean = false) { + viewModel?.videoIsMuted = true + volume.setImageResource(R.drawable.ic_game_detail_volume_off) + CustomManager.getCustomManager(getKey()).isNeedMute = true + if (isManual) { + Utils.toast(context, "当前处于静音状态") + logVideoEvent("video_game_collect_detail_mute") + } + } + + private fun unMute(isManual: Boolean = false) { + viewModel?.videoIsMuted = false + volume.setImageResource(R.drawable.ic_game_detail_volume_on) + CustomManager.getCustomManager(getKey()).isNeedMute = false + if (isManual) { + logVideoEvent("video_game_collect_detail_mute_cancel") + } + } + + // 重载以减少横竖屏切换的时间 + override fun checkoutState() { + removeCallbacks(mCheckoutTask) + postDelayed(mCheckoutTask, 300) + } + + override fun clearFullscreenLayout() { + super.clearFullscreenLayout() + updateMuteStatus() + hideBackBtn() + } + + fun updateThumb(url: String) { + Picasso.with(context).load(url).fit().into(findViewById(R.id.thumbImage)) + } + + override fun touchDoubleUp(e: MotionEvent?) { + // we do not need double click to play or pause + } + + // 不需要弹弹窗,直接播放 + override fun showWifiDialog() { + startPlayLogic(false) + //val trafficVideo = PreferenceManager.getDefaultSharedPreferences(context).getBoolean(SettingsFragment.TRAFFIC_VIDEO_SP_KEY, false) + //if (trafficVideo) { + // 不延迟的话 isCacheFile 可能直接返回 false + postDelayed({ + // 这个库的 NetworkUtils.isWifiConnected 可能会触发空指针,这里换为我们自己的 + if (!com.shuyu.gsyvideoplayer.utils.NetworkUtils.isAvailable(mContext) && !gsyVideoManager.isCacheFile) { + Utils.toast(context, "网络异常,请检查手机网络状态") + } else if (!NetworkUtils.isWifiConnected(mContext) && !gsyVideoManager.isCacheFile) { + Utils.toast(context, "当前为非Wi-Fi环境,请注意流量消耗") + } + }, 100) + // } + } + + override fun getGSYVideoManager(): GSYVideoViewBridge { + CustomManager.getCustomManager(getKey()).initContext(context.applicationContext) + return CustomManager.getCustomManager(getKey()) + } + + fun getKey(): String { + return uuid + } + + override fun onAutoCompletion() { + // 这个方法在内核被释放时也会被回调,所以可以用来统计播放量和播放时长 + val playedTime = (gsyVideoManager.currentPosition / 1000).toInt() + + //播放完成后判断是否已缓冲完毕,没有完成显示播放错误状态 + if (mBufferPoint != 0 && mBufferPoint != 100 && isShown) { + gsyVideoManager.releaseMediaPlayer() + changeUiToPreparingShow() + postDelayed({ + if (!com.shuyu.gsyvideoplayer.utils.NetworkUtils.isAvailable(mContext)) { + Utils.toast(context, "网络错误,视频播放失败") + changeUiToError() + } + }, 10 * 1000) + } + logVideoEvent("video_game_collect_detail_stop", stopAction = "播放完毕") + super.onAutoCompletion() + } + + override fun onStopTrackingTouch(seekBar: SeekBar?) { + super.onStopTrackingTouch(seekBar) + logVideoEvent("video_game_collect_detail_progress_drag") + } + + override fun isShowNetConfirm(): Boolean { + return (!mOriginUrl.startsWith("file") && !mOriginUrl.startsWith("android.resource") && !CommonUtil.isWifiConnected(context) + && mNeedShowWifiTip) + } + + override fun updateStartImage() { + 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) + } + } + } + + override fun setStateAndUi(state: Int) { + super.setStateAndUi(state) + + if (state == CURRENT_STATE_AUTO_COMPLETE) { + hideAllWidget() + replayContainer.visibility = View.VISIBLE + mTopContainer.visibility = View.VISIBLE + replayIv.setOnClickListener { + startButton.performClick() + violenceUpdateMuteStatus() + logVideoEvent("video_game_collect_detail_play", "再次播放") + } + updateThumb(video!!.poster) + } else { + replayContainer.visibility = View.GONE + } + } + + private fun showBackBtn() { + mTopContainer.background = ContextCompat.getDrawable(context, R.drawable.video_title_bg) + back.visibility = View.VISIBLE + } + + private fun hideBackBtn() { + mTopContainer?.setBackgroundResource(0) + back.visibility = View.GONE + } + + override fun getEnlargeImageRes(): Int { + return R.drawable.ic_game_detail_enter_full_screen + } + + override fun getShrinkImageRes(): Int { + 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) { + super.onSurfaceUpdated(surface) + if (mThumbImageViewLayout != null && mThumbImageViewLayout.visibility == View.VISIBLE) { + mThumbImageViewLayout.visibility = View.INVISIBLE + } + } + + override fun setViewShowState(view: View?, visibility: Int) { + if (view === mThumbImageViewLayout && visibility != View.VISIBLE) { + return + } + super.setViewShowState(view, visibility) + } + + /********************************各类UI的状态显示*********************************************/ + + override fun changeUiToNormal() { + super.changeUiToNormal() + errorContainer.visibility = View.GONE + } + + override fun changeUiToPreparingShow() { + super.changeUiToPreparingShow() + errorContainer.visibility = View.GONE + } + + override fun changeUiToPlayingShow() { + super.changeUiToPlayingShow() + errorContainer.visibility = View.GONE + } + + override fun changeUiToPauseShow() { + super.changeUiToPauseShow() + errorContainer.visibility = View.GONE + } + + override fun changeUiToCompleteShow() { + super.changeUiToCompleteShow() + errorContainer.visibility = View.GONE + } + + override fun changeUiToError() { + super.changeUiToError() + setViewShowState(mStartButton, View.INVISIBLE) + errorContainer.visibility = View.VISIBLE + } + + override fun netWorkErrorLogic() { + super.netWorkErrorLogic() + Utils.toast(context, "网络错误,视频播放失败") + setViewShowState(mStartButton, View.INVISIBLE) + errorContainer.visibility = View.VISIBLE + } + + //监控播放错误 + override fun onError(what: Int, extra: Int) { + super.onError(what, extra) + Utils.toast(context, "网络错误,视频播放失败") + setViewShowState(mStartButton, View.INVISIBLE) + errorContainer.visibility = View.VISIBLE + } + + override fun releaseVideos() { + CustomManager.releaseAllVideos(getKey()) + } + + override fun onVideoPause() { + super.onVideoPause() + logVideoEvent("video_game_collect_detail_stop", stopAction = if (mIsManualStop) "手动停止" else "自动停止") + mIsManualStop = false + } + + override fun onVideoResume() { + super.onVideoResume() + logVideoEvent("video_game_collect_detail_play", "继续播放") + } + + override fun onClick(v: View) { + when (v.id) { + R.id.start -> { + when (currentState) { + GSYVideoView.CURRENT_STATE_PLAYING -> mIsManualStop = true + + GSYVideoView.CURRENT_STATE_PAUSE -> logVideoEvent("video_game_collect_detail_play", "继续播放") + } + super.onClick(v) + } + else -> super.onClick(v) + } + } + + fun getCurrentPosition(): Long { + return mCurrentPosition + } + + fun logVideoEvent(event: String, playAction: String = "", stopAction: String = "") { + if (video == null || video?.url.isNullOrEmpty()) return + val videoPlayTs = currentPositionWhenPlaying / 1000 + val videoTotalTime = duration / 1000 + val progress = if (videoTotalTime != 0) videoPlayTs.toFloat() / videoTotalTime.toFloat() * 100 else 0f + //https://exoplayer.dev/hello-world.html#a-note-on-threading + runOnIoThread { + NewLogUtils.logGameCollectionVideoEvent( + event, + gameCollectionTitle, + gameCollectionId, + DecimalFormat("#.00").format(progress).toDouble(), + videoPlayTs, + playAction, + stopAction + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/detail/conversation/GameCollectionCommentConversationAdapter.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/detail/conversation/GameCollectionCommentConversationAdapter.kt new file mode 100644 index 0000000000..f766c23e31 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/detail/conversation/GameCollectionCommentConversationAdapter.kt @@ -0,0 +1,93 @@ +package com.gh.gamecenter.gamecollection.detail.conversation + +import android.content.Context +import android.view.View +import android.view.ViewGroup +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.constraintlayout.widget.ConstraintSet +import androidx.databinding.DataBindingUtil +import androidx.recyclerview.widget.RecyclerView +import com.gh.common.util.* +import com.gh.gamecenter.R +import com.gh.gamecenter.databinding.ItemArticleDetailCommentBinding +import com.gh.gamecenter.entity.CommentEntity +import com.gh.gamecenter.gamecollection.detail.GameCollectionDetailAdapter +import com.gh.gamecenter.gamecollection.detail.GameCollectionDetailViewModel + +class GameCollectionCommentConversationAdapter( + context: Context, + mViewModel: GameCollectionDetailViewModel, + type: AdapterType, + mEntrance: String, + commentClosure: ((CommentEntity) -> Unit)? = null +) : GameCollectionDetailAdapter(context, type, mEntrance, mViewModel, commentClosure) { + + var topCommentVH: TopCommentItemViewHolder? = null + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return when (viewType) { + ITEM_COMMENT_TOP -> { + val binding: ItemArticleDetailCommentBinding = + DataBindingUtil.inflate(mLayoutInflater, R.layout.item_article_detail_comment, parent, false) + TopCommentItemViewHolder(binding).apply { topCommentVH = this } + } + + else -> super.onCreateViewHolder(parent, viewType) + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is TopCommentItemViewHolder -> { + holder.bindView(mEntityList[position].commentTop!!) + } + + else -> super.onBindViewHolder(holder, position) + } + } + + inner class TopCommentItemViewHolder(var binding: ItemArticleDetailCommentBinding) : RecyclerView.ViewHolder(binding.root) { + + fun bindView(comment: CommentEntity) { + ConstraintSet().apply { + clone(binding.contentTv.parent as ConstraintLayout) + clear(binding.contentTv.id, ConstraintSet.START) + connect(binding.contentTv.id, ConstraintSet.START, binding.userIconIv.id, ConstraintSet.START) + + clear(binding.commentPictureRv.id, ConstraintSet.START) + connect(binding.commentPictureRv.id, ConstraintSet.START, binding.userIconIv.id, ConstraintSet.START) + + clear(binding.floorHintTv.id, ConstraintSet.START) + connect(binding.floorHintTv.id, ConstraintSet.START, binding.userIconIv.id, ConstraintSet.START) + + applyTo(binding.contentTv.parent as ConstraintLayout) + } + (binding.contentTv.layoutParams as ConstraintLayout.LayoutParams).apply { + topMargin = 16F.dip2px() + leftMargin = 9F.dip2px() + binding.contentTv.layoutParams = this + } + + (binding.commentPictureRv.layoutParams as ConstraintLayout.LayoutParams).apply { + leftMargin = 9F.dip2px() + binding.commentPictureRv.layoutParams = this + } + + (binding.floorHintTv.layoutParams as ConstraintLayout.LayoutParams).apply { + leftMargin = 9F.dip2px() + binding.floorHintTv.layoutParams = this + } + + binding.comment = comment + binding.moreIv.visibility = View.GONE + binding.divider.visibility = View.VISIBLE + binding.commentCountTv.visibility = View.GONE + binding.floorHintTv.text = if (comment.floor != 0) "${comment.floor}楼" else "" + binding.contentTv.text = comment.content + binding.contentTv.maxLines = Int.MAX_VALUE + + bindComment(binding, comment) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/detail/conversation/GameCollectionCommentConversationFragment.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/detail/conversation/GameCollectionCommentConversationFragment.kt new file mode 100644 index 0000000000..e868027ac6 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/detail/conversation/GameCollectionCommentConversationFragment.kt @@ -0,0 +1,171 @@ +package com.gh.gamecenter.gamecollection.detail.conversation + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import com.ethanhua.skeleton.Skeleton +import com.gh.common.AppExecutor +import com.gh.common.syncpage.SyncDataEntity +import com.gh.common.syncpage.SyncFieldConstants +import com.gh.common.syncpage.SyncPageRepository +import com.gh.common.util.* +import com.gh.gamecenter.R +import com.gh.gamecenter.baselist.ListAdapter +import com.gh.gamecenter.baselist.LoadType +import com.gh.gamecenter.databinding.FragmentArticleDetailCommentBinding +import com.gh.gamecenter.entity.CommentEntity +import com.gh.gamecenter.manager.UserManager +import com.gh.gamecenter.qa.comment.base.BaseCommentAdapter +import com.gh.gamecenter.qa.comment.base.BaseCommentFragment +import com.gh.gamecenter.qa.comment.base.BaseCommentViewModel +import com.gh.gamecenter.qa.article.detail.CommentItemData +import com.gh.gamecenter.qa.comment.CommentActivity +import com.halo.assistant.HaloApp + +class GameCollectionCommentConversationFragment : BaseCommentFragment() { + private lateinit var mViewModel: GameCollectionCommentConversationViewModel + private lateinit var mBinding: FragmentArticleDetailCommentBinding + private var mAdapterCommunity: GameCollectionCommentConversationAdapter? = null + + override fun getLayoutId() = R.layout.fragment_article_detail_comment + + override fun onCreate(savedInstanceState: Bundle?) { + mViewModel = provideListViewModel() + super.onCreate(savedInstanceState) + mViewModel.getComment() + mViewModel.positionInOriginList = arguments?.getInt(EntranceUtils.KEY_POSITION) + ?: -1 + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + initView() + initObserver() + } + + override fun getInflatedLayout(): View { + return FragmentArticleDetailCommentBinding.inflate(LayoutInflater.from(requireContext()), null, false).apply { mBinding = this }.root + } + + override fun provideListAdapter(): ListAdapter<*> { + return mAdapterCommunity + ?: GameCollectionCommentConversationAdapter(requireContext(), mViewModel, BaseCommentAdapter.AdapterType.SUB_COMMENT, mEntrance) { + if (it.user.id == UserManager.getInstance().userId) { + toast("不能回复自己") + } else { + startCommentActivity(it) + } + }.apply { + mAdapterCommunity = this + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + val commentCount = data?.getIntExtra(CommentActivity.COMMENT_COUNT, 0) + debounceActionWithInterval(Activity.RESULT_OK) { + if (commentCount != null && commentCount != 0) { + mViewModel.commentDetail?.reply = commentCount + mViewModel.commentCount = commentCount + mViewModel.load(LoadType.REFRESH) + mBinding.toolbarContainer.commentDialogCountTv.text = "${mViewModel.commentDetail?.reply}条回复" + SyncPageRepository.postSyncData(SyncDataEntity(mViewModel.commentId, SyncFieldConstants.ARTICLE_COMMENT_REPLY_COUNT, commentCount)) + } + } + } + + private fun initView() { + mSkeletonScreen = Skeleton.bind(skeletonView).shimmer(false).load(R.layout.fragment_article_detail_comment_skeleton).show() + + mBinding.inputContainer.bottomCommentIv.visibility = View.GONE + mBinding.inputContainer.bottomCommentTv.visibility = View.GONE + mBinding.inputContainer.bottomLikeIv.visibility = View.GONE + mBinding.inputContainer.bottomLikeTv.visibility = View.GONE + mBinding.inputContainer.bottomStarIv.visibility = View.GONE + mBinding.inputContainer.bottomStarTv.visibility = View.GONE + + mBinding.inputContainer.replyTv.setRoundedColorBackground(R.color.text_F0F0F0, 19F) + mBinding.inputContainer.replyTv.setDebouncedClickListener { + mViewModel.commentDetail?.let { startCommentActivity(it) } + } + mBinding.toolbarContainer.commentCloseIv.setOnClickListener { requireActivity().finish() } + } + + @SuppressLint("SetTextI18n") + private fun initObserver() { + mViewModel.loadResultLiveData.observeNonNull(this) { + when (it) { + BaseCommentViewModel.LoadResult.SUCCESS -> { + mReuseNoConn?.visibility = View.GONE + mListLoading?.visibility = View.GONE + + val showKeyboard = arguments?.getBoolean(EntranceUtils.KEY_SHOW_KEYBOARD_IF_NEEDED) + ?: false + if (showKeyboard) { + mBinding.inputContainer.replyTv.performClick() + } + + mBinding.inputContainer.replyTv.text = "回复:${mViewModel.commentDetail?.user?.name}" + mBinding.toolbarContainer.commentDialogCountTv.text = "${mViewModel.commentDetail?.reply}条回复" + AppExecutor.uiExecutor.executeWithDelay(Runnable { + if ((mAdapterCommunity?.entityList?.size ?: 0) > 2) { + mListRv.smoothScrollToPosition(1) + } + }, 100) + } + else -> { + if (it == BaseCommentViewModel.LoadResult.DELETED) { + mReuseNoConn?.visibility = View.GONE + mReuseNoData?.visibility = View.VISIBLE + toast(R.string.content_delete_toast) + } else { + mReuseNoConn?.visibility = View.VISIBLE + mReuseNoData?.visibility = View.GONE + mReuseNoConn?.setOnClickListener { + mViewModel.getComment() + mReuseNoConn?.visibility = View.GONE + showSkeleton(true) + } + } + + mListLoading?.visibility = View.GONE + mBinding.inputContainer.bottomContainer.visibility = View.GONE + mBinding.bottomShadowView.visibility = View.GONE + showSkeleton(false) + } + } + } + } + + override fun provideListViewModel(): GameCollectionCommentConversationViewModel { + return viewModelProvider( + GameCollectionCommentConversationViewModel.Factory( + HaloApp.getInstance().application, + arguments?.getString(CommentActivity.GAME_COLLECTION_ID) ?: "", + arguments?.getString(EntranceUtils.KEY_COMMENT_ID) ?: "" + ) + ) + } + + override fun onBackPressed(): Boolean { + requireActivity().finish() + return super.onBackPressed() + } + + override fun onLoadDone() { + showSkeleton(false) + super.onLoadDone() + } + + private fun startCommentActivity(comment: CommentEntity) { + val intent = CommentActivity.getGameCollectionCommentReplyIntent(requireContext(), + mViewModel.gameCollectionId, + arguments?.getString(EntranceUtils.KEY_COMMENT_ID) ?: "", + mViewModel.commentCount, + comment) + startActivityForResult(intent, CommentActivity.REQUEST_CODE) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/detail/conversation/GameCollectionCommentConversationViewModel.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/detail/conversation/GameCollectionCommentConversationViewModel.kt new file mode 100644 index 0000000000..411eaf376f --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/detail/conversation/GameCollectionCommentConversationViewModel.kt @@ -0,0 +1,69 @@ +package com.gh.gamecenter.gamecollection.detail.conversation + +import android.annotation.SuppressLint +import android.app.Application +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import com.gh.gamecenter.entity.CommentEntity +import com.gh.gamecenter.gamecollection.detail.GameCollectionDetailViewModel +import com.gh.gamecenter.qa.article.detail.CommentItemData +import com.gh.gamecenter.retrofit.BiResponse +import io.reactivex.Single +import io.reactivex.schedulers.Schedulers +import retrofit2.HttpException + +class GameCollectionCommentConversationViewModel(application: Application, + gameCollectionId: String = "", + var commentId: String = "") + : GameCollectionDetailViewModel(application, gameCollectionId) { + var commentDetail: CommentEntity? = null + var positionInOriginList = -1 + + override fun provideDataObservable(page: Int) = null + + override fun provideDataSingle(page: Int): Single> { + return mApi.getGameCollectionCommentReply(gameCollectionId, commentId, page) + } + + @SuppressLint("CheckResult") + fun getComment() { + mApi.getGameCollectionComment(gameCollectionId, commentId) + .subscribeOn(Schedulers.io()) + .subscribe(object : BiResponse() { + @SuppressLint("CheckResult") + override fun onSuccess(data: CommentEntity) { + commentDetail = data + commentDetail?.floor = positionInOriginList + commentCount = data.reply + topItemData = CommentItemData(commentTop = data) + loadResultLiveData.postValue(LoadResult.SUCCESS) + mergeListData(mListLiveData.value, hasFilter = false) + } + + override fun onFailure(exception: Exception) { + if (exception is HttpException && exception.code().toString().contains("404")) { + loadResultLiveData.postValue(LoadResult.DELETED) + } else { + loadResultLiveData.postValue(LoadResult.NETWORK_ERROR) + } + } + }) + } + + override fun mergeResultLiveData() { + mResultLiveData.addSource(mListLiveData) { mergeListData(it, hasFilter = false) } + } + + + class Factory(private val application: Application, + private val gameCollectionId: String = "", + private val commentId: String) : ViewModelProvider.NewInstanceFactory() { + + override fun create(modelClass: Class): T { + return GameCollectionCommentConversationViewModel( + application = application, + gameCollectionId = gameCollectionId, + commentId = commentId) as T + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/mine/MyGameCollectionActivity.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/mine/MyGameCollectionActivity.kt new file mode 100644 index 0000000000..2836609f67 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/mine/MyGameCollectionActivity.kt @@ -0,0 +1,18 @@ +package com.gh.gamecenter.gamecollection.mine + +import android.content.Context +import android.content.Intent +import com.gh.gamecenter.NormalActivity + +class MyGameCollectionActivity : NormalActivity() { + + override fun provideNormalIntent(): Intent { + return getTargetIntent(this, MyGameCollectionActivity::class.java, MyGameCollectionFragment::class.java) + } + + companion object { + fun getIntent(context: Context): Intent { + return getTargetIntent(context, MyGameCollectionActivity::class.java, MyGameCollectionFragment::class.java) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/mine/MyGameCollectionAdapter.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/mine/MyGameCollectionAdapter.kt new file mode 100644 index 0000000000..3eab62f0f9 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/mine/MyGameCollectionAdapter.kt @@ -0,0 +1,74 @@ +package com.gh.gamecenter.gamecollection.mine + +import android.content.Context +import android.view.ViewGroup +import androidx.core.content.ContextCompat +import androidx.recyclerview.widget.RecyclerView +import com.gh.common.constant.ItemViewType +import com.gh.common.util.DisplayUtils +import com.gh.common.util.toBinding +import com.gh.gamecenter.R +import com.gh.gamecenter.adapter.viewholder.FooterViewHolder +import com.gh.gamecenter.baselist.ListAdapter +import com.gh.gamecenter.entity.GamesCollectionEntity + +class MyGameCollectionAdapter( + context: Context, + val mViewModel: MyGameCollectionViewModel, + val entrance: String, + val path: String +) : ListAdapter(context) { + + override fun getItemViewType(position: Int): Int { + return if (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 -> MyGameCollectionViewHolder(parent.toBinding(), mViewModel, entrance, path) + ItemViewType.ITEM_FOOTER -> { + FooterViewHolder(mLayoutInflater.inflate(R.layout.refresh_footerview, parent, false)) + } + else -> MyGameCollectionViewHolder(parent.toBinding(), mViewModel, entrance, path) + } + } + + override fun areItemsTheSame(oldItem: GamesCollectionEntity?, newItem: GamesCollectionEntity?): Boolean { + return oldItem == newItem || oldItem?.id == newItem?.id + } + + override fun areContentsTheSame(oldItem: GamesCollectionEntity?, newItem: GamesCollectionEntity?): Boolean { + return oldItem?.id == newItem?.id + && oldItem?.title == newItem?.title + && oldItem?.intro == newItem?.intro + && oldItem?.cover == newItem?.cover + && oldItem?.display == newItem?.display + && oldItem?.status == newItem?.status + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is MyGameCollectionViewHolder -> { + val entity = mEntityList[position] + holder.bindGameCollectionItem(entity) + } + is FooterViewHolder -> { + holder.initItemPadding() + holder.initFooterViewHolder(mIsLoading, mIsNetworkError, mIsOver) + holder.hint.setTextColor(ContextCompat.getColor(mContext, R.color.aaaaaa)) + val lp = holder.itemView.layoutParams as RecyclerView.LayoutParams + lp.height = DisplayUtils.dip2px(48F) + lp.width = ViewGroup.LayoutParams.MATCH_PARENT + holder.itemView.layoutParams = lp + } + } + } + + override fun getItemCount(): Int { + return if (mEntityList == null || mEntityList.isEmpty()) return 0 else mEntityList.size + 1 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/mine/MyGameCollectionFragment.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/mine/MyGameCollectionFragment.kt new file mode 100644 index 0000000000..a6bd76620e --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/mine/MyGameCollectionFragment.kt @@ -0,0 +1,105 @@ +package com.gh.gamecenter.gamecollection.mine + +import android.content.Intent +import android.os.Bundle +import android.view.MenuItem +import android.view.View +import androidx.recyclerview.widget.RecyclerView +import com.gh.common.constant.Constants +import com.gh.common.util.NewLogUtils +import com.gh.common.util.showRegulationTestDialogIfNeeded +import com.gh.common.view.VerticalItemDecoration +import com.gh.gamecenter.R +import com.gh.gamecenter.WebActivity +import com.gh.gamecenter.baselist.ListAdapter +import com.gh.gamecenter.baselist.ListFragment +import com.gh.gamecenter.baselist.LoadType +import com.gh.gamecenter.databinding.FragmentMyGameCollectionListBinding +import com.gh.gamecenter.entity.GamesCollectionEntity +import com.gh.gamecenter.eventbus.EBReuse +import com.gh.gamecenter.gamecollection.publish.GameCollectionEditActivity +import com.gh.gamecenter.gamecollection.square.GameCollectionSquareActivity +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode + +class MyGameCollectionFragment : ListFragment() { + + private var mAdapter: MyGameCollectionAdapter? = null + private lateinit var mBinding: FragmentMyGameCollectionListBinding + + override fun getLayoutId(): Int = 0 + + override fun getInflatedLayout(): View { + return FragmentMyGameCollectionListBinding.inflate(layoutInflater, null, false).apply { + mBinding = this + }.root + } + + override fun onMenuItemClick(menuItem: MenuItem?) { + super.onMenuItemClick(menuItem) + menuItem?.run { + if (itemId == R.id.menu_game_collection_square) { + startActivity(Intent(requireContext(), GameCollectionSquareActivity::class.java)) + } + } + } + + override fun provideListAdapter(): ListAdapter<*> { + return mAdapter ?: MyGameCollectionAdapter(requireContext(), mListViewModel, mEntrance, "我的游戏单").apply { + mAdapter = this + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + NewLogUtils.logEnterMyGameCollection() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setNavigationTitle("我的游戏单") + initMenu(R.menu.menu_my_game_collection) + + mListViewModel.deleteLiveData.observe(viewLifecycleOwner) { + val index = mAdapter?.entityList?.indexOf(it) ?: -1 + mAdapter?.entityList?.removeAt(index) + if (mAdapter?.entityList?.isNullOrEmpty() == true) { + onLoadEmpty() + } else { + mAdapter?.notifyItemRemoved(index) + } + + toast("删除成功") + } + + mListViewModel.publishLiveData.observe(viewLifecycleOwner) { + mListViewModel.load(LoadType.REFRESH) + } + + mBinding.createBtn.setOnClickListener { + showRegulationTestDialogIfNeeded { + startActivity(GameCollectionEditActivity.getIntent(requireContext(), mEntrance, "我的游戏单")) + } + } + + mBinding.reuseNoneDataIv.setOnClickListener { + mBinding.createBtn.performClick() + } + + mBinding.specificationLinkTv.setOnClickListener { + startActivity(WebActivity.getWebIntent(requireContext(), "游戏单管理规范", Constants.GAME_COLLECTION_RULE)) + } + } + + override fun getItemDecoration(): RecyclerView.ItemDecoration { + return VerticalItemDecoration(requireContext(), 16F, true, R.color.white) + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onEventMainThread(changed: EBReuse) { + if (changed.type == "createGameCollectionSuccess") { + mListViewModel.load(LoadType.REFRESH) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/mine/MyGameCollectionViewHolder.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/mine/MyGameCollectionViewHolder.kt new file mode 100644 index 0000000000..5f8cf5878e --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/mine/MyGameCollectionViewHolder.kt @@ -0,0 +1,87 @@ +package com.gh.gamecenter.gamecollection.mine + +import android.view.LayoutInflater +import com.gh.base.BaseRecyclerViewHolder +import com.gh.common.util.DialogHelper +import com.gh.common.util.goneIf +import com.gh.gamecenter.databinding.ItemGameCollectionFlexTagBinding +import com.gh.gamecenter.databinding.ItemMyGameCollectionBinding +import com.gh.gamecenter.entity.GamesCollectionEntity +import com.gh.gamecenter.entity.TagInfoEntity +import com.gh.gamecenter.gamecollection.detail.GameCollectionDetailActivity +import com.gh.gamecenter.gamecollection.publish.GameCollectionEditActivity + +class MyGameCollectionViewHolder( + val binding: ItemMyGameCollectionBinding, + val mViewModel: MyGameCollectionViewModel, + val entrance: String, + val path: String +) : + BaseRecyclerViewHolder(binding.root) { + + fun bindGameCollectionItem(entity: GamesCollectionEntity) { + binding.entity = entity + initTagsUI(entity.tags ?: arrayListOf()) + val statusLabelRes = entity.getStatusLabelRes() + if (statusLabelRes > 0) { + binding.statusView.setImageResource(statusLabelRes) + } + + binding.statusView.setOnClickListener { + when { + entity.display == "self_only" && entity.status == "draft" -> { + DialogHelper.showDialog(binding.root.context, "仅自己可见", "游戏单开启“仅自己可见”后,将不会对其他用户展示,您可以通过关闭仅自己可见或者投稿分享游戏单", "我知道了", "", { + + }, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)) + } + entity.status == "pending" -> { + DialogHelper.showDialog(binding.root.context, "审核中", "游戏单会在1-2个工作日内审核完成,请您耐心等候", "我知道了", "", { + + }, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)) + } + entity.status == "failed" -> { + DialogHelper.showDialog(binding.root.context, "未通过", "您的游戏单可能含有违规内容或语意不明确,请您仔细检查后再次尝试提交", "我知道了", "", { + + }, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)) + } + } + } + binding.root.setOnClickListener { + if (entity.status == "pass" || (entity.display == "self_only" && entity.status == "draft")) { + binding.root.context.startActivity(GameCollectionDetailActivity.getIntent(binding.root.context, entity.id)) + } else { + binding.statusView.performClick() + } + } + + binding.publishBtn.setOnClickListener { + if ((entity.count?.game ?: 0) >= 8) { + DialogHelper.showDialog(binding.root.context, "温馨提示", "投稿通过后,将自动关闭“仅自己可见”,所有用户都能浏览到游戏单,确定投稿?", "确定", "取消", { + mViewModel.publishGameCollection(entity) + }, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)) + } else { + DialogHelper.showDialog(binding.root.context, "温馨提示", "游戏单需要收录至少8个游戏,才可以投稿至游戏单广场哦~", "添加游戏", "我知道了", { + binding.root.context.startActivity(GameCollectionEditActivity.getIntent(binding.root.context, entity, entrance, path)) + }, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)) + } + } + binding.editBtn.setOnClickListener { + binding.root.context.startActivity(GameCollectionEditActivity.getIntent(binding.root.context, entity, entrance, path)) + } + binding.deleteBtn.setOnClickListener { + DialogHelper.showDialog(binding.root.context, "温馨提示", "游戏单删除后将无法恢复,是否确认删除", "确定", "取消", { + mViewModel.deleteGameCollection(entity) + }, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)) + } + } + + private fun initTagsUI(tags: ArrayList) { + binding.gameCollectionTagsContainer.removeAllViews() + tags.forEachIndexed { index, tag -> + val tagBinding = ItemGameCollectionFlexTagBinding.inflate(LayoutInflater.from(binding.root.context), null, false) + tagBinding.tagNameTv.text = tag.name + tagBinding.divider.goneIf(index == tags.size - 1) + binding.gameCollectionTagsContainer.addView(tagBinding.root) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/mine/MyGameCollectionViewModel.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/mine/MyGameCollectionViewModel.kt new file mode 100644 index 0000000000..76811de00e --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/mine/MyGameCollectionViewModel.kt @@ -0,0 +1,60 @@ +package com.gh.gamecenter.gamecollection.mine + +import android.app.Application +import androidx.lifecycle.MutableLiveData +import com.gh.common.util.ToastUtils +import com.gh.common.util.observableToMain +import com.gh.gamecenter.baselist.ListViewModel +import com.gh.gamecenter.entity.GamesCollectionEntity +import com.gh.gamecenter.manager.UserManager +import com.gh.gamecenter.retrofit.Response +import com.gh.gamecenter.retrofit.RetrofitManager +import com.halo.assistant.HaloApp +import io.reactivex.Observable +import okhttp3.ResponseBody +import retrofit2.HttpException + +class MyGameCollectionViewModel(application: Application) : ListViewModel(application) { + private val mApi = RetrofitManager.getInstance(HaloApp.getInstance()).api + val deleteLiveData = MutableLiveData() + val publishLiveData = MutableLiveData() + override fun provideDataObservable(page: Int): Observable>? { + return mApi.getGameCollectionList(UserManager.getInstance().userId) + } + + override fun mergeResultLiveData() { + mResultLiveData.addSource(mListLiveData) { mResultLiveData.postValue(it) } + } + + fun deleteGameCollection(entity: GamesCollectionEntity) { + mApi.deleteGameCollection(entity.id) + .compose(observableToMain()) + .subscribe(object : Response() { + override fun onResponse(response: ResponseBody?) { + super.onResponse(response) + deleteLiveData.postValue(entity) + } + + override fun onFailure(e: HttpException?) { + super.onFailure(e) + ToastUtils.showToast("删除失败") + } + }) + } + + fun publishGameCollection(entity: GamesCollectionEntity){ + mApi.deleteGameCollection(entity.id) + .compose(observableToMain()) + .subscribe(object : Response() { + override fun onResponse(response: ResponseBody?) { + super.onResponse(response) + publishLiveData.postValue(entity) + } + + override fun onFailure(e: HttpException?) { + super.onFailure(e) + ToastUtils.showToast("投稿失败") + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/publish/GameCollectionEditActivity.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/publish/GameCollectionEditActivity.kt new file mode 100644 index 0000000000..900f3ada7a --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/publish/GameCollectionEditActivity.kt @@ -0,0 +1,439 @@ +package com.gh.gamecenter.gamecollection.publish + +import android.app.Activity +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.LayoutInflater +import android.view.MenuItem +import android.view.View +import androidx.core.widget.doOnTextChanged +import com.gh.base.ToolBarActivity +import com.gh.base.fragment.WaitingDialogFragment +import com.gh.common.constant.Constants +import com.gh.common.util.* +import com.gh.gamecenter.CropImageActivity +import com.gh.gamecenter.R +import com.gh.gamecenter.WebActivity +import com.gh.gamecenter.databinding.ActivityGameCollectionEditBinding +import com.gh.gamecenter.databinding.ItemGameCollectionFlexTagBinding +import com.gh.gamecenter.entity.GameCollectionDraft +import com.gh.gamecenter.entity.GamesCollectionEntity +import com.gh.gamecenter.entity.TagInfoEntity +import com.gh.gamecenter.eventbus.EBReuse +import com.gh.gamecenter.gamecollection.choose.ChooseGamesActivity +import com.gh.gamecenter.gamecollection.choose.ChooseGamesViewModel +import com.gh.gamecenter.gamecollection.tag.GameCollectionTagSelectActivity +import com.gh.gamecenter.gamecollection.tag.GameCollectionTagSelectFragment +import com.gh.gamecenter.mvvm.Status +import com.gh.gamecenter.qa.editor.LocalMediaActivity +import com.zhihu.matisse.Matisse +import com.zhihu.matisse.internal.utils.PathUtils +import org.greenrobot.eventbus.EventBus + +class GameCollectionEditActivity : ToolBarActivity() { + + private lateinit var mMenuPost: MenuItem + private lateinit var mBinding: ActivityGameCollectionEditBinding + private lateinit var mViewModel: GameCollectionEditViewModel + private lateinit var mChooseGamesViewModel: ChooseGamesViewModel + private var mProcessingDialog: WaitingDialogFragment? = null + private var mPatchCommitCount = 0 + + override fun getLayoutId(): Int = R.layout.activity_game_collection_edit + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + mBinding = ActivityGameCollectionEditBinding.bind(mContentView) + mViewModel = viewModelProvider() + mChooseGamesViewModel = viewModelProvider(ChooseGamesViewModel.Factory()) + setToolbarMenu(R.menu.menu_game_collection_edit) + mMenuPost = getMenuItem(R.id.menu_game_collection_post) + setNavigationTitle("创建游戏单") + mViewModel.gameCollectionPatch = intent.getParcelableExtra(GamesCollectionEntity::class.java.name) + val path = intent.getStringExtra(EntranceUtils.KEY_PATH) ?: "" + if (path.isNotEmpty()) { + NewLogUtils.logEnterGameCollectionEdit(path) + } + + initData() + observeData() + initListener() + } + + private fun initListener() { + mBinding.gameCollectionTitleEt.run { + doOnTextChanged { text, start, _, _ -> + if (text?.contains("\n") == true) { + setText(text.toString().replace("\n", "")) + setSelection(start) + return@doOnTextChanged + } + if (PatternUtils.isHasSpace(text.toString())) { + setText(PatternUtils.replaceSpace(text.toString())) + setSelection(start) + return@doOnTextChanged + } + } + } + mBinding.gameCollectionIntroduceEt.run { + doOnTextChanged { text, start, _, _ -> + if (PatternUtils.isHasWrap(text.toString())) { + setText(PatternUtils.replaceWrap(text.toString())) + setSelection(start) + return@doOnTextChanged + } + if (PatternUtils.isHasSpace(text.toString())) { + setText(PatternUtils.replaceSpace(text.toString())) + setSelection(start) + return@doOnTextChanged + } + } + } + mBinding.uploadPictureBtn.setOnClickListener { + PermissionHelper.checkStoragePermissionBeforeAction( + this, + object : EmptyCallback { + override fun onCallback() { + startActivityForResult( + LocalMediaActivity.getIntent( + this@GameCollectionEditActivity, + LocalMediaActivity.ChooseType.IMAGE, + 1, + "创建游戏单" + ), REQUEST_CODE_IMAGE + ) + } + }) + } + + mBinding.changePosterBtn.setOnClickListener { mBinding.uploadPictureBtn.performClick() } + + mBinding.deleteBtn.setOnClickListener { + mViewModel.imageUrl = "" + mViewModel.imagePath = "" + mBinding.uploadPictureBtn.text = "点击上传图片" + initPosterUI() + } + + mBinding.gameCollectionIntroduceEt.doOnTextChanged { text, _, _, _ -> + mBinding.introduceSizeTv.text = "${text?.length ?: 0}/200" + } + + mBinding.chooseLabelContainer.setOnClickListener { + startActivityForResult(GameCollectionTagSelectActivity.getIntent(this), REQUEST_CHOOSE_TAG) + } + + mBinding.chooseGameContainer.setOnClickListener { + startActivity(ChooseGamesActivity.getIntent(this)) + } + mBinding.normsLinkTv.setOnClickListener { + startActivity(WebActivity.getWebIntent(this, "游戏单管理规范", Constants.GAME_COLLECTION_RULE)) + } + } + + private fun initData(isFirst: Boolean = true) { + mBinding.gameCollectionTitleEt.filters = arrayOf(TextHelper.getFilter(20, "最多输入20个字")) + mBinding.gameCollectionIntroduceEt.filters = arrayOf(TextHelper.getFilter(200, "最多输入200个字")) + initTagsUI(mViewModel.tags) + + mViewModel.gameCollectionPatch?.run { + mViewModel.imageUrl = cover + if (status.isNotEmpty() && (status != "draft" || display == "self_only")) { + setNavigationTitle("编辑游戏单") + } + initPosterUI() + mBinding.gameCollectionTitleEt.setText(title) + mBinding.gameCollectionTitleEt.setSelection(title.length) + mBinding.gameCollectionIntroduceEt.setText(intro) + mBinding.gameCollectionIntroduceEt.setSelection(intro.length) + mBinding.selfOnlyCb.isChecked = display == "self_only" + if (isFirst) { + mViewModel.getGameCollectionDetail(id) + } + } ?: mViewModel.getDraft() + } + + private fun observeData() { + mChooseGamesViewModel.chooseGamesLiveData.observe(this) { + mBinding.gamesTv.text = if (it.isEmpty()) "选择游戏" else "已选 ${it.size} 款游戏" + mBinding.gamesTipTv.goneIf(it.isNotEmpty()) + } + mViewModel.processDialog.observe(this) { + if (it.isShow) { + if (mProcessingDialog?.dialog?.isShowing == true) { + mProcessingDialog?.uploadWaitingHint(it.msg) + } else { + mProcessingDialog = WaitingDialogFragment.newInstance(it.msg, false) + mProcessingDialog?.show(supportFragmentManager, null) + } + } else { + mProcessingDialog?.dismiss() + } + } + mViewModel.uploadImageSuccessLiveData.observe(this) { + if (it.isNotEmpty()) { + initPosterUI() + } + mBinding.uploadPictureBtn.text = "点击上传图片" + } + mViewModel.postLiveData.observe(this) { + if (it.status == Status.SUCCESS) { + toast("提交成功") + EventBus.getDefault().post(EBReuse("createGameCollectionSuccess")) + finish() + } else { + ErrorHelper.handleError(this, it.exception?.response()?.errorBody()?.string()) + } + } + mViewModel.detailLiveData.observe(this) { + mViewModel.gameCollectionPatch?.apply { + games = it.games + tags = it.tags + title = it.title + intro = it.intro + } + mViewModel.gameCollectionPatch?.run { + initData(false) + mViewModel.tags = tags ?: arrayListOf() + initTagsUI(mViewModel.tags) + val simpleGames = games?.map { game -> game.toGameEntity() }?.toList() + mChooseGamesViewModel.chooseGamesLiveData.postValue(ArrayList(simpleGames)) + } + } + mViewModel.draftLiveData.observe(this) { + mViewModel.gameCollectionPatch = it.convertGameCollectionEntity() + mViewModel.gameCollectionPatch?.run { + initData(false) + mViewModel.tags = tags ?: arrayListOf() + initTagsUI(mViewModel.tags) + val simpleGames = games?.map { game -> game.toGameEntity() } + mChooseGamesViewModel.chooseGamesLiveData.postValue(ArrayList(simpleGames)) + } + } + } + + override fun onMenuItemClick(item: MenuItem?): Boolean { + item?.run { + if (itemId == R.id.menu_game_collection_post) { + debounceActionWithInterval(R.id.menu_game_collection_post, 3000L) { + verifyData() + } + } + } + return super.onMenuItemClick(item) + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (data == null || resultCode != Activity.RESULT_OK) return + when (requestCode) { + REQUEST_CODE_IMAGE -> { + val selectedPaths = Matisse.obtainResult(data) + if (!selectedPaths.isNullOrEmpty()) { + val path = PathUtils.getPath(this, selectedPaths[0]) + val intent = CropImageActivity.getIntent(this, path, 142 / 328F, false, mEntrance) + startActivityForResult(intent, REQUEST_CODE_IMAGE_CROP) + } + } + REQUEST_CODE_IMAGE_CROP -> { + val imagePath = data.getStringExtra(CropImageActivity.RESULT_CLIP_PATH) ?: "" + mViewModel.imageUrl = "" + mViewModel.imagePath = imagePath + if (imagePath.isEmpty()) { + mBinding.uploadPictureBtn.text = "点击上传图片" + } else { + mBinding.uploadPictureBtn.text = "图片上传中..." + mViewModel.uploadPoster() + } + initPosterUI() + } + REQUEST_CHOOSE_TAG -> { + val tags = data.getParcelableArrayListExtra(GameCollectionTagSelectFragment.SELECTED_TAG) ?: arrayListOf() + mViewModel.tags = tags + initTagsUI(tags) + } + } + } + + private fun initPosterUI() { + mBinding.placeholderView.goneIf(mViewModel.imageUrl.isNotEmpty()) + mBinding.uploadPictureBtn.goneIf(mViewModel.imageUrl.isNotEmpty()) + mBinding.posterView.goneIf(mViewModel.imageUrl.isEmpty()) + mBinding.changePosterBtn.goneIf(mViewModel.imageUrl.isEmpty()) + mBinding.deleteBtn.goneIf(mViewModel.imageUrl.isEmpty()) + if (mViewModel.imageUrl.isNotEmpty()) { + ImageUtils.display(mBinding.posterView, mViewModel.imageUrl) + } + } + + private fun initTagsUI(tags: ArrayList) { + mBinding.gameCollectionTagsContainer.removeAllViews() + mBinding.labelTipTv.goneIf(tags.isNotEmpty()) + if (tags.isEmpty()) { + val tagBinding = ItemGameCollectionFlexTagBinding.inflate(LayoutInflater.from(this), null, false) + tagBinding.tagNameTv.run { + text = "选择标签" + setTextColor(R.color.text_333333.toColor()) + textSize = 14f + } + tagBinding.divider.visibility = View.GONE + mBinding.gameCollectionTagsContainer.addView(tagBinding.root) + } else { + tags.forEachIndexed { index, tag -> + val tagBinding = ItemGameCollectionFlexTagBinding.inflate(LayoutInflater.from(this), null, false) + tagBinding.tagNameTv.run { + text = tag.name + setTextColor(R.color.text_333333.toColor()) + textSize = 14f + } + tagBinding.divider.run { + alpha = 0.2f + setBackgroundColor(R.color.text_333333.toColor()) + goneIf(index == tags.size - 1) + setPadding(8f.dip2px(), 0, 8f.dip2px(), 0) + } + mBinding.gameCollectionTagsContainer.addView(tagBinding.root) + } + } + } + + override fun handleBackPressed(): Boolean { + val patch = mViewModel.gameCollectionPatch + if (patch != null && patch.status.isNotEmpty() && (patch.status != "draft" || patch.display == "self_only")) { + DialogHelper.showDialog(this, "温馨提示", "退出将不会保留本次游戏单编辑的内容,是否确定退出", "确定", "取消", { + finish() + }, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)) + return true + } else { + val games = mChooseGamesViewModel.chooseGamesLiveData.value ?: arrayListOf() + val title = mBinding.gameCollectionTitleEt.text.toString() + val introduce = mBinding.gameCollectionIntroduceEt.text.toString() + if (mViewModel.imageUrl.isNotEmpty() + || mViewModel.tags.isNotEmpty() + || games.isNotEmpty() + || title.isNotEmpty() + || introduce.isNotEmpty() + ) { + val entity = GameCollectionDraft() + entity.tags = mViewModel.tags + entity.title = title + entity.intro = introduce + entity.cover = mViewModel.imageUrl + entity.display = if (mBinding.selfOnlyCb.isChecked) "self_only" else "" + entity.games = arrayListOf() + entity.games?.addAll(games.map { it.toSimpleGame() }.toList()) + mViewModel.addDraft(entity) + } + } + return super.handleBackPressed() + } + + private fun verifyData() { + if (mViewModel.imageUrl.isEmpty()) { + toast("请上传游戏单的封面") + return + } + if (mViewModel.tags.isEmpty()) { + toast("请选择游戏单的标签") + return + } + val games = mChooseGamesViewModel.chooseGamesLiveData.value ?: arrayListOf() + + val title = mBinding.gameCollectionTitleEt.text.toString().trim() + if (title.isEmpty()) { + toast("请填写游戏单的标题") + return + } + val introduce = mBinding.gameCollectionIntroduceEt.text.toString().trim() + if (introduce.length < 10) { + toast("介绍至少10个字") + return + } + + val isSelfOnly = mBinding.selfOnlyCb.isChecked + val requestMap = hashMapOf( + "id" to (mViewModel.gameCollectionPatch?.id ?: ""), + "title" to title, + "intro" to introduce, + "tag_ids" to mViewModel.tags.map { it.id }.toList(), + "cover" to mViewModel.imageUrl, + "display" to if (isSelfOnly) "self_only" else "", + "games" to games.map { + hashMapOf( + "_id" to it.id, + "recommend_star" to it.recommendStar, + "recommend_text" to it.recommendText, + ) + }.toList() + ) + + val patch = mViewModel.gameCollectionPatch + if (patch != null + && patch.status.isNotEmpty() + && patch.status != "draft" + && games.size < 8 + ) { + if (mPatchCommitCount < 2) { + toast("游戏单收录的游戏不能少于8款") + mPatchCommitCount++ + return + } else { + if (!mBinding.selfOnlyCb.isChecked) { + toast("没想好收录的游戏,开启仅自己可见试试") + mPatchCommitCount++ + return + } + } + } + + if (isSelfOnly) { + DialogHelper.showDialog(this, "温馨提示", "游戏单开启“仅自己可见”后,将不会对其他用户展示,是否继续提交", "确定", "取消", { + mViewModel.uploadContent(requestMap) + }, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)) + } else { + if (games.size < 8) { + DialogHelper.showDialog(this, "温馨提示", "游戏单需要收录至少8个游戏,才可以投稿至游戏单广场,是否在“我的光环-我的游戏单”继续保存为草稿", "继续保存", "添加游戏", { + mViewModel.uploadContent(requestMap) + }, { + startActivity(ChooseGamesActivity.getIntent(this)) + }, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)) + } else { + DialogHelper.showDialog(this, "温馨提示", "游戏单会在1-2个工作日内审核完成,您可以在“我的光环-我的游戏单”查看进度", "继续提交", "取消", { + mViewModel.uploadContent(requestMap) + }, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)) + } + } + } + + override fun onDestroy() { + super.onDestroy() + mChooseGamesViewModel.chooseGamesLiveData.postValue(arrayListOf()) + } + + + companion object { + + const val REQUEST_CODE_IMAGE = 100 + const val REQUEST_CODE_IMAGE_CROP = 101 + const val REQUEST_CHOOSE_GAMES = 102 + const val REQUEST_CHOOSE_TAG = 103 + + @JvmStatic + fun getIntent(context: Context, entrance: String = "", path: String = ""): Intent { + val intent = Intent(context, GameCollectionEditActivity::class.java) + intent.putExtra(EntranceUtils.KEY_ENTRANCE, mergeEntranceAndPath(entrance, path)) + intent.putExtra(EntranceUtils.KEY_PATH, path) + return intent + } + + @JvmStatic + fun getIntent(context: Context, entity: GamesCollectionEntity, entrance: String = "", path: String = ""): Intent { + val intent = Intent(context, GameCollectionEditActivity::class.java) + intent.putExtra(GamesCollectionEntity::class.java.name, entity) + intent.putExtra(EntranceUtils.KEY_ENTRANCE, mergeEntranceAndPath(entrance, path)) + intent.putExtra(EntranceUtils.KEY_PATH, path) + return intent + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/publish/GameCollectionEditViewModel.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/publish/GameCollectionEditViewModel.kt new file mode 100644 index 0000000000..2e3e2f061a --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/publish/GameCollectionEditViewModel.kt @@ -0,0 +1,139 @@ +package com.gh.gamecenter.gamecollection.publish + +import android.annotation.SuppressLint +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.MediatorLiveData +import androidx.lifecycle.MutableLiveData +import com.gh.base.fragment.WaitingDialogFragment +import com.gh.common.runOnIoThread +import com.gh.common.util.* +import com.gh.gamecenter.entity.GameCollectionDraft +import com.gh.gamecenter.entity.GamesCollectionEntity +import com.gh.gamecenter.entity.TagInfoEntity +import com.gh.gamecenter.mvvm.Resource +import com.gh.gamecenter.retrofit.BiResponse +import com.gh.gamecenter.retrofit.Response +import com.gh.gamecenter.retrofit.RetrofitManager +import com.gh.gamecenter.room.AppDatabase +import com.halo.assistant.HaloApp +import okhttp3.ResponseBody +import org.json.JSONObject +import retrofit2.HttpException + +class GameCollectionEditViewModel(application: Application) : AndroidViewModel(application) { + var imagePath = "" + var imageUrl = "" + var tags = ArrayList() + var gameCollectionPatch: GamesCollectionEntity? = null + var uploadImageSuccessLiveData = MutableLiveData() + var detailLiveData = MutableLiveData() + var draftLiveData = MutableLiveData() + val postLiveData = MutableLiveData>() + val processDialog = MediatorLiveData() + private val mApi = RetrofitManager.getInstance(HaloApp.getInstance()).api + private val mDraftDao = AppDatabase.getInstance(HaloApp.getInstance()).gameCollectionDraftDao() + + fun uploadPoster() { + if (imagePath.isEmpty()) return + UploadImageUtils.uploadImage(UploadImageUtils.UploadType.poster, imagePath, object : UploadImageUtils.OnUploadImageListener { + override fun onSuccess(imageUrl: String) { + this@GameCollectionEditViewModel.imageUrl = imageUrl + uploadImageSuccessLiveData.postValue(imageUrl) + } + + override fun onError(e: Throwable?) { + uploadImageSuccessLiveData.postValue("") + ToastUtils.showToast("图片上传失败") + } + + override fun onProgress(total: Long, progress: Long) {} + }) + } + + fun uploadContent(requestMap: HashMap) { + val id = requestMap["id"]?.toString() ?: "" + requestMap.remove("id") + postContent(requestMap, id) + } + + private fun postContent(requestMap: HashMap, gameCollectionId: String) { + processDialog.postValue(WaitingDialogFragment.WaitingDialogData("提交中...", true)) + val requestBody = requestMap.toRequestBody() + val observable = if (gameCollectionId.isEmpty()) { + mApi.createGameCollection(requestBody) + } else { + mApi.patchGameCollection(requestBody, gameCollectionId) + } + observable + .compose(observableToMain()) + .subscribe(object : Response() { + override fun onResponse(response: ResponseBody?) { + super.onResponse(response) + val data = response?.string() + val id = if (!data.isNullOrEmpty()) { + JSONObject(data).optString("_id") + } else "" + postLiveData.postValue(Resource.success(id)) + processDialog.postValue(WaitingDialogFragment.WaitingDialogData("", false)) + deleteAllDraft() + } + + override fun onFailure(e: HttpException?) { + super.onFailure(e) + postLiveData.postValue(Resource.error(e)) + processDialog.postValue(WaitingDialogFragment.WaitingDialogData("", false)) + } + }) + } + + fun getGameCollectionDetail(gameCollectionId: String) { + if (gameCollectionId.isEmpty()) return + processDialog.postValue(WaitingDialogFragment.WaitingDialogData("加载中...", true)) + mApi.getGameCollectionDetailForDraft(gameCollectionId) + .compose(observableToMain()) + .subscribe(object : Response() { + override fun onResponse(response: GamesCollectionEntity?) { + super.onResponse(response) + if (response != null) { + detailLiveData.postValue(response) + processDialog.postValue(WaitingDialogFragment.WaitingDialogData("", false)) + } + } + + override fun onFailure(e: HttpException?) { + super.onFailure(e) + processDialog.postValue(WaitingDialogFragment.WaitingDialogData("", false)) + } + }) + } + + @SuppressLint("CheckResult") + fun getDraft() { + mDraftDao.getAllDraft() + .compose(singleToMain()) + .subscribe(object : BiResponse>() { + override fun onSuccess(data: List) { + if (data.isNotEmpty()) { + val draftEntity = data[0] + draftLiveData.postValue(draftEntity) + } + } + }) + } + + fun addDraft(entity: GameCollectionDraft) { + runOnIoThread { + val drafts = mDraftDao.getDrafts() + mDraftDao.delete(drafts) + mDraftDao.addDraft(entity) + } + } + + fun deleteAllDraft(){ + runOnIoThread { + val drafts = mDraftDao.getDrafts() + mDraftDao.delete(drafts) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/square/GameCollectionAmwayAdapter.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/square/GameCollectionAmwayAdapter.kt new file mode 100644 index 0000000000..9fc148d7ec --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/square/GameCollectionAmwayAdapter.kt @@ -0,0 +1,54 @@ +package com.gh.gamecenter.gamecollection.square + +import android.content.Context +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.gh.common.util.TextHelper +import com.gh.gamecenter.R +import com.gh.gamecenter.databinding.GameCollectionAmwayContentItemBinding +import com.gh.gamecenter.entity.AmwayCommentEntity +import com.lightgame.adapter.BaseRecyclerAdapter + +class GameCollectionAmwayAdapter(context: Context) : + BaseRecyclerAdapter(context) { + + private var mAmwayList = listOf() + + fun setAmwayList(amwayList: List) { + mAmwayList = amwayList + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = + GameCollectionAmwayContentItemViewHolder(GameCollectionAmwayContentItemBinding.inflate(mLayoutInflater, parent, false)) + + override fun getItemCount() = if (getRealCount() > 1) getRealCount() + INCREASE_COUNT else getRealCount() + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + if (holder is GameCollectionAmwayContentItemViewHolder) { + holder.binding.entity = mAmwayList[getRealPosition(position)] + holder.binding.amwayTv.text = TextHelper.getCommentLabelSpannableStringBuilder(mAmwayList[getRealPosition(position)].comment.content, R.color.white) + } + } + + class GameCollectionAmwayContentItemViewHolder(var binding: GameCollectionAmwayContentItemBinding) : + RecyclerView.ViewHolder(binding.root) + + fun getRealCount(): Int = mAmwayList.size + + fun getRealPosition(position: Int) = when (position) { + 0 -> { + getRealCount() - 1 + } + getRealCount() + 1 -> { + 0 + } + else -> { + position - 1 + } + } + + companion object{ + const val INCREASE_COUNT = 2 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/square/GameCollectionAmwayViewHolder.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/square/GameCollectionAmwayViewHolder.kt new file mode 100644 index 0000000000..a7f8d3ce6b --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/square/GameCollectionAmwayViewHolder.kt @@ -0,0 +1,114 @@ +package com.gh.gamecenter.gamecollection.square + +import android.animation.Animator +import android.animation.TimeInterpolator +import android.animation.ValueAnimator +import android.view.animation.AccelerateDecelerateInterpolator +import androidx.recyclerview.widget.RecyclerView +import androidx.viewpager2.widget.ViewPager2 +import com.gh.common.util.DirectUtils +import com.gh.common.util.NewLogUtils +import com.gh.gamecenter.databinding.GameCollectionSquareAmwayItemBinding +import com.gh.gamecenter.entity.AmwayCommentEntity +import java.lang.ref.WeakReference + +class GameCollectionAmwayViewHolder(var binding: GameCollectionSquareAmwayItemBinding) : RecyclerView.ViewHolder(binding.root) { + val mAdapter = GameCollectionAmwayAdapter(binding.root.context) + val mLoopTask = object : Runnable { + private val reference: WeakReference = WeakReference(binding.amwayVp) + override fun run() { + val vp: ViewPager2? = reference.get() + if (vp != null) { + val count = mAdapter.itemCount + if (count == 0) return + val next = (vp.currentItem + 1) % count + vp.setCurrentItem(next, 1000) + vp.postDelayed(this, AMWAY_LOOP_TIME) + } + } + } + + fun bindAmway(amwayList: List) { + mAdapter.setAmwayList(amwayList) + binding.amwayVp.run { + if (adapter is GameCollectionAmwayAdapter) return + isUserInputEnabled = false + adapter = mAdapter + orientation = ViewPager2.ORIENTATION_VERTICAL + registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { + private var mTempPosition = GameCollectionSquareAdapter.INVALID_VALUE + private var isScrolled = false + + override fun onPageSelected(position: Int) { + if (isScrolled) { + mTempPosition = position + } + } + + override fun onPageScrollStateChanged(state: Int) { + //手势滑动中,代码执行滑动中 + if (state == ViewPager2.SCROLL_STATE_DRAGGING || state == ViewPager2.SCROLL_STATE_SETTLING) { + isScrolled = true + } else if (state == ViewPager2.SCROLL_STATE_IDLE) { + //滑动闲置或滑动结束 + isScrolled = false + if (mTempPosition != GameCollectionSquareAdapter.INVALID_VALUE) { + if (mTempPosition == 0) { + setCurrentItem(mAdapter.getRealCount(), false) + } else if (mTempPosition == mAdapter.itemCount - 1) { + setCurrentItem(1, false) + } + } + } + } + }) + setCurrentItem(1, false) + postDelayed(mLoopTask, AMWAY_LOOP_TIME) + } + binding.root.setOnClickListener { + NewLogUtils.logClickGameCollectionAmway() + DirectUtils.directToAmway(binding.root.context) + NewLogUtils.logEnterGameCollectionAmway() + } + } + + fun start() { + stop() + binding.amwayVp.postDelayed(mLoopTask, AMWAY_LOOP_TIME) + } + + fun stop() { + binding.amwayVp.removeCallbacks(mLoopTask) + } + + fun ViewPager2.setCurrentItem( + item: Int, + duration: Long, + interpolator: TimeInterpolator = AccelerateDecelerateInterpolator(), + pagePxHeight: Int = height + ) { + val pxToDrag: Int = pagePxHeight * (item - currentItem) + val animator = ValueAnimator.ofInt(0, pxToDrag) + var previousValue = 0 + animator.addUpdateListener { valueAnimator -> + val currentValue = valueAnimator.animatedValue as Int + val currentPxToDrag = (currentValue - previousValue).toFloat() + fakeDragBy(-currentPxToDrag) + previousValue = currentValue + } + animator.addListener(object : Animator.AnimatorListener { + override fun onAnimationStart(animation: Animator?) { beginFakeDrag() } + override fun onAnimationEnd(animation: Animator?) { endFakeDrag() } + override fun onAnimationCancel(animation: Animator?) { } + override fun onAnimationRepeat(animation: Animator?) { } + }) + animator.interpolator = interpolator + animator.duration = duration + animator.start() + } + + companion object { + // 安利墙卡片轮播时间 + const val AMWAY_LOOP_TIME = 5000L + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/square/GameCollectionListItemData.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/square/GameCollectionListItemData.kt new file mode 100644 index 0000000000..8a0852d932 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/square/GameCollectionListItemData.kt @@ -0,0 +1,7 @@ +package com.gh.gamecenter.gamecollection.square + +import com.gh.gamecenter.entity.GamesCollectionEntity +import com.gh.gamecenter.home.LegacyHomeItemData + +data class GameCollectionListItemData(var gameCollectionItem: GamesCollectionEntity? = null, var gameStartPosition: Int = 0) : + LegacyHomeItemData() \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/square/GameCollectionSquareActivity.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/square/GameCollectionSquareActivity.kt new file mode 100644 index 0000000000..d61d327fba --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/square/GameCollectionSquareActivity.kt @@ -0,0 +1,23 @@ +package com.gh.gamecenter.gamecollection.square + +import android.os.Bundle +import com.gh.base.BaseActivity +import com.gh.common.util.DisplayUtils +import com.gh.gamecenter.R + +class GameCollectionSquareActivity : BaseActivity() { + override fun getLayoutId(): Int { + return R.layout.activity_amway + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + DisplayUtils.transparentStatusBar(this) + + val containerFragment = supportFragmentManager.findFragmentByTag( + GameCollectionSquareFragment::class.java.simpleName) + ?: GameCollectionSquareFragment().with(intent.extras) + supportFragmentManager.beginTransaction().replace(R.id.placeholder, containerFragment, GameCollectionSquareFragment::class.java.simpleName).commitAllowingStateLoss() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/square/GameCollectionSquareAdapter.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/square/GameCollectionSquareAdapter.kt new file mode 100644 index 0000000000..90adeb1433 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/square/GameCollectionSquareAdapter.kt @@ -0,0 +1,237 @@ +package com.gh.gamecenter.gamecollection.square + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Color +import android.util.SparseArray +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.content.ContextCompat +import androidx.core.graphics.drawable.toDrawable +import androidx.recyclerview.widget.RecyclerView +import com.gh.base.BaseActivity +import com.gh.common.constant.ItemViewType +import com.gh.common.exposure.ExposureEvent +import com.gh.common.exposure.ExposureSource +import com.gh.common.exposure.IExposable +import com.gh.common.util.* +import com.gh.gamecenter.GameDetailActivity +import com.gh.gamecenter.R +import com.gh.gamecenter.adapter.viewholder.FooterViewHolder +import com.gh.gamecenter.baselist.ListAdapter +import com.gh.gamecenter.databinding.GameCollectionSquareAmwayItemBinding +import com.gh.gamecenter.databinding.GameCollectionSquareItemBinding +import com.gh.gamecenter.entity.AmwayCommentEntity +import com.gh.gamecenter.entity.GamesCollectionEntity +import com.gh.gamecenter.gamecollection.detail.GameCollectionDetailActivity + +class GameCollectionSquareAdapter( + context: Context, + private val isHome: Boolean = false, + private val mViewModel: GameCollectionSquareViewModel, + private var mBasicExposureSource: List, + private val mOuterSequence: Int = -1 +) : + ListAdapter(context), IExposable { + + private var mAmwayList = listOf() + + fun setAmwayList(amwayList: List) { + if (isHome) { + mAmwayList = amwayList + notifyItemChanged(0) + } + } + + override fun getItemViewType(position: Int): Int { + return if (isHome && position == 0) ItemViewType.ITEM_HEADER else if (position == itemCount - 1) ItemViewType.ITEM_FOOTER else ItemViewType.ITEM_BODY + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + return when (viewType) { + ItemViewType.ITEM_HEADER -> { + GameCollectionAmwayViewHolder( + GameCollectionSquareAmwayItemBinding.bind( + mLayoutInflater.inflate( + R.layout.game_collection_square_amway_item, + parent, + false + ) + ) + ) + } + + ItemViewType.ITEM_BODY -> { + GameCollectionSquareItemViewHolder( + GameCollectionSquareItemBinding.bind( + mLayoutInflater.inflate( + R.layout.game_collection_square_item, + parent, + false + ) + ) + ) + } + + ItemViewType.ITEM_FOOTER -> FooterViewHolder( + mLayoutInflater.inflate( + R.layout.refresh_footerview, + parent, + false + ) + ) + + else -> GameCollectionSquareItemViewHolder( + GameCollectionSquareItemBinding.bind( + mLayoutInflater.inflate( + R.layout.game_collection_square_item, + parent, + false + ) + ) + ) + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is GameCollectionAmwayViewHolder -> { + holder.bindAmway(mAmwayList) + } + is GameCollectionSquareItemViewHolder -> { + val realPosition = if (isHome) position - 1 else position + val itemData = mEntityList[realPosition] + val filterTagName = mViewModel.selectedTagName + val exposureEventList = arrayListOf() + itemData.gameCollectionItem?.games?.get(0)?.let { + exposureEventList.add( + ExposureEvent.createEventWithSourceConcat( + it.toGameEntity().apply { + outerSequence = mOuterSequence; sequence = + itemData.gameStartPosition + 1 + }, + mBasicExposureSource, + listOf( + ExposureSource( + "游戏单", + if (isHome) "${itemData.gameCollectionItem?.title} + ${itemData.gameCollectionItem?.id}" else "${itemData.gameCollectionItem?.title} + ${itemData.gameCollectionItem?.id} + $filterTagName" + ) + ) + ) + ) + } + itemData.gameCollectionItem?.games?.get(1)?.let { + exposureEventList.add(ExposureEvent.createEventWithSourceConcat(it.toGameEntity().apply { outerSequence = mOuterSequence; sequence = itemData.gameStartPosition + 2 }, mBasicExposureSource, listOf(ExposureSource("游戏单", if (isHome) "${itemData.gameCollectionItem?.title} + ${itemData.gameCollectionItem?.id}" else "${itemData.gameCollectionItem?.title} + ${itemData.gameCollectionItem?.id} + $filterTagName")))) + } + itemData.gameCollectionItem?.games?.get(2)?.let { + exposureEventList.add(ExposureEvent.createEventWithSourceConcat(it.toGameEntity().apply { outerSequence = mOuterSequence; sequence = itemData.gameStartPosition + 3 }, mBasicExposureSource, listOf(ExposureSource("游戏单", if (isHome) "${itemData.gameCollectionItem?.title} + ${itemData.gameCollectionItem?.id}" else "${itemData.gameCollectionItem?.title} + ${itemData.gameCollectionItem?.id} + $filterTagName")))) + } + itemData.exposureEventList = exposureEventList + itemData.gameCollectionItem?.let { + holder.bindGameCollection(mViewModel, it, itemData) + } + } + is FooterViewHolder -> { + holder.initItemPadding() + holder.initFooterViewHolder(mViewModel, mIsLoading, mIsNetworkError, mIsOver) + holder.hint.setTextColor(ContextCompat.getColor(mContext, R.color.text_B3B3B3)) + val lp = holder.itemView.layoutParams as RecyclerView.LayoutParams + lp.height = DisplayUtils.dip2px(48F) + lp.width = ViewGroup.LayoutParams.MATCH_PARENT + holder.itemView.layoutParams = lp + } + } + } + + override fun getItemCount() = if (mEntityList.isNullOrEmpty()) 0 else if (isHome) mEntityList.size + 2 else mEntityList.size + 1 + + class GameCollectionSquareItemViewHolder(var binding: GameCollectionSquareItemBinding) : + RecyclerView.ViewHolder(binding.root) { + @SuppressLint("SetTextI18n") + fun bindGameCollection(viewModel: GameCollectionSquareViewModel, gamesCollectionEntity: GamesCollectionEntity, itemData: GameCollectionListItemData) { + binding.run { + val context = root.context + entity = gamesCollectionEntity + stampIv.setImageDrawable(if (gamesCollectionEntity.stamp == "official") R.drawable.ic_official.toDrawable() else R.drawable.ic_chosen.toDrawable()) + tagContainer.removeAllViews() + if (!gamesCollectionEntity.tags.isNullOrEmpty()) { + for ((index, tagEntity) in gamesCollectionEntity.tags!!.withIndex()) { + // 添加分隔线 + if (index != 0) tagContainer.addView(View(context).apply { + layoutParams = ViewGroup.LayoutParams(1F.dip2px(), 14F.dip2px()) + setBackgroundColor(Color.parseColor("#33FFFFFF")) + }) + // 添加标签 + tagContainer.addView(TextView(context).apply { + layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT) + setPadding(if (index == 0) 0 else 6F.dip2px(), 0, 6F.dip2px(), 0) + text = tagEntity.name + setTextColor(R.color.white.toColor()) + textSize = 10F + }) + } + } + tagContainer.goneIf(CheckLoginUtils.isLogin()) + playedGamesContainer.goneIf(!CheckLoginUtils.isLogin()) + iconIvOne.setOnClickListener { + val game = gamesCollectionEntity.games?.get(0) + NewLogUtils.logClickGameCollectionGameIcon( + gamesCollectionEntity.title, + gamesCollectionEntity.id, + game?.name ?: "", + game?.id ?: "" + ) + game?.id?.let { id -> + GameDetailActivity.startGameDetailActivity(context, id, BaseActivity.mergeEntranceAndPath(viewModel.entrance, "游戏单广场"), itemData.exposureEventList?.safelyGetInRelease(0)) + } + } + iconIvTwo.setOnClickListener { + val game = gamesCollectionEntity.games?.get(1) + NewLogUtils.logClickGameCollectionGameIcon( + gamesCollectionEntity.title, + gamesCollectionEntity.id, + game?.name ?: "", + game?.id ?: "" + ) + game?.id?.let { id -> + GameDetailActivity.startGameDetailActivity(context, id, BaseActivity.mergeEntranceAndPath(viewModel.entrance, "游戏单广场"), itemData.exposureEventList?.safelyGetInRelease(1)) + } + } + iconIvThree.setOnClickListener { + val game = gamesCollectionEntity.games?.get(2) + NewLogUtils.logClickGameCollectionGameIcon( + gamesCollectionEntity.title, + gamesCollectionEntity.id, + game?.name ?: "", + game?.id ?: "" + ) + game?.id?.let { id -> + GameDetailActivity.startGameDetailActivity(context, id, BaseActivity.mergeEntranceAndPath(viewModel.entrance, "游戏单广场"), itemData.exposureEventList?.safelyGetInRelease(2)) + } + } + userContainer.setOnClickListener { + NewLogUtils.logClickGameCollectionAuthor(gamesCollectionEntity.title, gamesCollectionEntity.id) + DirectUtils.directToHomeActivity(context, gamesCollectionEntity.user?.id, 0, viewModel.entrance, "游戏单广场") + } + root.setOnClickListener { + NewLogUtils.logEnterGameCollectionDetail(gamesCollectionEntity.title, gamesCollectionEntity.id) + context.startActivity(GameCollectionDetailActivity.getIntent(context, gamesCollectionEntity.id, true)) + } + } + } + } + + companion object { + const val INVALID_VALUE = -1 + } + + override fun getEventByPosition(pos: Int): ExposureEvent? { + return mEntityList[pos].exposureEvent + } + + override fun getEventListByPosition(pos: Int): List? { + return mEntityList[pos].exposureEventList + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/square/GameCollectionSquareFragment.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/square/GameCollectionSquareFragment.kt new file mode 100644 index 0000000000..726fbae2e9 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/square/GameCollectionSquareFragment.kt @@ -0,0 +1,259 @@ +package com.gh.gamecenter.gamecollection.square + +import android.annotation.SuppressLint +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.view.View +import android.view.ViewGroup +import androidx.core.view.ViewCompat +import com.gh.common.exposure.ExposureListener +import com.gh.common.exposure.ExposureSource +import com.gh.common.util.* +import com.gh.common.view.VerticalItemDecoration +import com.gh.gamecenter.R +import com.gh.gamecenter.baselist.LazyListFragment +import com.gh.gamecenter.baselist.ListAdapter +import com.gh.gamecenter.baselist.LoadType +import com.gh.gamecenter.databinding.FragmentGameCollectionSquareAlBinding +import com.gh.gamecenter.databinding.FragmentGameCollectionSquareBinding +import com.gh.gamecenter.entity.GamesCollectionEntity +import com.gh.gamecenter.entity.TagInfoEntity +import com.gh.gamecenter.eventbus.EBReuse +import com.gh.gamecenter.gamecollection.publish.GameCollectionEditActivity +import com.gh.gamecenter.gamecollection.tag.GameCollectionTagSelectActivity +import com.gh.gamecenter.gamecollection.tag.GameCollectionTagSelectFragment +import com.gh.gamecenter.personal.PersonalFragment +import com.google.android.material.appbar.AppBarLayout +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode +import kotlin.math.abs + +class GameCollectionSquareFragment : LazyListFragment() { + private lateinit var mViewModel: GameCollectionSquareViewModel + private lateinit var mExposureListener: ExposureListener + + private lateinit var mDefaultBinding: FragmentGameCollectionSquareBinding + private lateinit var mAlternativeBinding: FragmentGameCollectionSquareAlBinding + + private var mAdapter: GameCollectionSquareAdapter? = null + + private var mUseAlternativeLayout = false + private var mForumName = "" + private var mGameCollectionTitle = "" + private var mGameCollectionId = "" + + override fun onCreate(savedInstanceState: Bundle?) { + mUseAlternativeLayout = arguments?.getBoolean(EntranceUtils.KEY_IS_HOME, false) ?: false + mForumName = arguments?.getString(EntranceUtils.KEY_FORUM_NAME, "") ?: "" + mGameCollectionTitle = arguments?.getString(EntranceUtils.KEY_GAME_COLLECTION_TITLE, "") ?: "" + mGameCollectionId = arguments?.getString(EntranceUtils.KEY_GAME_COLLECTION_ID, "") ?: "" + super.onCreate(savedInstanceState) + } + + override fun getLayoutId() = R.layout.fragment_stub + + override fun getRealLayoutId(): Int { + return if (mUseAlternativeLayout) R.layout.fragment_game_collection_square_al else R.layout.fragment_game_collection_square + } + + override fun onFragmentFirstVisible() { + super.onFragmentFirstVisible() + mViewModel.entrance = mEntrance + mViewModel.isHome = mUseAlternativeLayout + mListViewModel.load(LoadType.NORMAL) + + NewLogUtils.logEnterGameCollectionSquare(mEntrance, mForumName, mGameCollectionTitle, mGameCollectionId) + } + + override fun isAutomaticLoad() = false + + override fun onRealLayoutInflated(inflatedView: View) { + super.onRealLayoutInflated(inflatedView) + if (mUseAlternativeLayout) { + mAlternativeBinding = FragmentGameCollectionSquareAlBinding.bind(inflatedView) + } else { + mDefaultBinding = FragmentGameCollectionSquareBinding.bind(inflatedView) + } + } + + override fun initRealView() { + super.initRealView() + + mExposureListener = ExposureListener(this, mAdapter!!) + mListRv.addOnScrollListener(mExposureListener) + + if (!mUseAlternativeLayout) { + initDefaultLayout() + } else { + initAlternativeLayout() + } + } + + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult(requestCode, resultCode, data) + if (data == null || resultCode != Activity.RESULT_OK) return + if (requestCode == REQUEST_SELECT_TAG) { + val tagInfoEntity = data.getParcelableExtra(GameCollectionTagSelectFragment.SELECTED_TAG) + mDefaultBinding.tagTv.text = tagInfoEntity?.name ?: "标签筛选" + mViewModel.selectedTagEntity = tagInfoEntity + mViewModel.selectedTagId = tagInfoEntity?.id ?: "" + mViewModel.selectedTagName = tagInfoEntity?.name ?: "全部标签" + onLoadRefresh() + } + } + + override fun provideListViewModel(): GameCollectionSquareViewModel { + mViewModel = viewModelProvider() + return mViewModel + } + + override fun getItemDecoration() = VerticalItemDecoration(context, 16F, false, R.color.white) + + override fun provideListAdapter(): ListAdapter<*> { + if (mAdapter == null) { + val outerSequence = arguments?.getInt(EntranceUtils.KEY_TAB_INDEX) + val tabName = arguments?.getString(EntranceUtils.KEY_NAME) ?: "" + val basicExposureSource = arrayListOf().apply { + add(ExposureSource(if (mUseAlternativeLayout) "顶部tab" else "游戏单广场", if (mUseAlternativeLayout) tabName else "")) + } + mAdapter = GameCollectionSquareAdapter(requireContext(), mUseAlternativeLayout, mViewModel, basicExposureSource, outerSequence?:(-1)) + } + return mAdapter!! + } + + private fun initDefaultLayout() { + // toolbar 消费 fitsSystemWindows 避免在 collapsingToolbar 下面出现多出来的 padding + // [https://stackoverflow.com/questions/48137666/viewgroup-inside-collapsingtoolbarlayout-show-extra-bottom-padding-when-set-fits] + ViewCompat.setOnApplyWindowInsetsListener(mDefaultBinding.appbar) { _, insets -> + (mDefaultBinding.toolbar.layoutParams as ViewGroup.MarginLayoutParams).topMargin = insets.systemWindowInsetTop + insets.consumeSystemWindowInsets() + } + + val collapsingTrigger = 66F.dip2px() + DisplayUtils.getStatusBarHeight(context?.resources) + + mDefaultBinding.toolbar.setNavigationOnClickListener { requireActivity().finish() } + + mDefaultBinding.collapsingToolbar.scrimVisibleHeightTrigger = collapsingTrigger + mDefaultBinding.collapsingToolbar.scrimShownAction = { + DisplayUtils.setLightStatusBar(requireActivity(), it) + if (it) { + mDefaultBinding.titleTv.alpha = 1F + mDefaultBinding.titleTv.visibility = View.VISIBLE + mDefaultBinding.titleTv.setTextColor(R.color.black.toColor()) + mDefaultBinding.toolbar.navigationIcon = R.drawable.ic_bar_back.toDrawable() + } else { + mDefaultBinding.titleTv.visibility = View.GONE + mDefaultBinding.toolbar.navigationIcon = R.drawable.ic_bar_back_light.toDrawable() + } + } + + mDefaultBinding.titleTv.setOnClickListener { + if (ClickUtils.isFastDoubleClick(mDefaultBinding.titleTv.id, 300)) { + scrollToTop() + } + } + + mDefaultBinding.appbar.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { _, verticalOffset -> + val absOffset = abs(verticalOffset) + val invisibleOffset = DisplayUtils.dip2px(30F) + + if (absOffset <= invisibleOffset) { + mDefaultBinding.titleTv.alpha = 1 - (absOffset.toFloat() / invisibleOffset) + } else { + mDefaultBinding.titleTv.alpha = 0F + } + + mListRefresh?.isEnabled = absOffset <= 2 + }) + + mDefaultBinding.orderRg.setOnCheckedChangeListener { _, checkedId -> + mViewModel.view = if (checkedId == R.id.hotRb) "hot" else "new" + onLoadRefresh() + } + + mDefaultBinding.fab.setOnClickListener { + // 创建游戏单 + ifLogin(mEntrance) { + showRegulationTestDialogIfNeeded { + startActivity(GameCollectionEditActivity.getIntent(requireContext(), mEntrance, "游戏单广场")) + } + } + } + + mDefaultBinding.tagFilter.setOnClickListener { + startActivityForResult(GameCollectionTagSelectActivity.getIntent(requireContext(), true, mViewModel.selectedTagEntity), REQUEST_SELECT_TAG) + } + + mListRefresh?.setProgressViewOffset(false, 0, 118F.dip2px() + DisplayUtils.getStatusBarHeight(requireContext().resources)) +// mSkeletonScreen = Skeleton.bind(mDefaultBinding?.skeletonPlaceholder).shimmer(false).load(R.layout.fragment_amway_skeleton).show() + } + + private fun initAlternativeLayout() { + mAlternativeBinding.fab.setOnClickListener { + // 创建游戏单 + ifLogin(mEntrance) { + showRegulationTestDialogIfNeeded { + startActivity(GameCollectionEditActivity.getIntent(requireContext(), mEntrance, "游戏单广场")) + } + } + } + + mViewModel.mAmwayCommentList.observeNonNull(this) { + mAdapter?.setAmwayList(it) + } + +// mSkeletonScreen = Skeleton.bind(mAlternativeBinding?.skeletonPlaceholder).shimmer(false).load( +// R.layout.fragment_amway_skeleton_al).show() + } + + @SuppressLint("NotifyDataSetChanged") + override fun onResume() { + if (isEverPause) mAdapter?.notifyDataSetChanged() + super.onResume() + } + + override fun onLoadEmpty() { + super.onLoadEmpty() + if (!mUseAlternativeLayout) mDefaultBinding.tagFilterContainer.visibility = View.VISIBLE + } + + override fun onLoadDone() { + super.onLoadDone() + if (!mUseAlternativeLayout) mDefaultBinding.tagFilterContainer.visibility = View.VISIBLE + } + + override fun onLoadError() { + super.onLoadError() + if (!mUseAlternativeLayout) mDefaultBinding.tagFilterContainer.visibility = View.GONE + } + + override fun onLoadRefresh() { + super.onLoadRefresh() + if (!mUseAlternativeLayout) mDefaultBinding.tagFilterContainer.visibility = View.VISIBLE + mViewModel.getAmwayCommentList() + } + + private fun scrollToTop() { + val firstItemPosition = mLayoutManager.findFirstVisibleItemPosition() + if (firstItemPosition >= 10) { + mListRv.scrollToPosition(6) + } + mListRv.smoothScrollToPosition(0) + mDefaultBinding.appbar.setExpanded(true) + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onEventMainThread(reuse: EBReuse) { + if ("Refresh" == reuse.type) { + mAdapter?.notifyDataSetChanged() + } else if (reuse.type == PersonalFragment.LOGIN_TAG) { // 登入 + scrollToTop() + onRefresh() + } + } + + companion object { + const val REQUEST_SELECT_TAG = 100 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/square/GameCollectionSquareViewModel.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/square/GameCollectionSquareViewModel.kt new file mode 100644 index 0000000000..b8b5afd501 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/square/GameCollectionSquareViewModel.kt @@ -0,0 +1,76 @@ +package com.gh.gamecenter.gamecollection.square + +import android.annotation.SuppressLint +import android.app.Application +import androidx.lifecycle.MutableLiveData +import com.gh.gamecenter.baselist.ListViewModel +import com.gh.gamecenter.baselist.LoadParams +import com.gh.gamecenter.baselist.LoadStatus +import com.gh.gamecenter.entity.AmwayCommentEntity +import com.gh.gamecenter.entity.GamesCollectionEntity +import com.gh.gamecenter.entity.TagInfoEntity +import com.gh.gamecenter.retrofit.BiResponse +import com.gh.gamecenter.retrofit.RetrofitManager +import io.reactivex.Observable +import io.reactivex.Single +import io.reactivex.schedulers.Schedulers +import java.util.* + +class GameCollectionSquareViewModel(application: Application) : + ListViewModel(application) { + + var entrance: String? = null + var selectedTagEntity: TagInfoEntity? = null + var selectedTagId = "" + var selectedTagName = "全部标签" + var view = "hot" + var isHome = false + + val mAmwayCommentList = MutableLiveData>() + + init { + getAmwayCommentList() + } + + override fun initLoadParams() { + mCurLoadParams = LoadParams(PAGE_SIZE, LoadParams.DEFAULT_OFFSET) + mLoadStatusLiveData.value = LoadStatus.INIT + } + + override fun provideDataSingle(page: Int): Single> = + if (isHome) RetrofitManager.getInstance(getApplication()) + .api.getHomeGameCollectionSquareList(UUID.randomUUID().toString(), page, PAGE_SIZE) + else RetrofitManager.getInstance(getApplication()) + .api.getGameCollectionSquareList(view, selectedTagId, page, PAGE_SIZE) + + override fun mergeResultLiveData() { + mResultLiveData.addSource(mListLiveData) { list -> + val itemDataList = arrayListOf().apply { + var position = 0 + for (item in list) { + add(GameCollectionListItemData(gameCollectionItem = item, gameStartPosition = position)) + position += if (item.count?.game!! > 2) 3 else item.count?.game ?: 0 + } + } + mResultLiveData.postValue(itemDataList) + } + } + + override fun provideDataObservable(page: Int): Observable>? = null + + @SuppressLint("CheckResult") + fun getAmwayCommentList() { + RetrofitManager.getInstance(getApplication()).api + .getAmwayCommentList(1, 10) + .subscribeOn(Schedulers.io()) + .subscribe(object : BiResponse>() { + override fun onSuccess(data: List) { + mAmwayCommentList.postValue(data) + } + }) + } + + companion object { + const val PAGE_SIZE = 15 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/tag/GameCollectionTagAdapter.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/tag/GameCollectionTagAdapter.kt new file mode 100644 index 0000000000..e65bb188a0 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/tag/GameCollectionTagAdapter.kt @@ -0,0 +1,145 @@ +package com.gh.gamecenter.gamecollection.tag + +import android.content.Context +import android.view.View +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.gh.common.util.DisplayUtils +import com.gh.common.util.dip2px +import com.gh.common.util.goneIf +import com.gh.gamecenter.databinding.* +import com.gh.gamecenter.entity.GameCollectionTagEntity +import com.gh.gamecenter.entity.TagInfoEntity +import com.lightgame.adapter.BaseRecyclerAdapter + +class GameCollectionTagAdapter( + context: Context, + val singleChoice: Boolean = true, + val singleSelectedTag: TagInfoEntity? = null, + private val updateCallback: (() -> Unit) +) : + BaseRecyclerAdapter(context) { + + private var mTagViewList = arrayListOf() + var mTagList = arrayListOf() + var selectedTagEntity: TagInfoEntity? = null + var selectedTagCategory = "" + var selectedTagEntityList = arrayListOf() + private var flagFirst = true + + fun setTagList(tagList: ArrayList) { + mTagList = tagList + notifyDataSetChanged() + } + + override fun getItemViewType(position: Int): Int { + return if (!singleChoice && position == 0) SELECTED_TAGS else TAG_ITEM + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = when (viewType){ + SELECTED_TAGS -> GameCollectionSelectedTagViewHolder( + GameCollectionSelectedTagItemBinding.inflate(mLayoutInflater, parent, false) + ) + else -> GameCollectionTagItemViewHolder( + GameCollectionTagItemBinding.inflate(mLayoutInflater, parent, false) + ) + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + when (holder) { + is GameCollectionSelectedTagViewHolder -> { + holder.binding.selectedTagFlexbox.removeAllViews() + holder.binding.hintTv.goneIf(selectedTagEntityList.size != 0) + for (tag in selectedTagEntityList) { + val selectedTagView = + ItemGameCollectionSelectedTagBinding.inflate(mLayoutInflater) + .apply { + tagTv.text = tag.name + root.setOnClickListener { + selectedTagEntityList.remove(tag) + updateCallback.invoke() + notifyDataSetChanged() + } + }.root + holder.binding.selectedTagFlexbox.addView(selectedTagView) + } + } + is GameCollectionTagItemViewHolder -> { + val data = mTagList[if (singleChoice) position else position - 1] + holder.binding.tagFlexbox.removeAllViews() + holder.binding.titleTv.text = data.categoryName + holder.binding.titleTv.setPadding( + 16F.dip2px(), + if (position == 0) 40F.dip2px() else 10F.dip2px(), + 16F.dip2px(), + 10F.dip2px() + ) + holder.binding.tagFlexbox.setPadding( + 12F.dip2px(), + 5F.dip2px(), + 12F.dip2px(), + if (position == itemCount - 1) 97F.dip2px() else 15F.dip2px() + ) + for (tagEntity in data.tags) { + val tag = if (singleChoice) getSingleTag(tagEntity, data.categoryName) else getMultipleTag(tagEntity) + mTagViewList.add(tag) + holder.binding.tagFlexbox.addView(tag.root) + } + } + } + } + + private fun getSingleTag(tag: TagInfoEntity, tagCategory: String) = ItemGameCollectionTagBinding.inflate(mLayoutInflater).apply { + tagTv.text = tag.name + tagTv.layoutParams = tagTv.layoutParams.apply { width = (DisplayUtils.getScreenWidth() - 56F.dip2px()) / 4 } + singleSelectedTag?.let { + if (flagFirst && tag == it) { + tagTv.isChecked = true + selectedTagEntity = tag + } + } + root.setOnClickListener { + for (tagView in mTagViewList) { + if (tagView.tagTv != tagTv) tagView.tagTv.isChecked = false + } + tagTv.isChecked = !tagTv.isChecked + if (tagTv.isChecked) { + selectedTagEntity = tag + selectedTagCategory = tagCategory + } else { + selectedTagEntity = null + selectedTagCategory = "" + } + } + } + + private fun getMultipleTag(tag: TagInfoEntity) = + ItemGameCollectionTagBinding.inflate(mLayoutInflater).apply { + tagTv.layoutParams = tagTv.layoutParams.apply { width = (DisplayUtils.getScreenWidth() - 56F.dip2px()) / 4 } + tagTv.text = tag.name + if (selectedTagEntityList.contains(tag)) tagTv.isChecked = true + root.setOnClickListener { + tagTv.isChecked = !tagTv.isChecked + if (tagTv.isChecked) { + selectedTagEntityList.add(tag) + } else { + if (selectedTagEntityList.contains(tag)) selectedTagEntityList.remove(tag) + } + updateCallback.invoke() + notifyItemChanged(0) + } + } + + override fun getItemCount() = if (singleChoice) mTagList.size else mTagList.size + 1 + + class GameCollectionSelectedTagViewHolder(var binding: GameCollectionSelectedTagItemBinding) : + RecyclerView.ViewHolder(binding.root) + + class GameCollectionTagItemViewHolder(var binding: GameCollectionTagItemBinding) : + RecyclerView.ViewHolder(binding.root) + + companion object { + const val SELECTED_TAGS = 100 + const val TAG_ITEM = 101 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/tag/GameCollectionTagSelectActivity.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/tag/GameCollectionTagSelectActivity.kt new file mode 100644 index 0000000000..10b184eeb1 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/tag/GameCollectionTagSelectActivity.kt @@ -0,0 +1,35 @@ +package com.gh.gamecenter.gamecollection.tag + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import com.gh.gamecenter.NormalActivity +import com.gh.gamecenter.R +import com.gh.gamecenter.entity.TagInfoEntity + +class GameCollectionTagSelectActivity : NormalActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setNavigationTitle("选择标签") + setToolbarMenu(R.menu.menu_save) + } + + companion object { + const val KEY_IS_SINGLE_CHOICE = "single_choice" + const val KEY_SINGLE_SELECTED_TAG = "single_selected_tag" + + @JvmStatic + fun getIntent(context: Context, singleChoice: Boolean = false, singleSelectedTag: TagInfoEntity? = null): Intent { + val bundle = Bundle() + bundle.putBoolean(KEY_IS_SINGLE_CHOICE, singleChoice) + bundle.putParcelable(KEY_SINGLE_SELECTED_TAG, singleSelectedTag) + return getTargetIntent( + context, + GameCollectionTagSelectActivity::class.java, + GameCollectionTagSelectFragment::class.java, + bundle + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/tag/GameCollectionTagSelectFragment.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/tag/GameCollectionTagSelectFragment.kt new file mode 100644 index 0000000000..1d6c2eb5ab --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/tag/GameCollectionTagSelectFragment.kt @@ -0,0 +1,102 @@ +package com.gh.gamecenter.gamecollection.tag + +import android.app.Activity +import android.content.Intent +import android.os.Bundle +import android.view.MenuItem +import android.view.View +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.gh.common.util.NewLogUtils +import com.gh.common.util.viewModelProvider +import com.gh.gamecenter.R +import com.gh.gamecenter.databinding.FragmentGameCollectionTagSelectBinding +import com.gh.gamecenter.databinding.ItemGameCollectionSelectedTagBinding +import com.gh.gamecenter.entity.TagInfoEntity +import com.gh.gamecenter.normal.NormalFragment + +class GameCollectionTagSelectFragment : NormalFragment() { + + private var mSingleChoice = false + private var mSelectedTag: TagInfoEntity? = null + private lateinit var mBinding: FragmentGameCollectionTagSelectBinding + private lateinit var mAdapter: GameCollectionTagAdapter + private lateinit var mViewModel: GameCollectionTagViewModel + private var firstVisibleItemPosition = 0 + + private val updateSelectedTagView: (() -> Unit) = { + mAdapter.selectedTagEntityList.let { list -> + mBinding.selectedTagContainer.removeAllViews() + for (tag in list) { + val selectedTagView = ItemGameCollectionSelectedTagBinding.inflate(layoutInflater) + selectedTagView.run { + tagTv.text = tag.name + root.setOnClickListener { + list.remove(tag) + mBinding.selectedTagContainer.removeView(selectedTagView.root) + mAdapter.notifyDataSetChanged() + mBinding.selectedTagScrollView.visibility = if (firstVisibleItemPosition == 0 || list.isEmpty()) View.GONE else View.VISIBLE + } + } + mBinding.selectedTagContainer.addView(selectedTagView.root) + } + mBinding.selectedTagScrollView.visibility = if (firstVisibleItemPosition == 0 || list.isEmpty()) View.GONE else View.VISIBLE + } + } + + override fun getLayoutId(): Int = 0 + + override fun getInflatedLayout() = + FragmentGameCollectionTagSelectBinding.inflate(layoutInflater).apply { + mBinding = this + }.root + + override fun onMenuItemClick(menuItem: MenuItem?) { + if (menuItem?.itemId == R.id.layout_menu_save) { + if (mSingleChoice) { + if (mAdapter.selectedTagEntity != null) { + NewLogUtils.logFilterGameCollectionTag(mAdapter.selectedTagCategory, mAdapter.selectedTagEntity!!.name) + } + requireActivity().setResult( + Activity.RESULT_OK, + Intent().putExtra(SELECTED_TAG, mAdapter.selectedTagEntity) + ) + } else { + requireActivity().setResult( + Activity.RESULT_OK, + Intent().putExtra(SELECTED_TAG, mAdapter.selectedTagEntityList) + ) + } + requireActivity().finish() + } + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + mSingleChoice = arguments?.getBoolean(GameCollectionTagSelectActivity.KEY_IS_SINGLE_CHOICE) ?: false + mSelectedTag = arguments?.getParcelable(GameCollectionTagSelectActivity.KEY_SINGLE_SELECTED_TAG) + mAdapter = GameCollectionTagAdapter(requireContext(), mSingleChoice, mSelectedTag, updateSelectedTagView) + mViewModel = viewModelProvider() + if (mSingleChoice) NewLogUtils.logEnterGameCollectionTag() + mBinding.tagRv.run { + layoutManager = LinearLayoutManager(requireContext()) + adapter = mAdapter + addOnScrollListener(object :RecyclerView.OnScrollListener() { + + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + firstVisibleItemPosition = (layoutManager as LinearLayoutManager).findFirstVisibleItemPosition() + mBinding.selectedTagScrollView.visibility = if (firstVisibleItemPosition == 0 || mAdapter.selectedTagEntityList.isEmpty()) View.GONE else View.VISIBLE + } + }) + } + + mViewModel.tagListLiveData.observe(viewLifecycleOwner) { + mAdapter.setTagList(it) + } + } + + companion object { + const val SELECTED_TAG = "selected_tag" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/gamecollection/tag/GameCollectionTagViewModel.kt b/app/src/main/java/com/gh/gamecenter/gamecollection/tag/GameCollectionTagViewModel.kt new file mode 100644 index 0000000000..f4c26d2804 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/gamecollection/tag/GameCollectionTagViewModel.kt @@ -0,0 +1,33 @@ +package com.gh.gamecenter.gamecollection.tag + +import android.annotation.SuppressLint +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.MutableLiveData +import com.gh.gamecenter.entity.GameCollectionTagEntity +import com.gh.gamecenter.retrofit.BiResponse +import com.gh.gamecenter.retrofit.RetrofitManager +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers + +class GameCollectionTagViewModel(application: Application) : AndroidViewModel(application) { + + var tagListLiveData = MutableLiveData>() + private val mApi = RetrofitManager.getInstance(getApplication()).api + + init { + getGameCollectionTagList() + } + + @SuppressLint("CheckResult") + fun getGameCollectionTagList() { + mApi.gameCollectionTagList + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object: BiResponse>(){ + override fun onSuccess(data: ArrayList) { + tagListLiveData.postValue(data) + } + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/history/HistoryWrapperFragment.kt b/app/src/main/java/com/gh/gamecenter/history/HistoryWrapperFragment.kt index 85f5205222..971dcfc6e5 100644 --- a/app/src/main/java/com/gh/gamecenter/history/HistoryWrapperFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/history/HistoryWrapperFragment.kt @@ -6,15 +6,16 @@ import com.gh.base.fragment.BaseFragment_TabLayout import com.gh.common.util.EntranceUtils import com.gh.common.util.MtaHelper import com.gh.gamecenter.R -import com.gh.gamecenter.collection.AnswerFragment -import com.gh.gamecenter.collection.ArticleFragment -import com.gh.gamecenter.collection.CommunityArticleFragment -import com.gh.gamecenter.collection.VideoFragment +import com.gh.gamecenter.collection.* +import com.gh.gamecenter.manager.UserManager class HistoryWrapperFragment : BaseFragment_TabLayout() { + override fun getLayoutId() = R.layout.fragment_no_padding_tablayout_viewpager + override fun initTabTitleList(tabTitleList: MutableList) { tabTitleList.add(getString(R.string.main_game)) + tabTitleList.add(getString(R.string.game_collection)) tabTitleList.add(getString(R.string.video)) tabTitleList.add(getString(R.string.answer)) tabTitleList.add(getString(R.string.collection_article)) @@ -23,6 +24,10 @@ class HistoryWrapperFragment : BaseFragment_TabLayout() { override fun initFragmentList(fragments: MutableList) { fragments.add(HistoryGameListFragment().with(arguments)) + fragments.add(GamesCollectionFragment().with(arguments?.apply { + putString(EntranceUtils.KEY_USER_ID, UserManager.getInstance().userId) + putString(EntranceUtils.KEY_TYPE, GamesCollectionFragment.TYPE_HISTORY) + })) fragments.add(VideoFragment().with(arguments?.apply { putString("videoStyle", VideoFragment.VideoStyle.BROWSING_HISTORY.value) })) diff --git a/app/src/main/java/com/gh/gamecenter/home/HomeFragmentAdapter.kt b/app/src/main/java/com/gh/gamecenter/home/HomeFragmentAdapter.kt index b3cc9e8de1..b4d6113e2f 100644 --- a/app/src/main/java/com/gh/gamecenter/home/HomeFragmentAdapter.kt +++ b/app/src/main/java/com/gh/gamecenter/home/HomeFragmentAdapter.kt @@ -21,10 +21,13 @@ import com.gh.gamecenter.baselist.DiffUtilAdapter import com.gh.gamecenter.baselist.LoadStatus import com.gh.gamecenter.databinding.* import com.gh.gamecenter.entity.AmwayCommentEntity +import com.gh.gamecenter.entity.GamesCollectionEntity import com.gh.gamecenter.eventbus.EBDownloadStatus import com.gh.gamecenter.game.GameAndPosition import com.gh.gamecenter.game.rank.RankCollectionAdapter import com.gh.gamecenter.game.vertical.GameVerticalAdapter +import com.gh.gamecenter.gamecollection.square.GameCollectionListItemData +import com.gh.gamecenter.home.gamecollection.HomeGameCollectionViewHolder import com.gh.gamecenter.gamedetail.rating.RatingReplyActivity import com.gh.gamecenter.home.amway.HomeAmwayListViewHolder import com.gh.gamecenter.home.slide.HomeSlideListAdapter @@ -32,6 +35,7 @@ import com.gh.gamecenter.home.slide.HomeSlideListViewHolder import com.gh.gamecenter.video.detail.VideoDetailContainerViewModel import com.halo.assistant.fragment.game.GamePluginAdapter import com.lightgame.download.DownloadEntity +import com.lightgame.utils.Utils import com.shuyu.gsyvideoplayer.builder.GSYVideoOptionBuilder import com.shuyu.gsyvideoplayer.video.base.GSYVideoView import java.util.* @@ -96,6 +100,7 @@ class HomeFragmentAdapter( if (itemData.recommends != null) return RECOMMENDS_ITEM if (itemData.attachGame != null) return GAME_ITEM if (itemData.amway != null) return AMWAY_ITEM + if (itemData.gameCollection != null) return GAME_COLLECTION_ITEM if (itemData.lineDivider != null) return DIVIDER_ITEM if (itemData.unknownData != null) return UNKNOWN_ITEM @@ -121,6 +126,10 @@ class HomeFragmentAdapter( view = mLayoutInflater.inflate(R.layout.home_amway_list, parent, false) HomeAmwayListViewHolder(HomeAmwayListBinding.bind(view)) } + GAME_COLLECTION_ITEM -> { + view = mLayoutInflater.inflate(R.layout.home_game_collection_item, parent, false) + HomeGameCollectionViewHolder(HomeGameCollectionItemBinding.bind(view)) + } DIVIDER_ITEM -> { view = mLayoutInflater.inflate(R.layout.home_divider_item, parent, false) HomeDividerViewHolder(HomeDividerItemBinding.bind(view)) @@ -160,6 +169,7 @@ class HomeFragmentAdapter( is FooterViewHolder -> bindFooterView(holder) is ReuseViewHolder -> bindUnknown(holder) is HomeDividerViewHolder -> holder.bindView(mDataList[position].lineDivider ?: 1F) + is HomeGameCollectionViewHolder -> bindGameCollection(holder, position) else -> mLegacyHomeFragmentAdapterAssistant.bindLegacyViewHolder( holder, @@ -222,6 +232,53 @@ class HomeFragmentAdapter( holder.bindAmwayList(amwayList, clickClosure) } + private fun bindGameCollection(holder: HomeGameCollectionViewHolder, position: Int) { + val homeItemData = mDataList[position] + val gameCollectionItemDataList = homeItemData.gameCollection?: listOf() + + val exposureList = arrayListOf() + for (gameCollectionItemData in gameCollectionItemDataList) { + runOnIoThread(true) { + val gameCollection = gameCollectionItemData.gameCollectionItem + val gameCollectionSource = listOf(ExposureSource("游戏单", "${gameCollection?.title} + ${gameCollection?.id}")) + val gameExposureList = arrayListOf() + gameCollection?.games?.get(0)?.let { + gameExposureList.add(ExposureEvent.createEventWithSourceConcat( + gameEntity = it.toGameEntity().apply { outerSequence = homeItemData.blockPosition; sequence = gameCollectionItemData.gameStartPosition + 1 }, + basicSource = mBasicExposureSource, + source = gameCollectionSource + )) + } + gameCollection?.games?.get(1)?.let { + gameExposureList.add(ExposureEvent.createEventWithSourceConcat( + gameEntity = it.toGameEntity().apply { outerSequence = homeItemData.blockPosition; sequence = gameCollectionItemData.gameStartPosition + 2 }, + basicSource = mBasicExposureSource, + source = gameCollectionSource + )) + } + gameCollection?.games?.get(2)?.let { + gameExposureList.add(ExposureEvent.createEventWithSourceConcat( + gameEntity = it.toGameEntity().apply { outerSequence = homeItemData.blockPosition; sequence = gameCollectionItemData.gameStartPosition + 3 }, + basicSource = mBasicExposureSource, + source = gameCollectionSource + )) + } + gameCollectionItemData.exposureEventList = gameExposureList + exposureList.addAll(gameExposureList) + } + } + homeItemData.exposureEventList = exposureList + holder.bindGameCollectionList(gameCollectionItemDataList, "首页内容列表") +// val testData = arrayListOf( +// GameCollectionListItemData(gameCollectionItem = GamesCollectionEntity(title = "1111"), gameStartPosition = 0), +// GameCollectionListItemData(gameCollectionItem = GamesCollectionEntity(title = "2222"), gameStartPosition = 0), +// GameCollectionListItemData(gameCollectionItem = GamesCollectionEntity(title = "3333"), gameStartPosition = 0), +// GameCollectionListItemData(gameCollectionItem = GamesCollectionEntity(title = "4444"), gameStartPosition = 0), +// GameCollectionListItemData(gameCollectionItem = GamesCollectionEntity(title = "55555"), gameStartPosition = 0) +// ) +// holder.bindGameCollectionList(testData, "首页内容列表") + } + private fun bindAttachGame(holder: HomeGameItemViewHolder, position: Int) { val homeItemData = mDataList[position] val game = homeItemData.attachGame?.linkGame!! @@ -460,5 +517,6 @@ class HomeFragmentAdapter( const val FOOTER_ITEM: Int = 110 const val UNKNOWN_ITEM: Int = 111 const val COMMON_ITEM: Int = 115 + const val GAME_COLLECTION_ITEM: Int = 116 } } \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/home/HomeItemData.kt b/app/src/main/java/com/gh/gamecenter/home/HomeItemData.kt index 96fa562399..6c35cae520 100644 --- a/app/src/main/java/com/gh/gamecenter/home/HomeItemData.kt +++ b/app/src/main/java/com/gh/gamecenter/home/HomeItemData.kt @@ -1,10 +1,12 @@ package com.gh.gamecenter.home import com.gh.gamecenter.entity.* +import com.gh.gamecenter.gamecollection.square.GameCollectionListItemData data class HomeItemData(var slides: List? = null, var recommends: List? = null, var amway: List? = null, + var gameCollection: List? = null, var attachGame: HomeContent? = null, var lineDivider: Float? = null, var unknownData: Any? = null) : LegacyHomeItemData() \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/home/HomeViewModel.kt b/app/src/main/java/com/gh/gamecenter/home/HomeViewModel.kt index d78bed91c5..84bb6980b4 100644 --- a/app/src/main/java/com/gh/gamecenter/home/HomeViewModel.kt +++ b/app/src/main/java/com/gh/gamecenter/home/HomeViewModel.kt @@ -13,6 +13,7 @@ import com.gh.download.DownloadManager import com.gh.gamecenter.BuildConfig import com.gh.gamecenter.baselist.LoadStatus import com.gh.gamecenter.entity.* +import com.gh.gamecenter.gamecollection.square.GameCollectionListItemData import com.gh.gamecenter.packagehelper.PackageRepository import com.gh.gamecenter.retrofit.BiResponse import com.gh.gamecenter.retrofit.Response @@ -23,7 +24,10 @@ import com.lightgame.utils.Utils import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers import retrofit2.HttpException +import java.io.BufferedReader +import java.io.InputStreamReader import java.util.* +import java.util.stream.Collectors import kotlin.collections.ArrayList import kotlin.collections.set @@ -398,6 +402,25 @@ class HomeViewModel(application: Application) : AndroidViewModel(application) { commonCollectionItem.commonCollection = subjectEntity commonCollectionItem.blockPosition = i mSnapshotItemList.add(commonCollectionItem) + } else if (linkType == "game_list_collection") { + val head = HomeItemData() + head.columnHead = SubjectEntity(type = linkType, name = "热门游戏单") + mSnapshotItemList.add(LegacyHomeSubjectTransformer.getBlankSpacingItem(HomeItemData()) as HomeItemData) + mSnapshotItemList.add(head) + + val gameCollection = HomeItemData() + gameCollection.blockPosition = i + val itemDataList = arrayListOf().apply { + if (!homeContent.linkGameCollection.isNullOrEmpty()) { + var position = 0 + for (item in homeContent.linkGameCollection) { + add(GameCollectionListItemData(gameCollectionItem = item, gameStartPosition = position)) + position += if (item.count?.game!! > 2) 3 else item.count?.game ?: 0 + } + } + } + gameCollection.gameCollection = itemDataList + mSnapshotItemList.add(gameCollection) } else { val unknown = HomeItemData() unknown.blockPosition = i + 1 diff --git a/app/src/main/java/com/gh/gamecenter/home/LegacyHomeFragmentAdapterAssistant.kt b/app/src/main/java/com/gh/gamecenter/home/LegacyHomeFragmentAdapterAssistant.kt index 0f5921e79a..ad31d4d7da 100644 --- a/app/src/main/java/com/gh/gamecenter/home/LegacyHomeFragmentAdapterAssistant.kt +++ b/app/src/main/java/com/gh/gamecenter/home/LegacyHomeFragmentAdapterAssistant.kt @@ -583,6 +583,8 @@ class LegacyHomeFragmentAdapterAssistant( ) ) NewLogUtils.logAccessToCommonCollectionDetail(column.id ?: "", column.name ?: "", "首页内容列表") + } else if (column.type == "game_list_collection") { + DirectUtils.directToGameCollectionSquare(mContext, "首页内容列表", column.name ?: "") } else { if (column.indexRightTopLink != null) { val link = column.indexRightTopLink!! diff --git a/app/src/main/java/com/gh/gamecenter/home/gamecollection/GameCollectionStackAnimation.kt b/app/src/main/java/com/gh/gamecenter/home/gamecollection/GameCollectionStackAnimation.kt new file mode 100644 index 0000000000..010e20c8b3 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/home/gamecollection/GameCollectionStackAnimation.kt @@ -0,0 +1,144 @@ +package com.gh.gamecenter.home.gamecollection + +import android.view.View +import com.gh.common.util.DisplayUtils +import com.gh.common.view.stacklayoutmanager.StackAnimation +import com.gh.common.view.stacklayoutmanager.StackLayoutManager +import com.gh.gamecenter.R +import com.halo.assistant.HaloApp +import com.lightgame.utils.Utils +import kotlin.math.pow + +class GameCollectionStackAnimation( + scrollOrientation: StackLayoutManager.ScrollOrientation, + visibleCount: Int +) : StackAnimation(scrollOrientation, visibleCount) { + private var mScale = 0.9F + private var mOutScale = 1.0F + private var mOutRotation = 0 + + /** + * 设置 item 缩放比例. + * @param scale 缩放比例 + */ + fun setItemScaleRate(scale: Float) { + mScale = scale + } + + /** + * 获取item缩放比例. + * @return item缩放比例 + */ + fun getItemScaleRate(): Float { + return mScale + } + + /** + * 设置 itemView 离开屏幕时候的缩放比例. + * @param scale 缩放比例 + */ + fun setOutScale(scale: Float) { + mOutScale = scale + } + + /** + * 获取 itemView 离开屏幕时候的缩放比例. + * @return 缩放比例 + */ + fun getOutScale(): Float { + return mOutScale + } + + /** + * 设置 itemView 离开屏幕时候的旋转角度. + * @param rotation 旋转角度 + */ + fun setOutRotation(rotation: Int) { + mOutRotation = rotation + } + + /** + * 获取 itemView 离开屏幕时候的旋转角度 + * @return 旋转角度 + */ + fun getOutRotation(): Int { + return mOutRotation + } + + override fun doAnimation(firstMovePercent: Float, itemView: View, position: Int) { + val cover = itemView.findViewById(R.id.cover) + val content = itemView.findViewById(R.id.content) + val scale: Float + var alpha = 1.0F + val rotation: Float + if (position == 0) { + // 顶层item透明度变化 + cover.alpha = 0F + content.alpha = 1F + if (firstMovePercent > 0.5) alpha = 1F - 2 * (firstMovePercent - 0.5F) + scale = 1 - ((1 - mOutScale) * firstMovePercent) + rotation = mOutRotation * firstMovePercent + } else { + val minScale = (mScale.toDouble().pow(position.toDouble())).toFloat() + val maxScale = (mScale.toDouble().pow((position - 1).toDouble())).toFloat() + scale = minScale + (maxScale - minScale) * firstMovePercent + when (position) { + 1 -> { + cover.alpha = 0.3F - 0.3F * firstMovePercent + content.alpha = firstMovePercent + } + 2 -> cover.alpha = 0.6F - 0.3F * firstMovePercent + 3 -> { + cover.alpha = 1F - 0.4F * firstMovePercent + // 如果不改变会出现残留边框 +// alpha = if (firstMovePercent == 0F) 0F else 1F + } + } + rotation = 0F + } + + setItemPivotXY(mScrollOrientation, itemView) + rotationFirstVisibleItem(mScrollOrientation, itemView, rotation) + itemView.scaleX = scale + itemView.scaleY = scale + itemView.alpha = alpha + } + + private fun setItemPivotXY( + scrollOrientation: StackLayoutManager.ScrollOrientation, + view: View + ) { + when (scrollOrientation) { + StackLayoutManager.ScrollOrientation.RIGHT_TO_LEFT -> { + view.pivotX = view.measuredWidth.toFloat() + view.pivotY = view.measuredHeight.toFloat() / 2 + } + StackLayoutManager.ScrollOrientation.LEFT_TO_RIGHT -> { + view.pivotX = 0F + view.pivotY = view.measuredHeight.toFloat() / 2 + } + StackLayoutManager.ScrollOrientation.BOTTOM_TO_TOP -> { + view.pivotX = view.measuredWidth.toFloat() / 2 + view.pivotY = view.measuredHeight.toFloat() + } + StackLayoutManager.ScrollOrientation.TOP_TO_BOTTOM -> { + view.pivotX = view.measuredWidth.toFloat() / 2 + view.pivotY = 0F + } + } + } + + private fun rotationFirstVisibleItem( + scrollOrientation: StackLayoutManager.ScrollOrientation, + view: View, + rotation: Float + ) { + when (scrollOrientation) { + StackLayoutManager.ScrollOrientation.RIGHT_TO_LEFT -> view.rotationY = rotation + StackLayoutManager.ScrollOrientation.LEFT_TO_RIGHT -> view.rotationY = -rotation + StackLayoutManager.ScrollOrientation.BOTTOM_TO_TOP -> view.rotationX = -rotation + StackLayoutManager.ScrollOrientation.TOP_TO_BOTTOM -> view.rotationX = rotation + } + } + +} diff --git a/app/src/main/java/com/gh/gamecenter/home/gamecollection/GameCollectionStackLayout.kt b/app/src/main/java/com/gh/gamecenter/home/gamecollection/GameCollectionStackLayout.kt new file mode 100644 index 0000000000..67fd5e3bfc --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/home/gamecollection/GameCollectionStackLayout.kt @@ -0,0 +1,110 @@ +package com.gh.gamecenter.home.gamecollection + +import android.view.View +import com.gh.common.util.dip2px +import com.gh.common.view.stacklayoutmanager.StackLayout +import com.gh.common.view.stacklayoutmanager.StackLayoutManager + +class GameCollectionStackLayout( + scrollOrientation: StackLayoutManager.ScrollOrientation, + visibleCount: Int, + perItemOffset: Int +) : StackLayout(scrollOrientation, visibleCount, perItemOffset) { + + private var mHasMeasureItemSize = false + private var mWidthSpace = 0 + private var mHeightSpace = 0 + private var mStartMargin = 0 + + private var mWidth = 0 + private var mHeight = 0 + private var mScrollOffset = 0 + + override fun doLayout( + stackLayoutManager: StackLayoutManager, + scrollOffset: Int, + firstMovePercent: Float, + itemView: View, + position: Int + ) { + mWidth = stackLayoutManager.width + mHeight = stackLayoutManager.height + mScrollOffset = scrollOffset + if (!mHasMeasureItemSize) { + mWidthSpace = + mWidth - stackLayoutManager.getDecoratedMeasuredWidth(itemView) - 18F.dip2px() + mHeightSpace = mHeight - stackLayoutManager.getDecoratedMeasuredHeight(itemView) + mStartMargin = 16F.dip2px() + mHasMeasureItemSize = true + } + val left: Int + val top: Int + if (position == 0) { + left = getFirstVisibleItemLeft() + top = getFirstVisibleItemTop() + } else { + left = getAfterFirstVisibleItemLeft(position, firstMovePercent) + top = getAfterFirstVisibleItemTop(position, firstMovePercent) + } + + val right = left + stackLayoutManager.getDecoratedMeasuredWidth(itemView) + val bottom = top + stackLayoutManager.getDecoratedMeasuredHeight(itemView) + + stackLayoutManager.layoutDecorated(itemView, left, top, right, bottom) + } + + override fun requestLayout() { + mHasMeasureItemSize = false //表示尺寸可能发生了改变 + } + + private fun getFirstVisibleItemLeft(): Int { + return when (mScrollOrientation) { + StackLayoutManager.ScrollOrientation.RIGHT_TO_LEFT -> mStartMargin - mScrollOffset % mWidth + StackLayoutManager.ScrollOrientation.LEFT_TO_RIGHT -> { + return if (mScrollOffset % mWidth == 0) { + mStartMargin + } else { + mStartMargin + (mWidth - mScrollOffset % mWidth) + } + } + else -> mWidthSpace / 2 + } + } + + private fun getFirstVisibleItemTop(): Int { + return when (mScrollOrientation) { + StackLayoutManager.ScrollOrientation.BOTTOM_TO_TOP -> mStartMargin - mScrollOffset % mHeight + StackLayoutManager.ScrollOrientation.TOP_TO_BOTTOM -> { + return if (mScrollOffset % mHeight == 0) { + mStartMargin + } else { + mStartMargin + (mHeight - mScrollOffset % mHeight) + } + } + else -> mHeightSpace / 2 + } + } + + private fun getAfterFirstVisibleItemLeft(visiblePosition: Int, movePercent: Float): Int { + return when (mScrollOrientation) { + StackLayoutManager.ScrollOrientation.RIGHT_TO_LEFT -> (mStartMargin + mPerItemOffset * (visiblePosition - movePercent)).toInt() + StackLayoutManager.ScrollOrientation.LEFT_TO_RIGHT -> (mStartMargin - mPerItemOffset * (visiblePosition - movePercent)).toInt() + else -> mWidthSpace / 2 + } + } + + private fun getAfterFirstVisibleItemTop(visiblePosition: Int, movePercent: Float): Int { + return when (mScrollOrientation) { + StackLayoutManager.ScrollOrientation.BOTTOM_TO_TOP -> (mStartMargin + mPerItemOffset * (visiblePosition - movePercent)).toInt() + StackLayoutManager.ScrollOrientation.TOP_TO_BOTTOM -> (mStartMargin - mPerItemOffset * (visiblePosition - movePercent)).toInt() + else -> mHeightSpace / 2 + } + } + + private fun getStartMargin(): Int { + return when (mScrollOrientation) { + StackLayoutManager.ScrollOrientation.RIGHT_TO_LEFT, StackLayoutManager.ScrollOrientation.LEFT_TO_RIGHT -> mWidthSpace / 2 + else -> mHeightSpace / 2 + } + } +} diff --git a/app/src/main/java/com/gh/gamecenter/home/gamecollection/HomeGameCollectionAdapter.kt b/app/src/main/java/com/gh/gamecenter/home/gamecollection/HomeGameCollectionAdapter.kt new file mode 100644 index 0000000000..5e76a14984 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/home/gamecollection/HomeGameCollectionAdapter.kt @@ -0,0 +1,123 @@ +package com.gh.gamecenter.home.gamecollection + +import android.content.Context +import android.view.ViewGroup +import androidx.core.graphics.drawable.toDrawable +import androidx.recyclerview.widget.RecyclerView +import com.gh.base.BaseActivity +import com.gh.common.util.DirectUtils +import com.gh.common.util.NewLogUtils +import com.gh.common.util.safelyGetInRelease +import com.gh.gamecenter.GameDetailActivity +import com.gh.gamecenter.R +import com.gh.gamecenter.databinding.HomeGameCollectionCardItemBinding +import com.gh.gamecenter.gamecollection.detail.GameCollectionDetailActivity +import com.gh.gamecenter.gamecollection.square.GameCollectionListItemData +import com.lightgame.adapter.BaseRecyclerAdapter + +class HomeGameCollectionAdapter(context: Context, private val entrance: String) : + BaseRecyclerAdapter(context) { + + private var mGameCollectionItemDataList = listOf() + + fun setGameCollectionList(gameCollectionItemDataList: List) { + mGameCollectionItemDataList = gameCollectionItemDataList + notifyDataSetChanged() + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = HomeGameCollectionCardViewHolder( + HomeGameCollectionCardItemBinding.bind( + mLayoutInflater.inflate( + R.layout.home_game_collection_card_item, + parent, + false + ) + ) + ) + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + if (holder is HomeGameCollectionCardViewHolder && mGameCollectionItemDataList.isNotEmpty()) { + val realPosition: Int + when (position) { + 0 -> { + realPosition = getRealCount() - 2 + } + 1 -> { + realPosition = getRealCount() - 1 + } + itemCount - 2 -> { + realPosition = 0 + } + itemCount - 1 -> { + realPosition = 1 + } + else -> { + realPosition = position - 2 + } + } + holder.bindGameListCard(mGameCollectionItemDataList[realPosition], entrance) + } + } + + override fun getItemCount() = getRealCount() + 4 + + fun getRealCount() = mGameCollectionItemDataList.size + + class HomeGameCollectionCardViewHolder(val binding: HomeGameCollectionCardItemBinding) : + RecyclerView.ViewHolder(binding.root) { + fun bindGameListCard(itemData: GameCollectionListItemData, entrance: String) { + binding.run { + val context = root.context + val gamesCollectionEntity = itemData.gameCollectionItem + if (gamesCollectionEntity != null) { + entity = gamesCollectionEntity + stampIv.setImageDrawable(if (gamesCollectionEntity.stamp == "official") R.drawable.ic_official.toDrawable() else R.drawable.ic_chosen.toDrawable()) + iconIvOne.setOnClickListener { + val game = gamesCollectionEntity.games?.get(0) + NewLogUtils.logClickGameCollectionGameIcon( + gamesCollectionEntity.title, + gamesCollectionEntity.id, + game?.name ?: "", + game?.id ?: "" + ) + game?.id?.let { id -> + GameDetailActivity.startGameDetailActivity(context, id, BaseActivity.mergeEntranceAndPath(entrance, "游戏单"), itemData.exposureEventList?.safelyGetInRelease(0)) + } + } + iconIvTwo.setOnClickListener { + val game = gamesCollectionEntity.games?.get(1) + NewLogUtils.logClickGameCollectionGameIcon( + gamesCollectionEntity.title, + gamesCollectionEntity.id, + game?.name ?: "", + game?.id ?: "" + ) + game?.id?.let { id -> + GameDetailActivity.startGameDetailActivity(context, id, BaseActivity.mergeEntranceAndPath(entrance, "游戏单"), itemData.exposureEventList?.safelyGetInRelease(1)) + } + } + iconIvThree.setOnClickListener { + val game = gamesCollectionEntity.games?.get(2) + NewLogUtils.logClickGameCollectionGameIcon( + gamesCollectionEntity.title, + gamesCollectionEntity.id, + game?.name ?: "", + game?.id ?: "" + ) + game?.id?.let { id -> + GameDetailActivity.startGameDetailActivity(context, id, BaseActivity.mergeEntranceAndPath(entrance, "游戏单"), itemData.exposureEventList?.safelyGetInRelease(2)) + } + } + userContainer.setOnClickListener { + NewLogUtils.logClickGameCollectionAuthor(gamesCollectionEntity.title, gamesCollectionEntity.id) + DirectUtils.directToHomeActivity(context, gamesCollectionEntity.user?.id, 0, entrance, "游戏单") + } + root.setOnClickListener { + NewLogUtils.logEnterGameCollectionDetail(gamesCollectionEntity.title, gamesCollectionEntity.id) + context.startActivity(GameCollectionDetailActivity.getIntent(context, gamesCollectionEntity.id, true)) + } + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/home/gamecollection/HomeGameCollectionViewHolder.kt b/app/src/main/java/com/gh/gamecenter/home/gamecollection/HomeGameCollectionViewHolder.kt new file mode 100644 index 0000000000..25c67b9ef9 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/home/gamecollection/HomeGameCollectionViewHolder.kt @@ -0,0 +1,46 @@ +package com.gh.gamecenter.home.gamecollection + +import com.gh.base.BaseRecyclerViewHolder +import com.gh.common.util.dip2px +import com.gh.common.view.stacklayoutmanager.StackLayoutManager +import com.gh.gamecenter.databinding.HomeGameCollectionItemBinding +import com.gh.gamecenter.gamecollection.square.GameCollectionListItemData + +class HomeGameCollectionViewHolder(val binding: HomeGameCollectionItemBinding) : + BaseRecyclerViewHolder(binding.root) { + fun bindGameCollectionList(gameCollectionItemDataList: List, entrance: String) { + if (binding.recyclerView.adapter is HomeGameCollectionAdapter) { + return + } + val adapter = HomeGameCollectionAdapter(binding.root.context, entrance) + val manager = StackLayoutManager( + StackLayoutManager.ScrollOrientation.RIGHT_TO_LEFT, + 3, + GameCollectionStackAnimation::class.java, + GameCollectionStackLayout::class.java + ) + manager.setItemOffset(9F.dip2px()) + manager.setItemChangedListener(object : StackLayoutManager.ItemChangedListener { + override fun onItemChanged(position: Int) { + when (position) { + 0 -> binding.recyclerView.scrollToPosition(adapter.itemCount - 3) + // 最后一个item跳转 + adapter.itemCount - 3 -> binding.recyclerView.scrollToPosition(1) + } + } + }) + binding.recyclerView.layoutManager = manager + binding.recyclerView.adapter = adapter + binding.recyclerView.isNestedScrollingEnabled = false + + adapter.setGameCollectionList(gameCollectionItemDataList) + binding.recyclerView.post { + // 定位到实际的第一个item + manager.scrollToPosition(FIRST_ITEM_POSITION) + } + } + + companion object { + const val FIRST_ITEM_POSITION = 2 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/message/MessageItemViewHolder.java b/app/src/main/java/com/gh/gamecenter/message/MessageItemViewHolder.java index 4bd813c2d5..948994a277 100644 --- a/app/src/main/java/com/gh/gamecenter/message/MessageItemViewHolder.java +++ b/app/src/main/java/com/gh/gamecenter/message/MessageItemViewHolder.java @@ -26,6 +26,7 @@ import com.gh.gamecenter.entity.LinkEntity; import com.gh.gamecenter.entity.MessageEntity; import com.gh.gamecenter.entity.MessageFold; import com.gh.gamecenter.entity.UserEntity; +import com.gh.gamecenter.gamecollection.detail.GameCollectionDetailActivity; import com.gh.gamecenter.gamedetail.rating.RatingReplyActivity; import com.gh.gamecenter.manager.UserManager; import com.gh.gamecenter.qa.answer.detail.SimpleAnswerDetailActivity; @@ -413,6 +414,50 @@ public class MessageItemViewHolder extends BaseRecyclerViewHolder mBinding.messageArticleIcon.setVisibility(View.GONE); voteMoreUser(messageEntity); break; + case "game_list_vote": + mBinding.messageCommand.setText("赞了你的游戏单"); + mBinding.messageContent.setVisibility(View.GONE); + mBinding.messageOriginalTitle.setText(messageEntity.getGameList().getTitle()); + targetUrl = messageEntity.getGameList().getCover(); + ImageUtils.displayIcon(mBinding.messageAskIcon, targetUrl); + mBinding.messageAskIcon.setVisibility(View.VISIBLE); + mBinding.messageArticleIcon.setVisibility(View.GONE); + voteMoreUser(messageEntity); + break; + case "game_list_comment": + mBinding.messageCommand.setText("评论了你"); + mBinding.messageContent.setVisibility(View.VISIBLE); + mBinding.messageContent.setMaxLines(Integer.MAX_VALUE); + mBinding.messageContent.setText(messageEntity.getComment().getContent()); + mBinding.messageOriginalTitle.setText(messageEntity.getGameList().getTitle()); + targetUrl = messageEntity.getGameList().getCover(); + ImageUtils.displayIcon(mBinding.messageAskIcon, targetUrl); + mBinding.messageAskIcon.setVisibility(View.VISIBLE); + mBinding.messageArticleIcon.setVisibility(View.GONE); + voteMoreUser(messageEntity); + break; + case "game_list_comment_reply": + mBinding.messageCommand.setText("回复了你"); + mBinding.messageContent.setVisibility(View.VISIBLE); + mBinding.messageContent.setMaxLines(Integer.MAX_VALUE); + mBinding.messageContent.setText(messageEntity.getComment().getContent()); + mBinding.messageOriginalTitle.setText(messageEntity.getGameList().getTitle()); + targetUrl = messageEntity.getGameList().getCover(); + ImageUtils.displayIcon(mBinding.messageAskIcon, targetUrl); + mBinding.messageAskIcon.setVisibility(View.VISIBLE); + mBinding.messageArticleIcon.setVisibility(View.GONE); + voteMoreUser(messageEntity); + break; + case "game_list_comment_vote": + mBinding.messageCommand.setText("赞了你的评论"); + mBinding.messageContent.setVisibility(View.GONE); + mBinding.messageOriginalTitle.setText(messageEntity.getGameList().getTitle()); + targetUrl = messageEntity.getGameList().getCover(); + ImageUtils.displayIcon(mBinding.messageAskIcon, targetUrl); + mBinding.messageAskIcon.setVisibility(View.VISIBLE); + mBinding.messageArticleIcon.setVisibility(View.GONE); + voteMoreUser(messageEntity); + break; } switch (messageEntity.getType()) { case "video_comment": @@ -661,6 +706,33 @@ public class MessageItemViewHolder extends BaseRecyclerViewHolder context.startActivity(WebActivity.getIntent(context, entity.getActivity().getUrlComment(), true)); } break; + + case "game_list_comment": + context.startActivity(GameCollectionDetailActivity.getIntent(context, entity.getGameList().getId(), false, true)); + break; + case "game_list_comment_reply": + context.startActivity(CommentActivity.getGameCollectionCommentDetailIntent( + context, + entity.getComment().getTopId(), + entity.getGameList().getId(), + false, + entrance, + path + )); + break; + case "game_list_comment_vote": + context.startActivity(CommentActivity.getGameCollectionCommentDetailIntent( + context, + entity.getComment().getId(), + entity.getGameList().getId(), + false, + entrance, + path + )); + break; + case "game_list_vote": + context.startActivity(GameCollectionDetailActivity.getIntent(context, entity.getGameList().getId(), false)); + break; } } } diff --git a/app/src/main/java/com/gh/gamecenter/mygame/MyGameActivity.kt b/app/src/main/java/com/gh/gamecenter/mygame/MyGameActivity.kt index 2df1c1b7d8..1a61e45d12 100644 --- a/app/src/main/java/com/gh/gamecenter/mygame/MyGameActivity.kt +++ b/app/src/main/java/com/gh/gamecenter/mygame/MyGameActivity.kt @@ -3,15 +3,26 @@ package com.gh.gamecenter.mygame import android.content.Context import android.content.Intent import android.os.Bundle +import android.view.Gravity +import android.view.MenuItem +import android.widget.LinearLayout import androidx.fragment.app.Fragment import com.gh.base.BaseActivity_TabLayout -import com.gh.common.util.MtaHelper +import com.gh.common.AppExecutor +import com.gh.common.constant.Constants +import com.gh.common.util.* +import com.gh.common.view.BugFixedPopupWindow +import com.gh.gamecenter.R +import com.gh.gamecenter.databinding.PopupMyGameGuideBinding +import com.gh.gamecenter.gamecollection.publish.GameCollectionEditActivity class MyGameActivity : BaseActivity_TabLayout() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setNavigationTitle("我的游戏") + setToolbarMenu(R.menu.menu_my_game) + showGuide() } override fun initFragmentList(fragments: MutableList?) { @@ -32,6 +43,33 @@ class MyGameActivity : BaseActivity_TabLayout() { MtaHelper.onEvent("我的光环_新", "我的游戏", "${mTabTitleList[position]}Tab") } + override fun onMenuItemClick(item: MenuItem?): Boolean { + if (item?.itemId == R.id.menu_create_game_collection) { + showRegulationTestDialogIfNeeded { + startActivity(GameCollectionEditActivity.getIntent(this, mEntrance, "我的游戏")) + } + } + return super.onMenuItemClick(item) + } + + private fun showGuide() { + AppExecutor.uiExecutor.executeWithDelay({ + if (!SPUtils.getBoolean(Constants.SP_MY_GAME_GUIDE)) { + val binding = PopupMyGameGuideBinding.inflate(layoutInflater, null, false) + val popupWindow = BugFixedPopupWindow(binding.root, LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT) + binding.root.setOnClickListener { + SPUtils.setBoolean(Constants.SP_MY_GAME_GUIDE, true) + popupWindow.dismiss() + } + popupWindow.run { + isTouchable = true + isFocusable = true + showAtLocation(window.decorView, Gravity.TOP, 0, 0) + } + } + }, 500) + } + companion object { @JvmStatic fun getIntentWithConfig(context: Context, defaultFragmentPosition: Int): Intent { diff --git a/app/src/main/java/com/gh/gamecenter/mygame/PlayedGameViewModel.kt b/app/src/main/java/com/gh/gamecenter/mygame/PlayedGameViewModel.kt index 203f6982f9..17bb6bd809 100644 --- a/app/src/main/java/com/gh/gamecenter/mygame/PlayedGameViewModel.kt +++ b/app/src/main/java/com/gh/gamecenter/mygame/PlayedGameViewModel.kt @@ -16,8 +16,8 @@ import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.schedulers.Schedulers import okhttp3.ResponseBody -class PlayedGameViewModel(application: Application, var userId: String) - : ListViewModel(application) { +class PlayedGameViewModel(application: Application, var userId: String, val isKeepTagStyle: Boolean = false) : + ListViewModel(application) { override fun provideDataObservable(page: Int): Observable>? { return null @@ -29,9 +29,11 @@ class PlayedGameViewModel(application: Application, var userId: String) override fun mergeResultLiveData() { mResultLiveData.addSource(mListLiveData) { - it.forEach { game -> - game.hideSizeInsideDes = true - game.tagStyle.clear() + if (!isKeepTagStyle) { + it.forEach { game -> + game.hideSizeInsideDes = true + game.tagStyle.clear() + } } mResultLiveData.postValue(it) } @@ -40,32 +42,32 @@ class PlayedGameViewModel(application: Application, var userId: String) @SuppressLint("CheckResult") fun deletePlayedGame(gameEntity: GameEntity) { RetrofitManager.getInstance(getApplication()).api - .deletePlayedGame(userId, gameEntity.playedGameId) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(object : BiResponse() { - override fun onSuccess(data: ResponseBody) { - mListLiveData.value?.let { - for (game in it) { - if (gameEntity.id == game.id) { - it.remove(game) - mListLiveData.postValue(it) - break - } + .deletePlayedGame(userId, gameEntity.playedGameId) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : BiResponse() { + override fun onSuccess(data: ResponseBody) { + mListLiveData.value?.let { + for (game in it) { + if (gameEntity.id == game.id) { + it.remove(game) + mListLiveData.postValue(it) + break } } } + } - override fun onFailure(exception: Exception) { - super.onFailure(exception) - Utils.toast(getApplication(), exception.localizedMessage) - } - }) + override fun onFailure(exception: Exception) { + super.onFailure(exception) + Utils.toast(getApplication(), exception.localizedMessage) + } + }) } - class Factory(private val mUserId: String) : ViewModelProvider.NewInstanceFactory() { + class Factory(private val mUserId: String, val isKeepTagStyle: Boolean = false) : ViewModelProvider.NewInstanceFactory() { override fun create(modelClass: Class): T { - return PlayedGameViewModel(HaloApp.getInstance().application, mUserId) as T + return PlayedGameViewModel(HaloApp.getInstance().application, mUserId, isKeepTagStyle) as T } } diff --git a/app/src/main/java/com/gh/gamecenter/personal/PersonalFunctionAdapter.kt b/app/src/main/java/com/gh/gamecenter/personal/PersonalFunctionAdapter.kt index 316bdfac35..ef399a48fd 100644 --- a/app/src/main/java/com/gh/gamecenter/personal/PersonalFunctionAdapter.kt +++ b/app/src/main/java/com/gh/gamecenter/personal/PersonalFunctionAdapter.kt @@ -18,6 +18,7 @@ import com.gh.gamecenter.entity.FunctionalGroupEntity import com.gh.gamecenter.entity.FunctionalLinkEntity import com.gh.gamecenter.entity.FunctionalMessageType import com.gh.gamecenter.game.upload.GameSubmissionActivity +import com.gh.gamecenter.gamecollection.mine.MyGameCollectionActivity import com.gh.gamecenter.gamedetail.myrating.MyRatingActivity import com.gh.gamecenter.history.HistoryActivity import com.gh.gamecenter.manager.UserManager @@ -32,8 +33,8 @@ import com.gh.gamecenter.video.videomanager.VideoManagerActivity import com.halo.assistant.HaloApp import com.lightgame.adapter.BaseRecyclerAdapter -class PersonalFunctionAdapter(val context: Context, val groupName: String, var mEntityList: ArrayList) - : BaseRecyclerAdapter(context) { +class PersonalFunctionAdapter(val context: Context, val groupName: String, var mEntityList: ArrayList) : + BaseRecyclerAdapter(context) { private var mDisplayUpdateHint = false private val gameTrendsDao = GameTrendsDao(HaloApp.getInstance().application) @@ -287,6 +288,13 @@ class PersonalFunctionAdapter(val context: Context, val groupName: String, var m "青少年模式" -> { context.startActivity(TeenagerModeActivity.getIntent(context)) } + "我的游戏单" -> { + if (UserManager.getInstance().isLoggedIn) { + context.startActivity(MyGameCollectionActivity.getIntent(context)) + } else { + CheckLoginUtils.checkLogin(context, "我的光环-我的游戏单") { } + } + } else -> { DirectUtils.directToLinkPage(context, linkEntity, "", "我的光环") } diff --git a/app/src/main/java/com/gh/gamecenter/personal/PersonalViewModel.kt b/app/src/main/java/com/gh/gamecenter/personal/PersonalViewModel.kt index 66d7b945c4..44086bad67 100644 --- a/app/src/main/java/com/gh/gamecenter/personal/PersonalViewModel.kt +++ b/app/src/main/java/com/gh/gamecenter/personal/PersonalViewModel.kt @@ -44,7 +44,8 @@ class PersonalViewModel(application: Application) : AndroidViewModel(application Triple("浏览记录", R.drawable.personal_browsing_history, "浏览记录"), Triple("账号安全", R.drawable.personal_account_security, "账号安全"), Triple("模拟器游戏", R.drawable.personal_simulator_game, "模拟器游戏"), - Triple("收货信息", R.drawable.personal_delivery_info, "收货信息") + Triple("收货信息", R.drawable.personal_delivery_info, "收货信息"), + Triple("我的游戏单", R.drawable.icon_game_collection, "我的游戏单") ) private val contentCenterFuncs = arrayListOf( Triple("游戏动态", R.drawable.personal_game_dynamic, "游戏动态"), diff --git a/app/src/main/java/com/gh/gamecenter/personalhome/UserHomeFragment.kt b/app/src/main/java/com/gh/gamecenter/personalhome/UserHomeFragment.kt index b3da17d5bf..0549a8ef36 100644 --- a/app/src/main/java/com/gh/gamecenter/personalhome/UserHomeFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/personalhome/UserHomeFragment.kt @@ -345,7 +345,7 @@ class UserHomeFragment : NormalFragment() { val tag = "android:switcher:${mHomeBinding?.viewpager?.id}:" val gameFragment = childFragmentManager.findFragmentByTag("${tag}0") - ?: UserGameFragment.getInstance(mUserHomeViewModel.userId, count.gameComment) + ?: UserGameFragment.getInstance(mUserHomeViewModel.userId, count) val qaFragment = childFragmentManager.findFragmentByTag("${tag}1") ?: UserHistoryFragment.getInstance( mUserHomeViewModel.userId, diff --git a/app/src/main/java/com/gh/gamecenter/personalhome/home/game/UserGameFragment.kt b/app/src/main/java/com/gh/gamecenter/personalhome/home/game/UserGameFragment.kt index 2d12705a54..2bdfdfbebb 100644 --- a/app/src/main/java/com/gh/gamecenter/personalhome/home/game/UserGameFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/personalhome/home/game/UserGameFragment.kt @@ -10,22 +10,24 @@ import android.widget.TextView import androidx.core.content.ContextCompat import androidx.core.os.bundleOf import com.gh.common.util.* -import com.gh.common.util.EntranceUtils.KEY_COMMENT_COUNT -import com.gh.common.util.EntranceUtils.KEY_USER_ID +import com.gh.common.util.EntranceUtils.* import com.gh.gamecenter.R +import com.gh.gamecenter.collection.GamesCollectionFragment import com.gh.gamecenter.databinding.FragmentUserGameBinding +import com.gh.gamecenter.entity.PersonalEntity import com.gh.gamecenter.normal.NormalFragment class UserGameFragment: NormalFragment() { private var mBinding: FragmentUserGameBinding? = null private var mViewModel: UserGameViewModel? = null + private var mGamesCollectionFragment: GamesCollectionFragment? = null private var mPlayedGameFragment: UserPlayedGameFragment? = null private var mCommentFragment: UserCommentHistoryFragment? = null private var mUserId: String = "" private var mFilter: String = "全部" - private var mCommentCount = 0 - private var mCurrentType = TYPE_PLAYED_GAME + private var mCount = PersonalEntity.Count() + private var mCurrentType = TYPE_GAME_COLLECTION override fun getLayoutId() = 0 @@ -34,9 +36,9 @@ class UserGameFragment: NormalFragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) mUserId = arguments?.getString(KEY_USER_ID, "") ?: "" - mCommentCount = arguments?.getInt(KEY_COMMENT_COUNT, 0) ?: 0 + mCount = arguments?.getParcelable(COUNT) ?: PersonalEntity.Count() mViewModel = viewModelProvider(UserGameViewModel.Factory(mUserId)) - changeType(TYPE_PLAYED_GAME) + changeType(TYPE_GAME_COLLECTION) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { @@ -47,20 +49,23 @@ class UserGameFragment: NormalFragment() { } mBinding?.run { - commentType.text = "评论 $mCommentCount" + gameCollectionType.text = "游戏单 ${mCount.gameList}" + commentType.text = "评论 ${mCount.gameComment}" - gameType.setOnClickListener { - if (mCurrentType == TYPE_PLAYED_GAME) return@setOnClickListener - mCurrentType = TYPE_PLAYED_GAME - updateTypeView(TYPE_PLAYED_GAME) - changeType(TYPE_PLAYED_GAME) - } + val typePairList = arrayListOf( + Pair(TYPE_GAME_COLLECTION, gameCollectionType), + Pair(TYPE_PLAYED_GAME, gameType), + Pair(TYPE_COMMENT, commentType) + ) - commentType.setOnClickListener { - if (mCurrentType == TYPE_COMMENT) return@setOnClickListener - mCurrentType = TYPE_COMMENT - updateTypeView(TYPE_COMMENT) - changeType(TYPE_COMMENT) + typePairList.forEach { pair -> + pair.second.setOnClickListener { + if (mCurrentType != pair.first) { + mCurrentType = pair.first + updateTypeView(pair.first) + changeType(pair.first) + } + } } commentSubType.setOnClickListener { @@ -71,35 +76,62 @@ class UserGameFragment: NormalFragment() { private fun updateTypeView(type: Int) { mBinding?.run { - if (type == TYPE_PLAYED_GAME) { - commentSubType.visibility = View.GONE - arrowIv.visibility = View.GONE - gameType.setBackgroundResource(R.drawable.button_round_f0f8ff) - gameType.setTextColor(R.color.theme_font.toColor()) - commentType.setBackgroundResource(R.drawable.button_round_fafafa) - commentType.setTextColor(R.color.text_333333.toColor()) - } else { - commentSubType.visibility = View.VISIBLE - arrowIv.visibility = View.VISIBLE - gameType.setBackgroundResource(R.drawable.button_round_fafafa) - gameType.setTextColor(R.color.text_333333.toColor()) - commentType.setBackgroundResource(R.drawable.button_round_f0f8ff) - commentType.setTextColor(R.color.theme_font.toColor()) + when (type) { + TYPE_GAME_COLLECTION -> { + commentSubType.visibility = View.GONE + arrowIv.visibility = View.GONE + gameCollectionType.setBackgroundResource(R.drawable.button_round_f0f8ff) + gameCollectionType.setTextColor(R.color.theme_font.toColor()) + gameType.setBackgroundResource(R.drawable.button_round_fafafa) + gameType.setTextColor(R.color.text_333333.toColor()) + commentType.setBackgroundResource(R.drawable.button_round_fafafa) + commentType.setTextColor(R.color.text_333333.toColor()) + } + TYPE_PLAYED_GAME -> { + commentSubType.visibility = View.GONE + arrowIv.visibility = View.GONE + gameCollectionType.setBackgroundResource(R.drawable.button_round_fafafa) + gameCollectionType.setTextColor(R.color.text_333333.toColor()) + gameType.setBackgroundResource(R.drawable.button_round_f0f8ff) + gameType.setTextColor(R.color.theme_font.toColor()) + commentType.setBackgroundResource(R.drawable.button_round_fafafa) + commentType.setTextColor(R.color.text_333333.toColor()) + } + else -> { + commentSubType.visibility = View.VISIBLE + arrowIv.visibility = View.VISIBLE + gameCollectionType.setBackgroundResource(R.drawable.button_round_fafafa) + gameCollectionType.setTextColor(R.color.text_333333.toColor()) + gameType.setBackgroundResource(R.drawable.button_round_fafafa) + gameType.setTextColor(R.color.text_333333.toColor()) + commentType.setBackgroundResource(R.drawable.button_round_f0f8ff) + commentType.setTextColor(R.color.theme_font.toColor()) + } } } } private fun changeType(type: Int) { - if (type == TYPE_PLAYED_GAME) { - mPlayedGameFragment = childFragmentManager.findFragmentByTag(UserPlayedGameFragment::class.java.simpleName) as? UserPlayedGameFragment + when (type) { + TYPE_GAME_COLLECTION -> { + mGamesCollectionFragment = childFragmentManager.findFragmentByTag(UserPlayedGameFragment::class.java.simpleName) as? GamesCollectionFragment + ?: GamesCollectionFragment() + mGamesCollectionFragment?.arguments = + bundleOf(KEY_USER_ID to mUserId, KEY_TYPE to GamesCollectionFragment.TYPE_USER) + childFragmentManager.beginTransaction().replace(R.id.contentContainer, mGamesCollectionFragment!!, GamesCollectionFragment::class.java.simpleName).commitAllowingStateLoss() + } + TYPE_PLAYED_GAME -> { + mPlayedGameFragment = childFragmentManager.findFragmentByTag(UserPlayedGameFragment::class.java.simpleName) as? UserPlayedGameFragment ?: UserPlayedGameFragment() - mPlayedGameFragment?.arguments = bundleOf(KEY_USER_ID to mUserId) - childFragmentManager.beginTransaction().replace(R.id.contentContainer, mPlayedGameFragment!!, UserPlayedGameFragment::class.java.simpleName).commitAllowingStateLoss() - } else { - mCommentFragment = childFragmentManager.findFragmentByTag(UserCommentHistoryFragment::class.java.simpleName) as? UserCommentHistoryFragment + mPlayedGameFragment?.arguments = bundleOf(KEY_USER_ID to mUserId) + childFragmentManager.beginTransaction().replace(R.id.contentContainer, mPlayedGameFragment!!, UserPlayedGameFragment::class.java.simpleName).commitAllowingStateLoss() + } + else -> { + mCommentFragment = childFragmentManager.findFragmentByTag(UserCommentHistoryFragment::class.java.simpleName) as? UserCommentHistoryFragment ?: UserCommentHistoryFragment() - mCommentFragment?.arguments = bundleOf(KEY_USER_ID to mUserId) - childFragmentManager.beginTransaction().replace(R.id.contentContainer, mCommentFragment!!, UserCommentHistoryFragment::class.java.simpleName).commitAllowingStateLoss() + mCommentFragment?.arguments = bundleOf(KEY_USER_ID to mUserId) + childFragmentManager.beginTransaction().replace(R.id.contentContainer, mCommentFragment!!, UserCommentHistoryFragment::class.java.simpleName).commitAllowingStateLoss() + } } } @@ -154,14 +186,16 @@ class UserGameFragment: NormalFragment() { } companion object { - private const val TYPE_PLAYED_GAME = 100 - private const val TYPE_COMMENT = 101 + private const val TYPE_GAME_COLLECTION = 100 + private const val TYPE_PLAYED_GAME = 101 + private const val TYPE_COMMENT = 102 + const val COUNT = "count" - fun getInstance(userId: String, commentCount: Int): UserGameFragment { + fun getInstance(userId: String, count: PersonalEntity.Count): UserGameFragment { return UserGameFragment().apply { with(bundleOf( - KEY_USER_ID to userId, - KEY_COMMENT_COUNT to commentCount + KEY_USER_ID to userId, + COUNT to count )) } } diff --git a/app/src/main/java/com/gh/gamecenter/personalhome/home/game/UserPlayedGameFragment.kt b/app/src/main/java/com/gh/gamecenter/personalhome/home/game/UserPlayedGameFragment.kt index 5eab999e09..e810967e03 100644 --- a/app/src/main/java/com/gh/gamecenter/personalhome/home/game/UserPlayedGameFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/personalhome/home/game/UserPlayedGameFragment.kt @@ -26,7 +26,7 @@ import com.lightgame.download.DownloadEntity import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode -class UserPlayedGameFragment: ListFragment() { +open class UserPlayedGameFragment: ListFragment() { private var mUserId = "" private var mAdapter: UserPlayedGameAdapter? = null diff --git a/app/src/main/java/com/gh/gamecenter/qa/answer/detail/AnswerDetailFragment.kt b/app/src/main/java/com/gh/gamecenter/qa/answer/detail/AnswerDetailFragment.kt index 4d0009d7a7..c95a6e392e 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/answer/detail/AnswerDetailFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/answer/detail/AnswerDetailFragment.kt @@ -635,8 +635,8 @@ open class AnswerDetailFragment : NormalFragment() { } } - private fun getShareEntity(answer: AnswerDetailEntity): ForumShareEntity { - return ForumShareEntity( + private fun getShareEntity(answer: AnswerDetailEntity): NormalShareEntity { + return NormalShareEntity( id = mAnswerId, shareUrl = if (isPublishEnv()) { getString(R.string.share_answers_url, mAnswerId) diff --git a/app/src/main/java/com/gh/gamecenter/qa/article/detail/ArticleDetailFragment.kt b/app/src/main/java/com/gh/gamecenter/qa/article/detail/ArticleDetailFragment.kt index 21a6b49f39..11aaa9b9ed 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/article/detail/ArticleDetailFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/article/detail/ArticleDetailFragment.kt @@ -26,7 +26,6 @@ import com.gh.common.util.* import com.gh.gamecenter.ImageViewerActivity import com.gh.gamecenter.R import com.gh.gamecenter.baselist.ListAdapter -import com.gh.gamecenter.baselist.LoadStatus import com.gh.gamecenter.baselist.LoadType import com.gh.gamecenter.databinding.FragmentArticleDetailBinding import com.gh.gamecenter.entity.* @@ -575,8 +574,8 @@ class ArticleDetailFragment : BaseCommentFragment { + GameCollectionCommentConversationFragment().with(intent.extras) + } isCommentConversation && commentId.isNotEmpty() -> { CommentConversationFragment().with(intent.extras) } @@ -96,6 +102,17 @@ class CommentActivity : BaseActivity() { commentEntity, commentCallback) } + gameCollectionId.isNotEmpty() -> { + NewCommentFragment.getGameCollectionCommentInstance( + gameCollectionId, + commentId, + showKeyboard, + commentCount, + mShowInputOnly, + useReplyApi, + commentEntity, + commentCallback) + } else -> { NewCommentFragment.getVideoCommentInstance( videoId, @@ -158,6 +175,8 @@ class CommentActivity : BaseActivity() { const val VIDEO_ID = "video_id" + const val GAME_COLLECTION_ID = "game_collection_id" + const val REQUEST_CODE = 8123 @JvmStatic @@ -337,5 +356,65 @@ class CommentActivity : BaseActivity() { } return intent } + + /** + * 评论游戏单 + */ + @JvmStatic + fun getGameCollectionCommentIntent(context: Context, + gameCollectionId: String, + commentCount: Int? = 0): Intent { + val intent = Intent(context, CommentActivity::class.java) + intent.putExtra(GAME_COLLECTION_ID, gameCollectionId) + intent.putExtra(COMMENT_COUNT, commentCount) + intent.putExtra(SHOW_KEYBOARD, true) + intent.putExtra(SHOW_INPUT_ONLY, true) + if (context is Activity) { + context.overridePendingTransition(0, 0) + } + return intent + } + + /** + * 回复游戏单评论 + */ + fun getGameCollectionCommentReplyIntent(context: Context, + gameCollectionId: String, + commentId: String, + commentCount: Int? = 0, + commentEntity: CommentEntity? = null): Intent { + val intent = Intent(context, CommentActivity::class.java) + intent.putExtra(GAME_COLLECTION_ID, gameCollectionId) + intent.putExtra(KEY_COMMENT_ID, commentId) + intent.putExtra(COMMENT_COUNT, commentCount) + intent.putExtra(SHOW_KEYBOARD, true) + intent.putExtra(SHOW_INPUT_ONLY, true) + intent.putExtra(COMMENT_ENTITY, commentEntity) + intent.putExtra(USE_REPLY_API, true) + if (context is Activity) { + context.overridePendingTransition(0, 0) + } + return intent + } + + /** + * 游戏单评论对话 + */ + @JvmStatic + fun getGameCollectionCommentDetailIntent(context: Context, + commentId: String, + gameCollectionId: String, + showKeyboard: Boolean = false, + entrance: String, + path: String): Intent { + val intent = Intent(context, CommentActivity::class.java) + intent.putExtra(EntranceUtils.KEY_ENTRANCE, NormalActivity.mergeEntranceAndPath(entrance, path)) + intent.putExtra(GAME_COLLECTION_ID, gameCollectionId) + intent.putExtra(EntranceUtils.KEY_COMMENT_ID, commentId) + intent.putExtra(EntranceUtils.KEY_SHOW_KEYBOARD_IF_NEEDED, showKeyboard) + intent.putExtra(EntranceUtils.KEY_PATH, path) + intent.putExtra(EntranceUtils.KEY_IS_COMMENT_CONVERSATION, true) + return intent + } } } \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/qa/comment/NewCommentAdapter.kt b/app/src/main/java/com/gh/gamecenter/qa/comment/NewCommentAdapter.kt index 90e6f16681..75246370ca 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/comment/NewCommentAdapter.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/comment/NewCommentAdapter.kt @@ -232,6 +232,9 @@ class NewCommentAdapter( CommentType.COMMUNITY_QUESTION, CommentType.COMMUNITY_QUESTION_CONVERSATION -> "问题详情-评论管理" + + CommentType.GAME_COLLECTION, + CommentType.GAME_COLLECTION_CONVERSATION -> "游戏单详情-评论管理" } val userHomePageTabPosition = if (mViewModel.commentType.isVideo()) 2 else 1 diff --git a/app/src/main/java/com/gh/gamecenter/qa/comment/NewCommentFragment.kt b/app/src/main/java/com/gh/gamecenter/qa/comment/NewCommentFragment.kt index 13dff479ff..c1e7bd4c63 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/comment/NewCommentFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/comment/NewCommentFragment.kt @@ -25,6 +25,7 @@ import com.gh.common.syncpage.SyncDataEntity import com.gh.common.syncpage.SyncFieldConstants import com.gh.common.syncpage.SyncPageRepository import com.gh.common.util.* +import com.gh.common.util.EntranceUtils.KEY_COMMENT_ID import com.gh.common.view.VerticalItemDecoration import com.gh.gamecenter.CommentDetailActivity import com.gh.gamecenter.R @@ -37,6 +38,7 @@ import com.gh.gamecenter.eventbus.EBCommentSuccess import com.gh.gamecenter.eventbus.EBDeleteComment import com.gh.gamecenter.eventbus.EBReuse import com.gh.gamecenter.qa.answer.detail.AnswerDetailFragment +import com.gh.gamecenter.qa.comment.CommentActivity.Companion.GAME_COLLECTION_ID import com.gh.gamecenter.qa.comment.CommentActivity.Companion.QUESTION_ID import com.gh.gamecenter.qa.editor.LocalMediaActivity import com.halo.assistant.HaloApp @@ -113,7 +115,9 @@ open class NewCommentFragment : ListFragment protected var mCommunityId: String = "" protected var mVideoId: String = "" protected var mQuestionId: String = "" + protected var mGameCollectionId: String = "" protected var mCommentId: String = "" + protected var mRootCommentId: String = "" protected var mShowInputOnly: Boolean = false // 是否只显示输入框,不显示列表 protected var mIsVideoAuthor: Boolean = false//是否是视频作者 protected var mCommentType = CommentType.ANSWER @@ -135,7 +139,9 @@ open class NewCommentFragment : ListFragment mAnswerId = getString(ANSWER_ID, "") mArticleId = getString(ARTICLE_ID, "") mVideoId = getString(VIDEO_ID, "") - mQuestionId = getString(CommentActivity.QUESTION_ID, "") + mQuestionId = getString(QUESTION_ID, "") + mGameCollectionId = getString(GAME_COLLECTION_ID, "") + mRootCommentId = getString(KEY_COMMENT_ID, "") mCommentCount = getInt(COMMENT_COUNT, 0) mCommentType = getSerializable(COMMENT_TYPE) as? CommentType ?: CommentType.ANSWER mCommunityId = getString(COMMUNITY_ID, "") @@ -358,6 +364,8 @@ open class NewCommentFragment : ListFragment commentId = mCommentId, videoId = mVideoId, questionId = mQuestionId, + gameCollectionId = mGameCollectionId, + rootCommentId = mRootCommentId, commentType = mCommentType, isVideoAuthor = mIsVideoAuthor ) @@ -379,6 +387,9 @@ open class NewCommentFragment : ListFragment CommentType.VIDEO, CommentType.VIDEO_CONVERSATION -> "(视频详情-评论列表)" + + CommentType.GAME_COLLECTION, + CommentType.GAME_COLLECTION_CONVERSATION -> "(游戏单详情-评论列表)" } mAdapter = NewCommentAdapter(requireContext(), mViewModel, true, this, this, entrance) } @@ -544,6 +555,15 @@ open class NewCommentFragment : ListFragment "问题详情-评论-回复" } } + + CommentType.GAME_COLLECTION, + CommentType.GAME_COLLECTION_CONVERSATION -> { + if (mCommentEntity == null) { + "游戏单详情-评论-写评论" + } else { + "游戏单详情-评论-回复" + } + } } } @@ -580,7 +600,7 @@ open class NewCommentFragment : ListFragment mAnswerContent.orientation = LinearLayout.VERTICAL mAnswerContent.background = ContextCompat.getDrawable(requireActivity(), R.drawable.bg_shape_white_radius_10_top_only) mPlaceholderView.visibility = View.VISIBLE - mImageBtn.goneIf(mCommentEntity != null) + mImageBtn.goneIf(mCommentEntity != null || mGameCollectionId.isNotEmpty()) mScrollViewParams.width = LinearLayout.LayoutParams.MATCH_PARENT mScrollViewParams.height = 76f.dip2px() mScrollViewParams.topMargin = 4f.dip2px() @@ -854,5 +874,37 @@ open class NewCommentFragment : ListFragment } + + fun getGameCollectionCommentInstance( + gameCollectionId: String, + commentId: String, + showSoftKeyboardOnStartUp: Boolean, + commentCount: Int, + showInputOnly: Boolean, + useReplyApi: Boolean, + commentEntity: CommentEntity?, + listener: AnswerDetailFragment.CommentListener + ) + : NewCommentFragment { + val commentType = if (useReplyApi) { + CommentType.GAME_COLLECTION_CONVERSATION + } else { + CommentType.GAME_COLLECTION + } + return NewCommentFragment().apply { + mCommentListener = listener + with( + bundleOf( + SHOW_SOFT_KEY_BOARD_ON_STARTUP to showSoftKeyboardOnStartUp, + GAME_COLLECTION_ID to gameCollectionId, + KEY_COMMENT_ID to commentId, + COMMENT_COUNT to commentCount, + COMMENT_TYPE to commentType, + SHOW_INPUT_ONLY to showInputOnly, + COMMENT_ENTITY to commentEntity + ) + ) + } + } } } diff --git a/app/src/main/java/com/gh/gamecenter/qa/comment/NewCommentViewModel.kt b/app/src/main/java/com/gh/gamecenter/qa/comment/NewCommentViewModel.kt index 357aa0737a..b21f6ed186 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/comment/NewCommentViewModel.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/comment/NewCommentViewModel.kt @@ -6,7 +6,6 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider -import com.gh.common.json.json import com.gh.common.syncpage.SyncDataEntity import com.gh.common.syncpage.SyncFieldConstants import com.gh.common.syncpage.SyncPageRepository @@ -14,7 +13,6 @@ import com.gh.common.util.* import com.gh.gamecenter.baselist.ListViewModel import com.gh.gamecenter.entity.CommentDraft import com.gh.gamecenter.entity.CommentEntity -import com.gh.gamecenter.entity.ErrorEntity import com.gh.gamecenter.retrofit.BiResponse import com.gh.gamecenter.retrofit.Response import com.gh.gamecenter.retrofit.RetrofitManager @@ -37,6 +35,8 @@ open class NewCommentViewModel( var communityId: String = "", var videoId: String = "", var questionId: String = "", + var gameCollectionId: String = "", + var rootCommentId: String = "", var commentType: CommentType = CommentType.ANSWER, var isVideoAuthor: Boolean = false ) : ListViewModel(application) { @@ -119,6 +119,9 @@ open class NewCommentViewModel( if (!imageUrls.isNullOrEmpty()) { requestMap["images"] = imageUrls } + if (gameCollectionId.isNotEmpty() && commentEntity != null) { + requestMap["root"] = rootCommentId + } val body = requestMap.createRequestBodyAny() val observable = @@ -154,6 +157,14 @@ open class NewCommentViewModel( api.postReplyToVideoComment(videoId, commentEntity.id, body) } } + + CommentType.GAME_COLLECTION, CommentType.GAME_COLLECTION_CONVERSATION -> { + if (commentEntity == null) { + api.postGameCollectionComment(gameCollectionId, body) + } else { + api.postReplyToGameCollectionComment(gameCollectionId, commentEntity.id, body) + } + } } // TODO Remove this apiResponse crap. @@ -330,6 +341,8 @@ open class NewCommentViewModel( private val communityId: String = "", private val videoId: String = "", private val questionId: String = "", + private val gameCollectionId: String = "", + private val rootCommentId: String = "", private val isVideoAuthor: Boolean = false, private val commentType: CommentType ) : ViewModelProvider.NewInstanceFactory() { @@ -342,7 +355,9 @@ open class NewCommentViewModel( communityId = communityId, videoId = videoId, questionId = questionId, + gameCollectionId = gameCollectionId, commentId = commentId, + rootCommentId = rootCommentId, commentType = commentType, isVideoAuthor = isVideoAuthor ) as T @@ -361,7 +376,10 @@ enum class CommentType { COMMUNITY_QUESTION_CONVERSATION, VIDEO, - VIDEO_CONVERSATION; + VIDEO_CONVERSATION, + + GAME_COLLECTION, + GAME_COLLECTION_CONVERSATION; fun isVideo(): Boolean { return (this == VIDEO || this == VIDEO_CONVERSATION) diff --git a/app/src/main/java/com/gh/gamecenter/qa/comment/StairsCommentFragment.kt b/app/src/main/java/com/gh/gamecenter/qa/comment/StairsCommentFragment.kt index 12be0a6886..bbad14693e 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/comment/StairsCommentFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/comment/StairsCommentFragment.kt @@ -41,6 +41,9 @@ class StairsCommentFragment : NewCommentFragment() { CommentType.COMMUNITY_QUESTION, CommentType.COMMUNITY_QUESTION_CONVERSATION -> "(问题详情-评论列表)" + + CommentType.GAME_COLLECTION, + CommentType.GAME_COLLECTION_CONVERSATION -> "(游戏单详情-评论列表)" } mAdapter = StairsCommentAdapter(requireContext(), mViewModel, true, this, this, entrance) } diff --git a/app/src/main/java/com/gh/gamecenter/qa/comment/StairsCommentViewHolder.kt b/app/src/main/java/com/gh/gamecenter/qa/comment/StairsCommentViewHolder.kt index d0777345a8..5ea550c3fd 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/comment/StairsCommentViewHolder.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/comment/StairsCommentViewHolder.kt @@ -298,6 +298,9 @@ class StairsCommentViewHolder( CommentType.COMMUNITY_QUESTION, CommentType.COMMUNITY_QUESTION_CONVERSATION -> "问题详情-评论管理" + + CommentType.GAME_COLLECTION, + CommentType.GAME_COLLECTION_CONVERSATION -> "游戏单详情-评论管理" } holder.binding.commentUserIcon.setOnClickListener { DirectUtils.directToHomeActivity( diff --git a/app/src/main/java/com/gh/gamecenter/qa/comment/base/BaseCommentAdapter.kt b/app/src/main/java/com/gh/gamecenter/qa/comment/base/BaseCommentAdapter.kt index 8a778f6f24..7d4fbf71c5 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/comment/base/BaseCommentAdapter.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/comment/base/BaseCommentAdapter.kt @@ -3,9 +3,9 @@ package com.gh.gamecenter.qa.comment.base import android.annotation.SuppressLint import android.content.Context import android.text.SpannableStringBuilder -import android.util.SparseBooleanArray import android.view.View import android.view.ViewGroup +import androidx.annotation.StringRes import androidx.appcompat.app.AppCompatActivity import androidx.databinding.DataBindingUtil import androidx.recyclerview.widget.GridLayoutManager @@ -213,7 +213,7 @@ abstract class BaseCommentAdapter( inner class CommentFooterViewHolder(var binding: ItemArticleDetailCommentFooterBinding) : RecyclerView.ViewHolder(binding.root) { - fun bindView(isLoading: Boolean, isNetworkError: Boolean, isOver: Boolean) { + fun bindView(isLoading: Boolean, isNetworkError: Boolean, isOver: Boolean, @StringRes loadOverHint: Int = R.string.load_over_hint) { when { isNetworkError -> { binding.progressBar.visibility = View.GONE @@ -221,7 +221,7 @@ abstract class BaseCommentAdapter( } isOver -> { binding.progressBar.visibility = View.GONE - binding.footerTv.setText(R.string.load_over_hint) + binding.footerTv.setText(loadOverHint) } isLoading -> { binding.progressBar.visibility = View.VISIBLE @@ -241,7 +241,8 @@ abstract class BaseCommentAdapter( fun bindView( article: ArticleDetailEntity? = null, questions: QuestionsDetailEntity? = null, - comment: CommentEntity? = null + comment: CommentEntity? = null, + gameCollection: Boolean? = null ) { binding.run { val commentCount = mViewModel.commentCount @@ -255,12 +256,24 @@ abstract class BaseCommentAdapter( comment != null -> { "全部回复" } + gameCollection != null -> { + "游戏单评论" + } else -> { "" } } + commentHintCountTv.text = NumberUtils.transSimpleCount(commentCount) + if (gameCollection != null) { + filterLatestTv.text = "最新" + filterOldestTv.text = "最早" + } else { + filterLatestTv.text = "倒序" + filterOldestTv.text = "正序" + } + filterLatestTv.setOnClickListener { mViewModel.changeSort(BaseCommentViewModel.SortType.LATEST) updateSortType() diff --git a/app/src/main/java/com/gh/gamecenter/qa/comment/base/BaseCommentFragment.kt b/app/src/main/java/com/gh/gamecenter/qa/comment/base/BaseCommentFragment.kt index 7abc55a1b2..49642d441c 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/comment/base/BaseCommentFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/comment/base/BaseCommentFragment.kt @@ -12,6 +12,7 @@ import com.gh.common.view.CustomDividerItemDecoration import com.gh.common.view.vertical_recycler.SnappingLinearLayoutManager import com.gh.gamecenter.R import com.gh.gamecenter.baselist.ListFragment +import com.gh.gamecenter.gamecollection.detail.conversation.GameCollectionCommentConversationFragment import com.gh.gamecenter.qa.comment.conversation.CommentConversationFragment abstract class BaseCommentFragment : ListFragment() { @@ -40,7 +41,8 @@ abstract class BaseCommentFragment : ListFragment< mListRv.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) - if (this@BaseCommentFragment is CommentConversationFragment) return + if (this@BaseCommentFragment is CommentConversationFragment + || this@BaseCommentFragment is GameCollectionCommentConversationFragment) return val firstCompletelyVisiblePosition = mLayoutManager.findFirstCompletelyVisibleItemPosition() if (RecyclerView.NO_POSITION == firstCompletelyVisiblePosition) return diff --git a/app/src/main/java/com/gh/gamecenter/qa/comment/base/BaseCommentViewModel.kt b/app/src/main/java/com/gh/gamecenter/qa/comment/base/BaseCommentViewModel.kt index b025d3e774..695ec77801 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/comment/base/BaseCommentViewModel.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/comment/base/BaseCommentViewModel.kt @@ -200,7 +200,7 @@ abstract class BaseCommentViewModel( } @SuppressLint("CheckResult") - fun deleteComment(entity: CommentEntity, callback: () -> Unit) { + open fun deleteComment(entity: CommentEntity, callback: () -> Unit) { val observable = when { videoId.isNotEmpty() -> { if (entity.me?.isModerator == true) { diff --git a/app/src/main/java/com/gh/gamecenter/qa/comment/conversation/CommentConversationFragment.kt b/app/src/main/java/com/gh/gamecenter/qa/comment/conversation/CommentConversationFragment.kt index ed25b34f9c..b4062c022d 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/comment/conversation/CommentConversationFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/comment/conversation/CommentConversationFragment.kt @@ -1,11 +1,11 @@ package com.gh.gamecenter.qa.comment.conversation import android.annotation.SuppressLint +import android.app.Activity import android.content.Intent import android.os.Bundle import android.view.LayoutInflater import android.view.View -import androidx.appcompat.app.AppCompatActivity import com.ethanhua.skeleton.Skeleton import com.gh.common.AppExecutor import com.gh.common.syncpage.SyncDataEntity @@ -66,12 +66,14 @@ class CommentConversationFragment : BaseCommentFragment { mApi.getQuestionCommentReply(questionId, commentId, currentSortType.value, page) } + gameCollectionId.isNotEmpty() -> { + mApi.getGameCollectionCommentReply(gameCollectionId, commentId, page) + } else -> null } } @@ -53,6 +57,9 @@ class CommentConversationViewModel(application: Application, questionId.isNotEmpty() -> { mApi.getCommunityQuestionComment(questionId, commentId) } + gameCollectionId.isNotEmpty() -> { + mApi.getGameCollectionComment(gameCollectionId, commentId) + } else -> null } ?: return single.subscribeOn(Schedulers.io()) @@ -87,6 +94,7 @@ class CommentConversationViewModel(application: Application, private val videoId: String = "", private val questionId: String = "", private val communityId: String = "", + private val gameCollectionId: String = "", private val commentId: String) : ViewModelProvider.NewInstanceFactory() { override fun create(modelClass: Class): T { @@ -96,6 +104,7 @@ class CommentConversationViewModel(application: Application, videoId = videoId, questionId = questionId, communityId = communityId, + gameCollectionId = gameCollectionId, commentId = commentId) as T } } diff --git a/app/src/main/java/com/gh/gamecenter/qa/dialog/MoreFunctionPanelDialog.kt b/app/src/main/java/com/gh/gamecenter/qa/dialog/MoreFunctionPanelDialog.kt index 19d80c5739..142d8af081 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/dialog/MoreFunctionPanelDialog.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/dialog/MoreFunctionPanelDialog.kt @@ -15,7 +15,7 @@ import com.gh.common.dialog.BaseDraggableDialogFragment import com.gh.common.util.* import com.gh.gamecenter.R import com.gh.gamecenter.databinding.DialogGameDetailMoreBinding -import com.gh.gamecenter.entity.ForumShareEntity +import com.gh.gamecenter.entity.NormalShareEntity import com.gh.gamecenter.entity.MenuItemEntity class MoreFunctionPanelDialog : BaseDraggableDialogFragment() { @@ -25,7 +25,7 @@ class MoreFunctionPanelDialog : BaseDraggableDialogFragment() { private var mTitle: String = "" private var mParentTag: String = "" private var mStatus: String = "" - private var mShareEntity: ForumShareEntity? = null + private var mShareEntity: NormalShareEntity? = null private var mShareUtils: ShareUtils? = null override fun onCreate(savedInstanceState: Bundle?) { @@ -197,7 +197,7 @@ class MoreFunctionPanelDialog : BaseDraggableDialogFragment() { activity: AppCompatActivity, menuItems: ArrayList, title: String, - share: ForumShareEntity, + share: NormalShareEntity, status: String, parentTag: String ) { diff --git a/app/src/main/java/com/gh/gamecenter/qa/editor/GameActivity.kt b/app/src/main/java/com/gh/gamecenter/qa/editor/GameActivity.kt index 0028f12c36..bddaa7d784 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/editor/GameActivity.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/editor/GameActivity.kt @@ -2,201 +2,22 @@ package com.gh.gamecenter.qa.editor import android.content.Context import android.content.Intent -import android.os.Bundle -import android.os.Message -import android.text.Editable -import android.text.TextWatcher -import android.view.View -import android.view.inputmethod.EditorInfo -import android.widget.EditText -import android.widget.TextView -import androidx.lifecycle.ViewModelProviders -import androidx.recyclerview.widget.RecyclerView -import com.gh.common.constant.Config +import androidx.core.os.bundleOf import com.gh.common.util.EntranceUtils -import com.gh.common.view.FixLinearLayoutManager -import com.gh.common.view.VerticalItemDecoration -import com.gh.gamecenter.BuildConfig -import com.gh.gamecenter.R -import com.gh.gamecenter.baselist.ListActivity -import com.gh.gamecenter.baselist.NormalListViewModel -import com.gh.gamecenter.entity.GameEntity -import com.gh.gamecenter.manager.UserManager -import com.gh.gamecenter.qa.entity.EditorInsertDefaultEntity -import com.gh.gamecenter.retrofit.Response -import com.gh.gamecenter.retrofit.RetrofitManager -import com.google.android.material.appbar.AppBarLayout -import com.halo.assistant.HaloApp -import com.lightgame.utils.Util_System_Keyboard -import io.reactivex.Observable -import io.reactivex.android.schedulers.AndroidSchedulers -import io.reactivex.schedulers.Schedulers -import kotterknife.bindView +import com.gh.gamecenter.NormalActivity -class GameActivity : ListActivity>() { +class GameActivity : NormalActivity() { - val searchEt by bindView(R.id.search_input) - val searchTv by bindView(R.id.search_button) - val searchBack by bindView(R.id.search_back) - val appBar by bindView(R.id.list_appbar) - val noneText by bindView(R.id.reuse_tv_none_data) - val defaultList by bindView(R.id.default_list) - val defaultListContainer by bindView(R.id.default_list_container) - - private var mAdapter: GameAdapter? = null - - private var mSearchKey: String = "" - - override fun handleMessage(msg: Message) { - if (msg.what == 1) { - if (mSearchKey.isEmpty()) { - clearPage() - } else { - search() - } - } - } - - override fun getLayoutId(): Int { - return R.layout.activity_editor_insert_game - } - - override fun isAutomaticLoad(): Boolean { - return false - } - - override fun getItemDecoration(): RecyclerView.ItemDecoration { - return VerticalItemDecoration(this, 8f, false) - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - val title = intent?.getStringExtra(EntranceUtils.KEY_NAVIGATION_TITLE) ?: INSERT_GAME_TITLE - setNavigationTitle(title) - noneText.text = "搜索结果为空" - mListLoading.visibility = View.GONE - mListRefresh.isEnabled = false -// appBar.addOnOffsetChangedListener(AppBarLayout.OnOffsetChangedListener { appBarLayout, verticalOffset -> -// val totalScrollRange = appBarLayout.totalScrollRange -// if (totalScrollRange == -verticalOffset) { -// Util_System_Keyboard.hideSoftKeyboard(this) -// } -// }) - - searchEt.setOnEditorActionListener { _, actionId, _ -> - if (actionId == EditorInfo.IME_ACTION_SEARCH) { - search() - } - false - } - - searchTv.setOnClickListener { - search() - Util_System_Keyboard.hideSoftKeyboard(this) - } - searchBack.setOnClickListener { - searchEt.setText("") - } - - searchEt.addTextChangedListener(object : TextWatcher { - override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { - } - - override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { - } - - override fun afterTextChanged(s: Editable) { - val newSearchKey = s.toString().trim() - if (newSearchKey != mSearchKey) { - mBaseHandler.removeMessages(1) - mSearchKey = newSearchKey - mBaseHandler.sendEmptyMessageDelayed(1, 500) - } - searchBack.visibility = if (newSearchKey.isNotEmpty()) View.VISIBLE - else View.GONE - } - }) - - mListRv.clearOnScrollListeners() - - // default open soft keyboard - searchEt.requestFocus() - Util_System_Keyboard.showSoftKeyboard(this, searchEt) - - if (title == SELECT_GAME_TITLE) initDefaultData() - } - - private fun clearPage() { - mAdapter?.setListData(ArrayList()) - mListLoading.visibility = View.GONE - mReuseNoData.visibility = View.GONE - mReuseNoConn.visibility = View.GONE - mListRv.visibility = View.GONE - } - - fun search() { - if (mSearchKey.isEmpty()) { - toast("请输入搜索关键字") - } else { - clearPage() - onLoadRefresh() - } - } - - override fun provideListAdapter(): GameAdapter? { - if (mAdapter == null) { - mAdapter = GameAdapter(this) - } - return mAdapter - } - - override fun provideDataObservable(page: Int): Observable>? { - return RetrofitManager - .getInstance(this).api - .getSearchGame(Config.SENSITIVE_API_HOST + "games:search?keyword=" + searchEt.text + "&view=digest" + "&channel=" + HaloApp.getInstance().channel + "&version" + BuildConfig.VERSION_NAME) - } - - override fun provideListViewModel(): NormalListViewModel { - val factory = NormalListViewModel.Factory(HaloApp.getInstance().application, this) - return ViewModelProviders.of(this, factory).get(NormalListViewModel::class.java) as NormalListViewModel - } - - private fun initDefaultData() { - RetrofitManager.getInstance(this).api - .getEditorInsertDefaultData(UserManager.getInstance().userId) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(object : Response>() { - override fun onResponse(response: List?) { - if (response == null || response.isEmpty()) return - // init default page - defaultList.layoutManager = FixLinearLayoutManager(baseContext) - defaultList.adapter = GameDefaultAdapter(this@GameActivity, response) - defaultListContainer.visibility = View.VISIBLE - } - }) - } - - override fun onLoadRefresh() { - super.onLoadRefresh() - mListRv.visibility = View.VISIBLE - } - - override fun onLoadEmpty() { - super.onLoadEmpty() - mListRv.visibility = View.VISIBLE - } - - override fun onLoadDone() { - super.onLoadDone() - mListRv.visibility = View.VISIBLE + override fun provideNormalIntent(): Intent { + return getTargetIntent(this, GameActivity::class.java, GameFragment::class.java) } companion object { fun getIntent(context: Context, title: String): Intent { - val intent = Intent(context, GameActivity::class.java) - intent.putExtra(EntranceUtils.KEY_NAVIGATION_TITLE, title) - return intent + val bundle = bundleOf( + EntranceUtils.KEY_NAVIGATION_TITLE to title + ) + return getTargetIntent(context, GameActivity::class.java, GameFragment::class.java, bundle) } const val INSERT_GAME_TITLE = "插入游戏" diff --git a/app/src/main/java/com/gh/gamecenter/qa/editor/GameAdapter.kt b/app/src/main/java/com/gh/gamecenter/qa/editor/GameAdapter.kt index 332bd4e242..735ed0b950 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/editor/GameAdapter.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/editor/GameAdapter.kt @@ -11,7 +11,7 @@ import com.gh.gamecenter.databinding.GameItemBinding import com.gh.gamecenter.entity.GameEntity import com.gh.gamecenter.game.GameItemViewHolder -class GameAdapter(context: Context) : ListAdapter(context) { +open class GameAdapter(context: Context) : ListAdapter(context) { public override fun setListData(updateData: MutableList?) { super.setListData(updateData) diff --git a/app/src/main/java/com/gh/gamecenter/qa/editor/GameFragment.kt b/app/src/main/java/com/gh/gamecenter/qa/editor/GameFragment.kt new file mode 100644 index 0000000000..8fa0824cef --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/qa/editor/GameFragment.kt @@ -0,0 +1,190 @@ +package com.gh.gamecenter.qa.editor + +import android.os.Bundle +import android.os.Message +import android.text.Editable +import android.text.TextWatcher +import android.view.View +import android.view.inputmethod.EditorInfo +import android.widget.EditText +import android.widget.TextView +import androidx.lifecycle.ViewModelProviders +import androidx.recyclerview.widget.RecyclerView +import com.gh.common.constant.Config +import com.gh.common.util.EntranceUtils +import com.gh.common.view.FixLinearLayoutManager +import com.gh.common.view.VerticalItemDecoration +import com.gh.gamecenter.BuildConfig +import com.gh.gamecenter.R +import com.gh.gamecenter.baselist.ListFragment +import com.gh.gamecenter.baselist.NormalListViewModel +import com.gh.gamecenter.entity.GameEntity +import com.gh.gamecenter.manager.UserManager +import com.gh.gamecenter.qa.entity.EditorInsertDefaultEntity +import com.gh.gamecenter.retrofit.Response +import com.gh.gamecenter.retrofit.RetrofitManager +import com.google.android.material.appbar.AppBarLayout +import com.halo.assistant.HaloApp +import com.lightgame.utils.Util_System_Keyboard +import io.reactivex.Observable +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.schedulers.Schedulers +import kotterknife.bindView + +open class GameFragment : ListFragment>() { + val searchEt by bindView(R.id.search_input) + val searchTv by bindView(R.id.search_button) + val searchBack by bindView(R.id.search_back) + val appBar by bindView(R.id.list_appbar) + val noneText by bindView(R.id.reuse_tv_none_data) + val defaultList by bindView(R.id.default_list) + val defaultListContainer by bindView(R.id.default_list_container) + + protected var mAdapter: GameAdapter? = null + + private var mSearchKey: String = "" + + override fun handleMessage(msg: Message) { + if (msg.what == 1) { + if (mSearchKey.isEmpty()) { + clearPage() + } else { + search() + } + } + } + + override fun getLayoutId(): Int { + return R.layout.activity_editor_insert_game + } + + override fun isAutomaticLoad(): Boolean { + return false + } + + override fun getItemDecoration(): RecyclerView.ItemDecoration? { + return VerticalItemDecoration(requireContext(), 8f, false) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val title = arguments?.getString(EntranceUtils.KEY_NAVIGATION_TITLE) ?: "" + if (title.isNotEmpty()) { + setNavigationTitle(title) + } + noneText.text = "搜索结果为空" + mListLoading?.visibility = View.GONE + mListRefresh?.isEnabled = false + + searchEt.setOnEditorActionListener { _, actionId, _ -> + if (actionId == EditorInfo.IME_ACTION_SEARCH) { + search() + } + false + } + + searchTv.setOnClickListener { + search() + Util_System_Keyboard.hideSoftKeyboard(requireActivity()) + } + searchBack.setOnClickListener { + searchEt.setText("") + } + + searchEt.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { + } + + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + } + + override fun afterTextChanged(s: Editable) { + val newSearchKey = s.toString().trim() + if (newSearchKey != mSearchKey) { + mBaseHandler.removeMessages(1) + mSearchKey = newSearchKey + mBaseHandler.sendEmptyMessageDelayed(1, 500) + } + searchBack.visibility = if (newSearchKey.isNotEmpty()) View.VISIBLE else View.GONE + } + }) + + mListRv.clearOnScrollListeners() + + // default open soft keyboard + if (isAutoShowKeyboard()) { + searchEt.requestFocus() + } + Util_System_Keyboard.showSoftKeyboard(requireActivity(), searchEt) + + if (title == GameActivity.SELECT_GAME_TITLE) initDefaultData() + } + + private fun clearPage() { + mAdapter?.setListData(ArrayList()) + mListLoading?.visibility = View.GONE + mReuseNoData?.visibility = View.GONE + mReuseNoConn?.visibility = View.GONE + mListRv.visibility = View.GONE + } + + fun search() { + if (mSearchKey.isEmpty()) { + toast("请输入搜索关键字") + } else { + clearPage() + onLoadRefresh() + } + } + + override fun provideListAdapter(): GameAdapter? { + if (mAdapter == null) { + mAdapter = GameAdapter(requireContext()) + } + return mAdapter + } + + override fun provideDataObservable(page: Int): Observable>? { + return RetrofitManager + .getInstance(requireContext()).api + .getSearchGame(Config.SENSITIVE_API_HOST + "games:search?keyword=" + searchEt.text + "&view=digest" + "&channel=" + HaloApp.getInstance().channel + "&version" + BuildConfig.VERSION_NAME) + } + + override fun provideListViewModel(): NormalListViewModel { + val factory = NormalListViewModel.Factory(HaloApp.getInstance().application, this) + return ViewModelProviders.of(this, factory).get(NormalListViewModel::class.java) as NormalListViewModel + } + + private fun initDefaultData() { + RetrofitManager.getInstance(requireContext()).api + .getEditorInsertDefaultData(UserManager.getInstance().userId) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(object : Response>() { + override fun onResponse(response: List?) { + if (response == null || response.isEmpty()) return + // init default page + defaultList.layoutManager = FixLinearLayoutManager(requireContext()) + defaultList.adapter = GameDefaultAdapter(requireContext(), response) + defaultListContainer.visibility = View.VISIBLE + } + }) + } + + override fun onLoadRefresh() { + super.onLoadRefresh() + mListRv.visibility = View.VISIBLE + } + + override fun onLoadEmpty() { + super.onLoadEmpty() + mListRv.visibility = View.VISIBLE + } + + override fun onLoadDone() { + super.onLoadDone() + mListRv.visibility = View.VISIBLE + } + + open fun isAutoShowKeyboard(): Boolean = true +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/qa/editor/InsertGameCollectionWrapperActivity.kt b/app/src/main/java/com/gh/gamecenter/qa/editor/InsertGameCollectionWrapperActivity.kt new file mode 100644 index 0000000000..0baf3555ef --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/qa/editor/InsertGameCollectionWrapperActivity.kt @@ -0,0 +1,48 @@ +package com.gh.gamecenter.qa.editor + +import android.content.Context +import android.content.Intent +import androidx.core.os.bundleOf +import androidx.fragment.app.Fragment +import com.gh.base.BaseActivity_TabLayout +import com.gh.common.util.EntranceUtils +import com.gh.gamecenter.R +import com.gh.gamecenter.collection.GamesCollectionFragment +import com.gh.gamecenter.manager.UserManager + +class InsertGameCollectionWrapperActivity : BaseActivity_TabLayout() { + + override fun getLayoutId(): Int = R.layout.activity_tablayout_no_title_viewpager + + override fun initFragmentList(fragments: MutableList?) { + fragments?.add( + GamesCollectionFragment().with( + bundleOf( + EntranceUtils.KEY_USER_ID to UserManager.getInstance().userId, + EntranceUtils.KEY_TYPE to GamesCollectionFragment.TYPE_USER, + EntranceUtils.KEY_INSERT_GAME_COLLECTION to true + ) + ) + ) + fragments?.add( + GamesCollectionFragment().with( + bundleOf( + EntranceUtils.KEY_USER_ID to UserManager.getInstance().userId, + EntranceUtils.KEY_TYPE to GamesCollectionFragment.TYPE_COLLECT, + EntranceUtils.KEY_INSERT_GAME_COLLECTION to true + ) + ) + ) + } + + override fun initTabTitleList(tabTitleList: MutableList?) { + tabTitleList?.add("我的游戏单") + tabTitleList?.add("收藏游戏单") + } + + companion object { + fun getIntent(context: Context): Intent { + return Intent(context, InsertGameCollectionWrapperActivity::class.java) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/qa/editor/OnLinkClickListener.kt b/app/src/main/java/com/gh/gamecenter/qa/editor/OnLinkClickListener.kt index ef565f6dff..ad169d2a6d 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/editor/OnLinkClickListener.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/editor/OnLinkClickListener.kt @@ -4,26 +4,27 @@ import android.content.Context import android.webkit.JavascriptInterface import com.gh.base.BaseActivity import com.gh.common.AppExecutor -import com.gh.common.DefaultJsApi import com.gh.common.util.* import com.gh.gamecenter.GameDetailActivity import com.gh.gamecenter.entity.CommunityEntity import com.gh.gamecenter.entity.MtaEvent import com.gh.gamecenter.entity.MyVideoEntity +import com.gh.gamecenter.gamecollection.detail.GameCollectionDetailActivity import com.gh.gamecenter.qa.answer.detail.SimpleAnswerDetailActivity import com.gh.gamecenter.qa.article.detail.ArticleDetailActivity import com.gh.gamecenter.qa.entity.EditorInsertEntity -import com.lightgame.utils.Utils /** * 富文本编辑框的链接回调 */ -class OnLinkClickListener(val context: Context, - val title: String = "", - val status: String = "", - val entrance: String, - val path: String, - val mtaEvent: MtaEvent? = null) { +class OnLinkClickListener( + val context: Context, + val title: String = "", + val status: String = "", + val entrance: String, + val path: String, + val mtaEvent: MtaEvent? = null +) { @JavascriptInterface fun onClick(content: String) { AppExecutor.uiExecutor.execute { @@ -31,25 +32,34 @@ class OnLinkClickListener(val context: Context, val insertEntity = GsonUtils.fromJson(content, EditorInsertEntity::class.java) when (insertEntity.type) { "answer" -> { - val intent = SimpleAnswerDetailActivity.getIntent(context, - insertEntity.id ?: "", - entrance, - "$path-链接") + val intent = SimpleAnswerDetailActivity.getIntent( + context, + insertEntity.id ?: "", + entrance, + "$path-链接" + ) context.startActivity(intent) } "community_article" -> { val community = CommunityEntity(insertEntity.communityId!!, "") - val intent = ArticleDetailActivity.getIntent(context, - community, - insertEntity.id!!, - entrance, - "$path-链接") + val intent = ArticleDetailActivity.getIntent( + context, + community, + insertEntity.id!!, + entrance, + "$path-链接" + ) context.startActivity(intent) } "game" -> { - GameDetailActivity.startGameDetailActivity(context, - insertEntity.id, - BaseActivity.mergeEntranceAndPath(entrance, "$path-链接")) + GameDetailActivity.startGameDetailActivity( + context, + insertEntity.id, + BaseActivity.mergeEntranceAndPath(entrance, "$path-链接") + ) + } + "game_collection" -> { + context.startActivity(GameCollectionDetailActivity.getIntent(context, insertEntity.id ?: "")) } } } @@ -62,7 +72,7 @@ class OnLinkClickListener(val context: Context, tryWithDefaultCatch { if (mtaEvent != null) MtaHelper.onEvent(mtaEvent.name, mtaEvent.key, mtaEvent.value) val videoEntity = GsonUtils.fromJson(content, MyVideoEntity::class.java) - clickToastByStatus(videoEntity.status){ + clickToastByStatus(videoEntity.status) { FullScreenVideoActivity.start(context, title, videoEntity.url, videoEntity.poster) } } @@ -71,7 +81,7 @@ class OnLinkClickListener(val context: Context, @JavascriptInterface fun onVideoClick(url: String, poster: String) { - clickToastByStatus(status){ + clickToastByStatus(status) { FullScreenVideoActivity.start(context, title, url, poster) } } diff --git a/app/src/main/java/com/gh/gamecenter/qa/entity/ArticleEntity.kt b/app/src/main/java/com/gh/gamecenter/qa/entity/ArticleEntity.kt index f7eb8ac1a8..cf617121c7 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/entity/ArticleEntity.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/entity/ArticleEntity.kt @@ -22,47 +22,47 @@ import kotlinx.android.parcel.Parcelize @Entity @Parcelize data class ArticleEntity( - @PrimaryKey - @SerializedName("_id") - var id: String = "", - var title: String = "", - var brief: String = "", - var active: Boolean = true, - var orderTag: Long = 0, - @TypeConverters(ListStringConverter::class) - var images: List = ArrayList(), - @TypeConverters(ImageInfoConverter::class) - @SerializedName("images_info") - var imagesInfo: List = ArrayList(), - @TypeConverters(CommunityVideoConverter::class) - var videos: List = ArrayList(), - var count: Count = Count(), - var community: CommunityEntity = CommunityEntity(), - var time: TimeEntity? = TimeEntity(), - var user: UserEntity = UserEntity(), - @Ignore - var read: Boolean = true, - @Ignore - var me: MeEntity = MeEntity(), - @Ignore - var commentable: Boolean = true, - @Ignore - var bbs: CommunityEntity = CommunityEntity(), - var des: String = "", - var url: String = "", - @TypeConverters(VideoInfoConverter::class) - @SerializedName("video_info") - var videoInfo: VideoInfo = VideoInfo(), - var poster: String = "", - var length: Long = 0, - @Ignore - var type: String = "", - var status: String = "", - var content: String = "", - @SyncIgnore // questions里的vote当前entity的vote冲突 - @TypeConverters(QuestionsConverter::class) - @SerializedName("question") - var questions: Questions = Questions() + @PrimaryKey + @SerializedName("_id") + var id: String = "", + var title: String = "", + var brief: String = "", + var active: Boolean = true, + var orderTag: Long = 0, + @TypeConverters(ListStringConverter::class) + var images: List = ArrayList(), + @TypeConverters(ImageInfoConverter::class) + @SerializedName("images_info") + var imagesInfo: List = ArrayList(), + @TypeConverters(CommunityVideoConverter::class) + var videos: List = ArrayList(), + var count: Count = Count(), + var community: CommunityEntity = CommunityEntity(), + var time: TimeEntity? = TimeEntity(), + var user: UserEntity = UserEntity(), + @Ignore + var read: Boolean = true, + @Ignore + var me: MeEntity = MeEntity(), + @Ignore + var commentable: Boolean = true, + @Ignore + var bbs: CommunityEntity = CommunityEntity(), + var des: String = "", + var url: String = "", + @TypeConverters(VideoInfoConverter::class) + @SerializedName("video_info") + var videoInfo: VideoInfo = VideoInfo(), + var poster: String = "", + var length: Long = 0, + @Ignore + var type: String = "", + var status: String = "", + var content: String = "", + @SyncIgnore // questions里的vote当前entity的vote冲突 + @TypeConverters(QuestionsConverter::class) + @SerializedName("question") + var questions: Questions = Questions() ) : Parcelable { fun getPassVideos(): List { @@ -147,13 +147,19 @@ data class ArticleEntity( @Parcelize data class Count( - @SyncPage(syncNames = [SyncFieldConstants.ARTICLE_COMMENT_COUNT, SyncFieldConstants.ANSWER_COMMENT_COUNT]) - var comment: Int = 0, - @SyncPage(syncNames = [SyncFieldConstants.ARTICLE_VOTE_COUNT, SyncFieldConstants.ANSWER_VOTE_COUNT]) - var vote: Int = 0, - var favorite: Int = 0, - @SyncPage(syncNames = [SyncFieldConstants.ANSWER_COMMENT_REPLY_COUNT]) - var reply: Int = 0) : Parcelable { + @SyncPage(syncNames = [SyncFieldConstants.ARTICLE_COMMENT_COUNT, SyncFieldConstants.ANSWER_COMMENT_COUNT]) + var comment: Int = 0, + @SyncPage(syncNames = [SyncFieldConstants.ARTICLE_VOTE_COUNT, SyncFieldConstants.ANSWER_VOTE_COUNT]) + var vote: Int = 0, + var favorite: Int = 0, + @SyncPage(syncNames = [SyncFieldConstants.ANSWER_COMMENT_REPLY_COUNT]) + var reply: Int = 0, + var game: Int = 0, + @SerializedName("game_played") + var playedGame: Int = 0, + var hot: Int = 0, + var share: Int = 0 +) : Parcelable { @SyncPage(syncNames = [SyncFieldConstants.ANSWER_COUNT]) var answer: Int = 0 @@ -181,7 +187,9 @@ data class Count( } @Parcelize -data class TimeEntity(var create: Long = 0, - var update: Long = 0, - var edit: Long = 0, - var upload: Long = 0) : Parcelable \ No newline at end of file +data class TimeEntity( + var create: Long = 0, + var update: Long = 0, + var edit: Long = 0, + var upload: Long = 0 +) : Parcelable \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/qa/entity/EditorInsertEntity.kt b/app/src/main/java/com/gh/gamecenter/qa/entity/EditorInsertEntity.kt index cae2ca5aa5..5d4ae3ff93 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/entity/EditorInsertEntity.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/entity/EditorInsertEntity.kt @@ -3,16 +3,19 @@ package com.gh.gamecenter.qa.entity import android.os.Parcelable import com.gh.common.util.eliminateDoubleQuote import com.gh.gamecenter.entity.GameEntity +import com.gh.gamecenter.entity.GamesCollectionEntity import kotlinx.android.parcel.Parcelize @Parcelize -data class EditorInsertEntity(var id: String? = "", - var communityId: String? = "", - var type: String? = "", - var title: String? = "", - var brief: String? = "", - var icon: String? = "", - var tags: List? = null) : Parcelable { +data class EditorInsertEntity( + var id: String? = "", + var communityId: String? = "", + var type: String? = "", + var title: String? = "", + var brief: String? = "", + var icon: String? = "", + var tags: List? = null +) : Parcelable { companion object { @JvmStatic @@ -48,5 +51,16 @@ data class EditorInsertEntity(var id: String? = "", entity.icon = game.icon return entity } + + @JvmStatic + fun transform(game: GamesCollectionEntity): EditorInsertEntity { + val entity = EditorInsertEntity() + entity.id = game.id + entity.type = "game_collection" + entity.title = game.title + entity.brief = game.intro + entity.icon = "https://static-web.ghzs.com/website-static/images/icon_game_collection.png" + return entity + } } } \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/qa/questions/detail/QuestionsDetailFragment.kt b/app/src/main/java/com/gh/gamecenter/qa/questions/detail/QuestionsDetailFragment.kt index 8bf3d17a15..02c96cabab 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/questions/detail/QuestionsDetailFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/questions/detail/QuestionsDetailFragment.kt @@ -33,7 +33,7 @@ import com.gh.gamecenter.SuggestionActivity import com.gh.gamecenter.baselist.ListAdapter import com.gh.gamecenter.baselist.ListFragment import com.gh.gamecenter.baselist.LoadType -import com.gh.gamecenter.entity.ForumShareEntity +import com.gh.gamecenter.entity.NormalShareEntity import com.gh.gamecenter.entity.MenuItemEntity import com.gh.gamecenter.entity.Permissions import com.gh.gamecenter.entity.SpecialColumn @@ -446,8 +446,8 @@ class QuestionsDetailFragment : } } - private fun getShareEntity(questionEntity: QuestionsDetailEntity): ForumShareEntity { - return ForumShareEntity( + private fun getShareEntity(questionEntity: QuestionsDetailEntity): NormalShareEntity { + return NormalShareEntity( id = questionEntity.id ?: "", shareUrl = if (isPublishEnv()) { getString(R.string.share_questions_url, questionEntity.id) diff --git a/app/src/main/java/com/gh/gamecenter/qa/questions/newdetail/NewQuestionDetailFragment.kt b/app/src/main/java/com/gh/gamecenter/qa/questions/newdetail/NewQuestionDetailFragment.kt index e1dd264230..eb9792a79b 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/questions/newdetail/NewQuestionDetailFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/questions/newdetail/NewQuestionDetailFragment.kt @@ -23,7 +23,7 @@ import com.gh.gamecenter.baselist.ListAdapter import com.gh.gamecenter.baselist.LoadType import com.gh.gamecenter.databinding.FragmentArticleDetailBinding import com.gh.gamecenter.entity.CommunityEntity -import com.gh.gamecenter.entity.ForumShareEntity +import com.gh.gamecenter.entity.NormalShareEntity import com.gh.gamecenter.entity.MenuItemEntity import com.gh.gamecenter.entity.Permissions import com.gh.gamecenter.eventbus.EBDeleteDetail @@ -529,8 +529,8 @@ class NewQuestionDetailFragment : } } - private fun getShareEntity(questionEntity: QuestionsDetailEntity): ForumShareEntity { - return ForumShareEntity( + private fun getShareEntity(questionEntity: QuestionsDetailEntity): NormalShareEntity { + return NormalShareEntity( id = questionEntity.id ?: "", shareUrl = if (isPublishEnv()) { getString(R.string.share_questions_url, questionEntity.id) diff --git a/app/src/main/java/com/gh/gamecenter/qa/video/detail/ForumVideoDetailFragment.kt b/app/src/main/java/com/gh/gamecenter/qa/video/detail/ForumVideoDetailFragment.kt index 16a1cb74d0..9c53a92203 100644 --- a/app/src/main/java/com/gh/gamecenter/qa/video/detail/ForumVideoDetailFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/qa/video/detail/ForumVideoDetailFragment.kt @@ -505,8 +505,8 @@ class ForumVideoDetailFragment : BaseFragment_TabLayout() { } } - private fun getShareEntity(): ForumShareEntity { - return ForumShareEntity( + private fun getShareEntity(): NormalShareEntity { + return NormalShareEntity( id = mForumVideoEntity?.id ?: "", shareUrl = if (isPublishEnv()) { "https://m.ghzs666.com/video/${mForumVideoEntity?.id}" diff --git a/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java b/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java index 1b8236d4e0..4fe22ad017 100644 --- a/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java +++ b/app/src/main/java/com/gh/gamecenter/retrofit/service/ApiService.java @@ -3270,4 +3270,154 @@ public interface ApiService { */ @PUT("api_go/teen_mode/password") Single putTeenModePassword(@Body RequestBody body); + + /** + * 游戏单列表(返回所有数据) + */ + @GET("users/{user_id}/game_lists?view=draft") + Observable> getGameCollectionList(@Path("user_id") String userId); + + /** + * 创建游戏单 + */ + @POST("game_lists") + Observable createGameCollection(@Body RequestBody body); + + /** + * 编辑游戏单 + */ + @PUT("game_lists/{game_list_id}") + Observable patchGameCollection(@Body RequestBody body, @Path("game_list_id") String id); + + /** + * 游戏单详情(用户编辑自己的游戏单时调用) + */ + @GET("game_lists/{game_list_id}?view=draft") + Observable getGameCollectionDetailForDraft(@Path("game_list_id") String id); + + /** + * 删除单条游戏单数据 + */ + @DELETE("game_lists/{game_list_id}") + Observable deleteGameCollection(@Path("game_list_id") String id); + + /** + * 提交审核 (投稿) + */ + @POST("game_lists/{game_list_id}:submit") + Observable publishGameCollection(@Path("game_list_id") String id); + + /** + * 用户游戏单列表 + */ + @GET("users/{user_id}/game_lists") + Single> getUserGameCollectionList(@Path("user_id") String userId, @QueryMap Map params); + + /** + * 游戏单详情 + */ + @GET("game_lists/{game_list_id}") + Single getGameCollectionDetail(@Path("game_list_id") String id); + + /** + * 获取游戏单广场列表 + */ + @GET("game_lists") + Single> getGameCollectionSquareList(@Query("view") String view, @Query("tag_id") String tagId, @Query("page") int page, @Query("page_size") int pageSize); + + /** + * 获取游戏单标签列表 + */ + @GET("game_lists/tags") + Single> getGameCollectionTagList(); + + /** + * 获取用户收藏的游戏单列表 + */ + @GET("users/{user_id}/favorites/game_list") + Single> getFavoriteGameCollectionList(@Path("user_id") String userId); + + /** + * 添加游戏单收藏 + */ + @POST("users/{user_id}/favorites/game_list/{game_list_id}") + Single favoriteGameCollection(@Path("user_id") String userId, @Path("game_list_id") String id); + + /** + * 取消游戏单收藏 + */ + @DELETE("users/{user_id}/favorites/game_list/{game_list_id}") + Single deleteFavoriteGameCollection(@Path("user_id") String userId, @Path("game_list_id") String id); + + /** + * 点赞游戏单 + */ + @POST("game_lists/{game_list_id}:vote") + Single voteGameCollection(@Path("game_list_id") String id); + + /** + * 取消点赞游戏单 + */ + @POST("game_lists/{game_list_id}:unvote") + Single unVoteGameCollection(@Path("game_list_id") String id); + + /** + * 分享游戏单统计 + */ + @POST("game_lists/{game_list_id}:share") + Single shareGameCollection(@Path("game_list_id") String gameCollectionId); + + /** + * 获取首页tab游戏单广场列表 + */ + @GET("game_lists?view=hot") + Single> getHomeGameCollectionSquareList(@Query("random") String id, @Query("page") int page, @Query("page_size") int pageSize); + + /** + * 游戏单评论列表 + */ + @GET("api_go/game_list/{game_list_id}/comment") + Single> getGameCollectionComments(@Path("game_list_id") String id, @Query("page") int page, @Query("sort") String sort); + + /** + * 获取单条游戏单评论 + */ + @GET("api_go/game_list/{game_list_id}/comment/{comment_id}") + Single getGameCollectionComment(@Path("game_list_id") String gameCollectionId, @Path("comment_id") String commentId); + + /** + * 获取游戏单评论的回复列表 + */ + @GET("api_go/game_list/{game_list_id}/comment/{comment_id}/reply") + Single> getGameCollectionCommentReply(@Path("game_list_id") String gameCollectionId, @Path("comment_id") String commentId, @Query("page") int page); + + /** + * 游戏单添加评论 + */ + @POST("api_go/game_list/{game_list_id}/comment") + Observable postGameCollectionComment(@Path("game_list_id") String gameCollectionId, @Body RequestBody body); + + /** + * 点赞游戏单评论 + */ + @POST("api_go/game_list/{game_list_id}/comment/{comment_id}/like") + Single voteGameCollectionComment(@Path("game_list_id") String gameCollectionId, @Path("comment_id") String commentId); + + /** + * 举报游戏单评论 + */ + @POST("api_go/game_list/{game_list_id}/comment/{comment_id}/report") + Observable reportGameCollectionComment(@Path("game_list_id") String gameCollectionId, @Path("comment_id") String commentId, @Body RequestBody body); + + /** + * 回复游戏单评论 + */ + @POST("api_go/game_list/{game_list_id}/comment/{comment_id}") + Observable postReplyToGameCollectionComment(@Path("game_list_id") String gameCollectionId, @Path("comment_id") String commentId, @Body RequestBody body); + + /** + * 删除游戏单评论 + */ + @DELETE("api_go/game_list/{game_list_id}/comment/{comment_id}") + Observable deleteGameCollectionComment(@Path("game_list_id") String gameCollectionId, @Path("comment_id") String commentId); } \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/room/AppDatabase.java b/app/src/main/java/com/gh/gamecenter/room/AppDatabase.java index 71e21a83aa..55eab163bb 100644 --- a/app/src/main/java/com/gh/gamecenter/room/AppDatabase.java +++ b/app/src/main/java/com/gh/gamecenter/room/AppDatabase.java @@ -14,6 +14,7 @@ import com.gh.common.videolog.VideoRecordDao; import com.gh.common.videolog.VideoRecordEntity; import com.gh.gamecenter.entity.CommentDraft; import com.gh.gamecenter.entity.ForumEntity; +import com.gh.gamecenter.entity.GameCollectionDraft; import com.gh.gamecenter.entity.HomePluggableFilterEntity; import com.gh.gamecenter.entity.SignEntity; import com.gh.gamecenter.entity.SimulatorGameRecordEntity; @@ -21,13 +22,16 @@ import com.gh.gamecenter.qa.entity.AnswerEntity; import com.gh.gamecenter.room.converter.ApkArrayListConverter; import com.gh.gamecenter.room.converter.MeConverter; import com.gh.gamecenter.room.converter.SimpleGameConverter; +import com.gh.gamecenter.room.converter.SimpleGameListConverter; import com.gh.gamecenter.room.converter.SimulatorConverter; import com.gh.gamecenter.room.converter.StringArrayListConverter; +import com.gh.gamecenter.room.converter.TagInfoListConverter; import com.gh.gamecenter.room.converter.TagStyleListConverter; import com.gh.gamecenter.room.converter.VideoInfoConverter; import com.gh.gamecenter.room.dao.AnswerDao; import com.gh.gamecenter.room.dao.CommentDraftDao; import com.gh.gamecenter.room.dao.ForumDao; +import com.gh.gamecenter.room.dao.GameCollectionDraftDao; import com.gh.gamecenter.room.dao.HomePluggableFilterDao; import com.gh.gamecenter.room.dao.SignDao; import com.gh.gamecenter.room.dao.SimulatorGameDao; @@ -45,7 +49,8 @@ import com.gh.gamecenter.video.upload.UploadEntity; HomePluggableFilterEntity.class, VideoRecordEntity.class, SimulatorGameRecordEntity.class, - ForumEntity.class}, version = 20, exportSchema = false) + ForumEntity.class, + GameCollectionDraft.class}, version = 21, exportSchema = false) @TypeConverters({ StringArrayListConverter.class, TagStyleListConverter.class, @@ -53,7 +58,9 @@ import com.gh.gamecenter.video.upload.UploadEntity; ApkArrayListConverter.class, SimpleGameConverter.class, VideoInfoConverter.class, - MeConverter.class + MeConverter.class, + SimpleGameListConverter.class, + TagInfoListConverter.class }) public abstract class AppDatabase extends RoomDatabase { @@ -73,6 +80,8 @@ public abstract class AppDatabase extends RoomDatabase { public abstract ForumDao forumDao(); + public abstract GameCollectionDraftDao gameCollectionDraftDao(); + private static AppDatabase sInstance; private static final String DATABASE_NAME = "gh-db"; @@ -218,6 +227,13 @@ public abstract class AppDatabase extends RoomDatabase { } }; + static final Migration MIGRATION_20_21 = new Migration(20, 21) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase database) { + database.execSQL("CREATE TABLE GameCollectionDraft(primaryKey TEXT NOT NULL PRIMARY KEY, tags TEXT DEFAULT '', games TEXT DEFAULT '' , title TEXT NOT NULL DEFAULT '', intro TEXT NOT NULL DEFAULT '', cover TEXT NOT NULL DEFAULT '', display TEXT NOT NULL DEFAULT '')"); + } + }; + private static AppDatabase buildDatabase(Context context) { return Room.databaseBuilder(context, AppDatabase.class, DATABASE_NAME) .addMigrations( @@ -237,7 +253,8 @@ public abstract class AppDatabase extends RoomDatabase { MIGRATION_16_17, MIGRATION_17_18, MIGRATION_18_19, - MIGRATION_19_20 + MIGRATION_19_20, + MIGRATION_20_21 ) // 不允许主线程查询 .allowMainThreadQueries() diff --git a/app/src/main/java/com/gh/gamecenter/room/converter/SimpleGameListConverter.kt b/app/src/main/java/com/gh/gamecenter/room/converter/SimpleGameListConverter.kt new file mode 100644 index 0000000000..e439911df1 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/room/converter/SimpleGameListConverter.kt @@ -0,0 +1,20 @@ +package com.gh.gamecenter.room.converter + +import androidx.room.TypeConverter +import com.gh.common.util.toJson +import com.gh.common.util.toObject +import com.gh.gamecenter.entity.ApkEntity +import com.gh.gamecenter.entity.SimpleGame + +class SimpleGameListConverter { + @TypeConverter + fun toSimpleGameString(data: ArrayList?): String { + return data?.toJson() ?: "" + } + + @TypeConverter + fun toSimpleGameEntity(data: String?): ArrayList { + val apkArray = data?.toObject() ?: arrayOf() + return ArrayList(apkArray.toList()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/room/converter/TagInfoListConverter.kt b/app/src/main/java/com/gh/gamecenter/room/converter/TagInfoListConverter.kt new file mode 100644 index 0000000000..9f07ce30c4 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/room/converter/TagInfoListConverter.kt @@ -0,0 +1,21 @@ +package com.gh.gamecenter.room.converter + +import androidx.room.TypeConverter +import com.gh.common.util.toJson +import com.gh.common.util.toObject +import com.gh.gamecenter.entity.ApkEntity +import com.gh.gamecenter.entity.SimpleGame +import com.gh.gamecenter.entity.TagInfoEntity + +class TagInfoListConverter { + @TypeConverter + fun toTagInfoString(data: ArrayList?): String { + return data?.toJson() ?: "" + } + + @TypeConverter + fun toTagInfoEntity(data: String?): ArrayList { + val array = data?.toObject() ?: arrayOf() + return ArrayList(array.toList()) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/room/dao/GameCollectionDraftDao.kt b/app/src/main/java/com/gh/gamecenter/room/dao/GameCollectionDraftDao.kt new file mode 100644 index 0000000000..8c9365cd08 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/room/dao/GameCollectionDraftDao.kt @@ -0,0 +1,21 @@ +package com.gh.gamecenter.room.dao + +import androidx.room.* +import com.gh.gamecenter.entity.GameCollectionDraft +import io.reactivex.Single + +@Dao +interface GameCollectionDraftDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun addDraft(entity: GameCollectionDraft) + + @Query("select * from GameCollectionDraft") + fun getAllDraft(): Single> + + @Query("select * from GameCollectionDraft") + fun getDrafts(): List + + @Delete + fun delete(list: List) +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/room/dao/GamesCollectionDao.kt b/app/src/main/java/com/gh/gamecenter/room/dao/GamesCollectionDao.kt new file mode 100644 index 0000000000..d827f41249 --- /dev/null +++ b/app/src/main/java/com/gh/gamecenter/room/dao/GamesCollectionDao.kt @@ -0,0 +1,18 @@ +package com.gh.gamecenter.room.dao + +import androidx.room.* +import com.gh.gamecenter.entity.GamesCollectionEntity +import io.reactivex.Single + +@Dao +interface GamesCollectionDao { + + @Insert(onConflict = OnConflictStrategy.REPLACE) + fun addGamesCollection(gamesCollection: GamesCollectionEntity) + + @Query("select * from GamesCollectionEntity order by orderTag desc limit :pageSize offset :offset") + fun getGamesCollectionWithOffset(pageSize: Int, offset: Int): Single> + + @Delete + fun deleteGamesCollection(gamesCollection: GamesCollectionEntity) +} \ No newline at end of file diff --git a/app/src/main/java/com/gh/gamecenter/servers/GameServersActivity.kt b/app/src/main/java/com/gh/gamecenter/servers/GameServersActivity.kt index c3be91738e..84e7c59478 100644 --- a/app/src/main/java/com/gh/gamecenter/servers/GameServersActivity.kt +++ b/app/src/main/java/com/gh/gamecenter/servers/GameServersActivity.kt @@ -6,6 +6,7 @@ import android.os.Bundle import android.view.View import android.widget.CheckedTextView import android.widget.RelativeLayout +import androidx.appcompat.widget.Toolbar import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.viewpager.widget.ViewPager @@ -23,6 +24,7 @@ import kotterknife.bindView class GameServersActivity : ToolBarActivity() { + private val mToolbar by bindView(R.id.normal_toolbar) private val mServersTest by bindView(R.id.server_test) private val mServersPublish by bindView(R.id.server_publish) private val mViewpager by bindView(R.id.viewpager) @@ -58,6 +60,7 @@ class GameServersActivity : ToolBarActivity() { } }) + mToolbar.setNavigationOnClickListener { finish() } mServersPublish.isChecked = true mServersTest.setTextColor(DrawableView.getSelectorColorStyle(R.color.black, R.color.theme_font)) mServersPublish.setTextColor(DrawableView.getSelectorColorStyle(R.color.black, R.color.theme_font)) diff --git a/app/src/main/java/com/gh/gamecenter/servers/GameServersContentFragment.kt b/app/src/main/java/com/gh/gamecenter/servers/GameServersContentFragment.kt index 36959a88ba..380ab23402 100644 --- a/app/src/main/java/com/gh/gamecenter/servers/GameServersContentFragment.kt +++ b/app/src/main/java/com/gh/gamecenter/servers/GameServersContentFragment.kt @@ -220,14 +220,22 @@ class GameServersContentFragment : BaseFragment() { onRefreshPage() } - mBinding?.recyclerView?.viewTreeObserver?.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { - override fun onGlobalLayout() { - mLayoutManager?.scrollToPosition(1) - if (mLayoutManager?.findFirstVisibleItemPosition() == 1) { - mBinding?.recyclerView?.viewTreeObserver?.removeOnGlobalLayoutListener(this) + mBinding?.recyclerView?.run { + val listener = object : ViewTreeObserver.OnGlobalLayoutListener { + override fun onGlobalLayout() { + mLayoutManager?.scrollToPosition(1) + if (mLayoutManager?.findFirstVisibleItemPosition() == 1) { + viewTreeObserver?.removeOnGlobalLayoutListener(this) + } } } - }) + viewTreeObserver?.addOnGlobalLayoutListener(listener) + postDelayed({ + tryCatchInRelease { + mBinding?.recyclerView?.viewTreeObserver?.removeOnGlobalLayoutListener(listener) + } + }, 2000) + } } diff --git a/app/src/main/java/com/gh/gamecenter/wxapi/WXEntryActivity.java b/app/src/main/java/com/gh/gamecenter/wxapi/WXEntryActivity.java index d2bad101ae..b0803f47e6 100644 --- a/app/src/main/java/com/gh/gamecenter/wxapi/WXEntryActivity.java +++ b/app/src/main/java/com/gh/gamecenter/wxapi/WXEntryActivity.java @@ -82,6 +82,19 @@ public class WXEntryActivity extends Activity implements IWXAPIEventHandler, WeC ShareUtils.shareEntrance == ShareUtils.ShareEntrance.communityArticle || ShareUtils.shareEntrance == ShareUtils.ShareEntrance.video) { NewLogUtils.logShareResult(true); + } else if (ShareUtils.shareEntrance == ShareUtils.ShareEntrance.gameCollection) { + String shareType; + if ("wechat_friend".equals(ShareUtils.shareType)) { + shareType = "微信"; + } else { + shareType = "朋友圈"; + } + NewLogUtils.logViewOrClickGameCollectionDetail( + "click_game_collect_detail_favorite_success", + ShareUtils.shareEntity.getShareTitle(), + ShareUtils.resourceId, + shareType + ); } } else { if (baseResp instanceof SendAuth.Resp) { diff --git a/app/src/main/res/color/game_collection_rg_button_selector.xml b/app/src/main/res/color/game_collection_rg_button_selector.xml new file mode 100644 index 0000000000..948223d8e9 --- /dev/null +++ b/app/src/main/res/color/game_collection_rg_button_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/game_collection_tag_text_color_selector.xml b/app/src/main/res/color/game_collection_tag_text_color_selector.xml new file mode 100644 index 0000000000..ec1c4419b0 --- /dev/null +++ b/app/src/main/res/color/game_collection_tag_text_color_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-xxhdpi/ic_share_copy.webp b/app/src/main/res/drawable-xxhdpi/ic_share_copy.webp new file mode 100644 index 0000000000..86256469ff Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_share_copy.webp differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_share_qq.webp b/app/src/main/res/drawable-xxhdpi/ic_share_qq.webp new file mode 100644 index 0000000000..6dee908c5f Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_share_qq.webp differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_share_qq_zone.webp b/app/src/main/res/drawable-xxhdpi/ic_share_qq_zone.webp new file mode 100644 index 0000000000..cd4ac32236 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_share_qq_zone.webp differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_share_sms.webp b/app/src/main/res/drawable-xxhdpi/ic_share_sms.webp new file mode 100644 index 0000000000..45d020ce5c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_share_sms.webp differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_share_wechat.webp b/app/src/main/res/drawable-xxhdpi/ic_share_wechat.webp new file mode 100644 index 0000000000..77d9dc3a9a Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_share_wechat.webp differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_share_wechat_moments.webp b/app/src/main/res/drawable-xxhdpi/ic_share_wechat_moments.webp new file mode 100644 index 0000000000..32c3b3b4ac Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_share_wechat_moments.webp differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_share_weibo.webp b/app/src/main/res/drawable-xxhdpi/ic_share_weibo.webp new file mode 100644 index 0000000000..9afadd7b15 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_share_weibo.webp differ diff --git a/app/src/main/res/drawable-xxhdpi/icon_editor_link_game_collection.webp b/app/src/main/res/drawable-xxhdpi/icon_editor_link_game_collection.webp new file mode 100644 index 0000000000..4fe08c4894 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/icon_editor_link_game_collection.webp differ diff --git a/app/src/main/res/drawable-xxhdpi/icon_game_collection.webp b/app/src/main/res/drawable-xxhdpi/icon_game_collection.webp new file mode 100644 index 0000000000..c5daff48b2 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/icon_game_collection.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/game_collection_comment.webp b/app/src/main/res/drawable-xxxhdpi/game_collection_comment.webp new file mode 100644 index 0000000000..de1f777744 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/game_collection_comment.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/game_collection_square_head.webp b/app/src/main/res/drawable-xxxhdpi/game_collection_square_head.webp new file mode 100644 index 0000000000..5ad4777985 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/game_collection_square_head.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/game_collection_vote.webp b/app/src/main/res/drawable-xxxhdpi/game_collection_vote.webp new file mode 100644 index 0000000000..a828f98d66 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/game_collection_vote.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_choose_game.webp b/app/src/main/res/drawable-xxxhdpi/ic_choose_game.webp new file mode 100644 index 0000000000..a66d35ceff Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_choose_game.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_choose_game_del.webp b/app/src/main/res/drawable-xxxhdpi/ic_choose_game_del.webp new file mode 100644 index 0000000000..1f436ab9ee Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_choose_game_del.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_choose_games_drag.png b/app/src/main/res/drawable-xxxhdpi/ic_choose_games_drag.png new file mode 100644 index 0000000000..c0caeb6fb2 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_choose_games_drag.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_choose_games_top.png b/app/src/main/res/drawable-xxxhdpi/ic_choose_games_top.png new file mode 100644 index 0000000000..5b68501e04 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_choose_games_top.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_chosen.webp b/app/src/main/res/drawable-xxxhdpi/ic_chosen.webp new file mode 100644 index 0000000000..5e7f593a00 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_chosen.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_chosen_big.webp b/app/src/main/res/drawable-xxxhdpi/ic_chosen_big.webp new file mode 100644 index 0000000000..5d6df01296 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_chosen_big.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_create_game_collection.webp b/app/src/main/res/drawable-xxxhdpi/ic_create_game_collection.webp new file mode 100644 index 0000000000..3b929d90fe Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_create_game_collection.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_game_collection_del.webp b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_del.webp new file mode 100644 index 0000000000..1bc9c423b6 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_del.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_game_collection_des.webp b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_des.webp new file mode 100644 index 0000000000..c55413d099 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_des.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_game_collection_des_blue.webp b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_des_blue.webp new file mode 100644 index 0000000000..188a37e5e3 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_des_blue.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_game_collection_des_gray.webp b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_des_gray.webp new file mode 100644 index 0000000000..5629d8cffd Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_des_gray.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_game_collection_detail_share_bottom_bar.png b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_detail_share_bottom_bar.png new file mode 100644 index 0000000000..309f81d3b3 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_detail_share_bottom_bar.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_game_collection_draft.webp b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_draft.webp new file mode 100644 index 0000000000..21687bca5d Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_draft.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_game_collection_edit.webp b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_edit.webp new file mode 100644 index 0000000000..1041793657 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_edit.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_game_collection_fail.webp b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_fail.webp new file mode 100644 index 0000000000..96b7d50a9e Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_fail.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_game_collection_hot.webp b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_hot.webp new file mode 100644 index 0000000000..1ac9abd47d Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_hot.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_game_collection_more.webp b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_more.webp new file mode 100644 index 0000000000..ab49e2e6d9 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_more.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_game_collection_pending.webp b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_pending.webp new file mode 100644 index 0000000000..03c38aeb9e Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_pending.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_game_collection_private.webp b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_private.webp new file mode 100644 index 0000000000..d0b58d7a9f Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_private.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_game_collection_publish.webp b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_publish.webp new file mode 100644 index 0000000000..6004ae13ca Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_publish.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_game_collection_square.webp b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_square.webp new file mode 100644 index 0000000000..3d451223cc Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_square.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_game_collection_square_amway_go.webp b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_square_amway_go.webp new file mode 100644 index 0000000000..650b241e80 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_square_amway_go.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_game_collection_square_light.webp b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_square_light.webp new file mode 100644 index 0000000000..831b141f80 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_square_light.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_game_collection_tag_close.png b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_tag_close.png new file mode 100644 index 0000000000..98a1e5dfc1 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_tag_close.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_game_collection_upload.webp b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_upload.webp new file mode 100644 index 0000000000..65d14fb5fa Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_game_collection_upload.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_menu_game_collection_square.webp b/app/src/main/res/drawable-xxxhdpi/ic_menu_game_collection_square.webp new file mode 100644 index 0000000000..f7f219813a Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_menu_game_collection_square.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_official.webp b/app/src/main/res/drawable-xxxhdpi/ic_official.webp new file mode 100644 index 0000000000..8d0ac71c17 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_official.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_official_big.webp b/app/src/main/res/drawable-xxxhdpi/ic_official_big.webp new file mode 100644 index 0000000000..d17b07f8c5 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_official_big.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/icon_add_games.webp b/app/src/main/res/drawable-xxxhdpi/icon_add_games.webp new file mode 100644 index 0000000000..fc412c8bba Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/icon_add_games.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/pic_game_collection_only_myself.webp b/app/src/main/res/drawable-xxxhdpi/pic_game_collection_only_myself.webp new file mode 100644 index 0000000000..2c6000b27c Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/pic_game_collection_only_myself.webp differ diff --git a/app/src/main/res/drawable-xxxhdpi/pic_my_game_guide.webp b/app/src/main/res/drawable-xxxhdpi/pic_my_game_guide.webp new file mode 100644 index 0000000000..56f302771f Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/pic_my_game_guide.webp differ diff --git a/app/src/main/res/drawable/background_shape_white_alpha_10_radius_2.xml b/app/src/main/res/drawable/background_shape_white_alpha_10_radius_2.xml new file mode 100644 index 0000000000..4bdd4e68eb --- /dev/null +++ b/app/src/main/res/drawable/background_shape_white_alpha_10_radius_2.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_border_blue_radius_2.xml b/app/src/main/res/drawable/bg_border_blue_radius_2.xml new file mode 100644 index 0000000000..a32c663798 --- /dev/null +++ b/app/src/main/res/drawable/bg_border_blue_radius_2.xml @@ -0,0 +1,13 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_game_collection_hot_container.xml b/app/src/main/res/drawable/bg_game_collection_hot_container.xml new file mode 100644 index 0000000000..ed2cefd0ca --- /dev/null +++ b/app/src/main/res/drawable/bg_game_collection_hot_container.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_game_collection_square.xml b/app/src/main/res/drawable/bg_game_collection_square.xml new file mode 100644 index 0000000000..3349ffadb0 --- /dev/null +++ b/app/src/main/res/drawable/bg_game_collection_square.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_game_collection_tag_select.xml b/app/src/main/res/drawable/bg_game_collection_tag_select.xml new file mode 100644 index 0000000000..4e59394287 --- /dev/null +++ b/app/src/main/res/drawable/bg_game_collection_tag_select.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_game_collection_tag_unselect.xml b/app/src/main/res/drawable/bg_game_collection_tag_unselect.xml new file mode 100644 index 0000000000..0683c258fc --- /dev/null +++ b/app/src/main/res/drawable/bg_game_collection_tag_unselect.xml @@ -0,0 +1,7 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_game_collection_user_container.xml b/app/src/main/res/drawable/bg_game_collection_user_container.xml new file mode 100644 index 0000000000..4bfcfd216a --- /dev/null +++ b/app/src/main/res/drawable/bg_game_collection_user_container.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_game_collectionchange_poster.xml b/app/src/main/res/drawable/bg_game_collectionchange_poster.xml new file mode 100644 index 0000000000..fa06805b62 --- /dev/null +++ b/app/src/main/res/drawable/bg_game_collectionchange_poster.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_my_game_collection_btn.xml b/app/src/main/res/drawable/bg_my_game_collection_btn.xml new file mode 100644 index 0000000000..f4e094865a --- /dev/null +++ b/app/src/main/res/drawable/bg_my_game_collection_btn.xml @@ -0,0 +1,8 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_shape_f2_alpha_10_radius_8.xml b/app/src/main/res/drawable/bg_shape_f2_alpha_10_radius_8.xml new file mode 100644 index 0000000000..4fc1c93493 --- /dev/null +++ b/app/src/main/res/drawable/bg_shape_f2_alpha_10_radius_8.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_shape_f5_radius_2.xml b/app/src/main/res/drawable/bg_shape_f5_radius_2.xml new file mode 100644 index 0000000000..2aa26231b8 --- /dev/null +++ b/app/src/main/res/drawable/bg_shape_f5_radius_2.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_shape_f5_radius_6.xml b/app/src/main/res/drawable/bg_shape_f5_radius_6.xml new file mode 100644 index 0000000000..71a09a8af3 --- /dev/null +++ b/app/src/main/res/drawable/bg_shape_f5_radius_6.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_round_1a2496ff.xml b/app/src/main/res/drawable/button_round_1a2496ff.xml new file mode 100644 index 0000000000..59dd042258 --- /dev/null +++ b/app/src/main/res/drawable/button_round_1a2496ff.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_round_black_alpha_10.xml b/app/src/main/res/drawable/button_round_black_alpha_10.xml new file mode 100644 index 0000000000..b2dd803ce5 --- /dev/null +++ b/app/src/main/res/drawable/button_round_black_alpha_10.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_round_black_alpha_30.xml b/app/src/main/res/drawable/button_round_black_alpha_30.xml new file mode 100644 index 0000000000..d69c5b6616 --- /dev/null +++ b/app/src/main/res/drawable/button_round_black_alpha_30.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/button_round_white_alpha_10.xml b/app/src/main/res/drawable/button_round_white_alpha_10.xml new file mode 100644 index 0000000000..20b04b477d --- /dev/null +++ b/app/src/main/res/drawable/button_round_white_alpha_10.xml @@ -0,0 +1,9 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/game_collection_detail_poster_mask.xml b/app/src/main/res/drawable/game_collection_detail_poster_mask.xml new file mode 100644 index 0000000000..2ef9289b3b --- /dev/null +++ b/app/src/main/res/drawable/game_collection_detail_poster_mask.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/game_collection_poster_mask.xml b/app/src/main/res/drawable/game_collection_poster_mask.xml new file mode 100644 index 0000000000..43f2df4e50 --- /dev/null +++ b/app/src/main/res/drawable/game_collection_poster_mask.xml @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/game_collection_tag_selector.xml b/app/src/main/res/drawable/game_collection_tag_selector.xml new file mode 100644 index 0000000000..dd68f2d7fe --- /dev/null +++ b/app/src/main/res/drawable/game_collection_tag_selector.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/game_collection_vote_selector.xml b/app/src/main/res/drawable/game_collection_vote_selector.xml new file mode 100644 index 0000000000..a39bd3fab7 --- /dev/null +++ b/app/src/main/res/drawable/game_collection_vote_selector.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/progressbar_game_collection.xml b/app/src/main/res/drawable/progressbar_game_collection.xml new file mode 100644 index 0000000000..f36ccf6bc0 --- /dev/null +++ b/app/src/main/res/drawable/progressbar_game_collection.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/progressbar_played_game_blue_style.xml b/app/src/main/res/drawable/progressbar_played_game_blue_style.xml new file mode 100644 index 0000000000..f68bfa3c16 --- /dev/null +++ b/app/src/main/res/drawable/progressbar_played_game_blue_style.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/progressbar_played_game_style.xml b/app/src/main/res/drawable/progressbar_played_game_style.xml new file mode 100644 index 0000000000..7361e015ba --- /dev/null +++ b/app/src/main/res/drawable/progressbar_played_game_style.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_cropimage.xml b/app/src/main/res/layout/activity_cropimage.xml index b856815e21..a32853d74d 100644 --- a/app/src/main/res/layout/activity_cropimage.xml +++ b/app/src/main/res/layout/activity_cropimage.xml @@ -1,9 +1,17 @@ - + android:fitsSystemWindows="true" + android:orientation="vertical" + app:consumeWindowInsets="true"> + + - - - + android:layout_below="@+id/status_bar" + android:orientation="vertical"> - \ No newline at end of file + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_editor_insert_game.xml b/app/src/main/res/layout/activity_editor_insert_game.xml index 1ed9c790e8..2278b66dfa 100644 --- a/app/src/main/res/layout/activity_editor_insert_game.xml +++ b/app/src/main/res/layout/activity_editor_insert_game.xml @@ -5,8 +5,6 @@ android:layout_height="match_parent" android:orientation="vertical"> - - @@ -32,7 +30,6 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_tablayout_no_title_viewpager.xml b/app/src/main/res/layout/activity_tablayout_no_title_viewpager.xml index 128a96300a..62b06767a4 100644 --- a/app/src/main/res/layout/activity_tablayout_no_title_viewpager.xml +++ b/app/src/main/res/layout/activity_tablayout_no_title_viewpager.xml @@ -18,7 +18,7 @@ diff --git a/app/src/main/res/layout/dialog_game_collection_share.xml b/app/src/main/res/layout/dialog_game_collection_share.xml new file mode 100644 index 0000000000..c086f1753b --- /dev/null +++ b/app/src/main/res/layout/dialog_game_collection_share.xml @@ -0,0 +1,211 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/editor_insert_container.xml b/app/src/main/res/layout/editor_insert_container.xml index a4228dd546..226f336008 100644 --- a/app/src/main/res/layout/editor_insert_container.xml +++ b/app/src/main/res/layout/editor_insert_container.xml @@ -275,6 +275,31 @@ android:textSize="11sp" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_choose_games.xml b/app/src/main/res/layout/fragment_choose_games.xml new file mode 100644 index 0000000000..a51dacfd0e --- /dev/null +++ b/app/src/main/res/layout/fragment_choose_games.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_game_collection_detail.xml b/app/src/main/res/layout/fragment_game_collection_detail.xml new file mode 100644 index 0000000000..49bbc9ff74 --- /dev/null +++ b/app/src/main/res/layout/fragment_game_collection_detail.xml @@ -0,0 +1,219 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_game_collection_poster.xml b/app/src/main/res/layout/fragment_game_collection_poster.xml new file mode 100644 index 0000000000..f986c9c40a --- /dev/null +++ b/app/src/main/res/layout/fragment_game_collection_poster.xml @@ -0,0 +1,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_game_collection_square.xml b/app/src/main/res/layout/fragment_game_collection_square.xml new file mode 100644 index 0000000000..f372f2aab1 --- /dev/null +++ b/app/src/main/res/layout/fragment_game_collection_square.xml @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_game_collection_square_al.xml b/app/src/main/res/layout/fragment_game_collection_square_al.xml new file mode 100644 index 0000000000..2c588e7e0b --- /dev/null +++ b/app/src/main/res/layout/fragment_game_collection_square_al.xml @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_game_collection_tag_select.xml b/app/src/main/res/layout/fragment_game_collection_tag_select.xml new file mode 100644 index 0000000000..bdc9bc5918 --- /dev/null +++ b/app/src/main/res/layout/fragment_game_collection_tag_select.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_my_game_collection_list.xml b/app/src/main/res/layout/fragment_my_game_collection_list.xml new file mode 100644 index 0000000000..aaa8d28c1a --- /dev/null +++ b/app/src/main/res/layout/fragment_my_game_collection_list.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_no_padding_tablayout_viewpager.xml b/app/src/main/res/layout/fragment_no_padding_tablayout_viewpager.xml new file mode 100644 index 0000000000..75f6966603 --- /dev/null +++ b/app/src/main/res/layout/fragment_no_padding_tablayout_viewpager.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_search_game.xml b/app/src/main/res/layout/fragment_search_game.xml new file mode 100644 index 0000000000..462a6ea270 --- /dev/null +++ b/app/src/main/res/layout/fragment_search_game.xml @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_user_game.xml b/app/src/main/res/layout/fragment_user_game.xml index c718980a23..a196bd0068 100644 --- a/app/src/main/res/layout/fragment_user_game.xml +++ b/app/src/main/res/layout/fragment_user_game.xml @@ -1,7 +1,6 @@ @@ -19,17 +18,31 @@ android:paddingBottom="16dp" android:background="@color/white"> + + + android:text="玩过" + android:textColor="@color/text_333333" + android:textSize="12sp" /> + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/game_collection_detail_divider_item.xml b/app/src/main/res/layout/game_collection_detail_divider_item.xml new file mode 100644 index 0000000000..e77d50014a --- /dev/null +++ b/app/src/main/res/layout/game_collection_detail_divider_item.xml @@ -0,0 +1,5 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/game_collection_detail_image_item.xml b/app/src/main/res/layout/game_collection_detail_image_item.xml new file mode 100644 index 0000000000..d5f00f5c13 --- /dev/null +++ b/app/src/main/res/layout/game_collection_detail_image_item.xml @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/game_collection_detail_none_game_item.xml b/app/src/main/res/layout/game_collection_detail_none_game_item.xml new file mode 100644 index 0000000000..88e7cee78c --- /dev/null +++ b/app/src/main/res/layout/game_collection_detail_none_game_item.xml @@ -0,0 +1,25 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/game_collection_detail_video_item.xml b/app/src/main/res/layout/game_collection_detail_video_item.xml new file mode 100644 index 0000000000..221212b401 --- /dev/null +++ b/app/src/main/res/layout/game_collection_detail_video_item.xml @@ -0,0 +1,257 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/game_collection_game_item.xml b/app/src/main/res/layout/game_collection_game_item.xml new file mode 100644 index 0000000000..223adb5c18 --- /dev/null +++ b/app/src/main/res/layout/game_collection_game_item.xml @@ -0,0 +1,149 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/game_collection_item.xml b/app/src/main/res/layout/game_collection_item.xml new file mode 100644 index 0000000000..78eade974c --- /dev/null +++ b/app/src/main/res/layout/game_collection_item.xml @@ -0,0 +1,275 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/game_collection_selected_tag_item.xml b/app/src/main/res/layout/game_collection_selected_tag_item.xml new file mode 100644 index 0000000000..9957806ec3 --- /dev/null +++ b/app/src/main/res/layout/game_collection_selected_tag_item.xml @@ -0,0 +1,31 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/game_collection_square_amway_item.xml b/app/src/main/res/layout/game_collection_square_amway_item.xml new file mode 100644 index 0000000000..2bc937e1b9 --- /dev/null +++ b/app/src/main/res/layout/game_collection_square_amway_item.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/game_collection_square_item.xml b/app/src/main/res/layout/game_collection_square_item.xml new file mode 100644 index 0000000000..b9d01c3e24 --- /dev/null +++ b/app/src/main/res/layout/game_collection_square_item.xml @@ -0,0 +1,278 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/game_collection_tag_item.xml b/app/src/main/res/layout/game_collection_tag_item.xml new file mode 100644 index 0000000000..7316f7c7fc --- /dev/null +++ b/app/src/main/res/layout/game_collection_tag_item.xml @@ -0,0 +1,34 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/home_game_collection_card_item.xml b/app/src/main/res/layout/home_game_collection_card_item.xml new file mode 100644 index 0000000000..b413024e26 --- /dev/null +++ b/app/src/main/res/layout/home_game_collection_card_item.xml @@ -0,0 +1,221 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/home_game_collection_item.xml b/app/src/main/res/layout/home_game_collection_item.xml new file mode 100644 index 0000000000..b81cada242 --- /dev/null +++ b/app/src/main/res/layout/home_game_collection_item.xml @@ -0,0 +1,17 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_article_detail_comment.xml b/app/src/main/res/layout/item_article_detail_comment.xml index c369ff049f..f9f4c4b3ad 100644 --- a/app/src/main/res/layout/item_article_detail_comment.xml +++ b/app/src/main/res/layout/item_article_detail_comment.xml @@ -326,5 +326,18 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" /> + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_choose_games.xml b/app/src/main/res/layout/item_choose_games.xml new file mode 100644 index 0000000000..8e50b6418b --- /dev/null +++ b/app/src/main/res/layout/item_choose_games.xml @@ -0,0 +1,118 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_game_collection_flex_tag.xml b/app/src/main/res/layout/item_game_collection_flex_tag.xml new file mode 100644 index 0000000000..0766a2a48b --- /dev/null +++ b/app/src/main/res/layout/item_game_collection_flex_tag.xml @@ -0,0 +1,26 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_game_collection_selected_tag.xml b/app/src/main/res/layout/item_game_collection_selected_tag.xml new file mode 100644 index 0000000000..21f15f9800 --- /dev/null +++ b/app/src/main/res/layout/item_game_collection_selected_tag.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_game_collection_tag.xml b/app/src/main/res/layout/item_game_collection_tag.xml new file mode 100644 index 0000000000..afae12e218 --- /dev/null +++ b/app/src/main/res/layout/item_game_collection_tag.xml @@ -0,0 +1,25 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_my_game_collection.xml b/app/src/main/res/layout/item_my_game_collection.xml new file mode 100644 index 0000000000..d448043b36 --- /dev/null +++ b/app/src/main/res/layout/item_my_game_collection.xml @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_game_collection_tag.xml b/app/src/main/res/layout/layout_game_collection_tag.xml new file mode 100644 index 0000000000..7558227384 --- /dev/null +++ b/app/src/main/res/layout/layout_game_collection_tag.xml @@ -0,0 +1,25 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_menu_game_collection_post.xml b/app/src/main/res/layout/layout_menu_game_collection_post.xml new file mode 100644 index 0000000000..4f9ecb7707 --- /dev/null +++ b/app/src/main/res/layout/layout_menu_game_collection_post.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/app/src/main/res/layout/layout_menu_save.xml b/app/src/main/res/layout/layout_menu_save.xml new file mode 100644 index 0000000000..860f643b6b --- /dev/null +++ b/app/src/main/res/layout/layout_menu_save.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/layout/piece_article_input_container.xml b/app/src/main/res/layout/piece_article_input_container.xml index 28027de83e..fa0b7f06bd 100644 --- a/app/src/main/res/layout/piece_article_input_container.xml +++ b/app/src/main/res/layout/piece_article_input_container.xml @@ -31,6 +31,38 @@ app:layout_constraintTop_toTopOf="parent" tools:text="说点什么吧" /> + + + + + diff --git a/app/src/main/res/layout/popup_my_game_guide.xml b/app/src/main/res/layout/popup_my_game_guide.xml new file mode 100644 index 0000000000..3116951743 --- /dev/null +++ b/app/src/main/res/layout/popup_my_game_guide.xml @@ -0,0 +1,13 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_game_collection_edit.xml b/app/src/main/res/menu/menu_game_collection_edit.xml new file mode 100644 index 0000000000..60dbd3b86e --- /dev/null +++ b/app/src/main/res/menu/menu_game_collection_edit.xml @@ -0,0 +1,11 @@ + +

+ + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_my_game.xml b/app/src/main/res/menu/menu_my_game.xml new file mode 100644 index 0000000000..068cc30ff4 --- /dev/null +++ b/app/src/main/res/menu/menu_my_game.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_my_game_collection.xml b/app/src/main/res/menu/menu_my_game_collection.xml new file mode 100644 index 0000000000..cf96d0c461 --- /dev/null +++ b/app/src/main/res/menu/menu_my_game_collection.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_save.xml b/app/src/main/res/menu/menu_save.xml new file mode 100644 index 0000000000..b14097d34f --- /dev/null +++ b/app/src/main/res/menu/menu_save.xml @@ -0,0 +1,11 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index 67cdbc0027..ebd673bd84 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -4,6 +4,7 @@ #2496FF + #332496FF #FFA142 @@ -129,6 +130,7 @@ #66000000 #80000000 + #1AFFFFFF #33FFFFFF #66FFFFFF #80FFFFFF diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 6e1403884e..6ae719aacd 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -344,6 +344,7 @@ 帖子 资讯 视频 + 游戏单 今天开服 明天开服 后续开服 @@ -536,6 +537,7 @@ 上拉查看历史礼包 没有更多了 + 创作不易,留言点赞一下 ~ 到底了哦~点击回到顶部 全部 调整顺序 diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 3f95011d70..75e08d7d6a 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -341,6 +341,33 @@ 11sp + + + + + + + +