Compare commits

...

17 Commits

Author SHA1 Message Date
1613fe00ed feat:社区新增关注页面—客户端 https://jira.shanqu.cc/browse/GHZSCY-5098 2024-06-04 15:58:10 +08:00
e955920f7e feat:社区新增关注页面—客户端 https://jira.shanqu.cc/browse/GHZSCY-5098 2024-06-04 14:22:29 +08:00
d7f307603b fix:社区新增关注页面—06/03测试—客户端 https://jira.shanqu.cc/browse/GHZSCY-5610 2024-06-04 10:00:09 +08:00
25c60c61d8 fix:社区新增关注页面—0603UI测试—客户端 https://jira.shanqu.cc/browse/GHZSCY-5602 2024-06-03 15:24:13 +08:00
7a30967ec6 fix:社区新增关注页面—0529UI测试—客户端 https://jira.shanqu.cc/browse/GHZSCY-5530 2024-05-31 18:04:01 +08:00
33aecc1f71 fix: 社区新增关注页面—0528产品测试—客户端 https://jira.shanqu.cc/browse/GHZSCY-5498 2024-05-31 17:07:57 +08:00
c7b3b98ed0 fix:社区新增关注页面—0529UI测试—客户端 https://jira.shanqu.cc/projects/GHZSCY/issues/GHZSCY-5530?filter=myopenissues 2024-05-31 10:30:19 +08:00
883e250538 fix:社区新增关注页面—0529测试—客户端 https://jira.shanqu.cc/browse/GHZSCY-5521 2024-05-30 10:45:03 +08:00
0b8cc539da fix:社区新增关注页面—0529测试—客户端 https://jira.shanqu.cc/browse/GHZSCY-5521 2024-05-29 14:57:13 +08:00
1b63ae871a fix:社区新增关注页面—0528UI测试—客户端 https://jira.shanqu.cc/browse/GHZSCY-5509 2024-05-29 14:16:07 +08:00
37d37374b6 fix:社区新增关注页面—0528测试—客户端 https://jira.shanqu.cc/browse/GHZSCY-5502 2024-05-28 17:35:46 +08:00
23abf14e32 fix:社区新增关注页面—0528测试—客户端 https://jira.shanqu.cc/browse/GHZSCY-5498 2024-05-28 16:06:29 +08:00
fd114f13aa fix:社区新增关注页面(数据类放在entity包下,防止混淆)https://jira.shanqu.cc/browse/GHZSCY-5098 2024-05-28 10:06:52 +08:00
b13b196639 fix:社区新增关注页面(禁止group相关的viewId混淆) https://jira.shanqu.cc/browse/GHZSCY-5098 2024-05-28 09:24:40 +08:00
5302bc7972 fix:禁止group相关的viewId混淆 https://jira.shanqu.cc/browse/GHZSCY-5098 2024-05-27 17:50:22 +08:00
61bab61ccf feat:社区新增关注页面—客户端 https://jira.shanqu.cc/browse/GHZSCY-5098 2024-05-27 15:35:22 +08:00
60635e748f feat:社区新增关注页面 https://jira.shanqu.cc/browse/GHZSCY-5098 2024-05-27 10:36:23 +08:00
163 changed files with 11348 additions and 2072 deletions

View File

@ -72,6 +72,7 @@ android_build:
only:
- dev
- release
- feat/GHZSCY-5098
# 代码检查
sonarqube_analysis:
@ -103,6 +104,7 @@ sonarqube_analysis:
only:
- dev
- release
- feat/GHZSCY-5098
## 发送简易检测结果报告
send_sonar_report:
@ -121,6 +123,7 @@ send_sonar_report:
only:
- dev
- release
- feat/GHZSCY-5098
oss-upload&send-email:
tags:
@ -152,4 +155,5 @@ oss-upload&send-email:
- /usr/local/bin/python /ci-android-mail-jira-comment.py
only:
- dev
- release
- release
- feat/GHZSCY-5098

View File

@ -569,7 +569,16 @@ andResGuard {
"R.id.cardMask",
"R.id.cardGradientMask",
"R.id.gameIconIv",
"R.id.titleContainer"
"R.id.titleContainer",
"R.id.v_login_background",
"R.id.tv_login_tips",
"R.id.tv_login",
"R.id.v_code_background",
"R.id.tv_code_tips",
"R.id.tv_code",
"R.id.tv_copy",
"R.id.v_indicator_background",
"R.id.v_indicator"
]
compressFilePattern = [
"*.png",

View File

@ -770,8 +770,19 @@
android:name="com.gh.gamecenter.wrapper.ToolbarWrapperActivity"
android:screenOrientation="portrait" />
<activity android:name=".forum.home.CommunityActivity"
android:screenOrientation="portrait"/>
<activity
android:name=".forum.home.CommunityActivity"
android:screenOrientation="portrait" />
<activity
android:name=".forum.home.follow.FollowDynamicActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.Transparent" />
<activity
android:name=".forum.home.follow.AllFollowedActivity"
android:screenOrientation="portrait"
android:theme="@style/AppCompatTheme.APP" />
<!-- <activity-->
<!-- android:name="${applicationId}.douyinapi.DouYinEntryActivity"-->

View File

@ -0,0 +1,103 @@
package com.gh.common.provider
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.viewbinding.ViewBinding
import com.alibaba.android.arouter.facade.annotation.Route
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.common.util.ConcernContentUtils
import com.gh.common.view.ImageContainerView
import com.gh.gamecenter.ImageViewerActivity.Companion.getIntent
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.utils.toArrayList
import com.gh.gamecenter.core.provider.IConcernArticleUtilsProvider
import com.gh.gamecenter.databinding.RecyclerGameArticleBinding
import com.gh.gamecenter.message.R
@Route(path = RouteConsts.provider.concernContentUtils, name = "ConcernContentUtils暴露服务")
class ConcernArticleUtilsProviderImpl : IConcernArticleUtilsProvider {
override fun addContentPic(
context: Context,
linearLayout: LinearLayout,
list: List<String>,
entrance: String,
width: Int
) {
ConcernContentUtils.addContentPic(context, linearLayout, list, entrance, width)
}
override fun createArticleBinding(parent: ViewGroup): ViewBinding {
val inflater = LayoutInflater.from(parent.context)
return RecyclerGameArticleBinding.inflate(inflater, parent, false)
}
override fun initArticleStyle(binding: ViewBinding) {
if (binding is RecyclerGameArticleBinding) {
val context = binding.root.context
binding.root
.setBackground(ContextCompat.getDrawable(context, R.drawable.reuse_listview_item_style))
binding.tvGameName.setTextColor(ContextCompat.getColor(context, R.color.text_primary))
binding.tvTime.setTextColor(ContextCompat.getColor(context, R.color.text_tertiary))
binding.tvTitle.setTextColor(ContextCompat.getColor(context, R.color.text_primary))
binding.tvDescription.setTextColor(ContextCompat.getColor(context, R.color.text_secondary))
binding.tvComment.setTextColor(ContextCompat.getColor(context, R.color.text_tertiary))
binding.tvShare.setTextColor(ContextCompat.getColor(context, R.color.text_tertiary))
}
}
override fun getIvIcon(binding: ViewBinding): View =
(binding as RecyclerGameArticleBinding).ivIcon
override fun getTvGameName(binding: ViewBinding): TextView =
(binding as RecyclerGameArticleBinding).tvGameName
override fun getTvTime(binding: ViewBinding): TextView = (binding as RecyclerGameArticleBinding).tvTime
override fun getTvTitle(binding: ViewBinding): TextView = (binding as RecyclerGameArticleBinding).tvTitle
override fun getTvDescription(binding: ViewBinding): TextView =
(binding as RecyclerGameArticleBinding).tvDescription
override fun bindImgs(binding: ViewBinding, img: List<String>) {
if (binding is RecyclerGameArticleBinding) {
val images = img.map {
ImageContainerView.ImageContainerData.ImageInfo(it, 3, 2)
}
val imageContainerData =
ImageContainerView.ImageContainerData("", false, images, show = images.isNotEmpty())
binding.ivImagesContainer.bindData(imageContainerData,
object : ImageContainerView.OnImageContainerEventListener {
override fun onImageClick(
images: List<String>,
position: Int,
imageViewList: ArrayList<SimpleDraweeView>
) {
val checkIntent = getIntent(
binding.root.context,
images.toArrayList(),
position,
imageViewList,
""
)
binding.root.context.startActivity(checkIntent)
}
override fun onVideoCLick(videoId: String) = Unit
})
}
}
override fun getTvComment(binding: ViewBinding): TextView = (binding as RecyclerGameArticleBinding).tvComment
override fun getTvShare(binding: ViewBinding): TextView = (binding as RecyclerGameArticleBinding).tvShare
override fun init(context: Context?) {
// Do nothing
}
}

View File

@ -1,26 +0,0 @@
package com.gh.common.provider
import android.content.Context
import android.widget.LinearLayout
import com.alibaba.android.arouter.facade.annotation.Route
import com.gh.common.util.ConcernContentUtils
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.core.provider.IConcernContentUtilsProvider
@Route(path = RouteConsts.provider.concernContentUtils, name = "ConcernContentUtils暴露服务")
class ConcernContentUtilsProviderImpl : IConcernContentUtilsProvider {
override fun addContentPic(
context: Context,
linearLayout: LinearLayout,
list: List<String>,
entrance: String,
width: Int
) {
ConcernContentUtils.addContentPic(context, linearLayout, list, entrance, width)
}
override fun init(context: Context?) {
// Do nothing
}
}

View File

@ -0,0 +1,55 @@
package com.gh.common.provider
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.viewbinding.ViewBinding
import com.alibaba.android.arouter.facade.annotation.Route
import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.core.provider.IConcernGiftPackUtilsProvider
import com.gh.gamecenter.databinding.RecyclerGiftPackBinding
@Route(path = RouteConsts.provider.concernGiftPackUtils, name = "ConcernGiftPackUtils暴露服务")
class ConcernGiftPackUtilsProviderImpl : IConcernGiftPackUtilsProvider {
override fun createBinding(parent: ViewGroup): ViewBinding {
val inflater = LayoutInflater.from(parent.context)
return RecyclerGiftPackBinding.inflate(inflater, parent, false)
.also {
it.gCode.goneIf(true)
}
}
override fun initStyle(viewBinding: ViewBinding) {
with(viewBinding as RecyclerGiftPackBinding) {
val context = root.context
tvGameName.setTextColor(R.color.text_primary.toColor(context))
tvTime.setTextColor(R.color.text_tertiary.toColor(context))
tvGiftPackName.setTextColor(R.color.text_primary.toColor(context))
tvGiftPackContent.setTextColor(R.color.text_secondary.toColor(context))
}
}
override fun getIvGameIcon(viewBinding: ViewBinding) =
(viewBinding as RecyclerGiftPackBinding).ivIcon
override fun getTvGameName(viewBinding: ViewBinding) =
(viewBinding as RecyclerGiftPackBinding).tvGameName
override fun getTvTime(viewBinding: ViewBinding) =
(viewBinding as RecyclerGiftPackBinding).tvTime
override fun getTvGiftPackName(viewBinding: ViewBinding) =
(viewBinding as RecyclerGiftPackBinding).tvGiftPackName
override fun getTvGiftPackContent(viewBinding: ViewBinding) =
(viewBinding as RecyclerGiftPackBinding).tvGiftPackContent
override fun init(context: Context?) {
// Do Nothing
}
}

View File

@ -0,0 +1,19 @@
package com.gh.common.provider
import android.content.Context
import androidx.appcompat.app.AppCompatActivity
import com.alibaba.android.arouter.facade.annotation.Route
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.core.provider.IConcernShareNewsProvider
import com.gh.gamecenter.newsdetail.NewsShareDialog
@Route(path = RouteConsts.provider.concernShareNews, name = "ConcernShareNews暴露服务")
class ConcernShareNewsProviderImpl : IConcernShareNewsProvider {
override fun share(activity: AppCompatActivity, shortId: String?, id: String?, gameIcon: String?, title: String?) {
NewsShareDialog.show(activity, shortId, id, gameIcon, title)
}
override fun init(context: Context?) {
// Do Nothing
}
}

View File

@ -7,8 +7,8 @@ import android.widget.LinearLayout;
import com.facebook.drawee.drawable.ScalingUtils;
import com.facebook.drawee.view.SimpleDraweeView;
import com.gh.gamecenter.ImageViewerActivity;
import com.gh.gamecenter.core.utils.DisplayUtils;
import com.gh.gamecenter.common.utils.ImageUtils;
import com.gh.gamecenter.core.utils.DisplayUtils;
import java.util.ArrayList;
import java.util.List;
@ -121,5 +121,4 @@ public class ConcernContentUtils {
}
return imageView;
}
}

View File

@ -27,8 +27,8 @@ import com.gh.gamecenter.gamedetail.rating.RatingReplyActivity
object SyncDataBetweenPageHelper {
private const val REQUEST_CODE_TAG = "REQUEST_CODE_TAG"
private const val DATA_POSITION_TAG = "DATA_POSITION_TAG"
const val REQUEST_CODE_TAG = "REQUEST_CODE_TAG"
const val DATA_POSITION_TAG = "DATA_POSITION_TAG"
private const val DEFAULT_NUMBER = -1111
fun startActivityForResult(context: Context, intent: Intent, requestCode: Int, dataPosition: Int) {

View File

@ -6,25 +6,21 @@ import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import androidx.core.content.ContextCompat
import androidx.core.view.get
import com.facebook.drawee.drawable.ScalingUtils
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.ImageViewerActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.common.utils.debounceActionWithInterval
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.toResString
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.core.utils.TopCutProcess
import com.gh.gamecenter.databinding.ItemCommunityImageBinding
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.feature.entity.CommunityVideoEntity
import com.gh.gamecenter.feature.entity.ImageInfo
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.video.detail.VideoDetailContainerViewModel
class ImageContainerView : LinearLayout {
private var mAnswerEntity: AnswerEntity? = null
private var data: ImageContainerData? = null
//三图默认宽度
private var mDefaultWidth = 0f
@ -41,9 +37,6 @@ class ImageContainerView : LinearLayout {
//长图比例
private var mLongPictureRatio = 9 / 18f
private var mEntrance = ""
private var mPath = ""
//图片之间的间距
private val mItemSpace = 4f.dip2px()
private var mOffset = 0
@ -51,6 +44,8 @@ class ImageContainerView : LinearLayout {
private val imageViewList = arrayListOf<SimpleDraweeView>()
private var onImageContainerEventListener: OnImageContainerEventListener? = null
constructor(context: Context) : this(context, null)
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
@ -75,40 +70,34 @@ class ImageContainerView : LinearLayout {
calculateWidth()
}
fun bindData(entity: AnswerEntity, entrance: String = "", path: String = "", imageClick: (() -> Unit)? = null) {
fun bindData(
data: ImageContainerData,
listener: OnImageContainerEventListener? = null
) {
this.data = data
onImageContainerEventListener = listener
imageViewList.clear()
if (entity.id != mAnswerEntity?.id) {
removeAllViews()
}
mAnswerEntity = entity
mEntrance = entrance
mPath = path
removeAllViews()
index = 0
if ((entity.user.id == UserManager.getInstance().userId && entity.videos.isNotEmpty()) ||
(entity.user.id != UserManager.getInstance().userId && entity.getPassVideos().isNotEmpty()) ||
entity.images.isNullOrEmpty()
) {
if (!data.show) {
visibility = View.GONE
return
}
visibility = View.VISIBLE
if (mAnswerEntity?.type == "community_article") {
if (data.isPostCard) {
//若文章内容含有图片及视频,则信息流卡片,仅展示图片,且标题后带有‘有视频’标签
//若文章内容仅含有图片,则信息流卡片,仅展示图片,无标签
//若文章内容仅含有视频,则信息流卡片,仅展示视频,无标签
when {
entity.images.isNotEmpty() -> {
val imagesInfo = entity.imagesInfo
val images = entity.images.take(3)
images.forEachIndexed { index, url ->
val width = if (index <= entity.imagesInfo.size - 1) imagesInfo[index].width else 0
val height = if (index <= entity.imagesInfo.size - 1) imagesInfo[index].height else 0
bindImage(url, width, height, images.size == 1, imageClick)
}
data.images.isNotEmpty() -> {
data.images.take(3)
.forEach {
bindImage(it.url, it.width, it.height, data.images.size == 1)
}
}
entity.getPassVideos().isNotEmpty() -> {
val video = entity.getPassVideos()[0]
data.video != null -> {
val video = data.video
bindVideo(video, video.width, video.height, true)
}
@ -120,28 +109,22 @@ class ImageContainerView : LinearLayout {
//若问答内容含有图片及视频,则信息流卡片,同时展示图片及视频,且参考以往排序逻辑(视频优先放置第一位),无标签
//若问答内容仅含有图片,则信息流卡片,仅展示图片,无标签
//若问答内容仅含有视频,则信息流卡片,仅展示视频,无标签
if (entity.getPassVideos().isNotEmpty()) {
val video = entity.getPassVideos()[0]
bindVideo(video, video.width, video.height, mAnswerEntity?.images.isNullOrEmpty())
entity.images.take(2).forEachIndexed { index, url ->
val imagesInfo = entity.imagesInfo
val width = if (index <= entity.imagesInfo.size - 1) imagesInfo[index].width else 0
val height = if (index <= entity.imagesInfo.size - 1) imagesInfo[index].height else 0
bindImage(url, width, height, false, imageClick)
if (data.video != null) {
val video = data.video
bindVideo(video, video.width, video.height, data.images.isNullOrEmpty())
data.images.take(2).forEach {
bindImage(it.url, it.width, it.height, false)
}
} else {
val images = entity.images.take(3)
images.forEachIndexed { index, url ->
val imagesInfo = entity.imagesInfo
val width = if (index <= entity.imagesInfo.size - 1) imagesInfo[index].width else 0
val height = if (index <= entity.imagesInfo.size - 1) imagesInfo[index].height else 0
bindImage(url, width, height, images.size == 1, imageClick)
}
data.images.take(3)
.forEach {
bindImage(it.url, it.width, it.height, data.images.size == 1)
}
}
}
}
private fun bindVideo(video: CommunityVideoEntity, width: Int, height: Int, isChangeRatio: Boolean) {
private fun bindVideo(video: ImageContainerData.VideoInfo, width: Int, height: Int, isChangeRatio: Boolean) {
val oldView = if (childCount == 0 || index >= childCount) null else getChildAt(index)
val binding = if (oldView != null) {
ItemCommunityImageBinding.bind(oldView)
@ -158,16 +141,7 @@ class ImageContainerView : LinearLayout {
displayImage(binding, video.poster, width.toFloat(), height.toFloat(), isChangeRatio, true)
binding.root.setOnClickListener {
debounceActionWithInterval(it.id, 1000) {
if (mAnswerEntity == null) return@debounceActionWithInterval
val videoEntity = mAnswerEntity!!.getPassVideos().firstOrNull()
DirectUtils.directToVideoDetail(
context,
videoEntity?.id ?: "",
VideoDetailContainerViewModel.Location.VIDEO_HOT.value,
showComment = false,
entrance = mEntrance,
path = mPath
)
onImageContainerEventListener?.onVideoCLick(video.id)
}
}
index++
@ -177,8 +151,7 @@ class ImageContainerView : LinearLayout {
url: String,
width: Int,
height: Int,
isChangeRatio: Boolean,
imageClick: (() -> Unit)?
isChangeRatio: Boolean
) {
val oldView = if (childCount == 0 || index >= childCount) null else getChildAt(index)
val binding = if (oldView != null) {
@ -195,25 +168,21 @@ class ImageContainerView : LinearLayout {
binding.videoPlay.visibility = View.GONE
displayImage(binding, url, width.toFloat(), height.toFloat(), isChangeRatio)
binding.root.setOnClickListener {
if (mAnswerEntity?.status == "pending" || mAnswerEntity?.status == "fail") return@setOnClickListener
imageClick?.invoke()
if (data?.status == "pending" || data?.status == "fail") return@setOnClickListener
debounceActionWithInterval(it.id, 1000) {
if (mAnswerEntity == null) return@debounceActionWithInterval
val position = if (mAnswerEntity?.type == "community_article") {
binding.root.tag as Int
} else {
if (mAnswerEntity!!.getPassVideos()
.isNullOrEmpty()
) binding.root.tag as Int else (binding.root.tag as Int) - 1
data?.run {
val position = if (isPostCard) {
binding.root.tag as Int
} else {
if (video == null) binding.root.tag as Int else (binding.root.tag as Int) - 1
}
onImageContainerEventListener?.onImageClick(
images.map(ImageContainerData.ImageInfo::url),
position,
imageViewList
)
}
if (mAnswerEntity?.communityId.isNullOrEmpty()) {
mAnswerEntity?.communityId = mAnswerEntity?.bbs?.id
}
val intent = ImageViewerActivity.getIntent(
context, mAnswerEntity!!.images as ArrayList<String>, position, imageViewList,
if (mAnswerEntity?.type == "community_article") mAnswerEntity else null, mEntrance, true
)
context.startActivity(intent)
}
}
index++
@ -279,7 +248,7 @@ class ImageContainerView : LinearLayout {
}
binding.pendingView.run {
when (mAnswerEntity?.status) {
when (data?.status) {
"pending" -> {
visibility = View.VISIBLE
text = R.string.pending_status.toResString()
@ -297,7 +266,7 @@ class ImageContainerView : LinearLayout {
}
val imageCount = mAnswerEntity?.images?.size ?: 0
val imageCount = data?.images?.size ?: 0
if (!isVideo && index == 2 && imageCount > 3) {
binding.labelIcon.visibility = View.GONE
binding.durationOrNumTv.visibility = View.VISIBLE
@ -308,4 +277,80 @@ class ImageContainerView : LinearLayout {
if (index != 0) params.leftMargin = mItemSpace
binding.root.layoutParams = params
}
companion object {
private const val COMMUNITY_ARTICLE = "community_article"
fun AnswerEntity.toImageContainerData(): ImageContainerData {
val imageInfoList = arrayListOf<ImageContainerData.ImageInfo>()
images.forEachIndexed { index, url ->
if (index < 3) {
imageInfoList.add(ImageContainerData.ImageInfo(url))
}
}
imageInfoList.forEachIndexed { index, imageInfo ->
val item = imagesInfo.getOrNull(index)
if (item != null) {
imageInfo.width = item.width
imageInfo.height = item.height
}
}
val video =
getPassVideos().firstOrNull()?.let {
ImageContainerData.VideoInfo(
it.id,
it.duration,
it.poster,
it.width,
it.height
)
}
val show = !((user.id == UserManager.getInstance().userId && videos.isNotEmpty())
|| (user.id != UserManager.getInstance().userId && getPassVideos().isNotEmpty())
|| images.isNullOrEmpty())
return ImageContainerData(
status = status,
isPostCard = type == COMMUNITY_ARTICLE,
images = imageInfoList,
video = video,
show
)
}
}
data class ImageContainerData(
val status: String,
val isPostCard: Boolean, // 是否是帖子卡片
val images: List<ImageInfo>,
val video: VideoInfo? = null,
val show: Boolean
) {
data class ImageInfo(
val url: String,
var width: Int = 0,
var height: Int = 0
)
data class VideoInfo(
val id: String,
val duration: String,
val poster: String,
var width: Int = 0,
var height: Int = 0,
)
}
interface OnImageContainerEventListener {
fun onImageClick(
images: List<String>,
position: Int,
imageViewList: ArrayList<SimpleDraweeView>
)
fun onVideoCLick(videoId: String)
}
}

View File

@ -5,6 +5,7 @@ import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.ListAdapter
import com.gh.gamecenter.common.utils.formatTime
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.databinding.ItemArchiveLimitBinding
import com.gh.gamecenter.entity.ArchiveEntity
@ -29,10 +30,7 @@ class ArchiveLimitAdapter(context: Context) : ListAdapter<ArchiveLimitAdapter.Ar
if (holder is ArchiveLimitViewHolder) {
val item = mEntityList[position]
holder.binding.tvTitle.text = item.data.name
val timeLong = item.data.time.create
val sdf = SimpleDateFormat("yyyy-MM-dd HH:mm", Locale.CHINA)
val date = Date(timeLong)
holder.binding.tvTime.text = sdf.format(date)
holder.binding.tvTime.text = item.data.time.create.formatTime("yyyy-MM-dd HH:mm")
val resId = if (item.isChecked) R.drawable.ic_selector_selected else R.drawable.ic_selector_default
holder.binding.ivSelector.setImageResource(resId)
@ -53,10 +51,10 @@ class ArchiveLimitAdapter(context: Context) : ListAdapter<ArchiveLimitAdapter.Ar
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: MutableList<Any?>) {
if(holder is ArchiveLimitViewHolder){
if(payloads.isEmpty()){
if (holder is ArchiveLimitViewHolder) {
if (payloads.isEmpty()) {
onBindViewHolder(holder, position)
}else{
} else {
val item = mEntityList[position]
val resId = if (item.isChecked) R.drawable.ic_selector_selected else R.drawable.ic_selector_default
holder.binding.ivSelector.setImageResource(resId)

View File

@ -0,0 +1,23 @@
package com.gh.gamecenter.entity
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.google.gson.annotations.SerializedName
data class FollowCommonContentCollection(
@SerializedName("title")
private val _title: String? = null,
@SerializedName("link")
private val _link: LinkEntity? = null,
@SerializedName("link_common_collection")
private val _linkCommonCollection: CustomPageData.CommonContentCollection? = null
) {
val title: String
get() = _title ?: ""
val link: LinkEntity
get() = _link ?: LinkEntity()
val linkCommonCollection: CustomPageData.CommonContentCollection
get() = _linkCommonCollection ?: CustomPageData.CommonContentCollection()
}

View File

@ -0,0 +1,137 @@
package com.gh.gamecenter.entity
import android.os.Parcelable
import com.gh.gamecenter.feature.entity.*
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
@Parcelize
data class FollowDynamicEntity(
@SerializedName("type")
private val _type: String? = null,
@SerializedName("libao")
val libao: LibaoEntity? = null,
@SerializedName("libao_exchange")
val libaoExchange: LibaoEntity? = null,
@SerializedName("game")
private val _game: GameEntity? = null,
@SerializedName("me")
private val _me: MeEntity? = null,
@SerializedName("time")
private val _time: Long? = null,
@SerializedName("article")
val article: Article? = null,
@SerializedName("user_post")
val userPost: ArticleEntity? = null
) : Parcelable {
val type: String
get() = _type ?: ""
val game: GameEntity
get() = _game ?: GameEntity()
val me: MeEntity
get() = _me ?: MeEntity()
val time: Long
get() = _time ?: 0L
companion object {
const val FOLLOW_UPDATE_TYPE_LIBAO = "libao"
const val FOLLOW_UPDATE_TYPE_LIBAO_EXCHANGE = "libao_exchange"
const val FOLLOW_UPDATE_TYPE_ARTICLE = "article"
const val FOLLOW_UPDATE_TYPE_USER_POST = "user_post"
}
@Parcelize
data class Article(
@SerializedName("_id")
private val _id: String? = null,
@SerializedName("title")
private val _title: String? = null,
@SerializedName("content")
private val _content: String? = null,
@SerializedName("img")
private val _img: List<String>? = null,
@SerializedName("count")
private val _count: Count? = null,
@SerializedName("game")
private val _game: GameEntity? = null,
@SerializedName("time")
private val _time: Long? = null,
@SerializedName("_seq")
private val _shortId: String? = null
) : Parcelable {
val id: String
get() = _id ?: ""
val title: String
get() = _title ?: ""
val content: String
get() = _content ?: ""
val img: List<String>
get() = _img ?: listOf()
val count: Count
get() = _count ?: Count()
val game: GameEntity
get() = _game ?: GameEntity()
val time: Long
get() = _time ?: 0L
val shortId: String
get() = _shortId ?: ""
@Parcelize
data class Count(
@SerializedName("comment")
private val _comment: Int? = null
) : Parcelable {
val comment: Int
get() = _comment ?: 0
}
}
@Parcelize
data class LibaoExchange(
@SerializedName("_id")
private val _id: String? = null,
@SerializedName("libao_id")
private val _libaoId: String? = null,
@SerializedName("libao_code")
private val _libaoCode: String? = null,
@SerializedName("name")
private val _name: String? = null,
@SerializedName("content")
private val _content: String? = null,
@SerializedName("active")
private val _active: Boolean? = null
) : Parcelable {
val id: String
get() = _id ?: ""
val libaoId: String
get() = _libaoId ?: ""
val libaoCode: String
get() = _libaoCode ?: ""
val name: String
get() = _name ?: ""
val content: String
get() = _content ?: ""
val active: Boolean
get() = _active ?: false
}
}

View File

@ -0,0 +1,7 @@
package com.gh.gamecenter.entity
data class FollowOperateTopRequest(
val _id: String,
val order: Int
) {
}

View File

@ -0,0 +1,159 @@
package com.gh.gamecenter.entity
import android.os.Parcelable
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.User
import com.gh.gamecenter.feature.entity.UserEntity
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.Parcelize
import java.util.Objects
@Parcelize
data class FollowUserEntity(
@SerializedName("_id")
private val _id: String? = null,
@SerializedName("type")
private val _type: String? = null, // user用户、bbs论坛
@SerializedName("user")
private val _user: UserEntity? = null,
@SerializedName("bbs")
private var _bbs: Bbs? = null,
@SerializedName("is_top")
private val _isTop: Int? = null,
@SerializedName("is_show_tip")
private val _isShowTip: Int? = null,
) : Parcelable {
val id: String
get() = _id ?: ""
val type: String
get() = _type ?: ""
val user: UserEntity
get() = _user ?: UserEntity()
val isTop: Boolean
get() = _isTop == 1
val isShowTip: Boolean
get() = _isShowTip == 1
val bbs: Bbs
get() = _bbs ?: Bbs()
val isUser: Boolean
get() = type == TYPE_USER
val name: String
get() = if (isUser) {
user.name ?: ""
} else {
bbs.name
}
val icon: String
get() = when {
type == TYPE_USER -> user.icon ?: ""
bbs.type == BBS_TYPE_GAME -> bbs.game.icon ?: ""
else -> bbs.icon
}
override fun equals(other: Any?): Boolean {
return other is FollowUserEntity
&& id == other.id
&& type == other.type
&& user.id == other.user.id
&& user.name == other.user.name
&& bbs.id == other.bbs.id
&& bbs.name == other.bbs.name
&& bbs.type == other.bbs.type
&& bbs.icon == other.bbs.icon
&& bbs.game.icon == other.bbs.game.icon
&& isTop == other.isTop
&& isShowTip == other.isShowTip
&& icon == other.icon
}
override fun hashCode(): Int {
return Objects.hash(
_id,
type,
user.id,
user.name,
bbs.id,
bbs.name,
bbs.type,
bbs.icon,
bbs.game.icon,
isTop,
isShowTip,
icon
)
}
companion object {
private const val TYPE_USER = "user"
private const val BBS_TYPE_GAME = "game_bbs"
}
@Parcelize
data class Bbs(
@SerializedName("_id")
private val _id: String? = null,
@SerializedName("name")
private val _name: String? = null,
@SerializedName("type")
private val _type: String? = null,
@SerializedName("icon")
private val _icon: String? = null,
@SerializedName("game")
private val _game: GameEntity? = null
) : Parcelable {
val id: String
get() = _id ?: ""
val name: String
get() = _name ?: ""
val type: String
get() = _type ?: ""
val icon: String
get() = _icon ?: ""
val game: GameEntity
get() = _game ?: GameEntity()
}
@Parcelize
data class Game(
@SerializedName("_id")
private val _id: String? = null,
@SerializedName("name")
private val _name: String? = null,
@SerializedName("icon")
private val _icon: String? = null,
@SerializedName("ori_icon")
private val _oriIcon: String? = null,
@SerializedName("active")
private val _active: Boolean? = null
) : Parcelable {
val id: String
get() = _id ?: ""
val name: String
get() = _name ?: ""
val icon: String
get() = _icon ?: ""
val oriIcon: String
get() = _oriIcon ?: ""
val active: Boolean
get() = _active ?: false
}
}

View File

@ -163,6 +163,14 @@ data class PersonalHistoryEntity(
get() = community.name
set(_) {}
override fun vote(isVote: Boolean) {
_count.vote = if (isVote) {
_count.vote + 1
} else {
_count.vote - 1
}
}
fun getPassVideos(): List<CommunityVideoEntity> {
val passVideos = arrayListOf<CommunityVideoEntity>()
for (video in videos) {

View File

@ -4,10 +4,7 @@ import android.os.Parcelable
import com.alibaba.android.arouter.launcher.ARouter
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.core.provider.IConfigProvider
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.MeEntity
import com.gh.gamecenter.feature.entity.SourceEntity
import com.gh.gamecenter.feature.entity.UserEntity
import com.gh.gamecenter.feature.entity.*
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
@ -62,6 +59,16 @@ open class VideoEntity(
@SerializedName("_source")
val ipSource: SourceEntity? = null,
// 关注首页新增字段
@SerializedName("choiceness")
private val _choiceness: Boolean? = null,
@SerializedName("video_info")
private val _videoInfo: VideoInfo? = null,
@SerializedName("show_me_only")
private val _showMeOnly: Boolean? = null,
@SerializedName("count")
private val _count: Count? = null,
//本地数据
@IgnoredOnParcel
var videoIsMuted: Boolean = false,//是否静音标记
@ -73,6 +80,18 @@ open class VideoEntity(
val gameName: String
get() = mGameName.removeSuffix(".")
val choiceness: Boolean
get() = _choiceness ?: false
val videoInfo: VideoInfo
get() = _videoInfo ?: VideoInfo()
val showMeOnly: Boolean
get() = _showMeOnly ?: false
val count: Count
get() = _count ?: Count()
fun getThumb(): String {
val configProvider = ARouter.getInstance().build(RouteConsts.provider.config).navigation() as? IConfigProvider
return if (!configProvider?.getVideoSnapshotSuffix().isNullOrEmpty()) {

View File

@ -42,6 +42,10 @@ class ForumArticleAskListAdapter(
private var mVideoOrderList = listOf("推荐", "发布")
private var mFilterPosition = if (path == "视频") 1 else 0
override fun setListData(updateData: MutableList<AnswerEntity>?) {
super.setListData(updateData)
}
override fun areItemsTheSame(oldItem: AnswerEntity?, newItem: AnswerEntity?): Boolean {
return oldItem?.id == newItem?.id
}

View File

@ -0,0 +1,86 @@
package com.gh.gamecenter.forum.home
import android.app.Activity
import com.gh.gamecenter.common.entity.AdditionalParamsEntity
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.ShareUtils
import com.gh.gamecenter.common.utils.isPublishEnv
import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.login.user.UserManager
import com.shuyu.gsyvideoplayer.utils.OrientationUtils
class AnswerArticleVideoViewEventHelper(
private val entity: ForumVideoEntity,
private val orientationUtils: OrientationUtils
) : ArticleItemVideoView.OnArticleItemVideoViewEventListener {
override fun onTrackVideoStartPlaying() {
SensorsBridge.trackVideoStartPlaying(
articleId = entity.id,
bbsId = entity.bbs?.id ?: "",
bbsType = entity.bbs?.typeChinese ?: "综合论坛",
customerType = entity.user.auth?.text ?: "",
videoId = entity.id,
playType = "视频贴",
gameForumType = entity.bbs?.game?.categoryChinese ?: "",
activityTag = entity.tagActivityName,
articleType = entity.typeChinese
)
}
override fun onTrackVideoEndPlaying(maxPlayedProgress: Int) {
SensorsBridge.trackVideoEndPlaying(
articleId = entity.id,
bbsId = entity.bbs?.id ?: "",
bbsType = entity.bbs?.typeChinese ?: "综合论坛",
customerType = entity.user.auth?.text ?: "",
videoId = entity.id,
playType = "视频贴",
gameForumType = entity.bbs?.game?.categoryChinese ?: "",
activityTag = entity.tagActivityName,
articleType = entity.typeChinese,
result = if (maxPlayedProgress >= 95) "" else ""
)
}
override fun onShare(videoView: ArticleItemVideoView) {
val shareIcon = entity.poster
val shareUrl = if (isPublishEnv()) {
"https://m.ghzs666.com/video/${entity.id}"
} else {
"https://resource.ghzs.com/page/video_play/video/video.html?video=${entity.id}"
}
val additionalParams = AdditionalParamsEntity().apply {
contentType = "视频帖"
contentId = entity.id ?: ""
bbsId = entity.bbs?.id ?: ""
bbsType = entity.bbs?.typeChinese ?: "综合论坛"
customerType = entity.user.auth?.text ?: ""
activityTagName = entity.tagActivityName ?: ""
gameForumType = entity.bbs?.game?.categoryChinese ?: ""
refUserId = UserManager.getInstance().userId
}
val activity = (videoView.context as? Activity) ?: return
ShareUtils.getInstance(videoView.context).showShareWindowsCallback(activity,
videoView,
shareUrl,
shareIcon,
entity.title,
entity.des,
ShareUtils.ShareEntrance.video,
entity.id,
additionalParams,
object : ShareUtils.ShareCallBack {
override fun onSuccess(label: String) = Unit
override fun onCancel() = Unit
})
}
override fun onMutedChanged(isMuted: Boolean) {
entity.videoIsMuted = isMuted
}
override fun onStartWindowFullscreen() {
orientationUtils.resolveByClick()
}
}

View File

@ -1,7 +1,8 @@
package com.gh.gamecenter.forum.home
import android.app.Activity
import android.content.Context
import android.os.Handler
import android.os.Looper
import android.util.AttributeSet
import android.view.MotionEvent
import android.view.Surface
@ -12,11 +13,9 @@ import android.widget.TextView
import androidx.core.content.ContextCompat
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.gamecenter.R
import com.gh.gamecenter.common.entity.AdditionalParamsEntity
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.MD5Utils
import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.video.detail.CustomManager
import com.lightgame.utils.Utils
import com.shuyu.gsyvideoplayer.utils.CommonUtil
@ -25,13 +24,12 @@ import com.shuyu.gsyvideoplayer.video.base.GSYVideoView
import com.shuyu.gsyvideoplayer.video.base.GSYVideoViewBridge
import io.reactivex.disposables.Disposable
import java.util.*
import android.os.Handler
import android.os.Looper
class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :
StandardGSYVideoPlayer(context, attrs) {
private var mVideoEntity: ForumVideoEntity? = null
var data: ArticleVideoData? = null
private var mMuteDisposable: Disposable? = null
private var mIsAutoPlay = false
var uuid = UUID.randomUUID().toString()
@ -53,6 +51,8 @@ class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: At
private val mTrackHandler = Handler(Looper.getMainLooper())
private var onEventListener: OnArticleItemVideoViewEventListener? = null
override fun getLayoutId(): Int {
return R.layout.layout_article_item_video
}
@ -81,7 +81,7 @@ class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: At
fun startPlayLogic(isAutoPlay: Boolean) {
mIsAutoPlay = isAutoPlay
if (mIsAutoPlay) {
val seekTime = ForumScrollCalculatorHelper.getPlaySchedule(MD5Utils.getContentMD5(mVideoEntity?.url))
val seekTime = ForumScrollCalculatorHelper.getPlaySchedule(MD5Utils.getContentMD5(data?.url))
seekOnStart = seekTime
}
startPlayLogic()
@ -137,7 +137,7 @@ class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: At
}
override fun onClickUiToggle(e: MotionEvent?) {
if (mVideoEntity?.status != "pending" && mVideoEntity?.status != "fail") {
if (data?.status != "pending" && data?.status != "fail") {
if (mCurrentState == CURRENT_STATE_PLAYING) {
if (mStartButton.visibility == View.VISIBLE) {
changeUiToPlayingClear()
@ -156,7 +156,6 @@ class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: At
super.onSurfaceUpdated(surface)
if (mThumbImageViewLayout != null && mThumbImageViewLayout.visibility == View.VISIBLE) {
mThumbImageViewLayout.visibility = View.INVISIBLE
uploadVideoStreamingPlaying("开始播放")
}
}
@ -169,14 +168,9 @@ class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: At
// }
override fun releaseVideos() {
uploadVideoStreamingPlaying("结束播放")
CustomManager.releaseAllVideos(getKey())
}
override fun onVideoPause() {
super.onVideoPause()
uploadVideoStreamingPlaying("暂停播放")
}
// 重载以减少横竖屏切换的时间
override fun checkoutState() {
@ -206,9 +200,7 @@ class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: At
private fun showBackBtn() {
mTopContainer.background = ContextCompat.getDrawable(context, R.drawable.video_title_bg)
back.visibility = View.VISIBLE
mVideoEntity?.run {
titleTv.text = title
}
data?.title?.let(titleTv::setText)
}
private fun hideBackBtn() {
@ -217,19 +209,32 @@ class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: At
titleTv.text = ""
}
fun updateThumb(url: String) {
ImageUtils.display(thumbImage, url)
}
fun updateDurationTv(duration: String) {
durationTv.text = duration
}
fun updateVideoData(video: ForumVideoEntity) {
fun updateVideoData(video: ArticleVideoData, listener: OnArticleItemVideoViewEventListener) {
this.onEventListener = listener
this.mStopTrackRunnable = null
mVideoEntity = video
data = video
titleTv.text = if (mIfCurrentIsFullscreen) video.title else ""
ImageUtils.display(thumbImage, video.poster)
if (!mIfCurrentIsFullscreen) {
durationTv.text = video.duration
setVideoStatus(video.status)
fullscreenButton.setOnClickListener {
val horizontalVideoView =
startWindowFullscreen(context, true, true) as? ArticleItemVideoView
if (horizontalVideoView == null) {
toastInInternalRelease("全屏失败,请向技术人员提供具体的操作步骤")
return@setOnClickListener
}
onEventListener?.onStartWindowFullscreen()
horizontalVideoView.uuid = uuid
horizontalVideoView.updateVideoData(video, listener)
horizontalVideoView.violenceUpdateMuteStatus()
horizontalVideoView.setFullViewStatus()
}
}
}
override fun setViewShowState(view: View?, visibility: Int) {
@ -266,25 +271,16 @@ class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: At
override fun setStateAndUi(state: Int) {
if (state != currentState
&& state == CURRENT_STATE_PLAYING
&& currentState != CURRENT_STATE_PLAYING_BUFFERING_START) {// 上报开始视频播放埋点
&& currentState != CURRENT_STATE_PLAYING_BUFFERING_START
) {// 上报开始视频播放埋点
// 视频停止播放后再恢复播放的间隔时间未超过3秒则取消上报结束视频播放埋点
mStopTrackRunnable?.let { runnable ->
mTrackHandler.removeCallbacks(runnable)
}
mVideoEntity?.let {
data?.let {
val startTrackRunnable = Runnable {
SensorsBridge.trackVideoStartPlaying(
articleId = it.id,
bbsId = it.bbs?.id ?: "",
bbsType = it.bbs?.typeChinese ?: "综合论坛",
customerType = it.user.auth?.text ?: "",
videoId = it.id,
playType = "视频贴",
gameForumType = it.bbs?.game?.categoryChinese ?: "",
activityTag = it.tagActivityName,
articleType = it.typeChinese
)
onEventListener?.onTrackVideoStartPlaying()
mStartTrackRunnable = null
}.also {
mStartTrackRunnable = it
@ -303,20 +299,9 @@ class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: At
mTrackHandler.removeCallbacks(it)
mStartTrackRunnable = null
} ?: let {
mVideoEntity?.let {
data?.let {
val stopTrackRunnable = Runnable {
SensorsBridge.trackVideoEndPlaying(
articleId = it.id,
bbsId = it.bbs?.id ?: "",
bbsType = it.bbs?.typeChinese ?: "综合论坛",
customerType = it.user.auth?.text ?: "",
videoId = it.id,
playType = "视频贴",
gameForumType = it.bbs?.game?.categoryChinese ?: "",
activityTag = it.tagActivityName,
articleType = it.typeChinese,
result = if (mMaxPlayedProgress >= 95) "" else ""
)
onEventListener?.onTrackVideoEndPlaying(mMaxPlayedProgress)
}.also {
this.mStopTrackRunnable = it
}
@ -342,10 +327,9 @@ class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: At
replayTv.setOnClickListener {
startButton.performClick()
violenceUpdateMuteStatus()
uploadVideoStreamingPlaying("重新播放")
}
shareTv.setOnClickListener {
share()
onEventListener?.onShare(this)
}
} else {
setViewShowState(completeContainer, View.GONE)
@ -363,88 +347,33 @@ class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: At
}
private fun toggleMute() {
if (mVideoEntity?.videoIsMuted == true) {
unMute(true)
if (data?.videoIsMuted == true) {
unMute()
} else {
mute(true)
mute()
}
}
fun updateMuteStatus() {
if (mVideoEntity?.videoIsMuted == true) {
private fun updateMuteStatus() {
if (data?.videoIsMuted == true) {
mute()
} else {
unMute()
}
}
fun mute(isManual: Boolean = false) {
mVideoEntity?.videoIsMuted = true
private fun mute() {
onEventListener?.onMutedChanged(true)
data?.videoIsMuted = true
volume.setImageResource(R.drawable.ic_article_video_volume_off)
CustomManager.getCustomManager(getKey()).isNeedMute = true
if (isManual) {
// Utils.toast(context, "当前处于静音状态")
uploadVideoStreamingPlaying("点击静音")
}
}
fun unMute(isManual: Boolean = false) {
mVideoEntity?.videoIsMuted = false
private fun unMute() {
onEventListener?.onMutedChanged(false)
data?.videoIsMuted = false
volume.setImageResource(R.drawable.ic_article_video_volume_on)
CustomManager.getCustomManager(getKey()).isNeedMute = false
if (isManual) {
uploadVideoStreamingPlaying("取消静音")
}
}
override fun onAutoCompletion() {
super.onAutoCompletion()
uploadVideoStreamingPlaying("播放完毕")
}
override fun onStopTrackingTouch(seekBar: SeekBar?) {
super.onStopTrackingTouch(seekBar)
uploadVideoStreamingPlaying("拖动")
}
private fun share() {
mVideoEntity?.let {
val shareIcon = it.poster
val shareUrl = if (isPublishEnv()) {
"https://m.ghzs666.com/video/${it.id}"
} else {
"https://dev-and-static.ghzs66.com/page/video_play/video/video.html?video=${it.id}"
}
val additionalParams = AdditionalParamsEntity().apply {
contentType = "视频帖"
contentId = mVideoEntity?.id ?: ""
bbsId = mVideoEntity?.bbs?.id ?: ""
bbsType = mVideoEntity?.bbs?.typeChinese ?: "综合论坛"
customerType = mVideoEntity?.user?.auth?.text ?: ""
activityTagName = mVideoEntity?.tagActivityName ?: ""
gameForumType = mVideoEntity?.bbs?.game?.categoryChinese ?: ""
refUserId = UserManager.getInstance().userId
}
ShareUtils.getInstance(context).showShareWindowsCallback(context as Activity,
this,
shareUrl,
shareIcon,
it.title,
it.des,
ShareUtils.ShareEntrance.video, it.id, additionalParams, object : ShareUtils.ShareCallBack {
override fun onSuccess(label: String) {
// if ("短信" == label || "复制链接" == label) viewModel?.shareVideoStatistics(it)
}
override fun onCancel() {
uploadVideoStreamingPlaying("取消分享")
}
})
}
}
fun uploadVideoStreamingPlaying(action: String) {
}
fun setFullViewStatus() {
@ -456,7 +385,7 @@ class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: At
durationTv.visibility = View.GONE
}
fun setVideoStatus(status: String) {
private fun setVideoStatus(status: String) {
if (status == "pending" || status == "fail") {
pendingView.text = if (status == "pending") "审核中...请耐心等待" else "审核不通过"
pendingView.visibility = View.VISIBLE
@ -494,4 +423,32 @@ class ArticleItemVideoView @JvmOverloads constructor(context: Context, attrs: At
super.onDetachedFromWindow()
mMuteDisposable?.dispose()
}
companion object {
fun ForumVideoEntity.toArticleVideoData() =
ArticleVideoData(url, title, status, poster, duration, videoIsMuted)
}
data class ArticleVideoData(
val url: String,
val title: String,
val status: String,
val poster: String,
val duration: String,
var videoIsMuted: Boolean
)
interface OnArticleItemVideoViewEventListener {
fun onTrackVideoStartPlaying()
fun onTrackVideoEndPlaying(maxPlayedProgress: Int)
fun onShare(videoView: ArticleItemVideoView)
fun onMutedChanged(isMuted: Boolean)
fun onStartWindowFullscreen()
}
}

View File

@ -4,6 +4,7 @@ import android.app.Activity
import android.content.Intent
import android.content.res.ColorStateList
import android.graphics.Bitmap
import android.graphics.ColorFilter
import android.graphics.Typeface
import android.os.Build
import android.os.Bundle
@ -11,11 +12,16 @@ import android.view.*
import android.view.animation.AnimationUtils
import android.widget.FrameLayout
import android.widget.TextView
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.graphics.ColorUtils
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.viewpager.widget.ViewPager
import com.airbnb.lottie.LottieProperty
import com.airbnb.lottie.SimpleColorFilter
import com.airbnb.lottie.model.KeyPath
import com.airbnb.lottie.value.LottieValueCallback
import com.gh.common.browse.BrowseTimer
import com.gh.common.util.DirectUtils
import com.gh.common.util.NewFlatLogUtils
@ -30,6 +36,7 @@ import com.gh.gamecenter.common.constant.EntranceConsts.IS_DETAIL_PAGE
import com.gh.gamecenter.common.retrofit.ApiResponse
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.AvatarBorderView
import com.gh.gamecenter.core.iinterface.IScrollable
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.SPUtils
@ -41,8 +48,11 @@ import com.gh.gamecenter.eventbus.EBSkip
import com.gh.gamecenter.eventbus.EBTypeChange
import com.gh.gamecenter.feature.entity.ArticleEntity
import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.forum.home.follow.FollowHomeFilterPopWindow
import com.gh.gamecenter.forum.home.follow.fragment.FollowHomeFragment
import com.gh.gamecenter.forum.search.ForumOrUserSearchActivity
import com.gh.gamecenter.login.entity.UserInfoEntity
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.login.user.UserRepository
import com.gh.gamecenter.qa.article.detail.ArticleDetailWebCacheManager
import com.gh.gamecenter.qa.article.edit.ArticleEditActivity
@ -52,6 +62,7 @@ import com.gh.gamecenter.qa.video.publish.VideoPublishActivity
import com.gh.gamecenter.video.detail.VideoDetailContainerViewModel
import com.gh.gamecenter.wrapper.MainWrapperViewModel
import com.google.android.material.tabs.TabLayout
import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
import com.halo.assistant.HaloApp
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
@ -64,7 +75,7 @@ class CommunityHomeFragment : LazyFragment() {
private var mViewModel: CommunityHomeViewModel? = null
private var mMainWrapperViewModel: MainWrapperViewModel? = null
private var mFragmentList = arrayListOf<Fragment>()
private var mTitleList = arrayListOf("推荐", "论坛", "活动")
private var mTitleList = arrayListOf("关注", "推荐", "论坛", "活动")
private var mTabList = arrayListOf<Any>()
private var mDefaultSelectedTab = -1
private var mNavigationBitmap: Bitmap? = null
@ -76,6 +87,42 @@ class CommunityHomeFragment : LazyFragment() {
SensorsBridge.trackCommunityBrowsingDuration(it / 1000.0)
}
private val followFilterPopWindow by lazy {
FollowHomeFilterPopWindow.create(requireContext()).apply {
setOnDismissListener {
resetFollowTab()
}
setFilterListener(object : FollowHomeFilterPopWindow.OnFilteredChangedListener {
override fun filterFollowed(position: Int) {
mViewModel?.filterFollowed(position)
}
})
}
}
private val obTabSelectedListener = object : OnTabSelectedListener {
override fun onTabSelected(tab: TabLayout.Tab?) {
if (tab?.position == TAB_FOLLOW_INDEX) {
mViewModel?.updateFilterResId(R.drawable.ic_follow_arrow_down)
}
}
override fun onTabUnselected(tab: TabLayout.Tab?) {
if (tab?.position == TAB_FOLLOW_INDEX) {
mViewModel?.updateFilterResId(null)
}
}
override fun onTabReselected(tab: TabLayout.Tab?) {
if (mViewModel?.followFilterStatus?.value == R.drawable.ic_follow_arrow_down) {
mViewModel?.updateFilterResId(R.drawable.ic_follow_arrow_up)
mBinding?.tabLayout?.let(followFilterPopWindow::showAsDropDown)
}
}
}
override fun getRealLayoutId(): Int {
return R.layout.fragment_community_home
}
@ -94,6 +141,19 @@ class CommunityHomeFragment : LazyFragment() {
super.onFragmentFirstVisible()
initViewPager()
mViewModel?.followFilterStatus?.observe(viewLifecycleOwner, Observer { resId ->
val tabLayout = mBinding?.tabLayout ?: return@Observer
val tab = tabLayout.getTabAt(TAB_FOLLOW_INDEX)
val tvTitle = tab?.customView?.findViewById<TextView>(R.id.tab_title)
if (resId != null) {
tvTitle?.setDrawableEnd(resId, 8F.dip2px(), 8F.dip2px())
} else {
tvTitle?.setDrawableEnd(null, 8F.dip2px(), 8F.dip2px())
}
})
}
override fun initRealView() {
@ -144,7 +204,13 @@ class CommunityHomeFragment : LazyFragment() {
topBg.visibleIf(!mIsDarkModeOn)
videoLottie.setOnClickListener {
DirectUtils.directToLegacyVideoDetail(requireContext(), "", VideoDetailContainerViewModel.Location.VIDEO_ACTIVITY.value, referer = "视频流-社区右上角", isHomeVideo = true)
DirectUtils.directToLegacyVideoDetail(
requireContext(),
"",
VideoDetailContainerViewModel.Location.VIDEO_ACTIVITY.value,
referer = "视频流-社区右上角",
isHomeVideo = true
)
}
searchIconIv.setOnClickListener {
NewLogUtils.logCommunitySearchClick()
@ -234,7 +300,12 @@ class CommunityHomeFragment : LazyFragment() {
mTabList.clear()
mFragmentList.clear()
val tag = "android:switcher:${viewPager.id}:"
val forumArticleListFragment = childFragmentManager.findFragmentByTag("${tag}0")
val followFragment = childFragmentManager.findFragmentByTag("$tag$TAB_FOLLOW_INDEX")
?: FollowHomeFragment()
mFragmentList.add(followFragment)
val forumArticleListFragment = childFragmentManager.findFragmentByTag("$tag$TAB_RECOMMEND_INDEX")
?: ForumArticleListFragment().with(
bundleOf(
EntranceConsts.KEY_ENTRANCE to "社区",
@ -243,11 +314,11 @@ class CommunityHomeFragment : LazyFragment() {
)
mFragmentList.add(forumArticleListFragment)
val forumFragment = childFragmentManager.findFragmentByTag("${tag}1")
val forumFragment = childFragmentManager.findFragmentByTag("${tag}$TAB_FORUM_INDEX")
?: ForumFragment().with(bundleOf(EntranceConsts.KEY_ENTRANCE to "社区"))
mFragmentList.add(forumFragment)
val activityFragment = childFragmentManager.findFragmentByTag("${tag}2")
val activityFragment = childFragmentManager.findFragmentByTag("${tag}$TAB_ACTIVITY_INDEX")
?: ForumActivityFragment().with(bundleOf(EntranceConsts.KEY_ENTRANCE to "活动"))
mFragmentList.add(activityFragment)
@ -261,8 +332,16 @@ class CommunityHomeFragment : LazyFragment() {
adapter = FragmentAdapter(childFragmentManager, mFragmentList, mTitleList)
doOnScroll(
onPageSelected = { position ->
communityEditBtn.goneIf(position != 0)
communityEditBtn.goneIf(position != TAB_RECOMMEND_INDEX && position != TAB_FOLLOW_INDEX)
when (position) {
TAB_FOLLOW_INDEX -> {
root.setBackgroundColor(R.color.ui_background.toColor(requireContext()))
topBg.translationY = 0F
changeNavigationBg()
NewLogUtils.logCommunityHomeEvent("click_for_you_tab")
SensorsBridge.trackCommunityTopTabSelected("关注")
}
TAB_RECOMMEND_INDEX -> {
root.setBackgroundColor(R.color.ui_background.toColor(requireContext()))
topBg.translationY = 0F
@ -273,7 +352,7 @@ class CommunityHomeFragment : LazyFragment() {
TAB_FORUM_INDEX -> {
root.setBackgroundColor(R.color.ui_surface.toColor(requireContext()))
(mFragmentList[1] as ForumFragment).translationY.run {
(mFragmentList[2] as ForumFragment).translationY.run {
topBg.translationY = -this.toFloat()
changeNavigationBg(this)
}
@ -283,7 +362,7 @@ class CommunityHomeFragment : LazyFragment() {
TAB_ACTIVITY_INDEX -> {
root.setBackgroundColor(R.color.ui_background.toColor(requireContext()))
(mFragmentList[2] as ForumActivityFragment).translationY.run {
(mFragmentList[3] as ForumActivityFragment).translationY.run {
topBg.translationY = -this.toFloat()
changeNavigationBg(this)
}
@ -295,8 +374,6 @@ class CommunityHomeFragment : LazyFragment() {
onPageScrolled = { position, positionOffset, _ ->
if (position + 1 != mTabList.size) {
(mTabList[position] as TextView).run {
layoutParams.width =
(DEFAULT_TAB_TEXT_WIDTH + ((1 - positionOffset) * 4F.dip2px())).roundToInt()
textSize = (DEFAULT_TAB_TEXT_SIZE + ((1 - positionOffset) * 4)).roundTo(1)
setTextColor(
ColorUtils.blendARGB(
@ -308,8 +385,6 @@ class CommunityHomeFragment : LazyFragment() {
}
if (mTabList[position + 1] is TextView) {
(mTabList[position + 1] as TextView).run {
layoutParams.width =
(DEFAULT_TAB_TEXT_WIDTH + ((positionOffset) * 4F.dip2px())).roundToInt()
textSize = (DEFAULT_TAB_TEXT_SIZE + ((positionOffset) * 4)).roundTo(1)
setTextColor(
ColorUtils.blendARGB(
@ -321,13 +396,12 @@ class CommunityHomeFragment : LazyFragment() {
}
} else {
(mTabList[position + 1] as TabItemCommunityBinding).run {
tabTitle.layoutParams.width =
(DEFAULT_TAB_TEXT_WIDTH + ((positionOffset) * 4F.dip2px())).roundToInt()
tabTitle.textSize = (DEFAULT_TAB_TEXT_SIZE + ((positionOffset) * 4)).roundTo(1)
tabImg.scaleX =
(DEFAULT_TAB_IMG_WIDTH + ((positionOffset) * 8)).roundTo(1) / DEFAULT_TAB_IMG_WIDTH
tabImg.scaleY =
(DEFAULT_TAB_IMG_HEIGHT + ((positionOffset) * 4)).roundTo(1) / DEFAULT_TAB_IMG_HEIGHT
val layoutParams = tabImg.layoutParams
layoutParams.width =
(DEFAULT_TAB_IMG_WIDTH + 8F.dip2px() * positionOffset).roundToInt()
layoutParams.height =
((DEFAULT_TAB_IMG_HEIGHT) + 4F.dip2px() * positionOffset).roundToInt()
tabImg.layoutParams = layoutParams
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
tabImg.imageTintList =
ColorStateList.valueOf(
@ -338,6 +412,7 @@ class CommunityHomeFragment : LazyFragment() {
)
)
}
}
}
@ -356,20 +431,26 @@ class CommunityHomeFragment : LazyFragment() {
}
)
}
tabLayout.addOnTabSelectedListener(obTabSelectedListener)
tabLayout.setupWithViewPager(viewPager)
indicatorView.run {
setupWithTabLayout(tabLayout)
setupWithViewPager(viewPager)
setIndicatorWidth(18)
}
val selectedPosition = if (UserManager.getInstance().isLoggedIn) {
TAB_FOLLOW_INDEX
} else {
TAB_RECOMMEND_INDEX
}
for (i in 0 until tabLayout.tabCount) {
val tab = tabLayout.getTabAt(i) ?: continue
val tabTitle = if (tab.text != null) tab.text.toString() else ""
val tabViewBinding = generateTabView(tabTitle, i)
val tabViewBinding = generateTabView(tabTitle, i, selectedPosition)
tab.customView = tabViewBinding.root
tab.view.setPadding(0, 0, 0, 0)
}
viewPager.setCurrentItem(selectedPosition, false)
}
}
@ -392,7 +473,6 @@ class CommunityHomeFragment : LazyFragment() {
if (index == selectedPosition) {
if (positionOffset == 0F) {
titleView.setTextColor(TAB_SELECTED_COLOR.toColor(requireContext()))
titleView.setTypeface(null, Typeface.NORMAL)
titleView.setTypeface(titleView.typeface, Typeface.BOLD)
} else if (positionOffset > 0F) {
titleView.setTypeface(null, Typeface.NORMAL)
@ -426,9 +506,6 @@ class CommunityHomeFragment : LazyFragment() {
} else {
tabImg.setImageResource(R.drawable.ic_tab_activity_active)
}
tabTitle.layoutParams.width = 64F.dip2px()
tabImg.scaleX = SCALE_TAB_IMG_WIDTH / DEFAULT_TAB_IMG_WIDTH
tabImg.scaleY = SCALE_TAB_IMG_HEIGHT / DEFAULT_TAB_IMG_HEIGHT
}
}
}
@ -442,30 +519,22 @@ class CommunityHomeFragment : LazyFragment() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
tabImg.imageTintList = ColorStateList.valueOf(TAB_DEFAULT_COLOR.toColor(requireContext()))
}
tabTitle.layoutParams.width = DEFAULT_TAB_TEXT_WIDTH
tabImg.scaleX = 1F
tabImg.scaleY = 1F
}
}
}
private fun generateTabView(title: String, index: Int): TabItemCommunityBinding {
private fun generateTabView(title: String, index: Int, selectedPosition: Int): TabItemCommunityBinding {
val binding = TabItemCommunityBinding.inflate(LayoutInflater.from(requireContext()))
binding.run {
if (index == TAB_ACTIVITY_INDEX) {
mTabList.add(binding)
tabTitle.visibility = View.INVISIBLE
tabTitle.visibility = View.GONE
tabImg.visibility = View.VISIBLE
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
tabImg.setImageResource(R.drawable.ic_tab_activity_svg)
} else {
tabImg.setImageResource(R.drawable.ic_tab_activity_default)
}
tabTitle.run {
text = title
// textSize = DEFAULT_TAB_TEXT_SIZE
// setTextColor(TAB_DEFAULT_COLOR.toColor(requireContext()))
}
} else {
mTabList.add(tabTitle)
tabTitle.visibility = View.VISIBLE
@ -474,13 +543,12 @@ class CommunityHomeFragment : LazyFragment() {
text = title
textSize = DEFAULT_TAB_TEXT_SIZE
setTextColor(TAB_DEFAULT_COLOR.toColor(requireContext()))
if (index == TAB_FOLLOW_INDEX && selectedPosition == TAB_FOLLOW_INDEX) {
mViewModel?.updateFilterResId(R.drawable.ic_follow_arrow_down)
}
}
}
}
// binding.invisibleTabTitle.run {
// text = title
// textSize = DEFAULT_TAB_TEXT_SIZE
// }
return binding
}
@ -568,6 +636,18 @@ class CommunityHomeFragment : LazyFragment() {
}
}
private fun resetFollowTab() {
mBinding?.tabLayout?.run {
if (selectedTabPosition == TAB_FOLLOW_INDEX) {
mViewModel?.updateFilterResId(R.drawable.ic_follow_arrow_down)
} else {
mViewModel?.updateFilterResId(null)
}
}
mBinding?.tabLayout?.removeOnTabSelectedListener(obTabSelectedListener)
mBinding?.tabLayout?.addOnTabSelectedListener(obTabSelectedListener)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
@ -594,13 +674,13 @@ class CommunityHomeFragment : LazyFragment() {
}
private fun insertDataToRecommendTab(entity: ArticleEntity) {
(mFragmentList[0] as? ForumArticleListFragment)?.insertDataToFirstIndex(entity)
(mFragmentList[TAB_RECOMMEND_INDEX] as? ForumArticleListFragment)?.insertDataToFirstIndex(entity)
}
override fun onBackPressed(): Boolean {
mBinding?.viewPager?.run {
if (currentItem == 0) {
return (mFragmentList[0] as ForumArticleListFragment).onBackPressed()
if (currentItem == 1) {
return (mFragmentList[1] as ForumArticleListFragment).onBackPressed()
}
}
return super.onBackPressed()
@ -608,7 +688,10 @@ class CommunityHomeFragment : LazyFragment() {
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(status: EBTypeChange) {
if (mBinding?.viewPager?.currentItem != 0) return
val currentPosition = mBinding?.viewPager?.currentItem ?: 0
if (currentPosition != TAB_FOLLOW_INDEX && currentPosition != TAB_RECOMMEND_INDEX) {
return
}
if (status.type == EB_SHOW_QUESTION_BUTTON) {
setPutQuestionButtonStatus(View.VISIBLE)
@ -634,8 +717,14 @@ class CommunityHomeFragment : LazyFragment() {
private fun scrollToTop() {
if (mFragmentList.isEmpty()) return
(mFragmentList[0] as ForumArticleListFragment).scrollToTop()
setPutQuestionButtonStatus(View.VISIBLE)
val currentPosition = mBinding?.viewPager?.currentItem ?: 0
val fragment = mFragmentList.getOrNull(currentPosition)
if (fragment is IScrollable) {
fragment.scrollToTop()
}
if (currentPosition == TAB_FOLLOW_INDEX || currentPosition == TAB_RECOMMEND_INDEX) {
setPutQuestionButtonStatus(View.VISIBLE)
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
@ -669,7 +758,7 @@ class CommunityHomeFragment : LazyFragment() {
} else if (!mIsDarkModeOn && navigationBg.drawable != null) {
navigationBg.setBackgroundColor(R.color.transparent.toColor(requireContext()))
navigationBg.setImageDrawable(null)
} else if (viewPager.currentItem == TAB_RECOMMEND_INDEX) {
} else if (viewPager.currentItem == TAB_RECOMMEND_INDEX || viewPager.currentItem == TAB_FOLLOW_INDEX) {
navigationBg.setBackgroundColor(
if (mIsDarkModeOn && y > 0) R.color.ui_surface.toColor(requireContext()) else if (mIsDarkModeOn && y == 0) R.color.ui_background.toColor(
requireContext()
@ -690,7 +779,20 @@ class CommunityHomeFragment : LazyFragment() {
for (i in 0 until tabCount) {
val tab: TabLayout.Tab? = getTabAt(i)
if (tab != null) {
updateTabStyle(mBinding?.viewPager?.currentItem ?: 0, 0f)
val tvTitle = tab.customView?.findViewById<TextView>(R.id.tab_title)
if (tvTitle != null && tvTitle.visibility == View.VISIBLE) {
tvTitle.post {
if (i == TAB_FOLLOW_INDEX && tab.isSelected) {
val resId = mViewModel?.followFilterStatus?.value
if (resId != null) {
mViewModel?.updateFilterResId(resId)
}
}
tvTitle.setTextColor(
ColorStateList.valueOf(R.color.text_primary.toColor(requireContext()))
)
}
}
}
}
}
@ -715,7 +817,21 @@ class CommunityHomeFragment : LazyFragment() {
}
}
}
searchIconIv.setImageResource(R.drawable.ic_column_search)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
searchIconIv.imageTintList = ColorStateList.valueOf(R.color.text_primary.toColor(requireContext()))
}
val csl = AppCompatResources.getColorStateList(requireContext(), R.color.text_primary)
val filter = SimpleColorFilter(csl.defaultColor)
val keyPath = KeyPath("**")
val callback = LottieValueCallback<ColorFilter>(filter)
videoLottie.addValueCallback(keyPath, LottieProperty.COLOR_FILTER, callback)
}
followFilterPopWindow.onDarkModeChanged()
}
fun getTopBgView() = mBinding?.topBg
@ -723,16 +839,14 @@ class CommunityHomeFragment : LazyFragment() {
companion object {
var TAB_SELECTED_COLOR: Int = R.color.text_primary
var TAB_DEFAULT_COLOR: Int = R.color.community_forum_more
var DEFAULT_TAB_TEXT_SIZE = 18F
var DEFAULT_TAB_TEXT_WIDTH = 60F.dip2px()
var DEFAULT_TAB_IMG_WIDTH = 34F
var DEFAULT_TAB_IMG_HEIGHT = 18F
var SCALE_TAB_IMG_WIDTH = 42F
var SCALE_TAB_IMG_HEIGHT = 22F
var DEFAULT_TAB_TEXT_SIZE = 16F
var DEFAULT_TAB_IMG_WIDTH = 34F.dip2px()
var DEFAULT_TAB_IMG_HEIGHT = 16F.dip2px()
const val EB_TAB = "forum_tab"
const val TAB_RECOMMEND_INDEX = 0
const val TAB_FORUM_INDEX = 1
const val TAB_ACTIVITY_INDEX = 2
const val TAB_FOLLOW_INDEX = 0
const val TAB_RECOMMEND_INDEX = 1
const val TAB_FORUM_INDEX = 2
const val TAB_ACTIVITY_INDEX = 3
const val ARTICLE_REQUEST_CODE = 200
const val QUESTION_REQUEST_CODE = 201
const val VIDEO_REQUEST_CODE = 202

View File

@ -1,19 +1,20 @@
package com.gh.gamecenter.forum.home
import android.app.Application
import androidx.lifecycle.*
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import com.gh.gamecenter.common.entity.CommunityEntity
import com.gh.gamecenter.common.utils.clearHtmlFormatCompletely
import com.gh.gamecenter.common.utils.observableToMain
import com.gh.gamecenter.common.utils.removeInsertedContent
import com.gh.gamecenter.common.utils.removeVideoContent
import com.gh.gamecenter.qa.entity.ArticleDetailEntity
import com.gh.gamecenter.feature.entity.ArticleEntity
import com.gh.gamecenter.qa.entity.QuestionsDetailEntity
import com.gh.gamecenter.feature.entity.TimeEntity
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.feature.entity.ArticleEntity
import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.feature.entity.TimeEntity
import com.gh.gamecenter.feature.entity.UserEntity
import com.gh.gamecenter.livedata.Event
import com.gh.gamecenter.qa.entity.ArticleDetailEntity
import com.gh.gamecenter.qa.entity.QuestionsDetailEntity
import com.gh.gamecenter.retrofit.RetrofitManager
class CommunityHomeViewModel(application: Application) : AndroidViewModel(application) {
@ -131,4 +132,64 @@ class CommunityHomeViewModel(application: Application) : AndroidViewModel(applic
return articleEntity
}
private val _hasFollowedUsers = MutableLiveData<Boolean>()
val hasFollowedUser: LiveData<Boolean> = _hasFollowedUsers
fun updateHasFollowUser(show: Boolean) {
_hasFollowedUsers.value = show
}
private val filterDrawableResId = MutableLiveData<Int?>()
fun updateFilterResId(resId: Int?) {
filterDrawableResId.value = resId
}
val followFilterStatus = MediatorLiveData<Int?>().apply {
addSource(hasFollowedUser) {
if (it) {
value = filterDrawableResId.value
} else {
value = null
}
}
addSource(filterDrawableResId) { resId ->
// 当 hasFollowedUser.value == null 时不需要调用setValue
if (hasFollowedUser.value == true) {
value = resId
} else if (hasFollowedUser.value == false) {
value = null
}
when {
resId == null ||
hasFollowedUser.value == false -> {
// 当resId == null 时表示已切换到别的tab 直接setValue
// 当 resId != null && hasFollowedUser,value 为false时说明关注页数据已加载完毕并且没有关注任何用户直接setValue
value = null
}
hasFollowedUser.value == true -> {
// 当 resId = null 且 有关注用户,显示当前 resId
value = resId
}
else -> {
// do Nothing 当resId != null 但是hasFollowedUser.value == null,说明关注页数据正在loading等待无需setValue
}
}
}
}
private val _filterFollowedAction = MutableLiveData<Event<Int>>()
val filterFollowedAction: LiveData<Event<Int>> = _filterFollowedAction
fun filterFollowed(position: Int) {
val filterName = when (position) {
0 -> "全部"
1 -> "关注的人"
else -> "关注的游戏"
}
SensorsBridge.trackFollowTabFilterOptionClick(filterName)
_filterFollowedAction.value = Event(position)
}
}

View File

@ -13,6 +13,7 @@ import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.SpacingItemDecoration
import com.gh.gamecenter.core.iinterface.IScrollable
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.databinding.FragmentForumActivityBinding
import com.gh.gamecenter.databinding.LayoutForumActivityCategoryItemBinding
@ -20,7 +21,7 @@ import com.gh.gamecenter.entity.ForumActivityCategoryEntity
import com.gh.gamecenter.entity.ForumActivityEntity
import com.google.android.flexbox.FlexboxLayout
class ForumActivityFragment : LazyListFragment<ForumActivityEntity, ForumActivityViewModel>() {
class ForumActivityFragment : LazyListFragment<ForumActivityEntity, ForumActivityViewModel>(), IScrollable {
private var mBinding: FragmentForumActivityBinding? = null
private var mAdapter: ForumActivityAdapter? = null
@ -200,4 +201,12 @@ class ForumActivityFragment : LazyListFragment<ForumActivityEntity, ForumActivit
}
changeCategoryBg()
}
override fun scrollToTop() {
mBinding?.listRv?.let {
if (it.visibility == View.VISIBLE) {
it.scrollToPosition(0)
}
}
}
}

View File

@ -6,13 +6,18 @@ import android.graphics.Color
import android.graphics.drawable.GradientDrawable
import android.text.SpannableStringBuilder
import android.view.View
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.common.util.*
import com.gh.common.util.DialogUtils
import com.gh.common.util.LogUtils
import com.gh.common.util.NewLogUtils
import com.gh.common.view.ImageContainerView
import com.gh.common.view.ImageContainerView.Companion.toImageContainerData
import com.gh.gamecenter.ImageViewerActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.callback.ConfirmListener
import com.gh.gamecenter.common.entity.AdditionalParamsEntity
import com.gh.gamecenter.common.entity.CommunityEntity
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.MtaHelper
@ -28,6 +33,7 @@ import com.gh.gamecenter.qa.article.detail.ArticleDetailActivity
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.feature.entity.ArticleEntity
import com.gh.gamecenter.feature.entity.CommunityItemData
import com.gh.gamecenter.forum.home.ArticleItemVideoView.Companion.toArticleVideoData
import com.gh.gamecenter.forum.search.CommunitySearchEventListener
import com.gh.gamecenter.forum.search.CommunitySearchEventListener.Companion.SEARCH_BUTTON_COMMENT
import com.gh.gamecenter.forum.search.CommunitySearchEventListener.Companion.SEARCH_BUTTON_DISLIKE
@ -42,6 +48,7 @@ import com.gh.gamecenter.qa.entity.QuestionsDetailEntity
import com.gh.gamecenter.qa.questions.invite.QuestionsInviteActivity
import com.gh.gamecenter.qa.questions.newdetail.NewQuestionDetailActivity
import com.gh.gamecenter.qa.video.detail.ForumVideoDetailActivity
import com.gh.gamecenter.video.detail.VideoDetailContainerViewModel
import com.shuyu.gsyvideoplayer.builder.GSYVideoOptionBuilder
import com.shuyu.gsyvideoplayer.listener.GSYSampleCallBack
import com.shuyu.gsyvideoplayer.utils.OrientationUtils
@ -148,15 +155,46 @@ class ForumArticleAskItemViewHolder(
}
}
binding.imageContainer.bindData(entity, entrance, path) {
clickListener?.onItemChildClick(
entity.id,
entity.type,
htmlToString(entity.questions.title),
position,
SEARCH_BUTTON_VIEW_IMAGE
)
}
binding.imageContainer.bindData(
entity.toImageContainerData(),
object : ImageContainerView.OnImageContainerEventListener {
override fun onImageClick(
images: List<String>,
position: Int,
imageViewList: ArrayList<SimpleDraweeView>,
) {
clickListener?.onItemChildClick(
entity.id,
entity.type,
htmlToString(entity.questions.title),
position,
SEARCH_BUTTON_VIEW_IMAGE,
)
if (entity.communityId.isNullOrEmpty()) {
entity.communityId = entity.bbs.id
}
val intent = ImageViewerActivity.getIntent(
itemView.context, entity.images as ArrayList<String>, position, imageViewList,
if (entity.type == "community_article") entity else null, entrance, true,
)
itemView.context.startActivity(intent)
}
override fun onVideoCLick(videoId: String) {
DirectUtils.directToVideoDetail(
itemView.context,
videoId,
VideoDetailContainerViewModel.Location.VIDEO_HOT.value,
showComment = false,
entrance = entrance,
path = path
)
}
},
)
bindVideoData(entity.transformForumVideoEntity())
val user = entity.user
@ -315,34 +353,13 @@ class ForumArticleAskItemViewHolder(
.setVideoAllCallBack(object : GSYSampleCallBack() {
override fun onQuitFullscreen(url: String?, vararg objects: Any) {
orientationUtils.backToProtVideo()
visibleView.uploadVideoStreamingPlaying("退出全屏")
}
})
.build(visibleView)
visibleView.run {
updateVideoData(entity)
updateThumb(entity.poster)
updateDurationTv(entity.duration)
setVideoStatus(entity.status)
fullscreenButton.setOnClickListener {
val horizontalVideoView =
startWindowFullscreen(itemView.context, true, true) as? ArticleItemVideoView
if (horizontalVideoView == null) {
toastInInternalRelease("全屏失败,请向技术人员提供具体的操作步骤")
return@setOnClickListener
}
orientationUtils.resolveByClick()
horizontalVideoView.uuid = uuid
horizontalVideoView.updateVideoData(entity)
horizontalVideoView.updateThumb(entity.poster)
horizontalVideoView.violenceUpdateMuteStatus()
horizontalVideoView.setFullViewStatus()
uploadVideoStreamingPlaying("开始播放")
uploadVideoStreamingPlaying("点击全屏")
}
}
visibleView.updateVideoData(
entity.toArticleVideoData(),
AnswerArticleVideoViewEventHelper(entity, orientationUtils)
)
}
}
}
@ -382,7 +399,13 @@ class ForumArticleAskItemViewHolder(
SEARCH_BUTTON_VIEW_FORUM_DETAIL
)
MtaHelper.onEvent(getEventId(entrance), getKey(entrance), entity.community.name)
itemView.context.startActivity(ForumDetailActivity.getIntent(itemView.context, entity.community.id, entrance))
itemView.context.startActivity(
ForumDetailActivity.getIntent(
itemView.context,
entity.community.id,
entrance
)
)
LogUtils.uploadAccessToBbs(entity.community.id, "文章外所属论坛")
}

View File

@ -22,6 +22,7 @@ import com.gh.gamecenter.common.base.fragment.LazyFragment
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.GridSpacingItemColorDecoration
import com.gh.gamecenter.core.iinterface.IScrollable
import com.gh.gamecenter.databinding.ForumBannerIndicatorItemBinding
import com.gh.gamecenter.databinding.FragmentForumBinding
import com.gh.gamecenter.entity.ForumBannerEntity
@ -34,7 +35,7 @@ import com.halo.assistant.HaloApp
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class ForumFragment : LazyFragment(), SwipeRefreshLayout.OnRefreshListener {
class ForumFragment : LazyFragment(), SwipeRefreshLayout.OnRefreshListener, IScrollable {
private var mBinding: FragmentForumBinding? = null
private var mViewModel: ForumViewModel? = null
@ -513,4 +514,12 @@ class ForumFragment : LazyFragment(), SwipeRefreshLayout.OnRefreshListener {
super.onDarkModeChanged()
mBinding?.hotForumRv?.adapter?.let { it.notifyItemRangeChanged(0, it.itemCount) }
}
override fun scrollToTop() {
mBinding?.contentContainer?.let {
if (it.visibility == View.VISIBLE) {
it.scrollTo(0, 0)
}
}
}
}

View File

@ -0,0 +1,31 @@
package com.gh.gamecenter.forum.home.follow
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.forum.home.follow.fragment.AllFollowedFragment
class AllFollowedActivity : BaseActivity() {
override fun getLayoutId(): Int = R.layout.activity_all_followed
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DisplayUtils.setLightStatusBar(this, true)
setStatusBarColor(Color.TRANSPARENT)
val containerFragment = supportFragmentManager.findFragmentByTag(AllFollowedFragment::class.java.name)
?: AllFollowedFragment().with(intent.extras)
// 若 placeholder 外层为 RelativeLayout 的话,会出现莫名的偏移
supportFragmentManager.beginTransaction()
.replace(R.id.layout_activity_content, containerFragment, AllFollowedFragment::class.java.name)
.commitAllowingStateLoss()
}
companion object {
fun getIntent(context: Context): Intent = Intent(context, AllFollowedActivity::class.java)
}
}

View File

@ -0,0 +1,50 @@
package com.gh.gamecenter.forum.home.follow
import com.gh.gamecenter.entity.FollowUserEntity
abstract class AllFollowedItem() {
abstract val itemType: Int
open fun areItemsTheSame(other: AllFollowedItem): Boolean {
return other.itemType == itemType && doAreItemsTheSame(other)
}
open fun areContentsTheSame(other: AllFollowedItem): Boolean {
return other.itemType == itemType && doAreContentsTheSame(other)
}
open fun doAreItemsTheSame(other: AllFollowedItem): Boolean = true
open fun doAreContentsTheSame(other: AllFollowedItem): Boolean = this == other
companion object {
const val ALL_FOLLOWED_ITEM_TYPE_NO_TOP = 0
const val ALL_FOLLOWED_ITEM_TYPE_NORMAL = 1
const val ALL_FOLLOWED_ITEM_TYPE_MY_FOLLOW_HEADER = 2
}
}
class AllFollowedNoTopItem : AllFollowedItem() {
override val itemType: Int
get() = ALL_FOLLOWED_ITEM_TYPE_NO_TOP
}
data class AllFollowedMyFollowedHeaderItem(val isTop: Boolean) : AllFollowedItem() {
override val itemType: Int
get() = ALL_FOLLOWED_ITEM_TYPE_MY_FOLLOW_HEADER
}
data class AllFollowedNormalItem(
val data: FollowUserEntity,
var isTop: Boolean
) : AllFollowedItem() {
override val itemType: Int
get() = ALL_FOLLOWED_ITEM_TYPE_NORMAL
override fun doAreItemsTheSame(other: AllFollowedItem): Boolean {
return other is AllFollowedNormalItem && data.id == other.data.id
}
}

View File

@ -0,0 +1,306 @@
package com.gh.gamecenter.forum.home.follow
import android.content.Context
import android.content.Intent
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.ActivityResultRegistry
import androidx.activity.result.contract.ActivityResultContract
import androidx.activity.result.contract.ActivityResultContracts.GetContent
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.LifecycleOwner
import com.gh.common.util.SyncDataBetweenPageHelper
import com.gh.gamecenter.common.entity.CommunityEntity
import com.gh.gamecenter.entity.PersonalHistoryEntity
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.feature.entity.ArticleEntity
import com.gh.gamecenter.feature.entity.CommentEntity
import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.personalhome.home.UserHistoryFragment
import com.gh.gamecenter.qa.article.detail.ArticleDetailActivity
import com.gh.gamecenter.qa.entity.ArticleDetailEntity
import com.gh.gamecenter.qa.entity.QuestionsDetailEntity
import com.gh.gamecenter.qa.questions.newdetail.NewQuestionDetailActivity
import com.gh.gamecenter.qa.video.detail.ForumVideoDetailActivity
class FollowActivityResultLauncher(
private val registry: ActivityResultRegistry,
private val onResultListener: OnResultListener
) : DefaultLifecycleObserver {
private lateinit var articleDetailLauncher: ActivityResultLauncher<LauncherDestination>
private lateinit var commentEntityLauncher: ActivityResultLauncher<LauncherDestination>
private lateinit var questionsDetailLauncher: ActivityResultLauncher<LauncherDestination>
private lateinit var forumVideoLauncher: ActivityResultLauncher<LauncherDestination>
override fun onCreate(owner: LifecycleOwner) {
super.onCreate(owner)
articleDetailLauncher = registry.register(
KEY_ARTICLE_DETAIL_LAUNCHER,
owner,
object : ActivityResultContract<LauncherDestination, ArticleDetailResult?>() {
override fun createIntent(context: Context, input: LauncherDestination): Intent {
val intent =
if (input.historyEntity != null) {
ArticleDetailActivity.getIntent(
context,
input.historyEntity.community,
input.historyEntity.id,
"",
UserHistoryFragment.PATH_USER_QUESTION_ANSWER,
sourceEntrance = "关注-用户动态"
)
} else {
ArticleDetailActivity.getIntent(
context,
CommunityEntity(input.answerEntity?.bbs?.id ?: ""),
input.answerEntity?.id ?: "",
"",
"",
sourceEntrance = "关注-论坛动态",
)
}
intent.putExtra(SyncDataBetweenPageHelper.DATA_POSITION_TAG, input.position)
intent.putExtra(SyncDataBetweenPageHelper.REQUEST_CODE_TAG, 101)
return intent
}
override fun parseResult(resultCode: Int, intent: Intent?): ArticleDetailResult? {
val position = intent?.getIntExtra(SyncDataBetweenPageHelper.DATA_POSITION_TAG, -1)
val resultData =
intent?.getParcelableExtra<ArticleDetailEntity>(ArticleDetailEntity::class.java.simpleName)
if (position != null && resultData != null) {
return ArticleDetailResult(position, resultData)
} else {
return null
}
}
})
{
it?.run {
onResultListener.onArticleDetailResult(position, articleDetailEntity)
}
}
commentEntityLauncher = registry.register(
KEY_COMMENT_LAUNCHER,
owner,
object : ActivityResultContract<LauncherDestination, CommentResult?>() {
override fun createIntent(context: Context, input: LauncherDestination): Intent {
val intent = NewQuestionDetailActivity.getSpecifiedCommentIntent(
context,
input.historyEntity?.question?.id ?: "",
input.historyEntity?.id ?: "",
"",
UserHistoryFragment.PATH_USER_QUESTION_ANSWER,
sourceEntrance = "关注-动态"
)
intent.putExtra(SyncDataBetweenPageHelper.DATA_POSITION_TAG, input.position)
intent.putExtra(SyncDataBetweenPageHelper.REQUEST_CODE_TAG, 102)
return intent
}
override fun parseResult(resultCode: Int, intent: Intent?): CommentResult? {
val position = intent?.getIntExtra(SyncDataBetweenPageHelper.DATA_POSITION_TAG, -1)
val commentEntity = intent?.getParcelableExtra<CommentEntity>(CommentEntity::class.java.simpleName)
if (position != null && commentEntity != null) {
return CommentResult(position, commentEntity)
} else {
return null
}
}
})
{
it?.run {
onResultListener.onCommentResult(position, commentEntity)
}
}
questionsDetailLauncher = registry.register(
KEY_QUESTION_DETAIL_LAUNCHER,
owner,
object : ActivityResultContract<LauncherDestination, QuestionsDetailResult?>() {
override fun createIntent(context: Context, input: LauncherDestination): Intent {
val intent = if (input.historyEntity != null) {
NewQuestionDetailActivity.getIntent(
context,
input.historyEntity.id,
"",
UserHistoryFragment.PATH_USER_QUESTION_ANSWER
)
} else {
if (input.answerEntity?.type == "question") {
NewQuestionDetailActivity.getIntent(
context, input.answerEntity.id,
"",
"",
sourceEntrance = "关注-论坛动态"
)
} else {
NewQuestionDetailActivity.getCommentIntent(
context,
input.answerEntity?.questions?.id ?: "",
input.answerEntity?.answerId ?: "",
"",
""
)
}
}
intent.putExtra(SyncDataBetweenPageHelper.DATA_POSITION_TAG, input.position)
intent.putExtra(SyncDataBetweenPageHelper.REQUEST_CODE_TAG, 103)
return intent
}
override fun parseResult(resultCode: Int, intent: Intent?): QuestionsDetailResult? {
val position = intent?.getIntExtra(SyncDataBetweenPageHelper.DATA_POSITION_TAG, -1)
val questionsDetailEntity =
intent?.getParcelableExtra<QuestionsDetailEntity>(QuestionsDetailEntity::class.java.simpleName)
return if (position != null && questionsDetailEntity != null) {
QuestionsDetailResult(position, questionsDetailEntity)
} else {
null
}
}
}
) {
it?.run {
onResultListener.onQuestionsDetailResult(position, questionsDetailEntity)
}
}
forumVideoLauncher = registry.register(
KEY_FORUM_VIDEO_LAUNCHER,
owner,
object : ActivityResultContract<LauncherDestination, ForumVideoResult?>() {
override fun createIntent(context: Context, input: LauncherDestination): Intent {
val intent = if (input.historyEntity != null) {
ForumVideoDetailActivity.getIntent(
context,
input.historyEntity.id,
input.historyEntity.community.id,
sourceEntrance = "关注-个人动态"
)
} else {
ForumVideoDetailActivity.getIntent(
context,
input.answerEntity?.id ?: "",
input.answerEntity?.bbs?.id ?: "",
"关注-论坛动态"
)
}
intent.putExtra(SyncDataBetweenPageHelper.DATA_POSITION_TAG, input.position)
intent.putExtra(SyncDataBetweenPageHelper.REQUEST_CODE_TAG, 104)
return intent
}
override fun parseResult(resultCode: Int, intent: Intent?): ForumVideoResult? {
val position = intent?.getIntExtra(SyncDataBetweenPageHelper.DATA_POSITION_TAG, -1)
val forumVideoEntity =
intent?.getParcelableExtra<ForumVideoEntity>(ForumVideoEntity::class.java.simpleName)
return if (position != null && forumVideoEntity != null) {
ForumVideoResult(position, forumVideoEntity)
} else {
null
}
}
}
)
{
it?.run {
onResultListener.onForumVideoResult(position, forumVideoEntity)
}
}
}
fun onPersonItemClick(position: Int, entity: PersonalHistoryEntity) {
when {
entity.type == "community_article"
|| entity.type == "community_article_vote" -> {
articleDetailLauncher.launch(LauncherDestination(position, historyEntity = entity))
}
entity.type.contains("video") -> {
forumVideoLauncher.launch(LauncherDestination(position, historyEntity = entity))
}
entity.type.contains("question") -> {
questionsDetailLauncher.launch(LauncherDestination(position, historyEntity = entity))
}
else -> {
commentEntityLauncher.launch(LauncherDestination(position, historyEntity = entity))
}
}
}
fun onBbsItemClick(position: Int, answerEntity: AnswerEntity) {
when (answerEntity.type) {
"community_article" -> {
articleDetailLauncher.launch(LauncherDestination(position, answerEntity = answerEntity))
}
"video" -> {
forumVideoLauncher.launch(LauncherDestination(position, answerEntity = answerEntity))
}
"question" -> {
questionsDetailLauncher.launch(LauncherDestination(position, answerEntity = answerEntity))
}
"answer" -> {
questionsDetailLauncher.launch(LauncherDestination(position, answerEntity = answerEntity))
}
}
}
companion object {
private const val KEY_ARTICLE_DETAIL_LAUNCHER = "key_article_detail_launcher"
private const val KEY_COMMENT_LAUNCHER = "key_comment_launcher"
private const val KEY_QUESTION_DETAIL_LAUNCHER = "key_question_detail_launcher"
private const val KEY_FORUM_VIDEO_LAUNCHER = "KEY_forum_video_LAUNCHER"
}
private data class LauncherDestination(
val position: Int,
val historyEntity: PersonalHistoryEntity? = null,
val answerEntity: AnswerEntity? = null
)
private data class ArticleDetailResult(
val position: Int,
val articleDetailEntity: ArticleDetailEntity
)
private data class CommentResult(
val position: Int,
val commentEntity: CommentEntity
)
private data class QuestionsDetailResult(
val position: Int,
val questionsDetailEntity: QuestionsDetailEntity
)
private data class ForumVideoResult(
val position: Int,
val forumVideoEntity: ForumVideoEntity
)
interface OnResultListener {
fun onArticleDetailResult(position: Int, articleDetailEntity: ArticleDetailEntity)
fun onCommentResult(position: Int, commentEntity: CommentEntity)
fun onQuestionsDetailResult(position: Int, questionsDetailEntity: QuestionsDetailEntity)
fun onForumVideoResult(position: Int, forumVideoEntity: ForumVideoEntity)
}
}

View File

@ -0,0 +1,32 @@
package com.gh.gamecenter.forum.home.follow
import android.content.Context
import android.graphics.Canvas
import android.graphics.Path
import android.util.AttributeSet
import androidx.core.content.ContextCompat
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.core.utils.DisplayUtils
class FollowClippedView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : androidx.appcompat.widget.AppCompatImageView(context, attrs, defStyleAttr) {
override fun onDraw(canvas: Canvas) {
val path = Path()
path.addRect(
0f,
0f,
width.toFloat(),
(DisplayUtils.getStatusBarHeight(resources) + 52f.dip2px() + 56f.dip2px()).toFloat(),
Path.Direction.CW
)
canvas.clipPath(path)
canvas.drawColor(ContextCompat.getColor(context, R.color.ui_background))
super.onDraw(canvas)
}
}

View File

@ -0,0 +1,69 @@
package com.gh.gamecenter.forum.home.follow
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.widget.TextView
import androidx.activity.viewModels
import androidx.core.content.ContextCompat
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.utils.DarkModeUtils.isDarkModeOn
import com.gh.gamecenter.common.utils.toArrayList
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.entity.FollowUserEntity
import com.gh.gamecenter.forum.home.follow.fragment.FollowDynamicFragment
import com.gh.gamecenter.forum.home.follow.viewmodel.FollowDynamicViewModel
class FollowDynamicActivity : BaseActivity() {
private val viewModel by viewModels<FollowDynamicViewModel>()
override fun getLayoutId(): Int = R.layout.activity_amway
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
DisplayUtils.transparentStatusAndNavigation(this)
DisplayUtils.setLightStatusBar(this, true)
val containerFragment = supportFragmentManager.findFragmentByTag(FollowDynamicFragment::class.java.name)
?: FollowDynamicFragment().with(intent.extras)
// 若 placeholder 外层为 RelativeLayout 的话,会出现莫名的偏移
supportFragmentManager.beginTransaction()
.replace(R.id.placeholder, containerFragment, FollowDynamicFragment::class.java.name)
.commitAllowingStateLoss()
}
override fun onDarkModeChanged() {
val tv = findViewById<TextView>(ID_NIGHT_INDICATOR)
if (tv != null) {
tv.text = if (isDarkModeOn(this)) "深色模式" else "浅色模式"
tv.alpha = if (isDarkModeOn(this)) 0.8f else 0.15f
}
if (isAutoResetViewBackgroundEnabled) {
updateStaticViewBackground(window.decorView)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
window.navigationBarColor = ContextCompat.getColor(this, R.color.ui_surface)
}
}
override fun handleBackPressed(): Boolean {
viewModel.finish()
return true
}
companion object {
const val KEY_SELECTED_POSITION = "key_selected_position"
const val KEY_USERS = "key_users"
fun start(context: Context, position: Int, followedList: List<FollowUserEntity>) {
val intent = Intent(context, FollowDynamicActivity::class.java)
intent.putParcelableArrayListExtra(KEY_USERS, followedList.toArrayList())
intent.putExtra(KEY_SELECTED_POSITION, position)
context.startActivity(intent)
}
}
}

View File

@ -0,0 +1,61 @@
package com.gh.gamecenter.forum.home.follow
import com.gh.gamecenter.entity.HistoryGameEntity
import com.gh.gamecenter.entity.PersonalHistoryEntity
import com.gh.gamecenter.feature.entity.AnswerEntity
abstract class FollowDynamicItem {
abstract val itemType: Int
fun areItemsTheSame(other: FollowDynamicItem) =
itemType == other.itemType && doAreItemsTheSame(other)
fun areContentsTheSame(other: FollowDynamicItem) =
itemType == other.itemType && doAreContentsTheSame(other)
protected open fun doAreItemsTheSame(other: FollowDynamicItem) = true
protected open fun doAreContentsTheSame(other: FollowDynamicItem) = this == other
companion object {
const val FOLLOW_DYNAMIC_ITEM_TYPE_FOOTER = -1
const val FOLLOW_DYNAMIC_ITEM_TYPE_PERSONAL = 0
const val FOLLOW_DYNAMIC_ITEM_TYPE_BBS = 1
}
}
data class FollowDynamicPersonalItem(
val data: PersonalHistoryEntity
) : FollowDynamicItem() {
override val itemType: Int
get() = FOLLOW_DYNAMIC_ITEM_TYPE_PERSONAL
override fun doAreItemsTheSame(other: FollowDynamicItem): Boolean {
return other is FollowDynamicPersonalItem
&& data.id == other.data.id
}
}
data class FollowDynamicBbsItem(
val data: AnswerEntity
) : FollowDynamicItem() {
override val itemType: Int
get() = FOLLOW_DYNAMIC_ITEM_TYPE_BBS
override fun doAreItemsTheSame(other: FollowDynamicItem): Boolean {
return other is FollowDynamicBbsItem
&& data.id == other.data.id
}
}
object FollowDynamicFooterItem : FollowDynamicItem() {
override val itemType: Int
get() = FOLLOW_DYNAMIC_ITEM_TYPE_FOOTER
}

View File

@ -0,0 +1,107 @@
package com.gh.gamecenter.forum.home.follow
import android.content.Context
import android.content.res.ColorStateList
import android.view.LayoutInflater
import android.view.ViewGroup.LayoutParams
import android.view.ViewGroup.MarginLayoutParams
import android.widget.CheckedTextView
import android.widget.PopupWindow
import androidx.core.view.children
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.databinding.LayoutForumActivityCategoryItemBinding
import com.gh.gamecenter.databinding.PopFollowFilterBinding
import com.google.android.flexbox.FlexboxLayout
class FollowHomeFilterPopWindow(
private val binding: PopFollowFilterBinding
) : PopupWindow(binding.root, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT) {
private val data = arrayListOf("全部", "关注的人", "关注的游戏")
init {
isOutsideTouchable = true
isFocusable = true
val layoutParams = binding.ivTopBackground.layoutParams as MarginLayoutParams
layoutParams.topMargin = -52F.dip2px() - DisplayUtils.getStatusBarHeight(binding.root.context.resources)
binding.ivTopBackground.layoutParams = layoutParams
val inflater = LayoutInflater.from(binding.root.context)
data.forEachIndexed { index, entity ->
LayoutForumActivityCategoryItemBinding.inflate(inflater).apply {
val params =
FlexboxLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)
params.setMargins(0, 8F.dip2px(), 8F.dip2px(), 0)
params.height = 24F.dip2px()
root.layoutParams = params
name.text = entity
name.isChecked = index == 0
root.setOnClickListener {
updateCategory(index)
}
binding.fblFilter.addView(root)
}
}
binding.vBackground.setOnClickListener {
dismiss()
}
if (DarkModeUtils.isDarkModeOn(binding.root.context)) {
binding.clFilterContainer.setBackgroundColor(R.color.ui_background.toColor(binding.root.context))
binding.ivTopBackground.goneIf(true)
} else {
binding.clFilterContainer.background = null
binding.ivTopBackground.goneIf(false)
}
}
private fun updateCategory(index: Int) {
filteredListener?.filterFollowed(index)
binding.fblFilter.children.forEachIndexed { position, child ->
(child as? CheckedTextView)?.isChecked = (position == index)
}
dismiss()
}
private var filteredListener: OnFilteredChangedListener? = null
fun setFilterListener(filteredListener: OnFilteredChangedListener) {
this.filteredListener = filteredListener
}
fun onDarkModeChanged() {
if (DarkModeUtils.isDarkModeOn(binding.root.context)) {
binding.clFilterContainer.setBackgroundColor(R.color.ui_background.toColor(binding.root.context))
binding.ivTopBackground.goneIf(true)
} else {
binding.clFilterContainer.background = null
binding.ivTopBackground.goneIf(false)
}
val context = binding.root.context
binding.fblFilter.children.forEach {
if (it is CheckedTextView) {
val colorInt = R.color.forum_category_selector.toColor(context)
it.setTextColor(ColorStateList.valueOf(colorInt))
val drawable = R.drawable.selector_bg_forum_activity_category.toDrawable(context)
it.background = drawable
}
}
}
companion object {
fun create(context: Context): FollowHomeFilterPopWindow {
val inflater = LayoutInflater.from(context)
val binding = PopFollowFilterBinding.inflate(inflater, null, false)
return FollowHomeFilterPopWindow(binding)
}
}
interface OnFilteredChangedListener {
fun filterFollowed(position: Int)
}
}

View File

@ -0,0 +1,201 @@
package com.gh.gamecenter.forum.home.follow
import com.gh.gamecenter.entity.FollowCommonContentCollection
import com.gh.gamecenter.entity.FollowUserEntity
import com.gh.gamecenter.entity.FollowDynamicEntity
import com.gh.gamecenter.feature.entity.*
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.model.CustomPageItem
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_BANNER
abstract class FollowItem {
abstract val itemType: Int
fun areItemsTheSame(other: FollowItem) =
itemType == other.itemType && doAreItemsTheSame(other)
fun areContentsTheSame(other: FollowItem) =
itemType == other.itemType && doAreContentsTheSame(other)
protected open fun doAreItemsTheSame(other: FollowItem) = true
protected open fun doAreContentsTheSame(other: FollowItem) = this == other
companion object {
const val FOLLOW_ITEM_TYPE_INVALID = -4
const val FOLLOW_ITEM_TYPE_EMPTY = -3
const val FOLLOW_ITEM_TYPE_FOOTER = -2
const val FOLLOW_ITEM_TYPE_HEADER = -1
const val FOLLOW_ITEM_TYPE_RECOMMEND_USER = 0
const val FOLLOW_ITEM_TYPE_COMMON_BANNER = 1
const val FOLLOW_ITEM_TYPE_COMMON_BANNER_WITH_CARDS = 2
const val FOLLOW_ITEM_TYPE_NAVIGATION = 3
const val FOLLOW_ITEM_TYPE_RECOMMEND = 4
const val FOLLOW_ITEM_TYPE_COMMON_HORIZONTAL_SLIDE = 5
const val FOLLOW_ITEM_TYPE_COMMON_VERTICAL_TWO_CARDS = 6
const val FOLLOW_ITEM_TYPE_POST_CARD = 7
const val FOLLOW_ITEM_TYPE_GIFT_PACK = 8
const val FOLLOW_ITEM_TYPE_ARTICLE = 9
// 通用内容合集 样式
val commonContentCollection: HashMap<Int, Int> = hashMapOf(
CustomPageItem.COMMON_CONTENT_COLLECTION_LAYOUT_NAVIGATION to FOLLOW_ITEM_TYPE_NAVIGATION,
CustomPageItem.COMMON_CONTENT_COLLECTION_LAYOUT_KING_KONG to FOLLOW_ITEM_TYPE_RECOMMEND,
CustomPageItem.COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_SLIDE_BANNER to FOLLOW_ITEM_TYPE_COMMON_HORIZONTAL_SLIDE,
CustomPageItem.COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_BANNER to FOLLOW_ITEM_TYPE_COMMON_VERTICAL_TWO_CARDS,
CustomPageItem.COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_VERTICAL_CARD to FOLLOW_ITEM_TYPE_COMMON_HORIZONTAL_SLIDE,
CustomPageItem.COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_VERTICAL_CARD to FOLLOW_ITEM_TYPE_COMMON_VERTICAL_TWO_CARDS,
CustomPageItem.COMMON_CONTENT_COLLECTION_LAYOUT_VERTICAL_IMAGE_TEXT to FOLLOW_ITEM_TYPE_COMMON_VERTICAL_TWO_CARDS,
CustomPageItem.COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_IMAGE_TEXT to FOLLOW_ITEM_TYPE_COMMON_HORIZONTAL_SLIDE
)
}
}
data class FollowRecommendUsersItem(
val data: List<UserEntity>
) : FollowItem() {
override val itemType: Int
get() = FOLLOW_ITEM_TYPE_RECOMMEND_USER
}
class FollowCommonCollectionItem(
val data: FollowCommonContentCollection
) : FollowItem() {
override val itemType: Int
get() = if (data.linkCommonCollection.layout == COMMON_CONTENT_COLLECTION_LAYOUT_BANNER) {
// 轮播图右边卡片列表, 至少需要2个卡片才能显示
if (data.linkCommonCollection.slides.subSlide.size < 2) {
FOLLOW_ITEM_TYPE_COMMON_BANNER
} else {
FOLLOW_ITEM_TYPE_COMMON_BANNER_WITH_CARDS
}
} else {
commonContentCollection[data.linkCommonCollection.layout] ?: FOLLOW_ITEM_TYPE_INVALID
}
override fun doAreItemsTheSame(other: FollowItem): Boolean {
return other is FollowCommonCollectionItem
&& data.linkCommonCollection.id == other.data.linkCommonCollection.id
}
override fun doAreContentsTheSame(other: FollowItem): Boolean {
return other is FollowCommonCollectionItem
&& data.title == other.data.title
&& data.linkCommonCollection == other.data.linkCommonCollection
}
}
data class FollowSplitCommonContentCollectionItem(
val data: CustomPageData.CommonContentCollection,
val leftPosition: Int,
) : FollowItem() {
override val itemType
get() = commonContentCollection[data.layout] ?: FOLLOW_ITEM_TYPE_INVALID
override fun doAreItemsTheSame(other: FollowItem): Boolean {
return other is FollowSplitCommonContentCollectionItem
&& data.id == other.data.id
}
override fun doAreContentsTheSame(other: FollowItem): Boolean {
return other is FollowSplitCommonContentCollectionItem
&& data == other.data
&& data.data.getOrNull(leftPosition) == other.data.data.getOrNull(leftPosition)
&& data.data.getOrNull(leftPosition + 1) == other.data.data.getOrNull(leftPosition + 1)
}
}
data class FollowPostCardItem(
val data: ArticleEntity
) : FollowItem() {
override val itemType: Int
get() = FOLLOW_ITEM_TYPE_POST_CARD
override fun doAreItemsTheSame(other: FollowItem): Boolean {
return other is FollowPostCardItem
&& data.id == other.data.id
}
}
data class FollowArticleItem(
val data: FollowDynamicEntity.Article
) : FollowItem() {
override val itemType: Int
get() = FOLLOW_ITEM_TYPE_ARTICLE
override fun doAreItemsTheSame(other: FollowItem): Boolean {
return other is FollowArticleItem
&& data.id == other.data.id
}
}
data class FollowGiftPackItem(
val libao: LibaoEntity,
val time: Long
) : FollowItem() {
override val itemType: Int
get() = FOLLOW_ITEM_TYPE_GIFT_PACK
override fun doAreItemsTheSame(other: FollowItem): Boolean {
return other is FollowGiftPackItem
&& libao.id == other.libao.id
}
override fun doAreContentsTheSame(other: FollowItem): Boolean {
return other is FollowGiftPackItem
&& time == other.time
&& libao.name == other.libao.name
&& libao.des == other.libao.des
&& libao.content == other.libao.content
&& libao.code == other.libao.code
&& libao.game?.mIcon == other.libao.game?.mIcon
&& libao.game?.iconFloat == other.libao.game?.iconFloat
&& libao.game?.iconSubscript == other.libao.game?.iconSubscript
&& libao.game?.name == other.libao.game?.name
}
}
data class FollowUserItem(
val isLogin: Boolean,
val data: List<FollowUserEntity>
) : FollowItem() {
override val itemType: Int
get() = FOLLOW_ITEM_TYPE_HEADER
override fun doAreItemsTheSame(other: FollowItem): Boolean {
return true
}
override fun doAreContentsTheSame(other: FollowItem): Boolean {
return other is FollowUserItem
&& isLogin == other.isLogin
&& data == other.data
}
}
object FollowEmptyItem : FollowItem() {
override val itemType: Int
get() = FOLLOW_ITEM_TYPE_EMPTY
}
object FollowFooterItem : FollowItem() {
override val itemType: Int
get() = FOLLOW_ITEM_TYPE_FOOTER
}
object InvalidItem : FollowItem() {
override val itemType: Int
get() = FOLLOW_ITEM_TYPE_INVALID
}

View File

@ -0,0 +1,6 @@
package com.gh.gamecenter.forum.home.follow
class FollowPageConfiguration(
val entrance: String,
val path: String
)

View File

@ -0,0 +1,51 @@
package com.gh.gamecenter.forum.home.follow
import android.content.Context
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.utils.DarkModeUtils
class ResetDataChangeHelper<T>(
private val context: Context,
private val adapter: RecyclerView.Adapter<*>
) {
private var darkMode = DarkModeUtils.isDarkModeOn(context)
private val _dataList = arrayListOf<T>()
val dataList: List<T> = _dataList
val dataCount: Int
get() = dataList.size
fun getItem(position: Int) = dataList[position]
private var _countAndKey: Pair<Int, String>? = null
fun submitList(data: List<T>?, getKey: (T) -> String) {
_dataList.clear()
data?.let(_dataList::addAll)
if (data.isNullOrEmpty()) {
_countAndKey = null
adapter.notifyDataSetChanged()
return
}
val countAndKey = _countAndKey ?: Pair(0, "")
val (oldSize, oldKeys) = countAndKey
val newSize = data.size
var newKeys = ""
data.forEach {
newKeys += getKey(it)
}
if (oldSize == newSize) {
if (oldKeys != newKeys || darkMode != DarkModeUtils.isDarkModeOn(context)) { // 数量不变,内容发生变化 || 切换浅色模式
adapter.notifyItemRangeChanged(0, adapter.itemCount, "")
}
} else {// 数量发生变化
adapter.notifyDataSetChanged()
}
darkMode = DarkModeUtils.isDarkModeOn(context)
_countAndKey = Pair(newSize, newKeys)
}
}

View File

@ -0,0 +1,209 @@
package com.gh.gamecenter.forum.home.follow.adapter
import android.annotation.SuppressLint
import android.view.MotionEvent
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.consume
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.databinding.RecyclerAllFollowMyFollowHeaderBinding
import com.gh.gamecenter.databinding.RecyclerAllFollowedBinding
import com.gh.gamecenter.databinding.RecyclerAllFollowedNoTopBinding
import com.gh.gamecenter.entity.FollowOperateTopRequest
import com.gh.gamecenter.forum.home.follow.AllFollowedItem
import com.gh.gamecenter.forum.home.follow.AllFollowedItem.Companion.ALL_FOLLOWED_ITEM_TYPE_MY_FOLLOW_HEADER
import com.gh.gamecenter.forum.home.follow.AllFollowedItem.Companion.ALL_FOLLOWED_ITEM_TYPE_NORMAL
import com.gh.gamecenter.forum.home.follow.AllFollowedItem.Companion.ALL_FOLLOWED_ITEM_TYPE_NO_TOP
import com.gh.gamecenter.forum.home.follow.AllFollowedMyFollowedHeaderItem
import com.gh.gamecenter.forum.home.follow.AllFollowedNoTopItem
import com.gh.gamecenter.forum.home.follow.AllFollowedNormalItem
import com.gh.gamecenter.forum.home.follow.viewholder.FollowInvalidViewHolder
import com.gh.gamecenter.forum.home.follow.viewmodel.AllFollowedViewModel
class AllFollowedAdapter(
private val viewModel: AllFollowedViewModel,
private val startDrag: (ViewHolder) -> Unit,
) : ListAdapter<AllFollowedItem, ViewHolder>(createDiffItemCallBack()) {
private var _isEdit = false
val isEdit: Boolean
get() = _isEdit
// 置顶需要移动到的位置
private var _topItemCount = 0
val topItemCount: Int
get() = _topItemCount
// 已关注用户的数量排除header footer等其它类型的item
private var _followedCount = 0
val followedCount: Int
get() = _followedCount
private val _dataList = arrayListOf<AllFollowedNormalItem>()
val dataList: List<AllFollowedNormalItem>
get() = _dataList
fun setData(data: List<AllFollowedItem>) {
_dataList.clear()
_dataList.addAll(data.filterIsInstance<AllFollowedNormalItem>())
_followedCount = dataList.size
_topItemCount = dataList.count(AllFollowedNormalItem::isTop)
val dataWithHeader = arrayListOf<AllFollowedItem>()
data.forEachIndexed { index, item ->
if (index == 0) {
dataWithHeader.add(AllFollowedMyFollowedHeaderItem(true))
}
if (topItemCount == 0 && index == 0) {
// 暂时没有置顶的关注,需要显示提示
dataWithHeader.add(AllFollowedNoTopItem())
dataWithHeader.add(AllFollowedMyFollowedHeaderItem(false))
} else if (topItemCount == index) {
dataWithHeader.add(AllFollowedMyFollowedHeaderItem(false))
}
dataWithHeader.add(item)
}
super.submitList(dataWithHeader)
}
public override fun getItem(position: Int): AllFollowedItem {
return super.getItem(position)
}
override fun getItemViewType(position: Int): Int = currentList[position].itemType
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
when (viewType) {
ALL_FOLLOWED_ITEM_TYPE_NO_TOP -> NoTopViewHolder(parent.toBinding())
ALL_FOLLOWED_ITEM_TYPE_NORMAL -> AllFollowedViewHolder(parent.toBinding())
ALL_FOLLOWED_ITEM_TYPE_MY_FOLLOW_HEADER -> MyFollowerHeaderViewHolder(parent.toBinding())
else -> FollowInvalidViewHolder(parent.toBinding())
}
@SuppressLint("ClickableViewAccessibility")
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
if (holder is AllFollowedViewHolder) {
val itemBinding = holder.binding
val item = getItem(position) as AllFollowedNormalItem
val data = item.data
if (isEdit) {
itemBinding.ivUpOrDown.goneIf(false)
itemBinding.ivDrag.goneIf(!item.isTop)
itemBinding.vNewTips.goneIf(true)
} else {
itemBinding.ivUpOrDown.goneIf(true)
itemBinding.ivDrag.goneIf(true)
itemBinding.vNewTips.goneIf(!data.isShowTip)
}
val editResourceId = if (item.isTop) R.drawable.ic_all_followed_down else R.drawable.ic_all_followed_up
itemBinding.ivUpOrDown.setImageResource(editResourceId)
itemBinding.tvName.text = data.name
if (data.isUser) {
itemBinding.ivUserIcon.goneIf(false) {
itemBinding.ivUserIcon.displayGameIcon(data.icon, null)
}
itemBinding.ivGameIcon.goneIf(true)
} else {
itemBinding.ivUserIcon.goneIf(true)
itemBinding.ivGameIcon.goneIf(false) {
itemBinding.ivGameIcon.displayGameIcon(data.icon, null)
}
}
itemBinding.ivUpOrDown.setOnClickListener {
if (item.isTop) {
cancelToTop(data.id)
} else {
toTop(data.id)
}
}
itemBinding.ivDrag.setOnTouchListener { _, event ->
consume {
if (event.action == MotionEvent.ACTION_DOWN) {
startDrag(holder)
}
}
}
arrayOf(itemBinding.ivGameIcon, itemBinding.ivUserIcon, itemBinding.tvName)
.forEach {
it.setOnClickListener {
viewModel.navigateToDetailPage(item)
}
}
}
}
private fun toTop(id: String) {
if (topItemCount >= itemCount) {
return
}
val newList = arrayListOf<AllFollowedNormalItem>()
val targetItem = dataList.find { it.data.id == id }
dataList.forEach {
if (newList.size == topItemCount && targetItem != null) {
newList.add(targetItem.copy(isTop = true))
}
if (it.data.id != id) {
newList.add(AllFollowedNormalItem(it.data, it.isTop))
}
}
setData(newList)
}
private fun cancelToTop(id: String) {
if (topItemCount == 0) {
return
}
val newList = arrayListOf<AllFollowedNormalItem>()
val targetItem = dataList.find { it.data.id == id }
dataList.forEach {
if (topItemCount == newList.size && targetItem != null) {
newList.add(targetItem.copy(isTop = false))
}
if (it.data.id != id) {
newList.add(it.copy())
}
}
setData(newList)
}
fun changeToEditable() {
_isEdit = !isEdit
notifyItemRangeChanged(0, itemCount, "")
}
fun getTopItem() =
dataList.filter {
it.isTop
}.mapIndexed { index, item ->
FollowOperateTopRequest(item.data.id, index + 1)
}
companion object {
fun createDiffItemCallBack() = object : ItemCallback<AllFollowedItem>() {
override fun areItemsTheSame(oldItem: AllFollowedItem, newItem: AllFollowedItem): Boolean {
return oldItem.areItemsTheSame(newItem)
}
override fun areContentsTheSame(oldItem: AllFollowedItem, newItem: AllFollowedItem): Boolean {
return oldItem.areContentsTheSame(newItem)
}
}
}
class NoTopViewHolder(val binding: RecyclerAllFollowedNoTopBinding) : ViewHolder(binding.root)
class MyFollowerHeaderViewHolder(val binding: RecyclerAllFollowMyFollowHeaderBinding) : ViewHolder(binding.root)
class AllFollowedViewHolder(val binding: RecyclerAllFollowedBinding) : ViewHolder(binding.root)
}

View File

@ -0,0 +1,217 @@
package com.gh.gamecenter.forum.home.follow.adapter
import android.content.Context
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.databinding.CommunityAnswerItemBinding
import com.gh.gamecenter.entity.PersonalHistoryEntity
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.feature.entity.Questions
import com.gh.gamecenter.forum.home.ForumArticleAskItemViewHolder
import com.gh.gamecenter.forum.home.follow.FollowDynamicBbsItem
import com.gh.gamecenter.forum.home.follow.FollowDynamicFooterItem
import com.gh.gamecenter.forum.home.follow.FollowDynamicItem
import com.gh.gamecenter.forum.home.follow.FollowDynamicItem.Companion.FOLLOW_DYNAMIC_ITEM_TYPE_BBS
import com.gh.gamecenter.forum.home.follow.FollowDynamicItem.Companion.FOLLOW_DYNAMIC_ITEM_TYPE_FOOTER
import com.gh.gamecenter.forum.home.follow.FollowDynamicItem.Companion.FOLLOW_DYNAMIC_ITEM_TYPE_PERSONAL
import com.gh.gamecenter.forum.home.follow.FollowDynamicPersonalItem
import com.gh.gamecenter.forum.home.follow.viewholder.FollowFooterViewHolder
import com.gh.gamecenter.forum.home.follow.viewholder.FollowInvalidViewHolder
import com.gh.gamecenter.forum.home.follow.viewmodel.FollowDynamicListViewModel
import com.gh.gamecenter.forum.home.follow.viewmodel.FollowDynamicViewModel
import com.gh.gamecenter.forum.search.CommunitySearchEventListener
import com.gh.gamecenter.personalhome.PersonalItemViewHolder
class FollowDynamicAdapter(
private val context: Context,
private val viewModel: FollowDynamicListViewModel
) : ListAdapter<FollowDynamicItem, ViewHolder>(createDiffCallback()) {
private var _recyclerView: RecyclerView? = null
private var loadStatus: LoadStatus? = null
fun setLoadStatus(status: LoadStatus) {
loadStatus = status
notifyItemChanged(itemCount - 1)
}
override fun submitList(list: List<FollowDynamicItem>?) {
val dataWithFooter = if (list.isNullOrEmpty()) {
ArrayList(list)
} else {
list + FollowDynamicFooterItem
}
super.submitList(dataWithFooter)
}
override fun getItemViewType(position: Int): Int {
return currentList[position].itemType
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return when (viewType) {
FOLLOW_DYNAMIC_ITEM_TYPE_PERSONAL -> {
FollowDynamicPersonalViewHolder(context, parent.toBinding()) { history, position ->
viewModel.navigateToPersonDetailPage(history, position)
}
}
FOLLOW_DYNAMIC_ITEM_TYPE_BBS -> {
FollowDynamicBbSViewHolder(parent.toBinding(), viewModel.bbsId) { position, answer ->
viewModel.navigateToBbsDetailPage(position, answer)
}
}
FOLLOW_DYNAMIC_ITEM_TYPE_FOOTER ->
FollowFooterViewHolder(parent.toBinding())
else -> {
FollowInvalidViewHolder(parent.toBinding())
}
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
when (holder) {
is FollowDynamicPersonalViewHolder -> {
val item = getItem(position)
if (item is FollowDynamicPersonalItem) {
holder.onBind(item.data, "", position)
}
}
is FollowDynamicBbSViewHolder -> {
val item = getItem(position)
if (item is FollowDynamicBbsItem) {
holder.onBind(item.data, "", "", position)
}
}
is FollowFooterViewHolder -> {
holder.initFooterViewHolder(
loadStatus == LoadStatus.LIST_LOADING,
loadStatus == LoadStatus.LIST_FAILED,
loadStatus == LoadStatus.LIST_OVER,
R.string.load_over_with_click_hint
) {
if (loadStatus == LoadStatus.LIST_OVER) {
_recyclerView?.scrollToPosition(0)
} else if (loadStatus == LoadStatus.LIST_FAILED) {
viewModel.loadMore()
notifyItemChanged(itemCount - 1)
}
}
}
else -> Unit
}
}
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
recyclerView.addOnScrollListener(onLoadMoreListener)
_recyclerView = recyclerView
}
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
recyclerView.removeOnScrollListener(onLoadMoreListener)
_recyclerView = null
}
private val onLoadMoreListener = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
val layoutManger = recyclerView.layoutManager
if (layoutManger is LinearLayoutManager && itemCount != 0) {
val lastVisibleItemPosition = layoutManger.findLastVisibleItemPosition()
if (lastVisibleItemPosition == itemCount - 1 && loadStatus != LoadStatus.LIST_OVER && loadStatus != LoadStatus.LIST_LOADING) {
viewModel.loadMore()
}
}
}
}
}
companion object {
fun createDiffCallback() = object : ItemCallback<FollowDynamicItem>() {
override fun areItemsTheSame(oldItem: FollowDynamicItem, newItem: FollowDynamicItem): Boolean {
return oldItem.areItemsTheSame(newItem)
}
override fun areContentsTheSame(oldItem: FollowDynamicItem, newItem: FollowDynamicItem): Boolean {
return oldItem.areContentsTheSame(newItem)
}
}
}
class FollowDynamicPersonalViewHolder(
private val context: Context,
val binding: CommunityAnswerItemBinding,
private val itemClickCallback: (historyEntity: PersonalHistoryEntity, position: Int) -> Unit
) :
RecyclerView.ViewHolder(binding.root) {
// 复用原有 viewHolder
private val realViewHolder by lazy(LazyThreadSafetyMode.NONE) {
PersonalItemViewHolder(context, binding, itemClickCallback)
}
fun onBind(historyEntity: PersonalHistoryEntity, entrance: String, position: Int) {
realViewHolder.bindPersonalItem(historyEntity, entrance, position)
}
}
class FollowDynamicBbSViewHolder(
val binding: CommunityAnswerItemBinding,
private val bbsId: String,
private val onItemClick: (Int, AnswerEntity) -> Unit
) : RecyclerView.ViewHolder(binding.root) {
private val viewHolder by lazy(LazyThreadSafetyMode.NONE) {
ForumArticleAskItemViewHolder(binding)
}
fun onBind(answer: AnswerEntity, entrance: String, path: String, position: Int) {
val articlePosition = position - 1
if (answer.type != "answer") {
val questions = Questions()
questions.id = answer.id
questions.title = answer.title
questions.description = answer.description
questions.answerCount = answer.count.answer
answer.questions = questions
}
if (path == "精华" && answer.type != "answer" && answer.type != "video") answer.type = "community_article"
if (path == "问答") answer.type = "question"
if (path == "视频") answer.type = "video"
if (answer.bbs.id.isEmpty()) answer.bbs.id = bbsId
binding.forumNameLl.visibility = View.GONE
binding.topLine.goneIf(articlePosition == 0)
val params = binding.includeVoteAndComment.root.layoutParams as LinearLayout.LayoutParams
params.width = LinearLayout.LayoutParams.MATCH_PARENT
params.leftMargin = 0
params.rightMargin = 0
binding.includeVoteAndComment.root.layoutParams = params
viewHolder.bindForumAnswerItem(answer, entrance, path, position)
viewHolder.itemView.setOnClickListener {
onItemClick(position, answer)
}
}
}
}

View File

@ -0,0 +1,504 @@
package com.gh.gamecenter.forum.home.follow.adapter
import android.view.ViewGroup
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.syncpage.ISyncAdapterHandler
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.entity.FollowDynamicEntity
import com.gh.gamecenter.entity.FollowUserEntity
import com.gh.gamecenter.eventbus.EBUserFollow
import com.gh.gamecenter.feature.entity.ConcernEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.LibaoEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.forum.home.follow.*
import com.gh.gamecenter.forum.home.follow.FollowItem.Companion.FOLLOW_ITEM_TYPE_ARTICLE
import com.gh.gamecenter.forum.home.follow.FollowItem.Companion.FOLLOW_ITEM_TYPE_COMMON_BANNER
import com.gh.gamecenter.forum.home.follow.FollowItem.Companion.FOLLOW_ITEM_TYPE_COMMON_BANNER_WITH_CARDS
import com.gh.gamecenter.forum.home.follow.FollowItem.Companion.FOLLOW_ITEM_TYPE_COMMON_HORIZONTAL_SLIDE
import com.gh.gamecenter.forum.home.follow.FollowItem.Companion.FOLLOW_ITEM_TYPE_COMMON_VERTICAL_TWO_CARDS
import com.gh.gamecenter.forum.home.follow.FollowItem.Companion.FOLLOW_ITEM_TYPE_EMPTY
import com.gh.gamecenter.forum.home.follow.FollowItem.Companion.FOLLOW_ITEM_TYPE_FOOTER
import com.gh.gamecenter.forum.home.follow.FollowItem.Companion.FOLLOW_ITEM_TYPE_GIFT_PACK
import com.gh.gamecenter.forum.home.follow.FollowItem.Companion.FOLLOW_ITEM_TYPE_HEADER
import com.gh.gamecenter.forum.home.follow.FollowItem.Companion.FOLLOW_ITEM_TYPE_NAVIGATION
import com.gh.gamecenter.forum.home.follow.FollowItem.Companion.FOLLOW_ITEM_TYPE_POST_CARD
import com.gh.gamecenter.forum.home.follow.FollowItem.Companion.FOLLOW_ITEM_TYPE_RECOMMEND
import com.gh.gamecenter.forum.home.follow.FollowItem.Companion.FOLLOW_ITEM_TYPE_RECOMMEND_USER
import com.gh.gamecenter.forum.home.follow.viewholder.*
import com.gh.gamecenter.forum.home.follow.viewmodel.FollowHomeViewModel
import com.gh.gamecenter.home.custom.OnViewHolderAttachListener
import com.gh.gamecenter.home.custom.model.CustomPageData
class FollowHomeAdapter(
private val lifecycleOwner: LifecycleOwner,
private val viewModel: FollowHomeViewModel,
private val pageConfiguration: FollowPageConfiguration
) : ListAdapter<FollowItem, ViewHolder>(createItemCallback()), ISyncAdapterHandler {
private var loadStatus: LoadStatus? = null
private var _recyclerView: RecyclerView? = null
override fun submitList(list: List<FollowItem>?) {
val dataWithFooter = if (list.isNullOrEmpty()) {
ArrayList(list)
} else {
list + FollowFooterItem
}
super.submitList(dataWithFooter)
}
fun setLoadStatus(status: LoadStatus) {
loadStatus = status
notifyItemChanged(itemCount - 1)
}
override fun getItemViewType(position: Int): Int {
return currentList[position].itemType
}
override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
var change: EBUserFollow? = null
var readStatusUpdate: ReadStatusUpdate? = null
payloads.forEach {
if (it is EBUserFollow) {
change = it
}
if (it is ReadStatusUpdate) {
readStatusUpdate = it
}
}
when {
change != null -> { // 局部刷新,关注/取消关注
(holder as? FollowRecommendListViewHolder)?.updateFollowed(change!!)
}
readStatusUpdate != null -> { // 局部刷新 关注用户是否有新消息
(holder as? FollowHomeHeaderViewHolder)?.updateReadStatus(readStatusUpdate!!.ids)
}
else -> {
super.onBindViewHolder(holder, position, payloads)
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
when (viewType) {
FOLLOW_ITEM_TYPE_HEADER -> {
FollowHomeHeaderViewHolder(parent.toBinding(), object : FollowHomeHeaderViewHolder.OnEventListener {
override fun onItemClick(position: Int, data: List<FollowUserEntity>) {
viewModel.viewItemFollowed(position, data)
}
override fun onAllClick() {
viewModel.viewAllFollowed()
}
override fun onLoginClick() {
viewModel.login()
}
})
}
FOLLOW_ITEM_TYPE_RECOMMEND_USER -> {
FollowRecommendListViewHolder(parent.toBinding(),
object : FollowRecommendListAdapter.OnChildEventListener {
override fun onItemClick(childPosition: Int, userId: String) {
SensorsBridge.trackFollowPageRecommendedUserCardClick(userId, childPosition + 1)
viewModel.navigateToUserHomePage(userId)
}
override fun onFollowClick(childPosition: Int, userId: String, isFollow: Boolean) {
SensorsBridge.trackRecommendedUserFollowButtonClick(userId, childPosition + 1)
viewModel.followUser(userId, isFollow)
}
})
}
FOLLOW_ITEM_TYPE_COMMON_BANNER -> {
FollowHomeSlideListViewHolder(
parent.toBinding(),
lifecycleOwner,
object : FollowHomeSlideListViewHolder.OnChildEventListener {
override fun navigateToLinkPage(
position: Int,
link: LinkEntity,
data: CustomPageData.CommonContentCollection,
text: String,
exposureEvent: ExposureEvent?
) {
viewModel.navigateToLinkPageInCommonContent(position, link, data, text, exposureEvent)
}
override fun navigateToGameDetailPage(position: Int, game: GameEntity, text: String) {
viewModel.navigateToGameDetailPage(position, game, text)
}
})
}
FOLLOW_ITEM_TYPE_COMMON_BANNER_WITH_CARDS -> {
FollowHomeSlideWithCardsViewHolder(
parent.toBinding(),
viewModel.commonContentCollectionUseCase,
lifecycleOwner,
object : FollowHomeSlideWithCardsViewHolder.OnChildEventListener {
override fun navigateToGameDetailPage(
childPosition: Int,
gameEntity: GameEntity,
text: String
) {
viewModel.navigateToGameDetailPage(childPosition, gameEntity, text)
}
override fun navigateToLinkPage(
position: Int,
link: LinkEntity,
data: CustomPageData.CommonContentCollection,
text: String,
exposureEvent: ExposureEvent?
) {
viewModel.navigateToLinkPageInCommonContent(position, link, data, text, exposureEvent)
}
})
}
FOLLOW_ITEM_TYPE_NAVIGATION -> {
FollowNavigationViewHolder(parent.toBinding(),
object : FollowNavigationViewHolder.OnChildEventListener {
override fun navigateToLinkPage(
position: Int,
link: LinkEntity,
data: CustomPageData.CommonContentCollection,
text: String,
exposureEvent: ExposureEvent?
) {
viewModel.navigateToLinkPageInCommonContent(position, link, data, text, exposureEvent)
}
})
}
FOLLOW_ITEM_TYPE_RECOMMEND -> {
FollowHomeRecommendItemViewHolder(
parent.toBinding(),
object : FollowHomeRecommendItemViewHolder.OnChildEventListener {
override fun navigateToLinkPage(
position: Int,
link: LinkEntity,
data: CustomPageData.CommonContentCollection,
text: String,
exposureEvent: ExposureEvent?
) {
viewModel.navigateToLinkPageInCommonContent(position, link, data, text, exposureEvent)
}
})
}
FOLLOW_ITEM_TYPE_COMMON_HORIZONTAL_SLIDE ->
FollowCommonCollectionViewHolder(
parent.toBinding(),
object : FollowCommonCollectionViewHolder.OnChildEventListener {
override fun navigateToLinkPage(
position: Int,
link: LinkEntity,
data: CustomPageData.CommonContentCollection,
text: String,
exposureEvent: ExposureEvent?
) {
viewModel.navigateToLinkPageInCommonContent(position, link, data, text, exposureEvent)
}
override fun navigateToCommonCollectionDetailPage(data: CustomPageData.CommonContentCollection) {
viewModel.navigateToCommonCollectionDetailPage(data)
}
})
FOLLOW_ITEM_TYPE_COMMON_VERTICAL_TWO_CARDS ->
FollowCommonCollection12ViewHolder(
parent.toBinding(),
object : FollowCommonCollection12ViewHolder.OnChildEventListener {
override fun navigateToLinkPage(
position: Int,
link: LinkEntity,
data: CustomPageData.CommonContentCollection,
text: String,
exposureEvent: ExposureEvent?
) {
viewModel.navigateToLinkPageInCommonContent(position, link, data, text, exposureEvent)
}
override fun navigateToCommonCollectionDetailPage(data: CustomPageData.CommonContentCollection) {
viewModel.navigateToCommonCollectionDetailPage(data)
}
})
FOLLOW_ITEM_TYPE_POST_CARD -> {
FollowPostCardViewHolder(
parent.toBinding(),
pageConfiguration.entrance,
pageConfiguration.path
)
}
FOLLOW_ITEM_TYPE_GIFT_PACK -> {
GiftPackViewHolder(parent.toBinding(), object : GiftPackViewHolder.OnChildEventListener {
override fun onGameClick(game: GameEntity) {
viewModel.navigateToGameDetailPage(0, game, "礼包")
}
override fun onGiftPackageClick(libaoEntity: LibaoEntity) {
SensorsBridge.trackFollowPageContentClick("游戏礼包", libaoEntity.name ?: "")
viewModel.navigateToLibaoDetailPage(libaoEntity)
}
override fun onCopy(code: String) {
viewModel.copyExchangeCode(code)
}
})
}
FOLLOW_ITEM_TYPE_ARTICLE -> {
GameArticleViewHolder(parent.toBinding(), object : GameArticleViewHolder.OnChildEventListener {
override fun onGameCLick(game: GameEntity) {
viewModel.navigateToGameDetailPage(0, game, "关注-文章")
}
override fun onArticleClick(article: FollowDynamicEntity.Article) {
SensorsBridge.trackFollowPageContentClick("文章", article.title)
viewModel.navigateToNewsDetailPage(article.id)
}
override fun onComment(article: FollowDynamicEntity.Article) {
SensorsBridge.trackFollowPageContentClick("文章", article.title)
viewModel.navigateToArticleCommentDetailPage(article.id)
}
override fun onShare(concernEntity: ConcernEntity) {
viewModel.onShareArticle(concernEntity)
}
})
}
FOLLOW_ITEM_TYPE_FOOTER -> FollowFooterViewHolder(parent.toBinding())
FOLLOW_ITEM_TYPE_EMPTY -> {
FollowHomeEmptyViewHolder(parent.toBinding())
}
else -> FollowInvalidViewHolder(parent.toBinding())
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
when (holder) {
is FollowHomeHeaderViewHolder -> {
getRealTypeItem<FollowUserItem>(position) {
holder.bind(it.isLogin, it.data)
}
}
is FollowRecommendListViewHolder -> {
getRealTypeItem<FollowRecommendUsersItem>(position) {
holder.bindView(it.data)
}
}
is FollowHomeSlideListViewHolder -> {
getRealTypeItem<FollowCommonCollectionItem>(position) {
holder.bindView(it.data)
}
}
is FollowHomeSlideWithCardsViewHolder -> {
getRealTypeItem<FollowCommonCollectionItem>(position) {
holder.bindView(it.data)
}
}
is FollowNavigationViewHolder -> {
getRealTypeItem<FollowCommonCollectionItem>(position) {
holder.bindView(it.data)
}
}
is FollowHomeRecommendItemViewHolder -> {
getRealTypeItem<FollowCommonCollectionItem>(position) {
holder.bindView(it.data)
}
}
is FollowCommonCollectionViewHolder -> {
getRealTypeItem<FollowCommonCollectionItem>(position) {
holder.bindView(it.data)
}
}
is FollowCommonCollection12ViewHolder -> {
getRealTypeItem<FollowSplitCommonContentCollectionItem>(position) {
holder.bind(it.data, it.leftPosition)
}
}
is FollowPostCardViewHolder -> {
getRealTypeItem<FollowPostCardItem>(position) {
setItemViewBackground(holder)
holder.bind(it.data, position)
}
}
is GameArticleViewHolder -> {
getRealTypeItem<FollowArticleItem>(position) {
setItemViewBackground(holder)
holder.bind(it.data)
}
}
is GiftPackViewHolder -> {
getRealTypeItem<FollowGiftPackItem>(position) {
setItemViewBackground(holder)
holder.bind(it.libao, it.time)
}
}
is FollowFooterViewHolder -> {
holder.initFooterViewHolder(
loadStatus == LoadStatus.LIST_LOADING,
loadStatus == LoadStatus.LIST_FAILED,
loadStatus == LoadStatus.LIST_OVER,
R.string.load_over_with_click_hint
) {
if (loadStatus == LoadStatus.LIST_OVER) {
_recyclerView?.scrollToPosition(0)
} else if (loadStatus == LoadStatus.LIST_FAILED) {
viewModel.loadMore()
notifyItemChanged(itemCount - 1)
}
}
}
else -> Unit
}
}
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
_recyclerView = recyclerView
recyclerView.addOnScrollListener(onLoadMoreListener)
}
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
recyclerView.removeOnScrollListener(onLoadMoreListener)
_recyclerView = null
}
override fun onViewAttachedToWindow(holder: ViewHolder) {
if (holder is OnViewHolderAttachListener) {
holder.onViewAttach(_recyclerView)
}
}
override fun onViewDetachedFromWindow(holder: ViewHolder) {
if (holder is OnViewHolderAttachListener) {
holder.onViewDetach(_recyclerView)
}
}
override fun getSyncData(position: Int): Pair<String, Any>? {
if (position >= itemCount) return null
val item = getItem(position)
return when {
item is FollowPostCardItem -> {
Pair(item.data.id, item.data.count)
}
else -> null
}
}
private inline fun <T> getRealTypeItem(position: Int, block: (T) -> Unit) {
val item = getItem(position)
(item as? T)?.let(block)
}
fun notifyFollowUserChanged(change: EBUserFollow) {
val position = currentList.indexOfFirst { it is FollowRecommendUsersItem }
notifyItemChanged(position, change)
}
fun notifyFollowUserReadStatus(ids: List<String>) {
val position = currentList.indexOfFirst { it is FollowUserItem }
notifyItemChanged(position, ReadStatusUpdate(ids))
}
private val onLoadMoreListener = object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
val layoutManger = recyclerView.layoutManager
if (layoutManger is LinearLayoutManager) {
val lastVisibleItemPosition = layoutManger.findLastVisibleItemPosition()
if (lastVisibleItemPosition == itemCount - 1 && loadStatus != LoadStatus.LIST_OVER && loadStatus != LoadStatus.LIST_LOADING) {
viewModel.loadMore()
}
}
}
}
}
companion object {
fun createItemCallback() = object : ItemCallback<FollowItem>() {
override fun areItemsTheSame(oldItem: FollowItem, newItem: FollowItem): Boolean {
return oldItem.areItemsTheSame(newItem)
}
override fun areContentsTheSame(oldItem: FollowItem, newItem: FollowItem): Boolean {
return oldItem.areContentsTheSame(newItem)
}
}
fun setItemViewBackground(viewHolder: ViewHolder) {
if (viewHolder.bindingAdapterPosition == 1) {
viewHolder.itemView.setBackgroundResource(R.drawable.background_shape_white_radius_12_top_only)
} else {
viewHolder.itemView.setBackgroundColor(R.color.ui_surface.toColor(viewHolder.itemView.context))
}
}
}
data class ReadStatusUpdate(
val ids: List<String>
)
}

View File

@ -0,0 +1,101 @@
package com.gh.gamecenter.forum.home.follow.adapter
import android.content.Context
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.debounceActionWithInterval
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.toDrawable
import com.gh.gamecenter.databinding.RecyclerFollowRecommendBinding
import com.gh.gamecenter.eventbus.EBUserFollow
import com.gh.gamecenter.feature.entity.UserEntity
import com.gh.gamecenter.forum.home.follow.ResetDataChangeHelper
class FollowRecommendListAdapter(
context: Context,
private val listener: OnChildEventListener
) : RecyclerView.Adapter<FollowRecommendListAdapter.RecommendViewHolder>() {
private val resetDataChangeHelper = ResetDataChangeHelper<UserEntity>(context, this)
fun submitList(data: List<UserEntity>) {
resetDataChangeHelper.submitList(data) {
it.id ?: ""
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecommendViewHolder {
return RecommendViewHolder(parent.toBinding())
}
override fun getItemCount(): Int =
resetDataChangeHelper.dataList.size
override fun onBindViewHolder(holder: RecommendViewHolder, position: Int) {
val item = resetDataChangeHelper.dataList[position]
val context = holder.itemView.context
holder.binding.ivIcon.displayGameIcon(item.icon, null)
holder.binding.tvFollowerCount.text = if (item.count.fans > NUMBER_MAX) {
context.getString(R.string.fans_with_number_max)
} else {
context.getString(R.string.fans_with_number, item.count.fans)
}
val createCount = item.count.question + item.count.communityArticle + item.count.video
holder.binding.tvArticleCount.text = if (createCount > NUMBER_MAX) {
context.getString(R.string.creations_with_number_max)
} else {
context.getString(R.string.creations_with_number, createCount)
}
holder.binding.tvSign.text = if (item.introduce.isBlank()) {
context.getString(R.string.user_default_introduce)
} else {
item.introduce
}
holder.binding.tvFollow.text = if (item.isFollowed) {
context.getString(R.string.concerned)
} else {
context.getString(R.string.menu_concern)
}
holder.itemView.background = R.drawable.bg_shape_recycler_follow_recommend.toDrawable(context)
holder.itemView.setOnClickListener {
listener.onItemClick(position, item.id ?: "")
}
holder.binding.tvFollow.setOnClickListener {
debounceActionWithInterval(it.id, 1000) {
listener.onFollowClick(position, item.id ?: "", !item.isFollowed)
}
}
}
fun updateFollowed(change: EBUserFollow) {
val position = resetDataChangeHelper.dataList.indexOfFirst { it.id == change.userId }
if (position != -1) {
val item = resetDataChangeHelper.dataList[position]
item.isFollowed = change.isFollow
notifyItemChanged(position)
}
}
companion object {
private const val NUMBER_MAX = 99
}
class RecommendViewHolder(val binding: RecyclerFollowRecommendBinding) : ViewHolder(binding.root)
interface OnChildEventListener {
fun onItemClick(childPosition: Int, userId: String)
fun onFollowClick(childPosition: Int, userId: String, isFollow: Boolean)
}
}

View File

@ -0,0 +1,214 @@
package com.gh.gamecenter.forum.home.follow.adapter
import android.content.Context
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.RecyclerFollowedListAllBinding
import com.gh.gamecenter.databinding.RecyclerFollowedListBbsBinding
import com.gh.gamecenter.databinding.RecyclerFollowedListPersonBinding
import com.gh.gamecenter.entity.FollowUserEntity
import com.gh.gamecenter.forum.home.follow.ResetDataChangeHelper
import com.gh.gamecenter.forum.home.follow.model.FollowRepository.Companion.FOLLOW_UPDATE_TYPE_USER
class FollowedHeaderAdapter(
private val context: Context,
private val listener: OnChildEventListener
) :
RecyclerView.Adapter<ViewHolder>() {
/**
* 1.当 _selectedPosition 为 -1 时
* 不显示 viewAll 按钮
* 显示选中框
*
* 2. 当_selectedPosition 为 -2 时
* 不显示 viewAll 按钮
* 暂无选中框
*/
private var _selectedPosition = -1
private val showAll: Boolean
get() = _selectedPosition == -1
private val resetDataChangeHelper = ResetDataChangeHelper<FollowUserEntity>(context, this)
val dataList: List<FollowUserEntity>
get() = resetDataChangeHelper.dataList
fun submitList(data: List<FollowUserEntity>, selectedPosition: Int = -1) {
_selectedPosition = selectedPosition
resetDataChangeHelper.submitList(data) {
"${it.id}-${it.type}-${it.isShowTip}"
}
}
override fun getItemCount(): Int =
if (showAll) resetDataChangeHelper.dataCount + 1 else resetDataChangeHelper.dataCount
override fun getItemViewType(position: Int): Int {
return if (showAll) {
if (position < resetDataChangeHelper.dataCount) {
if (resetDataChangeHelper.getItem(position).type == FOLLOW_UPDATE_TYPE_USER) {
VIEW_TYPE_BBS
} else {
VIEW_TYPE_GAME
}
} else {
VIEW_TYPE_VIEW_ALL
}
} else {
if (resetDataChangeHelper.getItem(position).type == FOLLOW_UPDATE_TYPE_USER) {
VIEW_TYPE_BBS
} else {
VIEW_TYPE_GAME
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder =
when (viewType) {
VIEW_TYPE_GAME -> {
FollowedGameViewHolder(parent.toBinding(), ::onSelectedChanged)
}
VIEW_TYPE_BBS -> {
FollowedPersonViewHolder(parent.toBinding(), ::onSelectedChanged)
}
else -> FollowedViewAllViewHolder(parent.toBinding(), listener::onViewAll)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
when (holder) {
is FollowedGameViewHolder -> {
holder.bindView(
resetDataChangeHelper.getItem(position),
position,
_selectedPosition == position,
showAll
)
}
is FollowedPersonViewHolder -> {
holder.bindView(
resetDataChangeHelper.getItem(position),
position,
_selectedPosition == position,
showAll
)
}
}
}
private fun onSelectedChanged(position: Int) {
selectPosition(position)
listener.onViewItem(position)
}
fun selectPosition(position: Int) {
if (_selectedPosition != -1 && _selectedPosition != position) {
val lastSelectedPosition = _selectedPosition
_selectedPosition = position
notifyItemChanged(lastSelectedPosition)
notifyItemChanged(_selectedPosition)
_selectedPosition = position
}
}
fun clearSelectedPosition() {
val lastSelectedPosition = _selectedPosition
_selectedPosition = -2
if (lastSelectedPosition >= 0) {
notifyItemChanged(lastSelectedPosition)
}
}
companion object {
private const val VIEW_TYPE_GAME = 0
private const val VIEW_TYPE_BBS = 1
private const val VIEW_TYPE_VIEW_ALL = 2
}
private class FollowedGameViewHolder(
val binding: RecyclerFollowedListBbsBinding,
private val itemClick: (Int) -> Unit,
) : ViewHolder(binding.root) {
fun bindView(item: FollowUserEntity, position: Int, isSelected: Boolean, showName: Boolean) {
if (showName) {
binding.tvName.goneIf(false)
binding.tvName.text = item.bbs.name
} else {
binding.tvName.goneIf(true)
}
binding.tvName.setTextColor(R.color.text_secondary.toColor(itemView.context))
binding.ivIcon.displayGameIcon(item.icon, null)
val resourceId = if (isSelected) {
R.drawable.background_shape_theme_radius_14
} else {
R.drawable.background_shape_white_radius_14
}
binding.gIndicator.goneIf(!item.isShowTip)
binding.vIconBackground.background = resourceId.toDrawable(itemView.context)
itemView.setOnClickListener {
itemClick(position)
}
}
}
private class FollowedPersonViewHolder(
val binding: RecyclerFollowedListPersonBinding,
private val itemClick: (Int) -> Unit,
) : ViewHolder(binding.root) {
fun bindView(item: FollowUserEntity, position: Int, isSelected: Boolean, showName: Boolean) {
if (showName) {
binding.tvName.goneIf(false)
binding.tvName.text = item.user.name
} else {
binding.tvName.goneIf(true)
}
binding.tvName.setTextColor(R.color.text_secondary.toColor(itemView.context))
binding.ivIcon.displayGameIcon(item.icon, null)
val resourceId = if (isSelected) {
R.drawable.background_shape_theme_radius_999
} else {
R.drawable.background_shape_white_radius_999
}
binding.gIndicator.goneIf(!item.isShowTip)
binding.vIconBackground.background = resourceId.toDrawable(itemView.context)
itemView.setOnClickListener {
itemClick(position)
}
}
}
private class FollowedViewAllViewHolder(
binding: RecyclerFollowedListAllBinding,
private val viewAll: () -> Unit,
) : ViewHolder(binding.root) {
init {
binding.root.setOnClickListener {
viewAll()
}
}
}
interface OnChildEventListener {
fun onViewItem(position: Int)
fun onViewAll()
}
}

View File

@ -0,0 +1,31 @@
package com.gh.gamecenter.forum.home.follow.adapter
import android.annotation.SuppressLint
import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
import com.gh.gamecenter.entity.FollowUserEntity
import com.gh.gamecenter.forum.home.follow.fragment.FollowDynamicListFragment
class FollowedViewPagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
private val dataList = arrayListOf<FollowUserEntity>()
@SuppressLint("NotifyDataSetChanged")
fun addData(newData: List<FollowUserEntity>) {
if (dataList.isEmpty()) {
dataList.addAll(newData)
notifyDataSetChanged()
} else {
val start = dataList.size
dataList.addAll(newData)
notifyItemRangeChanged(start, newData.size)
}
}
override fun getItemCount(): Int = dataList.size
override fun createFragment(position: Int): Fragment {
val item = dataList[position]
return FollowDynamicListFragment.newInstance(item.user.id ?: "", item.bbs.id,item.bbs.type)
}
}

View File

@ -0,0 +1,47 @@
package com.gh.gamecenter.forum.home.follow.eventlistener
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.entity.FollowUserEntity
import com.gh.gamecenter.feature.entity.ArticleEntity
import com.gh.gamecenter.feature.entity.ConcernEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.LibaoEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.home.custom.model.CustomPageData
interface OnFollowHomeEventListener {
fun viewItemFollowed(position: Int, data: List<FollowUserEntity>)
fun viewAllFollowed()
fun login()
fun followUser(userId: String, isFollow: Boolean)
fun navigateToLinkPageInCommonContent(
position: Int,
link: LinkEntity,
data: CustomPageData.CommonContentCollection,
text: String,
exposureEvent: ExposureEvent?
)
fun navigateToGameDetailPage(position: Int, game: GameEntity, text: String)
fun navigateToCommonCollectionDetailPage(data: CustomPageData.CommonContentCollection)
fun navigateToUserHomePage(userId: String)
fun onPostCardClick(position: Int, article: ArticleEntity)
fun navigateToLibaoDetailPage(libaoEntity: LibaoEntity)
fun navigateToNewsDetailPage(articleId: String)
fun navigateToArticleCommentDetailPage(articleId: String)
fun onShareArticle(concernEntity: ConcernEntity)
fun copyExchangeCode(code: String)
}

View File

@ -0,0 +1,283 @@
package com.gh.gamecenter.forum.home.follow.fragment
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Rect
import android.view.View
import androidx.core.view.children
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ItemDecoration
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.LazyFragment
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.FragmentAllFollowedBinding
import com.gh.gamecenter.forum.detail.ForumDetailActivity
import com.gh.gamecenter.forum.home.follow.AllFollowedNoTopItem
import com.gh.gamecenter.forum.home.follow.AllFollowedNormalItem
import com.gh.gamecenter.forum.home.follow.adapter.AllFollowedAdapter
import com.gh.gamecenter.forum.home.follow.viewmodel.AllFollowedViewModel
import com.gh.gamecenter.livedata.EventObserver
import com.lightgame.listeners.OnBackPressedListener
import com.lightgame.utils.toast.ToastHelper
import splitties.resources.color
import java.util.*
import kotlin.math.abs
class AllFollowedFragment : LazyFragment(), OnBackPressedListener {
private lateinit var binding: FragmentAllFollowedBinding
private val viewModel by viewModels<AllFollowedViewModel>()
private val itemTouchHelper = ItemTouchHelper(MyItemTouchCallback())
private var originalTopList = listOf<AllFollowedNormalItem>()
private val paint by lazy {
Paint().apply {
color = R.color.text_primary.toColor(requireContext())
textSize = 14F.dip2px().toFloat()
}
}
private val overPaint by lazy {
Paint().apply {
color = R.color.ui_surface.toColor(requireContext())
}
}
private val adapter by lazy(LazyThreadSafetyMode.NONE) {
AllFollowedAdapter(viewModel) {
startDrag(it)
}
}
override fun getRealLayoutId(): Int = R.layout.fragment_all_followed
override fun onRealLayoutInflated(inflatedView: View) {
binding = FragmentAllFollowedBinding.bind(inflatedView)
}
override fun onFragmentFirstVisible() {
super.onFragmentFirstVisible()
binding.rvFollowed.layoutManager = LinearLayoutManager(requireContext())
binding.rvFollowed.adapter = adapter
binding.rvFollowed.addItemDecoration(MyItemDecoration(requireContext(), paint, overPaint))
itemTouchHelper.attachToRecyclerView(binding.rvFollowed)
with(viewModel) {
dataList.observe(viewLifecycleOwner) {
originalTopList = it.filter(AllFollowedNormalItem::isTop)
adapter.setData(it)
}
operateTopSuccessfully.observe(viewLifecycleOwner, EventObserver {
// 提交成功
ToastHelper.showToast(requireContext(), getString(R.string.save_successfully))
finish(true)
})
detailDestination.observe(viewLifecycleOwner, EventObserver {
if (it.isUser) {
DirectUtils.directToHomeActivity(requireContext(), it.user.id)
} else {
startActivity(ForumDetailActivity.getIntent(requireContext(), it.bbs.id, ""))
}
})
loadFirst()
}
binding.tvRight.setOnClickListener {
if (adapter.isEdit && isDataChanged()) {
viewModel.operateTop(adapter.getTopItem())
} else {
adapter.changeToEditable()
binding.tvRight.text = if (adapter.isEdit) "保存" else "完成"
val textResId = if (adapter.isEdit) R.string.finish else R.string.manager
binding.tvRight.setText(textResId)
}
}
binding.ivBack.setOnClickListener {
if (adapter.isEdit && isDataChanged()) {
showSaveTipsDialog()
} else {
finish()
}
}
}
private fun showSaveTipsDialog() {
DialogHelper.showDialog(
requireContext(),
getString(R.string.archive_dialog_title),
getString(R.string.whether_to_save_changes),
cancelText = getString(R.string.dialog_nickname_cancel),
confirmText = getString(R.string.servers_subscribed_game_unsubscribe_dialog_confirm),
confirmClickCallback = {
viewModel.operateTop(adapter.getTopItem())
},
cancelClickCallback = {
finish()
},
extraConfig = DialogHelper.Config(showCloseIcon = false, centerTitle = true, centerContent = true)
)
}
private fun isDataChanged(): Boolean = originalTopList != adapter.dataList.filter(AllFollowedNormalItem::isTop)
private fun startDrag(viewHolder: RecyclerView.ViewHolder) {
itemTouchHelper.startDrag(viewHolder)
}
override fun onHandleBackPressed(): Boolean {
if (adapter.isEdit && isDataChanged()) {
showSaveTipsDialog()
return true
}
finish()
return true
}
private fun finish(isSorted: Boolean = false) {
if (viewModel.hasReadSetIds.isNotEmpty() || isSorted) {
val intent = Intent()
// 如果顺序发生变化,则直接通知外部重新加载列表,无需局部更新阅读状态
if (!isSorted) {
intent.putStringArrayListExtra(KEY_HAS_READ_ID, viewModel.hasReadSetIds.toList().toArrayList())
}
activity?.setResult(Activity.RESULT_OK, intent)
}
activity?.finish()
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
paint.color = R.color.text_primary.toColor(requireContext())
overPaint.color = R.color.ui_surface.toColor(requireContext())
binding.rvFollowed.tryToClearRecycler()
binding.rvFollowed.recycledViewPool.clear()
adapter.notifyItemRangeChanged(0, adapter.itemCount)
}
companion object {
const val KEY_HAS_READ_ID = "key_has_read_id"
}
private class MyItemDecoration(
private val context: Context,
private val paint: Paint,
private val overPaint: Paint
) : ItemDecoration() {
private val headerHeight by lazy(LazyThreadSafetyMode.NONE) {
HEADER_HEIGHT.dip2px()
}
private val screenWidth by lazy(LazyThreadSafetyMode.NONE) {
context.resources.displayMetrics.widthPixels
}
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
// 是否需要绘制 置顶 悬浮头部
val adapter = parent.adapter
if (adapter is AllFollowedAdapter) {
// 寻找最后一个 置顶 的itemView
val lastTopView = parent.children.lastOrNull {
val position = parent.getChildAdapterPosition(it)
val item = if (position != -1) {
adapter.getItem(position)
} else {
null
}
(item is AllFollowedNormalItem && item.isTop) || item is AllFollowedNoTopItem
}
if (lastTopView != null) {
// 绘制 置顶 悬浮条
val bottom = if (lastTopView.bottom >= headerHeight) {
headerHeight
} else {
lastTopView.bottom
}
c.drawRect(
Rect(0, 0, screenWidth, bottom),
overPaint
)
val text = "置顶(${adapter.topItemCount}/${adapter.followedCount})"
drawFollowedHeader(text, c, bottom)
} else {
// 绘制 “我的关注” 悬浮条
c.drawRect(
Rect(0, 0, screenWidth, headerHeight),
overPaint
)
drawFollowedHeader("我的关注", c, headerHeight)
}
}
}
private fun drawFollowedHeader(text: String, c: Canvas, bottom: Int) {
val fontMetrics = paint.fontMetrics
val centerYDistanceToBaseline =
(abs(fontMetrics.descent) + abs(fontMetrics.ascent) + abs(fontMetrics.leading)) / 2 - abs(
fontMetrics.descent
)
val baselineY = bottom - 25F.dip2px().toFloat() + centerYDistanceToBaseline
c.drawText(text, 0, text.length, 16F.dip2px().toFloat(), baselineY, paint)
}
companion object {
private const val HEADER_HEIGHT = 50F
}
}
private class MyItemTouchCallback : ItemTouchHelper.Callback() {
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 adapter = recyclerView.adapter as? AllFollowedAdapter
if (adapter != null) {
val position = viewHolder.bindingAdapterPosition
val item = adapter.getItem(position)
if (item is AllFollowedNormalItem && item.isTop) {
val targetPosition = target.bindingAdapterPosition
val targetItem = adapter.getItem(targetPosition)
if (targetItem is AllFollowedNormalItem && targetItem.isTop) {
val newList = ArrayList(adapter.dataList)
val dataPosition = newList.indexOfFirst { it.data.id == item.data.id }
val targetDataPosition = newList.indexOfFirst { it.data.id == targetItem.data.id }
Collections.swap(newList, dataPosition, targetDataPosition)
adapter.setData(newList)
return true
}
}
}
return false
}
override fun isLongPressDragEnabled(): Boolean = false
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit
}
}

View File

@ -0,0 +1,271 @@
package com.gh.gamecenter.forum.home.follow.fragment
import android.animation.ValueAnimator
import android.graphics.PointF
import android.os.Bundle
import android.view.View
import android.view.animation.Interpolator
import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.LinearSmoothScroller
import androidx.recyclerview.widget.OrientationHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.OnScrollListener
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.LazyFragment
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.visibleIf
import com.gh.gamecenter.databinding.FragmentFollowDynamicBinding
import com.gh.gamecenter.entity.FollowUserEntity
import com.gh.gamecenter.forum.home.follow.FollowDynamicActivity.Companion.KEY_SELECTED_POSITION
import com.gh.gamecenter.forum.home.follow.FollowDynamicActivity.Companion.KEY_USERS
import com.gh.gamecenter.forum.home.follow.adapter.FollowedHeaderAdapter
import com.gh.gamecenter.forum.home.follow.adapter.FollowedViewPagerAdapter
import com.gh.gamecenter.forum.home.follow.viewmodel.FollowDynamicViewModel
import com.gh.gamecenter.livedata.EventObserver
import kotlin.math.pow
class FollowDynamicFragment : LazyFragment() {
private lateinit var binding: FragmentFollowDynamicBinding
private val viewModel by activityViewModels<FollowDynamicViewModel>()
private var selectedPosition = 0
private val screenCenterX by lazy(LazyThreadSafetyMode.NONE) {
resources.displayMetrics.widthPixels / 2
}
private val offsetX by lazy(LazyThreadSafetyMode.NONE) {
screenCenterX - 32F.dip2px()
}
private val layoutManager by lazy(LazyThreadSafetyMode.NONE) {
LinearLayoutManager(requireContext(), RecyclerView.HORIZONTAL, false)
}
private val helper by lazy {
OrientationHelper.createHorizontalHelper(layoutManager)
}
private val followedAdapter by lazy {
FollowedHeaderAdapter(requireContext(), object : FollowedHeaderAdapter.OnChildEventListener {
override fun onViewItem(position: Int) {
selectedPosition = position
binding.viewPager.setCurrentItem(position, false)
alignIndicator()
}
override fun onViewAll() = Unit
})
}
private val dynamicListVpAdapter by lazy {
FollowedViewPagerAdapter(this)
}
override fun getRealLayoutId(): Int = R.layout.fragment_follow_dynamic
override fun onRealLayoutInflated(inflatedView: View) {
binding = FragmentFollowDynamicBinding.bind(inflatedView)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val list = arguments?.getParcelableArrayList<FollowUserEntity>(KEY_USERS)
if (list != null) {
viewModel.addFirstPageUsers(list)
}
selectedPosition = arguments?.getInt(KEY_SELECTED_POSITION, 0) ?: 0
}
override fun onFragmentFirstVisible() {
super.onFragmentFirstVisible()
with(viewModel) {
followedUsers.observe(viewLifecycleOwner, Observer { (isFirst, data) ->
followedAdapter.submitList(data, selectedPosition)
if (isFirst) {
scrollSelectedPositionToCenter(false)
startAnimation(true)
}
dynamicListVpAdapter.addData(data)
})
}
binding.rvFollowedHeader.layoutManager = layoutManager
binding.rvFollowedHeader.adapter = followedAdapter
binding.viewPager.adapter = dynamicListVpAdapter
binding.viewPager.offscreenPageLimit = 1
binding.viewPager.setCurrentItem(selectedPosition, false)
binding.viewPager.registerOnPageChangeCallback(object : OnPageChangeCallback() {
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) = Unit
override fun onPageScrollStateChanged(state: Int) {
super.onPageScrollStateChanged(state)
if (state != 0) {
binding.ivIndicator.visibleIf(false)
followedAdapter.clearSelectedPosition()
} else {
binding.ivIndicator.visibleIf(true)
followedAdapter.selectPosition(selectedPosition)
}
}
override fun onPageSelected(position: Int) {
if (selectedPosition != position) {
selectedPosition = position
followedAdapter.selectPosition(position)
scrollSelectedPositionToCenter(true)
}
}
})
with(viewModel) {
finishAction.observe(viewLifecycleOwner, EventObserver {
startAnimation(false)
requireActivity().finish()
requireActivity().overridePendingTransition(0, R.anim.no_anim)
})
}
}
private fun scrollSelectedPositionToCenter(isSmoothScroll: Boolean) {
binding.rvFollowedHeader.clearOnScrollListeners()
if (isSmoothScroll) {
// 1. 如果当前 itemView 在屏幕内,则只需要直接将其滚动到中间即可
if (!checkTargetViewIfExist()) {
// 2. 如果当前itemView不在屏幕内则需要先将 itemView 滚动到屏幕内,在将其滚到屏幕中间
binding.rvFollowedHeader.addOnScrollListener(object : OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
binding.rvFollowedHeader.clearOnScrollListeners()
checkTargetViewIfExist()
}
}
})
val vector = layoutManager.computeScrollVectorForPosition(selectedPosition) ?: PointF()
val linearSmoothScroller = object : LinearSmoothScroller(requireContext()) {
override fun getHorizontalSnapPreference(): Int {
super.getHorizontalSnapPreference()
return if (vector.x > 0) SNAP_TO_END else SNAP_TO_START
}
}
linearSmoothScroller.targetPosition = selectedPosition
layoutManager.startSmoothScroll(linearSmoothScroller)
}
} else {
layoutManager.scrollToPositionWithOffset(selectedPosition, offsetX)
}
binding.rvFollowedHeader.addOnScrollListener(object : OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
alignIndicator()
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
alignIndicator()
}
})
}
private fun alignIndicator() {
val targetView = layoutManager.findViewByPosition(selectedPosition)
if (targetView != null) {
binding.ivIndicator.translationX = distanceToStart(targetView).toFloat()
}
}
private fun checkTargetViewIfExist(): Boolean {
val targetView = layoutManager.findViewByPosition(selectedPosition)
if (targetView != null) {
val distanceToCenter = distanceToCenter(targetView)
if (distanceToCenter != 0) {
binding.rvFollowedHeader.smoothScrollBy(distanceToCenter, 0)
}
}
return targetView != null
}
private val easeOutInterpolator = Interpolator { input -> 1 - (1 - input).pow(2) }
private fun startAnimation(isStart: Boolean) {
val values = if (isStart) {
floatArrayOf(ANIMATION_START_VALUE, ANIMATION_END_VALUE)
} else {
floatArrayOf(ANIMATION_END_VALUE, ANIMATION_START_VALUE)
}
val valueAnimator = ValueAnimator.ofFloat(*values)
val followHeaderTranslationY =
requireContext().resources.getDimensionPixelSize(R.dimen.follow_detail_header_translation_y)
val dynamicListTranslationY =
requireContext().resources.getDimensionPixelSize(R.dimen.follow_detail_content_translation_y)
with(valueAnimator) {
duration = ANIMATION_DURATION
interpolator = easeOutInterpolator
addUpdateListener {
val value = it.animatedValue as Float
val alphaEnd = 5F / 8F
val alpha = if (value >= 0 && value <= alphaEnd) {
value / alphaEnd
} else {
1F
}
binding.ivTopBackground.alpha = alpha
binding.rvFollowedHeader.alpha = alpha
binding.viewPager.alpha = alpha
if (value >= 0.5F) {
binding.ivIndicator.alpha = (value - 0.5F) * 2
} else {
binding.ivIndicator.alpha = 0F
}
binding.ivTopBackground.translationY = followHeaderTranslationY * (1 - value)
binding.rvFollowedHeader.translationY = followHeaderTranslationY * (1 - value)
binding.viewPager.translationY = dynamicListTranslationY * (1 - value)
if (isStart && value == 1F) {
viewModel.startLoad()
}
}
start()
}
}
private fun distanceToCenter(targetView: View): Int {
val childCenter = helper.getDecoratedStart(targetView) + helper.getDecoratedMeasurement(targetView) / 2
val containerCenter = helper.startAfterPadding + helper.totalSpace / 2
return childCenter - containerCenter
}
private fun distanceToStart(targetView: View): Int = distanceToCenter(targetView) + screenCenterX
override fun onDarkModeChanged() {
super.onDarkModeChanged()
followedAdapter.notifyItemRangeChanged(0, followedAdapter.itemCount, "")
}
companion object {
private const val ANIMATION_DURATION = 400L
private const val ANIMATION_START_VALUE = 0F
private const val ANIMATION_END_VALUE = 1F
}
}

View File

@ -0,0 +1,318 @@
package com.gh.gamecenter.forum.home.follow.fragment
import android.graphics.Canvas
import android.graphics.Paint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.children
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.BaseFragment
import com.gh.gamecenter.common.base.fragment.ToolbarFragment
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.FragmentFollowDynamicListBinding
import com.gh.gamecenter.feature.entity.CommentEntity
import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.forum.detail.ForumDetailActivity
import com.gh.gamecenter.forum.home.follow.FollowActivityResultLauncher
import com.gh.gamecenter.forum.home.follow.FollowDynamicBbsItem
import com.gh.gamecenter.forum.home.follow.FollowDynamicPersonalItem
import com.gh.gamecenter.forum.home.follow.adapter.FollowDynamicAdapter
import com.gh.gamecenter.forum.home.follow.viewmodel.FollowDynamicListViewModel
import com.gh.gamecenter.forum.home.follow.viewmodel.FollowDynamicViewModel
import com.gh.gamecenter.livedata.EventObserver
import com.gh.gamecenter.qa.BbsType
import com.gh.gamecenter.qa.entity.ArticleDetailEntity
import com.gh.gamecenter.qa.entity.QuestionsDetailEntity
class FollowDynamicListFragment : BaseFragment<Unit>() {
private var userId: String = ""
private var bbsId: String = ""
private var bbsType: String = ""
private val paint by lazy {
Paint().apply {
color = R.color.ui_divider.toColor(requireContext())
strokeWidth = 0.5F.dip2px().toFloat()
}
}
private val viewModel by viewModels<FollowDynamicListViewModel>()
private val followDynamicViewModel by activityViewModels<FollowDynamicViewModel>()
private val binding by lazy {
FragmentFollowDynamicListBinding.inflate(layoutInflater, null, false)
}
private val adapter by lazy {
FollowDynamicAdapter(requireContext(), viewModel)
}
private lateinit var launcher: FollowActivityResultLauncher
override fun getInflatedLayout(): View {
return binding.root
}
override fun getLayoutId(): Int = R.layout.fragment_follow_dynamic_list
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
userId = arguments?.getString(KEY_USER_ID) ?: ""
bbsId = arguments?.getString(KEY_BBS_ID) ?: ""
bbsType = arguments?.getString(KEY_BBS_TYPE) ?: ""
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initView()
with(viewModel) {
loadStatus.observe(viewLifecycleOwner) { (loadStatus, isPullRefresh) ->
if (isPullRefresh) {
if (loadStatus != LoadStatus.INIT_LOADING) {
binding.srlRefresh.isRefreshing = false
}
} else {
binding.reuseLoading.root.goneIf(loadStatus != LoadStatus.INIT_LOADING)
binding.reuseNoData.root.goneIf(loadStatus != LoadStatus.INIT_EMPTY)
binding.reuseNoConnection.root.goneIf(loadStatus != LoadStatus.LIST_FAILED)
binding.srlRefresh.isEnabled =
!(loadStatus == LoadStatus.INIT_FAILED || loadStatus == LoadStatus.INIT_LOADING)
}
adapter.setLoadStatus(loadStatus)
}
dataList.observe(viewLifecycleOwner) {
adapter.submitList(it)
}
personDetailDestination.observe(viewLifecycleOwner, EventObserver { (personalEntity, position) ->
launcher.onPersonItemClick(position, personalEntity)
})
bbsDetailDestination.observe(viewLifecycleOwner, EventObserver { (position, answer) ->
launcher.onBbsItemClick(position, answer)
})
}
followDynamicViewModel.isStartLoadAction.observe(viewLifecycleOwner, Observer {
if (it) {
viewModel.loadFirst(userId, bbsId, false)
}
})
binding.tvTitle.text = if (userId.isNotBlank()) {
getString(R.string.follow_detail_person_updates)
} else {
if (bbsType == BbsType.OFFICIAL_BBS.value) {
getString(R.string.follow_detail_bbs_updates)
} else {
getString(R.string.follow_detail_game_updates)
}
}
binding.tvRight.text =
if (userId.isNotBlank()) {
getString(R.string.user_home_page)
} else {
getString(R.string.go_to_forum)
}
binding.tvRight.setOnClickListener {
if (userId.isNotBlank()) {
DirectUtils.directToHomeActivity(requireContext(), userId)
} else {
startActivity(ForumDetailActivity.getIntent(requireContext(), bbsId, ""))
}
}
}
private fun initView() {
launcher = FollowActivityResultLauncher(requireActivity().activityResultRegistry,
object : FollowActivityResultLauncher.OnResultListener {
override fun onArticleDetailResult(position: Int, articleDetailEntity: ArticleDetailEntity) {
val item = adapter.currentList[position]
if (item is FollowDynamicPersonalItem) {
item.data.apply {
val newCount = count
newCount.vote = articleDetailEntity.count.vote
newCount.comment = articleDetailEntity.count.comment
count = newCount
title = articleDetailEntity.title
brief = articleDetailEntity.content.removeVideoContent().removeInsertedContent()
.clearHtmlFormatCompletely()
me.isCommunityArticleVote = articleDetailEntity.me.isCommunityArticleVote
}
}
if (item is FollowDynamicBbsItem) {
item.data.apply {
val newCount = count
newCount.vote = articleDetailEntity.count.vote
newCount.comment = articleDetailEntity.count.comment
count = newCount
title = articleDetailEntity.title
brief = articleDetailEntity.content.removeVideoContent().removeInsertedContent()
.clearHtmlFormatCompletely()
me.isCommunityArticleVote = articleDetailEntity.me.isCommunityArticleVote
}
}
adapter.notifyItemChanged(position)
}
override fun onCommentResult(position: Int, commentEntity: CommentEntity) {
val item = adapter.currentList[position]
if (item is FollowDynamicPersonalItem) {
item.data.apply {
val newCount = count
newCount.vote = commentEntity.vote
newCount.comment = commentEntity.reply
count = newCount
brief = commentEntity.content ?: ""
me.isAnswerVoted = commentEntity.me?.isCommentVoted ?: false
}
}
if (item is FollowDynamicBbsItem) {
item.data.apply {
val newCount = count
newCount.vote = commentEntity.vote
newCount.comment = commentEntity.reply
count = newCount
brief = commentEntity.content ?: ""
me.isAnswerVoted = commentEntity.me?.isCommentVoted ?: false
}
}
adapter.notifyItemChanged(position)
}
override fun onQuestionsDetailResult(position: Int, questionsDetailEntity: QuestionsDetailEntity) {
val item = adapter.currentList[position]
if (item is FollowDynamicPersonalItem) {
item.data.apply {
val newCount = count
newCount.answer = questionsDetailEntity.count.answer - count.reply
count = newCount
title = questionsDetailEntity.title ?: ""
}
}
if (item is FollowDynamicBbsItem) {
item.data.apply {
val newCount = count
newCount.answer = questionsDetailEntity.count.answer - count.reply
count = newCount
title = questionsDetailEntity.title ?: ""
}
}
adapter.notifyItemChanged(position)
}
override fun onForumVideoResult(position: Int, forumVideoEntity: ForumVideoEntity) {
val item = adapter.currentList[position]
if (item is FollowDynamicPersonalItem) {
item.data.apply {
val newCount = count
newCount.vote = forumVideoEntity.count.vote
newCount.comment = forumVideoEntity.count.comment
count = newCount
des = forumVideoEntity.des
title = forumVideoEntity.title
me.isVoted = forumVideoEntity.me.isVoted
}
}
if (item is FollowDynamicBbsItem) {
item.data.apply {
val newCount = count
newCount.vote = forumVideoEntity.count.vote
newCount.comment = forumVideoEntity.count.comment
count = newCount
des = forumVideoEntity.des
title = forumVideoEntity.title
me.isVoted = forumVideoEntity.me.isVoted
}
}
adapter.notifyItemChanged(position)
}
})
viewLifecycleOwner.lifecycle.addObserver(launcher)
binding.rvDynamic.layoutManager = LinearLayoutManager(requireContext())
binding.rvDynamic.itemAnimator = null
binding.rvDynamic.addItemDecoration(FollowItemDecoration(paint))
binding.rvDynamic.adapter = adapter
binding.srlRefresh.setOnRefreshListener {
viewModel.loadFirst(userId, bbsId, true)
}
binding.ivClose.setOnClickListener {
followDynamicViewModel.finish()
}
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
context?.let {
binding.vHeaderBackground.background = R.drawable.background_shape_white_radius_8_top_only.toDrawable(it)
}
binding.rvDynamic.tryToClearRecycler()
binding.rvDynamic.recycledViewPool.clear()
adapter.notifyItemRangeChanged(0, adapter.itemCount)
paint.color = R.color.ui_divider.toColor(requireContext())
}
private class FollowItemDecoration(
private val paint: Paint
) : RecyclerView.ItemDecoration() {
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDrawOver(c, parent, state)
parent.children.forEach { child ->
val bottom = child.bottom
c.drawLine(
16F.dip2px().toFloat(),
bottom.toFloat(),
(child.width - 16F.dip2px()).toFloat(),
bottom.toFloat(),
paint
)
}
}
}
companion object {
private const val KEY_USER_ID = "key_user_id"
private const val KEY_BBS_ID = "key_bbs_id"
private const val KEY_BBS_TYPE = "key_bbs_type"
fun newInstance(userId: String, bbsId: String, bbsType: String): FollowDynamicListFragment {
val fragment = FollowDynamicListFragment()
val bundle = Bundle().apply {
putString(KEY_USER_ID, userId)
putString(KEY_BBS_ID, bbsId)
putString(KEY_BBS_TYPE, bbsType)
}
fragment.arguments = bundle
return fragment
}
}
}

View File

@ -0,0 +1,550 @@
package com.gh.gamecenter.forum.home.follow.fragment
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.Rect
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.View
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContract
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.children
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.OnScrollListener
import com.alibaba.android.arouter.launcher.ARouter
import com.gh.common.util.CheckLoginUtils
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.ShareCardActivity
import com.gh.gamecenter.ShareCardPicActivity
import com.gh.gamecenter.common.base.fragment.LazyFragment
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.iinterface.IScrollable
import com.gh.gamecenter.core.provider.IVisitManagerProvider
import com.gh.gamecenter.core.utils.MD5Utils
import com.gh.gamecenter.databinding.FragmentFollowHomeBinding
import com.gh.gamecenter.entity.FollowUserEntity
import com.gh.gamecenter.eventbus.EBUserFollow
import com.gh.gamecenter.feature.provider.IMessageDetailProvider
import com.gh.gamecenter.forum.home.CommunityHomeFragment
import com.gh.gamecenter.forum.home.CommunityHomeViewModel
import com.gh.gamecenter.forum.home.ForumScrollCalculatorHelper
import com.gh.gamecenter.forum.home.follow.*
import com.gh.gamecenter.forum.home.follow.adapter.FollowHomeAdapter
import com.gh.gamecenter.forum.home.follow.fragment.AllFollowedFragment.Companion.KEY_HAS_READ_ID
import com.gh.gamecenter.forum.home.follow.model.FollowRepository.Companion.FOLLOW_UPDATE_TYPE_GAME
import com.gh.gamecenter.forum.home.follow.model.FollowRepository.Companion.FOLLOW_UPDATE_TYPE_USER
import com.gh.gamecenter.forum.home.follow.viewholder.FollowHomeHeaderViewHolder
import com.gh.gamecenter.forum.home.follow.viewholder.FollowRecommendListViewHolder
import com.gh.gamecenter.forum.home.follow.viewmodel.FollowHomeViewModel
import com.gh.gamecenter.game.commoncollection.detail.CustomCommonCollectionDetailActivity
import com.gh.gamecenter.libao.LibaoDetailActivity
import com.gh.gamecenter.livedata.EventObserver
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.login.user.UserViewModel
import com.gh.gamecenter.message.view.concern.ConcernFragment
import com.gh.gamecenter.newsdetail.NewsShareDialog
import com.shuyu.gsyvideoplayer.video.base.GSYVideoView
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class FollowHomeFragment : LazyFragment(), IScrollable {
override fun addSyncPageObserver(): Boolean {
return true
}
private val viewModel by viewModels<FollowHomeViewModel>()
private val communityHomeViewModel by viewModels<CommunityHomeViewModel>(
ownerProducer = { requireParentFragment() }
)
private lateinit var userViewModel: UserViewModel
private var userId: String? = null
private var dynamicType: String? = null
private var binding: FragmentFollowHomeBinding? = null
private var hasLoaded = false
private lateinit var pageConfiguration: FollowPageConfiguration
private var isFirstTimeEnter = true
private val handler = Handler(Looper.getMainLooper())
private val dividerPaint by lazy {
Paint().apply {
color = R.color.ui_divider.toColor(requireContext())
strokeWidth = 0.5F.dip2px().toFloat()
}
}
private val rvBackgroundPaint by lazy {
Paint().apply {
color = R.color.ui_surface.toColor(requireContext())
}
}
private val layoutManager by lazy {
LinearLayoutManager(context)
}
private val adapter: FollowHomeAdapter by lazy {
FollowHomeAdapter(viewLifecycleOwner, viewModel, pageConfiguration)
}
// 3s 以后才开始计算
private var enterStartTime = 0L
private lateinit var allFollowLauncher: ActivityResultLauncher<Unit>
private var mScrollCalculatorHelper: ForumScrollCalculatorHelper? = null
private val headerViewHolder by lazy(LazyThreadSafetyMode.NONE) {
binding?.let {
FollowHomeHeaderViewHolder(it.layoutHeader, object : FollowHomeHeaderViewHolder.OnEventListener {
override fun onItemClick(position: Int, data: List<FollowUserEntity>) {
viewModel.viewItemFollowed(position, data)
}
override fun onAllClick() {
viewModel.viewAllFollowed()
}
override fun onLoginClick() {
viewModel.login()
}
})
}
}
override fun provideSyncAdapter(): RecyclerView.Adapter<*> {
return adapter
}
override fun getRealLayoutId(): Int = R.layout.fragment_follow_home
override fun onRealLayoutInflated(inflatedView: View) {
binding = FragmentFollowHomeBinding.bind(inflatedView)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
pageConfiguration = FollowPageConfiguration("社区-关注", "")
allFollowLauncher = requireActivity().activityResultRegistry.register(
KEY_LAUNCH_ALL_FOLLOW,
this,
object : ActivityResultContract<Unit, List<String>?>() {
override fun createIntent(context: Context, input: Unit?): Intent {
return AllFollowedActivity.getIntent(context)
}
override fun parseResult(resultCode: Int, intent: Intent?): List<String>? {
if (resultCode == Activity.RESULT_OK) {
val hasReadIds = intent?.getStringArrayListExtra(KEY_HAS_READ_ID)
return hasReadIds ?: listOf()
}
return null
}
}
) { value ->
value?.let {
if (it.isEmpty()) {
viewModel.loadFollowedUsers()
} else {
adapter.notifyFollowUserReadStatus(it)
}
}
}
viewModel.hasFollowedUser.observe(this, Observer {
communityHomeViewModel.updateHasFollowUser(it)
})
}
override fun initRealView() {
super.initRealView()
mScrollCalculatorHelper = ForumScrollCalculatorHelper(R.id.horizontalVideoView, R.id.verticalVideoView, 0)
binding?.recyclerView?.itemAnimator = null
userViewModel = viewModelProvider(UserViewModel.Factory(requireActivity().application))
userViewModel.loginObsUserinfo.observe(viewLifecycleOwner) {
/* 由于其它页面每次初始化 userViewModel时loginObsUserInfo都会被赋值一次为避免重复加载这里需要过滤重复的用户信息 */
if (hasLoaded && userId == it?.data?.userId) {
// 已经加载过数据并且userId不变不需要重复加载
return@observe
}
hasLoaded = true
if (it?.data == null) {
// 未登录,加载推荐关注列表
userId = null
viewModel.loadFirst(userId != null, isPullToRefresh = false)
} else {
// 已登录,加载已关注列表
userId = it.data.userId
if (userId != null) {
viewModel.loadFirst(userId != null, isPullToRefresh = false)
}
}
}
communityHomeViewModel.filterFollowedAction.observe(viewLifecycleOwner, EventObserver {
val newDynamicType = when (it) {
0 -> ""
1 -> FOLLOW_UPDATE_TYPE_USER
else -> FOLLOW_UPDATE_TYPE_GAME
}
if (dynamicType != newDynamicType) {
dynamicType = newDynamicType
viewModel.loadFirst(userId != null, dynamicType ?: "", false)
}
})
with(viewModel) {
loadStatus.observe(viewLifecycleOwner) { (loadStatus, isPullRefresh) ->
if (isPullRefresh) {
if (loadStatus != LoadStatus.INIT_LOADING) {
binding?.srlRefresh?.isRefreshing = false
}
} else {
binding?.layoutLoading?.root?.goneIf(loadStatus != LoadStatus.INIT_LOADING)
}
adapter.setLoadStatus(loadStatus)
if (loadStatus == LoadStatus.INIT_LOADED) {
AppExecutor.uiExecutor.executeWithDelay(Runnable {
tryCatchInRelease {
scroll()
mScrollCalculatorHelper?.onScrollStateChanged(
binding?.recyclerView!!,
RecyclerView.SCROLL_STATE_IDLE
)
}
}, 100)
}
}
dataList.observe(viewLifecycleOwner) {
if (it.size == 1) {
// 由于第一个是头部如果list长度为1则说明后面没有数据
binding?.llEmptyContainer?.goneIf(false)
binding?.recyclerView?.goneIf(true)
val headerItem = it.firstOrNull() as? FollowUserItem
if (headerItem != null) {
headerViewHolder?.bind(headerItem.isLogin, headerItem.data)
setEmptyLayout(headerItem)
}
} else {
binding?.llEmptyContainer?.goneIf(true)
binding?.recyclerView?.goneIf(false)
setDataList(it)
}
}
followedDetailDestination.observe(viewLifecycleOwner, EventObserver { (position, data) ->
FollowDynamicActivity.start(requireContext(), position, data)
activity?.overridePendingTransition(0, 0) // 取消进入和退出动画
})
myFollowedListDestination.observe(viewLifecycleOwner, EventObserver {
allFollowLauncher.launch(Unit)
})
loginDestination.observe(viewLifecycleOwner, EventObserver {
CheckLoginUtils.checkLogin(context, "关注", null)
})
gameDetailDestination.observe(viewLifecycleOwner, EventObserver {
GameDetailActivity.startGameDetailActivity(
requireContext(),
it.id,
"",
-1,
traceEvent = it.exposureEvent
)
})
linkDestination.observe(viewLifecycleOwner, EventObserver { (link, exposureEvent) ->
DirectUtils.directToLinkPage(requireContext(), link, "关注页面", "", exposureEvent)
})
commonCollectionDestination.observe(viewLifecycleOwner, EventObserver {
val intent = CustomCommonCollectionDetailActivity.getIntent(
requireContext(),
it.id,
it.layout,
"自定义页面",
)
startActivity(intent)
})
userHomeDestination.observe(viewLifecycleOwner, EventObserver {
DirectUtils.directToHomeActivity(requireContext(), it, 1, "", "")
})
libaoDetailDestination.observe(viewLifecycleOwner, EventObserver {
val intent = LibaoDetailActivity.getIntent(requireContext(), it, false, "$mEntrance+关注列表")
startActivity(intent)
})
newDetailDestination.observe(viewLifecycleOwner, EventObserver {
ARouter.getInstance().build(RouteConsts.activity.newsDetailActivity)
.withString(EntranceConsts.KEY_NEWSID, it)
.withString(EntranceConsts.KEY_ENTRANCE, "")
.navigation(requireActivity(), ConcernFragment.NEWS_MESSAGE_ARTICLE_REQUEST)
})
articleCommentDetailDestination.observe(viewLifecycleOwner, EventObserver {
val messageDetailProvider = ARouter.getInstance().build(RouteConsts.provider.messageDetail)
.navigation() as? IMessageDetailProvider
if (messageDetailProvider != null) {
val intent = messageDetailProvider.getIntentById(requireContext(), it, -1, false, "")
startActivityForResult(intent, ConcernFragment.NEWS_MESSAGE_ARTICLE_REQUEST)
}
})
shareArticleDestination.observe(viewLifecycleOwner, EventObserver { concernEntity ->
NewsShareDialog.show(
requireActivity() as AppCompatActivity,
concernEntity.shortId,
concernEntity.id,
concernEntity.gameIcon,
concernEntity.title
)
})
copyExchangeCodeAction.observe(viewLifecycleOwner, EventObserver {
it.copyTextAndToast()
})
updateOkhttpCacheAction.observe(viewLifecycleOwner, EventObserver {
// 更新okhttp缓存数据
val visitManagerProvider =
ARouter.getInstance()
.build(RouteConsts.provider.visitManager)
.navigation() as? IVisitManagerProvider
visitManagerProvider?.updateOkhttpCache(requireContext(), it)
})
}
binding?.srlRefresh?.setOnRefreshListener {
mScrollCalculatorHelper?.currentPlayer?.release()
mScrollCalculatorHelper?.reset()
val isLogin = userId != null
viewModel.loadFirst(isLogin, dynamicType ?: "", true)
}
binding?.recyclerView?.addOnScrollListener(object : OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
binding?.recyclerView?.let {
mScrollCalculatorHelper?.onScrollStateChanged(it, newState)
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
if (dy != 0) scroll()
}
})
}
private fun setEmptyLayout(headerItem: FollowUserItem) {
binding?.layoutEmpty?.run {
root.goneIf(false)
if (headerItem.isLogin) {
reuseNoneDataDescTv.goneIf(true)
} else {
reuseNoneDataDescTv.goneIf(false)
reuseNoneDataDescTv.setText(R.string.follow_home_no_login_tips)
}
}
}
private fun scroll() {
val firstVisibleItem = layoutManager.findFirstVisibleItemPosition()
val lastVisibleItem = layoutManager.findLastVisibleItemPosition()
mScrollCalculatorHelper?.onScroll(viewModel.videoList, firstVisibleItem, lastVisibleItem)
}
override fun onFragmentResume() {
resumeVideo()
super.onFragmentResume()
handler.postDelayed({
enterStartTime = System.currentTimeMillis()
SensorsBridge.trackFollowTabBrowse(isFirstTimeEnter)
}, TRACK_ENTER_TIME)
}
override fun onFragmentPause() {
super.onFragmentPause()
pauseVideo()
isFirstTimeEnter = false
handler.removeCallbacksAndMessages(null)
if (enterStartTime != 0L) {
val endTime = System.currentTimeMillis()
val stayLength = "${(endTime - enterStartTime) / MS_UNIT}"
SensorsBridge.trackFollowTabBrowseDuration(stayLength)
}
enterStartTime = 0L
}
private fun resumeVideo() {
mScrollCalculatorHelper?.run {
if (currentPlayer != null
&& currentPosition >= 0
&& currentPosition < viewModel.videoList.size
) {
val video = viewModel.videoList.safelyGetInRelease(currentPosition)
if (video != null) {
val position = ForumScrollCalculatorHelper.getPlaySchedule(MD5Utils.getContentMD5(video.url))
//这里必须要延迟操作,否则会白屏
mBaseHandler.postDelayed({
tryCatchInRelease {
if (position != 0L) {
if (currentPlayer?.currentState == GSYVideoView.CURRENT_STATE_PAUSE) {
currentPlayer?.startPlayLogic(true)
}
} else {
currentPlayer?.release()
}
}
}, 50)
}
}
}
}
private fun pauseVideo() {
mScrollCalculatorHelper?.run {
if (currentPlayer != null
&& currentPosition >= 0
&& currentPosition < viewModel.videoList.size
) {
currentPlayer?.onVideoPause()
val position = currentPlayer?.getCurrentPosition() ?: 0L
val video = viewModel.videoList.safelyGetInRelease(currentPosition)
if (video != null) {
ForumScrollCalculatorHelper.savePlaySchedule(MD5Utils.getContentMD5(video.url), position)
}
}
}
}
override fun onDestroy() {
super.onDestroy()
mScrollCalculatorHelper?.currentPlayer?.release()
binding = null
}
private fun setDataList(dataList: List<FollowItem>) {
binding?.run {
if (recyclerView.adapter == null) {
recyclerView.layoutManager = layoutManager
recyclerView.addItemDecoration(FollowItemDecoration(dividerPaint, rvBackgroundPaint))
recyclerView.adapter = adapter
}
adapter.submitList(dataList)
}
}
override fun scrollToTop() {
binding?.recyclerView?.scrollToPosition(0)
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onUserFollow(change: EBUserFollow) {
adapter.notifyFollowUserChanged(change)
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
val rvFollows = binding?.recyclerView
if (rvFollows != null) {
if (parentFragment is CommunityHomeFragment) {
(parentFragment as CommunityHomeFragment).translateTopBg(rvFollows.computeVerticalScrollOffset() - 8F.dip2px())
}
rvFollows.tryToClearRecycler()
rvFollows.recycledViewPool.clear()
adapter.notifyItemRangeChanged(0, adapter.itemCount)
}
dividerPaint.color = R.color.ui_divider.toColor(requireContext())
rvBackgroundPaint.color = R.color.ui_surface.toColor(requireContext())
}
private class FollowItemDecoration(
private val dividerPaint: Paint,
private val backgroundPaint: Paint
) : RecyclerView.ItemDecoration() {
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDrawOver(c, parent, state)
parent.children.forEach { child ->
val position = parent.getChildAdapterPosition(child)
// 1.第一个itemView为头部关注人列表不需要分割线
// 2.默认推荐数据不需要分割线
val isLoggedIn = UserManager.getInstance().isLoggedIn
if (isLoggedIn && position > 0) {
val bottom = child.bottom
c.drawLine(
16F.dip2px().toFloat(),
bottom.toFloat(),
(child.width - 16F.dip2px()).toFloat(),
bottom.toFloat(),
dividerPaint
)
}
}
}
/**
* 由于 关注用户/论坛 列表需要和顶部 tab连在一起组成渐变色所以RecyclerView不能直接设置背景颜色
* 在这里绘制除了 关注用户/论坛 列表其它位置的背景颜色
* + 12 是为了预留出圆角的高度
*/
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDraw(c, parent, state)
val firstChild = parent.children.firstOrNull()
if (firstChild != null) {
c.drawRect(Rect(0, firstChild.bottom + 12F.dip2px(), parent.width, parent.bottom), backgroundPaint)
}
}
}
companion object {
private const val KEY_LAUNCH_ALL_FOLLOW = "key_launch_all_follow"
const val LOCATION_FOLLOW_PAGE = "关注页面"
private const val TRACK_ENTER_TIME = 3000L
private const val MS_UNIT = 1000.0
}
}

View File

@ -0,0 +1,42 @@
package com.gh.gamecenter.forum.home.follow.model
import com.gh.common.util.PackageUtils
import com.gh.gamecenter.common.utils.toRequestBody
import com.gh.gamecenter.entity.FollowOperateTopRequest
import com.gh.gamecenter.forum.home.follow.AllFollowedNormalItem
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.retrofit.service.ApiService
import com.halo.assistant.HaloApp
import io.reactivex.Single
import okhttp3.ResponseBody
class AllFollowRepository(private val apiService: ApiService) {
fun loadMyFollowedUser(pageNo: Int) =
apiService.getMyFollowedUsers(
UserManager.getInstance().userId,
PackageUtils.getGhVersionName(),
HaloApp.getInstance().channel,
pageNo,
true,
mapOf()
)
.map { list ->
list.map {
AllFollowedNormalItem(it, it.isTop)
}
}
fun operateTop(tops: List<FollowOperateTopRequest>): Single<ResponseBody> {
val body = mapOf("top" to tops).toRequestBody()
return apiService.operateTop(
UserManager.getInstance().userId,
PackageUtils.getGhVersionName(),
HaloApp.getInstance().channel,
body
)
}
fun postRead(type: String, typeId: String) =
apiService.postRead(UserManager.getInstance().userId, type, typeId)
}

View File

@ -0,0 +1,47 @@
package com.gh.gamecenter.forum.home.follow.model
import com.gh.common.util.PackageUtils
import com.gh.gamecenter.forum.home.follow.FollowDynamicBbsItem
import com.gh.gamecenter.forum.home.follow.FollowDynamicPersonalItem
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.retrofit.service.ApiService
import com.halo.assistant.HaloApp
class FollowDynamicRepository(
private val apiService: ApiService
) {
fun loadData(userId: String, bbsId: String, pageNo: Int) =
if (userId.isNotBlank()) {
loadPersonDynamic(userId, pageNo)
} else {
loadBbsDynamic(bbsId, pageNo)
}
private fun loadPersonDynamic(userId: String, pageNo: Int) =
apiService.getPersonalHistory(
userId,
pageNo,
HaloApp.getInstance().channel,
PERSON_DYNAMIC_FILTER
).map {
it.map(::FollowDynamicPersonalItem)
}
private fun loadBbsDynamic(bbsId: String, pageNo: Int) =
apiService.getAllForumList(bbsId, BBS_DYNAMIC_SORT, pageNo, mapOf())
.map {
it.map(::FollowDynamicBbsItem)
}
companion object {
private const val PERSON_DYNAMIC_FILTER = "scene:follow_personal,type:all"
private const val BBS_DYNAMIC_SORT = "time.reply:-1&type=community_article|question|video"
fun newInstance(): FollowDynamicRepository = FollowDynamicRepository(
RetrofitManager.getInstance().api
)
}
}

View File

@ -0,0 +1,204 @@
package com.gh.gamecenter.forum.home.follow.model
import com.gh.common.util.LibaoUtils
import com.gh.common.util.PackageUtils
import com.gh.gamecenter.core.utils.GsonUtils
import com.gh.gamecenter.core.utils.UrlFilterUtils
import com.gh.gamecenter.entity.FollowCommonContentCollection
import com.gh.gamecenter.entity.FollowDynamicEntity
import com.gh.gamecenter.entity.FollowDynamicEntity.Companion.FOLLOW_UPDATE_TYPE_ARTICLE
import com.gh.gamecenter.entity.FollowDynamicEntity.Companion.FOLLOW_UPDATE_TYPE_LIBAO
import com.gh.gamecenter.entity.FollowDynamicEntity.Companion.FOLLOW_UPDATE_TYPE_LIBAO_EXCHANGE
import com.gh.gamecenter.entity.FollowDynamicEntity.Companion.FOLLOW_UPDATE_TYPE_USER_POST
import com.gh.gamecenter.entity.FollowUserEntity
import com.gh.gamecenter.feature.entity.LibaoEntity
import com.gh.gamecenter.forum.home.follow.*
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_BANNER
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_VERTICAL_CARD
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_VERTICAL_IMAGE_TEXT
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.retrofit.service.ApiService
import com.halo.assistant.HaloApp
import io.reactivex.Single
class FollowRepository(
private val apiService: ApiService,
private val newApiService: ApiService
) {
private var hasFollowUser = false
private val version: String
get() = PackageUtils.getGhVersionName()
private val channel: String
get() = HaloApp.getInstance().channel
fun loadHomeData(isLogin: Boolean, type: String, pageNo: Int) =
if (isLogin) {
loadFollowedUsers()
.flatMap { users ->
if (users.isEmpty()) {
hasFollowUser = false
// 默认推荐
loadFollowRecommend(pageNo)
.map {
listOf(FollowUserItem(true, listOf())) + it
}
} else {
// 关注动态
hasFollowUser = true
loadFollowUpdates(type, pageNo)
.map {
listOf(FollowUserItem(true, users)) + it
}
}
}
} else {
loadFollowRecommend(pageNo)
.map {
listOf(FollowUserItem(false, listOf())) + it
}
}
fun loadMore(type: String, pageNo: Int) =
if (hasFollowUser) {
loadFollowUpdates(type, pageNo)
} else {
loadFollowRecommend(pageNo)
}
fun loadFollowedUsers(): Single<List<FollowUserEntity>> {
return newApiService.getMyFollowedUsers(
UserManager.getInstance().userId,
PackageUtils.getGhVersionName(),
HaloApp.getInstance().channel,
1,
true,
mapOf("page_size" to 20)
).onErrorReturnItem(listOf())
}
private fun loadFollowUpdates(type: String, pageNo: Int): Single<List<FollowItem>> =
newApiService.getFollowUpdates(
UserManager.getInstance().userId,
type,
pageNo,
PackageUtils.getGhVersionName(),
HaloApp.getInstance().channel
)
.onErrorReturnItem(listOf())
.map(::transformFollowUpdatesToItemData)
fun loadFollowRecommend(pageNo: Int): Single<List<FollowItem>> =
if (pageNo == 1) {
loadRecommendUsers().zipWith(
loadFollowCommonCollection(pageNo)
) { user, commonCollections ->
val data = arrayListOf<FollowItem>()
if (user.data.isNotEmpty()) {
data.add(user)
}
data.addAll(commonCollections)
data
}
} else {
Single.just(listOf())
}
private fun loadRecommendUsers(): Single<FollowRecommendUsersItem> =
newApiService.getRecommendUser(version, channel)
.onErrorReturnItem(listOf())
.map(::FollowRecommendUsersItem)
private fun loadFollowCommonCollection(pageNo: Int): Single<List<FollowItem>> =
newApiService.getFollowCommonCollection(version, channel, pageNo)
.onErrorReturnItem(listOf())
.map { components ->
val data = arrayListOf<FollowItem>()
components.forEach {
val layout = it.linkCommonCollection?.layout
if (layout == COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_BANNER
|| layout == COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_VERTICAL_CARD
|| layout == COMMON_CONTENT_COLLECTION_LAYOUT_VERTICAL_IMAGE_TEXT
) {
data.addAll(convertSplitCommonContentCollection(it))
} else {
// data.title 为后台配置的板块title
it.linkCommonCollection.name = it.title
data.add(
FollowCommonCollectionItem(it)
)
}
}
data
}
private fun convertSplitCommonContentCollection(item: FollowCommonContentCollection): List<FollowSplitCommonContentCollectionItem> {
val linkCommonCollection = item.linkCommonCollection
// data.title 为后台配置的板块title
linkCommonCollection.name = item.title
val data = linkCommonCollection.data
val items = arrayListOf<FollowSplitCommonContentCollectionItem>()
(data.indices step 2).forEach { left ->
items.add(
FollowSplitCommonContentCollectionItem(
linkCommonCollection,
left
)
)
}
return items
}
fun followUser(userId: String, isFollow: Boolean) =
if (isFollow) {
apiService.postFollowing(userId)
} else {
apiService.deleteFollowing(userId)
}
fun postArticleVisit(articleId: String) =
apiService.postArticleVisit(articleId)
companion object {
const val FOLLOW_UPDATE_TYPE_USER = "user"
const val FOLLOW_UPDATE_TYPE_GAME = "game"
fun transformFollowUpdatesToItemData(updates: List<FollowDynamicEntity>): List<FollowItem> {
val data = arrayListOf<FollowItem>()
updates.forEach {
when (it.type) {
FOLLOW_UPDATE_TYPE_LIBAO -> {
if (it.libao != null) {
FollowGiftPackItem(it.libao, it.time)
} else {
null
}
}
FOLLOW_UPDATE_TYPE_LIBAO_EXCHANGE -> {
if (it.libaoExchange != null) {
FollowGiftPackItem(it.libaoExchange, it.time)
} else {
null
}
}
FOLLOW_UPDATE_TYPE_ARTICLE -> it.article?.let(::FollowArticleItem)
FOLLOW_UPDATE_TYPE_USER_POST -> it.userPost?.let(::FollowPostCardItem)
else -> null
}?.let(data::add)
}
return data
}
fun newInstance() = FollowRepository(RetrofitManager.getInstance().api, RetrofitManager.getInstance().newApi)
}
}

View File

@ -0,0 +1,85 @@
package com.gh.gamecenter.forum.home.follow.viewholder
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.databinding.CommonCollection12ItemCustomBinding
import com.gh.gamecenter.entity.CommonCollectionContentEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.ui.CommonContentCollection12Ui
/**
* 关注-双列banner 双列竖式卡片 竖排图文列表
* 通用链接合集(1-2样式)
*/
class FollowCommonCollection12ViewHolder(
val binding: CommonCollection12ItemCustomBinding,
private val listener: OnChildEventListener
) :
ViewHolder(binding.root) {
private lateinit var _data: CustomPageData.CommonContentCollection
private val collection12Ui by lazy {
CommonContentCollection12Ui(binding, object : CommonContentCollection12Ui.OnCollection12Listener {
override fun onChildItemClick(childPosition: Int, contentEntity: CommonCollectionContentEntity) {
val linkEntity = contentEntity.linkEntity
NewLogUtils.logCommonCollectionClick(
_data.id,
_data.name,
"",
"",
"关注列表",
"关注首页",
linkEntity.title ?: "",
contentEntity.addedContent1 ?: "",
contentEntity.addedContent2 ?: "",
linkEntity.type ?: "",
linkEntity.text ?: "",
childPosition + 1
)
NewLogUtils.logCommonCategoryHomeContentClick(
contentEntity.title,
linkEntity.type ?: "",
linkEntity.link ?: "",
linkEntity.text ?: "",
_data.name,
_data.id,
"关注",
""
)
listener.navigateToLinkPage(bindingAdapterPosition, linkEntity, _data, "内容卡片", null)
}
override fun navigateToCommonCollectionDetailPage(collection: CustomPageData.CommonContentCollection) {
listener.navigateToCommonCollectionDetailPage(collection)
}
})
}
fun bind(data: CustomPageData.CommonContentCollection, leftPosition: Int) {
_data = data
collection12Ui.bind(data, leftPosition)
itemView.setBackgroundColor(R.color.ui_surface.toColor(itemView.context))
}
interface OnChildEventListener {
fun navigateToLinkPage(
position: Int,
link: LinkEntity,
data: CustomPageData.CommonContentCollection,
text: String,
exposureEvent: ExposureEvent?
)
fun navigateToCommonCollectionDetailPage(data: CustomPageData.CommonContentCollection)
}
}

View File

@ -0,0 +1,105 @@
package com.gh.gamecenter.forum.home.follow.viewholder
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.common.exposure.ExposureManager
import com.gh.common.exposure.ExposureTraceUtils
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.databinding.CommonCollectionListCustomBinding
import com.gh.gamecenter.entity.CommonCollectionContentEntity
import com.gh.gamecenter.entity.ExposureLinkEntity
import com.gh.gamecenter.entity.FollowCommonContentCollection
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.ExposureType
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.ui.CommonContentHorizontalSlideListUi
/**
* 关注-通用内容合集-横排滑动Banner
* 关注-通用内容合集-横排竖式卡片
* 关注-通用内容合集-横排图文列表
*/
class FollowCommonCollectionViewHolder(
val binding: CommonCollectionListCustomBinding,
private val listener: OnChildEventListener
) : ViewHolder(binding.root) {
private lateinit var _data: FollowCommonContentCollection
private val horizontalSlideUi by lazy {
CommonContentHorizontalSlideListUi(binding,
object : CommonContentHorizontalSlideListUi.OnHorizontalSlideListListener {
override fun addExposureEvent(childPosition: Int, link: ExposureLinkEntity) = Unit
override fun onChildItemClick(childPosition: Int, entity: CommonCollectionContentEntity) {
val linkEntity = entity.linkEntity
NewLogUtils.logCommonCollectionClick(
_data.linkCommonCollection.id,
_data.linkCommonCollection.name,
"",
"",
"关注推荐列表",
"关注页",
linkEntity.title ?: "",
entity.addedContent1 ?: "",
entity.addedContent2 ?: "",
linkEntity.type ?: "",
linkEntity.text ?: "",
childPosition + 1
)
NewLogUtils.logCommonCategoryHomeContentClick(
entity.title,
linkEntity.type ?: "",
linkEntity.link ?: "",
linkEntity.text ?: "",
_data.linkCommonCollection.name,
_data.linkCommonCollection.id,
"",
""
)
linkEntity.exposureEvent?.let {
val clickEvent = ExposureEvent(
payload = it.payload,
source = it.source,
eTrace = ExposureTraceUtils.appendTrace(it),
event = ExposureType.CLICK
)
if (linkEntity.type != "game") {
ExposureManager.log(clickEvent)
}
}
listener.navigateToLinkPage(bindingAdapterPosition, linkEntity, _data.linkCommonCollection, "内容卡片", null)
}
override fun navigateToCommonCollectionDetailPage(data: CustomPageData.CommonContentCollection) {
listener.navigateToCommonCollectionDetailPage(data)
}
})
}
fun bindView(data: FollowCommonContentCollection) {
_data = data
horizontalSlideUi.bind(_data.linkCommonCollection)
itemView.setBackgroundColor(R.color.ui_surface.toColor(itemView.context))
}
interface OnChildEventListener {
fun navigateToLinkPage(
position: Int,
link: LinkEntity,
data: CustomPageData.CommonContentCollection,
text: String,
exposureEvent: ExposureEvent?
)
fun navigateToCommonCollectionDetailPage(data: CustomPageData.CommonContentCollection)
}
}

View File

@ -0,0 +1,149 @@
package com.gh.gamecenter.forum.home.follow.viewholder
import android.view.View
import android.widget.ProgressBar
import android.widget.TextView
import androidx.annotation.StringRes
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.R
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.baselist.ListViewModel
import com.gh.gamecenter.common.baselist.LoadType
import com.gh.gamecenter.common.databinding.RefreshFooterviewBinding
class FollowFooterViewHolder(val binding: RefreshFooterviewBinding) : RecyclerView.ViewHolder(binding.root) {
val loading: ProgressBar
val hint: TextView
init {
loading = itemView.findViewById(R.id.footerview_loading)
hint = itemView.findViewById(R.id.footerview_hint)
}
fun initItemPadding() {
itemView.setPadding(0, 0, 0, 0)
}
fun initFooterViewHolder(
isNetworkError: Boolean,
isOver: Boolean,
@StringRes loadOverHint: Int
) {
if (isNetworkError) {
loading.visibility = View.GONE
hint.setText(R.string.loading_failed_retry)
} else if (isOver) {
loading.visibility = View.GONE
hint.setText(loadOverHint)
} else {
loading.visibility = View.VISIBLE
hint.setText(R.string.loading)
}
}
fun initFooterViewHolder(
viewModel: ListViewModel<*, *>,
isLoading: Boolean,
isNetworkError: Boolean,
isOver: Boolean
) {
initFooterViewHolder(
isLoading,
isNetworkError,
isOver,
R.string.load_over_hint
) { v: View? -> if (isNetworkError) viewModel.load(LoadType.RETRY) }
}
fun bindFooterDefaultEmpty(
viewModel: ListViewModel<*, *>,
isLoading: Boolean,
isNetworkError: Boolean,
isOver: Boolean
) {
if (isNetworkError) {
loading.visibility = View.GONE
hint.visibility = View.VISIBLE
hint.setText(R.string.loading_failed_retry)
} else if (isOver) {
loading.visibility = View.GONE
hint.visibility = View.VISIBLE
hint.setText(R.string.load_over_hint)
} else if (isLoading) {
loading.visibility = View.VISIBLE
hint.visibility = View.VISIBLE
hint.setText(R.string.loading)
} else {
loading.visibility = View.GONE
hint.visibility = View.GONE
}
itemView.setOnClickListener { v: View? -> if (isNetworkError) viewModel.load(LoadType.RETRY) }
}
@JvmOverloads
fun initFooterViewHolder(
isLoading: Boolean,
isNetworkError: Boolean,
isOver: Boolean,
@StringRes loadOverHint: Int = R.string.load_over_hint
) {
BaseActivity.updateStaticView(itemView, ArrayList())
if (isNetworkError) {
loading.visibility = View.GONE
hint.setText(R.string.loading_failed_retry)
} else if (isOver) {
loading.visibility = View.GONE
hint.setText(loadOverHint)
} else if (isLoading) {
loading.visibility = View.VISIBLE
hint.setText(R.string.loading)
} else {
loading.visibility = View.GONE
hint.setText(R.string.loading_more_hint)
}
}
fun initFooterViewHolder(
isLoading: Boolean,
mIsNetworkError: Boolean,
mIsOver: Boolean,
onClickListener: View.OnClickListener?
) {
initFooterViewHolder(
isLoading,
mIsNetworkError,
mIsOver,
R.string.load_over_hint,
onClickListener
)
}
fun initFooterViewHolder(
isLoading: Boolean,
mIsNetworkError: Boolean,
mIsOver: Boolean,
@StringRes loadOverHint: Int,
onClickListener: View.OnClickListener?
) {
if (mIsNetworkError) {
loading.visibility = View.GONE
hint.setText(R.string.loading_failed_retry)
itemView.isClickable = true
itemView.setOnClickListener(onClickListener)
} else if (mIsOver) {
loading.visibility = View.GONE
hint.setText(loadOverHint)
itemView.isClickable = true
itemView.setOnClickListener(onClickListener)
} else if (isLoading) {
loading.visibility = View.VISIBLE
hint.setText(R.string.loading)
itemView.isClickable = false
} else {
loading.visibility = View.GONE
hint.setText(R.string.loading_more_hint)
itemView.isClickable = false
}
}
}

View File

@ -0,0 +1,8 @@
package com.gh.gamecenter.forum.home.follow.viewholder
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.databinding.RecyclerFollowHomeEmptyBinding
class FollowHomeEmptyViewHolder(val binding: RecyclerFollowHomeEmptyBinding) : ViewHolder(binding.root) {
}

View File

@ -0,0 +1,85 @@
package com.gh.gamecenter.forum.home.follow.viewholder
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.databinding.RecyclerFollowHomeHeaderBinding
import com.gh.gamecenter.entity.FollowUserEntity
import com.gh.gamecenter.forum.home.follow.adapter.FollowedHeaderAdapter
class FollowHomeHeaderViewHolder(
val binding: RecyclerFollowHomeHeaderBinding,
val listener: OnEventListener
) : ViewHolder(binding.root) {
private var data: List<FollowUserEntity> = listOf()
private val followedHeaderAdapter by lazy {
FollowedHeaderAdapter(itemView.context, object : FollowedHeaderAdapter.OnChildEventListener {
override fun onViewItem(position: Int) {
listener.onItemClick(position, data)
}
override fun onViewAll() {
listener.onAllClick()
}
})
}
fun bind(isLogin: Boolean, followedList: List<FollowUserEntity>) {
fun setViewsVisible(tipsGone: Boolean, recyclerGone: Boolean, loginGone: Boolean) {
binding.tvNoDataTips.goneIf(tipsGone)
binding.rvFollowed.goneIf(recyclerGone)
binding.gLogin.goneIf(loginGone)
}
when {
!isLogin -> {
setViewsVisible(tipsGone = true, recyclerGone = true, loginGone = false)
binding.tvLogin.setOnClickListener {
listener.onLoginClick()
}
}
followedList.isEmpty() -> {
setViewsVisible(tipsGone = false, recyclerGone = true, loginGone = true)
}
else -> {
setViewsVisible(tipsGone = true, recyclerGone = false, loginGone = true)
if (binding.rvFollowed.adapter == null) {
binding.rvFollowed.layoutManager =
LinearLayoutManager(itemView.context, RecyclerView.HORIZONTAL, false)
binding.rvFollowed.adapter = followedHeaderAdapter
}
data = followedList
followedHeaderAdapter.submitList(followedList)
}
}
}
fun updateReadStatus(ids: List<String>) {
val oldData = followedHeaderAdapter.dataList
val newData = oldData.toMutableList()
oldData.forEachIndexed { index, followUserEntity ->
if (ids.contains(followUserEntity.id)) {
val newItem = followUserEntity.copy(_isShowTip = 0)
newData[index] = newItem
}
}
data = newData
followedHeaderAdapter.submitList(newData)
}
interface OnEventListener {
fun onItemClick(position: Int, data: List<FollowUserEntity>)
fun onAllClick()
fun onLoginClick()
}
}

View File

@ -0,0 +1,62 @@
package com.gh.gamecenter.forum.home.follow.viewholder
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.databinding.ItemHomeRecommendListCustomBinding
import com.gh.gamecenter.entity.FollowCommonContentCollection
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.ui.CommonContentRecommendUi
/**
* 通用内容合集-金刚区
* 首页推荐入口
*/
class FollowHomeRecommendItemViewHolder(
val binding: ItemHomeRecommendListCustomBinding,
private val listener: OnChildEventListener
) : ViewHolder(binding.root) {
private lateinit var data: FollowCommonContentCollection
private val recommendUi by lazy {
CommonContentRecommendUi(binding, object : CommonContentRecommendUi.OnRecommendListener {
override fun navigateToLinkPage(link: LinkEntity, text: String, exposureEvent: ExposureEvent?) {
listener.navigateToLinkPage(
bindingAdapterPosition,
link,
data.linkCommonCollection,
text,
exposureEvent
)
}
})
}
fun bindView(data: FollowCommonContentCollection) {
this.data = data
val recommends = data.linkCommonCollection.recommends
recommendUi.bind(recommends, listOf())
itemView.setBackgroundColor(R.color.ui_surface.toColor(itemView.context))
}
companion object {
//图标最多列数
private const val MAX_SPAN_COUNT = 5
}
interface OnChildEventListener {
fun navigateToLinkPage(
position: Int,
link: LinkEntity,
data: CustomPageData.CommonContentCollection,
text: String,
exposureEvent: ExposureEvent?
)
}
}

View File

@ -0,0 +1,80 @@
package com.gh.gamecenter.forum.home.follow.viewholder
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.databinding.HomeSlideListCustomBinding
import com.gh.gamecenter.entity.FollowCommonContentCollection
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.home.custom.OnViewHolderAttachListener
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.ui.CommonContentHomeSLideListUi
/**
* 关注-通用内容合集-轮播Banner
*/
class FollowHomeSlideListViewHolder(
val binding: HomeSlideListCustomBinding,
lifecycleOwner: LifecycleOwner,
listener: OnChildEventListener,
) :
ViewHolder(binding.root), OnViewHolderAttachListener {
private lateinit var data: FollowCommonContentCollection
private val slideListUi by lazy {
CommonContentHomeSLideListUi(
binding,
lifecycleOwner,
object : CommonContentHomeSLideListUi.OnCommonHomeSlideListEventListener {
override fun navigateToLinkPage(link: LinkEntity, text: String, exposureEvent: ExposureEvent?) {
listener.navigateToLinkPage(bindingAdapterPosition, link, data.linkCommonCollection, text, exposureEvent)
}
override fun navigateToGameDetailPage(position: Int, game: GameEntity, text: String) {
listener.navigateToGameDetailPage(position, game, text)
}
override fun updateImmersiveColor(color: Int) = Unit
override fun createExposureEvent(childPosition: Int, game: GameEntity?): ExposureEvent? = null
})
}
fun bindView(data: FollowCommonContentCollection) {
this.data = data
val slideList = data.linkCommonCollection.slides.slide
slideListUi.bind(slideList, bindingAdapterPosition + 1)
itemView.setBackgroundColor(R.color.ui_surface.toColor(itemView.context))
}
override fun onViewAttach(parent: RecyclerView?) {
slideListUi.onViewAttach(parent)
}
override fun onViewDetach(parent: RecyclerView?) {
slideListUi.onViewDetach(parent)
}
interface OnChildEventListener {
fun navigateToLinkPage(
position: Int,
link: LinkEntity,
data: CustomPageData.CommonContentCollection,
text: String,
exposureEvent: ExposureEvent?
)
fun navigateToGameDetailPage(position: Int, game: GameEntity, text: String)
}
}

View File

@ -0,0 +1,87 @@
package com.gh.gamecenter.forum.home.follow.viewholder
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.databinding.HomeSlideWithCardsCustomBinding
import com.gh.gamecenter.entity.FollowCommonContentCollection
import com.gh.gamecenter.entity.HomeSubSlide
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.home.custom.CommonContentCollectionUseCase
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.ui.CommonContentHomeSlideWithCardsUi
/**
* 关注-通用内容合集-轮播Banner
*/
class FollowHomeSlideWithCardsViewHolder(
val binding: HomeSlideWithCardsCustomBinding,
private val useCase: CommonContentCollectionUseCase,
lifecycleOwner: LifecycleOwner,
private val listener: OnChildEventListener
) : ViewHolder(binding.root) {
private lateinit var data: FollowCommonContentCollection
private val slideWithCardsUi by lazy {
CommonContentHomeSlideWithCardsUi(
useCase,
lifecycleOwner,
binding,
object : CommonContentHomeSlideWithCardsUi.HomeSLideWithCardsEventListener {
override fun updateImmersiveColor(color: Int) = Unit
override fun createEventWithSourceConcat(game: GameEntity, subSlideId: String) = Unit
override fun createExposureEvent(actualPosition: Int, game: GameEntity?): ExposureEvent? = null
override fun addGameExposureEvent(position: Int, game: GameEntity, subSlideId: String) = Unit
override fun navigateToGameDetailPage(childPosition: Int, gameEntity: GameEntity, text: String) {
listener.navigateToGameDetailPage(childPosition, gameEntity, text)
}
override fun navigateToLinkPageInSubSlide(game: GameEntity, homeSubSlide: HomeSubSlide, text: String) {
listener.navigateToLinkPage(
bindingAdapterPosition,
homeSubSlide.toLinkEntity(),
data.linkCommonCollection,
text,
null
)
}
override fun navigateToLinkPage(link: LinkEntity, text: String, exposureEvent: ExposureEvent?) {
listener.navigateToLinkPage(
bindingAdapterPosition,
link,
data.linkCommonCollection,
text,
exposureEvent
)
}
})
}
fun bindView(data: FollowCommonContentCollection) {
this.data = data
slideWithCardsUi.bind(data.linkCommonCollection, bindingAdapterPosition + 1)
itemView.setBackgroundColor(R.color.ui_surface.toColor(itemView.context))
}
interface OnChildEventListener {
fun navigateToGameDetailPage(childPosition: Int, gameEntity: GameEntity, text: String)
fun navigateToLinkPage(
position: Int,
link: LinkEntity,
data: CustomPageData.CommonContentCollection,
text: String,
exposureEvent: ExposureEvent?
)
}
}

View File

@ -0,0 +1,6 @@
package com.gh.gamecenter.forum.home.follow.viewholder
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.databinding.RecyclerInvalidBinding
class FollowInvalidViewHolder(val binding: RecyclerInvalidBinding) : ViewHolder(binding.root)

View File

@ -0,0 +1,57 @@
package com.gh.gamecenter.forum.home.follow.viewholder
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.databinding.RecyclerNavigationCustomBinding
import com.gh.gamecenter.entity.FollowCommonContentCollection
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.ui.CommonContentNavigationUi
/**
* 通用内容合集-导航栏
*/
class FollowNavigationViewHolder(
val binding: RecyclerNavigationCustomBinding,
private val listener: OnChildEventListener
) : ViewHolder(binding.root) {
private lateinit var data: FollowCommonContentCollection
private val navigationUi by lazy {
CommonContentNavigationUi(binding, object : CommonContentNavigationUi.OnNavigationListener {
override fun navigateToLinkPage(link: LinkEntity, text: String, exposureEvent: ExposureEvent?) {
listener.navigateToLinkPage(
bindingAdapterPosition,
link,
data.linkCommonCollection,
text,
exposureEvent
)
}
})
}
fun bindView(data: FollowCommonContentCollection) {
this.data = data
val navigationList = data.linkCommonCollection.navigations
if (!navigationList.isNullOrEmpty()) {
navigationUi.bind(navigationList, null)
}
itemView.setBackgroundColor(R.color.ui_surface.toColor(itemView.context))
}
interface OnChildEventListener {
fun navigateToLinkPage(
position: Int,
link: LinkEntity,
data: CustomPageData.CommonContentCollection,
text: String,
exposureEvent: ExposureEvent?
)
}
}

View File

@ -0,0 +1,124 @@
package com.gh.gamecenter.forum.home.follow.viewholder
import android.content.Context
import android.graphics.Color
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.CommunityAnswerItemBinding
import com.gh.gamecenter.feature.entity.ArticleEntity
import com.gh.gamecenter.forum.home.ForumArticleAskItemViewHolder
import com.gh.gamecenter.forum.search.CommunitySearchEventListener
import com.gh.gamecenter.forum.search.CommunitySearchEventListener.Companion.SEARCH_BUTTON_COMMENT
import com.gh.gamecenter.qa.article.detail.ArticleDetailActivity
import com.gh.gamecenter.qa.questions.newdetail.NewQuestionDetailActivity
import com.gh.gamecenter.qa.video.detail.ForumVideoDetailActivity
class FollowPostCardViewHolder(
val binding: CommunityAnswerItemBinding,
val entrance: String,
val path: String
) : ViewHolder(binding.root) {
private val context: Context
get() = itemView.context
private val viewHolder = ForumArticleAskItemViewHolder(binding, null)
fun bind(data: ArticleEntity, position: Int) {
val articleEntity = data
viewHolder.binding.run {
root.layoutParams = (root.layoutParams as ViewGroup.MarginLayoutParams).apply {
topMargin = if (position == 0) 8F.dip2px() else 0
}
if (position == 0) {
root.background = R.drawable.background_shape_white_radius_12_top_only.toDrawable(context)
} else {
root.setBackgroundColor(R.color.ui_surface.toColor(context))
}
rightContainer.setBackgroundColor(Color.TRANSPARENT)
topLine.goneIf(position == 0)
}
// 关注-动态列表一定是已关注的数据
articleEntity.me.isFollower = true
articleEntity.brief = articleEntity.content
viewHolder.bindForumArticleItem(articleEntity, entrance, path, position)
if (articleEntity.type == "question") {
if (articleEntity.count.answer > 0) {
viewHolder.commentCount.text = articleEntity.count.answer.toString()
} else {
viewHolder.commentCount.text = "回答"
}
viewHolder.commentCount.setDrawableStart(R.drawable.community_comment_count)
viewHolder.voteCountContainer.visibility = View.GONE
} else {
viewHolder.voteCountContainer.visibility = View.VISIBLE
}
itemView.setOnClickListener {
SensorsBridge.trackFollowPageContentClick("帖子", articleEntity.title)
when (articleEntity.type) {
"community_article" -> {
context.startActivity(
ArticleDetailActivity.getRecommendIntent(
context,
articleEntity.community,
articleEntity.id,
articleEntity.recommendId,
"",
path,
"社区-关注"
)
)
}
"video" -> {
context.startActivity(
ForumVideoDetailActivity.getRecommendIntent(
context,
articleEntity.id,
articleEntity.community.id,
articleEntity.recommendId,
"社区-关注"
)
)
}
"question" -> {
context.startActivity(
NewQuestionDetailActivity.getRecommendIntent(
context,
articleEntity.id,
"",
articleEntity.recommendId,
"",
path,
sourceEntrance = "社区-关注"
)
)
}
"answer" -> {
context.startActivity(
NewQuestionDetailActivity.getRecommendIntent(
context,
articleEntity.questions.id,
articleEntity.id,
articleEntity.recommendId,
"",
path,
true
)
)
}
}
}
}
}

View File

@ -0,0 +1,35 @@
package com.gh.gamecenter.forum.home.follow.viewholder
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.databinding.RecyclerFollowRecommendListBinding
import com.gh.gamecenter.eventbus.EBUserFollow
import com.gh.gamecenter.feature.entity.UserEntity
import com.gh.gamecenter.forum.home.follow.adapter.FollowRecommendListAdapter
class FollowRecommendListViewHolder(
val binding: RecyclerFollowRecommendListBinding,
listener: FollowRecommendListAdapter.OnChildEventListener
) :
ViewHolder(binding.root) {
private val adapter by lazy {
FollowRecommendListAdapter(itemView.context, listener)
}
fun bindView(data: List<UserEntity>) {
if (binding.rvRecommends.adapter == null) {
binding.rvRecommends.layoutManager =
LinearLayoutManager(itemView.context, RecyclerView.HORIZONTAL, false)
binding.rvRecommends.adapter = adapter
}
adapter.submitList(data)
}
fun updateFollowed(change: EBUserFollow) {
adapter.updateFollowed(change)
}
}

View File

@ -0,0 +1,105 @@
package com.gh.gamecenter.forum.home.follow.viewholder
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.common.util.NewsUtils
import com.gh.common.view.ImageContainerView
import com.gh.gamecenter.ImageViewerActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.debounceActionWithInterval
import com.gh.gamecenter.common.utils.toArrayList
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.databinding.RecyclerGameArticleBinding
import com.gh.gamecenter.entity.FollowDynamicEntity
import com.gh.gamecenter.feature.entity.ArticleEntity
import com.gh.gamecenter.feature.entity.ConcernEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.SimpleGame
class GameArticleViewHolder(
val binding: RecyclerGameArticleBinding,
private val listener: OnChildEventListener
) : ViewHolder(binding.root) {
fun bind(article: FollowDynamicEntity.Article) {
val game = article.game
binding.tvGameName.text = game.name
binding.ivIcon.displayGameIcon(game)
binding.tvTime.text = NewsUtils.getFormattedTime(article.time)
binding.tvTitle.text = article.title
binding.tvDescription.text = article.content
val images = article.img.take(1).map {
ImageContainerView.ImageContainerData.ImageInfo(it, 3, 2)
}
val imageContainerData = ImageContainerView.ImageContainerData("", false, images, show = images.isNotEmpty())
binding.ivImagesContainer.bindData(imageContainerData,
object : ImageContainerView.OnImageContainerEventListener {
override fun onImageClick(
images: List<String>,
position: Int,
imageViewList: ArrayList<SimpleDraweeView>
) {
val checkIntent = ImageViewerActivity.getIntent(
binding.root.context,
images.toArrayList(),
position,
imageViewList,
""
)
binding.root.context.startActivity(checkIntent)
}
override fun onVideoCLick(videoId: String) = Unit
})
arrayOf(binding.ivIcon, binding.tvTitle).forEach {
it.setOnClickListener {
listener.onGameCLick(game)
}
}
itemView.setOnClickListener {
listener.onArticleClick(article)
}
binding.tvComment.setOnClickListener {
listener.onComment(article)
}
binding.tvShare.setOnClickListener {
debounceActionWithInterval(800) {
listener.onShare(toConcernEntity(article))
}
}
}
private fun toConcernEntity(article: FollowDynamicEntity.Article): ConcernEntity {
val simpleGame = SimpleGame().apply {
mName = article.game.name
}
return ConcernEntity().apply {
id = article.id
title = article.title
brief = article.content
game = simpleGame
gameIcon = article.game.icon
img = article.img
shortId = article.shortId
}
}
interface OnChildEventListener {
fun onGameCLick(game: GameEntity)
fun onArticleClick(article: FollowDynamicEntity.Article)
fun onComment(article: FollowDynamicEntity.Article)
fun onShare(concernEntity: ConcernEntity)
}
}

View File

@ -0,0 +1,54 @@
package com.gh.gamecenter.forum.home.follow.viewholder
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.common.util.NewsUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.databinding.RecyclerGiftPackBinding
import com.gh.gamecenter.entity.FollowDynamicEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.LibaoEntity
import com.gh.gamecenter.feature.entity.MeEntity
class GiftPackViewHolder(
val binding: RecyclerGiftPackBinding,
val listener: OnChildEventListener
) : ViewHolder(binding.root) {
fun bind(
libao: LibaoEntity,
time: Long
) {
val game = libao.game?.toGameEntity() ?: return
binding.ivIcon.displayGameIcon(game)
binding.tvGameName.text = game.name
binding.tvTime.text = NewsUtils.getFormattedTime(libao.time)
binding.tvGiftPackName.text = libao.name
binding.tvGiftPackContent.text = libao.content
binding.gCode.goneIf(libao.code.isNullOrBlank()) {
binding.tvCode.text = libao.code
}
arrayOf(binding.ivIcon, binding.tvGameName).forEach {
it.setOnClickListener {
listener.onGameClick(game)
}
}
binding.tvCopy.setOnClickListener {
listener.onCopy(libao.code ?: "")
}
itemView.setOnClickListener {
listener.onGiftPackageClick(libao)
}
}
interface OnChildEventListener {
fun onGameClick(game: GameEntity)
fun onGiftPackageClick(libaoEntity: LibaoEntity)
fun onCopy(code: String)
}
}

View File

@ -0,0 +1,112 @@
package com.gh.gamecenter.forum.home.follow.viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.entity.FollowOperateTopRequest
import com.gh.gamecenter.entity.FollowUserEntity
import com.gh.gamecenter.forum.home.follow.AllFollowedNormalItem
import com.gh.gamecenter.forum.home.follow.model.AllFollowRepository
import com.gh.gamecenter.livedata.Event
import com.gh.gamecenter.retrofit.RetrofitManager
import io.reactivex.disposables.CompositeDisposable
import okhttp3.ResponseBody
class AllFollowedViewModel : ViewModel() {
private val repository = AllFollowRepository(RetrofitManager.getInstance().newApi)
private val compositeDisposable = CompositeDisposable()
private var pageNo = 1
private val _dataList = MutableLiveData<List<AllFollowedNormalItem>>()
val dataList: LiveData<List<AllFollowedNormalItem>> = _dataList
val hasReadSetIds = mutableSetOf<String>()
fun loadFirst() {
pageNo = 1
loadData()
}
fun loadMore() {
loadData()
}
private fun loadData() {
repository.loadMyFollowedUser(pageNo)
.compose(singleToMain())
.subscribe(object : BiResponse<List<AllFollowedNormalItem>>() {
override fun onSuccess(data: List<AllFollowedNormalItem>) {
addData(data)
}
}).let(compositeDisposable::add)
}
private val _operateTopSuccessfully = MutableLiveData<Event<Unit>>()
val operateTopSuccessfully: LiveData<Event<Unit>> = _operateTopSuccessfully
fun operateTop(topItem: List<FollowOperateTopRequest>) {
repository.operateTop(topItem)
.compose(singleToMain())
.subscribe(object : BiResponse<ResponseBody>() {
override fun onSuccess(data: ResponseBody) {
_operateTopSuccessfully.value = Event(Unit)
}
}).let(compositeDisposable::add)
}
private fun addData(newData: List<AllFollowedNormalItem>) {
val oldData = dataList.value
_dataList.value = if (oldData == null) {
newData
} else {
oldData + newData
}
pageNo++
}
private val _detailDestination = MutableLiveData<Event<FollowUserEntity>>()
val detailDestination: LiveData<Event<FollowUserEntity>> = _detailDestination
fun navigateToDetailPage(item: AllFollowedNormalItem) {
val entity = item.data
_detailDestination.value = Event(entity)
val id = if (entity.isUser) {
entity.user.id
} else {
entity.bbs.id
}
if (entity.isShowTip) {
repository.postRead(entity.type, id ?: "")
.compose(singleToMain())
.subscribe(object : BiResponse<ResponseBody>() {
override fun onSuccess(data: ResponseBody) {
hasReadSetIds.add(item.data.id)
updateReadStatus(item)
}
}).let(compositeDisposable::add)
}
}
private fun updateReadStatus(item: AllFollowedNormalItem) {
val oldData = _dataList.value ?: return
val position = oldData.indexOfFirst {
item.data.id == it.data.id
}
val newData = oldData.toMutableList()
val newItem = item.copy(data = item.data.copy(_isShowTip = 0))
newData[position] = newItem
_dataList.value = newData
}
override fun onCleared() {
compositeDisposable.clear()
}
}

View File

@ -0,0 +1,123 @@
package com.gh.gamecenter.forum.home.follow.viewmodel
import android.view.ViewGroup
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.gh.common.util.ErrorHelper
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.common.utils.observableToMain
import com.gh.gamecenter.entity.PersonalHistoryEntity
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.forum.home.follow.FollowDynamicItem
import com.gh.gamecenter.forum.home.follow.model.FollowDynamicRepository
import com.gh.gamecenter.livedata.Event
import com.halo.assistant.HaloApp
import io.reactivex.disposables.CompositeDisposable
import retrofit2.HttpException
class FollowDynamicListViewModel : ViewModel() {
private val compositeDisposable = CompositeDisposable()
private val repository = FollowDynamicRepository.newInstance()
private var pageNo = 1
private var userId: String = ""
var bbsId: String = ""
private val _loadStatus = MutableLiveData<Pair<LoadStatus, Boolean>>()
val loadStatus: LiveData<Pair<LoadStatus, Boolean>> = _loadStatus
private val _dataList = MutableLiveData<List<FollowDynamicItem>>()
val dataList: LiveData<List<FollowDynamicItem>> = _dataList
fun loadFirst(userId: String, bbsId: String, isPullRefresh: Boolean) {
pageNo = 1
this.userId = userId
this.bbsId = bbsId
loadData(userId, bbsId, isPullRefresh)
}
fun loadMore() {
loadData(userId, bbsId, false)
}
private fun loadData(userId: String, bbsId: String, isPullRefresh: Boolean) {
if (pageNo == 1) {
_loadStatus.value = LoadStatus.INIT_LOADING to isPullRefresh
} else {
_loadStatus.value = LoadStatus.LIST_LOADING to isPullRefresh
}
repository.loadData(userId, bbsId, pageNo)
.compose(observableToMain())
.subscribe(object : Response<List<FollowDynamicItem>>() {
override fun onResponse(data: List<FollowDynamicItem>?) {
if (data.isNullOrEmpty()) {
if (pageNo == 1) {
_loadStatus.value = LoadStatus.INIT_EMPTY to isPullRefresh
} else {
_loadStatus.value = LoadStatus.LIST_OVER to isPullRefresh
}
} else {
if (pageNo == 1) {
_loadStatus.value = LoadStatus.INIT_LOADED to isPullRefresh
} else {
_loadStatus.value = LoadStatus.LIST_LOADED to isPullRefresh
}
}
if (pageNo == 1) {
_dataList.value = data ?: listOf()
pageNo++
} else {
addData(data ?: listOf())
}
}
override fun onFailure(e: HttpException?) {
super.onFailure(e)
if (pageNo == 1) {
_loadStatus.value = LoadStatus.INIT_FAILED to isPullRefresh
} else {
_loadStatus.value = LoadStatus.LIST_FAILED to isPullRefresh
}
ErrorHelper.handleError(HaloApp.getInstance().application, e?.response()?.errorBody()?.string())
}
})
}
private fun addData(newData: List<FollowDynamicItem>) {
val oldData = dataList.value
_dataList.value = if (oldData == null) {
newData
} else {
oldData + newData
}
pageNo++
}
private val _personDetailDestination = MutableLiveData<Event<Pair<PersonalHistoryEntity, Int>>>()
val personDetailDestination: LiveData<Event<Pair<PersonalHistoryEntity, Int>>> = _personDetailDestination
fun navigateToPersonDetailPage(history: PersonalHistoryEntity, position: Int) {
_personDetailDestination.value = Event(history to position)
}
private val _bbsDetailDestination = MutableLiveData<Event<Pair<Int, AnswerEntity>>>()
val bbsDetailDestination: LiveData<Event<Pair<Int, AnswerEntity>>> = _bbsDetailDestination
fun navigateToBbsDetailPage(position: Int, answer: AnswerEntity) {
_bbsDetailDestination.value = Event(position to answer)
}
override fun onCleared() {
super.onCleared()
compositeDisposable.clear()
}
}

View File

@ -0,0 +1,50 @@
package com.gh.gamecenter.forum.home.follow.viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.gh.gamecenter.entity.FollowUserEntity
import com.gh.gamecenter.forum.home.follow.model.FollowDynamicRepository
import com.gh.gamecenter.livedata.Event
class FollowDynamicViewModel : ViewModel() {
private var userPageNo = 1
private val repository = FollowDynamicRepository.newInstance()
private val _followedUsers = MutableLiveData<Pair<Boolean, List<FollowUserEntity>>>()
val followedUsers: LiveData<Pair<Boolean, List<FollowUserEntity>>> = _followedUsers
private val _isStartLoadAction = MutableLiveData<Boolean>()
val isStartLoadAction: LiveData<Boolean> = _isStartLoadAction
fun startLoad() {
_isStartLoadAction.value = true
}
fun addFirstPageUsers(users: List<FollowUserEntity>) {
userPageNo = 1
addData(users, true)
}
private fun addData(newData: List<FollowUserEntity>, isFirstPage: Boolean) {
val oldData = followedUsers.value?.second
_followedUsers.value = isFirstPage to if (oldData == null) {
newData
} else {
oldData + newData
}
userPageNo++
}
private val _finishAction = MutableLiveData<Event<Unit>>()
val finishAction: LiveData<Event<Unit>> = _finishAction
fun finish() {
_finishAction.value = Event(Unit)
}
override fun onCleared() {
}
}

View File

@ -0,0 +1,316 @@
package com.gh.gamecenter.forum.home.follow.viewmodel
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import com.gh.common.util.CheckLoginUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.retrofit.JSONObjectResponse
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.observableToMain
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.entity.FollowUserEntity
import com.gh.gamecenter.eventbus.EBUserFollow
import com.gh.gamecenter.feature.entity.*
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.forum.home.follow.FollowArticleItem
import com.gh.gamecenter.forum.home.follow.FollowItem
import com.gh.gamecenter.forum.home.follow.FollowPostCardItem
import com.gh.gamecenter.forum.home.follow.FollowUserItem
import com.gh.gamecenter.forum.home.follow.eventlistener.OnFollowHomeEventListener
import com.gh.gamecenter.forum.home.follow.fragment.FollowHomeFragment.Companion.LOCATION_FOLLOW_PAGE
import com.gh.gamecenter.forum.home.follow.model.FollowRepository
import com.gh.gamecenter.home.custom.CommonContentCollectionUseCase
import com.gh.gamecenter.home.custom.CustomPageTracker
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.commonContentLayoutToComponentName
import com.gh.gamecenter.livedata.Event
import com.lightgame.utils.Utils
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import okhttp3.ResponseBody
import org.greenrobot.eventbus.EventBus
import org.json.JSONException
import org.json.JSONObject
import retrofit2.HttpException
class FollowHomeViewModel(application: Application) : AndroidViewModel(application), OnFollowHomeEventListener {
private var pageNo = 1
private val compositeDisposable = CompositeDisposable()
private val repository = FollowRepository.newInstance()
val commonContentCollectionUseCase = CommonContentCollectionUseCase.newInstance()
private var dynamicType = ""
var videoList = listOf<ForumVideoEntity>()
private val _dataList = MutableLiveData<List<FollowItem>>()
val dataList: LiveData<List<FollowItem>> = _dataList
private val _loadStatus = MutableLiveData<Pair<LoadStatus, Boolean>>()
val loadStatus: LiveData<Pair<LoadStatus, Boolean>> = _loadStatus
private val _hasFollowedUser = MutableLiveData<Boolean>()
val hasFollowedUser: LiveData<Boolean> = _hasFollowedUser
fun loadFirst(isLogin: Boolean, type: String = "", isPullToRefresh: Boolean) {
pageNo = 1
dynamicType = type
_loadStatus.value = LoadStatus.INIT_LOADING to isPullToRefresh
repository.loadHomeData(isLogin, dynamicType, pageNo)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BiResponse<List<FollowItem>>() {
override fun onSuccess(data: List<FollowItem>) {
findVideoList(data)
_hasFollowedUser.value = (data.firstOrNull() as? FollowUserItem)?.data?.isNotEmpty() == true
_loadStatus.value = LoadStatus.INIT_LOADED to isPullToRefresh
pageNo++
_dataList.value = data
}
override fun onFailure(exception: Exception) {
super.onFailure(exception)
_loadStatus.value = LoadStatus.INIT_LOADED to isPullToRefresh
}
}).let(compositeDisposable::add)
}
fun loadMore() {
_loadStatus.value = LoadStatus.LIST_LOADING to false
repository.loadMore(dynamicType, pageNo)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BiResponse<List<FollowItem>>() {
override fun onSuccess(data: List<FollowItem>) {
if (data.isNotEmpty()) {
_loadStatus.value = LoadStatus.LIST_LOADED to false
addData(data)
} else {
_loadStatus.value = LoadStatus.LIST_OVER to false
}
}
override fun onFailure(exception: Exception) {
super.onFailure(exception)
_loadStatus.value = LoadStatus.LIST_FAILED to false
}
}).let(compositeDisposable::add)
}
private fun findVideoList(data: List<FollowItem>) {
videoList = data.map {
if (it is FollowPostCardItem) {
it.data.transformForumVideoEntity()
} else {
ForumVideoEntity()
}
}
}
fun loadFollowedUsers() {
repository.loadFollowedUsers()
.compose(singleToMain())
.subscribe(object : BiResponse<List<FollowUserEntity>>() {
override fun onSuccess(data: List<FollowUserEntity>) {
val oldData = _dataList.value ?: return
val headItem = oldData.getOrNull(0)
if (headItem is FollowUserItem) {
val newHeadItem = headItem.copy(data = data)
val newData = oldData.toMutableList()
newData[0] = newHeadItem
_dataList.value = newData
}
}
}).let(compositeDisposable::add)
}
private fun addData(newData: List<FollowItem>) {
val oldData = dataList.value
val data = if (oldData == null) {
newData
} else {
oldData + newData
}
findVideoList(data)
_dataList.value = data
pageNo++
}
private val _followedDetailDestination = MutableLiveData<Event<Pair<Int, List<FollowUserEntity>>>>()
val followedDetailDestination: LiveData<Event<Pair<Int, List<FollowUserEntity>>>> = _followedDetailDestination
override fun viewItemFollowed(position: Int, data: List<FollowUserEntity>) {
val item = data.getOrNull(position) ?: return
SensorsBridge.trackFollowPageUserAndForumDataClick(
item.user.id ?: "",
item.bbs.id,
item.bbs.name,
item.bbs.type
)
_followedDetailDestination.value = Event(position to data)
}
private val _myFollowedListDestination = MutableLiveData<Event<Unit>>()
val myFollowedListDestination: LiveData<Event<Unit>> = _myFollowedListDestination
override fun viewAllFollowed() {
_myFollowedListDestination.value = Event(Unit)
}
private val _loginDestination = MutableLiveData<Event<Unit>>()
val loginDestination: LiveData<Event<Unit>> = _loginDestination
override fun login() {
_loginDestination.value = Event(Unit)
}
override fun followUser(userId: String, isFollow: Boolean) {
val isLogin = CheckLoginUtils.isLogin()
if (isLogin) {
repository.followUser(userId, isFollow)
.compose(observableToMain())
.subscribe(object : Response<ResponseBody>() {
override fun onResponse(response: ResponseBody?) {
if (isFollow) {
Utils.toast(getApplication(), R.string.concern_success)
} else {
Utils.toast(getApplication(), R.string.concern_cancel)
}
EventBus.getDefault().post(EBUserFollow(userId, isFollow))
}
override fun onFailure(e: HttpException?) {
super.onFailure(e)
Utils.toast(getApplication(), R.string.loading_failed_hint)
}
})
} else {
login()
}
}
private val _linkDestination = MutableLiveData<Event<Pair<LinkEntity, ExposureEvent?>>>()
val linkDestination: LiveData<Event<Pair<LinkEntity, ExposureEvent?>>> = _linkDestination
override fun navigateToLinkPageInCommonContent(
position: Int,
link: LinkEntity,
data: CustomPageData.CommonContentCollection,
text: String,
exposureEvent: ExposureEvent?
) {
SensorsBridge.trackLinkContentCollectionClick(
LOCATION_FOLLOW_PAGE,
"",
"",
data.name,
data.id,
position,
link.type ?: "",
link.link ?: "",
link.text ?: "",
text,
commonContentLayoutToComponentName[data.layout] ?: "",
)
_linkDestination.value = Event(link to exposureEvent)
}
private val _gameDetailDestination = MutableLiveData<Event<GameEntity>>()
val gameDetailDestination: LiveData<Event<GameEntity>> = _gameDetailDestination
override fun navigateToGameDetailPage(position: Int, game: GameEntity, text: String) {
_gameDetailDestination.value = Event(game)
}
private val _commonCollectionDestination = MutableLiveData<Event<CustomPageData.CommonContentCollection>>()
val commonCollectionDestination: LiveData<Event<CustomPageData.CommonContentCollection>> =
_commonCollectionDestination
override fun navigateToCommonCollectionDetailPage(data: CustomPageData.CommonContentCollection) {
_commonCollectionDestination.value = Event(data)
}
private val _userHomeDestination = MutableLiveData<Event<String>>()
val userHomeDestination: LiveData<Event<String>> = _userHomeDestination
override fun navigateToUserHomePage(userId: String) {
_userHomeDestination.value = Event(userId)
}
private val _postCardClickAction = MutableLiveData<Event<Pair<Int, ArticleEntity>>>()
val postCardClickAction: LiveData<Event<Pair<Int, ArticleEntity>>> = _postCardClickAction
override fun onPostCardClick(position: Int, article: ArticleEntity) {
_postCardClickAction.value = Event(position to article)
}
private val _libaoDetailDestination = MutableLiveData<Event<LibaoEntity>>()
val libaoDetailDestination: LiveData<Event<LibaoEntity>> = _libaoDetailDestination
override fun navigateToLibaoDetailPage(libaoEntity: LibaoEntity) {
_libaoDetailDestination.value = Event(libaoEntity)
}
private val _newDetailDestination = MutableLiveData<Event<String>>()
val newDetailDestination: LiveData<Event<String>> = _newDetailDestination
private val _updateOkhttpCacheAction = MutableLiveData<Event<String>>()
val updateOkhttpCacheAction: LiveData<Event<String>> = _updateOkhttpCacheAction
override fun navigateToNewsDetailPage(articleId: String) {
_newDetailDestination.value = Event(articleId)
// 统计文章阅读量
repository.postArticleVisit(articleId)
.compose(observableToMain())
.subscribe(object : JSONObjectResponse() {
override fun onResponse(response: JSONObject) {
super.onResponse(response)
if (response.length() != 0) {
try {
if ("success" == response.getString("status")) {
_updateOkhttpCacheAction.value = Event(articleId)
}
} catch (e: JSONException) {
e.printStackTrace();
}
}
}
});
}
private val _articleCommentDetailDestination = MutableLiveData<Event<String>>()
val articleCommentDetailDestination: LiveData<Event<String>> = _articleCommentDetailDestination
override fun navigateToArticleCommentDetailPage(articleId: String) {
_articleCommentDetailDestination.value = Event(articleId)
}
private val _shareArticleDestination = MutableLiveData<Event<ConcernEntity>>()
val shareArticleDestination: LiveData<Event<ConcernEntity>> = _shareArticleDestination
override fun onShareArticle(concernEntity: ConcernEntity) {
_shareArticleDestination.value = Event(concernEntity)
}
private val _copyExchangeCodeAction = MutableLiveData<Event<String>>()
val copyExchangeCodeAction: LiveData<Event<String>> = _copyExchangeCodeAction
override fun copyExchangeCode(code: String) {
_copyExchangeCodeAction.value = Event(code)
}
override fun onCleared() {
compositeDisposable.clear()
}
}

View File

@ -0,0 +1,55 @@
package com.gh.gamecenter.home.custom
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.gh.common.filter.RegionSettingHelper
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.common.utils.observableToMain
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.entity.HomeSubSlide
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.retrofit.service.ApiService
import com.lightgame.utils.Utils
import io.reactivex.Observable
import retrofit2.HttpException
class CommonContentCollectionUseCase private constructor(
private val api: ApiService
) {
var refreshCount = 0
private var slideDiscoveryGamesPage = -1
private val _slideDiscoveryCardGames = MutableLiveData<List<GameEntity>>()
val slideDiscoveryCardGames: LiveData<List<GameEntity>> = _slideDiscoveryCardGames
fun loadSlideDiscoverCardGames(homeSubSlide: HomeSubSlide) {
if (slideDiscoveryGamesPage != homeSubSlide.repeatCount || slideDiscoveryCardGames.value == null) {
val paramsMap = if (SPUtils.getBoolean(Constants.SP_DISCOVER_FORCE_REFRESH)) {
mapOf("page_size" to 3, "view" to "sub_slide", "refresh" to "true")
} else {
mapOf("page_size" to 3, "view" to "sub_slide")
}
api.getDiscoveryGames(1, paramsMap)
.map { it.games }
.map(RegionSettingHelper.filterGame)
.compose(observableToMain())
.subscribe(object : Response<List<GameEntity>>() {
override fun onResponse(response: List<GameEntity>?) {
response?.let {
SPUtils.setBoolean(Constants.SP_DISCOVER_FORCE_REFRESH, false)
_slideDiscoveryCardGames.postValue(it)
slideDiscoveryGamesPage = homeSubSlide.repeatCount
}
}
})
}
}
companion object {
fun newInstance() =
CommonContentCollectionUseCase(
RetrofitManager.getInstance().api
)
}
}

View File

@ -56,6 +56,8 @@ class CustomPageViewModel(
}
}
val commonContentCollectionUseCase = CommonContentCollectionUseCase.newInstance()
private val repository = CustomPageRepository.newInstance()
val gamePositionAndPackageHelper = GamePositionAndPackageHelper()
@ -200,6 +202,7 @@ class CustomPageViewModel(
override fun onRefresh() {
refreshCount++
commonContentCollectionUseCase.refreshCount = refreshCount
NewFlatLogUtils.logHomePagePullRefresh(refreshCount)
}
@ -423,31 +426,6 @@ class CustomPageViewModel(
_changeAppBarColorAction.value = Event(color)
}
private val _slideDiscoveryCardGames = MutableLiveData<List<GameEntity>>()
val slideDiscoveryCardGames: LiveData<List<GameEntity>> = _slideDiscoveryCardGames
override fun loadSlideDiscoverCardGames(homeSubSlide: HomeSubSlide) {
if (slideDiscoveryGamesPage == homeSubSlide.repeatCount && _slideDiscoveryCardGames.value != null) {
_slideDiscoveryCardGames.postValue(ArrayList(_slideDiscoveryCardGames.value!!))
} else {
repository.loadSlideDiscoverCardGames()
.compose(observableToMain())
.subscribe(object : Response<List<GameEntity>>() {
override fun onResponse(response: List<GameEntity>?) {
response?.let {
SPUtils.setBoolean(Constants.SP_DISCOVER_FORCE_REFRESH, false)
_slideDiscoveryCardGames.postValue(it)
slideDiscoveryGamesPage = homeSubSlide.repeatCount
}
}
override fun onFailure(e: HttpException?) {
Utils.toast(getApplication(), "网络异常")
}
})
}
}
private fun addData(newData: List<CustomPageItem>) {
val oldData = _dataList.value
if (oldData.isNullOrEmpty()) {
@ -634,7 +612,12 @@ class CustomPageViewModel(
private val _linkDestination = MutableLiveData<Event<Pair<LinkEntity, ExposureEvent?>>>()
val linkDestination: LiveData<Event<Pair<LinkEntity, ExposureEvent?>>> = _linkDestination
override fun navigateToLinkPage(item: CustomPageItem, link: LinkEntity, text: String, exposureEvent: ExposureEvent?) {
override fun navigateToLinkPage(
item: CustomPageItem,
link: LinkEntity,
text: String,
exposureEvent: ExposureEvent?
) {
_linkDestination.value = Event(Pair(link, exposureEvent))
}
@ -650,7 +633,8 @@ class CustomPageViewModel(
_badgeWallDestination.value = Event(comment)
}
private val _gameDetailDestinationOnAmway = MutableLiveData<Event<Triple<CustomPageTrackData, String, ExposureEvent?>>>()
private val _gameDetailDestinationOnAmway =
MutableLiveData<Event<Triple<CustomPageTrackData, String, ExposureEvent?>>>()
val gameDetailDestinationOnAmway: LiveData<Event<Triple<CustomPageTrackData, String, ExposureEvent?>>> =
_gameDetailDestinationOnAmway

View File

@ -0,0 +1,11 @@
package com.gh.gamecenter.home.custom
import androidx.recyclerview.widget.RecyclerView
interface OnViewHolderAttachListener {
fun onViewAttach(parent: RecyclerView?) = Unit
fun onViewDetach(parent: RecyclerView?) = Unit
}

View File

@ -10,29 +10,27 @@ import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.CommonCollectionItemBinding
import com.gh.gamecenter.entity.CommonCollectionContentEntity
import com.gh.gamecenter.entity.ExposureLinkEntity
import com.gh.gamecenter.home.custom.model.CustomCommonContentCollectionItem
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_IMAGE_TEXT
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_SLIDE_BANNER
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_HORIZONTAL_VERTICAL_CARD
class CustomCommonCollectionAdapter(
context: Context,
private val clickInvoke: (Int, CommonCollectionContentEntity) -> Unit,
private val exposureInvoke: (Int, ExposureLinkEntity) -> Unit
private val listener: OnEventListener
) :
CustomBaseChildAdapter<CommonCollectionContentEntity, CustomCommonCollectionAdapter.CustomCommonCollectionItemViewHolder>(
context
) {
private var layout = 0
private lateinit var _data: CustomCommonContentCollectionItem
fun setData(data: CustomCommonContentCollectionItem) {
this._data = data
submitList(data.data.data)
fun setData(data: CustomPageData.CommonContentCollection) {
layout = data.layout
submitList(data.data)
}
override fun getKey(t: CommonCollectionContentEntity): String {
return "${_data.data.layout}-${t.id}"
return "${layout}-${t.id}"
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CustomCommonCollectionItemViewHolder {
@ -40,7 +38,6 @@ class CustomCommonCollectionAdapter(
}
override fun onBindViewHolder(holder: CustomCommonCollectionItemViewHolder, position: Int) {
val layout = _data.data.layout
val item = getItem(position)
var width = 0
var height = 0
@ -60,7 +57,7 @@ class CustomCommonCollectionAdapter(
height = 80F.dip2px()
}
}
exposureInvoke.invoke(position, item.linkEntity)
listener.addExposureEvent(position, item.linkEntity)
holder.binding.apply {
ImageUtils.display(commonCollectionImage, item.image)
@ -99,7 +96,7 @@ class CustomCommonCollectionAdapter(
root.layoutParams = rootParams
root.setOnClickListener {
clickInvoke(position, item)
listener.onChildItemClick(position, item)
}
}
}
@ -114,4 +111,11 @@ class CustomCommonCollectionAdapter(
class CustomCommonCollectionItemViewHolder(val binding: CommonCollectionItemBinding) :
BaseRecyclerViewHolder<Any>(binding.root)
interface OnEventListener {
fun addExposureEvent(childPosition: Int, exposureLinkEntity: ExposureLinkEntity)
fun onChildItemClick(childPosition: Int, item: CommonCollectionContentEntity)
}
}

View File

@ -9,6 +9,7 @@ import com.gh.common.exposure.ExposureTraceUtils
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.entity.ExposureEntity
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.json.json
import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.common.utils.SensorsBridge
@ -25,7 +26,7 @@ import com.gh.gamecenter.home.custom.eventlistener.CommonContentCollectionEventH
class CustomGameNavigationAdapter(
context: Context,
private val eventHelper: CommonContentCollectionEventHelper,
private val listener: OnEventListener,
) : CustomBaseChildAdapter<GameNavigationEntity, CustomGameNavigationAdapter.GameNavigationViewHolder>(context) {
private var recordMap = SPUtils.getMap(Constants.SP_GAME_NAVIGATION)
@ -113,7 +114,7 @@ class CustomGameNavigationAdapter(
ExposureManager.log(clickEvent)
}
}
eventHelper.navigateToLinkPage(it, "导航栏", exposureEvent)
listener.navigateToLinkPage(it, "导航栏", exposureEvent)
}
}
@ -125,4 +126,8 @@ class CustomGameNavigationAdapter(
class GameNavigationViewHolder(val binding: ItemGameNavigationCustomBinding) :
BaseRecyclerViewHolder<RecyclerView.ViewHolder>(binding.root)
interface OnEventListener {
fun navigateToLinkPage(link: LinkEntity, text: String, exposureEvent: ExposureEvent?)
}
}

View File

@ -9,6 +9,7 @@ import com.gh.common.exposure.ExposureTraceUtils
import com.gh.common.util.LogUtils
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.setDebouncedClickListener
@ -28,7 +29,7 @@ import org.json.JSONObject
* description :
*/
class CustomHomeRecommendItemGridAdapter(
private val eventHelper: CommonContentCollectionEventHelper,
private val listener: OnEventListener,
) : BaseAdapter() {
private val dataList = arrayListOf<HomeRecommend>()
@ -114,7 +115,16 @@ class CustomHomeRecommendItemGridAdapter(
ExposureManager.log(clickEvent)
}
}
eventHelper.navigateToLinkPage(recommend.transformLinkEntity(), "金刚区", exposureEventList?.getOrNull(position))
listener.navigateToLinkPage(
recommend.transformLinkEntity(),
"金刚区",
exposureEventList?.getOrNull(position)
)
}
}
interface OnEventListener {
fun navigateToLinkPage(link: LinkEntity, text: String, exposureEvent: ExposureEvent?)
}
}

View File

@ -11,6 +11,7 @@ import com.gh.common.databind.BindingAdapters
import com.gh.common.exposure.ExposureManager
import com.gh.common.exposure.ExposureTraceUtils
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.utils.DataLogUtils
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toBinding
@ -21,7 +22,6 @@ import com.gh.gamecenter.entity.HomeSlide
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.ExposureType
import com.gh.gamecenter.home.custom.eventlistener.CommonContentCollectionEventHelper
import com.gh.gamecenter.home.custom.viewholder.CustomHomeSlideListItemViewHolder
import com.gh.gamecenter.home.custom.viewholder.CustomHomeSubSlideListItemViewHolder
import com.lightgame.download.DownloadEntity
@ -31,8 +31,7 @@ class CustomHomeSlideListAdapter(
val snapHelper: PagerSnapHelper,
private val layoutManager: LinearLayoutManager,
private val hasCards: Boolean = false,
private val eventHelper: CommonContentCollectionEventHelper,
private val exposureInvoke: (Int, GameEntity?) -> ExposureEvent
private val listener: OnEventListener
) : CustomBaseChildAdapter<HomeSlide, ViewHolder>(context) {
private var recyclerView: RecyclerView? = null
@ -85,14 +84,17 @@ class CustomHomeSlideListAdapter(
var exposureEvent: ExposureEvent? = null
if (homeSlide.linkType == "game") {
runOnIoThread(true) {
exposureEvent = exposureInvoke(actualPosition, game)
exposureEvent = listener.createExposureEvent(actualPosition, game)
}
} else {
val event = exposureInvoke(actualPosition, game)
event.payload.controlType = "轮播图"
event.payload.controlName = homeSlide.title
event.payload.controlLinkName = homeSlide.linkText
event.payload.controlLinkType = homeSlide.linkType
val event = listener.createExposureEvent(actualPosition, game)
?.also {
it.payload.controlType = "轮播图"
it.payload.controlName = homeSlide.title
it.payload.controlLinkName = homeSlide.linkText
it.payload.controlLinkType = homeSlide.linkType
}
exposureEvent = event
}
game?.exposureEvent = exposureEvent
@ -164,7 +166,7 @@ class CustomHomeSlideListAdapter(
ExposureManager.log(clickEvent)
}
}
eventHelper.navigateToLinkPage(homeSlide.transformLinkEntity(), "轮播图", exposureEvent)
listener.navigateToLinkPage(homeSlide.transformLinkEntity(), "轮播图", exposureEvent)
}
gameView.setOnClickListener {
val actualPositionString = (actualPosition + 1).toString()
@ -178,7 +180,7 @@ class CustomHomeSlideListAdapter(
"新首页"
)
if (linkGame != null) {
eventHelper.navigateToGameDetailPage(actualPosition, linkGame, "轮播图")
listener.navigateToGameDetailPage(actualPosition, linkGame, "轮播图")
}
}
}
@ -230,8 +232,21 @@ class CustomHomeSlideListAdapter(
}
fun getDataPosition(position: Int): Int {
return position % dataCount
return if (dataCount > 0) {
position % dataCount
} else {
position
}
}
fun getActualSize() = dataList.size
interface OnEventListener {
fun createExposureEvent(actualPosition: Int, game: GameEntity?): ExposureEvent?
fun navigateToGameDetailPage(actualPosition: Int, game: GameEntity, text: String)
fun navigateToLinkPage(link: LinkEntity, text: String, exposureEvent: ExposureEvent?)
}
}

View File

@ -33,8 +33,6 @@ interface OnCustomPageEventListener {
fun onChangeAppBarColor(color: Int)
fun loadSlideDiscoverCardGames(homeSubSlide: HomeSubSlide)
/**
* 通过Game进入游戏详情
*/

View File

@ -351,7 +351,7 @@ class CustomPageData(
@SerializedName("_id")
private val _id: String? = null,
@SerializedName("name")
private val _name: String? = null,
private var _name: String? = null,
@SerializedName("detail_style")
private val _detailStyle: String? = null,
@SerializedName("layout")
@ -375,8 +375,11 @@ class CustomPageData(
val id: String
get() = _id ?: ""
val name: String
var name: String
get() = _name ?: ""
set(value) {
_name = value
}
val detailStyle: String
get() = _detailStyle ?: ""

View File

@ -0,0 +1,155 @@
package com.gh.gamecenter.home.custom.ui
import android.content.Context
import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.databinding.CommonCollection12ItemCustomBinding
import com.gh.gamecenter.databinding.CommonCollectionItemBinding
import com.gh.gamecenter.entity.CommonCollectionContentEntity
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_VERTICAL_CARD
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_VERTICAL_IMAGE_TEXT
import com.gh.gamecenter.home.custom.viewholder.CustomCommonCollectionViewHolder
/**
* ui-双列banner 双列竖式卡片 竖排图文列表
*/
class CommonContentCollection12Ui(
val binding: CommonCollection12ItemCustomBinding,
private val listener: OnCollection12Listener
) {
private val context: Context
get() = binding.root.context
fun bind(
collection: CustomPageData.CommonContentCollection,
leftPosition: Int
) {
val data = collection.data
val isFirstLine = leftPosition == 0
binding.layoutTitle.root.goneIf(!isFirstLine) {
binding.layoutTitle.tvTitle.setTextColor(R.color.text_primary.toColor(context))
}
CustomCommonCollectionViewHolder.setTitle(collection, binding.layoutTitle) {
listener.navigateToCommonCollectionDetailPage(collection)
}
val width = (DisplayUtils.getScreenWidth() - 16F.dip2px() * 2 - 8F.dip2px()) / 2
val height = if (collection.layout == COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_VERTICAL_CARD) {
width * 4 / 3
} else {
width / 2
}
val leftBinding = CommonCollectionItemBinding.bind(binding.leftView.root)
val rightBinding = CommonCollectionItemBinding.bind(binding.rightView.root)
bindSubView(
data[leftPosition],
leftBinding,
collection.layout,
width,
height,
leftPosition
)
val rightPosition = leftPosition + 1
if (rightPosition < data.size) {
binding.rightView.root.visibility = View.VISIBLE
bindSubView(
data[rightPosition],
rightBinding,
collection.layout,
width,
height,
rightPosition
)
} else {
binding.rightView.root.visibility = View.INVISIBLE
val imageParams = binding.rightView.commonCollectionImage.layoutParams as ConstraintLayout.LayoutParams
imageParams.width = width
imageParams.height = 1
binding.rightView.commonCollectionImage.layoutParams = imageParams
}
val isLastLine = leftPosition + 2 >= data.size
val _paddingBottom =
if (isLastLine || collection.layout == COMMON_CONTENT_COLLECTION_LAYOUT_VERTICAL_IMAGE_TEXT) {
16f.dip2px()
} else {
8f.dip2px()
}
with(binding.root) {
setPadding(paddingLeft, paddingTop, paddingRight, _paddingBottom)
}
}
private fun bindSubView(
contentEntity: CommonCollectionContentEntity,
subBinding: CommonCollectionItemBinding,
layout: Int,
width: Int,
height: Int,
position: Int
) {
subBinding.run {
ImageUtils.display(commonCollectionImage, contentEntity.image)
maskView.goneIf(layout == COMMON_CONTENT_COLLECTION_LAYOUT_VERTICAL_IMAGE_TEXT || (contentEntity.title.isEmpty() && contentEntity.addedContent1.isNullOrEmpty()))
titleTv.goneIf(layout == COMMON_CONTENT_COLLECTION_LAYOUT_VERTICAL_IMAGE_TEXT) {
titleTv.text = contentEntity.title
}
desTv.goneIf(layout != COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_VERTICAL_CARD) {
desTv.text = contentEntity.addedContent1
}
linkTitleTv.goneIf(layout != COMMON_CONTENT_COLLECTION_LAYOUT_VERTICAL_IMAGE_TEXT) {
linkTitleTv.text = contentEntity.title
}
linkDes1.goneIf(layout != COMMON_CONTENT_COLLECTION_LAYOUT_VERTICAL_IMAGE_TEXT) {
linkDes1.text = contentEntity.addedContent1
linkDes1.setTextColor(R.color.text_secondary.toColor(root.context))
}
linkDes2.goneIf(layout != COMMON_CONTENT_COLLECTION_LAYOUT_VERTICAL_IMAGE_TEXT) {
linkDes2.text = contentEntity.addedContent2
linkDes2.setTextColor(R.color.text_tertiary.toColor(root.context))
}
val maskHeight = if (layout == COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_VERTICAL_CARD) {
60f.dip2px()
} else {
38f.dip2px()
}
val maskParams = maskView.layoutParams as ConstraintLayout.LayoutParams
maskParams.height = maskHeight
maskView.layoutParams = maskParams
val imageParams = commonCollectionImage.layoutParams as ConstraintLayout.LayoutParams
imageParams.width = width
imageParams.height = height
commonCollectionImage.layoutParams = imageParams
root.setOnClickListener {
listener.onChildItemClick(position, contentEntity)
}
}
}
interface OnCollection12Listener {
fun onChildItemClick(childPosition: Int, contentEntity: CommonCollectionContentEntity)
fun navigateToCommonCollectionDetailPage(collection: CustomPageData.CommonContentCollection)
}
}

View File

@ -0,0 +1,280 @@
package com.gh.gamecenter.home.custom.ui
import android.content.Context
import android.view.MotionEvent
import android.view.View
import androidx.core.graphics.ColorUtils
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.gh.gamecenter.R
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.ScrollEventListener
import com.gh.gamecenter.common.view.TouchSlopRecyclerView
import com.gh.gamecenter.databinding.HomeSlideListCustomBinding
import com.gh.gamecenter.entity.HomeSlide
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.home.custom.BannerInRecyclerController
import com.gh.gamecenter.home.custom.adapter.CustomHomeSlideListAdapter
import splitties.views.verticalPadding
import kotlin.math.abs
/**
* 通用内容合集-轮播banner
* ui逻辑封装方便多个地方复用
*/
class CommonContentHomeSLideListUi(
private val binding: HomeSlideListCustomBinding,
lifecycleOwner: LifecycleOwner,
private val listener: OnCommonHomeSlideListEventListener
) {
private val context: Context
get() = binding.root.context
private var _slideList: List<HomeSlide>? = null
private var _currentPosition = -1
private var mLastX: Int = 0
private var mLastY: Int = 0
private val layoutManager by lazy {
LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
}
private val snapHelper by lazy {
PagerSnapHelper()
}
private val scrollEventListener by lazy {
ScrollEventListener(binding.recyclerView)
}
private val adapter by lazy {
CustomHomeSlideListAdapter(
context,
snapHelper,
layoutManager,
false,
object : CustomHomeSlideListAdapter.OnEventListener {
override fun createExposureEvent(actualPosition: Int, game: GameEntity?): ExposureEvent? {
return listener.createExposureEvent(actualPosition, game)
}
override fun navigateToGameDetailPage(actualPosition: Int, game: GameEntity, text: String) {
listener.navigateToGameDetailPage(actualPosition, game, text)
}
override fun navigateToLinkPage(link: LinkEntity, text: String, exposureEvent: ExposureEvent?) {
listener.navigateToLinkPage(link, text, exposureEvent)
}
})
}
private val bannerController = BannerInRecyclerController {
adapter.scrollToNextPage()
}
init {
binding.recyclerView.itemAnimator = null
lifecycleOwner.lifecycle.addObserver(bannerController)
}
fun bind(slideList: List<HomeSlide>, position: Int) {
val isFirst = position == 0
val paddingVertical = if (isFirst) {
8f.dip2px()
} else {
16f.dip2px()
}
binding.recyclerView.verticalPadding = paddingVertical
if (binding.recyclerView.adapter == null) {
binding.recyclerView.layoutManager = layoutManager
binding.recyclerView.adapter = adapter
snapHelper.attachToRecyclerView(binding.recyclerView)
binding.recyclerView.addOnItemTouchListener(object : RecyclerView.SimpleOnItemTouchListener() {
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
val x = e.x.toInt()
val y = e.y.toInt()
when (e.action) {
MotionEvent.ACTION_DOWN -> {
mLastX = e.x.toInt()
mLastY = e.y.toInt()
rv.parent.parent.requestDisallowInterceptTouchEvent(true)
}
MotionEvent.ACTION_UP -> rv.parent.parent.requestDisallowInterceptTouchEvent(
false
)
MotionEvent.ACTION_MOVE -> {
val deltaX: Int = x - mLastX
val deltaY: Int = y - mLastY
val isHorizontalScroll = abs(deltaX) > abs(deltaY)
rv.parent.parent.requestDisallowInterceptTouchEvent(isHorizontalScroll)
}
}
val isStop =
e.action == MotionEvent.ACTION_DOWN || e.action == MotionEvent.ACTION_MOVE
if (isStop) bannerController.pause() else bannerController.start()
val touchSlopRecyclerView = binding.recyclerView.parent.parent
if (touchSlopRecyclerView is TouchSlopRecyclerView) {
touchSlopRecyclerView.touchSlopEnabled = isStop
} else throwExceptionInDebug("TouchSlopRecyclerView not found")
return false
}
})
binding.recyclerView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
requestTransform()
}
binding.recyclerView.addOnScrollListener(scrollEventListener.apply {
setOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
var lastStatePosition = -1
var lastScrollState = RecyclerView.SCROLL_STATE_IDLE
override fun onPageScrollStateChanged(state: Int) {
super.onPageScrollStateChanged(state)
if (state == RecyclerView.SCROLL_STATE_IDLE) {
bannerController.start()
} else {
bannerController.pause()
}
if (scrollState == RecyclerView.SCROLL_STATE_IDLE) {
val view = snapHelper.findSnapView(layoutManager)
var curPosition = 0
if (view != null) {
curPosition = adapter.getDataPosition(layoutManager.getPosition(view))
_currentPosition = layoutManager.getPosition(view)
}
lastStatePosition = curPosition
lastScrollState = scrollState
listener.updateImmersiveColor(
slideList.safelyGetInRelease(curPosition)?.placeholderColor?.hexStringToIntColor()
?: R.color.ui_surface.toColor(binding.root.context)
)
} else if (scrollState == RecyclerView.SCROLL_STATE_DRAGGING) {
lastScrollState = scrollState
}
}
override fun onPageSelected(position: Int) {
_currentPosition = position
}
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
_currentPosition = position
val currentPosition = adapter.getDataPosition(position)
val nextPosition = adapter.getDataPosition(position + 1)
val currentColor =
slideList.getOrNull(currentPosition)?.placeholderColor?.hexStringToIntColor()
?: R.color.ui_surface.toColor(binding.root.context)
val nextColor =
slideList.getOrNull(nextPosition)?.placeholderColor?.hexStringToIntColor()
?: currentColor
val colorInBetween =
ColorUtils.blendARGB(currentColor, nextColor, positionOffset)
transformPage(position, positionOffset)
listener.updateImmersiveColor(colorInBetween)
}
})
})
adapter.submitList(slideList, true)
} else {
if (_slideList != slideList) {
adapter.checkResetData(slideList)
}
}
_slideList = slideList
val currentPosition = if (_currentPosition == -1) {
adapter.getInitPosition()
} else {
_currentPosition
}
_currentPosition = currentPosition
layoutManager.scrollToPosition(position)
val dataPosition = adapter.getDataPosition(currentPosition)
listener.updateImmersiveColor(
slideList.getOrNull(dataPosition)?.placeholderColor?.hexStringToIntColor()
?: R.color.ui_surface.toColor(binding.root.context)
)
bannerController.start()
}
private fun requestTransform() {
val relativePosition: Double = scrollEventListener.relativeScrollPosition
val position = relativePosition.toInt()
val positionOffset = (relativePosition - position).toFloat()
transformPage(position, positionOffset)
}
private fun transformPage(position: Int, positionOffset: Float) {
val transformOffset = -positionOffset
for (i in 0 until layoutManager.childCount) {
val view: View = layoutManager.getChildAt(i) ?: return
val currPos: Int = layoutManager.getPosition(view)
val viewOffset = transformOffset + (currPos - position)
val scaleY = when {
viewOffset <= -1 -> {
0.9f
}
viewOffset < 0 -> {
(1 + viewOffset) * 0.1f + 0.9f
}
viewOffset < 1 -> {
(1 - viewOffset) * 0.1f + 0.9f
}
else -> {
0.9f
}
}
view.scaleY = scaleY
}
}
fun onViewAttach(parent: RecyclerView?) {
bannerController.onViewAttachedToWindow(parent)
}
fun onViewDetach(parent: RecyclerView?) {
bannerController.onViewDetachedFromWindow(parent)
}
interface OnCommonHomeSlideListEventListener {
fun navigateToLinkPage(link: LinkEntity, text: String, exposureEvent: ExposureEvent?)
fun navigateToGameDetailPage(position: Int, game: GameEntity, text: String)
fun updateImmersiveColor(color: Int)
fun createExposureEvent(childPosition: Int, game: GameEntity?): ExposureEvent?
}
}

View File

@ -0,0 +1,527 @@
package com.gh.gamecenter.home.custom.ui
import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Typeface
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.graphics.ColorUtils
import androidx.core.view.isVisible
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.FixLinearLayoutManager
import com.gh.gamecenter.common.view.ScrollEventListener
import com.gh.gamecenter.common.view.TouchSlopRecyclerView
import com.gh.gamecenter.core.utils.AlphaGradientProcess
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.databinding.HomeSlideCardItemCustomBinding
import com.gh.gamecenter.databinding.HomeSlideWithCardsCustomBinding
import com.gh.gamecenter.entity.HomeSlide
import com.gh.gamecenter.entity.HomeSubSlide
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.home.custom.BannerInRecyclerController
import com.gh.gamecenter.home.custom.CommonContentCollectionUseCase
import com.gh.gamecenter.home.custom.adapter.CustomHomeSlideListAdapter
import com.gh.gamecenter.home.custom.model.CustomPageData
import kotlin.math.abs
/**
* 通用内容合集-轮播banner
* ui逻辑封装方便多个地方复用
*/
class CommonContentHomeSlideWithCardsUi(
private val useCase: CommonContentCollectionUseCase,
private val lifecycleOwner: LifecycleOwner,
val binding: HomeSlideWithCardsCustomBinding,
private val listener: HomeSLideWithCardsEventListener
) {
private val context: Context
get() = binding.root.context
private val layoutManager by lazy {
FixLinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
}
private val snapHelper by lazy {
PagerSnapHelper()
}
private val adapter by lazy {
CustomHomeSlideListAdapter(
context,
snapHelper,
layoutManager,
true,
object : CustomHomeSlideListAdapter.OnEventListener {
override fun createExposureEvent(actualPosition: Int, game: GameEntity?): ExposureEvent? {
return listener.createExposureEvent(actualPosition, game)
}
override fun navigateToGameDetailPage(actualPosition: Int, game: GameEntity, text: String) {
listener.navigateToGameDetailPage(actualPosition, game, text)
}
override fun navigateToLinkPage(link: LinkEntity, text: String, exposureEvent: ExposureEvent?) {
listener.navigateToLinkPage(link, text, exposureEvent)
}
})
}
private val bannerController = BannerInRecyclerController {
adapter.scrollToNextPage()
}
private var mLastX: Int = 0
private var mLastY: Int = 0
private var _currentPosition = -1
private var firstSubSlide: HomeSubSlide? = null
private var secondSubSlide: HomeSubSlide? = null
private var _slideList: List<HomeSlide>? = null
init {
binding.recyclerView.itemAnimator = null
lifecycleOwner.lifecycle.addObserver(bannerController)
}
fun bind(collection: CustomPageData.CommonContentCollection, position: Int) {
val isFirst = position == 0
val bannerTopMargin = if (isFirst) {
8f.dip2px()
} else {
16f.dip2px()
}
val layoutParams = binding.bannerCv.layoutParams as? ViewGroup.MarginLayoutParams
layoutParams?.let {
it.topMargin = bannerTopMargin
it.bottomMargin = if (isFirst) 16f.dip2px() else 24f.dip2px()
binding.bannerCv.layoutParams = it
}
val rootLayoutParams = binding.root.layoutParams as? ViewGroup.MarginLayoutParams
rootLayoutParams?.let {
it.bottomMargin = -8f.dip2px()
binding.root.layoutParams = it
}
bindSlide(collection.slides.slide)
if (collection.slides.subSlide.isNotEmpty()) {
bindCards(isFirst, collection, collection.slides.subSlide)
}
}
private fun bindSlide(
slideList: List<HomeSlide>
) {
if (binding.recyclerView.adapter == null) {
binding.recyclerView.layoutManager = layoutManager
binding.recyclerView.adapter = adapter
snapHelper.attachToRecyclerView(binding.recyclerView)
binding.recyclerView.addOnItemTouchListener(object : RecyclerView.SimpleOnItemTouchListener() {
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
val x = e.x.toInt()
val y = e.y.toInt()
when (e.action) {
MotionEvent.ACTION_DOWN -> {
mLastX = e.x.toInt()
mLastY = e.y.toInt()
rv.parent.parent.requestDisallowInterceptTouchEvent(true)
}
MotionEvent.ACTION_UP -> rv.parent.parent.requestDisallowInterceptTouchEvent(
false
)
MotionEvent.ACTION_MOVE -> {
val deltaX: Int = x - mLastX
val deltaY: Int = y - mLastY
val isHorizontalScroll = abs(deltaX) > abs(deltaY)
rv.parent.parent.requestDisallowInterceptTouchEvent(isHorizontalScroll)
}
}
val isStop =
e.action == MotionEvent.ACTION_DOWN || e.action == MotionEvent.ACTION_MOVE
if (isStop) bannerController.pause() else bannerController.start()
val touchSlopRecyclerView = binding.recyclerView.parent.parent.parent
if (touchSlopRecyclerView is TouchSlopRecyclerView) {
touchSlopRecyclerView.touchSlopEnabled = isStop
} else throwExceptionInDebug("TouchSlopRecyclerView not found")
return false
}
})
binding.recyclerView.addOnScrollListener(ScrollEventListener(binding.recyclerView).apply {
setOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
var lastStatePosition = -1
var lastScrollState = RecyclerView.SCROLL_STATE_IDLE
override fun onPageSelected(position: Int) {
_currentPosition = position
}
override fun onPageScrollStateChanged(state: Int) {
super.onPageScrollStateChanged(state)
if (scrollState == RecyclerView.SCROLL_STATE_IDLE) {
val view = snapHelper.findSnapView(layoutManager)
var curPosition = 0
if (view != null) {
curPosition = adapter.getDataPosition(layoutManager.getPosition(view))
_currentPosition = layoutManager.getPosition(view)
}
if (lastScrollState == RecyclerView.SCROLL_STATE_DRAGGING && curPosition != lastStatePosition) {
MtaHelper.onEvent(
"首页_新",
"轮播_滑动",
if (curPosition > lastStatePosition) "左滑" else "右滑"
)
}
lastStatePosition = curPosition
lastScrollState = scrollState
listener.updateImmersiveColor(
slideList.safelyGetInRelease(curPosition)?.placeholderColor?.hexStringToIntColor()
?: R.color.ui_surface.toColor(binding.root.context)
)
} else if (scrollState == RecyclerView.SCROLL_STATE_DRAGGING) {
lastScrollState = scrollState
}
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels)
_currentPosition = position
binding.bannerIndicator.onPageScrolled(
adapter.getDataPosition(position),
positionOffset
)
val currentPosition = adapter.getDataPosition(position)
val nextPosition = adapter.getDataPosition(position + 1)
val currentColor =
slideList.safelyGetInRelease(currentPosition)?.placeholderColor?.hexStringToIntColor()
?: R.color.ui_surface.toColor(binding.root.context)
val nextColor =
slideList.safelyGetInRelease(nextPosition)?.placeholderColor?.hexStringToIntColor()
?: currentColor
val colorInBetween =
ColorUtils.blendARGB(currentColor, nextColor, positionOffset)
listener.updateImmersiveColor(colorInBetween)
}
})
})
adapter.submitList(slideList)
} else {
if (_slideList != slideList) {
adapter.checkResetData(slideList)
}
}
_slideList = slideList
val currentPosition = if (_currentPosition == -1) {
adapter.getInitPosition()
} else {
_currentPosition
}
binding.recyclerView.scrollToPosition(currentPosition)
binding.bannerIndicator.run {
pageSize = adapter.getActualSize()
notifyDataChanged()
}
val dataPosition = adapter.getDataPosition(currentPosition)
listener.updateImmersiveColor(
slideList.safelyGetInRelease(dataPosition)?.placeholderColor?.hexStringToIntColor()
?: R.color.ui_surface.toColor(binding.root.context)
)
bannerController.start()
}
private fun bindCards(
isFirst: Boolean,
data: CustomPageData.CommonContentCollection,
slideList: List<HomeSubSlide>
) {
val firstSubSlidePosition = (useCase.refreshCount * 2) % slideList.size
val secondSubSlidePosition = (useCase.refreshCount * 2 + 1) % slideList.size
slideList.getOrNull(firstSubSlidePosition)?.let {
firstSubSlide = it
it.sequence = firstSubSlidePosition
it.repeatCount = (useCase.refreshCount * 2) / slideList.size
bindCard(isFirst, data, binding.firstCv, it, 0)
}
slideList.getOrNull(secondSubSlidePosition)?.let {
secondSubSlide = it
it.sequence = secondSubSlidePosition
it.repeatCount = (useCase.refreshCount * 2 + 1) / slideList.size
bindCard(isFirst, data, binding.secondCv, it, 1)
}
}
@SuppressLint("SetTextI18n")
private fun bindCard(
isFirst: Boolean,
data: CustomPageData.CommonContentCollection,
homeSlideCardItemBinding: HomeSlideCardItemCustomBinding,
homeSubSlide: HomeSubSlide,
position: Int
) {
val cardCv = homeSlideCardItemBinding.root
val cardContainer = homeSlideCardItemBinding.cardContainer
val cardIv = homeSlideCardItemBinding.cardIv
val backgroundGroup = homeSlideCardItemBinding.backgroundGroup
val titleTv = homeSlideCardItemBinding.titleTv
val descTv = homeSlideCardItemBinding.descTv
val textContainer = homeSlideCardItemBinding.textContainer
val countTv = homeSlideCardItemBinding.countTv
val labelIv = homeSlideCardItemBinding.labelIv
val gameIconStackIv1 = homeSlideCardItemBinding.gameIconStackIv1
val gameIconStackIv2 = homeSlideCardItemBinding.gameIconStackIv2
val gameIconStackIv3 = homeSlideCardItemBinding.gameIconStackIv3
val gameIconStackGroup = listOf(gameIconStackIv1, gameIconStackIv2, gameIconStackIv3)
val gameIconIv1 = homeSlideCardItemBinding.gameIconIv1
val gameIconIv2 = homeSlideCardItemBinding.gameIconIv2
val gameIconIv3 = homeSlideCardItemBinding.gameIconIv3
val gameIconGroup = listOf(gameIconIv1, gameIconIv2, gameIconIv3)
titleTv.text = homeSubSlide.title
titleTv.setTextColor(R.color.text_primary.toColor(titleTv.context))
descTv.setTextColor(R.color.text_secondary.toColor(descTv.context))
listener.addGameExposureEvent(
position,
GameEntity(sequence = position, outerSequence = useCase.refreshCount),
homeSubSlide.id
)
// 获取发现卡片游戏
if (homeSubSlide.cardType == "game_explore" && homeSubSlide.cardData.games.isEmpty()) {
useCase.slideDiscoveryCardGames.observe(lifecycleOwner, object : Observer<List<GameEntity>> {
override fun onChanged(t: List<GameEntity>?) {
val currentSubSlide = if (position == 0) firstSubSlide else secondSubSlide
if (t != null && homeSubSlide == currentSubSlide) {
bindCard(
isFirst,
data,
homeSlideCardItemBinding,
homeSubSlide.apply { cardData.games = t },
position
)
useCase.slideDiscoveryCardGames.removeObserver(this)
}
}
})
useCase.loadSlideDiscoverCardGames(homeSubSlide)
return
}
when (homeSubSlide.cardType) {
"column",
"column_test_v2",
"server",
"game_explore" -> {
titleTv.visibility = View.VISIBLE
descTv.visibility = View.VISIBLE
countTv.visibility = View.GONE
labelIv.visibility = View.GONE
backgroundGroup.visibility = View.GONE
gameIconStackGroup.forEach { it.isVisible = homeSubSlide.cardData.games.isNotEmpty() }
gameIconGroup.forEach { it.visibility = View.GONE }
descTv.text = homeSubSlide.cardDesc
if (homeSubSlide.cardData.games.size == 1) {
gameIconStackIv2.visibility = View.GONE
gameIconStackIv1.visibility = View.GONE
}
if (homeSubSlide.cardData.games.size == 2) {
gameIconStackIv1.visibility = View.GONE
}
homeSubSlide.cardData.games.take(3).forEachIndexed { index, gameEntity ->
listener.addGameExposureEvent(position, gameEntity, homeSubSlide.id)
when (index) {
0 -> gameIconStackIv3.displayGameIcon(gameEntity, true)
1 -> gameIconStackIv2.displayGameIcon(gameEntity, true)
2 -> gameIconStackIv1.displayGameIcon(gameEntity, true)
}
}
ConstraintSet().apply {
clone(cardContainer)
clear(textContainer.id, ConstraintSet.TOP)
clear(textContainer.id, ConstraintSet.BOTTOM)
connect(textContainer.id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP)
connect(textContainer.id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM)
}.applyTo(cardContainer)
}
"game_list_detail" -> {
titleTv.visibility = View.VISIBLE
descTv.visibility = View.GONE
backgroundGroup.visibility = View.GONE
gameIconStackGroup.forEach { it.visibility = View.GONE }
gameIconGroup.forEach { it.isVisible = homeSubSlide.cardData.games.isNotEmpty() }
if (homeSubSlide.cardData.games.size == 1) {
gameIconIv2.visibility = View.GONE
gameIconIv3.visibility = View.GONE
}
if (homeSubSlide.cardData.games.size == 2) {
gameIconIv3.visibility = View.GONE
}
homeSubSlide.cardData.games.take(3).forEachIndexed { index, gameEntity ->
listener.addGameExposureEvent(position, gameEntity, homeSubSlide.id)
when (index) {
0 -> {
gameIconIv1.displayGameIcon(gameEntity, true)
gameIconIv1.setOnClickListener {
com.gh.common.util.NewFlatLogUtils.logRightSideCardClick(
homeSubSlide,
useCase.refreshCount,
"游戏"
)
listener.navigateToGameDetailPage(index, gameEntity, "右侧卡片")
}
}
1 -> {
gameIconIv2.displayGameIcon(gameEntity, true)
gameIconIv2.setOnClickListener {
com.gh.common.util.NewFlatLogUtils.logRightSideCardClick(
homeSubSlide,
useCase.refreshCount,
"游戏"
)
listener.navigateToGameDetailPage(index, gameEntity, "右侧卡片")
}
}
2 -> {
gameIconIv3.displayGameIcon(gameEntity, true)
gameIconIv3.setOnClickListener {
com.gh.common.util.NewFlatLogUtils.logRightSideCardClick(
homeSubSlide,
useCase.refreshCount,
"游戏"
)
listener.navigateToGameDetailPage(index, gameEntity, "右侧卡片")
}
}
}
}
countTv.isVisible = homeSubSlide.cardData.gameTotal.game > 3
countTv.typeface = Typeface.createFromAsset(countTv.context.assets, Constants.DIN_FONT_PATH)
countTv.text = "+${homeSubSlide.cardData.gameTotal.game - 3}"
labelIv.isVisible = homeSubSlide.cardData.stamp.isNotEmpty()
labelIv.setImageDrawable(
when (homeSubSlide.cardData.stamp) {
"official" -> R.drawable.label_official.toDrawable(labelIv.context)
"special_choice" -> R.drawable.label_premium.toDrawable(labelIv.context)
else -> null
}
)
ConstraintSet().apply {
clone(cardContainer)
clear(textContainer.id, ConstraintSet.TOP)
clear(textContainer.id, ConstraintSet.BOTTOM)
connect(
textContainer.id,
ConstraintSet.TOP,
ConstraintSet.PARENT_ID,
ConstraintSet.TOP,
8F.dip2px()
)
if (homeSubSlide.cardData.stamp.isEmpty()) {
clear(countTv.id, ConstraintSet.START)
connect(countTv.id, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END, 10F.dip2px())
} else {
clear(countTv.id, ConstraintSet.END)
connect(countTv.id, ConstraintSet.START, gameIconIv3.id, ConstraintSet.END, 4F.dip2px())
}
}.applyTo(cardContainer)
}
"column_collection", "common_collection" -> {
titleTv.visibility = View.VISIBLE
descTv.visibility = View.VISIBLE
countTv.visibility = View.GONE
labelIv.visibility = View.GONE
backgroundGroup.visibility = View.VISIBLE
val visibility = if (isFirst) View.VISIBLE else View.GONE
homeSlideCardItemBinding.cardMask.visibility = visibility
homeSlideCardItemBinding.cardGradientMask.visibility = visibility
homeSlideCardItemBinding.cardIv.visibility = View.VISIBLE
gameIconStackGroup.forEach { it.visibility = View.GONE }
gameIconGroup.forEach { it.visibility = View.GONE }
descTv.text = homeSubSlide.cardDesc
// 防止重复加载图片导致闪烁
if (homeSubSlide.image != cardIv.getTag(R.string.tag_img_url_id)) {
ImageUtils.display(cardIv, homeSubSlide.image, false, AlphaGradientProcess())
cardIv.setTag(R.string.tag_img_url_id, homeSubSlide.image)
}
ConstraintSet().apply {
clone(cardContainer)
clear(textContainer.id, ConstraintSet.TOP)
clear(textContainer.id, ConstraintSet.BOTTOM)
connect(textContainer.id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP)
connect(textContainer.id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM)
}.applyTo(cardContainer)
}
}
cardCv.setOnClickListener {
com.gh.common.util.NewFlatLogUtils.logRightSideCardClick(homeSubSlide, useCase.refreshCount, "卡片")
listener.navigateToLinkPageInSubSlide(
GameEntity(sequence = position, outerSequence = useCase.refreshCount),
homeSubSlide,
"右侧卡片"
)
}
}
fun onViewAttach(parent: RecyclerView?) {
bannerController.onViewAttachedToWindow(parent)
}
fun onViewDetach(parent: RecyclerView?) {
bannerController.onViewDetachedFromWindow(parent)
}
interface HomeSLideWithCardsEventListener {
fun updateImmersiveColor(color: Int)
fun createEventWithSourceConcat(game: GameEntity, subSlideId: String)
fun createExposureEvent(actualPosition: Int, game: GameEntity?): ExposureEvent?
fun addGameExposureEvent(position: Int, game: GameEntity, subSlideId: String)
fun navigateToGameDetailPage(childPosition: Int, gameEntity: GameEntity, text: String)
fun navigateToLinkPageInSubSlide(game: GameEntity, homeSubSlide: HomeSubSlide, text: String)
fun navigateToLinkPage(link: LinkEntity, text: String, exposureEvent: ExposureEvent?)
}
}

View File

@ -0,0 +1,110 @@
package com.gh.gamecenter.home.custom.ui
import android.content.Context
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.view.SpacingItemDecoration
import com.gh.gamecenter.databinding.CommonCollectionListCustomBinding
import com.gh.gamecenter.databinding.LayoutTitleCustomBinding
import com.gh.gamecenter.entity.CommonCollectionContentEntity
import com.gh.gamecenter.entity.ExposureLinkEntity
import com.gh.gamecenter.home.custom.adapter.CustomCommonCollectionAdapter
import com.gh.gamecenter.home.custom.model.CustomPageData
/**
* ui-通用内容合集-横排滑动Banner
* ui-通用内容合集-横排竖式卡片
* ui-通用内容合集-横排图文列表
*/
class CommonContentHorizontalSlideListUi(
val binding: CommonCollectionListCustomBinding,
private val listener: OnHorizontalSlideListListener
) {
private val context: Context
get() = binding.root.context
private val layoutManager by lazy {
LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)
}
private val adapter by lazy {
CustomCommonCollectionAdapter(context, object : CustomCommonCollectionAdapter.OnEventListener {
override fun addExposureEvent(childPosition: Int, exposureLinkEntity: ExposureLinkEntity) {
listener.addExposureEvent(childPosition, exposureLinkEntity)
}
override fun onChildItemClick(childPosition: Int, item: CommonCollectionContentEntity) {
listener.onChildItemClick(childPosition, item)
}
})
}
fun bind(data: CustomPageData.CommonContentCollection) {
binding.layoutTitle.tvTitle.setTextColor(R.color.text_primary.toColor(context))
setTitle(data, binding.layoutTitle) {
listener.navigateToCommonCollectionDetailPage(data)
}
if (binding.collectionList.adapter == null) {
binding.collectionList.addItemDecoration(
SpacingItemDecoration(
notDecorateTheFirstItem = true,
left = 8f.dip2px()
)
)
binding.collectionList.layoutManager = layoutManager
binding.collectionList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
var position = layoutManager.findLastCompletelyVisibleItemPosition()
if (position == -1) position = layoutManager.findLastVisibleItemPosition() - 1
if (position < 0) return
}
}
})
binding.collectionList.adapter = adapter
}
adapter.setData(data)
}
companion object {
fun setTitle(
data: CustomPageData.CommonContentCollection,
layoutTitle: LayoutTitleCustomBinding,
trackInvoke: () -> Unit
) {
with(layoutTitle) {
lavRefresh.goneIf(true)
gUser.goneIf(true)
tvTitle.text = data.name
if (data.topRightText.isEmpty()) {
tvRight.goneIf(true)
} else {
tvRight.goneIf(false)
tvRight.text = data.topRightText
tvRight.setOnClickListener {
trackInvoke()
}
}
}
}
}
interface OnHorizontalSlideListListener {
fun addExposureEvent(childPosition: Int, link: ExposureLinkEntity)
fun onChildItemClick(childPosition: Int, entity: CommonCollectionContentEntity)
fun navigateToCommonCollectionDetailPage(data: CustomPageData.CommonContentCollection)
}
}

View File

@ -0,0 +1,55 @@
package com.gh.gamecenter.home.custom.ui
import android.content.Context
import androidx.recyclerview.widget.GridLayoutManager
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.view.GridSpacingItemDecoration
import com.gh.gamecenter.databinding.RecyclerNavigationCustomBinding
import com.gh.gamecenter.entity.GameNavigationEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.home.custom.adapter.CustomGameNavigationAdapter
class CommonContentNavigationUi(
val binding: RecyclerNavigationCustomBinding,
private val listener: OnNavigationListener
) {
private val context: Context
get() = binding.root.context
private val layoutManager by lazy {
GridLayoutManager(context, 4)
}
private val adapter by lazy {
CustomGameNavigationAdapter(context, object : CustomGameNavigationAdapter.OnEventListener {
override fun navigateToLinkPage(link: LinkEntity, text: String, exposureEvent: ExposureEvent?) {
listener.navigateToLinkPage(link, text, exposureEvent)
}
})
}
fun bind(navigationList: List<GameNavigationEntity>?, trackEventList: List<ExposureEvent>?) {
if (binding.rvNavigation.adapter == null) {
binding.rvNavigation.layoutManager = layoutManager
binding.rvNavigation.addItemDecoration(GridSpacingItemDecoration(4, 8f.dip2px(), false, 16f.dip2px()))
binding.rvNavigation.adapter = adapter
binding.rvNavigation.isNestedScrollingEnabled = false
}
if (!navigationList.isNullOrEmpty()) {
adapter.setData(navigationList, trackEventList)
}
}
interface OnNavigationListener {
fun navigateToLinkPage(link: LinkEntity, text: String, exposureEvent: ExposureEvent?)
}
}

View File

@ -0,0 +1,75 @@
package com.gh.gamecenter.home.custom.ui
import android.widget.GridView
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.databinding.ItemHomeRecommendListCustomBinding
import com.gh.gamecenter.entity.HomeRecommend
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.home.custom.adapter.CustomHomeRecommendItemGridAdapter
import com.gh.gamecenter.home.custom.createExposureEvent
import com.gh.gamecenter.home.custom.eventlistener.CommonContentCollectionEventHelper
import com.gh.gamecenter.home.custom.model.CustomCommonContentCollectionItem
import com.gh.gamecenter.home.custom.model.CustomPageItem
/**
* ui-通用内容合集-金刚区
*/
class CommonContentRecommendUi(
val binding: ItemHomeRecommendListCustomBinding,
private val listener: OnRecommendListener
) {
private val adapter by lazy {
CustomHomeRecommendItemGridAdapter(object : CustomHomeRecommendItemGridAdapter.OnEventListener {
override fun navigateToLinkPage(link: LinkEntity, text: String, exposureEvent: ExposureEvent?) {
listener.navigateToLinkPage(link, text, exposureEvent)
}
})
}
/**
* 数据处理
* 当后台“日常管理(新)-首页推荐入口(新)/多渠道多版本-首页推荐入口”配置的可显示的内容≥10个时前端需要支持展示两行首页推荐入口
* 1 如果 6≤推荐入口数量10 ,前端均仅显示一行推荐入口的内容
* 2 如果 推荐入口数量5 ,还是保留原有的规则平铺展示
*/
private fun mapData(recommends: List<HomeRecommend>): List<HomeRecommend> {
val column = (recommends.size / MAX_SPAN_COUNT) // 显示几行
val count = if (column == 0) {
recommends.count()
} else {
column * MAX_SPAN_COUNT
}
return recommends.take(minOf(10, count))
}
fun bind(recommends: List<HomeRecommend>, exposureList: List<ExposureEvent>?) {
val filteredList = mapData(recommends)
val size = recommends.size
if (size == 0) return
if (binding.recommendGv.adapter == null) {
val spanCount = minOf(size, MAX_SPAN_COUNT)
binding.recommendGv.stretchMode = GridView.STRETCH_SPACING
binding.recommendGv.numColumns = spanCount
binding.recommendGv.columnWidth = 60F.dip2px()
}
binding.recommendGv.adapter = adapter
adapter.submitList(filteredList, exposureList)
}
companion object {
//图标最多列数
private const val MAX_SPAN_COUNT = 5
}
interface OnRecommendListener {
fun navigateToLinkPage(link: LinkEntity, text: String, exposureEvent: ExposureEvent?)
}
}

View File

@ -17,6 +17,7 @@ import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.home.PageConfigure
import com.gh.gamecenter.home.custom.CustomPageViewModel
import com.gh.gamecenter.home.custom.IGameChangedNotifier
import com.gh.gamecenter.home.custom.OnViewHolderAttachListener
import com.gh.gamecenter.home.custom.eventlistener.CustomPageItemChildEventHelper
import com.gh.gamecenter.home.custom.eventlistener.GameSubjectCollectionEventHelper
import com.gh.gamecenter.home.custom.eventlistener.SubjectEventHelper
@ -29,7 +30,7 @@ import com.lightgame.download.DownloadEntity
abstract class BaseCustomViewHolder(
val viewModel: CustomPageViewModel,
itemView: View
) : RecyclerView.ViewHolder(itemView), IGameChangedNotifier, IExposable {
) : RecyclerView.ViewHolder(itemView), IGameChangedNotifier, IExposable, OnViewHolderAttachListener {
protected val pageConfigure: PageConfigure
get() = viewModel.pageConfigure
@ -82,10 +83,6 @@ abstract class BaseCustomViewHolder(
gameChangedNotifier?.notifyInstalled(busFour)
}
open fun onViewAttach(parent: RecyclerView?) = Unit
open fun onViewDetach(parent: RecyclerView?) = Unit
fun setSplitSubjectTitle(
titleBinding: LayoutTitleCustomBinding,
item: CustomSplitSubjectItem,

View File

@ -1,6 +1,5 @@
package com.gh.gamecenter.home.custom.viewholder
import android.view.View
import androidx.constraintlayout.widget.ConstraintLayout
import com.gh.common.exposure.ExposureManager
import com.gh.common.exposure.ExposureTraceUtils
@ -11,7 +10,6 @@ import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.databinding.CommonCollection12ItemCustomBinding
import com.gh.gamecenter.databinding.CommonCollectionItemBinding
import com.gh.gamecenter.entity.CommonCollectionContentEntity
@ -21,10 +19,12 @@ import com.gh.gamecenter.feature.exposure.ExposureType
import com.gh.gamecenter.home.custom.CustomPageViewModel
import com.gh.gamecenter.home.custom.createExposureEvent
import com.gh.gamecenter.home.custom.eventlistener.CommonContentCollectionEventHelper
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.model.CustomPageItem
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_VERTICAL_CARD
import com.gh.gamecenter.home.custom.model.CustomPageItem.Companion.COMMON_CONTENT_COLLECTION_LAYOUT_VERTICAL_IMAGE_TEXT
import com.gh.gamecenter.home.custom.model.CustomSplitCommonContentCollectionItem
import com.gh.gamecenter.home.custom.ui.CommonContentCollection12Ui
/**
* 双列banner 双列竖式卡片 竖排图文列表
@ -40,46 +40,59 @@ class CustomCommonCollection12ViewHolder(
CommonContentCollectionEventHelper(viewModel)
}
private val clickInvoke = { childPosition: Int, contentEntity: CommonCollectionContentEntity ->
val item = _item as CustomSplitCommonContentCollectionItem
val collection = item.data
val linkEntity = contentEntity.linkEntity
NewLogUtils.logCommonCollectionClick(
collection.id,
collection.name,
"blockData?.link",
"blockData?.name",
"板块内容列表",
"合集首页",
linkEntity.title ?: "",
contentEntity.addedContent1 ?: "",
contentEntity.addedContent2 ?: "",
linkEntity.type ?: "",
linkEntity.text ?: "",
childPosition + 1
)
NewLogUtils.logCommonCategoryHomeContentClick(
contentEntity.title,
linkEntity.type ?: "",
linkEntity.link ?: "",
linkEntity.text ?: "",
collection.name,
collection.id,
"板块",
"blockData?.name"
)
item.exposureEventList.getOrNull(childPosition)?.let {
val clickEvent = ExposureEvent(
payload = it.payload,
source = it.source,
eTrace = ExposureTraceUtils.appendTrace(it),
event = ExposureType.CLICK
)
if (linkEntity.type != "game") {
ExposureManager.log(clickEvent)
private val collection12Ui by lazy {
CommonContentCollection12Ui(binding, object : CommonContentCollection12Ui.OnCollection12Listener {
override fun onChildItemClick(childPosition: Int, contentEntity: CommonCollectionContentEntity) {
val item = _item as CustomSplitCommonContentCollectionItem
val collection = item.data
val linkEntity = contentEntity.linkEntity
NewLogUtils.logCommonCollectionClick(
collection.id,
collection.name,
"blockData?.link",
"blockData?.name",
"板块内容列表",
"合集首页",
linkEntity.title ?: "",
contentEntity.addedContent1 ?: "",
contentEntity.addedContent2 ?: "",
linkEntity.type ?: "",
linkEntity.text ?: "",
childPosition + 1
)
NewLogUtils.logCommonCategoryHomeContentClick(
contentEntity.title,
linkEntity.type ?: "",
linkEntity.link ?: "",
linkEntity.text ?: "",
collection.name,
collection.id,
"板块",
"blockData?.name"
)
item.exposureEventList.getOrNull(childPosition)?.let {
val clickEvent = ExposureEvent(
payload = it.payload,
source = it.source,
eTrace = ExposureTraceUtils.appendTrace(it),
event = ExposureType.CLICK
)
if (linkEntity.type != "game") {
ExposureManager.log(clickEvent)
}
}
childEventHelper.navigateToLinkPage(
linkEntity,
"内容卡片",
item.exposureEventList.getOrNull(childPosition)
)
}
}
childEventHelper.navigateToLinkPage(linkEntity, "内容卡片", item.exposureEventList.getOrNull(childPosition))
override fun navigateToCommonCollectionDetailPage(collection: CustomPageData.CommonContentCollection) {
childEventHelper.navigateToCommonCollectionDetailPage(_item.link)
}
})
}
override fun bindView(item: CustomPageItem) {
@ -87,92 +100,45 @@ class CustomCommonCollection12ViewHolder(
if (item is CustomSplitCommonContentCollectionItem) {
item.exposureEventList.clear()
item.data.data.forEachIndexed { index, contentEntity ->
val gameEntity = if (contentEntity.linkEntity.type == "game") {
GameEntity(id = contentEntity.linkEntity.link, name = contentEntity.linkEntity.text)
} else {
GameEntity()
}
val event = createExposureEvent(
gameEntity.also {
it.sequence = index
it.outerSequence = item.position
},
listOf(
ExposureSource(
"通用内容合集",
"${item.data.name}+${item.data.layoutChinese}+${item.data.id}"
)
),
pageConfigure.exposureSourceList,
index,
item.componentPosition
).also { contentEntity.linkEntity.exposureEvent = it }
item.exposureEventList.add(event)
}
binding.layoutTitle.root.goneIf(!item.isFirstLine) {
setTitleStyle(binding.layoutTitle)
}
CustomCommonCollectionViewHolder.setTitle(item.data, binding.layoutTitle) {
childEventHelper.navigateToCommonCollectionDetailPage(item.link)
}
val collection = item.data
val data = collection.data
val width = (DisplayUtils.getScreenWidth() - 16F.dip2px() * 2 - 8F.dip2px()) / 2
val height = if (collection.layout == COMMON_CONTENT_COLLECTION_LAYOUT_DOUBLE_ROW_VERTICAL_CARD) {
width * 4 / 3
} else {
width / 2
}
val leftBinding = CommonCollectionItemBinding.bind(binding.leftView.root)
val rightBinding = CommonCollectionItemBinding.bind(binding.rightView.root)
bindSubView(
data[item.leftPosition],
leftBinding,
collection.layout,
width,
height,
item.leftPosition,
clickInvoke
)
val data = item.data.data
val leftPosition = item.leftPosition
addExposureEvent(leftPosition, data[leftPosition])
val rightPosition = item.leftPosition + 1
if (rightPosition < data.size) {
binding.rightView.root.visibility = View.VISIBLE
bindSubView(
data[rightPosition],
rightBinding,
collection.layout,
width,
height,
rightPosition,
clickInvoke
)
addExposureEvent(rightPosition, data[rightPosition])
}
item.isFirstLine
collection12Ui.bind(item.data, item.leftPosition)
}
}
private fun addExposureEvent(childPosition: Int, contentEntity: CommonCollectionContentEntity) {
(_item as? CustomSplitCommonContentCollectionItem)?.let { item ->
val gameEntity = if (contentEntity.linkEntity.type == "game") {
GameEntity(id = contentEntity.linkEntity.link, name = contentEntity.linkEntity.text)
} else {
binding.rightView.root.visibility = View.INVISIBLE
val imageParams = binding.rightView.commonCollectionImage.layoutParams as ConstraintLayout.LayoutParams
imageParams.width = width
imageParams.height = 1
binding.rightView.commonCollectionImage.layoutParams = imageParams
GameEntity()
}
val event = createExposureEvent(
gameEntity.also {
it.sequence = childPosition
it.outerSequence = item.position
},
listOf(
ExposureSource(
"通用内容合集",
"${item.data.name}+${item.data.layoutChinese}+${item.data.id}"
)
),
pageConfigure.exposureSourceList,
childPosition,
item.componentPosition
val _paddingBottom =
if (item.isLastLine || collection.layout == COMMON_CONTENT_COLLECTION_LAYOUT_VERTICAL_IMAGE_TEXT) {
16f.dip2px()
} else {
8f.dip2px()
}
with(binding.root) {
setPadding(paddingLeft, paddingTop, paddingRight, _paddingBottom)
}
).also { contentEntity.linkEntity.exposureEvent = it }
item.exposureEventList.add(event)
}
}

View File

@ -1,26 +1,24 @@
package com.gh.gamecenter.home.custom.viewholder
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.exposure.ExposureManager
import com.gh.common.exposure.ExposureTraceUtils
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.view.SpacingItemDecoration
import com.gh.gamecenter.databinding.CommonCollectionListCustomBinding
import com.gh.gamecenter.databinding.LayoutTitleCustomBinding
import com.gh.gamecenter.entity.CommonCollectionContentEntity
import com.gh.gamecenter.entity.ExposureLinkEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.ExposureType
import com.gh.gamecenter.home.custom.CustomPageViewModel
import com.gh.gamecenter.home.custom.adapter.CustomCommonCollectionAdapter
import com.gh.gamecenter.home.custom.createExposureEvent
import com.gh.gamecenter.home.custom.eventlistener.CommonContentCollectionEventHelper
import com.gh.gamecenter.home.custom.model.CustomCommonContentCollectionItem
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.model.CustomPageItem
import com.gh.gamecenter.home.custom.ui.CommonContentHorizontalSlideListUi
/**
* 通用内容合集-横排滑动Banner
@ -33,108 +31,100 @@ class CustomCommonCollectionViewHolder(
val binding: CommonCollectionListCustomBinding,
) : BaseCustomViewHolder(viewModel, binding.root) {
private val layoutManager by lazy {
LinearLayoutManager(itemView.context, LinearLayoutManager.HORIZONTAL, false)
}
override val childEventHelper by lazy(LazyThreadSafetyMode.NONE) {
CommonContentCollectionEventHelper(viewModel)
}
private val adapter by lazy {
CustomCommonCollectionAdapter(itemView.context, { childPosition, entity ->
val item = _item as CustomCommonContentCollectionItem
val subject = item.data
val linkEntity = entity.linkEntity
private val horizontalSlideUi by lazy {
CommonContentHorizontalSlideListUi(binding,
object : CommonContentHorizontalSlideListUi.OnHorizontalSlideListListener {
override fun addExposureEvent(childPosition: Int, link: ExposureLinkEntity) {
(_item as? CustomCommonContentCollectionItem)?.let { item ->
val gameEntity =
if (link.type == "game") GameEntity(id = link.link, name = link.text) else GameEntity()
_item.exposureEventList.add(
createExposureEvent(
gameEntity.also {
it.sequence = childPosition
it.outerSequence = _item.position
},
listOf(
ExposureSource(
"通用内容合集",
"${item.data.name}+${item.data.layoutChinese}+${item.data.id}"
)
),
pageConfigure.exposureSourceList,
childPosition,
_item.componentPosition
).also { link.exposureEvent = it }
)
}
NewLogUtils.logCommonCollectionClick(
subject.id,
subject.name,
"blockData?.link",
"blockData?.name",
"板块内容列表",
"合集首页",
linkEntity.title ?: "",
entity.addedContent1 ?: "",
entity.addedContent2 ?: "",
linkEntity.type ?: "",
linkEntity.text ?: "",
childPosition + 1
)
NewLogUtils.logCommonCategoryHomeContentClick(
entity.title,
linkEntity.type ?: "",
linkEntity.link ?: "",
linkEntity.text ?: "",
subject.name,
subject.id,
"板块",
"blockData?.name"
)
linkEntity.exposureEvent?.let {
val clickEvent = ExposureEvent(
payload = it.payload,
source = it.source,
eTrace = ExposureTraceUtils.appendTrace(it),
event = ExposureType.CLICK
)
if (linkEntity.type != "game") {
ExposureManager.log(clickEvent)
}
}
childEventHelper.navigateToLinkPage(
linkEntity,
"内容卡片",
(_item as CustomCommonContentCollectionItem).exposureEventList.getOrNull(childPosition)
)
}) { childPosition, link ->
val item = _item as CustomCommonContentCollectionItem
val gameEntity = if (link.type == "game") GameEntity(id = link.link, name = link.text) else GameEntity()
_item.exposureEventList.add(
createExposureEvent(
gameEntity.also {
it.sequence = childPosition
it.outerSequence = _item.position
},
listOf(ExposureSource("通用内容合集", "${item.data.name}+${item.data.layoutChinese}+${item.data.id}")),
pageConfigure.exposureSourceList,
childPosition,
_item.componentPosition
).also { link.exposureEvent = it }
)
}
override fun onChildItemClick(childPosition: Int, entity: CommonCollectionContentEntity) {
(_item as? CustomCommonContentCollectionItem)?.let { item ->
val subject = item.data
val linkEntity = entity.linkEntity
NewLogUtils.logCommonCollectionClick(
subject.id,
subject.name,
"blockData?.link",
"blockData?.name",
"板块内容列表",
"合集首页",
linkEntity.title ?: "",
entity.addedContent1 ?: "",
entity.addedContent2 ?: "",
linkEntity.type ?: "",
linkEntity.text ?: "",
childPosition + 1
)
NewLogUtils.logCommonCategoryHomeContentClick(
entity.title,
linkEntity.type ?: "",
linkEntity.link ?: "",
linkEntity.text ?: "",
subject.name,
subject.id,
"板块",
"blockData?.name"
)
linkEntity.exposureEvent?.let {
val clickEvent = ExposureEvent(
payload = it.payload,
source = it.source,
eTrace = ExposureTraceUtils.appendTrace(it),
event = ExposureType.CLICK
)
if (linkEntity.type != "game") {
ExposureManager.log(clickEvent)
}
}
childEventHelper.navigateToLinkPage(
linkEntity,
"内容卡片",
(_item as CustomCommonContentCollectionItem).exposureEventList.getOrNull(childPosition)
)
}
}
override fun navigateToCommonCollectionDetailPage(data: CustomPageData.CommonContentCollection) {
childEventHelper.navigateToCommonCollectionDetailPage(_item.link)
}
})
}
override fun bindView(item: CustomPageItem) {
super.bindView(item)
if (item is CustomCommonContentCollectionItem) {
item.exposureEventList.clear()
setTitleStyle(binding.layoutTitle)
setTitle(item.data, binding.layoutTitle) {
childEventHelper.navigateToCommonCollectionDetailPage(item.link)
}
if (binding.collectionList.adapter == null) {
binding.collectionList.addItemDecoration(
SpacingItemDecoration(
notDecorateTheFirstItem = true,
left = 8f.dip2px()
)
)
binding.collectionList.layoutManager = layoutManager
binding.collectionList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
var position = layoutManager.findLastCompletelyVisibleItemPosition()
if (position == -1) position = layoutManager.findLastVisibleItemPosition() - 1
if (position < 0) return
}
}
})
binding.collectionList.adapter = adapter
}
adapter.setData(item)
horizontalSlideUi.bind(item.data)
}
}

View File

@ -1,18 +1,17 @@
package com.gh.gamecenter.home.custom.viewholder
import android.widget.GridView
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.databinding.ItemHomeRecommendListCustomBinding
import com.gh.gamecenter.entity.HomeRecommend
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.home.custom.CustomPageViewModel
import com.gh.gamecenter.home.custom.adapter.CustomHomeRecommendItemGridAdapter
import com.gh.gamecenter.home.custom.createExposureEvent
import com.gh.gamecenter.home.custom.eventlistener.CommonContentCollectionEventHelper
import com.gh.gamecenter.home.custom.model.CustomCommonContentCollectionItem
import com.gh.gamecenter.home.custom.model.CustomPageItem
import com.gh.gamecenter.home.custom.ui.CommonContentRecommendUi
/**
* 通用内容合集-金刚区
@ -28,63 +27,44 @@ class CustomHomeRecommendItemViewHolder(
CommonContentCollectionEventHelper(viewModel)
}
private val adapter by lazy {
CustomHomeRecommendItemGridAdapter(childEventHelper)
}
/**
* 数据处理
* 当后台“日常管理(新)-首页推荐入口(新)/多渠道多版本-首页推荐入口”配置的可显示的内容≥10个时前端需要支持展示两行首页推荐入口
* 1 如果 6≤推荐入口数量10 ,前端均仅显示一行推荐入口的内容
* 2 如果 推荐入口数量5 ,还是保留原有的规则平铺展示
*/
private fun mapData(recommends: List<HomeRecommend>): List<HomeRecommend> {
_item.exposureEventList.clear()
val item = _item as CustomCommonContentCollectionItem
runOnIoThread(true) {
recommends.forEachIndexed { index, homeRecommend ->
val exposureEvent = createExposureEvent(
GameEntity().also { it.sequence = index },
listOf(
ExposureSource("通用内容合集", "${item.data.name}+${item.data.layoutChinese}+${item.data.id}"),
ExposureSource("金刚区", homeRecommend.name)
),
pageConfigure.exposureSourceList,
index,
_item.componentPosition
)
exposureEvent.payload.controlType = "推荐入口"
exposureEvent.payload.controlName = homeRecommend.name
exposureEvent.payload.controlLinkName = homeRecommend.linkText
exposureEvent.payload.controlLinkType = homeRecommend.linkType
homeRecommend.exposureEvent = exposureEvent
_item.exposureEventList.add(exposureEvent)
private val recommendUi by lazy {
CommonContentRecommendUi(binding, object : CommonContentRecommendUi.OnRecommendListener {
override fun navigateToLinkPage(link: LinkEntity, text: String, exposureEvent: ExposureEvent?) {
childEventHelper.navigateToLinkPage(link, text, exposureEvent)
}
}
val column = (recommends.size / MAX_SPAN_COUNT) // 显示几行
val count = if (column == 0) {
recommends.count()
} else {
column * MAX_SPAN_COUNT
}
return recommends.take(minOf(10, count))
})
}
override fun bindView(item: CustomPageItem) {
super.bindView(item)
if (item is CustomCommonContentCollectionItem) {
val recommends = mapData(item.data.recommends)
val size = recommends.size
if (size == 0) return
if (binding.recommendGv.adapter == null) {
val spanCount = minOf(size, MAX_SPAN_COUNT)
binding.recommendGv.stretchMode = GridView.STRETCH_SPACING
binding.recommendGv.numColumns = spanCount
binding.recommendGv.columnWidth = 60F.dip2px()
val recommends = item.data.recommends
_item.exposureEventList.clear()
runOnIoThread(true) {
item.data.recommends.forEachIndexed { index, homeRecommend ->
val exposureEvent = createExposureEvent(
GameEntity().also { it.sequence = index },
listOf(
ExposureSource(
"通用内容合集",
"${item.data.name}+${item.data.layoutChinese}+${item.data.id}"
),
ExposureSource("金刚区", homeRecommend.name)
),
pageConfigure.exposureSourceList,
index,
_item.componentPosition
)
exposureEvent.payload.controlType = "推荐入口"
exposureEvent.payload.controlName = homeRecommend.name
exposureEvent.payload.controlLinkName = homeRecommend.linkText
exposureEvent.payload.controlLinkType = homeRecommend.linkType
homeRecommend.exposureEvent = exposureEvent
_item.exposureEventList.add(exposureEvent)
}
}
binding.recommendGv.adapter = adapter
adapter.submitList(recommends, _item.exposureEventList)
recommendUi.bind(recommends, _item.exposureEventList)
}
}

View File

@ -1,32 +1,23 @@
package com.gh.gamecenter.home.custom.viewholder
import android.graphics.drawable.GradientDrawable
import android.view.MotionEvent
import android.view.View
import androidx.core.graphics.ColorUtils
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.gh.gamecenter.R
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.view.DrawableView
import com.gh.gamecenter.common.view.ScrollEventListener
import com.gh.gamecenter.common.view.TouchSlopRecyclerView
import com.gh.gamecenter.databinding.HomeSlideListCustomBinding
import com.gh.gamecenter.entity.HomeSlide
import com.gh.gamecenter.home.custom.BannerInRecyclerController
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.home.custom.CustomPageViewModel
import com.gh.gamecenter.home.custom.OnCustomPageRefreshStateListener
import com.gh.gamecenter.home.custom.adapter.CustomHomeSlideListAdapter
import com.gh.gamecenter.home.custom.createExposureEvent
import com.gh.gamecenter.home.custom.eventlistener.CommonContentCollectionEventHelper
import com.gh.gamecenter.home.custom.model.CustomCommonContentCollectionItem
import com.gh.gamecenter.home.custom.model.CustomPageItem
import splitties.views.verticalPadding
import kotlin.math.abs
import com.gh.gamecenter.home.custom.ui.CommonContentHomeSLideListUi
/**
* 通用内容合集-轮播Banner
@ -38,61 +29,59 @@ class CustomHomeSlideListViewHolder(
) :
BaseCustomViewHolder(viewModel, binding.root), OnCustomPageRefreshStateListener {
private var mLastX: Int = 0
private var mLastY: Int = 0
private var _currentPosition = -1
// 是否是首位
private val isFirst: Boolean
get() = bindingAdapterPosition == 0
private var backgroundAlpha: Float = 1F
private val layoutManager by lazy {
LinearLayoutManager(itemView.context, RecyclerView.HORIZONTAL, false)
}
private val scrollEventListener by lazy {
ScrollEventListener(binding.recyclerView)
}
private val snapHelper by lazy {
PagerSnapHelper()
}
override val childEventHelper by lazy(LazyThreadSafetyMode.NONE) {
CommonContentCollectionEventHelper(viewModel)
}
private val adapter by lazy {
CustomHomeSlideListAdapter(
itemView.context, snapHelper, layoutManager, false,
childEventHelper
) { childPosition, game ->
val item = _item as CustomCommonContentCollectionItem
createExposureEvent(
game,
listOf(
ExposureSource("通用内容合集", "${item.data.name}+${item.data.layoutChinese}+${item.data.id}"),
ExposureSource("轮播图", "")
),
pageConfigure.exposureSourceList,
childPosition,
_item.componentPosition
)
}
}
private val slideListUi by lazy {
CommonContentHomeSLideListUi(
binding,
lifecycleOwner,
object : CommonContentHomeSLideListUi.OnCommonHomeSlideListEventListener {
override fun navigateToLinkPage(link: LinkEntity, text: String, exposureEvent: ExposureEvent?) {
childEventHelper.navigateToLinkPage(link, text, exposureEvent)
}
private val bannerController = BannerInRecyclerController {
adapter.scrollToNextPage()
}
override fun navigateToGameDetailPage(position: Int, game: GameEntity, text: String) {
childEventHelper.navigateToGameDetailPage(position, game, text)
}
private var _slideList: List<HomeSlide>? = null
override fun updateImmersiveColor(color: Int) {
if (pageConfigure.isInSearchToolbarTabWrapperPage) {
if (isFirst) {
viewModel.onChangeAppBarColor(color)
}
updateBackground(color)
}
}
init {
binding.recyclerView.itemAnimator = null
lifecycleOwner.lifecycle.addObserver(bannerController)
override fun createExposureEvent(childPosition: Int, game: GameEntity?): ExposureEvent? {
val item = _item
return if (item is CustomCommonContentCollectionItem) {
createExposureEvent(
game,
listOf(
ExposureSource(
"通用内容合集",
"${item.data.name}+${item.data.layoutChinese}+${item.data.id}"
),
ExposureSource("轮播图", "")
),
pageConfigure.exposureSourceList,
childPosition,
_item.componentPosition
)
} else {
return null
}
}
})
}
override fun bindView(item: CustomPageItem) {
@ -103,198 +92,19 @@ class CustomHomeSlideListViewHolder(
item.exposureEventList.clear()
val paddingVertical = if (isFirst) {
8f.dip2px()
} else {
16f.dip2px()
}
binding.recyclerView.verticalPadding = paddingVertical
if (binding.recyclerView.adapter == null) {
binding.recyclerView.layoutManager = layoutManager
binding.recyclerView.adapter = adapter
snapHelper.attachToRecyclerView(binding.recyclerView)
binding.recyclerView.addOnItemTouchListener(object : RecyclerView.SimpleOnItemTouchListener() {
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
val x = e.x.toInt()
val y = e.y.toInt()
when (e.action) {
MotionEvent.ACTION_DOWN -> {
mLastX = e.x.toInt()
mLastY = e.y.toInt()
rv.parent.parent.requestDisallowInterceptTouchEvent(true)
}
MotionEvent.ACTION_UP -> rv.parent.parent.requestDisallowInterceptTouchEvent(
false
)
MotionEvent.ACTION_MOVE -> {
val deltaX: Int = x - mLastX
val deltaY: Int = y - mLastY
val isHorizontalScroll = abs(deltaX) > abs(deltaY)
rv.parent.parent.requestDisallowInterceptTouchEvent(isHorizontalScroll)
}
}
val isStop =
e.action == MotionEvent.ACTION_DOWN || e.action == MotionEvent.ACTION_MOVE
if (isStop) bannerController.pause() else bannerController.start()
val touchSlopRecyclerView = binding.recyclerView.parent.parent
if (touchSlopRecyclerView is TouchSlopRecyclerView) {
touchSlopRecyclerView.touchSlopEnabled = isStop
} else throwExceptionInDebug("TouchSlopRecyclerView not found")
return false
}
})
binding.recyclerView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
requestTransform()
}
binding.recyclerView.addOnScrollListener(scrollEventListener.apply {
setOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
var lastStatePosition = -1
var lastScrollState = RecyclerView.SCROLL_STATE_IDLE
override fun onPageScrollStateChanged(state: Int) {
super.onPageScrollStateChanged(state)
if (state == RecyclerView.SCROLL_STATE_IDLE) {
bannerController.start()
} else {
bannerController.pause()
}
if (scrollState == RecyclerView.SCROLL_STATE_IDLE) {
val view = snapHelper.findSnapView(layoutManager)
var curPosition = 0
if (view != null) {
curPosition = adapter.getDataPosition(layoutManager.getPosition(view))
_currentPosition = layoutManager.getPosition(view)
}
lastStatePosition = curPosition
lastScrollState = scrollState
updateImmersiveColor(
slideList.safelyGetInRelease(curPosition)?.placeholderColor?.hexStringToIntColor()
?: R.color.ui_surface.toColor(binding.root.context)
)
} else if (scrollState == RecyclerView.SCROLL_STATE_DRAGGING) {
lastScrollState = scrollState
}
}
override fun onPageSelected(position: Int) {
_currentPosition = position
}
override fun onPageScrolled(
position: Int,
positionOffset: Float,
positionOffsetPixels: Int
) {
_currentPosition = position
val currentPosition = adapter.getDataPosition(position)
val nextPosition = adapter.getDataPosition(position + 1)
val currentColor =
slideList.safelyGetInRelease(currentPosition)?.placeholderColor?.hexStringToIntColor()
?: R.color.ui_surface.toColor(binding.root.context)
val nextColor =
slideList.safelyGetInRelease(nextPosition)?.placeholderColor?.hexStringToIntColor()
?: currentColor
val colorInBetween =
ColorUtils.blendARGB(currentColor, nextColor, positionOffset)
transformPage(position, positionOffset)
updateImmersiveColor(colorInBetween)
}
})
})
adapter.submitList(slideList, true)
} else {
if (_slideList != slideList) {
adapter.checkResetData(slideList)
}
}
_slideList = slideList
val currentPosition = if (_currentPosition == -1) {
adapter.getInitPosition()
} else {
_currentPosition
}
_currentPosition = currentPosition
binding.recyclerView.scrollToPosition(currentPosition)
val dataPosition = adapter.getDataPosition(currentPosition)
updateImmersiveColor(
slideList.safelyGetInRelease(dataPosition)?.placeholderColor?.hexStringToIntColor()
?: R.color.ui_surface.toColor(binding.root.context)
)
bannerController.start()
}
}
private fun requestTransform() {
val relativePosition: Double = scrollEventListener.relativeScrollPosition
val position = relativePosition.toInt()
val positionOffset = (relativePosition - position).toFloat()
transformPage(position, positionOffset)
}
private fun transformPage(position: Int, positionOffset: Float) {
val transformOffset = -positionOffset
for (i in 0 until layoutManager.childCount) {
val view: View = layoutManager.getChildAt(i) ?: return
val currPos: Int = layoutManager.getPosition(view)
val viewOffset = transformOffset + (currPos - position)
val scaleY = when {
viewOffset <= -1 -> {
0.9f
}
viewOffset < 0 -> {
(1 + viewOffset) * 0.1f + 0.9f
}
viewOffset < 1 -> {
(1 - viewOffset) * 0.1f + 0.9f
}
else -> {
0.9f
}
}
view.scaleY = scaleY
slideListUi.bind(slideList, bindingAdapterPosition)
}
}
override fun onViewAttach(parent: RecyclerView?) {
bannerController.onViewAttachedToWindow(parent)
slideListUi.onViewAttach(parent)
}
override fun onViewDetach(parent: RecyclerView?) {
bannerController.onViewDetachedFromWindow(parent)
}
private fun updateImmersiveColor(color: Int) {
if (pageConfigure.isInSearchToolbarTabWrapperPage) {
if (isFirst) {
viewModel.onChangeAppBarColor(color)
}
updateBackground(color)
}
slideListUi.onViewDetach(parent)
}
override fun onBackgroundAlphaChanged(alpha: Float) {
backgroundAlpha = alpha
binding.backgroundView.alpha = alpha
}

View File

@ -1,48 +1,29 @@
package com.gh.gamecenter.home.custom.viewholder
import android.annotation.SuppressLint
import android.graphics.Typeface
import android.graphics.drawable.GradientDrawable
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup.MarginLayoutParams
import androidx.constraintlayout.widget.ConstraintSet
import androidx.core.graphics.ColorUtils
import androidx.core.view.isVisible
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.gh.common.exposure.ExposureManager
import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.json.json
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.view.DrawableView
import com.gh.gamecenter.common.view.FixLinearLayoutManager
import com.gh.gamecenter.common.view.ScrollEventListener
import com.gh.gamecenter.common.view.TouchSlopRecyclerView
import com.gh.gamecenter.core.utils.AlphaGradientProcess
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.databinding.HomeSlideCardItemCustomBinding
import com.gh.gamecenter.databinding.HomeSlideWithCardsCustomBinding
import com.gh.gamecenter.entity.HomeSlide
import com.gh.gamecenter.entity.HomeSubSlide
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.exposure.ExposureType
import com.gh.gamecenter.home.custom.BannerInRecyclerController
import com.gh.gamecenter.home.custom.CustomPageViewModel
import com.gh.gamecenter.home.custom.OnCustomPageRefreshStateListener
import com.gh.gamecenter.home.custom.adapter.CustomHomeSlideListAdapter
import com.gh.gamecenter.home.custom.createExposureEvent
import com.gh.gamecenter.home.custom.eventlistener.CommonContentCollectionEventHelper
import com.gh.gamecenter.home.custom.model.CustomCommonContentCollectionItem
import com.gh.gamecenter.home.custom.model.CustomPageData
import com.gh.gamecenter.home.custom.model.CustomPageItem
import kotlin.math.abs
import com.gh.gamecenter.home.custom.ui.CommonContentHomeSlideWithCardsUi
/**
* 通用内容合集-轮播banner
@ -54,517 +35,133 @@ class CustomHomeSlideWithCardsViewHolder(
val binding: HomeSlideWithCardsCustomBinding,
) : BaseCustomViewHolder(viewModel, binding.root), OnCustomPageRefreshStateListener {
private var mLastX: Int = 0
private var mLastY: Int = 0
private val hasCards = true
private var _currentPosition = -1
private val isFirst: Boolean
get() = bindingAdapterPosition == 0
private var backgroundAlpha: Float = 1F
private val layoutManager by lazy {
FixLinearLayoutManager(itemView.context, RecyclerView.HORIZONTAL, false)
}
private val snapHelper by lazy {
PagerSnapHelper()
}
override val childEventHelper by lazy(LazyThreadSafetyMode.NONE) {
CommonContentCollectionEventHelper(viewModel)
}
private val adapter by lazy {
CustomHomeSlideListAdapter(
itemView.context, snapHelper, layoutManager, hasCards,
childEventHelper
) { childPosition, game ->
val item = _item as CustomCommonContentCollectionItem
createExposureEvent(
game,
listOf(
ExposureSource("通用内容合集", "${item.data.name}+${item.data.layoutChinese}+${item.data.id}"),
ExposureSource("轮播图", "")
),
pageConfigure.exposureSourceList,
childPosition,
_item.componentPosition
)
}
}
private val slideWithCardsUi by lazy {
CommonContentHomeSlideWithCardsUi(
viewModel.commonContentCollectionUseCase,
lifecycleOwner,
binding,
object : CommonContentHomeSlideWithCardsUi.HomeSLideWithCardsEventListener {
override fun updateImmersiveColor(color: Int) {
this@CustomHomeSlideWithCardsViewHolder.updateImmersiveColor(color)
}
private val bannerController = BannerInRecyclerController {
adapter.scrollToNextPage()
}
override fun createEventWithSourceConcat(game: GameEntity, subSlideId: String) {
(_item as? CustomCommonContentCollectionItem)?.let {
ExposureEvent.createEventWithSourceConcat(
game,
basicSource = pageConfigure.exposureSourceList,
source = listOf(
ExposureSource(
"通用内容合集",
"${it.data.name}+${it.data.layoutChinese}+${it.data.id}"
),
ExposureSource("右侧卡片", subSlideId)
)
)
}
private var firstSubSlide: HomeSubSlide? = null
private var secondSubSlide: HomeSubSlide? = null
private var _slideList: List<HomeSlide>? = null
}
init {
binding.recyclerView.itemAnimator = null
lifecycleOwner.lifecycle.addObserver(bannerController)
override fun createExposureEvent(actualPosition: Int, game: GameEntity?): ExposureEvent? {
val item = _item
return if (item is CustomCommonContentCollectionItem) {
createExposureEvent(
game,
listOf(
ExposureSource(
"通用内容合集",
"${item.data.name}+${item.data.layoutChinese}+${item.data.id}"
),
ExposureSource("轮播图", "")
),
pageConfigure.exposureSourceList,
actualPosition,
_item.componentPosition
)
} else {
return null
}
}
override fun addGameExposureEvent(position: Int, game: GameEntity, subSlideId: String) {
(_item as? CustomCommonContentCollectionItem)?.let {
_item.exposureEventList.add(
getGameExposureEvent(
it.data,
subSlideId,
game,
position,
pageConfigure.exposureSourceList
)
)
}
}
override fun navigateToGameDetailPage(childPosition: Int, gameEntity: GameEntity, text: String) {
childEventHelper.navigateToGameDetailPage(childPosition, gameEntity, text)
}
override fun navigateToLinkPageInSubSlide(
game: GameEntity,
homeSubSlide: HomeSubSlide,
text: String
) {
(_item as? CustomCommonContentCollectionItem)?.let {
val clickEvent = ExposureEvent.createEventWithSourceConcat(
gameEntity = game,
basicSource = pageConfigure.exposureSourceList,
source = listOf(
ExposureSource(
"通用内容合集",
"${it.data.name}+${it.data.layoutChinese}+${it.data.id}"
),
ExposureSource("右侧卡片", homeSubSlide.id)
),
event = ExposureType.CLICK
)
val homeSubLink = homeSubSlide.toLinkEntity()
if (homeSubLink.type != "game") {
ExposureManager.log(clickEvent)
}
SensorsBridge.trackEvent("RightSideCardClick", json {
"position" to homeSubSlide.sequence
"title" to homeSubSlide.title
"card_id" to homeSubSlide.id
})
childEventHelper.navigateToLinkPage(homeSubLink, "右侧卡片", clickEvent)
}
}
override fun navigateToLinkPage(link: LinkEntity, text: String, exposureEvent: ExposureEvent?) {
childEventHelper.navigateToLinkPage(link, text, exposureEvent)
}
})
}
override fun bindView(item: CustomPageItem) {
super.bindView(item)
if (item is CustomCommonContentCollectionItem) {
val bannerTopMargin = if (isFirst) {
8f.dip2px()
} else {
16f.dip2px()
}
val layoutParams = binding.bannerCv.layoutParams as? MarginLayoutParams
layoutParams?.let {
it.topMargin = bannerTopMargin
it.bottomMargin = if (isFirst) 16f.dip2px() else 24f.dip2px()
binding.bannerCv.layoutParams = it
}
val rootLayoutParams = itemView.layoutParams as? MarginLayoutParams
rootLayoutParams?.let {
it.bottomMargin = -8f.dip2px()
itemView.layoutParams = it
}
bindSlide(item.data.slides.slide)
bindCards(item, item.data.slides.subSlide)
slideWithCardsUi.bind(item.data, bindingAdapterPosition)
}
}
private fun bindSlide(
slideList: List<HomeSlide>
) {
if (binding.recyclerView.adapter == null) {
binding.recyclerView.layoutManager = layoutManager
binding.recyclerView.adapter = adapter
snapHelper.attachToRecyclerView(binding.recyclerView)
binding.recyclerView.addOnItemTouchListener(object : RecyclerView.SimpleOnItemTouchListener() {
override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {
val x = e.x.toInt()
val y = e.y.toInt()
when (e.action) {
MotionEvent.ACTION_DOWN -> {
mLastX = e.x.toInt()
mLastY = e.y.toInt()
rv.parent.parent.requestDisallowInterceptTouchEvent(true)
}
MotionEvent.ACTION_UP -> rv.parent.parent.requestDisallowInterceptTouchEvent(
false
)
MotionEvent.ACTION_MOVE -> {
val deltaX: Int = x - mLastX
val deltaY: Int = y - mLastY
val isHorizontalScroll = abs(deltaX) > abs(deltaY)
rv.parent.parent.requestDisallowInterceptTouchEvent(isHorizontalScroll)
}
}
val isStop =
e.action == MotionEvent.ACTION_DOWN || e.action == MotionEvent.ACTION_MOVE
if (isStop) bannerController.pause() else bannerController.start()
val touchSlopRecyclerView = binding.recyclerView.parent.parent.parent
if (touchSlopRecyclerView is TouchSlopRecyclerView) {
touchSlopRecyclerView.touchSlopEnabled = isStop
} else throwExceptionInDebug("TouchSlopRecyclerView not found")
return false
}
})
binding.recyclerView.addOnScrollListener(ScrollEventListener(binding.recyclerView).apply {
setOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
var lastStatePosition = -1
var lastScrollState = RecyclerView.SCROLL_STATE_IDLE
override fun onPageSelected(position: Int) {
_currentPosition = position
}
override fun onPageScrollStateChanged(state: Int) {
super.onPageScrollStateChanged(state)
if (scrollState == RecyclerView.SCROLL_STATE_IDLE) {
val view = snapHelper.findSnapView(layoutManager)
var curPosition = 0
if (view != null) {
curPosition = adapter.getDataPosition(layoutManager.getPosition(view))
_currentPosition = layoutManager.getPosition(view)
}
if (lastScrollState == RecyclerView.SCROLL_STATE_DRAGGING && curPosition != lastStatePosition) {
// SensorsBridge.trackEvent("BannerSlide", json {
// "position" to curPosition
// "location" to location
// })
MtaHelper.onEvent(
"首页_新",
"轮播_滑动",
if (curPosition > lastStatePosition) "左滑" else "右滑"
)
}
lastStatePosition = curPosition
lastScrollState = scrollState
updateImmersiveColor(
slideList.safelyGetInRelease(curPosition)?.placeholderColor?.hexStringToIntColor()
?: R.color.ui_surface.toColor(binding.root.context)
)
} else if (scrollState == RecyclerView.SCROLL_STATE_DRAGGING) {
lastScrollState = scrollState
}
}
override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) {
super.onPageScrolled(position, positionOffset, positionOffsetPixels)
_currentPosition = position
binding.bannerIndicator.onPageScrolled(
adapter.getDataPosition(position),
positionOffset
)
val currentPosition = adapter.getDataPosition(position)
val nextPosition = adapter.getDataPosition(position + 1)
val currentColor =
slideList.safelyGetInRelease(currentPosition)?.placeholderColor?.hexStringToIntColor()
?: R.color.ui_surface.toColor(binding.root.context)
val nextColor =
slideList.safelyGetInRelease(nextPosition)?.placeholderColor?.hexStringToIntColor()
?: currentColor
val colorInBetween =
ColorUtils.blendARGB(currentColor, nextColor, positionOffset)
updateImmersiveColor(colorInBetween)
}
})
})
adapter.submitList(slideList)
} else {
if (_slideList != slideList) {
adapter.checkResetData(slideList)
}
}
_slideList = slideList
val currentPosition = if (_currentPosition == -1) {
adapter.getInitPosition()
} else {
_currentPosition
}
binding.recyclerView.scrollToPosition(currentPosition)
binding.bannerIndicator.run {
pageSize = adapter.getActualSize()
notifyDataChanged()
}
val dataPosition = adapter.getDataPosition(currentPosition)
updateImmersiveColor(
slideList.safelyGetInRelease(dataPosition)?.placeholderColor?.hexStringToIntColor()
?: R.color.ui_surface.toColor(binding.root.context)
)
bannerController.start()
}
private fun bindCards(item: CustomCommonContentCollectionItem, slideList: List<HomeSubSlide>) {
val firstSubSlidePosition = (viewModel.refreshCount * 2) % slideList.size
val secondSubSlidePosition = (viewModel.refreshCount * 2 + 1) % slideList.size
slideList.getOrNull(firstSubSlidePosition)?.let {
firstSubSlide = it
it.sequence = firstSubSlidePosition
it.repeatCount = (viewModel.refreshCount * 2) / slideList.size
bindCard(item, binding.firstCv, it, pageConfigure.exposureSourceList, 0)
}
slideList.getOrNull(secondSubSlidePosition)?.let {
secondSubSlide = it
it.sequence = secondSubSlidePosition
it.repeatCount = (viewModel.refreshCount * 2 + 1) / slideList.size
bindCard(item, binding.secondCv, it, pageConfigure.exposureSourceList, 1)
}
}
private fun getCardTypeChinese(type: String) = when (type) {
"column" -> "游戏专题"
"column_collection" -> "专题合集"
"column_test_v2" -> "新游开测"
"server" -> "开服表"
"game_explore" -> "发现页"
"game_list_detail" -> "游戏单"
"common_collection" -> "通用合集"
else -> ""
}
@SuppressLint("SetTextI18n")
private fun bindCard(
item: CustomCommonContentCollectionItem,
homeSlideCardItemBinding: HomeSlideCardItemCustomBinding,
homeSubSlide: HomeSubSlide,
basicExposureSource: List<ExposureSource>,
position: Int
) {
val cardCv = homeSlideCardItemBinding.root
val cardContainer = homeSlideCardItemBinding.cardContainer
val cardIv = homeSlideCardItemBinding.cardIv
val backgroundGroup = homeSlideCardItemBinding.backgroundGroup
val titleTv = homeSlideCardItemBinding.titleTv
val descTv = homeSlideCardItemBinding.descTv
val textContainer = homeSlideCardItemBinding.textContainer
val countTv = homeSlideCardItemBinding.countTv
val labelIv = homeSlideCardItemBinding.labelIv
val gameIconStackIv1 = homeSlideCardItemBinding.gameIconStackIv1
val gameIconStackIv2 = homeSlideCardItemBinding.gameIconStackIv2
val gameIconStackIv3 = homeSlideCardItemBinding.gameIconStackIv3
val gameIconStackGroup = listOf(gameIconStackIv1, gameIconStackIv2, gameIconStackIv3)
val gameIconIv1 = homeSlideCardItemBinding.gameIconIv1
val gameIconIv2 = homeSlideCardItemBinding.gameIconIv2
val gameIconIv3 = homeSlideCardItemBinding.gameIconIv3
val gameIconGroup = listOf(gameIconIv1, gameIconIv2, gameIconIv3)
titleTv.text = homeSubSlide.title
titleTv.setTextColor(R.color.text_primary.toColor(titleTv.context))
descTv.setTextColor(R.color.text_secondary.toColor(descTv.context))
val exposureEvent = ExposureEvent.createEventWithSourceConcat(
gameEntity = GameEntity().apply {
sequence = position
outerSequence = viewModel.refreshCount
},
basicSource = basicExposureSource,
source = listOf(
ExposureSource("通用内容合集", "${item.data.name}+${item.data.layoutChinese}+${item.data.id}"),
ExposureSource("右侧卡片", homeSubSlide.id)
)
)
// 获取发现卡片游戏
if (homeSubSlide.cardType == "game_explore" && homeSubSlide.cardData.games.isEmpty()) {
viewModel.slideDiscoveryCardGames.observe(lifecycleOwner, object : Observer<List<GameEntity>> {
override fun onChanged(t: List<GameEntity>?) {
val currentSubSlide = if (position == 0) firstSubSlide else secondSubSlide
if (t != null && homeSubSlide == currentSubSlide) {
bindCard(item, homeSlideCardItemBinding, homeSubSlide.apply {
cardData.games = t
}, basicExposureSource, position)
viewModel.slideDiscoveryCardGames.removeObserver(this)
}
}
})
viewModel.loadSlideDiscoverCardGames(homeSubSlide)
return
}
when (homeSubSlide.cardType) {
"column",
"column_test_v2",
"server",
"game_explore" -> {
titleTv.visibility = View.VISIBLE
descTv.visibility = View.VISIBLE
countTv.visibility = View.GONE
labelIv.visibility = View.GONE
backgroundGroup.visibility = View.GONE
gameIconStackGroup.forEach { it.isVisible = homeSubSlide.cardData.games.isNotEmpty() }
gameIconGroup.forEach { it.visibility = View.GONE }
descTv.text = homeSubSlide.cardDesc
if (homeSubSlide.cardData.games.size == 1) {
gameIconStackIv2.visibility = View.GONE
gameIconStackIv1.visibility = View.GONE
}
if (homeSubSlide.cardData.games.size == 2) {
gameIconStackIv1.visibility = View.GONE
}
homeSubSlide.cardData.games.take(3).forEachIndexed { index, gameEntity ->
_item.exposureEventList.add(
getGameExposureEvent(
item.data,
homeSubSlide,
gameEntity,
position,
basicExposureSource
)
)
when (index) {
0 -> gameIconStackIv3.displayGameIcon(gameEntity, true)
1 -> gameIconStackIv2.displayGameIcon(gameEntity, true)
2 -> gameIconStackIv1.displayGameIcon(gameEntity, true)
}
}
ConstraintSet().apply {
clone(cardContainer)
clear(textContainer.id, ConstraintSet.TOP)
clear(textContainer.id, ConstraintSet.BOTTOM)
connect(textContainer.id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP)
connect(textContainer.id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM)
}.applyTo(cardContainer)
}
"game_list_detail" -> {
titleTv.visibility = View.VISIBLE
descTv.visibility = View.GONE
backgroundGroup.visibility = View.GONE
gameIconStackGroup.forEach { it.visibility = View.GONE }
gameIconGroup.forEach { it.isVisible = homeSubSlide.cardData.games.isNotEmpty() }
if (homeSubSlide.cardData.games.size == 1) {
gameIconIv2.visibility = View.GONE
gameIconIv3.visibility = View.GONE
}
if (homeSubSlide.cardData.games.size == 2) {
gameIconIv3.visibility = View.GONE
}
homeSubSlide.cardData.games.take(3).forEachIndexed { index, gameEntity ->
_item.exposureEventList.add(
getGameExposureEvent(
item.data,
homeSubSlide,
gameEntity,
position,
basicExposureSource
)
)
when (index) {
0 -> {
gameIconIv1.displayGameIcon(gameEntity, true)
gameIconIv1.setOnClickListener {
com.gh.common.util.NewFlatLogUtils.logRightSideCardClick(
homeSubSlide,
viewModel.refreshCount,
"游戏"
)
childEventHelper.navigateToGameDetailPage(index, gameEntity, "右侧卡片")
}
}
1 -> {
gameIconIv2.displayGameIcon(gameEntity, true)
gameIconIv2.setOnClickListener {
com.gh.common.util.NewFlatLogUtils.logRightSideCardClick(
homeSubSlide,
viewModel.refreshCount,
"游戏"
)
childEventHelper.navigateToGameDetailPage(index, gameEntity, "右侧卡片")
}
}
2 -> {
gameIconIv3.displayGameIcon(gameEntity, true)
gameIconIv3.setOnClickListener {
com.gh.common.util.NewFlatLogUtils.logRightSideCardClick(
homeSubSlide,
viewModel.refreshCount,
"游戏"
)
childEventHelper.navigateToGameDetailPage(index, gameEntity, "右侧卡片")
}
}
}
}
countTv.isVisible = homeSubSlide.cardData.gameTotal.game > 3
countTv.typeface = Typeface.createFromAsset(countTv.context.assets, Constants.DIN_FONT_PATH)
countTv.text = "+${homeSubSlide.cardData.gameTotal.game - 3}"
labelIv.isVisible = homeSubSlide.cardData.stamp.isNotEmpty()
labelIv.setImageDrawable(
when (homeSubSlide.cardData.stamp) {
"official" -> R.drawable.label_official.toDrawable(labelIv.context)
"special_choice" -> R.drawable.label_premium.toDrawable(labelIv.context)
else -> null
}
)
ConstraintSet().apply {
clone(cardContainer)
clear(textContainer.id, ConstraintSet.TOP)
clear(textContainer.id, ConstraintSet.BOTTOM)
connect(
textContainer.id,
ConstraintSet.TOP,
ConstraintSet.PARENT_ID,
ConstraintSet.TOP,
8F.dip2px()
)
if (homeSubSlide.cardData.stamp.isEmpty()) {
clear(countTv.id, ConstraintSet.START)
connect(countTv.id, ConstraintSet.END, ConstraintSet.PARENT_ID, ConstraintSet.END, 10F.dip2px())
} else {
clear(countTv.id, ConstraintSet.END)
connect(countTv.id, ConstraintSet.START, gameIconIv3.id, ConstraintSet.END, 4F.dip2px())
}
}.applyTo(cardContainer)
}
"column_collection", "common_collection" -> {
titleTv.visibility = View.VISIBLE
descTv.visibility = View.VISIBLE
countTv.visibility = View.GONE
labelIv.visibility = View.GONE
backgroundGroup.visibility = View.VISIBLE
val visibility = if (isFirst) View.VISIBLE else View.GONE
homeSlideCardItemBinding.cardMask.visibility = visibility
homeSlideCardItemBinding.cardGradientMask.visibility = visibility
homeSlideCardItemBinding.cardIv.visibility = View.VISIBLE
gameIconStackGroup.forEach { it.visibility = View.GONE }
gameIconGroup.forEach { it.visibility = View.GONE }
descTv.text = homeSubSlide.cardDesc
// 防止重复加载图片导致闪烁
if (homeSubSlide.image != cardIv.getTag(R.string.tag_img_url_id)) {
ImageUtils.display(cardIv, homeSubSlide.image, false, AlphaGradientProcess())
cardIv.setTag(R.string.tag_img_url_id, homeSubSlide.image)
}
ConstraintSet().apply {
clone(cardContainer)
clear(textContainer.id, ConstraintSet.TOP)
clear(textContainer.id, ConstraintSet.BOTTOM)
connect(textContainer.id, ConstraintSet.TOP, ConstraintSet.PARENT_ID, ConstraintSet.TOP)
connect(textContainer.id, ConstraintSet.BOTTOM, ConstraintSet.PARENT_ID, ConstraintSet.BOTTOM)
}.applyTo(cardContainer)
_item.exposureEventList.add(exposureEvent)
}
}
cardCv.setOnClickListener {
val clickEvent = ExposureEvent.createEventWithSourceConcat(
gameEntity = GameEntity().apply {
sequence = position
outerSequence = viewModel.refreshCount
},
basicSource = basicExposureSource,
source = listOf(
ExposureSource("通用内容合集", "${item.data.name}+${item.data.layoutChinese}+${item.data.id}"),
ExposureSource("右侧卡片", homeSubSlide.id)
),
event = ExposureType.CLICK
)
if (homeSubSlide.toLinkEntity().type != "game") {
ExposureManager.log(clickEvent)
}
SensorsBridge.trackEvent("RightSideCardClick", json {
"position" to homeSubSlide.sequence
"title" to homeSubSlide.title
"card_id" to homeSubSlide.id
})
com.gh.common.util.NewFlatLogUtils.logRightSideCardClick(homeSubSlide, viewModel.refreshCount, "卡片")
childEventHelper.navigateToLinkPage(homeSubSlide.toLinkEntity(), "右侧卡片", clickEvent)
}
}
private fun getGameExposureEvent(
collectionData: CustomPageData.CommonContentCollection,
homeSubSlide: HomeSubSlide,
subSlideId: String,
gameEntity: GameEntity,
position: Int,
basicExposureSource: List<ExposureSource>
@ -579,16 +176,16 @@ class CustomHomeSlideWithCardsViewHolder(
"通用内容合集",
"${collectionData.name}+${collectionData.layoutChinese}+${collectionData.id}"
),
ExposureSource("右侧卡片", homeSubSlide.id)
ExposureSource("右侧卡片", subSlideId)
)
)
override fun onViewAttach(parent: RecyclerView?) {
bannerController.onViewAttachedToWindow(parent)
slideWithCardsUi.onViewAttach(parent)
}
override fun onViewDetach(parent: RecyclerView?) {
bannerController.onViewDetachedFromWindow(parent)
slideWithCardsUi.onViewDetach(parent)
}
private fun updateImmersiveColor(color: Int) {

View File

@ -1,20 +1,18 @@
package com.gh.gamecenter.home.custom.viewholder
import androidx.recyclerview.widget.GridLayoutManager
import com.gh.gamecenter.common.entity.ExposureEntity
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.view.GridSpacingItemDecoration
import com.gh.gamecenter.core.runOnIoThread
import com.gh.gamecenter.databinding.RecyclerNavigationCustomBinding
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.home.custom.CustomPageViewModel
import com.gh.gamecenter.home.custom.adapter.CustomGameNavigationAdapter
import com.gh.gamecenter.home.custom.createExposureEvent
import com.gh.gamecenter.home.custom.eventlistener.CommonContentCollectionEventHelper
import com.gh.gamecenter.home.custom.model.CustomCommonContentCollectionItem
import com.gh.gamecenter.home.custom.model.CustomPageItem
import com.gh.gamecenter.home.custom.ui.CommonContentNavigationUi
/**
* 通用内容合集-导航栏
@ -24,27 +22,21 @@ class CustomNavigationViewHolder(
val binding: RecyclerNavigationCustomBinding
) : BaseCustomViewHolder(viewModel, binding.root) {
private val layoutManager by lazy {
GridLayoutManager(itemView.context, 4)
}
override val childEventHelper by lazy(LazyThreadSafetyMode.NONE) {
CommonContentCollectionEventHelper(viewModel)
}
private val adapter by lazy {
CustomGameNavigationAdapter(itemView.context, childEventHelper)
private val navigationUi by lazy {
CommonContentNavigationUi(binding, object : CommonContentNavigationUi.OnNavigationListener {
override fun navigateToLinkPage(link: LinkEntity, text: String, exposureEvent: ExposureEvent?) {
childEventHelper.navigateToLinkPage(link, text, exposureEvent)
}
})
}
override fun bindView(item: CustomPageItem) {
super.bindView(item)
if (item is CustomCommonContentCollectionItem) {
if (binding.rvNavigation.adapter == null) {
binding.rvNavigation.layoutManager = layoutManager
binding.rvNavigation.addItemDecoration(GridSpacingItemDecoration(4, 8f.dip2px(), false, 16f.dip2px()))
binding.rvNavigation.adapter = adapter
binding.rvNavigation.isNestedScrollingEnabled = false
}
val navigationList = item.data.navigations
@ -61,7 +53,10 @@ class CustomNavigationViewHolder(
it.outerSequence = item.position
},
listOf(
ExposureSource("通用内容合集", "${item.data.name}+${item.data.layoutChinese}+${item.data.id}"),
ExposureSource(
"通用内容合集",
"${item.data.name}+${item.data.layoutChinese}+${item.data.id}"
),
ExposureSource("导航栏", entity.entryName)
),
pageConfigure.exposureSourceList,
@ -73,7 +68,7 @@ class CustomNavigationViewHolder(
}
item.exposureEventList = exposureEventList
adapter.setData(navigationList, exposureEventList)
navigationUi.bind(navigationList, exposureEventList)
}
}
}

View File

@ -7,6 +7,11 @@ import android.util.SparseBooleanArray
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.common.util.DirectUtils
import com.gh.common.view.ImageContainerView
import com.gh.common.view.ImageContainerView.Companion.toImageContainerData
import com.gh.gamecenter.ImageViewerActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.ListAdapter
import com.gh.gamecenter.common.constant.ItemViewType
@ -20,6 +25,9 @@ import com.gh.gamecenter.forum.home.ArticleItemVideoView
import com.gh.gamecenter.personalhome.PersonalItemViewHolder
import com.gh.gamecenter.personalhome.home.UserHistoryViewModel
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.forum.home.AnswerArticleVideoViewEventHelper
import com.gh.gamecenter.forum.home.ArticleItemVideoView.Companion.toArticleVideoData
import com.gh.gamecenter.video.detail.VideoDetailContainerViewModel
import com.shuyu.gsyvideoplayer.builder.GSYVideoOptionBuilder
import com.shuyu.gsyvideoplayer.listener.GSYSampleCallBack
import com.shuyu.gsyvideoplayer.utils.OrientationUtils
@ -47,9 +55,10 @@ class MyPostAdapter(
view = mLayoutInflater.inflate(R.layout.refresh_footerview, parent, false)
FooterViewHolder(view)
}
else -> {
view = mLayoutInflater.inflate(R.layout.community_answer_item, parent, false)
PersonalItemViewHolder(CommunityAnswerItemBinding.bind(view))
PersonalItemViewHolder(mContext, CommunityAnswerItemBinding.bind(view), itemClickCallback)
}
}
}
@ -67,177 +76,7 @@ class MyPostAdapter(
private fun bindNormalItem(holder: PersonalItemViewHolder, position: Int) {
val historyEntity = mEntityList[position]
holder.bindPersonalItem(historyEntity, mEntrance)
val answer = AnswerEntity()
answer.images = historyEntity.images
answer.imagesInfo = historyEntity.imagesInfo
answer.videos = historyEntity.videos
answer.type = historyEntity.type
answer.id = historyEntity.id
answer.communityId = historyEntity.community.id
answer.communityName = historyEntity.community.name
answer.status = historyEntity.status ?: "pending"
answer.user.id = historyEntity.user?.id ?: ""
holder.binding.imageContainer.bindData(answer, mEntrance)
bindVideoData(holder.binding, historyEntity.transformForumVideoEntity())
holder.binding.run {
topLine.goneIf(position == 0)
forumNameLl.visibleIf(historyEntity.community.id.isNotEmpty())
ImageUtils.display(userBadgeIcon, historyEntity.user?.badge?.icon)
statusTv.goneIf(!(historyEntity.status == "pending" || historyEntity.status == "fail") && !historyEntity.me.isFollower)
statusTv.setText(
when {
historyEntity.me.isFollower -> R.string.follow_status
historyEntity.status == "pending" -> R.string.content_pending_status
else -> R.string.fail_status
}
)
statusTv.setTextColor(
when {
historyEntity.me.isFollower -> R.color.theme_alpha_80
historyEntity.status == "pending" -> R.color.text_tertiary
else -> R.color.text_CCFF5269
}.toColor(mContext)
)
statusTv.setDrawableStart(
when {
historyEntity.me.isFollower -> R.drawable.ic_forum_follow
historyEntity.status == "pending" -> R.drawable.ic_forum_pending
else -> R.drawable.icon_forum_fail
}
)
title.goneIf(historyEntity.type.contains("answer"))
title.text =
if ((historyEntity.type.contains("question") || historyEntity.type.contains("article") || historyEntity.type.contains(
"video"
))
) historyEntity.title else historyEntity.question.title
questionTitle.goneIf(!historyEntity.type.contains("answer"))
questionTitle.text = historyEntity.question.title
content.goneIf(historyEntity.brief.isEmpty() && historyEntity.des.isEmpty() && historyEntity.description.isEmpty())
content.text = when {
historyEntity.type.contains("video") -> historyEntity.des
historyEntity.type.contains("question") -> historyEntity.description
else -> historyEntity.brief
}
forumNameTv.text = historyEntity.community.name
when (historyEntity.type) {
"question" -> {
val titleStr = " ${historyEntity.title}"
val drawable = R.drawable.ic_ask_label.toDrawable()
drawable?.run {
setBounds(0, 0, 16F.dip2px(), 16F.dip2px())
title.text = SpanBuilder(titleStr).image(0, 1, this).build()
}
}
"answer" -> {
val briefStr = " ${historyEntity.brief}"
val drawable = R.drawable.ic_answer_label.toDrawable()
drawable?.run {
setBounds(0, 0, 16F.dip2px(), 16F.dip2px())
content.text = SpanBuilder(briefStr).image(0, 1, this).build()
}
}
"community_article" -> {
if (historyEntity.getPassVideos().isNotEmpty() && historyEntity.images.isNotEmpty()) {
val titleStr = title.text
val videoSpan =
SpanBuilder(" ").image(1, " ".length, R.drawable.ic_article_video_label).build()
title.text = SpannableStringBuilder()
.append(titleStr)
.append(videoSpan)
}
}
}
userIcon.visibility = View.GONE
userName.visibility = View.GONE
userBadgeIcon.visibility = View.GONE
userBadgeName.visibility = View.GONE
concernBtn.visibility = View.GONE
timeContainer.visibility = View.GONE
title.layoutParams = (title.layoutParams as ViewGroup.MarginLayoutParams).apply {
topMargin = 20F.dip2px()
}
title.setOnClickListener {
holder.itemView.performClick()
}
}
holder.itemView.setOnClickListener {
itemClickCallback.invoke(historyEntity, position)
}
}
private fun bindVideoData(binding: CommunityAnswerItemBinding, entity: ForumVideoEntity) {
binding.run {
if (entity.url.isEmpty()) {
horizontalVideoView.visibility = View.GONE
verticalVideoView.visibility = View.GONE
} else {
val videoInfo = entity.videoInfo
val visibleView = if (videoInfo.height > videoInfo.width) {
horizontalVideoView.visibility = View.GONE
verticalVideoView.visibility = View.VISIBLE
verticalVideoView
} else {
horizontalVideoView.visibility = View.VISIBLE
verticalVideoView.visibility = View.GONE
horizontalVideoView
}
val orientationUtils = OrientationUtils(mContext as Activity, visibleView)
orientationUtils.isEnable = false
GSYVideoOptionBuilder()
.setIsTouchWiget(false)
.setUrl(entity.url)
.setRotateViewAuto(false)
.setCacheWithPlay(true)
.setRotateWithSystem(false)
.setReleaseWhenLossAudio(true)
.setLooping(false)
.setShowFullAnimation(false)
.setEnlargeImageRes(R.drawable.ic_game_detail_enter_full_screen)
.setShrinkImageRes(R.drawable.ic_game_detail_exit_full_screen)
.setVideoAllCallBack(object : GSYSampleCallBack() {
override fun onQuitFullscreen(url: String?, vararg objects: Any) {
orientationUtils.backToProtVideo()
visibleView.uploadVideoStreamingPlaying("退出全屏")
}
})
.build(visibleView)
visibleView.run {
updateVideoData(entity)
updateThumb(entity.poster)
updateDurationTv(entity.duration)
setVideoStatus(entity.status)
fullscreenButton.setOnClickListener {
val horizontalVideoView = startWindowFullscreen(mContext, true, true) as? ArticleItemVideoView
if (horizontalVideoView == null) {
toastInInternalRelease("全屏失败,请向技术人员提供具体的操作步骤")
return@setOnClickListener
}
orientationUtils.resolveByClick()
horizontalVideoView.uuid = uuid
horizontalVideoView.updateVideoData(entity)
horizontalVideoView.updateThumb(entity.poster)
horizontalVideoView.violenceUpdateMuteStatus()
horizontalVideoView.setFullViewStatus()
uploadVideoStreamingPlaying("开始播放")
uploadVideoStreamingPlaying("点击全屏")
}
}
}
}
holder.bindPersonalItem(historyEntity, mEntrance, position)
}
override fun getItemCount(): Int {

View File

@ -20,6 +20,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.ContextCompat;
import androidx.core.view.MotionEventCompat;
import androidx.recyclerview.widget.RecyclerView;
@ -43,6 +44,7 @@ import com.gh.gamecenter.common.base.activity.BaseActivity;
import com.gh.gamecenter.common.base.fragment.ToolbarFragment;
import com.gh.gamecenter.common.constant.Constants;
import com.gh.gamecenter.common.constant.EntranceConsts;
import com.gh.gamecenter.common.entity.NormalShareEntity;
import com.gh.gamecenter.common.eventbus.EBNetworkState;
import com.gh.gamecenter.common.eventbus.EBReuse;
import com.gh.gamecenter.common.exposure.ExposureSource;
@ -316,18 +318,11 @@ public class NewsDetailFragment extends ToolbarFragment {
if (adapter != null && adapter.getNewsDetailEntity() != null && !ClickUtils.isFastDoubleClick(R.id.menu_question_post)) {
DataCollectionUtils.uploadClick(requireContext(), "分享", "新闻详情"
, adapter.getNewsDetailEntity().getTitle());
String url = getString(R.string.share_news_article_url, adapter.getNewsDetailEntity().getShortId());
String shareIcon;
if (gameEntity == null) {
shareIcon = getString(R.string.gh_icon_url);
} else {
shareIcon = gameEntity.getIcon();
}
((BaseActivity) requireActivity()).showShare(url, shareIcon, adapter.getNewsDetailEntity().getTitle(),
"来自光环助手(最强卡牌神器)", ShareUtils.ShareEntrance.news, adapter.getNewsDetailEntity().getId());
NewsDetailEntity entity = adapter.getNewsDetailEntity();
String shortId = adapter.getNewsDetailEntity().getShortId();
NewsShareDialog.show((AppCompatActivity) requireActivity(), shortId, entity.getId(), gameEntity.getIcon(), entity.getTitle());
}
break;
case R.id.menu_collect:
CheckLoginUtils.checkLogin(requireContext(), "资讯文章详情-收藏", () -> {

View File

@ -0,0 +1,165 @@
package com.gh.gamecenter.newsdetail
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.children
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.BaseDraggableDialogFragment
import com.gh.gamecenter.common.entity.NormalShareEntity
import com.gh.gamecenter.common.eventbus.EBShare
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.DialogNewsDetailShareBinding
import org.greenrobot.eventbus.EventBus
class NewsShareDialog : BaseDraggableDialogFragment() {
private lateinit var mBinding: DialogNewsDetailShareBinding
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 DialogNewsDetailShareBinding.inflate(inflater, container, false)
.apply { mBinding = this }.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mBinding.run {
tvTitle.text = mShareEntity?.shareTitle ?: ""
vWechatBackground.setOnClickListener {
logEvent("微信")
mShareUtils?.wechatShare()
}
vWechatMomentsBackground.setOnClickListener {
logEvent("朋友圈")
mShareUtils?.wechatMomentsShare()
}
vQqBackground.setOnClickListener {
logEvent("QQ好友")
mShareUtils?.qqShare()
}
vQqZoneBackground.setOnClickListener {
logEvent("QQ空间")
mShareUtils?.qZoneShare()
}
vWeiboBackground.setOnClickListener {
logEvent("新浪微博")
mShareUtils?.sinaWeiboShare()
}
vSmsBackground.setOnClickListener {
logEvent("短信")
EventBus.getDefault().post(EBShare(ShareUtils.shareEntrance, "短信"))
mShareUtils?.shortMessageShare()
}
vCopyLinkBackground.setOnClickListener {
logEvent("复制链接")
EventBus.getDefault().post(EBShare(ShareUtils.shareEntrance, "复制链接"))
mShareUtils?.copyLink(mShareUtils?.shareUrl ?: "")
}
tvCancel.setOnClickListener {
dismissAllowingStateLoss()
}
}
}
private fun getShareUtils(): ShareUtils {
val shareUtils = ShareUtils.getInstance(requireContext())
mShareEntity?.run {
shareUtils.shareParamsDetail(
requireActivity(),
shareUrl,
shareIcon,
shareTitle,
shareSummary,
shareEntrance,
id
)
}
return shareUtils
}
private fun logEvent(shareType: String) {
NewLogUtils.logViewOrClickGameCollectionDetail(
"click_game_collect_detail_share",
mShareEntity?.shareTitle ?: "",
mShareEntity?.id ?: "",
shareType
)
SensorsBridge.trackEvent(
"GameCollectDetailShareClick",
"game_collect_title", mShareEntity?.shareTitle ?: "",
"game_collect_id", mShareEntity?.id ?: "",
"share_type", shareType
)
}
override fun getRootView(): View = mBinding.root
override fun getDragCloseView(): View = mBinding.dragClose
override fun onDarkModeChanged() {
super.onDarkModeChanged()
mBinding.run {
container.background = R.drawable.game_detail_more_dialog_background.toDrawable(requireContext())
tvCancel.background = R.drawable.bg_common_button_light_fill_gray.toDrawable(requireContext())
dividerLine1.setBackgroundColor(R.color.ui_divider.toColor(requireContext()))
tvCancel.setTextColor(R.color.text_secondary.toColor(requireContext()))
tvTitle.setTextColor(R.color.text_secondary.toColor(requireContext()))
handleView.background = R.drawable.download_dialog_close_hint.toDrawable(requireContext())
clShare.children.forEach {
if (it is TextView) {
it.setTextColor(R.color.text_tertiary.toColor(requireContext()))
}
}
tvCopyLink.setTextColor(R.color.text_tertiary.toColor(requireContext()))
}
}
companion object {
const val KEY_SHARE = "share"
const val REQUEST_CODE = 1105
@JvmStatic
fun show(activity: AppCompatActivity, shortId: String?, id: String?, gameIcon: String?, title: String?) {
val entity = NormalShareEntity(
id ?: "",
activity.getString(R.string.share_news_article_url, shortId),
gameIcon ?: activity.getString(R.string.gh_icon_url),
title ?: "",
activity.getString(R.string.news_share_description),
ShareUtils.ShareEntrance.news,
null
)
NewsShareDialog().apply {
arguments = Bundle().apply {
putParcelable(KEY_SHARE, entity)
}
}.show(
activity.supportFragmentManager,
NewsShareDialog::class.java.name
)
}
}
}

View File

@ -1,37 +1,261 @@
package com.gh.gamecenter.personalhome
import android.app.Activity
import android.content.Context
import android.text.SpannableStringBuilder
import android.view.View
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.common.util.DialogUtils
import com.gh.common.util.DirectUtils
import com.gh.common.util.NewsUtils
import com.gh.common.view.ImageContainerView
import com.gh.common.view.ImageContainerView.Companion.toImageContainerData
import com.gh.gamecenter.ImageViewerActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.callback.ConfirmListener
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.SpanBuilder
import com.gh.gamecenter.databinding.CommunityAnswerItemBinding
import com.gh.gamecenter.entity.PersonalHistoryEntity
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.forum.detail.ForumDetailActivity
import com.gh.gamecenter.forum.home.AnswerArticleVideoViewEventHelper
import com.gh.gamecenter.forum.home.ArticleItemVideoView.Companion.toArticleVideoData
import com.gh.gamecenter.qa.answer.BaseAnswerOrArticleItemViewHolder
import com.gh.gamecenter.video.detail.VideoDetailContainerViewModel
import com.shuyu.gsyvideoplayer.builder.GSYVideoOptionBuilder
import com.shuyu.gsyvideoplayer.listener.GSYSampleCallBack
import com.shuyu.gsyvideoplayer.utils.OrientationUtils
class PersonalItemViewHolder(val binding: CommunityAnswerItemBinding) :
class PersonalItemViewHolder(
val context: Context,
val binding: CommunityAnswerItemBinding,
private val itemClickCallback: (historyEntity: PersonalHistoryEntity, position: Int) -> Unit
) :
BaseAnswerOrArticleItemViewHolder(binding.root) {
fun bindPersonalItem(entity: PersonalHistoryEntity, entrance: String) {
fun bindPersonalItem(historyEntity: PersonalHistoryEntity, entrance: String, position: Int) {
commentCount.isClickable = true
bindCommendAndVote(entity, entrance)
bindCommendAndVote(historyEntity, entrance)
if (entity.community.type == "official_bbs") {
forumIcon?.displayGameIcon(entity.community.icon, null)
if (historyEntity.community.type == "official_bbs") {
forumIcon?.displayGameIcon(historyEntity.community.icon, null)
} else {
forumIcon?.displayGameIcon(
entity.community.game?.getIcon(),
entity.community.game?.iconSubscript,
entity.community.game?.iconFloat
historyEntity.community.game?.getIcon(),
historyEntity.community.game?.iconSubscript,
historyEntity.community.game?.iconFloat
)
}
forumNameContainer?.setOnClickListener {
MtaHelper.onEvent(getEventId(entrance), getKey(entrance), entity.community.name)
MtaHelper.onEvent(getEventId(entrance), getKey(entrance), historyEntity.community.name)
itemView.context.startActivity(
ForumDetailActivity.getIntent(
itemView.context,
entity.community.id,
historyEntity.community.id,
entrance
)
)
}
val answer = AnswerEntity()
answer.images = historyEntity.images
answer.imagesInfo = historyEntity.imagesInfo
answer.videos = historyEntity.videos
answer.type = historyEntity.type
answer.id = historyEntity.id
answer.communityId = historyEntity.community.id
answer.communityName = historyEntity.community.name
answer.status = historyEntity.status
answer.user.id = historyEntity.user.id ?: ""
binding.imageContainer.bindData(
answer.toImageContainerData(),
object : ImageContainerView.OnImageContainerEventListener {
override fun onImageClick(
images: List<String>,
position: Int,
imageViewList: ArrayList<SimpleDraweeView>
) {
val intent = ImageViewerActivity.getIntent(
context, answer.images.toArrayList(), position, imageViewList,
if (answer.type == "community_article") answer else null, entrance, true
)
context.startActivity(intent)
}
override fun onVideoCLick(videoId: String) {
DirectUtils.directToVideoDetail(
context,
videoId,
VideoDetailContainerViewModel.Location.VIDEO_HOT.value,
showComment = false,
entrance = entrance,
path = ""
)
}
})
bindVideoData(historyEntity.transformForumVideoEntity())
binding.run {
topLine.goneIf(position == 0)
forumNameLl.visibleIf(historyEntity.community.id.isNotEmpty())
userIcon.display(historyEntity.user.border, historyEntity.user.icon, historyEntity.user.auth?.icon)
userName.text = historyEntity.user.name
ImageUtils.display(userBadgeIcon, historyEntity.user.badge?.icon)
userBadgeIcon.goneIf(historyEntity.user.badge == null)
userBadgeName.text = historyEntity.user.badge?.name
time.text =
(if (historyEntity.me.isFollower || historyEntity.status == "pending" || historyEntity.status == "fail") " · " else "") + NewsUtils.getFormattedTime(
historyEntity.time!!
)
statusTv.goneIf(!(historyEntity.status == "pending" || historyEntity.status == "fail") && !historyEntity.me.isFollower)
statusTv.setText(
when {
historyEntity.me.isFollower -> R.string.follow_status
historyEntity.status == "pending" -> R.string.content_pending_status
else -> R.string.fail_status
}
)
statusTv.setTextColor(
when {
historyEntity.me.isFollower -> R.color.theme_alpha_80
historyEntity.status == "pending" -> R.color.text_tertiary
else -> R.color.text_CCFF5269
}.toColor(context)
)
statusTv.setDrawableStart(
when {
historyEntity.me.isFollower -> R.drawable.ic_forum_follow
historyEntity.status == "pending" -> R.drawable.ic_forum_pending
else -> R.drawable.icon_forum_fail
}
)
authTv.goneIf(historyEntity.user?.auth == null) {
authTv.text = historyEntity.user?.auth?.text
time.setPadding(8F.dip2px(), 0, 0, 0)
}
if (statusTv.visibility == View.VISIBLE) {
authTv.setPadding(8F.dip2px(), 0, 0, 0)
}
title.goneIf(historyEntity.type.contains("answer"))
title.text =
if ((historyEntity.type.contains("question") || historyEntity.type.contains("article") || historyEntity.type.contains(
"video"
))
) historyEntity.title else historyEntity.question.title
questionTitle.goneIf(!historyEntity.type.contains("answer"))
questionTitle.text = historyEntity.question.title
content.goneIf(historyEntity.brief.isEmpty() && historyEntity.des.isEmpty() && historyEntity.description.isEmpty())
content.text = when {
historyEntity.type.contains("video") -> historyEntity.des
historyEntity.type.contains("question") -> historyEntity.description
else -> historyEntity.brief
}
forumNameTv.text = historyEntity.community.name
when (historyEntity.type) {
"question" -> {
val titleStr = " ${historyEntity.title}"
val drawable = R.drawable.ic_ask_label.toDrawable()
drawable?.run {
setBounds(0, 0, 16F.dip2px(), 16F.dip2px())
title.text = SpanBuilder(titleStr).image(0, 1, this).build()
}
}
"answer" -> {
val briefStr = " ${historyEntity.brief}"
val drawable = R.drawable.ic_answer_label.toDrawable()
drawable?.run {
setBounds(0, 0, 16F.dip2px(), 16F.dip2px())
content.text = SpanBuilder(briefStr).image(0, 1, this).build()
}
}
"community_article" -> {
if (historyEntity.getPassVideos().isNotEmpty() && historyEntity.images.isNotEmpty()) {
val titleStr = title.text
val videoSpan =
SpanBuilder(" ").image(1, " ".length, R.drawable.ic_article_video_label).build()
title.text = SpannableStringBuilder()
.append(titleStr)
.append(videoSpan)
}
}
}
title.setOnClickListener {
itemView.performClick()
}
historyEntity.user.run {
userBadgeName.setOnClickListener { userBadgeIcon.performClick() }
userBadgeIcon.setOnClickListener {
DialogUtils.showViewBadgeDialog(context, badge, object : ConfirmListener {
override fun onConfirm() {
DirectUtils.directToBadgeWall(context, id, name, icon)
}
})
}
}
// 禁止click事件穿透
userIcon.setOnClickListener {}
}
itemView.setOnClickListener {
itemClickCallback.invoke(historyEntity, position)
}
}
private fun bindVideoData(entity: ForumVideoEntity) {
binding.run {
if (entity.url.isEmpty()) {
horizontalVideoView.visibility = View.GONE
verticalVideoView.visibility = View.GONE
} else {
val videoInfo = entity.videoInfo
val visibleView = if (videoInfo.height > videoInfo.width) {
horizontalVideoView.visibility = View.GONE
verticalVideoView.visibility = View.VISIBLE
verticalVideoView
} else {
horizontalVideoView.visibility = View.VISIBLE
verticalVideoView.visibility = View.GONE
horizontalVideoView
}
val orientationUtils = OrientationUtils(context as Activity, visibleView)
orientationUtils.isEnable = false
GSYVideoOptionBuilder()
.setIsTouchWiget(false)
.setUrl(entity.url)
.setRotateViewAuto(false)
.setCacheWithPlay(true)
.setRotateWithSystem(false)
.setReleaseWhenLossAudio(true)
.setLooping(false)
.setShowFullAnimation(false)
.setEnlargeImageRes(R.drawable.ic_game_detail_enter_full_screen)
.setShrinkImageRes(R.drawable.ic_game_detail_exit_full_screen)
.setVideoAllCallBack(object : GSYSampleCallBack() {
override fun onQuitFullscreen(url: String?, vararg objects: Any) {
orientationUtils.backToProtVideo()
}
})
.build(visibleView)
visibleView.updateVideoData(
entity.toArticleVideoData(),
AnswerArticleVideoViewEventHelper(entity, orientationUtils)
)
}
}
}
}

View File

@ -7,9 +7,13 @@ import android.util.SparseBooleanArray
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.common.util.DialogUtils
import com.gh.common.util.DirectUtils
import com.gh.common.util.NewsUtils
import com.gh.common.view.ImageContainerView
import com.gh.common.view.ImageContainerView.Companion.toImageContainerData
import com.gh.gamecenter.ImageViewerActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.adapter.viewholder.PersonalHomeRatingViewHolder
import com.gh.gamecenter.common.baselist.ListAdapter
@ -24,9 +28,12 @@ import com.gh.gamecenter.databinding.PersonalHomeRatingBinding
import com.gh.gamecenter.entity.PersonalHistoryEntity
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.forum.home.AnswerArticleVideoViewEventHelper
import com.gh.gamecenter.forum.home.ArticleItemVideoView
import com.gh.gamecenter.forum.home.ArticleItemVideoView.Companion.toArticleVideoData
import com.gh.gamecenter.gamedetail.rating.edit.RatingEditActivity
import com.gh.gamecenter.personalhome.PersonalItemViewHolder
import com.gh.gamecenter.video.detail.VideoDetailContainerViewModel
import com.shuyu.gsyvideoplayer.builder.GSYVideoOptionBuilder
import com.shuyu.gsyvideoplayer.listener.GSYSampleCallBack
import com.shuyu.gsyvideoplayer.utils.OrientationUtils
@ -56,13 +63,15 @@ class UserHistoryAdapter(
view = mLayoutInflater.inflate(R.layout.refresh_footerview, parent, false)
FooterViewHolder(view)
}
ItemViewType.RATING_ITEM -> {
view = mLayoutInflater.inflate(R.layout.personal_home_rating, parent, false)
PersonalHomeRatingViewHolder(PersonalHomeRatingBinding.bind(view))
}
else -> {
view = mLayoutInflater.inflate(R.layout.community_answer_item, parent, false)
PersonalItemViewHolder(CommunityAnswerItemBinding.bind(view))
PersonalItemViewHolder(mContext, CommunityAnswerItemBinding.bind(view), itemClickCallback)
}
}
}
@ -81,196 +90,7 @@ class UserHistoryAdapter(
private fun bindNormalItem(holder: PersonalItemViewHolder, position: Int) {
val historyEntity = mEntityList[holder.adapterPosition]
holder.bindPersonalItem(historyEntity, mEntrance)
val answer = AnswerEntity()
answer.images = historyEntity.images
answer.imagesInfo = historyEntity.imagesInfo
answer.videos = historyEntity.videos
answer.type = historyEntity.type
answer.id = historyEntity.id
answer.communityId = historyEntity.community.id
answer.communityName = historyEntity.community.name
answer.status = historyEntity.status ?: "pending"
answer.user.id = historyEntity.user?.id ?: ""
holder.binding.imageContainer.bindData(answer, mEntrance)
bindVideoData(holder.binding, historyEntity.transformForumVideoEntity())
holder.binding.run {
topLine.goneIf(position == 0)
forumNameLl.visibleIf(historyEntity.community.id.isNotEmpty())
userIcon.display(historyEntity.user?.border, historyEntity.user?.icon, historyEntity.user?.auth?.icon)
userName.text = historyEntity.user?.name
ImageUtils.display(userBadgeIcon, historyEntity.user?.badge?.icon)
userBadgeIcon.goneIf(historyEntity.user?.badge == null)
userBadgeName.text = historyEntity.user?.badge?.name
time.text =
(if (historyEntity.me.isFollower || historyEntity.status == "pending" || historyEntity.status == "fail") " · " else "") + NewsUtils.getFormattedTime(
historyEntity.time!!
)
statusTv.goneIf(!(historyEntity.status == "pending" || historyEntity.status == "fail") && !historyEntity.me.isFollower)
statusTv.setText(
when {
historyEntity.me.isFollower -> R.string.follow_status
historyEntity.status == "pending" -> R.string.content_pending_status
else -> R.string.fail_status
}
)
statusTv.setTextColor(
when {
historyEntity.me.isFollower -> R.color.theme_alpha_80
historyEntity.status == "pending" -> R.color.text_tertiary
else -> R.color.text_CCFF5269
}.toColor(mContext)
)
statusTv.setDrawableStart(
when {
historyEntity.me.isFollower -> R.drawable.ic_forum_follow
historyEntity.status == "pending" -> R.drawable.ic_forum_pending
else -> R.drawable.icon_forum_fail
}
)
authTv.goneIf(historyEntity.user?.auth == null) {
authTv.text = historyEntity.user?.auth?.text
time.setPadding(8F.dip2px(), 0, 0, 0)
}
if (statusTv.visibility == View.VISIBLE) {
authTv.setPadding(8F.dip2px(), 0, 0, 0)
}
title.goneIf(historyEntity.type.contains("answer"))
title.text =
if ((historyEntity.type.contains("question") || historyEntity.type.contains("article") || historyEntity.type.contains(
"video"
))
) historyEntity.title else historyEntity.question.title
questionTitle.goneIf(!historyEntity.type.contains("answer"))
questionTitle.text = historyEntity.question.title
content.goneIf(historyEntity.brief.isEmpty() && historyEntity.des.isEmpty() && historyEntity.description.isEmpty())
content.text = when {
historyEntity.type.contains("video") -> historyEntity.des
historyEntity.type.contains("question") -> historyEntity.description
else -> historyEntity.brief
}
forumNameTv.text = historyEntity.community.name
when (historyEntity.type) {
"question" -> {
val titleStr = " ${historyEntity.title}"
val drawable = R.drawable.ic_ask_label.toDrawable()
drawable?.run {
setBounds(0, 0, 16F.dip2px(), 16F.dip2px())
title.text = SpanBuilder(titleStr).image(0, 1, this).build()
}
}
"answer" -> {
val briefStr = " ${historyEntity.brief}"
val drawable = R.drawable.ic_answer_label.toDrawable()
drawable?.run {
setBounds(0, 0, 16F.dip2px(), 16F.dip2px())
content.text = SpanBuilder(briefStr).image(0, 1, this).build()
}
}
"community_article" -> {
if (historyEntity.getPassVideos().isNotEmpty() && historyEntity.images.isNotEmpty()) {
val titleStr = title.text
val videoSpan =
SpanBuilder(" ").image(1, " ".length, R.drawable.ic_article_video_label).build()
title.text = SpannableStringBuilder()
.append(titleStr)
.append(videoSpan)
}
}
}
title.setOnClickListener {
holder.itemView.performClick()
}
historyEntity.user?.run {
userBadgeName.setOnClickListener { userBadgeIcon.performClick() }
userBadgeIcon.setOnClickListener {
DialogUtils.showViewBadgeDialog(mContext, badge, object : ConfirmListener {
override fun onConfirm() {
DirectUtils.directToBadgeWall(mContext, id, name, icon)
}
})
}
}
// 禁止click事件穿透
userIcon.setOnClickListener {}
}
holder.itemView.setOnClickListener {
itemClickCallback.invoke(historyEntity, position)
}
}
private fun bindVideoData(binding: CommunityAnswerItemBinding, entity: ForumVideoEntity) {
binding.run {
if (entity.url.isEmpty()) {
horizontalVideoView.visibility = View.GONE
verticalVideoView.visibility = View.GONE
} else {
val videoInfo = entity.videoInfo
val visibleView = if (videoInfo.height > videoInfo.width) {
horizontalVideoView.visibility = View.GONE
verticalVideoView.visibility = View.VISIBLE
verticalVideoView
} else {
horizontalVideoView.visibility = View.VISIBLE
verticalVideoView.visibility = View.GONE
horizontalVideoView
}
val orientationUtils = OrientationUtils(mContext as Activity, visibleView)
orientationUtils.isEnable = false
GSYVideoOptionBuilder()
.setIsTouchWiget(false)
.setUrl(entity.url)
.setRotateViewAuto(false)
.setCacheWithPlay(true)
.setRotateWithSystem(false)
.setReleaseWhenLossAudio(true)
.setLooping(false)
.setShowFullAnimation(false)
.setEnlargeImageRes(R.drawable.ic_game_detail_enter_full_screen)
.setShrinkImageRes(R.drawable.ic_game_detail_exit_full_screen)
.setVideoAllCallBack(object : GSYSampleCallBack() {
override fun onQuitFullscreen(url: String?, vararg objects: Any) {
orientationUtils.backToProtVideo()
visibleView.uploadVideoStreamingPlaying("退出全屏")
}
})
.build(visibleView)
visibleView.run {
updateVideoData(entity)
updateThumb(entity.poster)
updateDurationTv(entity.duration)
setVideoStatus(entity.status)
fullscreenButton.setOnClickListener {
val horizontalVideoView = startWindowFullscreen(mContext, true, true) as? ArticleItemVideoView
if (horizontalVideoView == null) {
toastInInternalRelease("全屏失败,请向技术人员提供具体的操作步骤")
return@setOnClickListener
}
orientationUtils.resolveByClick()
horizontalVideoView.uuid = uuid
horizontalVideoView.updateVideoData(entity)
horizontalVideoView.updateThumb(entity.poster)
horizontalVideoView.violenceUpdateMuteStatus()
horizontalVideoView.setFullViewStatus()
uploadVideoStreamingPlaying("开始播放")
uploadVideoStreamingPlaying("点击全屏")
}
}
}
}
holder.bindPersonalItem(historyEntity, mEntrance, position)
}
private fun bindRatingItem(holder: PersonalHomeRatingViewHolder, position: Int) {

View File

@ -15,6 +15,7 @@ import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.*
import com.gh.gamecenter.entity.PersonalHistoryEntity
import com.gh.gamecenter.entity.VoteEntity
import com.gh.gamecenter.forum.detail.ForumDetailActivity
import com.gh.gamecenter.qa.article.detail.ArticleDetailActivity
@ -22,6 +23,7 @@ import com.gh.gamecenter.qa.comment.CommentActivity
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.feature.entity.CommunityItemData
import com.gh.gamecenter.feature.entity.ArticleEntity
import com.gh.gamecenter.feature.entity.Count
import com.gh.gamecenter.qa.entity.QuestionsDetailEntity
import com.gh.gamecenter.qa.questions.invite.QuestionsInviteActivity
import com.gh.gamecenter.qa.questions.newdetail.NewQuestionDetailActivity
@ -52,7 +54,12 @@ open class BaseAnswerOrArticleItemViewHolder(itemView: View) : BaseRecyclerViewH
val forumIcon: GameIconView? = itemView.findViewById(R.id.forumIcon)
open fun bindCommendAndVote(entity: CommunityItemData, entrance: String, path: String? = null, position: Int? = null) {
open fun bindCommendAndVote(
entity: CommunityItemData,
entrance: String,
path: String? = null,
position: Int? = null
) {
binNormalView(entity)
commentCount.setOnClickListener {
@ -68,6 +75,7 @@ open class BaseAnswerOrArticleItemViewHolder(itemView: View) : BaseRecyclerViewH
)
itemView.context.startActivity(intent)
}
"video" -> {
val communityId = if (!entity.communityId.isNullOrEmpty()) entity.communityId
?: "" else entity.community.id
@ -77,6 +85,7 @@ open class BaseAnswerOrArticleItemViewHolder(itemView: View) : BaseRecyclerViewH
)
)
}
"answer" -> {
val intent = CommentActivity.getAnswerCommentIntent(
itemView.context,
@ -86,6 +95,7 @@ open class BaseAnswerOrArticleItemViewHolder(itemView: View) : BaseRecyclerViewH
)
itemView.context.startActivity(intent)
}
else -> {
val intent = NewQuestionDetailActivity.getCommentIntent(
itemView.context, entity.id, entrance, ""
@ -223,11 +233,17 @@ open class BaseAnswerOrArticleItemViewHolder(itemView: View) : BaseRecyclerViewH
}
}
if (entrance == "社区-关注") {
SensorsBridge.trackFollowPageContentClick("帖子", entity.title)
}
when (entity.type) {
"question" -> {
val intent = NewQuestionDetailActivity.getCommentIntent(it.context, entity.id, entrance, "")
itemView.context.startActivity(intent)
}
"answer" -> {
val intent = NewQuestionDetailActivity.getCommentIntent(
it.context,
@ -238,6 +254,7 @@ open class BaseAnswerOrArticleItemViewHolder(itemView: View) : BaseRecyclerViewH
)
itemView.context.startActivity(intent)
}
"video" -> {
itemView.context.startActivity(
ForumVideoDetailActivity.getIntent(
@ -248,6 +265,7 @@ open class BaseAnswerOrArticleItemViewHolder(itemView: View) : BaseRecyclerViewH
)
)
}
else -> {
val communityId = entity.community.id
val intent = ArticleDetailActivity.getCommentIntent(
@ -311,10 +329,12 @@ open class BaseAnswerOrArticleItemViewHolder(itemView: View) : BaseRecyclerViewH
voteCountContainer.visibility = View.GONE
commentCount.text = if (entity.count.answer > 0) entity.count.answer.toString() else "回答"
}
"answer" -> {
commentCount.text = if (entity.count.reply > 0) entity.count.reply.toString() else "评论"
voteCount.text = if (entity.count.vote > 0) entity.count.vote.toString() else "赞同"
}
else -> {
commentCount.text = if (entity.count.comment > 0) entity.count.comment.toString() else "评论"
voteCount.text = if (entity.count.vote > 0) entity.count.vote.toString() else "赞同"
@ -360,8 +380,7 @@ open class BaseAnswerOrArticleItemViewHolder(itemView: View) : BaseRecyclerViewH
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BiResponse<ResponseBody>() {
override fun onSuccess(data: ResponseBody) {
entity.count.vote = entity.count.vote + 1
entity.me.isVoted = true
entity.vote(true)
voteCount.text = if (entity.count.vote > 0) entity.count.vote.toString() else "赞同"
setVoteAndCommentStyle(entity)
}
@ -396,7 +415,7 @@ open class BaseAnswerOrArticleItemViewHolder(itemView: View) : BaseRecyclerViewH
entity.me.isAnswerVoted = true
}
ToastUtils.showToast("已赞同")
entity.count.vote = entity.count.vote + 1
entity.vote(true)
voteCount.text = if (entity.count.vote > 0) entity.count.vote.toString() else "赞同"
setVoteAndCommentStyle(entity)
}
@ -412,16 +431,19 @@ open class BaseAnswerOrArticleItemViewHolder(itemView: View) : BaseRecyclerViewH
Utils.toast(itemView.context, R.string.ask_vote_hint)
true
}
403036 -> {
Utils.toast(itemView.context, R.string.ask_vote_limit_hint)
true
}
404001 -> {
Utils.toast(itemView.context, "内容可能已被删除")
entity.active = false
setVoteAndCommentStyle(entity)
true
}
else -> {
setVoteAndCommentStyle(entity)
false
@ -443,8 +465,7 @@ open class BaseAnswerOrArticleItemViewHolder(itemView: View) : BaseRecyclerViewH
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BiResponse<ResponseBody>() {
override fun onSuccess(data: ResponseBody) {
entity.count.vote = entity.count.vote - 1
entity.me.isVoted = false
entity.vote(false)
voteCount.text = if (entity.count.vote > 0) entity.count.vote.toString() else "赞同"
setVoteAndCommentStyle(entity)
Utils.toast(itemView.context, "取消点赞")
@ -473,12 +494,12 @@ open class BaseAnswerOrArticleItemViewHolder(itemView: View) : BaseRecyclerViewH
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Response<VoteEntity>() {
override fun onResponse(response: VoteEntity?) {
entity.count.vote = entity.count.vote - 1
if (entity.type == "community_article") {
entity.me.isCommunityArticleVote = false
} else {
entity.me.isAnswerVoted = false
}
entity.vote(false)
voteCount.text = if (entity.count.vote > 0) entity.count.vote.toString() else "赞同"
setVoteAndCommentStyle(entity)
Utils.toast(itemView.context, "取消赞同")
@ -495,16 +516,19 @@ open class BaseAnswerOrArticleItemViewHolder(itemView: View) : BaseRecyclerViewH
Utils.toast(itemView.context, R.string.ask_vote_hint)
true
}
403036 -> {
Utils.toast(itemView.context, R.string.ask_vote_limit_hint)
true
}
404001 -> {
Utils.toast(itemView.context, "内容可能已被删除")
entity.active = false
setVoteAndCommentStyle(entity)
true
}
else -> {
setVoteAndCommentStyle(entity)
false

View File

@ -8,10 +8,14 @@ import android.view.View
import android.widget.LinearLayout
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.content.ContextCompat
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.common.util.DialogUtils
import com.gh.common.util.DirectUtils
import com.gh.common.util.NewsUtils
import com.gh.common.view.ImageContainerView
import com.gh.common.view.ImageContainerView.Companion.toImageContainerData
import com.gh.gamecenter.CollectionActivity
import com.gh.gamecenter.ImageViewerActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.callback.ConfirmListener
@ -23,6 +27,7 @@ import com.gh.gamecenter.databinding.CommunityAnswerItemBinding
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.feature.entity.ArticleEntity
import com.gh.gamecenter.video.detail.VideoDetailContainerViewModel
/**
* 因为社区回答和社区文章的样式完全一样,所以就统一处理吧!有需要时可以直接复制一份作为单独的社区文章样式
@ -123,7 +128,36 @@ class CommunityAnswerItemViewHolder(val binding: CommunityAnswerItemBinding) :
}
binding.userIcon.display(entity.user.border, entity.user.icon, entity.user.auth?.icon)
binding.imageContainer.bindData(entity, entrance, path)
binding.imageContainer.bindData(
entity.toImageContainerData(),
object : ImageContainerView.OnImageContainerEventListener {
override fun onImageClick(
images: List<String>,
position: Int,
imageViewList: ArrayList<SimpleDraweeView>
) {
if (entity.communityId.isNullOrEmpty()) {
entity.communityId = entity.bbs.id
}
val intent = ImageViewerActivity.getIntent(
itemView.context, entity.images.toArrayList(), position, imageViewList,
if (entity.type == "community_article") entity else null, entrance, true
)
itemView.context.startActivity(intent)
}
override fun onVideoCLick(videoId: String) {
DirectUtils.directToVideoDetail(
itemView.context,
videoId,
VideoDetailContainerViewModel.Location.VIDEO_HOT.value,
showComment = false,
entrance = entrance,
path = path
)
}
})
val spanBuilder = SpannableStringBuilder()
if (entity.type == "question") {

View File

@ -30,6 +30,8 @@ import com.gh.gamecenter.entity.DeviceDialogEntity;
import com.gh.gamecenter.entity.DialogEntity;
import com.gh.gamecenter.entity.DiscoveryGameCardEntity;
import com.gh.gamecenter.entity.DiscoveryGameCardLabel;
import com.gh.gamecenter.entity.FollowCommonContentCollection;
import com.gh.gamecenter.entity.FollowDynamicEntity;
import com.gh.gamecenter.entity.FollowersOrFansEntity;
import com.gh.gamecenter.entity.ForumActivityCategoryEntity;
import com.gh.gamecenter.entity.ForumActivityEntity;
@ -103,6 +105,7 @@ import com.gh.gamecenter.feature.entity.BackgroundImageEntity;
import com.gh.gamecenter.feature.entity.CommentEntity;
import com.gh.gamecenter.feature.entity.CommentnumEntity;
import com.gh.gamecenter.feature.entity.ConcernEntity;
import com.gh.gamecenter.entity.FollowUserEntity;
import com.gh.gamecenter.feature.entity.ForumVideoEntity;
import com.gh.gamecenter.feature.entity.GameEntity;
import com.gh.gamecenter.feature.entity.LibaoEntity;
@ -119,6 +122,7 @@ import com.gh.gamecenter.feature.entity.ServerCalendarGame;
import com.gh.gamecenter.feature.entity.ServerCalendarNotifySetting;
import com.gh.gamecenter.feature.entity.SettingsEntity;
import com.gh.gamecenter.feature.entity.SimulatorEntity;
import com.gh.gamecenter.feature.entity.UserEntity;
import com.gh.gamecenter.feature.entity.ViewsEntity;
import com.gh.gamecenter.feature.entity.WXSubscribeMsgConfig;
import com.gh.gamecenter.floatingwindow.FloatingWindowEntity;
@ -144,6 +148,7 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import io.reactivex.Completable;
import io.reactivex.Observable;
import io.reactivex.Single;
import okhttp3.RequestBody;
@ -761,6 +766,48 @@ public interface ApiService {
@Query("view") String view,
@Query("page_size") int pageSize);
@GET("community/user/{user_id}/my_follow")
Single<List<FollowUserEntity>> getMyFollowedUsers(@Path("user_id") String userId,
@Query("version") String version,
@Query("channel") String channel,
@Query("page") int page,
@Query("refresh") boolean refresh,
@QueryMap Map<String, Object> params);
@GET("community/user/{user_id}/follow_data")
Single<List<FollowDynamicEntity>> getFollowUpdates(@Path("user_id") String userId,
@Query("type") String type,
@Query("page") int page,
@Query("version") String version,
@Query("channel") String channel);
@POST("community/user/{user_id}/my_follow/operate_top")
Single<ResponseBody> operateTop(
@Path("user_id") String userId,
@Query("version") String version,
@Query("channel") String channel,
@Body RequestBody body);
@POST("community/user/{user_id}/cancel_tip/{type}/{type_id}")
Single<ResponseBody> postRead(@Path("user_id") String userId,
@Path("type") String type,
@Path("type_id") String typeId);
@GET("community/follow_tab/recommend_user")
Single<List<UserEntity>> getRecommendUser(
@Query("version") String version,
@Query("channel") String channel
);
/**
* 关注-通用内容合集配置
*/
@GET("community/follow_tab/common_collection")
Single<List<FollowCommonContentCollection>> getFollowCommonCollection(
@Query("version") String version,
@Query("channel") String channel,
@Query("page") int page);
@GET("settings")
Observable<SettingsEntity> getSettings(@Query("version") String version, @Query("channel") String channel);
@ -3244,7 +3291,7 @@ public interface ApiService {
*/
@POST("messages/wechat/one_time/call_back")
Single<ResponseBody> postWxSubscribeMsgCallback(@Query("filter") String filter,
@Body RequestBody body);
@Body RequestBody body);
/**
* 获取网游插件可能用到的签名

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">
<alpha
android:duration="400"
android:fromYDelta="100%p"
android:toYDelta="100%p" />
</set>

View File

@ -5,6 +5,6 @@
<item android:color="@color/text_primary" android:state_focused="true" />
<item android:color="@color/text_primary" android:state_selected="true" />
<item android:color="@color/text_primary" android:state_checked="true" />
<item android:color="@color/text_A2ADB8" />
<item android:color="@color/community_forum_more" />
</selector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 484 KiB

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