Merge branch 'feature-games_collection' into dev-5.5.0

This commit is contained in:
juntao
2021-11-29 18:17:10 +08:00
255 changed files with 12959 additions and 445 deletions

View File

@ -22,12 +22,10 @@ import com.gh.common.view.RichEditor
import com.gh.gamecenter.CropImageActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.entity.GameEntity
import com.gh.gamecenter.entity.GamesCollectionEntity
import com.gh.gamecenter.entity.LocalVideoEntity
import com.gh.gamecenter.entity.VideoEntity
import com.gh.gamecenter.qa.editor.GameActivity
import com.gh.gamecenter.qa.editor.InsertAnswerWrapperActivity
import com.gh.gamecenter.qa.editor.InsertArticleWrapperActivity
import com.gh.gamecenter.qa.editor.LocalMediaActivity
import com.gh.gamecenter.qa.editor.*
import com.gh.gamecenter.qa.entity.AnswerEntity
import com.gh.gamecenter.qa.entity.ArticleEntity
import com.gh.gamecenter.qa.entity.EditorInsertEntity
@ -114,6 +112,14 @@ abstract class BaseRichEditorActivity<VM : BaseRichEditorViewModel> : ToolBarAct
mRichEditor.insertCustomStyleLink(insertData)
}
}
INSERT_GAME_COLLECTION_CODE -> {
val gameCollectionEntity = data?.getParcelableExtra<GamesCollectionEntity>(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<VM : BaseRichEditorViewModel> : 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<VM : BaseRichEditorViewModel> : 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<VM : BaseRichEditorViewModel> : 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

View File

@ -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

View File

@ -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;

View File

@ -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; // 游戏单
/**
* 普通列表

View File

@ -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()
}
}

View File

@ -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
}
}

View File

@ -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,

View File

@ -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)
}
}

View File

@ -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);

View File

@ -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如有疑问请联系客服QQ1562479331",
"你因违反《问答版块规则》,已被禁言$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)
)

View File

@ -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)
}
}

View File

@ -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;
}
}

View File

@ -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<ResponseBody>() {
@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);

View File

@ -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
);
}
}

View File

@ -0,0 +1,26 @@
package com.gh.common.util
open class SingletonHolder<out T>(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
}
}
}
}

View File

@ -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)
}
}

View File

@ -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)
}

View File

@ -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()
}

View File

@ -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<out StackAnimation>,
layout: Class<out StackLayout>) : 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 式翻页模式.
* <p>
* 当设置为 true 的时候,可以配合[StackLayoutManager.setPagerFlingVelocity]设置触发翻页的最小速度.
* @param isPagerMode 这个值默认是 false当设置为 true 的时候,会有 viewPager 翻页效果.
*/
fun setPagerMode(isPagerMode: Boolean) {
mPagerMode = isPagerMode
}
/**
* @return 当前是否为ViewPager翻页模式.
*/
fun getPagerMode(): Boolean {
return mPagerMode
}
/**
* 设置触发ViewPager翻页效果的最小速度.
* <p>
* 该值仅在 [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
}
}
}

View File

@ -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

View File

@ -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;

View File

@ -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("成功", "微博");

View File

@ -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<Any>(binding.root)

View File

@ -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

View File

@ -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<String> 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<Fragment> 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

View File

@ -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<GamesCollectionEntity>(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<LinearLayout>(R.id.container)
for (text in contentList) {
val item = inflater.inflate(R.layout.layout_popup_option_item, container, false)
container.addView(item)
val hitText = item.findViewById<TextView>(R.id.hint_text)
hitText.text = text
item.setOnClickListener {
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))
}
}
}
}

View File

@ -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<GamesCollectionEntity, GamesCollectionViewModel>() {
private var mUserId = ""
private var mType = ""
private var mIsInsertGameCollection = false
private var mAdapter: GamesCollectionAdapter? = null
override fun provideListViewModel() = viewModelProvider<GamesCollectionViewModel>(
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"
}
}

View File

@ -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<GamesCollectionEntity, GamesCollectionEntity>(application) {
private val mApi = RetrofitManager.getInstance(getApplication()).api
val deleteLiveData = MutableLiveData<GamesCollectionEntity>()
val publishLiveData = MutableLiveData<GamesCollectionEntity>()
override fun provideDataObservable(page: Int) = null
override fun provideDataSingle(page: Int): Single<MutableList<GamesCollectionEntity>> {
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<ResponseBody>() {
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<ResponseBody>() {
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<ResponseBody>() {
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 <T : ViewModel> create(modelClass: Class<T>): T {
return GamesCollectionViewModel(HaloApp.getInstance().application, mUserId, mType, mIsInsertGameCollection) as T
}
}
}

View File

@ -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<TagInfoEntity>? = null,
@TypeConverters(SimpleGameListConverter::class)
var games: ArrayList<SimpleGame>? = 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
}
}

View File

@ -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<TagInfoEntity> = ArrayList()
) : Parcelable

View File

@ -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
}
}

View File

@ -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<TagInfoEntity>? = null,
var games: ArrayList<GameEntity>? = 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
}

View File

@ -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<TagInfoEntity>? = null,
@TypeConverters(SimpleGameListConverter::class)
var games: ArrayList<SimpleGame>? = 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
}
}
}

View File

@ -13,6 +13,8 @@ data class HomeContent(
val linkGame: GameEntity? = null,
@SerializedName("link_column")
val linkColumn: SubjectEntity? = null,
@SerializedName("game_list")
val linkGameCollection: List<GamesCollectionEntity>? = null,
@SerializedName("link_top_game_comment")
val linkTopGameComment: List<AmwayCommentEntity>? = null,
@SerializedName("display_content")

View File

@ -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

View File

@ -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 = ""
}
}

View File

@ -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 = "",

View File

@ -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 {

View File

@ -30,6 +30,9 @@ data class SubjectEntity(
@SerializedName("common_collection_content")
var commonCollectionList: MutableList<CommonCollectionContentEntity>? = null,
@SerializedName("game_list_collection")
var gameListCollection: List<GamesCollectionEntity>? = null,
@SerializedName("show_name")
var showName: Boolean = true, // 是否显示“专题名字”true、false
@SerializedName("show_suffix")

View File

@ -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

View File

@ -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<SubjectRecommendEntity>): ArrayList<Fragment> {
val fragmentList = arrayListOf<Fragment>()
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)
})

View File

@ -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<ExposureEvent>()
for (gameCollectionItemData in gameCollectionItemDataList) {
runOnIoThread(true) {
val gameCollection = gameCollectionItemData.gameCollectionItem
val gameCollectionSource = listOf(ExposureSource("游戏单", "${gameCollection?.title} + ${gameCollection?.id}"))
val gameExposureList = arrayListOf<ExposureEvent>()
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
}

View File

@ -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<GameCollectionListItemData>().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)
}

View File

@ -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<GameCollectionListItemData>? = null
var blankDivider: Float? = null // 空白的空间补全item
var exposureEvent: ExposureEvent? = null

View File

@ -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)
}
}
}

View File

@ -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<Fragment>) {
fragments.add(AddSearchGameFragment().apply { bundleOf(EntranceUtils.KEY_NAVIGATION_TITLE to "添加游戏") })
fragments.add(AddUserPlayedGameFragment())
}
override fun initTabTitleList(tabTitleList: MutableList<String>) {
tabTitleList.add("搜索游戏")
tabTitleList.add("玩过游戏")
}
}

View File

@ -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)
}
}
}
}

View File

@ -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
}

View File

@ -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<GameEntity, PlayedGameViewModel>() {
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<GameEntity> {
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
}
}

View File

@ -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)
}
}
}

View File

@ -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<GameEntity>(context) {
public override fun setListData(updateData: MutableList<GameEntity>?) {
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<GameEntity>(binding.root)
interface ItemDragListener {
fun startDragItem(holder: RecyclerView.ViewHolder)
fun setToTop(holder: RecyclerView.ViewHolder)
fun deleteItem(entity: GameEntity)
}
}

View File

@ -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<ChooseGamesFragment> = 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
}
}
}

View File

@ -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<ArrayList<GameEntity>> = MutableLiveData()
companion object : SingletonHolder<ChooseGamesRepository>(::ChooseGamesRepository)
}

View File

@ -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 <T : ViewModel?> create(modelClass: Class<T>): T {
return ChooseGamesViewModel(
HaloApp.getInstance().application,
ChooseGamesRepository.getInstance()
) as T
}
}
}

View File

@ -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)
}
}
}

View File

@ -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<String, Int>()
private var mExposureEventArray: SparseArray<ExposureEvent>? = null
override fun setListData(updateData: MutableList<CommentItemData>?) {
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<ExposureSource>().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<ExposureEvent>? = null
companion object {
const val ITEM_GAME = 900
const val ITEM_GAME_EMPTY = 901
const val ITEM_DIVIDER = 902
}
}

View File

@ -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<CommentItemData, GameCollectionDetailViewModel>() {
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>(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
)
}
}

View File

@ -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<Boolean>()
val favoriteLiveData = MutableLiveData<Boolean>()
val likeLiveData = MutableLiveData<Boolean>()
val shareLiveData = MutableLiveData<Boolean>()
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<GamesCollectionDetailEntity>() {
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<MutableList<CommentEntity>> {
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<CommentEntity>?) {
gameCollectionDetail?.run {
val itemDataList = arrayListOf<CommentItemData>().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<ResponseBody>() {
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<ResponseBody>() {
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<ResponseBody>() {
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<ResponseBody>() {
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<ResponseBody>() {
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<ResponseBody>() {
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 <T : ViewModel> create(modelClass: Class<T>): T {
return GameCollectionDetailViewModel(HaloApp.getInstance().application, gameCollectionId) as T
}
}
}

View File

@ -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)
}
}
}

View File

@ -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<Bitmap, Boolean> {
@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)
)
}
}
}
}
}
}

View File

@ -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<Boolean>()
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<ResponseBody>() {
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)
}
})
}
}

View File

@ -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
)
}
}
}

View File

@ -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<ImageView>(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
)
}
}
}

View File

@ -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)
}
}
}

View File

@ -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<CommentItemData, GameCollectionCommentConversationViewModel>() {
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)
}
}

View File

@ -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<MutableList<CommentEntity>> {
return mApi.getGameCollectionCommentReply(gameCollectionId, commentId, page)
}
@SuppressLint("CheckResult")
fun getComment() {
mApi.getGameCollectionComment(gameCollectionId, commentId)
.subscribeOn(Schedulers.io())
.subscribe(object : BiResponse<CommentEntity>() {
@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 <T : ViewModel> create(modelClass: Class<T>): T {
return GameCollectionCommentConversationViewModel(
application = application,
gameCollectionId = gameCollectionId,
commentId = commentId) as T
}
}
}

View File

@ -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)
}
}
}

View File

@ -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<GamesCollectionEntity>(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
}
}

View File

@ -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<GamesCollectionEntity, MyGameCollectionViewModel>() {
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)
}
}
}

View File

@ -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<GamesCollectionEntity>(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<TagInfoEntity>) {
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)
}
}
}

View File

@ -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<GamesCollectionEntity, GamesCollectionEntity>(application) {
private val mApi = RetrofitManager.getInstance(HaloApp.getInstance()).api
val deleteLiveData = MutableLiveData<GamesCollectionEntity>()
val publishLiveData = MutableLiveData<GamesCollectionEntity>()
override fun provideDataObservable(page: Int): Observable<MutableList<GamesCollectionEntity>>? {
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<ResponseBody>() {
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<ResponseBody>() {
override fun onResponse(response: ResponseBody?) {
super.onResponse(response)
publishLiveData.postValue(entity)
}
override fun onFailure(e: HttpException?) {
super.onFailure(e)
ToastUtils.showToast("投稿失败")
}
})
}
}

View File

@ -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<TagInfoEntity>(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<TagInfoEntity>) {
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<String, Any>(
"_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
}
}
}

View File

@ -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<TagInfoEntity>()
var gameCollectionPatch: GamesCollectionEntity? = null
var uploadImageSuccessLiveData = MutableLiveData<String>()
var detailLiveData = MutableLiveData<GamesCollectionEntity>()
var draftLiveData = MutableLiveData<GameCollectionDraft>()
val postLiveData = MutableLiveData<Resource<String>>()
val processDialog = MediatorLiveData<WaitingDialogFragment.WaitingDialogData>()
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<String, Any>) {
val id = requestMap["id"]?.toString() ?: ""
requestMap.remove("id")
postContent(requestMap, id)
}
private fun postContent(requestMap: HashMap<String, Any>, 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<ResponseBody>() {
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<GamesCollectionEntity>() {
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<List<GameCollectionDraft>>() {
override fun onSuccess(data: List<GameCollectionDraft>) {
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)
}
}
}

View File

@ -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<RecyclerView.ViewHolder>(context) {
private var mAmwayList = listOf<AmwayCommentEntity>()
fun setAmwayList(amwayList: List<AmwayCommentEntity>) {
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
}
}

View File

@ -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<ViewPager2> = 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<AmwayCommentEntity>) {
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
}
}

View File

@ -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()

View File

@ -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()
}
}

View File

@ -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<ExposureSource>,
private val mOuterSequence: Int = -1
) :
ListAdapter<GameCollectionListItemData>(context), IExposable {
private var mAmwayList = listOf<AmwayCommentEntity>()
fun setAmwayList(amwayList: List<AmwayCommentEntity>) {
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<ExposureEvent>()
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<ExposureEvent>? {
return mEntityList[pos].exposureEventList
}
}

View File

@ -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<GamesCollectionEntity, GameCollectionSquareViewModel>() {
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<TagInfoEntity>(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<ExposureSource>().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
}
}

View File

@ -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<GamesCollectionEntity, GameCollectionListItemData>(application) {
var entrance: String? = null
var selectedTagEntity: TagInfoEntity? = null
var selectedTagId = ""
var selectedTagName = "全部标签"
var view = "hot"
var isHome = false
val mAmwayCommentList = MutableLiveData<List<AmwayCommentEntity>>()
init {
getAmwayCommentList()
}
override fun initLoadParams() {
mCurLoadParams = LoadParams(PAGE_SIZE, LoadParams.DEFAULT_OFFSET)
mLoadStatusLiveData.value = LoadStatus.INIT
}
override fun provideDataSingle(page: Int): Single<MutableList<GamesCollectionEntity>> =
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<GameCollectionListItemData>().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<MutableList<GamesCollectionEntity>>? = null
@SuppressLint("CheckResult")
fun getAmwayCommentList() {
RetrofitManager.getInstance(getApplication()).api
.getAmwayCommentList(1, 10)
.subscribeOn(Schedulers.io())
.subscribe(object : BiResponse<List<AmwayCommentEntity>>() {
override fun onSuccess(data: List<AmwayCommentEntity>) {
mAmwayCommentList.postValue(data)
}
})
}
companion object {
const val PAGE_SIZE = 15
}
}

View File

@ -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<RecyclerView.ViewHolder>(context) {
private var mTagViewList = arrayListOf<ItemGameCollectionTagBinding>()
var mTagList = arrayListOf<GameCollectionTagEntity>()
var selectedTagEntity: TagInfoEntity? = null
var selectedTagCategory = ""
var selectedTagEntityList = arrayListOf<TagInfoEntity>()
private var flagFirst = true
fun setTagList(tagList: ArrayList<GameCollectionTagEntity>) {
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
}
}

View File

@ -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
)
}
}
}

View File

@ -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"
}
}

View File

@ -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<ArrayList<GameCollectionTagEntity>>()
private val mApi = RetrofitManager.getInstance(getApplication()).api
init {
getGameCollectionTagList()
}
@SuppressLint("CheckResult")
fun getGameCollectionTagList() {
mApi.gameCollectionTagList
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object: BiResponse<ArrayList<GameCollectionTagEntity>>(){
override fun onSuccess(data: ArrayList<GameCollectionTagEntity>) {
tagListLiveData.postValue(data)
}
})
}
}

View File

@ -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<String>) {
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<Fragment>) {
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)
}))

View File

@ -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<ExposureEvent>()
for (gameCollectionItemData in gameCollectionItemDataList) {
runOnIoThread(true) {
val gameCollection = gameCollectionItemData.gameCollectionItem
val gameCollectionSource = listOf(ExposureSource("游戏单", "${gameCollection?.title} + ${gameCollection?.id}"))
val gameExposureList = arrayListOf<ExposureEvent>()
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
}
}

View File

@ -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<HomeSlide>? = null,
var recommends: List<HomeRecommend>? = null,
var amway: List<AmwayCommentEntity>? = null,
var gameCollection: List<GameCollectionListItemData>? = null,
var attachGame: HomeContent? = null,
var lineDivider: Float? = null,
var unknownData: Any? = null) : LegacyHomeItemData()

View File

@ -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<GameCollectionListItemData>().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

View File

@ -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!!

View File

@ -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<View>(R.id.cover)
val content = itemView.findViewById<View>(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
}
}
}

View File

@ -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
}
}
}

View File

@ -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<RecyclerView.ViewHolder>(context) {
private var mGameCollectionItemDataList = listOf<GameCollectionListItemData>()
fun setGameCollectionList(gameCollectionItemDataList: List<GameCollectionListItemData>) {
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))
}
}
}
}
}
}

View File

@ -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<Any>(binding.root) {
fun bindGameCollectionList(gameCollectionItemDataList: List<GameCollectionListItemData>, 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
}
}

View File

@ -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<MessageEntity>
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<MessageEntity>
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;
}
}
}

View File

@ -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<Fragment>?) {
@ -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 {

View File

@ -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<GameEntity, GameEntity>(application) {
class PlayedGameViewModel(application: Application, var userId: String, val isKeepTagStyle: Boolean = false) :
ListViewModel<GameEntity, GameEntity>(application) {
override fun provideDataObservable(page: Int): Observable<MutableList<GameEntity>>? {
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<ResponseBody>() {
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<ResponseBody>() {
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 <T : ViewModel> create(modelClass: Class<T>): T {
return PlayedGameViewModel(HaloApp.getInstance().application, mUserId) as T
return PlayedGameViewModel(HaloApp.getInstance().application, mUserId, isKeepTagStyle) as T
}
}

View File

@ -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<FunctionalLinkEntity>)
: BaseRecyclerAdapter<RecyclerView.ViewHolder>(context) {
class PersonalFunctionAdapter(val context: Context, val groupName: String, var mEntityList: ArrayList<FunctionalLinkEntity>) :
BaseRecyclerAdapter<RecyclerView.ViewHolder>(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, "", "我的光环")
}

View File

@ -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, "游戏动态"),

View File

@ -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,

View File

@ -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
))
}
}

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