Compare commits

...

21 Commits

Author SHA1 Message Date
5d645d408b feat:社区推荐信息流改版&新增图文内容类型—客户端 https://jira.shanqu.cc/browse/GHZSCY-6136 2024-10-24 16:54:20 +08:00
880c7df2ce feat:社区推荐信息流改版&新增图文内容类型—客户端 https://jira.shanqu.cc/browse/GHZSCY-6136 2024-10-22 17:40:10 +08:00
8cf8207d5b feat:社区推荐信息流改版&新增图文内容类型—客户端 https://jira.shanqu.cc/browse/GHZSCY-6136 2024-10-21 18:15:24 +08:00
2ce6b1665d feat:社区推荐信息流改版&新增图文内容类型—客户端 https://jira.shanqu.cc/browse/GHZSCY-6136 2024-10-17 17:01:36 +08:00
ba70162638 feat:社区推荐信息流改版&新增图文内容类型—客户端 https://jira.shanqu.cc/browse/GHZSCY-6136 2024-10-17 16:04:21 +08:00
a3d1b1a262 feat:社区推荐信息流改版&新增图文内容类型—客户端 https://jira.shanqu.cc/browse/GHZSCY-6136 2024-10-17 11:17:34 +08:00
ebcb68fb7e feat:https://jira.shanqu.cc/browse/GHZSCY-6136 https://jira.shanqu.cc/browse/GHZSCY-6136 2024-10-17 09:55:02 +08:00
1f1bf4f9ee feat:社区推荐信息流改版&新增图文内容类型—客户端 https://jira.shanqu.cc/browse/GHZSCY-6136 2024-10-15 15:59:41 +08:00
9de1ef1f99 fix:GHZSCY-6136 社区推荐信息流改版&新增图文内容类型—客户端 2024-10-15 15:59:41 +08:00
b5bfce7a60 社区相关埋点 2024-10-15 15:59:41 +08:00
49e77f7464 feat:社区推荐信息流改版&新增图文内容类型—客户端 https://jira.shanqu.cc/browse/GHZSCY-6136 2024-10-15 15:59:41 +08:00
e3056ec6a1 feat:社区推荐信息流改版&新增图文内容类型—客户端 https://jira.shanqu.cc/browse/GHZSCY-6136 2024-10-15 15:59:41 +08:00
b06a452b8b feat:社区推荐信息流改版&新增图文内容类型—客户端 https://jira.shanqu.cc/browse/GHZSCY-6136 2024-10-15 15:59:41 +08:00
ae269298e4 fix:社区推荐信息流改版&新增图文内容类型—0912UI测试—客户端 https://jira.shanqu.cc/browse/GHZSCY-6676 2024-10-15 15:59:41 +08:00
bfac3a6684 fix:社区推荐信息流改版&新增图文内容类型—0912UI测试—客户端 https://jira.shanqu.cc/browse/GHZSCY-6676 2024-10-15 15:59:41 +08:00
03921fb64d fix:社区推荐信息流改版&新增图文内容类型—0912UI测试—客户端 https://jira.shanqu.cc/browse/GHZSCY-6676 2024-10-15 15:59:41 +08:00
01603ee8c9 fix:社区推荐信息流改版&新增图文内容类型—0912UI测试—客户端 https://jira.shanqu.cc/browse/GHZSCY-6676 2024-10-15 15:59:41 +08:00
f22033b61f feat:社区推荐信息流改版&新增图文内容类型—客户端 https://jira.shanqu.cc/browse/GHZSCY-6136 2024-10-15 15:59:38 +08:00
8b44d65118 删除没有用到的资源 2024-10-15 15:59:14 +08:00
838caaa5fc feat:社区推荐信息流改版&新增图文内容类型—客户端 https://jira.shanqu.cc/browse/GHZSCY-6136 2024-10-15 15:59:14 +08:00
4c521c75b2 ci 2024-10-15 15:59:14 +08:00
164 changed files with 10293 additions and 305 deletions

View File

@ -72,6 +72,7 @@ android_build:
only:
- dev
- release
- feat/GHZSCY-6136
# 代码检查
sonarqube_analysis:
@ -103,6 +104,7 @@ sonarqube_analysis:
only:
- dev
- release
- feat/GHZSCY-6136
## 发送简易检测结果报告
send_sonar_report:
@ -121,6 +123,7 @@ send_sonar_report:
only:
- dev
- release
- feat/GHZSCY-6136
oss-upload&send-email:
tags:
@ -157,3 +160,4 @@ oss-upload&send-email:
only:
- dev
- release
- feat/GHZSCY-6136

View File

@ -815,6 +815,14 @@
<!-- android:taskAffinity="${applicationId}"-->
<!-- android:exported="true" />-->
<activity
android:name=".forum.home.recommend.PublishImageArticleActivity"
android:screenOrientation="portrait" />
<activity
android:name=".forum.home.recommend.ImageArticleDetailActivity"
android:screenOrientation="portrait" />
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}"

View File

@ -1,6 +1,7 @@
package com.gh.common.exposure
import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.common.loghub.LoghubUtils
import com.gh.gamecenter.common.loghub.TLogHubHelper
import com.gh.gamecenter.common.utils.FixedSizeLinkedHashSet
import com.gh.gamecenter.common.utils.toJson
@ -78,7 +79,12 @@ object ExposureManager {
private fun uploadExposures(eventSet: HashSet<ExposureEvent>, forced: Boolean) {
eventSet.forEach {
TLogHubHelper.sendLog(buildLog(it), LOG_STORE)
val jsonExposure = it.jsonExposure
if (jsonExposure == null) {
TLogHubHelper.sendLog(buildLog(it), LOG_STORE)
} else {
LoghubUtils.log(jsonExposure, it.logStore, true)
}
// LoghubHelper.uploadLog(buildLog(it), LOG_STORE, forced)
// it.recycle()
}

View File

@ -77,6 +77,19 @@ class NewCommentDetailProviderImpl : INewCommentDetailProvider {
)
}
override fun getImageArticleCommentIntent(
context: Context,
commentId: String,
imageArticleId: String,
topCommentId: String,
entrance: String,
path: String
):Intent {
return NewCommentDetailActivity.getImageArticleCommentIntent(
context, commentId, imageArticleId, topCommentId, entrance, path
)
}
override fun init(context: Context?) {
// Do nothing
}

View File

@ -14,6 +14,7 @@ import com.gh.gamecenter.adapter.ReportReasonAdapter
import com.gh.gamecenter.common.constant.CommonConsts
import com.gh.gamecenter.common.json.json
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.toObject
import com.gh.gamecenter.common.utils.toRequestBody
@ -24,15 +25,17 @@ import com.gh.gamecenter.databinding.DialogReportReasonBinding
import com.gh.gamecenter.feature.entity.SettingsEntity
import com.gh.gamecenter.retrofit.RetrofitManager
import com.halo.assistant.HaloApp
import io.reactivex.Single
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import okhttp3.RequestBody
import okhttp3.ResponseBody
import org.json.JSONObject
import retrofit2.HttpException
object BbsReportHelper {
fun showReportDialog(contentId: String) {
fun showReportDialog(report: CommunityReporter) {
val sp = PreferenceManager.getDefaultSharedPreferences(HaloApp.getInstance())
val suggestion: SettingsEntity.Suggestion? =
sp.getString(CommonConsts.SUGGESTION_HINT_TYPE, null)?.toObject()
@ -52,7 +55,7 @@ object BbsReportHelper {
binding.normalReasonContainer.visibility = View.GONE
binding.otherReasonContainer.visibility = View.VISIBLE
} else {
postReport(contentId, json {
report.postReport(json {
"reason" to reason
})
dialog.cancel()
@ -82,7 +85,7 @@ object BbsReportHelper {
) {
ToastUtils.showToast("请填写举报原因")
} else {
postReport(contentId, json {
report.postReport(json {
"reason" to "其它"
"description" to binding.otherReasonEt.text.toString()
})
@ -105,28 +108,45 @@ object BbsReportHelper {
}
}
@SuppressLint("CheckResult")
private fun postReport(contentId: String, reportContent: JSONObject) {
RetrofitManager.getInstance()
.api
.postBbsReport(contentId, reportContent.toRequestBody())
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : BiResponse<ResponseBody>() {
override fun onSuccess(data: ResponseBody) {
ToastUtils.toast("举报成功")
}
interface CommunityReporter {
override fun onFailure(exception: Exception) {
super.onFailure(exception)
if (exception is HttpException) {
ErrorHelper.handleError(
HaloApp.getInstance().application,
exception.response().errorBody()?.string()
)
fun createReportSingle(content: JSONObject): Single<ResponseBody>
@SuppressLint("CheckResult")
fun postReport(content: JSONObject) {
createReportSingle(content)
.compose(singleToMain())
.subscribe(object : BiResponse<ResponseBody>() {
override fun onSuccess(data: ResponseBody) {
ToastUtils.toast("举报成功")
}
}
})
override fun onFailure(exception: Exception) {
super.onFailure(exception)
if (exception is HttpException) {
ErrorHelper.handleError(
HaloApp.getInstance().application,
exception.response().errorBody()?.string()
)
}
}
})
}
}
class PostsReporter(private val contentId: String) : CommunityReporter {
override fun createReportSingle(content: JSONObject): Single<ResponseBody> =
RetrofitManager.getInstance()
.api
.postBbsReport(contentId, content.toRequestBody())
}
class ImageArticleReporter(private val contentId: String) : CommunityReporter {
override fun createReportSingle(content: JSONObject): Single<ResponseBody> =
RetrofitManager.getInstance().api
.postImageArticleReport(contentId, content.toRequestBody())
}
}

View File

@ -119,6 +119,23 @@ object CommentHelper {
)
}
fun showImageArticleCommentOptions(
view: View,
commentEntity: CommentEntity,
imageArticleId: String,
isShowTop: Boolean = false,
listener: OnCommentOptionClickListener?
) {
showCommentOptions(
view,
commentEntity,
imageArticleId = imageArticleId,
showConversation = false,
isShowTopOrAccept = isShowTop,
listener = listener
)
}
private fun showCommentOptions(
view: View,
commentEntity: CommentEntity,
@ -129,6 +146,7 @@ object CommentHelper {
questionId: String? = null,
videoId: String? = null,
gameCollectionId: String? = null,
imageArticleId: String? = null,
isShowTopOrAccept: Boolean = false,
ignoreModerator: Boolean = false,
isVideoAuthor: Boolean = false,
@ -137,8 +155,7 @@ object CommentHelper {
val context = view.context
val dialogOptions = ArrayList<String>()
val isContentAuthor = commentEntity.me?.isContentAuthor == true
if (isShowTopOrAccept && (articleId != null || questionId != null || videoId != null) && isContentAuthor) {
if (isShowTopOrAccept && (articleId != null || questionId != null || videoId != null || imageArticleId != null) && isContentAuthor) {
dialogOptions.add(if (commentEntity.isTop) "取消置顶" else "置顶")
}
if (isShowTopOrAccept && questionId != null && isContentAuthor) {
@ -229,6 +246,7 @@ object CommentHelper {
commentListener
)
}
articleId != null -> {
PostCommentUtils.reportCommunityArticleComment(
commentEntity.id,
@ -236,6 +254,7 @@ object CommentHelper {
commentListener
)
}
questionId != null -> {
PostCommentUtils.reportQuestionComment(
questionId,
@ -244,6 +263,7 @@ object CommentHelper {
commentListener
)
}
gameCollectionId != null -> {
PostCommentUtils.reportGameCollectionComment(
gameCollectionId,
@ -252,6 +272,15 @@ object CommentHelper {
commentListener
)
}
imageArticleId != null -> {
PostCommentUtils.reportImageArticleComment(
commentEntity.id,
reportType,
commentListener
)
}
else -> {
PostCommentUtils.reportVideoComment(
videoId,
@ -287,6 +316,17 @@ object CommentHelper {
null
)
)
} else if (imageArticleId != null) {
context.startActivity(
CommentDetailActivity
.getImageArticleCommentIntent(
context,
imageArticleId,
commentEntity.id,
communityId,
null
)
)
} else {
context.startActivity(
CommentDetailActivity

View File

@ -305,6 +305,7 @@ public class CommentUtils {
String articleCommunityId,
String videoId,
String questionId,
String imageArticleId,
final CommentEntity commentEntity,
final TextView commentLikeCountTv,
final ImageView commentLikeIv,
@ -328,7 +329,7 @@ public class CommentUtils {
commentLikeCountTv.setText(NumberUtils.transSimpleCount(commentEntity.getVote()));
commentLikeCountTv.setVisibility(View.VISIBLE);
PostCommentUtils.likeComment(answerId, articleId, videoId, questionId, commentEntity.getId(),
PostCommentUtils.likeComment(answerId, articleId, videoId, questionId, imageArticleId, commentEntity.getId(),
new PostCommentUtils.PostCommentListener() {
@Override
public void postSuccess(JSONObject response) {
@ -374,6 +375,7 @@ public class CommentUtils {
String articleId,
String articleCommunityId,
String videoId,
String imageArticleId,
final CommentEntity commentEntity,
final TextView commentLikeCountTv,
final ImageView commentLikeIv,
@ -382,7 +384,7 @@ public class CommentUtils {
String entrance = "视频流-评论-点赞";
CheckLoginUtils.checkLogin(context, entrance, () -> {
PostCommentUtils.likeComment(answerId, articleId, videoId, "", commentEntity.getId(),
PostCommentUtils.likeComment(answerId, articleId, videoId, "", imageArticleId, commentEntity.getId(),
new PostCommentUtils.PostCommentListener() {
@Override
public void postSuccess(JSONObject response) {

View File

@ -568,6 +568,16 @@ object DirectUtils {
}
}
"image_article" -> {
val imageArticleUri = Uri.Builder()
.path(RouteConsts.activity.imageArticleDetailActivity)
.appendQueryParameter(KEY_IMAGE_ARTICLE_ID, linkEntity.link)
.appendQueryParameter(KEY_SOURCE_ENTRANCE, sourceEntrance)
.build()
ARouter.getInstance().build(imageArticleUri).navigation()
}
"" -> {
// do nothing
}

View File

@ -33,6 +33,8 @@ object NewFlatLogUtils {
private const val EVENT_INSTALLED_LAUNCH_DIALOG_SHOW = "halo_fun_installed_launch_dialog_show"
private const val EVENT_INSTALLED_LAUNCH_DIALOG_CLICK = "halo_fun_installed_launch_dialog_click"
private const val LOG_STORE_BBS_COMMUNITY = "bbs_community"
private fun log(jsonObject: JSONObject, logStore: String = "event", uploadImmediately: Boolean = false) {
Utils.log("NewFlatLogUtils", jsonObject.toString(4))
LoghubUtils.log(jsonObject, logStore, uploadImmediately, true)
@ -96,7 +98,12 @@ object NewFlatLogUtils {
// 畅玩助手更新弹窗展示事件
@JvmStatic
fun logHaloFunUpdateDialogShow(gameId: String, gameName: String, gameArchitecture: String, targetVaVersion: String) {
fun logHaloFunUpdateDialogShow(
gameId: String,
gameName: String,
gameArchitecture: String,
targetVaVersion: String
) {
val json = json {
KEY_EVENT to "halo_fun_update_dialog_show"
"game_id" to gameId
@ -146,7 +153,12 @@ object NewFlatLogUtils {
// 畅玩助手更新弹窗点击事件
@JvmStatic
fun logHaloFunUpdateDialogClick(dialogType: String, buttonType: String, architecture: String, targetVaVersion: String) {
fun logHaloFunUpdateDialogClick(
dialogType: String,
buttonType: String,
architecture: String,
targetVaVersion: String
) {
val json = json {
KEY_EVENT to "halo_fun_update_dialog_click"
"dialog_type" to dialogType
@ -740,7 +752,7 @@ object NewFlatLogUtils {
"search_key" to searchKey
parseAndPutMeta().invoke(this)
}
log(json)
log(json, LOG_STORE_BBS_COMMUNITY)
}
//社区搜索结果浏览
@ -752,7 +764,7 @@ object NewFlatLogUtils {
"search_key" to searchKey
parseAndPutMeta().invoke(this)
}
log(json)
log(json, LOG_STORE_BBS_COMMUNITY)
}
//社区搜索tab结果切换事件
@ -764,7 +776,7 @@ object NewFlatLogUtils {
"tab_type" to tab
parseAndPutMeta().invoke(this)
}
log(json)
log(json, LOG_STORE_BBS_COMMUNITY)
}
//点击论坛-搜索页返回按钮
@ -776,7 +788,7 @@ object NewFlatLogUtils {
"bbs_id" to bbsId
parseAndPutMeta().invoke(this)
}
log(json)
log(json, LOG_STORE_BBS_COMMUNITY)
}
//论坛搜索结果浏览
@ -789,7 +801,7 @@ object NewFlatLogUtils {
"bbs_id" to bbsId
parseAndPutMeta().invoke(this)
}
log(json)
log(json, LOG_STORE_BBS_COMMUNITY)
}
//新版我的光环触发登录
@ -869,7 +881,7 @@ object NewFlatLogUtils {
"bbs_id" to bbsId
parseAndPutMeta().invoke(this)
}
log(json)
log(json, LOG_STORE_BBS_COMMUNITY)
}
//个人主页-内容
@ -882,7 +894,7 @@ object NewFlatLogUtils {
"tab_name" to tabName
parseAndPutMeta().invoke(this)
}
log(json)
log(json, LOG_STORE_BBS_COMMUNITY)
}
//游戏详情页-视频合集内容
@ -894,7 +906,7 @@ object NewFlatLogUtils {
"ref_user_id" to refUserId
parseAndPutMeta().invoke(this)
}
log(json)
log(json, LOG_STORE_BBS_COMMUNITY)
}
//进入游戏详情

View File

@ -31,9 +31,10 @@ object NewLogUtils {
private const val KEY_CONTENT_TYPE = "content_type"
private const val KEY_CONTENT_ID = "content_id"
private const val KEY_USER_ID = "ref_user_id"
private const val KEY_BUTTON = "button"
private const val LOG_STORE_EVENT = "event"
private const val LOG_STORE_BBS = "bbs_community"
const val LOG_STORE_BBS = "bbs_community"
private fun log(jsonObject: JSONObject, logStore: String, uploadImmediately: Boolean = false) {
Utils.log("NewLogUtils", jsonObject.toString(4))
@ -2104,7 +2105,13 @@ object NewLogUtils {
//点击底部导航栏
@JvmStatic
fun logBottomNavigationClick(navigationName: String, linkType: String, linkText: String, linkId: String = "", sequence: Int) {
fun logBottomNavigationClick(
navigationName: String,
linkType: String,
linkText: String,
linkId: String = "",
sequence: Int
) {
val json = json {
KEY_EVENT to "bottom_navigation_click"
"navigation_name" to navigationName
@ -2638,4 +2645,183 @@ object NewLogUtils {
}
log(json, LOG_STORE_EVENT)
}
/**
* 社区推荐信息流曝光埋点
*/
@JvmStatic
fun getCommunityExposureJsonObject(
contentId: String,
bbsId: String,
bbsType: String,
sequence: Int,
location: String
): JSONObject {
return json {
KEY_EVENT to "community_exposure"
KEY_CONTENT_ID to contentId
KEY_BBS_ID to bbsId
KEY_BBS_TYPE to bbsType
"sequence" to sequence
KEY_LOCATION to location
parseAndPutMeta()(this)
}
}
/**
* 埋点序号:180
* 事件ID:view_note_detail
* 事件名称:进入图文帖详情页
* 触发时机:点击图文帖类型的内容时触发
*/
@JvmStatic
fun logViewNoteDetail(
contentId: String,
bbsId: String,
bbsType: String,
userId: String
) {
val json = json {
KEY_EVENT to "view_note_detail"
KEY_CONTENT_ID to contentId
KEY_BBS_ID to bbsId
KEY_BBS_TYPE to bbsType
KEY_USER_ID to userId
parseAndPutMeta()(this)
}
log(json, LOG_STORE_BBS)
}
/**
* 埋点序号:181
* 事件ID:click_note_detail_more
* 事件名称:点击更多按钮
* 触发时机:点击时触发
*/
@JvmStatic
fun logClickNoteDetailMore() {
val json = json {
KEY_EVENT to "click_note_detail_more"
parseAndPutMeta()(this)
}
log(json, LOG_STORE_BBS)
}
/**
* 埋点序号:182
* 事件ID:click_note_detail_profile_photo
* 事件名称:点击头像
* 触发时机:点击时触发
*/
@JvmStatic
fun logClickNoteDetailProfilePhoto(
contentId: String,
bbsId: String,
bbsType: String,
userId: String
) {
val json = json {
KEY_EVENT to "click_note_detail_profile_photo"
KEY_CONTENT_ID to contentId
KEY_BBS_ID to bbsId
KEY_BBS_TYPE to bbsType
KEY_USER_ID to userId
parseAndPutMeta()(this)
}
log(json, LOG_STORE_BBS)
}
/**
* 埋点序号:183
* 事件ID:click_note_detail_nickname
* 事件名称:点击昵称
* 触发时机:点击时触发
*/
@JvmStatic
fun logClickNoteDetailNickname(
contentId: String,
bbsId: String,
bbsType: String,
userId: String
) {
val json = json {
KEY_EVENT to "click_note_detail_nickname"
KEY_CONTENT_ID to contentId
KEY_BBS_ID to bbsId
KEY_BBS_TYPE to bbsType
KEY_USER_ID to userId
parseAndPutMeta()(this)
}
log(json, LOG_STORE_BBS)
}
/**
* 埋点序号:184
* 事件ID:click_note_detail_follow
* 事件名称:点击关注按钮
* 触发时机:点击时触发
*/
@JvmStatic
fun logClickNoteDetailFollow(
contentId: String,
bbsId: String,
bbsType: String,
userId: String,
follow: Boolean
) {
val json = json {
KEY_EVENT to "click_note_detail_follow"
KEY_CONTENT_ID to contentId
KEY_BBS_ID to bbsId
KEY_BBS_TYPE to bbsType
KEY_USER_ID to userId
KEY_BUTTON to if(follow) "关注" else "取消关注"
parseAndPutMeta()(this)
}
log(json, LOG_STORE_BBS)
}
/**
* 埋点序号:185
* 事件ID:jump_note_detail
* 事件名称:跳出帖子详情页
* 触发时机:跳出当前页后触发
*/
@JvmStatic
fun logJumpNoteDetail(
contentId: String,
bbsId: String,
bbsType: String,
stayTime: Long
) {
val json = json {
KEY_EVENT to "jump_note_detail"
KEY_CONTENT_ID to contentId
KEY_BBS_ID to bbsId
KEY_BBS_TYPE to bbsType
"stay_time" to stayTime
parseAndPutMeta()(this)
}
log(json, LOG_STORE_BBS)
}
/**
* 埋点序号:186
* 事件ID:click_note_detail_forum
* 事件名称:点击论坛
* 触发时机:点击时触发
*/
@JvmStatic
fun logClickNoteDetailForum(
bbsId: String,
bbsType: String,
) {
val json = json {
KEY_EVENT to "click_note_detail_forum"
KEY_BBS_ID to bbsId
KEY_BBS_TYPE to bbsType
parseAndPutMeta()(this)
}
log(json, LOG_STORE_BBS)
}
}

View File

@ -77,6 +77,7 @@ public class PostCommentUtils {
String articleId,
String videoId,
String questionId,
final String imageArticleId,
final String commentId,
final PostCommentListener listener) {
@ -88,6 +89,8 @@ public class PostCommentUtils {
observable = RetrofitManager.getInstance().getApi().postVoteCommunityArticleComment(commentId);
} else if (!TextUtils.isEmpty(questionId)) {
observable = RetrofitManager.getInstance().getApi().postVoteQuestionComment(questionId, commentId);
} else if (!TextUtils.isEmpty(imageArticleId)) {
observable = RetrofitManager.getInstance().getApi().postVoteToImageArticle(commentId);
} else {
observable = RetrofitManager.getInstance().getApi().postVoteToVideo(videoId, commentId);
}
@ -116,6 +119,7 @@ public class PostCommentUtils {
String articleCommunityId,
String videoId,
String questionId,
String imageArticleId,
final String commentId,
final PostCommentListener listener) {
Observable<ResponseBody> observable;
@ -124,6 +128,8 @@ public class PostCommentUtils {
observable = RetrofitManager.getInstance().getApi().postUnVoteQuestionComment(questionId, commentId);
} else if (!TextUtils.isEmpty(articleId)) {
observable = RetrofitManager.getInstance().getApi().postUnVoteArticleComment(commentId);
} else if (!TextUtils.isEmpty(imageArticleId)) {
observable = RetrofitManager.getInstance().getApi().postUnVoteImageArticle(commentId);
} else {
observable = RetrofitManager.getInstance().getApi().postUnVoteVideoComment(videoId, commentId);
}
@ -300,6 +306,25 @@ public class PostCommentUtils {
});
}
public static void reportImageArticleComment(String commentId, String reportData, PostCommentListener listener) {
RequestBody body = RequestBody.create(MediaType.parse("application/json"), reportData);
RetrofitManager.getInstance().getApi()
.reportImageArticleComment(commentId, body)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Response<ResponseBody>() {
@Override
public void onResponse(ResponseBody response) {
listener.postSuccess(null);
}
@Override
public void onFailure(HttpException e) {
listener.postFailed(e);
}
});
}
public interface PostCommentListener {
void postSuccess(JSONObject response);

View File

@ -14,6 +14,7 @@ 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.entity.ImageArticleEntity
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.feature.entity.ImageInfo
import com.gh.gamecenter.login.user.UserManager
@ -311,7 +312,7 @@ class ImageContainerView : LinearLayout {
|| images.isNullOrEmpty())
return ImageContainerData(
status = status,
isPostCard = type == COMMUNITY_ARTICLE,
isPostCard = type == COMMUNITY_ARTICLE || type == ImageArticleEntity.IMAGE_ARTICLE_TYPE,
images = imageInfoList,
video = video,
show

View File

@ -0,0 +1,277 @@
package com.gh.common.view
import android.animation.Animator
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Canvas
import android.graphics.Paint
import android.util.AttributeSet
import android.view.View
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.toColor
class RedBookBannerIndicatorView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0,
) : View(context, attrs, defStyleAttr) {
private var defaultColor = com.gh.gamecenter.common.R.color.text_instance.toColor(context)
private var selectedColor = com.gh.gamecenter.common.R.color.primary_theme.toColor(context)
private var largeRadius = 2F.dip2px().toFloat()
private var smallRadius = 1.2F.dip2px().toFloat()
private var distanceBetweenCenters = 8F.dip2px().toFloat()
private var maxShowDotCount = 5
private var lastPosition = 0
private var currentPosition = 0
private var firstShowPosition = 0
private var lastShowPosition = 0
private var animationProgress = 0F
private var animationState: AnimationState = AnimationState.No
private var valueAnimator: ValueAnimator? = null
private val paint by lazy {
Paint().apply {
style = Paint.Style.FILL
isAntiAlias = true
}
}
init {
initAttrs(context, attrs)
}
private fun initAttrs(context: Context, attrs: AttributeSet?) {
val typedArray = context.obtainStyledAttributes(attrs, R.styleable.RedBookBannerIndicatorView)
defaultColor = typedArray.getColor(R.styleable.RedBookBannerIndicatorView_default_color, defaultColor)
selectedColor = typedArray.getColor(R.styleable.RedBookBannerIndicatorView_selected_color, selectedColor)
largeRadius = typedArray.getDimension(R.styleable.RedBookBannerIndicatorView_large_radius, largeRadius)
smallRadius = typedArray.getDimension(R.styleable.RedBookBannerIndicatorView_small_radius, smallRadius)
distanceBetweenCenters = typedArray.getDimension(
R.styleable.RedBookBannerIndicatorView_distance_between_centers,
distanceBetweenCenters
)
maxShowDotCount =
typedArray.getInt(R.styleable.RedBookBannerIndicatorView_max_show_dot_count, maxShowDotCount)
typedArray.recycle()
}
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
val width = if (lastPosition >= maxShowDotCount) {
(maxShowDotCount - 1) * distanceBetweenCenters + 2 * largeRadius
} else {
lastShowPosition * distanceBetweenCenters + 2 * largeRadius
}
val height = largeRadius * 2
setMeasuredDimension(width.toInt(), height.toInt())
}
fun show(total: Int) {
valueAnimator?.cancel()
lastPosition = total - 1
lastShowPosition = if (total > maxShowDotCount) {
maxShowDotCount - 1
} else {
lastPosition
}
requestLayout()
}
/**
* 请注意selectPosition只能一步一步跳
* 比如当currentPosition ==0 时selectPosition为2仍然只会往前跳一步最终 currentPosition == 1
*/
fun selectPosition(position: Int) {
if (currentPosition == position || position < 0 || position > lastPosition) {
return
}
valueAnimator?.cancel()
if (position > currentPosition) {
currentPosition++
} else if (position < currentPosition) {
currentPosition--
}
when (currentPosition) {
0 -> {
firstShowPosition = 0
lastShowPosition = maxShowDotCount - 1
if (lastShowPosition > lastPosition) {
lastShowPosition = lastPosition
}
}
lastPosition -> {
lastShowPosition = lastPosition
firstShowPosition = lastPosition - maxShowDotCount + 1
if (firstShowPosition < 0) {
firstShowPosition = (0)
}
}
firstShowPosition -> {
animationState = AnimationState.RightReady
}
lastShowPosition -> {
animationState = AnimationState.LeftReady
}
}
invalidate()
}
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
// 保存 Canvas 的当前状态
canvas.save();
// 限制绘制区域为 View 的边界
canvas.clipRect(0, 0, getWidth(), getHeight());
if (lastShowPosition > firstShowPosition) {
val first = if (firstShowPosition > 0) {
firstShowPosition - 1
} else {
firstShowPosition
}
val last = if (lastShowPosition < lastPosition) {
lastShowPosition + 1
} else {
lastPosition
}
for (position in first..last) {
var radius = when (position) {
firstShowPosition -> if (firstShowPosition == 0) {
largeRadius
} else {
smallRadius
}
lastShowPosition -> if (lastShowPosition == lastPosition) {
largeRadius
} else {
smallRadius
}
else -> largeRadius
}
val offset = if (animationState == AnimationState.LeftPlaying) {
-animationProgress * distanceBetweenCenters
} else {
animationProgress * distanceBetweenCenters
}
val centerX = ((position - firstShowPosition) * distanceBetweenCenters + largeRadius) + offset
paint.color = if (position == currentPosition) {
selectedColor
} else {
defaultColor
}
radius = getScaleRadius(position, radius)
canvas.drawCircle(centerX, largeRadius, radius, paint)
}
if (animationState == AnimationState.LeftReady || animationState == AnimationState.RightReady) {
startAnimation()
}
}
// 恢复 Canvas 到之前的状态
canvas.restore();
}
private fun getScaleRadius(position: Int, radius: Float): Float {
var newRadius = radius
if (animationState == AnimationState.LeftPlaying) {
// 第二个可见的小圆点
if (position - 1 == firstShowPosition) {
newRadius = largeRadius - (largeRadius - smallRadius) * animationProgress
}
// 最后一个可见的小圆点
if (position == lastShowPosition) {
newRadius = smallRadius + (largeRadius - smallRadius) * animationProgress
}
// 最后一个不可见的小圆点
if (position == lastShowPosition + 1) {
newRadius = smallRadius
}
}
if (animationState == AnimationState.RightPlaying) {
if (position == firstShowPosition - 1) {
newRadius = smallRadius
}
if (position == firstShowPosition) {
newRadius = smallRadius + (largeRadius - smallRadius) * animationProgress
}
if (position + 1 == lastShowPosition) {
newRadius = largeRadius - (largeRadius - smallRadius) * animationProgress
}
}
return newRadius
}
/**
* 执行向左的平移动画
*/
private fun startAnimation() {
animationState = if (animationState == AnimationState.LeftReady) {
AnimationState.LeftPlaying
} else {
AnimationState.RightPlaying
}
fun resetShowPosition() {
if (animationState == AnimationState.LeftPlaying) {
firstShowPosition++
lastShowPosition++
} else {
firstShowPosition--
lastShowPosition--
}
animationState = AnimationState.No
animationProgress = 0F
invalidate()
}
valueAnimator = ValueAnimator.ofFloat(0F, 1F).apply {
setDuration(ANIMATION_DURATION)
addUpdateListener {
val progress = it.animatedValue as Float
animationProgress = progress
invalidate()
}
addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(animation: Animator) = Unit
override fun onAnimationEnd(animation: Animator) {
resetShowPosition()
}
override fun onAnimationCancel(animation: Animator) {
resetShowPosition()
}
override fun onAnimationRepeat(animation: Animator) = Unit
})
start()
}
}
companion object {
private const val ANIMATION_DURATION = 300L
}
sealed class AnimationState {
object No : AnimationState()
object LeftReady : AnimationState()
object RightReady : AnimationState()
object LeftPlaying : AnimationState()
object RightPlaying : AnimationState()
}
}

View File

@ -0,0 +1,107 @@
package com.gh.common.view
import android.content.Context
import android.util.AttributeSet
import android.view.LayoutInflater
import android.view.View
import android.view.View.OnClickListener
import android.widget.LinearLayout
import com.gh.gamecenter.R
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.doOnAnimationEnd
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.toResString
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.databinding.LayoutVoteBinding
import com.gh.gamecenter.entity.VoteEntity
import com.gh.gamecenter.forum.home.recommend.ImageArticleUseCase.Companion.notifyVoteChanged
import com.gh.gamecenter.livedata.Event
class VoteStateView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : LinearLayout(context, attrs, defStyleAttr) {
private var binding: LayoutVoteBinding = LayoutVoteBinding.inflate(LayoutInflater.from(context), this, true)
private var status = IMAGE_ARTICLE_PENDING
private var isAnimatorPlaying = false
private var outClickListener: OnClickListener? = null
private val inClickListener = OnClickListener {
if (isAnimatorPlaying) {
return@OnClickListener
}
when (status) {
IMAGE_ARTICLE_PENDING -> {
ToastUtils.showToast(R.string.content_pending_status.toResString())
}
IMAGE_ARTICLE_FAIL -> {
ToastUtils.showToast(R.string.fail_status.toResString())
}
IMAGE_ARTICLE_PASS -> {
outClickListener?.onClick(it)
if (!binding.ivVote.isChecked) {
playVoteAnimation()
}
}
}
}
fun setVote(isVote: Boolean, count: Int, status: String) {
this.status = status
binding.ivVote.isChecked = isVote
binding.tvVoteCount.text = getVoteCountText(count, context)
val textColorResId = if (isVote) {
com.gh.gamecenter.common.R.color.text_theme
} else {
com.gh.gamecenter.common.R.color.text_secondary
}
binding.tvVoteCount.setTextColor(textColorResId.toColor(context))
}
override fun setOnClickListener(l: OnClickListener?) {
outClickListener = l
super.setOnClickListener(inClickListener)
}
private fun getVoteCountText(count: Int, context: Context) =
when {
count <= 0 -> R.string.like.toResString()
count in 1 until VOTE_TEN_THOUSAND -> "$count"
else -> context.getString(R.string.ten_thousand, "$count")
}
private fun playVoteAnimation() {
with(binding) {
isAnimatorPlaying = true
tvVoteCount.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(context))
ivVote.isChecked = true
ivVote.visibility = View.INVISIBLE
voteAnimation.visibility = View.VISIBLE
voteAnimation.setAnimation("lottie/community_vote.json")
voteAnimation.playAnimation()
voteAnimation.doOnAnimationEnd {
voteAnimation.visibility = View.GONE
ivVote.visibility = View.VISIBLE
isAnimatorPlaying = false
}
}
}
companion object {
private const val VOTE_TEN_THOUSAND = 10000
private const val IMAGE_ARTICLE_PENDING = "pending"
private const val IMAGE_ARTICLE_FAIL = "fail"
private const val IMAGE_ARTICLE_PASS = "pass"
}
}

View File

@ -76,6 +76,19 @@ public class CommentDetailActivity extends ToolBarActivity {
return getTargetIntent(context, CommentDetailActivity.class, NewCommentConversationFragment.class, args);
}
public static Intent getImageArticleCommentIntent(Context context,
String articleId,
String articleCommentId,
String communityId,
LinkEntity linkEntity) {
Bundle args = new Bundle();
args.putString(CommentActivity.IMAGE_ARTICLE_ID, articleId);
args.putString(EntranceConsts.KEY_COMMENTID, articleCommentId);
args.putString(CommentActivity.COMMUNITY_ID, communityId);
args.putParcelable(EntranceConsts.KEY_LINK, linkEntity);
return getTargetIntent(context, CommentDetailActivity.class, NewCommentConversationFragment.class, args);
}
public static Intent getVideoCommentIntent(Context context,
String commentId,
String videoId,

View File

@ -43,6 +43,7 @@ data class FollowDynamicEntity(
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"
const val FOLLOW_UPDATE_TYPE_IMAGE_ARTICLE = "image_article"
}
@Parcelize

View File

@ -0,0 +1,230 @@
package com.gh.gamecenter.entity
import android.os.Parcelable
import com.gh.gamecenter.common.entity.CommunityEntity
import com.gh.gamecenter.feature.entity.*
import com.gh.gamecenter.feature.entity.TimeEntity
import com.google.gson.annotations.SerializedName
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
@Parcelize
data class ImageArticleEntity(
@SerializedName("_id")
private var _id: String? = null,
@SerializedName("title")
private var _title: String? = null,
@SerializedName("content")
private val _content: String? = null,
@SerializedName("community_id")
private var _communityId: String? = null,
@SerializedName("community_type")
private val _communityType: String? = null,
@SerializedName("sections")
private val _sections: List<SectionEntity>? = null,
@SerializedName("images")
private var _images: List<String>? = null,
@SerializedName("images_info")
private val _imagesInfos: List<ImageInfo>? = null,
@SerializedName("community")
val community: Community? = null,
@SerializedName("user")
private val _user: UserEntity? = null,
@SerializedName("time")
private val _time: TimeEntity? = null,
@SerializedName("status")
private val _status: String? = null,
@SerializedName("source")
private val _source: SourceEntity? = null,
@SerializedName("count")
private val _count: Count? = null,
@SerializedName("me")
private val _me: MeEntity? = null,
@SerializedName("_seq")
private val _shortId: String? = null,
@SerializedName("choiceness_status")
private var _choicenessStatus: String? = null,// 精选状态 apply申请, pass already已精选 cancel not_yet未精选
@SerializedName("recommend_id")
private val _recommendId: String? = null,
/**
* 临时变量,区分是编辑发布还是编辑草稿
*/
var isDraft: Boolean = false
) : Parcelable {
val id: String
get() = _id ?: ""
val title: String
get() = _title ?: ""
val content: String
get() = _content ?: ""
val communityId: String
get() = _communityId ?: ""
val communityType: String
get() = _communityType ?: ""
val sections: List<SectionEntity>
get() = _sections ?: emptyList()
val images: List<String>
get() = _images ?: emptyList()
val imagesInfos: List<ImageInfo>
get() = _imagesInfos ?: emptyList()
val user: UserEntity
get() = _user ?: UserEntity()
val time: TimeEntity
get() = _time ?: TimeEntity()
val status: String
get() = _status ?: "pending"
val source: SourceEntity
get() = _source ?: SourceEntity()
val count: Count
get() = _count ?: Count()
val me: MeEntity
get() = _me ?: MeEntity()
val shortId: String
get() = _shortId ?: ""
var choicenessStatus: String
get() = _choicenessStatus ?: ""
set(value) {
_choicenessStatus = value
}
val recommendId: String
get() = _recommendId ?: ""
fun getSimplifyChoicenessStatus(): String {
return when (choicenessStatus) {
"already" -> "pass"
"not_yet" -> "cancel"
else -> choicenessStatus ?: ""
}
}
companion object {
const val IMAGE_ARTICLE_TYPE = "image_article"
fun getImageRadio(imageInfo: ImageInfo?, minRadio: Float, maxRadio: Float): Float {
return if (imageInfo != null && imageInfo.width > 0 && imageInfo.height > 0) {
val imageRadio = imageInfo.height.toFloat() / imageInfo.width
when {
imageRadio < minRadio -> {
minRadio
}
imageRadio in 0.5..1.33 -> {
imageRadio
}
else -> {
maxRadio
}
}
} else {
minRadio
}
}
}
@Parcelize
data class Community(
@SerializedName("_id")
private val _id: String? = null,
@SerializedName("name")
private val _name: String? = null,
@SerializedName("icon")
private val _icon: String? = null,
@SerializedName("hot")
private val _hot: Int? = null,
@SerializedName("game")
val game: GameEntity? = null,
@SerializedName("type")
private val _type: String? = null,
@SerializedName("me")
private val _me: MeEntity?
) : Parcelable {
val id: String
get() = _id ?: ""
val name: String
get() = _name ?: ""
val icon: String
get() = _icon ?: ""
val hot: Int
get() = _hot ?: 0
val type: String
get() = _type ?: "game_bbs"
val typeChinese: String
get() = when (type) {
"game_bbs" -> "游戏论坛"
"official_bbs" -> "综合论坛"
else -> ""
}
var isFollowed: Boolean
get() = _me?.isFollower ?: false
set(value) {
_me?.isFollower = value
}
fun toCommunityEntity() = CommunityEntity(
id = id,
name = name,
icon = icon,
type = type,
)
}
fun toPersonHistoryEntity() = PersonalHistoryEntity().also {
it.id = id
it.title = title
it.brief = content
it.community = community?.toCommunityEntity() ?: CommunityEntity()
it.sections = sections
it.images = images
it.imagesInfo = imagesInfos
it.count = count
it.user = user
it.time = time.update
it.status = status
it.me = me
}
fun toAnswerEntity() = AnswerEntity().also {
it.id = id
it.title = title
it.brief = content
it.content = content
it.community = community?.toCommunityEntity() ?: CommunityEntity()
it.sections = sections
it.images = images
it.imagesInfo = imagesInfos
it.count = count
it.user = user
it.time = time.update
it.status = status
it.me = me
it.type = IMAGE_ARTICLE_TYPE
}
}

View File

@ -36,12 +36,13 @@ data class PersonalHistoryEntity(
var length: Long = 0,
@SerializedName("count")
private var _count: Count = Count(),
val time: Long = 0,
var time: Long = 0,
@SerializedName("title")
private var _title: String = "",
var description: String = "",
@SerializedName("community")
private var _community: CommunityEntity = CommunityEntity(),
private var _community: CommunityEntity? = null,
var videos: List<CommunityVideoEntity> = ArrayList(),
@SerializedName("user")
private var _user: UserEntity? = null,
@ -63,8 +64,17 @@ data class PersonalHistoryEntity(
@SerializedName("sections")
@Ignore
private var _sections: List<SectionEntity> = ArrayList(),
// 图文新增字段
@SerializedName("content")
private val _content: String? = null,
@SerializedName("source")
private val _source: SourceEntity? = null,
) : Parcelable, CommunityItemData {
val content: String
get() = _content ?: ""
override var id: String
get() = _id
set(value) {
@ -104,7 +114,7 @@ data class PersonalHistoryEntity(
}
override var community: CommunityEntity
get() = _community
get() = _community ?: CommunityEntity()
set(value) {
_community = value
}

View File

@ -0,0 +1,17 @@
package com.gh.gamecenter.entity
import com.google.gson.annotations.SerializedName
data class PublishImageTextRequest(
val title: String,
val content: String,
@SerializedName("community_type")
val communityType: String,
@SerializedName("community_id")
val communityId: String,
@SerializedName("section_id")
val sectionId: List<String>,
var images: List<String> = listOf(),
@SerializedName("draft_id")
val draftId: String = ""
)

View File

@ -2,8 +2,15 @@ package com.gh.gamecenter.entity
import com.google.gson.annotations.SerializedName
class VoteEntity(
var vote: Int? = 0,
data class VoteEntity(
@SerializedName("vote")
private val _vote: Int? = null,
@SerializedName("is_guide_follow")
var isGuideFollow: Boolean = false
)
private var _isGuideFollow: Boolean? = null
) {
val vote: Int
get() = _vote ?: 0
val isGuideFollow: Boolean
get() = _isGuideFollow ?: false
}

View File

@ -0,0 +1,16 @@
package com.gh.gamecenter.eventbus
import com.gh.gamecenter.entity.ImageArticleEntity
sealed class EBImageArticleChanged {
data class VoteChanged(val id: String, val isVoted: Boolean, val count: Int) : EBImageArticleChanged()
data class CommentChanged(val id: String, val count: Int) : EBImageArticleChanged()
data class DataChanged(val data: ImageArticleEntity) : EBImageArticleChanged()
data class DataDeleted(val imageArticleId: String) : EBImageArticleChanged()
data class DataCreated(val imageArticle: ImageArticleEntity, val draftId: String = "") : EBImageArticleChanged()
}

View File

@ -1,11 +1,13 @@
package com.gh.gamecenter.forum.detail
import android.content.Context
import android.net.Uri
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import com.alibaba.android.arouter.launcher.ARouter
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.constant.ItemViewType
import com.gh.gamecenter.common.syncpage.ISyncAdapterHandler
@ -14,6 +16,8 @@ import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.viewholder.FooterViewHolder
import com.gh.gamecenter.common.baselist.ListAdapter
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.databinding.CommunityAnswerItemBinding
import com.gh.gamecenter.common.entity.CommunityEntity
@ -21,6 +25,7 @@ import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.toDrawable
import com.gh.gamecenter.databinding.ItemForumArticleHeadBinding
import com.gh.gamecenter.entity.ImageArticleEntity
import com.gh.gamecenter.forum.home.ForumArticleAskItemViewHolder
import com.gh.gamecenter.qa.article.detail.ArticleDetailActivity
import com.gh.gamecenter.feature.entity.AnswerEntity
@ -47,7 +52,7 @@ class ForumArticleAskListAdapter(
}
override fun areItemsTheSame(oldItem: AnswerEntity?, newItem: AnswerEntity?): Boolean {
return oldItem?.id == newItem?.id
return oldItem?.id == newItem?.id && oldItem?.type == newItem?.type
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
@ -55,9 +60,17 @@ class ForumArticleAskListAdapter(
ItemViewType.ITEM_HEADER -> {
ForumArticleHeadViewHolder(parent.toBinding())
}
ItemViewType.ITEM_FOOTER -> {
FooterViewHolder(mLayoutInflater.inflate(com.gh.gamecenter.common.R.layout.refresh_footerview, parent, false))
FooterViewHolder(
mLayoutInflater.inflate(
com.gh.gamecenter.common.R.layout.refresh_footerview,
parent,
false
)
)
}
else -> {
ForumArticleAskItemViewHolder(
CommunityAnswerItemBinding.bind(
@ -95,7 +108,8 @@ class ForumArticleAskListAdapter(
questions.answerCount = answer.count.answer
answer.questions = questions
}
if (path == "精华" && answer.type != "answer" && answer.type != "video") answer.type = "community_article"
if (path == "精华" && answer.type != "answer" && answer.type != "video") answer.type =
"community_article"
if (path == "问答") answer.type = "question"
if (path == "视频") answer.type = "video"
@ -148,6 +162,7 @@ class ForumArticleAskListAdapter(
)
)
}
"video" -> {
NewLogUtils.logForumDetailFeedContentClick(
"click_forum_detail_content",
@ -164,8 +179,16 @@ class ForumArticleAskListAdapter(
holder.getKey(entrance),
"${answer.title}${answer.id}"
)
mContext.startActivity(ForumVideoDetailActivity.getIntent(mContext, answer.id, bbsId, "论坛详情-信息流"))
mContext.startActivity(
ForumVideoDetailActivity.getIntent(
mContext,
answer.id,
bbsId,
"论坛详情-信息流"
)
)
}
"question" -> {
NewLogUtils.logForumDetailFeedContentClick(
"click_forum_detail_content",
@ -188,6 +211,7 @@ class ForumArticleAskListAdapter(
)
)
}
"answer" -> {
NewLogUtils.logForumDetailFeedContentClick(
"click_forum_detail_content",
@ -215,29 +239,63 @@ class ForumArticleAskListAdapter(
)
)
}
ImageArticleEntity.IMAGE_ARTICLE_TYPE -> {
NewLogUtils.logForumDetailFeedContentClick(
"click_forum_detail_content",
userId,
contentId,
"图文",
sequence,
bbsId,
bbsType,
tabInfo
)
val uri = Uri.Builder()
.path(RouteConsts.activity.imageArticleDetailActivity)
.appendQueryParameter(EntranceConsts.KEY_IMAGE_ARTICLE_ID, answer.id)
.appendQueryParameter(EntranceConsts.KEY_SOURCE_ENTRANCE, "论坛详情-信息流")
.build()
ARouter.getInstance().build(uri).navigation()
}
}
}
}
ItemViewType.ITEM_FOOTER -> {
val footerViewHolder = holder as FooterViewHolder
footerViewHolder.initItemPadding()
footerViewHolder.initFooterViewHolder(mIsLoading, mIsNetworkError, mIsOver)
footerViewHolder.hint.textSize = 12f
footerViewHolder.hint.setTextColor(ContextCompat.getColor(mContext, com.gh.gamecenter.common.R.color.aaaaaa))
footerViewHolder.hint.setTextColor(
ContextCompat.getColor(
mContext,
com.gh.gamecenter.common.R.color.aaaaaa
)
)
}
ItemViewType.ITEM_HEADER -> {
if (holder is ForumArticleHeadViewHolder) {
val articleListHead = if (path == "全部") {
viewModel.selectedSection.name
} else path
holder.binding.root.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_surface.toColor(mContext))
holder.binding.articleListHeadTv.setTextColor(com.gh.gamecenter.common.R.color.text_primary.toColor(mContext))
holder.binding.articleListHeadTv.setTextColor(
com.gh.gamecenter.common.R.color.text_primary.toColor(
mContext
)
)
holder.binding.articleListHeadTv.text = "${articleListHead}列表"
if (path != "精华") {
holder.binding.orderSfv.run {
visibility = View.VISIBLE
setContainerBackground(com.gh.gamecenter.common.R.drawable.button_round_f5f5f5.toDrawable(mContext))
setContainerBackground(
com.gh.gamecenter.common.R.drawable.button_round_f5f5f5.toDrawable(
mContext
)
)
setIndicatorBackground(R.drawable.bg_game_collection_sfv_indicator.toDrawable(mContext))
setTextColor(
com.gh.gamecenter.common.R.color.text_secondary.toColor(mContext),

View File

@ -10,6 +10,7 @@ import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.LazyListFragment
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.entity.CommunityEntity
import com.gh.gamecenter.common.utils.safelyGetInRelease
import com.gh.gamecenter.common.utils.toColor
import com.gh.gamecenter.common.utils.tryCatchInRelease
@ -18,10 +19,13 @@ import com.gh.gamecenter.core.iinterface.IScrollable
import com.gh.gamecenter.core.utils.MD5Utils
import com.gh.gamecenter.databinding.FragmentForumArticleAskListBinding
import com.gh.gamecenter.entity.ForumDetailEntity
import com.gh.gamecenter.entity.ImageArticleEntity
import com.gh.gamecenter.eventbus.EBDeleteDetail
import com.gh.gamecenter.eventbus.EBImageArticleChanged
import com.gh.gamecenter.eventbus.EBUserFollow
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.forum.home.ForumScrollCalculatorHelper
import com.gh.gamecenter.personalhome.home.UserHistoryAdapter.Companion.USER_HISTORY_PAYLOADS_VOTE_CHANGED
import com.gh.gamecenter.video.detail.CustomManager
import com.shuyu.gsyvideoplayer.video.base.GSYVideoView
import org.greenrobot.eventbus.Subscribe
@ -132,6 +136,72 @@ class ForumArticleAskListFragment : LazyListFragment<AnswerEntity, ForumArticleA
mScrollCalculatorHelper?.currentPlayer?.release()
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(changed: EBImageArticleChanged) {
when {
changed is EBImageArticleChanged.VoteChanged -> {
updateImageArticleItem(changed.id, true) {
val newCount = it.count
newCount.vote = changed.count
it.count = newCount
it.me.isCommunityArticleVote = changed.isVoted
}
}
changed is EBImageArticleChanged.CommentChanged -> {
updateImageArticleItem(changed.id, true) {
val newCount = it.count
newCount.comment = changed.count
it.count = newCount
}
}
changed is EBImageArticleChanged.DataDeleted -> {
mViewModel?.deleteImageArticle(changed.imageArticleId)
}
changed is EBImageArticleChanged.DataChanged -> {
val imageArticle = changed.data
updateImageArticleItem(changed.data.id, false) {
it.title = imageArticle.title
it.brief = imageArticle.content
it.images = imageArticle.images
it.imagesInfo = imageArticle.imagesInfos
it.community = imageArticle.community?.toCommunityEntity() ?: CommunityEntity()
it.sections = imageArticle.sections
it.count = imageArticle.count
it.user = imageArticle.user
it.time = imageArticle.time.update
it.status = imageArticle.status
}
}
changed is EBImageArticleChanged.DataCreated -> {
mViewModel?.addNewestImageArticle(changed.imageArticle.toAnswerEntity())
}
}
}
private fun updateImageArticleItem(
id: String,
hasPayload: Boolean,
block: (AnswerEntity) -> Unit
) {
val dataList = mAdapter?.entityList ?: return
val position =
dataList.indexOfFirst { it.type == ImageArticleEntity.IMAGE_ARTICLE_TYPE && it.id == id }
if (position != -1) {
val item = dataList[position]
block(item)
if (hasPayload) {
mAdapter?.notifyItemChanged(position, USER_HISTORY_PAYLOADS_VOTE_CHANGED)
} else {
mAdapter?.notifyItemChanged(position)
}
}
}
override fun onLoadRefresh() {
super.onLoadRefresh()
if (::mBinding.isInitialized) {

View File

@ -4,11 +4,13 @@ import android.app.Application
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.gh.gamecenter.common.baselist.ListViewModel
import com.gh.gamecenter.common.baselist.LoadType
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.core.utils.PageSwitchDataHelper
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.core.utils.UrlFilterUtils
import com.gh.gamecenter.entity.ForumDetailEntity
import com.gh.gamecenter.entity.ImageArticleEntity
import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.retrofit.RetrofitManager
@ -58,9 +60,11 @@ class ForumArticleAskListViewModel(application: Application, val bbsId: String =
}
}
"精华" -> {
RetrofitManager.getInstance().api.getEssenceForumList(bbsId, page)
}
"问答" -> {
RetrofitManager.getInstance().api.getAskForumList(
bbsId,
@ -68,6 +72,7 @@ class ForumArticleAskListViewModel(application: Application, val bbsId: String =
page
)
}
else -> {
RetrofitManager.getInstance().api.getVideoForumList(
bbsId,
@ -94,6 +99,28 @@ class ForumArticleAskListViewModel(application: Application, val bbsId: String =
}
}
fun deleteImageArticle(imageArticleId: String) {
val oldData = mResultLiveData.value ?: return
val newData = oldData.toMutableList()
val hasRemoved =
newData.removeAll { it.type == ImageArticleEntity.IMAGE_ARTICLE_TYPE && it.id == imageArticleId }
if (hasRemoved) {
mResultLiveData.value = newData
}
}
fun addNewestImageArticle(item: AnswerEntity) {
val oldData = mResultLiveData.value ?: return
val newData = oldData.toMutableList()
if (newData.isEmpty()) {
load(LoadType.REFRESH)
} else {
newData.add(1, item)
mResultLiveData.value = newData
}
}
class Factory(private val bbsId: String, val path: String) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ForumArticleAskListViewModel(HaloApp.getInstance().application, bbsId, path) as T

View File

@ -8,6 +8,7 @@ import android.graphics.Color
import android.graphics.Typeface
import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.GradientDrawable
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.view.Gravity
@ -28,6 +29,7 @@ import androidx.core.os.bundleOf
import androidx.core.view.ViewCompat
import androidx.core.view.WindowInsetsCompat
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DefaultItemAnimator
@ -35,6 +37,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.OnScrollListener
import androidx.viewpager.widget.PagerAdapter
import com.alibaba.android.arouter.launcher.ARouter
import com.ethanhua.skeleton.Skeleton
import com.ethanhua.skeleton.ViewSkeletonScreen
import com.facebook.drawee.drawable.ScalingUtils
@ -56,6 +59,7 @@ import com.gh.gamecenter.common.base.fragment.BaseLazyTabFragment
import com.gh.gamecenter.common.callback.BiCallback
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.databinding.ItemIconTabBinding
import com.gh.gamecenter.common.databinding.PopupAllTabsBinding
import com.gh.gamecenter.common.entity.CommunityEntity
@ -79,6 +83,7 @@ import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.UserEntity
import com.gh.gamecenter.forum.home.CommunityHomeFragment
import com.gh.gamecenter.forum.home.recommend.PublishImageArticleActivity
import com.gh.gamecenter.forum.moderator.ApplyModeratorActivity
import com.gh.gamecenter.forum.moderator.ModeratorListActivity
import com.gh.gamecenter.forum.search.ForumOrUserSearchActivity
@ -265,12 +270,14 @@ class ForumDetailFragment : BaseLazyTabFragment(), IScrollable {
if (!mIsFromTabWrapper) {
ViewCompat.setOnApplyWindowInsetsListener(mBinding.forumAppbar) { _, insets ->
(mBinding.toolbar.layoutParams as MarginLayoutParams).topMargin = insets.getInsets(WindowInsetsCompat.Type.systemBars()).top
(mBinding.toolbar.layoutParams as MarginLayoutParams).topMargin =
insets.getInsets(WindowInsetsCompat.Type.systemBars()).top
WindowInsetsCompat.CONSUMED
}
}
if (mIsFromMainWrapper) {
(mBinding.toolbar.layoutParams as MarginLayoutParams).topMargin = DisplayUtils.getStatusBarHeight(requireContext().resources)
(mBinding.toolbar.layoutParams as MarginLayoutParams).topMargin =
DisplayUtils.getStatusBarHeight(requireContext().resources)
}
mBinding.toolbar.setNavigationOnClickListener {
NewLogUtils.logForumDetailEnterOrClick("click_forum_detail_return", mBbsId, mBbsType)
@ -304,7 +311,12 @@ class ForumDetailFragment : BaseLazyTabFragment(), IScrollable {
}
if (absVerticalOffset == total) {
mBinding.toolbar.setTitleTextColor(ContextCompat.getColor(requireContext(), com.gh.gamecenter.common.R.color.black))
mBinding.toolbar.setTitleTextColor(
ContextCompat.getColor(
requireContext(),
com.gh.gamecenter.common.R.color.black
)
)
}
})
mViewPager.doOnPageSelected {
@ -630,7 +642,10 @@ class ForumDetailFragment : BaseLazyTabFragment(), IScrollable {
params.topMargin = DisplayUtils.dip2px(-8f)
mBinding.tabContainer.layoutParams = params
mBinding.tabContainer.background =
ContextCompat.getDrawable(requireContext(), com.gh.gamecenter.common.R.drawable.background_shape_white_radius_5_top_only)
ContextCompat.getDrawable(
requireContext(),
com.gh.gamecenter.common.R.drawable.background_shape_white_radius_5_top_only
)
}
if (background.isEmpty()) {
@ -881,7 +896,7 @@ class ForumDetailFragment : BaseLazyTabFragment(), IScrollable {
forumName = mForumDetail?.name ?: "",
bbsType = mForumDetail?.typeChinese ?: "",
buttonName = mBinding.followTv.text.toString(),
gameForumType = mForumDetail?.game?.categoryChinese?:""
gameForumType = mForumDetail?.game?.categoryChinese ?: ""
)
ifLogin(mEntrance) {
val forumEntity = ForumEntity(
@ -1020,14 +1035,31 @@ class ForumDetailFragment : BaseLazyTabFragment(), IScrollable {
val headView = SimpleDraweeView(requireContext())
val roundingParams = RoundingParams().apply {
roundAsCircle = true
setBorder(ContextCompat.getColor(requireContext(), com.gh.gamecenter.common.R.color.white), 1F.dip2px().toFloat())
setBorder(
ContextCompat.getColor(requireContext(), com.gh.gamecenter.common.R.color.white),
1F.dip2px().toFloat()
)
}
headView.hierarchy = GenericDraweeHierarchyBuilder(resources)
.setFadeDuration(500)
.setRoundingParams(roundingParams)
.setPressedStateOverlay(ColorDrawable(ContextCompat.getColor(requireContext(), com.gh.gamecenter.common.R.color.pressed_bg)))
.setPressedStateOverlay(
ColorDrawable(
ContextCompat.getColor(
requireContext(),
com.gh.gamecenter.common.R.color.pressed_bg
)
)
)
.setPlaceholderImage(com.gh.gamecenter.common.R.drawable.occupy2, ScalingUtils.ScaleType.CENTER)
.setBackground(ColorDrawable(ContextCompat.getColor(requireContext(), com.gh.gamecenter.common.R.color.placeholder_bg)))
.setBackground(
ColorDrawable(
ContextCompat.getColor(
requireContext(),
com.gh.gamecenter.common.R.color.placeholder_bg
)
)
)
.setActualImageScaleType(ScalingUtils.ScaleType.CENTER_CROP)
.build()
headView.layoutParams = params
@ -1089,6 +1121,35 @@ class ForumDetailFragment : BaseLazyTabFragment(), IScrollable {
}
}
contentView.findViewById<View>(R.id.community_edit_recommend_container).setOnClickListener {
val icon = if ("official_bbs" == mForumDetail?.type) {
mForumDetail?.icon ?: ""
} else {
mForumDetail?.game?.getIcon()
}
context?.ifLogin("论坛详情-发布-图文动态", action = {
val uri = Uri.Builder()
.path(RouteConsts.activity.publishImageArticleActivity)
.appendQueryParameter(EntranceConsts.KEY_ENTRANCE, "论坛详情页")
.build()
ARouter.getInstance()
.build(uri)
.withParcelable(
EntranceConsts.KEY_COMMUNITY_DATA,
CommunityEntity(
mBbsId,
name = mForumDetail?.name ?: "",
iconSubscript = mForumDetail?.game?.iconSubscript,
icon = icon,
type = mForumDetail?.type ?: "",
game = mForumDetail?.game?.toCommunityGameEntity()
)
)
.navigation()
dialog.dismiss()
})
}
contentView.findViewById<View>(R.id.community_edit_article_container).setOnClickListener {
context?.ifLogin("论坛详情-发布-发帖子", action = {
checkStoragePermissionBeforeAction {
@ -1214,10 +1275,12 @@ class ForumDetailFragment : BaseLazyTabFragment(), IScrollable {
private fun toggleHighlightedView(targetView: View, highlightIt: Boolean) {
val sectionTv = targetView.findViewById<TextView>(R.id.titleTv)
if (highlightIt) {
targetView.background = com.gh.gamecenter.common.R.drawable.button_round_theme_alpha_10.toDrawable(requireContext())
targetView.background =
com.gh.gamecenter.common.R.drawable.button_round_theme_alpha_10.toDrawable(requireContext())
sectionTv?.setTextColor(com.gh.gamecenter.common.R.color.text_theme.toColor(requireContext()))
} else {
targetView.background = com.gh.gamecenter.feature.R.drawable.button_round_gray_light.toDrawable(requireContext())
targetView.background =
com.gh.gamecenter.feature.R.drawable.button_round_gray_light.toDrawable(requireContext())
sectionTv?.setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(requireContext()))
}
}

View File

@ -17,11 +17,13 @@ import androidx.core.graphics.ColorUtils
import androidx.core.os.bundleOf
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.RecyclerView
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.alibaba.android.arouter.launcher.ARouter
import com.gh.common.browse.BrowseTimer
import com.gh.common.iinterface.ISuperiorChain
import com.gh.common.prioritychain.CommunityHomeGuideHandler
@ -36,6 +38,7 @@ import com.gh.gamecenter.common.base.fragment.LazyFragment
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.EntranceConsts.IS_DETAIL_PAGE
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.retrofit.ApiResponse
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.AvatarBorderView
@ -52,6 +55,8 @@ 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.home.recommend.PublishImageArticleActivity
import com.gh.gamecenter.forum.home.recommend.fragment.ImageArticleHomeFragment
import com.gh.gamecenter.forum.search.ForumOrUserSearchActivity
import com.gh.gamecenter.login.entity.UserInfoEntity
import com.gh.gamecenter.login.user.UserManager
@ -68,6 +73,7 @@ import com.google.android.material.tabs.TabLayout.OnTabSelectedListener
import com.halo.assistant.HaloApp
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import splitties.resources.int
import kotlin.math.abs
import kotlin.math.roundToInt
@ -127,6 +133,7 @@ class CommunityHomeFragment : LazyFragment() {
}
override fun getRealLayoutId(): Int {
return R.layout.fragment_community_home
}
@ -135,7 +142,6 @@ class CommunityHomeFragment : LazyFragment() {
mBinding = FragmentCommunityHomeBinding.bind(inflatedView)
}
override fun onFragmentFirstVisible() {
ArticleDetailWebCacheManager.init(requireContext().applicationContext)
@ -162,7 +168,8 @@ class CommunityHomeFragment : LazyFragment() {
private fun addHomeVideoGuideHandler() {
val decorView = activity?.window?.decorView as? FrameLayout
val communityHomeGuideHandler = CommunityHomeGuideHandler(21, requireContext(), decorView, mBinding?.videoLottie)
val communityHomeGuideHandler =
CommunityHomeGuideHandler(21, requireContext(), decorView, mBinding?.videoLottie)
mPriorityChain.addHandler(communityHomeGuideHandler)
}
@ -306,14 +313,14 @@ class CommunityHomeFragment : LazyFragment() {
?: FollowHomeFragment()
mFragmentList.add(followFragment)
val forumArticleListFragment = childFragmentManager.findFragmentByTag("$tag$TAB_RECOMMEND_INDEX")
?: ForumArticleListFragment().with(
val recommendFragment = childFragmentManager.findFragmentByTag("$tag$TAB_RECOMMEND_INDEX")
?: ImageArticleHomeFragment.newInstance().with(
bundleOf(
EntranceConsts.KEY_ENTRANCE to "社区",
EntranceConsts.KEY_PATH to "推荐"
)
)
mFragmentList.add(forumArticleListFragment)
mFragmentList.add(recommendFragment)
val forumFragment = childFragmentManager.findFragmentByTag("${tag}$TAB_FORUM_INDEX")
?: ForumFragment().with(bundleOf(EntranceConsts.KEY_ENTRANCE to "社区"))
@ -334,9 +341,18 @@ class CommunityHomeFragment : LazyFragment() {
doOnScroll(
onPageSelected = { position ->
communityEditBtn.goneIf(position != TAB_RECOMMEND_INDEX && position != TAB_FOLLOW_INDEX)
if (position == TAB_RECOMMEND_INDEX) {
communityEditBtn.setImageResource(R.drawable.ic_community_float_publish)
} else {
communityEditBtn.setImageResource(R.drawable.ic_community_float_create)
}
when (position) {
TAB_FOLLOW_INDEX -> {
root.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_background.toColor(requireContext()))
root.setBackgroundColor(
com.gh.gamecenter.common.R.color.ui_background.toColor(
requireContext()
)
)
topBg.translationY = 0F
changeNavigationBg()
NewLogUtils.logCommunityHomeEvent("click_for_you_tab")
@ -344,7 +360,11 @@ class CommunityHomeFragment : LazyFragment() {
}
TAB_RECOMMEND_INDEX -> {
root.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_background.toColor(requireContext()))
root.setBackgroundColor(
com.gh.gamecenter.common.R.color.ui_background.toColor(
requireContext()
)
)
topBg.translationY = 0F
changeNavigationBg()
NewLogUtils.logCommunityHomeEvent("click_for_you_tab")
@ -352,7 +372,11 @@ class CommunityHomeFragment : LazyFragment() {
}
TAB_FORUM_INDEX -> {
root.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_surface.toColor(requireContext()))
root.setBackgroundColor(
com.gh.gamecenter.common.R.color.ui_surface.toColor(
requireContext()
)
)
(mFragmentList[2] as ForumFragment).translationY.run {
topBg.translationY = -this.toFloat()
changeNavigationBg(this)
@ -362,7 +386,11 @@ class CommunityHomeFragment : LazyFragment() {
}
TAB_ACTIVITY_INDEX -> {
root.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_background.toColor(requireContext()))
root.setBackgroundColor(
com.gh.gamecenter.common.R.color.ui_background.toColor(
requireContext()
)
)
(mFragmentList[3] as ForumActivityFragment).translationY.run {
topBg.translationY = -this.toFloat()
changeNavigationBg(this)
@ -587,6 +615,20 @@ class CommunityHomeFragment : LazyFragment() {
UserRepository.getInstance().loginUserInfo.removeObserver(observer)
}
contentView.findViewById<View>(R.id.community_edit_recommend_container).setOnClickListener {
context?.ifLogin("论坛首页-发布-图文动态", action = {
showRegulationTestDialogIfNeeded {
NewLogUtils.logArticleEditEnter("推荐信息流", "", "")
// ARouter不支持 Fragment.startActivityForResult的形式启动页面,这里无奈只能使用原生方法
// ARouter.getInstance().build(RouteConsts.activity.publishImageArticleActivity)
// .navigation(this, IMAGE_ARTICLE_REQUEST_CODE)
val intent = PublishImageArticleActivity.getIntent(requireContext(), "社区推荐Tab")
startActivityForResult(intent, IMAGE_ARTICLE_REQUEST_CODE)
}
dialog.dismiss()
})
}
contentView.findViewById<View>(R.id.community_edit_article_container).setOnClickListener {
context?.ifLogin("论坛首页-发布-发帖子", action = {
showRegulationTestDialogIfNeeded {
@ -637,6 +679,7 @@ class CommunityHomeFragment : LazyFragment() {
}
}
private fun resetFollowTab() {
mBinding?.tabLayout?.run {
if (selectedTabPosition == TAB_FOLLOW_INDEX) {
@ -653,6 +696,11 @@ class CommunityHomeFragment : LazyFragment() {
super.onActivityResult(requestCode, resultCode, data)
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
IMAGE_ARTICLE_REQUEST_CODE -> {
// 发布图文成功切换到推荐tab
mBinding?.viewPager?.setCurrentItem(TAB_RECOMMEND_INDEX, false)
}
ARTICLE_REQUEST_CODE -> {
val articleId = data?.getStringExtra("article_id") ?: return
// val communityId = data?.getStringExtra("community_id") ?: return
@ -675,13 +723,13 @@ class CommunityHomeFragment : LazyFragment() {
}
private fun insertDataToRecommendTab(entity: ArticleEntity) {
(mFragmentList[TAB_RECOMMEND_INDEX] as? ForumArticleListFragment)?.insertDataToFirstIndex(entity)
// 废弃 后续会逐渐将 activityForResult迁移到 launcher
}
override fun onBackPressed(): Boolean {
mBinding?.viewPager?.run {
if (currentItem == 1) {
return (mFragmentList[1] as ForumArticleListFragment).onBackPressed()
return (mFragmentList[1] as ImageArticleHomeFragment).onBackPressed()
}
}
return super.onBackPressed()
@ -790,7 +838,11 @@ class CommunityHomeFragment : LazyFragment() {
}
}
tvTitle.setTextColor(
ColorStateList.valueOf(com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()))
ColorStateList.valueOf(
com.gh.gamecenter.common.R.color.text_primary.toColor(
requireContext()
)
)
)
}
}
@ -799,7 +851,9 @@ class CommunityHomeFragment : LazyFragment() {
}
mBinding?.run {
root.setBackgroundColor(
if (viewPager.currentItem == TAB_FORUM_INDEX) com.gh.gamecenter.common.R.color.ui_surface.toColor(requireContext()) else com.gh.gamecenter.common.R.color.ui_background.toColor(
if (viewPager.currentItem == TAB_FORUM_INDEX) com.gh.gamecenter.common.R.color.ui_surface.toColor(
requireContext()
) else com.gh.gamecenter.common.R.color.ui_background.toColor(
requireContext()
)
)
@ -820,10 +874,12 @@ class CommunityHomeFragment : LazyFragment() {
}
searchIconIv.setImageResource(R.drawable.ic_column_search)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
searchIconIv.imageTintList = ColorStateList.valueOf(com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()))
searchIconIv.imageTintList =
ColorStateList.valueOf(com.gh.gamecenter.common.R.color.text_primary.toColor(requireContext()))
}
val csl = AppCompatResources.getColorStateList(requireContext(), com.gh.gamecenter.common.R.color.text_primary)
val csl =
AppCompatResources.getColorStateList(requireContext(), com.gh.gamecenter.common.R.color.text_primary)
val filter = SimpleColorFilter(csl.defaultColor)
val keyPath = KeyPath("**")
val callback = LottieValueCallback<ColorFilter>(filter)
@ -856,6 +912,7 @@ class CommunityHomeFragment : LazyFragment() {
const val ARTICLE_REQUEST_CODE = 200
const val QUESTION_REQUEST_CODE = 201
const val VIDEO_REQUEST_CODE = 202
const val IMAGE_ARTICLE_REQUEST_CODE = 203
const val LAST_SELECTED_POSITION = "last_selected_position"
const val EB_SHOW_QUESTION_BUTTON = "EB_SHOW_QUESTION_BUTTON"
const val EB_HIDE_QUESTION_BUTTON = "EB_HIDE_QUESTION_BUTTON"

View File

@ -8,6 +8,7 @@ import androidx.lifecycle.MutableLiveData
import com.gh.gamecenter.common.entity.CommunityEntity
import com.gh.gamecenter.common.retrofit.Response
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.entity.ImageArticleEntity
import com.gh.gamecenter.feature.entity.ArticleEntity
import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.feature.entity.TimeEntity

View File

@ -4,8 +4,10 @@ package com.gh.gamecenter.forum.home
import android.app.Activity
import android.graphics.Color
import android.graphics.drawable.GradientDrawable
import android.net.Uri
import android.text.SpannableStringBuilder
import android.view.View
import com.alibaba.android.arouter.launcher.ARouter
import com.facebook.drawee.view.SimpleDraweeView
import com.gh.common.util.*
import com.gh.common.util.DialogUtils
@ -17,6 +19,8 @@ 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.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.entity.AdditionalParamsEntity
import com.gh.gamecenter.common.entity.CommunityEntity
import com.gh.gamecenter.common.utils.*
@ -24,6 +28,7 @@ import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.core.utils.SpanBuilder
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.databinding.CommunityAnswerItemBinding
import com.gh.gamecenter.entity.ImageArticleEntity
import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.eventbus.EBUserFollow
import com.gh.gamecenter.forum.detail.ForumDetailActivity
@ -106,9 +111,14 @@ class ForumArticleAskItemViewHolder(
else -> R.drawable.icon_forum_fail
}
)
title.goneIf(entity.type == "answer")
title.text = if (entity.type.contains("video")) entity.title else entity.questions.title
content.text = if (entity.type.contains("video")) entity.des else entity.brief
val titleText = if (entity.type.contains("video")) entity.title else entity.questions.title
title.goneIf(entity.type == "answer" || titleText.isNullOrEmpty()) {
title.text = titleText
}
val contentText = if (entity.type.contains("video")) entity.des else entity.brief
content.goneIf(contentText.isEmpty()) {
content.text = contentText
}
popularAnswerContainer.background = GradientDrawable().apply {
setColor(Color.parseColor("#F5F6F7"))
cornerRadius = 4F.dip2px().toFloat()
@ -138,11 +148,8 @@ class ForumArticleAskItemViewHolder(
binding.title.text = SpanBuilder(title).image(0, 1, this).build()
}
} else {
binding.content.visibility = View.VISIBLE
if (entity.type == "video") {
binding.content.goneIf(entity.des.isEmpty())
} else {
binding.content.visibility = View.VISIBLE
}
//若文章内有图片和视频,标题后增加‘有视频’标签 issues-1052
if (entity.getPassVideos().isNotEmpty()) {
@ -202,6 +209,7 @@ class ForumArticleAskItemViewHolder(
"community_article" -> "帖子"
"video" -> "视频帖"
"question" -> "提问帖"
ImageArticleEntity.IMAGE_ARTICLE_TYPE -> "图文"
else -> "提问帖评论"
}
val userId = user.id ?: ""
@ -380,6 +388,7 @@ class ForumArticleAskItemViewHolder(
voteCountContainer.visibility = View.GONE
}
forumNameContainer?.visibleIf(entity.community.id.isNotBlank())
if (entity.community.type == "official_bbs") {
forumIcon?.displayGameIcon(entity.community.icon, null)
} else {
@ -449,15 +458,17 @@ class ForumArticleAskItemViewHolder(
"community_article" -> "帖子"
"video" -> "视频帖"
"question" -> "提问帖"
ImageArticleEntity.IMAGE_ARTICLE_TYPE -> "图文"
else -> "提问帖评论"
}
val sequence = position + 1
val bbsId = entity.community.id
val bbsType = if (entity.community.type == "official_bbs") "综合论坛" else "游戏论坛"
val bbsType = entity.community.typeChinese
val tabInfo = "${path}tab"
val commentType = when (entity.type) {
"community_article" -> "帖子评论"
"video" -> "视频帖评论"
ImageArticleEntity.IMAGE_ARTICLE_TYPE ->"图文评论"
else -> "提问帖评论"
}
NewLogUtils.logForumDetailFeedContentClick(
@ -528,6 +539,14 @@ class ForumArticleAskItemViewHolder(
)
)
}
ImageArticleEntity.IMAGE_ARTICLE_TYPE -> {
val imageArticleUri = Uri.Builder()
.path(RouteConsts.activity.imageArticleDetailActivity)
.appendQueryParameter(EntranceConsts.KEY_IMAGE_ARTICLE_ID, entity.id)
.build()
ARouter.getInstance().build(imageArticleUri).navigation()
}
}
}
@ -544,11 +563,12 @@ class ForumArticleAskItemViewHolder(
"community_article" -> "帖子"
"video" -> "视频帖"
"question" -> "提问帖"
ImageArticleEntity.IMAGE_ARTICLE_TYPE -> "图文"
else -> "提问帖评论"
}
val sequence = position + 1
val bbsId = entity.community.id
val bbsType = if (entity.community.type == "official_bbs") "综合论坛" else "游戏论坛"
val bbsType = entity.community.typeChinese
val tabInfo = "${path}tab"
NewLogUtils.logForumDetailFeedContentClick(
"click_forum_detail_like",

View File

@ -16,6 +16,7 @@ import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.viewholder.FooterViewHolder
import com.gh.gamecenter.core.utils.MtaHelper
import com.gh.gamecenter.databinding.CommunityAnswerItemBinding
import com.gh.gamecenter.entity.ImageArticleEntity
import com.gh.gamecenter.qa.article.detail.ArticleDetailActivity
import com.gh.gamecenter.feature.entity.ArticleEntity
import com.gh.gamecenter.qa.questions.newdetail.NewQuestionDetailActivity
@ -71,7 +72,10 @@ class ForumArticleListAdapter(
topMargin = if (position == 0) 8F.dip2px() else 0
}
if (position == 0) {
root.background = com.gh.gamecenter.common.R.drawable.background_shape_white_radius_12_top_only.toDrawable(mContext)
root.background =
com.gh.gamecenter.common.R.drawable.background_shape_white_radius_12_top_only.toDrawable(
mContext
)
} else {
root.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_surface.toColor(mContext))
}
@ -98,6 +102,7 @@ class ForumArticleListAdapter(
"community_article" -> "帖子"
"video" -> "视频帖"
"question" -> "提问帖"
ImageArticleEntity.IMAGE_ARTICLE_TYPE -> "图文"
else -> "提问帖评论"
}
val bbsType = if (community.type == "official_bbs") "综合论坛" else "游戏论坛"
@ -136,6 +141,7 @@ class ForumArticleListAdapter(
)
)
}
"video" -> {
MtaHelper.onEvent(
"论坛首页",
@ -157,6 +163,7 @@ class ForumArticleListAdapter(
)
)
}
"question" -> {
MtaHelper.onEvent(
"论坛首页",
@ -180,6 +187,7 @@ class ForumArticleListAdapter(
)
)
}
"answer" -> {
MtaHelper.onEvent(
"论坛首页",
@ -213,7 +221,12 @@ class ForumArticleListAdapter(
footerViewHolder.initItemPadding()
footerViewHolder.initFooterViewHolder(viewModel, mIsLoading, mIsNetworkError, mIsOver)
footerViewHolder.hint.textSize = 12f
footerViewHolder.hint.setTextColor(ContextCompat.getColor(mContext, com.gh.gamecenter.common.R.color.aaaaaa))
footerViewHolder.hint.setTextColor(
ContextCompat.getColor(
mContext,
com.gh.gamecenter.common.R.color.aaaaaa
)
)
}
}
}

View File

@ -2,14 +2,19 @@ package com.gh.gamecenter.forum.home.follow
import android.content.Context
import android.content.Intent
import android.net.Uri
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.alibaba.android.arouter.launcher.ARouter
import com.gh.common.util.SyncDataBetweenPageHelper
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.entity.CommunityEntity
import com.gh.gamecenter.entity.ImageArticleEntity
import com.gh.gamecenter.entity.PersonalHistoryEntity
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.feature.entity.ArticleEntity
@ -235,6 +240,15 @@ class FollowActivityResultLauncher(
questionsDetailLauncher.launch(LauncherDestination(position, historyEntity = entity))
}
entity.type == ImageArticleEntity.IMAGE_ARTICLE_TYPE -> {
val imageArticleUri = Uri.Builder()
.path(RouteConsts.activity.imageArticleDetailActivity)
.appendQueryParameter(EntranceConsts.KEY_IMAGE_ARTICLE_ID, entity.id)
.appendQueryParameter(EntranceConsts.KEY_SOURCE_ENTRANCE, "关注-个人动态")
.build()
ARouter.getInstance().build(imageArticleUri).navigation()
}
else -> {
commentEntityLauncher.launch(LauncherDestination(position, historyEntity = entity))
}
@ -258,6 +272,14 @@ class FollowActivityResultLauncher(
"answer" -> {
questionsDetailLauncher.launch(LauncherDestination(position, answerEntity = answerEntity))
}
ImageArticleEntity.IMAGE_ARTICLE_TYPE -> {
val imageArticleUri = Uri.Builder()
.path(RouteConsts.activity.imageArticleDetailActivity)
.appendQueryParameter(EntranceConsts.KEY_SOURCE_ENTRANCE, "关注-论坛动态")
.build()
ARouter.getInstance().build(imageArticleUri).navigation()
}
}
}

View File

@ -1,8 +1,9 @@
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
import com.gh.gamecenter.feature.entity.Count
import com.gh.gamecenter.feature.entity.MeEntity
abstract class FollowDynamicItem {
@ -19,6 +20,14 @@ abstract class FollowDynamicItem {
protected open fun doAreContentsTheSame(other: FollowDynamicItem) = this == other
open val id = ""
open val type = ""
open var count: Count = Count()
open val me = MeEntity()
companion object {
const val FOLLOW_DYNAMIC_ITEM_TYPE_FOOTER = -1
@ -39,6 +48,21 @@ data class FollowDynamicPersonalItem(
&& data.id == other.data.id
}
override val id: String
get() = data.id
override val type: String
get() = data.type
override var count: Count
get() = data.count
set(value) {
data.count = value
}
override val me: MeEntity
get() = data.me
}
data class FollowDynamicBbsItem(
@ -53,6 +77,20 @@ data class FollowDynamicBbsItem(
&& data.id == other.data.id
}
override val id: String
get() = data.id
override val type: String
get() = data.type
override var count: Count
get() = data.count
set(value) {
data.count = value
}
override val me: MeEntity
get() = data.me
}
object FollowDynamicFooterItem : FollowDynamicItem() {

View File

@ -28,8 +28,6 @@ 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(

View File

@ -3,11 +3,8 @@ 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
@ -16,23 +13,29 @@ 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.entity.CommunityEntity
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.FragmentFollowDynamicListBinding
import com.gh.gamecenter.entity.ImageArticleEntity
import com.gh.gamecenter.eventbus.EBImageArticleChanged
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.FollowDynamicItem
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.personalhome.home.UserHistoryAdapter.Companion.USER_HISTORY_PAYLOADS_VOTE_CHANGED
import com.gh.gamecenter.qa.BbsType
import com.gh.gamecenter.qa.entity.ArticleDetailEntity
import com.gh.gamecenter.qa.entity.QuestionsDetailEntity
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class FollowDynamicListFragment : BaseFragment<Unit>() {
@ -267,10 +270,99 @@ class FollowDynamicListFragment : BaseFragment<Unit>() {
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(changed: EBImageArticleChanged) {
when (changed) {
is EBImageArticleChanged.VoteChanged -> {
updateImageArticleItem(changed.id, true) {
it.id
val newCount = it.count
newCount.vote = changed.count
it.count = newCount
it.me.isCommunityArticleVote = changed.isVoted
it.me.isVoted = changed.isVoted
it.count = newCount
}
}
is EBImageArticleChanged.CommentChanged -> {
updateImageArticleItem(changed.id, true) {
val newCount = it.count
newCount.comment = changed.count
it.count = newCount
}
}
is EBImageArticleChanged.DataDeleted -> {
viewModel.deleteImageArticle(changed.imageArticleId)
}
is EBImageArticleChanged.DataChanged -> {
val imageArticle = changed.data
updateImageArticleItem(changed.data.id, false) {
when (it) {
is FollowDynamicPersonalItem -> {
it.data.title = imageArticle.title
it.data.brief = imageArticle.content
it.data.images = imageArticle.images
it.data.imagesInfo = imageArticle.imagesInfos
it.data.community = imageArticle.community?.toCommunityEntity() ?: CommunityEntity()
it.data.sections = imageArticle.sections
it.count = imageArticle.count
it.data.user = imageArticle.user
it.data.time = imageArticle.time.update
it.data.status = imageArticle.status
}
is FollowDynamicBbsItem -> {
it.data.title = imageArticle.title
it.data.brief = imageArticle.content
it.data.images = imageArticle.images
it.data.imagesInfo = imageArticle.imagesInfos
it.data.community = imageArticle.community?.toCommunityEntity() ?: CommunityEntity()
it.data.sections = imageArticle.sections
it.count = imageArticle.count
it.data.user = imageArticle.user
it.data.time = imageArticle.time.update
it.data.status = imageArticle.status
}
}
}
}
else -> Unit
}
}
private fun updateImageArticleItem(
id: String,
hasPayload: Boolean,
block: (FollowDynamicItem) -> Unit
) {
val dataList = adapter.currentList
val position =
dataList.indexOfFirst {
it.type == ImageArticleEntity.IMAGE_ARTICLE_TYPE && it.id == id
}
if (position != -1) {
val item = dataList[position]
block(item)
if (hasPayload) {
adapter.notifyItemChanged(position, USER_HISTORY_PAYLOADS_VOTE_CHANGED)
} else {
adapter.notifyItemChanged(position)
}
}
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
context?.let {
binding.vHeaderBackground.background = com.gh.gamecenter.common.R.drawable.background_shape_white_radius_8_top_only.toDrawable(it)
binding.vHeaderBackground.background =
com.gh.gamecenter.common.R.drawable.background_shape_white_radius_8_top_only.toDrawable(it)
}
binding.rvDynamic.tryToClearRecycler()
binding.rvDynamic.recycledViewPool.clear()

View File

@ -35,6 +35,7 @@ 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.EBImageArticleChanged
import com.gh.gamecenter.eventbus.EBUserFollow
import com.gh.gamecenter.feature.provider.IMessageDetailProvider
import com.gh.gamecenter.forum.home.CommunityHomeFragment
@ -69,6 +70,7 @@ class FollowHomeFragment : LazyFragment(), IScrollable {
ownerProducer = { requireParentFragment() }
)
private lateinit var userViewModel: UserViewModel
private var userId: String? = null
@ -454,6 +456,30 @@ class FollowHomeFragment : LazyFragment(), IScrollable {
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(changed: EBImageArticleChanged) {
when (changed) {
is EBImageArticleChanged.VoteChanged -> {
viewModel.voteChanged(changed)
}
is EBImageArticleChanged.CommentChanged ->{
viewModel.commentChanged(changed)
}
is EBImageArticleChanged.DataChanged -> {
viewModel.itemChanged(changed)
}
is EBImageArticleChanged.DataDeleted -> {
viewModel.itemDeleted(changed)
}
else -> Unit
}
}
override fun onDestroy() {
super.onDestroy()
mScrollCalculatorHelper?.currentPlayer?.release()

View File

@ -7,6 +7,7 @@ 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_IMAGE_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

View File

@ -2,12 +2,17 @@ package com.gh.gamecenter.forum.home.follow.viewholder
import android.content.Context
import android.graphics.Color
import android.net.Uri
import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.alibaba.android.arouter.launcher.ARouter
import com.gh.gamecenter.R
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.CommunityAnswerItemBinding
import com.gh.gamecenter.entity.ImageArticleEntity
import com.gh.gamecenter.feature.entity.ArticleEntity
import com.gh.gamecenter.forum.home.ForumArticleAskItemViewHolder
import com.gh.gamecenter.forum.search.CommunitySearchEventListener
@ -29,13 +34,13 @@ class FollowPostCardViewHolder(
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 = com.gh.gamecenter.common.R.drawable.background_shape_white_radius_12_top_only.toDrawable(context)
root.background =
com.gh.gamecenter.common.R.drawable.background_shape_white_radius_12_top_only.toDrawable(context)
} else {
root.setBackgroundColor(com.gh.gamecenter.common.R.color.ui_surface.toColor(context))
}
@ -118,6 +123,15 @@ class FollowPostCardViewHolder(
)
)
}
ImageArticleEntity.IMAGE_ARTICLE_TYPE -> {
val imageArticleUri = Uri.Builder()
.path(RouteConsts.activity.imageArticleDetailActivity)
.appendQueryParameter(EntranceConsts.KEY_IMAGE_ARTICLE_ID, articleEntity.id)
.appendQueryParameter(EntranceConsts.KEY_SOURCE_ENTRANCE, "社区-关注")
.build()
ARouter.getInstance().build(imageArticleUri).navigation()
}
}
}
}

View File

@ -1,6 +1,5 @@
package com.gh.gamecenter.forum.home.follow.viewmodel
import android.view.ViewGroup
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
@ -8,6 +7,7 @@ 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.ImageArticleEntity
import com.gh.gamecenter.entity.PersonalHistoryEntity
import com.gh.gamecenter.feature.entity.AnswerEntity
import com.gh.gamecenter.forum.home.follow.FollowDynamicItem
@ -119,5 +119,15 @@ class FollowDynamicListViewModel : ViewModel() {
compositeDisposable.clear()
}
fun deleteImageArticle(imageArticleId: String) {
val oldData = dataList.value ?: return
val newData = oldData.toMutableList()
val hasRemoved =
newData.removeAll { it.type == ImageArticleEntity.IMAGE_ARTICLE_TYPE && it.id == imageArticleId }
if (hasRemoved) {
_dataList.value = newData
}
}
}

View File

@ -8,6 +8,7 @@ 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.CommunityEntity
import com.gh.gamecenter.common.entity.LinkEntity
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.retrofit.JSONObjectResponse
@ -16,6 +17,8 @@ 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.entity.ImageArticleEntity
import com.gh.gamecenter.eventbus.EBImageArticleChanged
import com.gh.gamecenter.eventbus.EBUserFollow
import com.gh.gamecenter.feature.entity.*
import com.gh.gamecenter.feature.exposure.ExposureEvent
@ -312,5 +315,71 @@ class FollowHomeViewModel(application: Application) : AndroidViewModel(applicati
compositeDisposable.clear()
}
fun voteChanged(changed: EBImageArticleChanged.VoteChanged) {
notifyImageArticleItemChanged(changed.id) {
FollowPostCardItem(
data = it.copy(
_me = it.me.copy(isCommunityArticleVote = changed.isVoted),
_count = it.count.copy(vote = changed.count)
),
)
}
}
fun commentChanged(changed: EBImageArticleChanged.CommentChanged) {
notifyImageArticleItemChanged(changed.id) {
FollowPostCardItem(
data = it.copy(
_count = it.count.copy(comment = changed.count)
)
)
}
}
fun itemChanged(changed: EBImageArticleChanged.DataChanged) {
val imageArticle = changed.data
notifyImageArticleItemChanged(imageArticle.id) { _ ->
val newArticle = ArticleEntity(
_id = imageArticle.id,
_title = imageArticle.title,
content = imageArticle.content,
_count = imageArticle.count,
time = imageArticle.time,
_images = imageArticle.images,
imagesInfo = imageArticle.imagesInfos,
_user = imageArticle.user,
_community = imageArticle.community?.toCommunityEntity() ?: CommunityEntity()
)
FollowPostCardItem(newArticle)
}
}
private fun notifyImageArticleItemChanged(
imageArticleId: String,
block: (ArticleEntity) -> FollowPostCardItem
) {
val oldData = dataList.value ?: return
val newData = oldData.toMutableList()
val position = newData.indexOfFirst {
it is FollowPostCardItem && it.data.type == ImageArticleEntity.IMAGE_ARTICLE_TYPE && it.data.id == imageArticleId
}
if (position != -1) {
val oldImageArticle = (newData[position] as FollowPostCardItem).data
val newItem = block(oldImageArticle)
newData[position] = newItem
_dataList.value = newData
}
}
fun itemDeleted(changed: EBImageArticleChanged.DataDeleted) {
val oldData = dataList.value ?: return
val newData = oldData.toMutableList()
val position = newData.indexOfFirst {
it is FollowPostCardItem && it.data.type == ImageArticleEntity.IMAGE_ARTICLE_TYPE && it.data.id == changed.imageArticleId
}
if (position != -1) {
newData.removeAt(position)
_dataList.value = newData
}
}
}

View File

@ -0,0 +1,59 @@
package com.gh.gamecenter.forum.home.recommend
import android.graphics.Color
import android.os.Bundle
import com.alibaba.android.arouter.facade.annotation.Autowired
import com.alibaba.android.arouter.facade.annotation.Route
import com.alibaba.android.arouter.launcher.ARouter
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.forum.home.recommend.fragment.ImageArticleDetailFragment
@Route(path = RouteConsts.activity.imageArticleDetailActivity)
class ImageArticleDetailActivity : BaseActivity() {
@JvmField
@Autowired(name = EntranceConsts.KEY_IMAGE_ARTICLE_ID, desc = "图文 id", required = true)
var imageArticleId = ""
@JvmField
@Autowired(name = EntranceConsts.KEY_TOP_COMMENT_ID, desc = "置顶评论 id", required = false)
var topCommentId: String? = null
@JvmField
@Autowired(name = EntranceConsts.KEY_SCROLL_TO_COMMENT_AREA, desc = "是否需要自动滚动到评论区域", required = false)
var scrollToCommentArea: Boolean = false
@JvmField
@Autowired(name = EntranceConsts.KEY_SOURCE_ENTRANCE, desc = "来源入口", required = false)
var sourceEntrance: String = ""
@JvmField
@Autowired(name = EntranceConsts.KEY_RECOMMEND_ID, desc = "推荐 id", required = false)
var recommendId: String = ""
override fun getLayoutId(): Int {
return com.gh.gamecenter.common.R.layout.activity_image_article_detail
}
override fun onCreate(savedInstanceState: Bundle?) {
ARouter.getInstance().inject(this)
super.onCreate(savedInstanceState)
DisplayUtils.setLightStatusBar(this, true)
setStatusBarColor(Color.TRANSPARENT)
val tag = ImageArticleDetailFragment::class.java.toString()
val fragment =
supportFragmentManager.findFragmentByTag(tag) ?: ImageArticleDetailFragment().apply {
arguments = intent.extras
}
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.layout_activity_content, fragment, tag)
transaction.commitAllowingStateLoss()
}
}

View File

@ -0,0 +1,154 @@
package com.gh.gamecenter.forum.home.recommend
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.NO_POSITION
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.PageLoader
import com.gh.gamecenter.common.databinding.RefreshFooterviewBinding
import com.gh.gamecenter.forum.home.follow.viewholder.FollowFooterViewHolder
abstract class ImageArticleFooterWrapperAdapter<T, VH : ViewHolder>(diffCallback: ItemCallback<T>) :
ListAdapter<T, ViewHolder>(diffCallback) {
private var pageState: PageLoader.PageState? = null
private var _recyclerView: RecyclerView? = null
val dataCount: Int
get() = super.getItemCount()
fun setPageState(pageState: PageLoader.PageState) {
this.pageState = pageState
_recyclerView?.post {
notifyItemChanged(itemCount - 1)
}
}
override fun getItemCount(): Int {
return if (dataCount > 0) dataCount + 1 else dataCount
}
override fun getItemViewType(position: Int): Int {
return if (position < dataCount) ITEM_TYPE_DATA else ITEM_TYPE_FOOTER
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return if (viewType == ITEM_TYPE_DATA) {
onCreateDataViewHolder(parent)
} else {
onCreateFooterViewHolder(parent)
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
if (holder.itemViewType == ITEM_TYPE_FOOTER) {
onBindFooterViewHolder(holder)
} else {
onBindDataViewHolder(holder as VH, position)
}
}
override fun onBindViewHolder(holder: ViewHolder, position: Int, payloads: MutableList<Any>) {
if (payloads.isEmpty()) {
onBindViewHolder(holder, position)
} else {
if (holder.itemViewType != ITEM_TYPE_FOOTER) {
onBindDataViewHolder(holder as VH, position, payloads)
}
}
}
open fun onBindFooterViewHolder(holder: ViewHolder) {
if (holder is FollowFooterViewHolder) {
holder.initFooterViewHolder(
pageState == PageLoader.PageState.PageLoadMoreLoading,
pageState == PageLoader.PageState.PageLoadMoreFailure,
pageState == PageLoader.PageState.PageLoadCompleted,
R.string.load_over_with_click_hint
) {
if (pageState == PageLoader.PageState.PageLoadCompleted) {
_recyclerView?.scrollToPosition(0)
} else if (pageState == PageLoader.PageState.PageLoadMoreFailure) {
pageState = PageLoader.PageState.PageLoadMoreLoading
loadMore()
notifyItemChanged(itemCount - 1)
}
}
}
}
abstract fun onCreateDataViewHolder(parent: ViewGroup): VH
open fun onCreateFooterViewHolder(parent: ViewGroup): ViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = RefreshFooterviewBinding.inflate(inflater, parent, false)
val viewHolder = FollowFooterViewHolder(binding)
val layoutParams = binding.root.layoutParams
if (layoutParams is StaggeredGridLayoutManager.LayoutParams) {
layoutParams.isFullSpan = true
binding.root.layoutParams = layoutParams
}
return viewHolder
}
abstract fun onBindDataViewHolder(holder: VH, position: Int)
open fun onBindDataViewHolder(holder: VH, position: Int, payloads: MutableList<Any>) {
onBindDataViewHolder(holder, position)
}
abstract fun loadMore()
private val onLoadMoreListener = object : RecyclerView.OnScrollListener() {
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
if (dy != 0) {
refreshIfNeed(recyclerView)
}
}
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
refreshIfNeed(recyclerView)
}
private fun refreshIfNeed(recyclerView: RecyclerView) {
val layoutManger = recyclerView.layoutManager
if (layoutManger is StaggeredGridLayoutManager) {
val lastVisibleItemPositions = IntArray(2)
layoutManger.findLastVisibleItemPositions(lastVisibleItemPositions)
val lastVisibleItemPosition = lastVisibleItemPositions.maxOrNull() ?: NO_POSITION
if (lastVisibleItemPosition >= itemCount - 3 // 提前两个开始加载下一页
&& pageState is PageLoader.PageState.PageLoadMoreReady
) {
pageState = PageLoader.PageState.PageLoadMoreLoading
loadMore()
}
}
}
}
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
_recyclerView = recyclerView
recyclerView.addOnScrollListener(onLoadMoreListener)
}
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
recyclerView.removeOnScrollListener(onLoadMoreListener)
_recyclerView = null
}
companion object {
private const val ITEM_TYPE_DATA = 0
const val ITEM_TYPE_FOOTER = 101
}
}

View File

@ -0,0 +1,241 @@
package com.gh.gamecenter.forum.home.recommend
import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.utils.UploadImageUtils
import com.gh.gamecenter.core.utils.SPUtils
import com.gh.gamecenter.entity.ImageArticleEntity
import com.gh.gamecenter.entity.PersonalHistoryEntity
import com.gh.gamecenter.entity.PublishImageTextRequest
import com.gh.gamecenter.entity.VoteEntity
import com.gh.gamecenter.feature.entity.CommentEntity
import com.gh.gamecenter.forum.home.recommend.adapter.ImageAndTextAdapter
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 com.zhihu.matisse.internal.utils.PathUtils
import io.reactivex.Completable
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.schedulers.Schedulers
import okhttp3.MediaType
import okhttp3.ResponseBody
class ImageArticleRepository private constructor(
private val api: ApiService,
private val newApi: ApiService
) {
fun loadRecommends(page: Int): Single<List<ImageArticleEntity>> {
val version = BuildConfig.VERSION_NAME
val channel = HaloApp.getInstance().channel
return api.loadImageArticleRecommends(IMAGE_ARTICLE_SORT, page, version, channel)
}
fun isShowDragTips() =
Single.create<Boolean> {
val hasShow = SPUtils.getBoolean(Constants.SP_HAS_SHOW_IMAGE_AND_TEXT_DRAG_TIPS)
it.onSuccess(!hasShow)
}
fun saveHasShowDragTips() {
SPUtils.setBoolean(Constants.SP_HAS_SHOW_IMAGE_AND_TEXT_DRAG_TIPS, true)
}
fun checkHasSections(bbsId: String) =
newApi.getForumSections(bbsId)
.map {
it.size > 0
}
fun getModeratorsInfo(bbsId: String) =
api.getModeratorsInfo(bbsId)
.map {
it["is_moderators"].asBoolean
}
fun createOrEditDraft(
request: PublishImageTextRequest,
localImages: List<ImageAndTextAdapter.LocalImage>
): Single<ResponseBody> {
// 需要先上传本地图片
val userId = UserManager.getInstance().userId
return updatePic(localImages)
.observeOn(Schedulers.io())
.flatMap {
request.images = it
if (request.draftId.isNotBlank()) {
// 编辑草稿
api.editImageArticleDrafts(userId, request.draftId, request)
.toSingleDefault(ResponseBody.create(MediaType.parse("application/json"), "{}"))
} else {
api.createImageArticleDrafts(userId, request)
}
}
}
fun publishImageText(
request: PublishImageTextRequest,
localImages: List<ImageAndTextAdapter.LocalImage>
): Single<ImageArticleEntity> {
return updatePic(localImages)
.observeOn(Schedulers.io())
.flatMap {
request.images = it
api.publishImageText(request)
.flatMap {
val view = "detail"
val versionName = BuildConfig.VERSION_NAME
val channel = HaloApp.getInstance().channel
api.loadImageArticleDetail(it["_id"].asString, view, versionName, channel)
}
}
}
fun editImageArticle(
id: String,
request: PublishImageTextRequest,
images: List<ImageAndTextAdapter.LocalImage>
): Single<ImageArticleEntity> {
return updatePic(images)
.observeOn(Schedulers.io())
.flatMap {
request.images = it
api.editImageArticle(id, request)
.toSingleDefault(id)
.flatMap {
val view = "detail"
val versionName = BuildConfig.VERSION_NAME
val channel = HaloApp.getInstance().channel
api.loadImageArticleDetail(it, view, versionName, channel)
}
}
}
fun loadDraft(): Single<List<ImageArticleEntity>> {
val userId = UserManager.getInstance().userId
val versionName = BuildConfig.VERSION_NAME
val channel = HaloApp.getInstance().channel
return api.loadImageTextDrafts(userId, versionName, channel)
}
fun deleteDraft(draftId: String): Completable {
val userId = UserManager.getInstance().userId
return api.deleteImageArticleDraft(userId, draftId)
}
fun loadImageArticleDetail(imageArticleId: String): Single<ImageArticleEntity> {
val view = "detail"
return api.loadImageArticleDetail(imageArticleId, view, BuildConfig.VERSION_NAME, HaloApp.getInstance().channel)
}
fun loadMyImageArticleList(page: Int): Observable<List<PersonalHistoryEntity>> {
val userId = UserManager.getInstance().userId
val version = BuildConfig.VERSION_NAME
val channel = HaloApp.getInstance().channel
return api.getPersonalHistory(userId, page, channel, IMAGE_ARTICLE_FILTER)
}
private fun updatePic(localImages: List<ImageAndTextAdapter.LocalImage>): Single<List<String>> =
Single.create {
// 已上传的图片
val urls = localImages.mapNotNull { image -> image.url }
val uris = localImages.mapNotNull { image -> image.uri }
if (uris.isEmpty()) {
it.onSuccess(urls)
return@create
}
// 需要上传的图片
val localPaths = uris.map { uri -> PathUtils.getPath(HaloApp.getInstance(), uri) }
UploadImageUtils.compressAndUploadImageList(
UploadImageUtils.UploadType.community_article,
localPaths,
false,
object : UploadImageUtils.OnUploadImageListListener {
override fun onSuccess(
imageUrlMap: LinkedHashMap<String, String>,
errorMap: Map<String, Exception>
) {
val finalUrls = urls + imageUrlMap.values
it.onSuccess(finalUrls)
}
override fun onSingleSuccess(imageUrlMap: Map<String, String>) = Unit
override fun onError(errorMap: Map<String, Exception>) {
// 图片上传失败,忽略
}
override fun onProgress(total: Long, progress: Long) = Unit
})
}
fun getSingleCommentById(commentId: String): Single<CommentEntity> {
return api.getImageArticleCommentDetail(
commentId,
BuildConfig.VERSION_NAME,
HaloApp.getInstance().channel,
System.currentTimeMillis()
)
}
fun followBbs(communityId: String, isFollow: Boolean) =
if (isFollow) {
api.followForum(communityId)
} else {
api.unFollowForum(communityId)
}
fun collect(imageArticleId: String, isCollected: Boolean) =
if (isCollected) {
api.collectImageArticle(imageArticleId)
.flatMapCompletable {
Completable.complete()
}
} else {
api.cancelImageArticleCollection(imageArticleId)
}
fun voteImageArticle(imageArticleId: String, vote: Boolean): Single<VoteEntity> = if (vote) {
api.voteArticleImage(imageArticleId)
} else {
api.unVoteArticleImage(imageArticleId)
}
fun applyHighlightForImageArticle(imageArticleId: String): Completable =
api.applyHighlightForImageArticle(imageArticleId)
fun addHighlight(isAdd: Boolean, imageArticleId: String) =
if (isAdd) {
api.addHighlightForImageArticle(imageArticleId)
} else {
api.cancelHighlightForImageArticle(imageArticleId)
}
fun deleteOrHideImageArticle(imageArticleId: String) =
api.deleteOrHideImageArticle(imageArticleId)
fun followUser(follow: Boolean, userId: String) =
if (follow) {
api.postFollowing(userId)
} else {
api.deleteFollowing(userId)
}
companion object {
private const val IMAGE_ARTICLE_FILTER = "scene:question_answer,type:image_article"
private const val IMAGE_ARTICLE_SORT = "time.comment:-1"
fun newInstance() =
ImageArticleRepository(RetrofitManager.getInstance().api, RetrofitManager.getInstance().newApi)
}
}

View File

@ -0,0 +1,11 @@
package com.gh.gamecenter.forum.home.recommend
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.ToolbarFragment
class ImageArticleRequestPermissionFragment : ToolbarFragment() {
override fun getLayoutId(): Int {
return R.layout.fragment_image_article_request_permission
}
}

View File

@ -0,0 +1,51 @@
package com.gh.gamecenter.forum.home.recommend
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.entity.ImageArticleEntity
import com.gh.gamecenter.entity.VoteEntity
import com.gh.gamecenter.eventbus.EBImageArticleChanged
import io.reactivex.disposables.CompositeDisposable
import org.greenrobot.eventbus.EventBus
class ImageArticleUseCase(
private val repository: ImageArticleRepository,
private val compositeDisposable: CompositeDisposable
) {
fun voteImageArticle(imageArticleId: String, isVote: Boolean) {
repository.voteImageArticle(imageArticleId, isVote)
.compose(singleToMain())
.subscribe(object : BiResponse<VoteEntity>() {
override fun onSuccess(data: VoteEntity) {
notifyVoteChanged(imageArticleId, isVote, data.vote)
}
}).let(compositeDisposable::add)
}
companion object {
fun notifyVoteChanged(id: String, isVoted: Boolean, count: Int) {
EventBus.getDefault().post(EBImageArticleChanged.VoteChanged(id, isVoted, count))
}
fun notifyCommentChanged(id: String, count: Int) {
EventBus.getDefault().post(EBImageArticleChanged.CommentChanged(id, count))
}
fun notifyDataDeleted(id: String) {
EventBus.getDefault().post(EBImageArticleChanged.DataDeleted(id))
}
fun notifyDataCreated(data: ImageArticleEntity, draftId: String = "") {
EventBus.getDefault().post(EBImageArticleChanged.DataCreated(data, draftId))
}
fun notifyDataEdited(data: ImageArticleEntity) {
EventBus.getDefault().post(EBImageArticleChanged.DataChanged(data))
}
}
}

View File

@ -0,0 +1,209 @@
package com.gh.gamecenter.forum.home.recommend
import android.content.Context
import android.content.Intent
import android.graphics.Color
import android.os.Bundle
import android.widget.ImageView
import android.widget.TextView
import androidx.activity.result.ActivityResultCallback
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContract
import androidx.activity.viewModels
import androidx.fragment.app.Fragment
import com.alibaba.android.arouter.facade.annotation.Autowired
import com.alibaba.android.arouter.facade.annotation.Route
import com.alibaba.android.arouter.launcher.ARouter
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.activity.BaseActivity
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.entity.CommunityEntity
import com.gh.gamecenter.common.utils.PermissionHelper
import com.gh.gamecenter.common.utils.SensorsBridge
import com.gh.gamecenter.core.utils.DisplayUtils
import com.gh.gamecenter.entity.ImageArticleEntity
import com.gh.gamecenter.feature.selector.ChooseType
import com.gh.gamecenter.feature.selector.LocalMediaActivity
import com.gh.gamecenter.forum.home.CommunityHomeFragment.Companion.IMAGE_ARTICLE_REQUEST_CODE
import com.gh.gamecenter.forum.home.recommend.fragment.ImageArticleDraftFragment
import com.gh.gamecenter.forum.home.recommend.fragment.PublishImageArticleFragment
import com.gh.gamecenter.forum.home.recommend.viewmodel.PublishImageArticleActivityViewModel
import com.gh.gamecenter.livedata.EventObserver
import com.zhihu.matisse.Matisse
@Route(path = RouteConsts.activity.publishImageArticleActivity)
class PublishImageArticleActivity : BaseActivity() {
private val viewModel by viewModels<PublishImageArticleActivityViewModel>()
private lateinit var chooseImageLauncher: ActivityResultLauncher<Int>
private var isFirstEnter = true
@JvmField
@Autowired(name = EntranceConsts.KEY_IMAGE_ARTICLE_ENTITY, desc = "编辑图文实体", required = false)
var imageArticleEntity: ImageArticleEntity? = null
@JvmField
@Autowired(name = EntranceConsts.KEY_COMMUNITY_DATA, desc = "绑定的社区实体", required = false)
var communityEntity: CommunityEntity? = null
private lateinit var tvTitle: TextView
override fun getLayoutId(): Int {
return R.layout.activity_publish_image_article
}
override fun onCreate(savedInstanceState: Bundle?) {
ARouter.getInstance().inject(this)
super.onCreate(savedInstanceState)
DisplayUtils.setLightStatusBar(this, true)
setStatusBarColor(Color.TRANSPARENT)
tvTitle = findViewById(R.id.tv_title)
val ivBack = findViewById<ImageView>(R.id.iv_back)
ivBack.setOnClickListener {
val fragment = supportFragmentManager.fragments.lastOrNull()
if(fragment is PublishImageArticleFragment){
SensorsBridge.trackEvent("ArticleCancelclick")
}
onBackPressed()
}
supportFragmentManager.addOnBackStackChangedListener {
val fragment = supportFragmentManager.fragments.lastOrNull()
val titleResId = if (fragment is ImageArticleDraftFragment) {
R.string.image_article_draft
} else {
R.string.post_image_and_text
}
tvTitle.setText(titleResId)
}
chooseImageLauncher = registerForActivityResult(object : ActivityResultContract<Int, Intent?>() {
override fun createIntent(context: Context, input: Int): Intent {
return LocalMediaActivity.getIntent(
context,
ChooseType.IMAGE,
input,
"图文动态"
)
}
override fun parseResult(resultCode: Int, intent: Intent?): Intent? {
return intent
}
}, ActivityResultCallback<Intent?> {
if (it != null) {
val uris = Matisse.obtainResult(it)
if (uris.isEmpty()) {
if (isFirstEnter) {
finish()
}
} else {
viewModel.addImages(uris)
// 打开发布页
if (isFirstEnter) {
replaceFragment(true) {
PublishImageArticleFragment.newInstance()
}
}
}
} else {
if (isFirstEnter) {
finish()
}
}
})
if (imageArticleEntity == null) {
// 新建图文,优先进入图库选择页
replaceFragment(false, ::ImageArticleRequestPermissionFragment)
PermissionHelper.checkStoragePermissionBeforeActionForResult(this) { grant ->
if (grant) {
chooseImage(9)
} else {
finish()
}
}
} else {
// 编辑图文草稿,不用弹出授权页,直接进入发布页
replaceFragment(true) {
PublishImageArticleFragment.newInstance()
}
}
SensorsBridge.trackEvent(
"ViewPostArticle",
"source_entrance",
mEntrance,
"article_type",
"图文",
"bbs_type",
imageArticleEntity?.community?.type ?: communityEntity?.type ?: "",
"bbs_id",
imageArticleEntity?.community?.id ?: communityEntity?.id ?: ""
)
with(viewModel) {
val lifecycleOwner = this@PublishImageArticleActivity
chooseImageAction.observe(lifecycleOwner, EventObserver {
isFirstEnter = false
PermissionHelper.checkStoragePermissionBeforeActionForResult(this@PublishImageArticleActivity) { grant ->
if (grant) {
this@PublishImageArticleActivity.chooseImage(it)
}
}
NewLogUtils.logChooseMedia(
"view_media",
"图文",
"图片"
)
})
draftBoxDestination.observe(lifecycleOwner, EventObserver {
replaceFragment(true) {
ImageArticleDraftFragment.newInstance(true)
}
})
setEditImageArticle(imageArticleEntity)
communityEntity?.let(::setCommunity)
}
}
private fun chooseImage(limit: Int) {
try {
chooseImageLauncher.launch(limit)
} catch (e: Exception) {
toast(R.string.media_image_hint)
e.printStackTrace()
}
}
private inline fun <reified T : Fragment> replaceFragment(addBackStack: Boolean = true, creator: () -> T) {
val tag = T::class.java.toString()
val fragment = supportFragmentManager.findFragmentByTag(tag) ?: creator()
val transaction = supportFragmentManager.beginTransaction()
transaction.replace(R.id.layout_activity_content, fragment, tag)
if (addBackStack) {
transaction.addToBackStack(tag)
}
transaction.commitAllowingStateLoss()
}
companion object {
fun getIntent(context: Context, entrance: String): Intent {
val intent = Intent(context, PublishImageArticleActivity::class.java)
intent.putExtra(EntranceConsts.KEY_ENTRANCE, entrance)
return intent
}
}
}

View File

@ -0,0 +1,107 @@
package com.gh.gamecenter.forum.home.recommend.adapter
import android.net.Uri
import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil.ItemCallback
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.utils.display
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.utils.visibleIf
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.databinding.RecyclerImageAndTextBinding
import com.gh.gamecenter.forum.home.recommend.viewmodel.PublishImageArticleViewModel
open class ImageAndTextAdapter(
private val viewModel: PublishImageArticleViewModel,
private val startDrag: (ViewHolder) -> Unit
) :
ListAdapter<ImageAndTextAdapter.LocalImage, ImageAndTextAdapter.ImageViewHolder>(
createItemDiffCallback()
) {
override fun submitList(list: List<LocalImage>?) {
val size = list?.size ?: 0
if (size < 9 && list != null) {
super.submitList(list + LocalImage(INVALID_ID, false, showClose = false))
} else {
super.submitList(list)
}
}
public override fun getItem(position: Int): LocalImage {
return super.getItem(position)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ImageViewHolder {
return ImageViewHolder(parent.toBinding())
}
override fun onBindViewHolder(holder: ImageViewHolder, position: Int) {
val item = getItem(position)
with(holder.binding) {
tvCover.visibleIf(item.isCover)
ivClose.visibleIf(item.showClose) {
ivClose.setOnClickListener {
if (itemCount <= 2) {
ToastUtils.showToast(root.context.getString(R.string.post_image_limit))
return@setOnClickListener
}
viewModel.removeImage(holder.bindingAdapterPosition)
}
}
if (item.id != INVALID_ID) {
if (item.uri != null) {
ivIcon.setImageURI(item.uri)
} else {
ivIcon.display(item.url)
}
ivIcon.setOnClickListener(null)
ivIcon.setOnLongClickListener {
startDrag(holder)
return@setOnLongClickListener true
}
} else {
ivIcon.setImageResource(R.drawable.ic_image_and_text_add)
ivIcon.setOnClickListener {
viewModel.chooseImage()
}
root.setOnLongClickListener(null)
}
}
}
companion object {
const val INVALID_ID = -1
private fun createItemDiffCallback() = object : ItemCallback<LocalImage>() {
override fun areItemsTheSame(oldItem: LocalImage, newItem: LocalImage): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: LocalImage, newItem: LocalImage): Boolean {
return oldItem == newItem
}
}
}
data class LocalImage(
val id: Int,
var isCover: Boolean,
var showClose: Boolean,
val uri: Uri? = null, // 本地图片
val url: String? = null // 线上图片
)
data class ImageViewHolder(
val binding: RecyclerImageAndTextBinding
) : RecyclerView.ViewHolder(binding.root)
}

View File

@ -0,0 +1,42 @@
package com.gh.gamecenter.forum.home.recommend.adapter
import android.view.ViewGroup
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.gamecenter.common.utils.ImageUtils
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.databinding.RecyclerImageArticleDetailBannerBinding
class ImageArticleDetailBannerAdapter : ListAdapter<String, ImageArticleDetailBannerAdapter.BannerViewHolder>(
createDiffCallback()
) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BannerViewHolder {
return BannerViewHolder(parent.toBinding())
}
override fun onBindViewHolder(holder: BannerViewHolder, position: Int) {
with(holder.binding) {
ImageUtils.display(ivBanner, getItem(position))
}
}
companion object {
fun createDiffCallback() = object : DiffUtil.ItemCallback<String>() {
override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
return oldItem == newItem
}
}
}
class BannerViewHolder(val binding: RecyclerImageArticleDetailBannerBinding) : ViewHolder(binding.root)
}

View File

@ -0,0 +1,15 @@
package com.gh.gamecenter.forum.home.recommend.adapter
import android.content.Context
import com.gh.gamecenter.feature.entity.CommentEntity
import com.gh.gamecenter.qa.comment.base.BaseCommentAdapter
import com.gh.gamecenter.qa.comment.base.BaseCommentAdapter.AdapterType
import com.gh.gamecenter.qa.comment.base.BaseCommentViewModel
class ImageArticleDetailCommentAdapter(
context: Context,
private val viewModel: BaseCommentViewModel,
type: AdapterType,
entrance: String
) : BaseCommentAdapter(context, viewModel, type, entrance) {
}

View File

@ -0,0 +1,79 @@
package com.gh.gamecenter.forum.home.recommend.adapter
import android.content.Context
import android.view.ViewGroup
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.common.util.NewsUtils
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.toBinding
import com.gh.gamecenter.databinding.RecyclerImageArticleDraftBinding
import com.gh.gamecenter.entity.ImageArticleEntity
import com.gh.gamecenter.forum.home.recommend.viewmodel.ImageArticleDraftViewModel
import com.lzf.easyfloat.utils.DisplayUtils
class ImageArticleDraftAdapter(
private val context: Context,
private val viewModel: ImageArticleDraftViewModel
) :
ListAdapter<ImageArticleEntity, ImageArticleDraftAdapter.DraftViewHolder>(createDiffCallback()) {
private val itemWidth by lazy {
(DisplayUtils.getScreenWidth(context) - 20F.dip2px()) / 2F
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DraftViewHolder {
return DraftViewHolder(parent.toBinding())
}
override fun onBindViewHolder(holder: DraftViewHolder, position: Int) {
val item = getItem(position)
with(holder.binding) {
val title = item.title.ifBlank { item.content }
tvTitle.goneIf(title.isBlank()) {
tvTitle.text = title
}
val imageInfo = item.imagesInfos.firstOrNull()
val imageRadio = ImageArticleEntity.getImageRadio(imageInfo, DRAFT_RADIO_MIN, DRAFT_RADIO_MAX)
ivCover.updateLayoutParams {
height = (itemWidth * imageRadio).toInt()
}
ImageUtils.display(ivCover, item.images.firstOrNull() ?: "")
tvTime.text = NewsUtils.getFormattedTime(item.time.update)
vDelete.setOnClickListener {
viewModel.showDeleteDraftDialog(item.id)
}
root.setOnClickListener {
viewModel.editDraft(item)
}
}
}
companion object {
const val DRAFT_RADIO_MIN = 0.75F
const val DRAFT_RADIO_MAX = 1.33F
private fun createDiffCallback() = object : ItemCallback<ImageArticleEntity>() {
override fun areItemsTheSame(oldItem: ImageArticleEntity, newItem: ImageArticleEntity): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(
oldItem: ImageArticleEntity,
newItem: ImageArticleEntity
): Boolean {
return oldItem == newItem
}
}
}
data class DraftViewHolder(val binding: RecyclerImageArticleDraftBinding) : ViewHolder(binding.root)
}

View File

@ -0,0 +1,193 @@
package com.gh.gamecenter.forum.home.recommend.adapter
import android.content.Context
import android.view.ViewGroup
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.RecyclerView
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.RecyclerRecommendHomeBinding
import com.gh.gamecenter.entity.ImageArticleEntity
import com.gh.gamecenter.entity.PersonalHistoryEntity
import com.gh.gamecenter.forum.home.recommend.ImageArticleFooterWrapperAdapter
import com.gh.gamecenter.forum.home.recommend.adapter.ImageArticleDraftAdapter.Companion.DRAFT_RADIO_MAX
import com.gh.gamecenter.forum.home.recommend.adapter.ImageArticleDraftAdapter.Companion.DRAFT_RADIO_MIN
import com.gh.gamecenter.forum.home.recommend.adapter.RecommendHomeAdapter.Companion.PAYLOADS_CHANGED_COVER
import com.gh.gamecenter.forum.home.recommend.adapter.RecommendHomeAdapter.Companion.PAYLOADS_CHANGED_TITLE
import com.gh.gamecenter.forum.home.recommend.adapter.RecommendHomeAdapter.Companion.PAYLOADS_CHANGED_VOTE
import com.gh.gamecenter.forum.home.recommend.viewmodel.MyImageArticleListViewModel
import com.lzf.easyfloat.utils.DisplayUtils
class MyImageArticleAdapter(
private val context: Context,
private val viewModel: MyImageArticleListViewModel
) :
ImageArticleFooterWrapperAdapter<PersonalHistoryEntity, MyImageArticleAdapter.MyImageArticleViewHolder>(
createDiffCallback()
) {
override fun onCreateDataViewHolder(parent: ViewGroup): MyImageArticleViewHolder {
return MyImageArticleViewHolder(parent.toBinding(), object : MyImageArticleViewHolder.OnMyArticleImageListener {
override fun navigateToImageArticleDetailPage(item: PersonalHistoryEntity, position: Int) {
viewModel.navigateToImageArticleDetailPage(item.id)
}
override fun voteImageArticle(id: String, vote: Boolean) {
viewModel.useCase.voteImageArticle(id, vote)
}
})
}
override fun onBindDataViewHolder(holder: MyImageArticleViewHolder, position: Int) {
val item = getItem(position)
holder.bind(item, position)
}
override fun onBindDataViewHolder(holder: MyImageArticleViewHolder, position: Int, payloads: MutableList<Any>) {
val item = getItem(position)
holder.bind(item, position, payloads)
}
override fun loadMore() {
viewModel.loadMore()
}
companion object {
fun createDiffCallback() = object : ItemCallback<PersonalHistoryEntity>() {
override fun areItemsTheSame(oldItem: PersonalHistoryEntity, newItem: PersonalHistoryEntity): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: PersonalHistoryEntity, newItem: PersonalHistoryEntity): Boolean {
return oldItem.title == newItem.title
&& oldItem.brief == newItem.brief
&& oldItem.user.id == newItem.user.id
&& oldItem.user.icon == newItem.user.icon
&& oldItem.user.name == newItem.user.name
&& oldItem.count.vote == newItem.count.vote
&& oldItem.status == newItem.status
}
override fun getChangePayload(oldItem: PersonalHistoryEntity, newItem: PersonalHistoryEntity): Any {
val payloads = mutableListOf<String>()
if (oldItem.images.firstOrNull() != newItem.images.firstOrNull()
|| oldItem.imagesInfo.firstOrNull() != newItem.imagesInfo.firstOrNull()
) {
payloads.add(PAYLOADS_CHANGED_COVER)
}
val oldTitle = oldItem.title.ifBlank { oldItem.content }
val newTitle = newItem.title.ifBlank { newItem.content }
if (oldTitle != newTitle) {
payloads.add(PAYLOADS_CHANGED_TITLE)
}
if (oldItem.me.isCommunityArticleVote != newItem.me.isCommunityArticleVote
|| oldItem.count.vote != newItem.count.vote
) {
payloads.add(PAYLOADS_CHANGED_VOTE)
}
return payloads
}
}
}
class MyImageArticleViewHolder(
val binding: RecyclerRecommendHomeBinding,
private val listener: OnMyArticleImageListener
) : RecyclerView.ViewHolder(binding.root) {
private val itemWidth by lazy {
(DisplayUtils.getScreenWidth(itemView.context) - 20F.dip2px()) / 2F
}
fun bind(item: PersonalHistoryEntity, position: Int, payloads: List<Any?>? = null) {
fun setCover() {
val imageInfo = item.imagesInfo.firstOrNull()
val imageRadio = ImageArticleEntity.getImageRadio(imageInfo, DRAFT_RADIO_MIN, DRAFT_RADIO_MAX)
binding.ivCover.updateLayoutParams {
height = (itemWidth * imageRadio).toInt()
}
binding.ivCover.display(item.images.firstOrNull())
}
fun setTitle() {
val title = item.title.ifBlank { item.content }
binding.tvTitle.goneIf(title.isBlank()) {
binding.tvTitle.text = title
}
}
fun setVote() {
binding.voteState.setVote(item.me.isCommunityArticleVote, item.count.vote, item.status)
}
with(binding) {
if (payloads.isNullOrEmpty()) {
ivIcon.displayGameIcon(item.user.icon, null)
tvAuthorName.text = item.user.name
setTitle()
setCover()
setVote()
} else {
payloads.filterIsInstance<List<String>>()
.flatten()
.forEach { change ->
when (change) {
PAYLOADS_CHANGED_COVER -> {
setCover()
}
PAYLOADS_CHANGED_TITLE -> {
setTitle()
}
PAYLOADS_CHANGED_VOTE -> {
binding.voteState.setVote(
item.me.isCommunityArticleVote,
item.count.vote,
item.status
)
}
}
}
}
root.setOnClickListener {
listener.navigateToImageArticleDetailPage(item, position)
}
setVote()
voteState.setOnClickListener {
val vote = !item.me.isCommunityArticleVote
SensorsBridge.trackArticleLikeClick(
customerType = item.user.auth?.text ?: "",
articleId = item.id,
bbsId = item.community.id,
bbsType = item.community.typeChinese,
activityTag = "",
gameForumType = item.community.game?.categoryChinese ?: "",
articleType = "图文",
buttonName = if (item.me.isCommunityArticleVote) "取消点赞" else "赞同"
)
listener.voteImageArticle(item.id, vote)
}
}
}
interface OnMyArticleImageListener {
fun navigateToImageArticleDetailPage(item: PersonalHistoryEntity, position: Int)
fun voteImageArticle(id: String, vote: Boolean)
}
}
}

View File

@ -0,0 +1,269 @@
package com.gh.gamecenter.forum.home.recommend.adapter
import android.content.Context
import android.util.SparseArray
import android.view.ViewGroup
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.DiffUtil.ItemCallback
import androidx.recyclerview.widget.RecyclerView.ViewHolder
import com.gh.common.exposure.IExposable
import com.gh.common.util.NewLogUtils
import com.gh.common.util.NewLogUtils.LOG_STORE_BBS
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.databinding.RecyclerRecommendHomeBinding
import com.gh.gamecenter.entity.ImageArticleEntity
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.forum.home.recommend.ImageArticleFooterWrapperAdapter
import com.gh.gamecenter.forum.home.recommend.adapter.ImageArticleDraftAdapter.Companion.DRAFT_RADIO_MAX
import com.gh.gamecenter.forum.home.recommend.adapter.ImageArticleDraftAdapter.Companion.DRAFT_RADIO_MIN
import com.gh.gamecenter.forum.home.recommend.viewmodel.ImageArticleHomeViewModel
import com.lzf.easyfloat.utils.DisplayUtils
class RecommendHomeAdapter(
private val context: Context,
private val viewModel: ImageArticleHomeViewModel
) :
ImageArticleFooterWrapperAdapter<ImageArticleEntity, RecommendHomeAdapter.RecommendHomeViewHolder>(
createDiffCallback()
), IExposable {
private val viewHolderSet = mutableSetOf<RecommendHomeViewHolder>()
private val exposureEventSparseArray: SparseArray<ExposureEvent> = SparseArray()
override fun onViewAttachedToWindow(holder: ViewHolder) {
if (holder is RecommendHomeViewHolder) {
viewHolderSet.add(holder)
}
}
override fun onViewDetachedFromWindow(holder: ViewHolder) {
viewHolderSet.remove(holder)
}
override fun onCreateDataViewHolder(parent: ViewGroup): RecommendHomeViewHolder {
return RecommendHomeViewHolder(parent.toBinding(), object : OnImageArticleListener {
override fun navigateToImageArticleDetailPage(imageArticleEntity: ImageArticleEntity) {
viewModel.navigateToImageArticleDetailPage(imageArticleEntity)
}
override fun voteImageArticle(position: Int, item: ImageArticleEntity) {
val vote = !item.me.isCommunityArticleVote
SensorsBridge.trackArticleLikeClick(
customerType = item.user.auth?.text ?: "",
articleId = item.id,
bbsId = item.community?.id ?: "",
bbsType = item.community?.typeChinese ?: "综合论坛",
activityTag = "",
gameForumType = item.community?.game?.categoryChinese ?: "",
articleType = "图文",
buttonName = if (item.me.isCommunityArticleVote) "取消点赞" else "赞同"
)
NewLogUtils.logRecommendFeedContentClick(
"click_for_you_like",
"图文",
item.id,
position + 1,
item.community?.id ?: "",
item.community?.typeChinese ?: "",
item.user.id ?: ""
)
viewModel.useCase.voteImageArticle(item.id, vote)
}
})
}
override fun onBindDataViewHolder(holder: RecommendHomeViewHolder, position: Int) {
val item = getItem(position)
val exposureEvent = ExposureEvent.createEvent(null, listOf())
.apply {
jsonExposure = NewLogUtils.getCommunityExposureJsonObject(
item.id,
item.community?.id ?: "",
item.community?.typeChinese ?: "",
position + 1,
"社区推荐信息流"
)
logStore = LOG_STORE_BBS
}
exposureEventSparseArray.append(position, exposureEvent)
holder.bind(item, position)
}
override fun onBindDataViewHolder(holder: RecommendHomeViewHolder, position: Int, payloads: MutableList<Any>) {
holder.bind(getItem(position), position, payloads)
}
override fun loadMore() {
viewModel.loadMore()
}
fun notifyItemChanged(result: ImageArticleEntity) {
val position = currentList.indexOfFirst { it.id == result.id }
if (position != -1) {
val newData = currentList.toMutableList()
newData[position] = result
submitList(newData)
}
}
fun onDarkModeChanged() {
viewHolderSet.forEach {
it.binding.root.setCardBackgroundColor(
com.gh.gamecenter.common.R.color.ui_surface.toColor(
it.itemView.context
)
)
}
}
override fun getEventByPosition(pos: Int): ExposureEvent? {
val event = exposureEventSparseArray.get(pos)
return event
}
override fun getEventListByPosition(pos: Int): List<ExposureEvent>? = null
companion object {
const val PAYLOADS_CHANGED_COVER = "payloads_changed_cover"
const val PAYLOADS_CHANGED_TITLE = "payloads_changed_title"
const val PAYLOADS_CHANGED_VOTE = "payloads_changed_vote"
const val PAYLOADS_CHANGED_DARK_MODE = "payloads_changed_dark_mode"
fun createDiffCallback() = object : ItemCallback<ImageArticleEntity>() {
override fun areItemsTheSame(oldItem: ImageArticleEntity, newItem: ImageArticleEntity): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: ImageArticleEntity, newItem: ImageArticleEntity): Boolean {
return oldItem.title == newItem.title
&& oldItem.content == newItem.content
&& oldItem.user.id == newItem.user.id
&& oldItem.user.icon == newItem.user.icon
&& oldItem.user.name == newItem.user.name
&& oldItem.count.vote == newItem.count.vote
}
override fun getChangePayload(oldItem: ImageArticleEntity, newItem: ImageArticleEntity): Any? {
val payloads = mutableListOf<String>()
if (oldItem.images.firstOrNull() != newItem.images.firstOrNull()
|| oldItem.imagesInfos.firstOrNull() != newItem.imagesInfos.firstOrNull()
) {
payloads.add(PAYLOADS_CHANGED_COVER)
}
val oldTitle = oldItem.title.ifBlank { oldItem.content }
val newTitle = newItem.title.ifBlank { newItem.content }
if (oldTitle != newTitle) {
payloads.add(PAYLOADS_CHANGED_TITLE)
}
if (oldItem.me.isCommunityArticleVote != newItem.me.isCommunityArticleVote
|| oldItem.count.vote != newItem.count.vote
) {
payloads.add(PAYLOADS_CHANGED_VOTE)
}
return payloads
}
}
}
class RecommendHomeViewHolder(
val binding: RecyclerRecommendHomeBinding,
private val listener: OnImageArticleListener
) : ViewHolder(binding.root) {
private val itemWidth by lazy {
(DisplayUtils.getScreenWidth(itemView.context) - 20F.dip2px()) / 2F
}
fun bind(item: ImageArticleEntity, position: Int, payloads: MutableList<Any> = mutableListOf()) {
fun setCover() {
val imageInfo = item.imagesInfos.firstOrNull()
val imageRadio = ImageArticleEntity.getImageRadio(imageInfo, DRAFT_RADIO_MIN, DRAFT_RADIO_MAX)
binding.ivCover.updateLayoutParams {
height = (itemWidth * imageRadio).toInt()
}
binding.ivCover.display(item.images.firstOrNull())
}
fun setTitle() {
val title = item.title.ifBlank { item.content }
binding.tvTitle.goneIf(title.isBlank()) {
binding.tvTitle.text = title
}
}
fun setVote() {
binding.voteState.setVote(item.me.isCommunityArticleVote, item.count.vote, item.status)
}
binding.root.setCardBackgroundColor(
com.gh.gamecenter.common.R.color.ui_surface.toColor(
itemView.context
)
)
with(binding) {
if (payloads.isEmpty()) {
ivIcon.displayGameIcon(item.user.icon, null)
tvAuthorName.text = item.user.name
setTitle()
setCover()
setVote()
} else {
payloads.filterIsInstance<List<String>>()
.flatten()
.forEach { change ->
when (change) {
PAYLOADS_CHANGED_COVER -> {
setCover()
}
PAYLOADS_CHANGED_TITLE -> {
setTitle()
}
PAYLOADS_CHANGED_VOTE -> {
setVote()
}
}
}
}
root.setOnClickListener {
NewLogUtils.logRecommendFeedContentClick(
"click_for_you_content",
"图文",
item.id,
position + 1,
item.community?.id ?: "",
item.community?.typeChinese ?: "",
item.user.id ?: ""
)
listener.navigateToImageArticleDetailPage(item)
}
voteState.setOnClickListener {
listener.voteImageArticle(position, item)
}
}
}
}
interface OnImageArticleListener {
fun navigateToImageArticleDetailPage(imageArticleEntity: ImageArticleEntity)
fun voteImageArticle(position: Int, item: ImageArticleEntity)
}
}

View File

@ -0,0 +1,136 @@
package com.gh.gamecenter.forum.home.recommend.fragment
import android.os.Bundle
import android.view.View
import androidx.core.content.ContextCompat
import androidx.fragment.app.viewModels
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.RecyclerView
import com.alibaba.android.arouter.launcher.ARouter
import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.ListAdapter
import com.gh.gamecenter.common.baselist.ListFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.viewModelProvider
import com.gh.gamecenter.common.view.CustomDividerItemDecoration
import com.gh.gamecenter.forum.home.recommend.adapter.ImageArticleDetailCommentAdapter
import com.gh.gamecenter.forum.home.recommend.viewmodel.ImageArticleCommentViewModel
import com.gh.gamecenter.forum.home.recommend.viewmodel.ImageArticleDetailViewModel
import com.gh.gamecenter.livedata.EventObserver
import com.gh.gamecenter.qa.article.detail.CommentItemData
import com.gh.gamecenter.qa.comment.base.BaseCommentAdapter
import com.halo.assistant.HaloApp
class ImageArticleCommentFragment : ListFragment<CommentItemData, ImageArticleCommentViewModel>() {
var imageArticleId = ""
var topCommentId: String? = ""
private val parentViewModel by viewModels<ImageArticleDetailViewModel>(ownerProducer = { parentFragment ?: this })
private val adapter by lazy {
ImageArticleDetailCommentAdapter(
requireContext(),
mListViewModel,
BaseCommentAdapter.AdapterType.COMMENT,
mEntrance
)
}
override fun provideListAdapter(): ListAdapter<*> = adapter
override fun provideListViewModel(): ImageArticleCommentViewModel {
return viewModelProvider(
ImageArticleCommentViewModel.Factory(
HaloApp.getInstance().application,
imageArticleId,
topCommentId ?: ""
)
)
}
override fun getItemDecoration(): RecyclerView.ItemDecoration {
val drawable = ContextCompat.getDrawable(requireContext(), R.drawable.divider_article_detail_comment)
val itemDecoration = CustomDividerItemDecoration(
requireContext(),
notDecorateTheFirstItem = true,
notDecorateTheLastItem = true
)
itemDecoration.setDrawable(drawable!!)
mItemDecoration = itemDecoration
return itemDecoration
}
override fun addSyncPageObserver() = true
override fun provideSyncAdapter() = adapter
override fun getLayoutId(): Int {
return R.layout.fragment_list_article_detail_comment
}
override fun onCreate(savedInstanceState: Bundle?) {
ARouter.getInstance().inject(this)
arguments?.let {
imageArticleId = it.getString(EntranceConsts.KEY_IMAGE_ARTICLE_ID) ?: ""
topCommentId = it.getString(EntranceConsts.KEY_TOP_COMMENT_ID) ?: ""
}
super.onCreate(savedInstanceState)
with(parentViewModel) {
sortType.observe(this@ImageArticleCommentFragment, EventObserver {
mListViewModel.changeSort(it)
})
insertNewestCommentAction.observe(this@ImageArticleCommentFragment, EventObserver {
mListViewModel.insertNewestComment(it)
})
commentCount.observe(this@ImageArticleCommentFragment) {
mListViewModel.commentCount = it
}
imageArticleDetailEntity.observe(this@ImageArticleCommentFragment) {
mListViewModel.topItemData = CommentItemData(imageArticleDetail = it)
}
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mListRefresh?.isEnabled = false
with(mListViewModel) {
updateCommentCountAction.observe(viewLifecycleOwner, EventObserver {
parentViewModel.updateComment("", commentCount)
})
}
}
override fun onLoadError() {
super.onLoadError()
mListRv.goneIf(false)
}
override fun onLoadNotFound() {
super.onLoadNotFound()
mListRv.goneIf(false)
}
override fun onLoadEmpty() {
super.onLoadEmpty()
mListRv.goneIf(false)
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
if (mListRv != null) {
mListRv.removeItemDecoration(mItemDecoration)
mListRv.addItemDecoration(itemDecoration)
}
}
}

View File

@ -0,0 +1,938 @@
package com.gh.gamecenter.forum.home.recommend.fragment
import android.app.Activity
import android.content.Intent
import android.graphics.Path
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.Message
import android.view.View
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.updateLayoutParams
import androidx.core.widget.TextViewCompat
import androidx.fragment.app.viewModels
import androidx.lifecycle.Lifecycle
import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback
import com.alibaba.android.arouter.launcher.ARouter
import com.gh.common.browse.ValueBrowseTimer
import com.gh.common.browse.asObserver
import com.gh.common.browse.withLifecycle
import com.gh.common.databind.BindingAdapters
import com.gh.common.util.BbsReportHelper
import com.gh.common.util.DirectUtils
import com.gh.common.util.DownloadItemUtils
import com.gh.common.util.NewLogUtils
import com.gh.common.util.NewsUtils
import com.gh.download.DownloadManager
import com.gh.gamecenter.GameDetailActivity
import com.gh.gamecenter.R
import com.gh.gamecenter.adapter.viewholder.GameViewHolder
import com.gh.gamecenter.common.base.fragment.BaseFragment
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.entity.AdditionalParamsEntity
import com.gh.gamecenter.common.entity.NormalShareEntity
import com.gh.gamecenter.common.eventbus.EBReuse
import com.gh.gamecenter.common.exposure.ExposureSource
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.databinding.FragmentImageArticleDetailBinding
import com.gh.gamecenter.entity.ImageArticleEntity
import com.gh.gamecenter.entity.MenuItemEntity
import com.gh.gamecenter.eventbus.EBDownloadStatus
import com.gh.gamecenter.eventbus.EBImageArticleChanged
import com.gh.gamecenter.eventbus.EBPackage
import com.gh.gamecenter.feature.entity.GameEntity
import com.gh.gamecenter.feature.entity.ImageInfo
import com.gh.gamecenter.feature.entity.Permissions
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.forum.home.recommend.adapter.ImageArticleDetailBannerAdapter
import com.gh.gamecenter.forum.home.recommend.viewmodel.ImageArticleDetailViewModel
import com.gh.gamecenter.livedata.EventObserver
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.qa.comment.CommentActivity
import com.gh.gamecenter.qa.comment.base.BaseCommentViewModel
import com.gh.gamecenter.qa.dialog.MoreFunctionPanelDialog
import com.gh.gamecenter.qa.entity.ArticleDetailEntity
import com.lightgame.download.DataWatcher
import com.lightgame.download.DownloadEntity
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class ImageArticleDetailFragment : BaseFragment<Unit>() {
private val viewModel by viewModels<ImageArticleDetailViewModel>()
private var imageArticleId = ""
private var topCommentId: String? = null
var scrollToCommentArea: Boolean = false
private var recommendId: String = ""
private var showIndicator = false
private var sourceEntrance = ""
private val binding: FragmentImageArticleDetailBinding by lazy {
FragmentImageArticleDetailBinding.inflate(layoutInflater)
}
private val adapter by lazy {
ImageArticleDetailBannerAdapter()
}
private val handler = object : Handler(Looper.getMainLooper()) {
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
if (msg.what == HIDE_BANNER_NUMBER_INDICATOR) {
binding.llNumberIndicator.goneIf(true)
}
}
}
private val dataWatcher = object : DataWatcher() {
override fun onDataChanged(downloadEntity: DownloadEntity) {
setDownloadButton(viewModel.imageArticleDetailEntity.value?.community?.game)
}
override fun onDataInit(downloadEntity: DownloadEntity) {
onDataChanged(downloadEntity)
}
}
private val browseTimer = ValueBrowseTimer<ImageArticleEntity>()
.withLifecycle(this)
.withStart {
SensorsBridge.trackArticleDetailsBrowsing(
bbsId = it?.community?.id ?: "",
bbsType = it?.community?.typeChinese ?: "",
customerType = it?.user?.auth?.text ?: "",
gameForumType = it?.community?.game?.categoryChinese ?: "",
activityTag = "",
articleType = "图文",
sourceEntrance = sourceEntrance,
articleId = it?.id ?: ""
)
}
.withResult { detail, time ->
SensorsBridge.trackArticleBrowsingDuration(
bbsId = detail?.community?.id ?: "",
bbsType = detail?.community?.typeChinese ?: "",
customerType = detail?.user?.auth?.text ?: "",
gameForumType = detail?.community?.game?.categoryChinese ?: "",
activityTag = "",
articleId = detail?.id ?: "",
articleType = "图文",
stayLength = time / 1000.0,
sourceEntrance = sourceEntrance
)
NewLogUtils.logJumpNoteDetail(
imageArticleId,
viewModel.imageArticleDetailEntity.value?.community?.id ?: "",
viewModel.imageArticleDetailEntity.value?.community?.typeChinese ?: "",
stayTime = time / 1000
)
}
override fun getInflatedLayout() = binding.root
override fun getLayoutId() = 0
override fun onCreate(savedInstanceState: Bundle?) {
arguments?.let {
imageArticleId = it.getString(EntranceConsts.KEY_IMAGE_ARTICLE_ID) ?: ""
topCommentId = it.getString(EntranceConsts.KEY_TOP_COMMENT_ID) ?: ""
scrollToCommentArea = it.getBoolean(EntranceConsts.KEY_SCROLL_TO_COMMENT_AREA)
sourceEntrance = it.getString(EntranceConsts.KEY_SOURCE_ENTRANCE) ?: ""
recommendId = it.getString(EntranceConsts.KEY_RECOMMEND_ID) ?: ""
}
super.onCreate(savedInstanceState)
viewModel.imageArticleDetailEntity.observe(this, browseTimer.asObserver())
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val tag = ImageArticleDetailFragment::class.java.name
val fragment =
childFragmentManager.findFragmentByTag(tag) ?: ImageArticleCommentFragment()
fragment.arguments = arguments
childFragmentManager.beginTransaction()
.replace(R.id.fcv_comment_container, fragment, tag)
.commitNowAllowingStateLoss()
with(viewModel) {
pageStatus.observe(viewLifecycleOwner) {
binding.reuseNoConnection.root.goneIf(it != LoadStatus.INIT_FAILED)
binding.clContent.goneIf(it != LoadStatus.INIT_LOADED)
binding.inputContainer.root.goneIf(it != LoadStatus.INIT_LOADED)
binding.layoutLoading.root.goneIf(it != LoadStatus.INIT_LOADING)
binding.reuseNoneData.root.goneIf(it != LoadStatus.INIT_EMPTY)
if (it == LoadStatus.INIT_EMPTY) {
toast(com.gh.gamecenter.common.R.string.content_delete_toast)
}
}
imageArticleDetailEntity.observe(viewLifecycleOwner) {
NewLogUtils.logViewNoteDetail(
it.id,
it.community?.id ?: "",
it.community?.typeChinese ?: "",
it.user.id ?: ""
)
fillUi(it)
}
isUserFollowed.observe(viewLifecycleOwner) {
binding.ctvFollow.isChecked = it
binding.ctvFollow.setText(
if (it) {
R.string.concerned
} else {
R.string.follow
}
)
}
commentCount.observe(viewLifecycleOwner) {
binding.inputContainer.bottomCommentTv.text = if (it == 0) {
R.string.comment.toResString()
} else {
"$it"
}
binding.tvCommentCount.text = "$it"
}
vote.observe(viewLifecycleOwner) {
val (isVoted, count) = it
val (imageDrawableResId, textColorResId) = if (isVoted) {
R.drawable.ic_article_detail_liked_bottom_bar to com.gh.gamecenter.common.R.color.text_theme
} else {
R.drawable.ic_article_detail_like_bottom_bar to com.gh.gamecenter.common.R.color.text_secondary
}
binding.inputContainer.bottomLikeIv.setImageResource(imageDrawableResId)
binding.inputContainer.bottomLikeTv.setTextColor(textColorResId.toColor(requireContext()))
binding.inputContainer.bottomLikeTv.text = if (count == 0) {
R.string.agree.toResString()
} else {
"$count"
}
}
isBbsFollowed.observe(viewLifecycleOwner) {
binding.ctvFollowBbs.isChecked = it
binding.ctvFollowBbs.setText(
if (it) {
R.string.concerned
} else {
R.string.follow
}
)
}
isFavorite.observe(viewLifecycleOwner) {
val (drawableResId, textColorResId) = if (it) {
R.drawable.ic_article_detail_stared_bottom_bar to com.gh.gamecenter.common.R.color.text_theme
} else {
R.drawable.ic_article_detail_star_bottom_bar to com.gh.gamecenter.common.R.color.text_secondary
}
binding.inputContainer.bottomStarIv.setImageResource(drawableResId)
binding.inputContainer.bottomStarTv.setTextColor(textColorResId.toColor(requireContext()))
binding.inputContainer.bottomStarTv.setText(
if (it) {
R.string.already_saved
} else {
R.string.menu_collect
}
)
}
applyHighlightAction.observe(viewLifecycleOwner, EventObserver {
val toastResId = if (it) {
R.string.apply_successfully
} else {
R.string.apply_failure
}
ToastUtils.showToast(toastResId.toResString())
})
addHighlightAction.observe(viewLifecycleOwner, EventObserver {
ToastUtils.showToast(it.toResString())
})
deleteOrHideImageArticleSuccessfully.observe(viewLifecycleOwner, EventObserver {
val (isSuccess, toastResId) = it
ToastUtils.showToast(toastResId.toResString())
if (isSuccess) {
EventBus.getDefault().post(EBImageArticleChanged.DataDeleted(imageArticleId))
requireActivity().finish()
}
})
loadImageArticleDetail(this@ImageArticleDetailFragment.imageArticleId, recommendId)
}
}
override fun initView(view: View?) {
super.initView(view)
binding.vpBanner.adapter = adapter
binding.vpBanner.offscreenPageLimit = 2
binding.vMore.setOnClickListener {
showMoreItemDialog()
}
binding.sfvOrder.setItemList(
listOf(R.string.positive_older.toResString(), R.string.reverse_order.toResString()), 0
)
binding.inputContainer.replyTv.setText(R.string.message_detail_comment_hint2)
binding.inputContainer.replyTv.setRoundedColorBackground(com.gh.gamecenter.common.R.color.ui_container_2, 19F)
binding.inputContainer.replyTv.setDebouncedClickListener {
NewLogUtils.logCommentClick(
"click_comment_area_comment_input_box",
viewModel.imageArticleDetailEntity.value?.user?.id ?: "",
"图文",
viewModel.imageArticleDetailEntity.value?.id
?: "",
viewModel.imageArticleDetailEntity.value?.community?.id ?: "",
viewModel.imageArticleDetailEntity.value?.community?.typeChinese ?: ""
)
clickToastByStatus(viewModel.imageArticleDetailEntity.value?.status ?: "") {
val imageArticle = viewModel.imageArticleDetailEntity.value ?: return@clickToastByStatus
val intent = CommentActivity.getImageArticleCommentIntent(
requireContext(),
imageArticle.id,
imageArticle.communityId,
viewModel.commentCount.value
)
startActivityForResult(intent, CommentActivity.REQUEST_CODE)
}
}
binding.inputContainer.bottomCommentIv.setOnClickListener {
binding.appBar.setExpanded(false, true)
NewLogUtils.logCommentAreaEnter("图文")
}
binding.inputContainer.bottomCommentTv.setOnClickListener { binding.inputContainer.bottomCommentIv.performClick() }
binding.inputContainer.bottomStarIv.setOnClickListener {
requireContext().ifLogin(entrance = "帖子详情-收藏") {
clickToastByStatus(viewModel.imageArticleDetailEntity.value?.status ?: "") {
viewModel.collect()
}
NewLogUtils.logCommentClick(
"click_comment_area_collect",
viewModel.imageArticleDetailEntity.value?.user?.id ?: "",
"图文",
imageArticleId,
viewModel.imageArticleDetailEntity.value?.community?.id ?: "",
viewModel.imageArticleDetailEntity.value?.community?.typeChinese ?: ""
)
}
}
binding.inputContainer.bottomStarTv.setOnClickListener { binding.inputContainer.bottomStarIv.performClick() }
binding.inputContainer.bottomLikeIv.setOnClickListener {
requireContext().ifLogin("帖子详情-赞同") {
clickToastByStatus(viewModel.imageArticleDetailEntity.value?.status ?: "") {
viewModel.vote()
}
}
}
binding.inputContainer.bottomLikeTv.setOnClickListener { binding.inputContainer.bottomLikeIv.performClick() }
binding.sfvOrder.setOnCheckedCallback {
val newSort = if (it == 0) {
BaseCommentViewModel.SortType.OLDEST
} else {
BaseCommentViewModel.SortType.LATEST
}
viewModel.changeSort(newSort)
}
binding.ctvFollow.setOnClickListener {
ifLogin("图文详情-关注") {
viewModel.followUser()
}
NewLogUtils.logClickNoteDetailFollow(
imageArticleId,
viewModel.imageArticleDetailEntity.value?.community?.id ?: "",
viewModel.imageArticleDetailEntity.value?.community?.typeChinese ?: "",
viewModel.imageArticleDetailEntity.value?.user?.id ?: "",
!(viewModel.isUserFollowed.value ?: false)
)
}
binding.vBack.setOnClickListener {
activity?.finish()
}
binding.ivAvatar.setOnClickListener {
DirectUtils.directToHomeActivity(
requireContext(),
viewModel.imageArticleDetailEntity.value?.user?.id,
1,
mEntrance,
"图文详情"
)
NewLogUtils.logClickNoteDetailProfilePhoto(
imageArticleId,
viewModel.imageArticleDetailEntity.value?.community?.id ?: "",
viewModel.imageArticleDetailEntity.value?.community?.typeChinese ?: "",
viewModel.imageArticleDetailEntity.value?.user?.id ?: ""
)
}
binding.tvName.setOnClickListener {
DirectUtils.directToHomeActivity(
requireContext(),
viewModel.imageArticleDetailEntity.value?.user?.id,
1,
mEntrance,
"图文详情"
)
NewLogUtils.logClickNoteDetailNickname(
imageArticleId,
viewModel.imageArticleDetailEntity.value?.community?.id ?: "",
viewModel.imageArticleDetailEntity.value?.community?.typeChinese ?: "",
viewModel.imageArticleDetailEntity.value?.user?.id ?: ""
)
}
binding.vpBanner.registerOnPageChangeCallback(object : OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
showNumberIndicator(position)
}
})
binding.reuseNoConnection.connectionReloadTv.setOnClickListener {
viewModel.loadImageArticleDetail(imageArticleId, recommendId)
}
}
private fun fillUi(imageArticle: ImageArticleEntity) {
binding.tvTitle.goneIf(imageArticle.title.isBlank()) {
binding.tvTitle.text = imageArticle.title
}
binding.tvContent.goneIf(imageArticle.content.isBlank()) {
binding.tvContent.text = imageArticle.content
}
binding.ivAvatar.display(imageArticle.user.border, imageArticle.user.icon, imageArticle.user.auth?.icon)
binding.tvName.text = imageArticle.user.name
if (imageArticle.user.auth == null) {
binding.tvAuth.goneIf(true)
TextViewCompat.setTextAppearance(binding.tvName, com.gh.gamecenter.common.R.style.TextHeadline)
} else {
binding.tvAuth.goneIf(false)
binding.tvAuth.text = imageArticle.user.auth?.text
TextViewCompat.setTextAppearance(binding.tvName, com.gh.gamecenter.common.R.style.TextBody2)
}
binding.ctvFollow.goneIf(imageArticle.me.isContentOwner)
binding.tvTime.text = NewsUtils.getFormattedTime(imageArticle.time.edit)
val imagesSize = imageArticle.images.size
showIndicator = imagesSize >= SHOW_DOT_INDICATOR_MIN
binding.dorIndicator.goneIf(!showIndicator) {
binding.dorIndicator.show(imagesSize)
}
binding.llNumberIndicator.goneIf(!showIndicator) {
binding.tvIndicatorTotal.text = "/${imagesSize}"
showNumberIndicator(0)
}
setBannerSize(imageArticle.imagesInfos.firstOrNull())
adapter.submitList(imageArticle.images)
val community = imageArticle.community
val game = community?.game
when {
game != null -> {
// 显示游戏
showGame(game, imageArticle.community.id)
}
community != null -> {
showCommunity(community)
}
else -> {
binding.clBbsContainer.goneIf(true)
binding.clGameContainer.goneIf(true)
}
}
if (scrollToCommentArea) {
scrollToCommentArea = false
binding.appBar.setExpanded(false, true)
}
}
private fun showCommunity(community: ImageArticleEntity.Community) {
binding.clBbsContainer.goneIf(false)
binding.clGameContainer.goneIf(true)
binding.ivBbsIcon.display(community.icon)
binding.tvBbsName.text = community.name
binding.tvHeat.text = "${community.hot}"
binding.clBbsContainer.setOnClickListener {
DirectUtils.directForumDetail(requireContext(), community.id, "帖子详情")
NewLogUtils.logClickNoteDetailForum(community.id, community.typeChinese)
}
binding.ctvFollowBbs.setOnClickListener {
SensorsBridge.trackFollowForumClick(
bbsId = community.id,
forumName = community.name,
bbsType = community.typeChinese,
buttonName = binding.ctvFollowBbs.text.toString(),
gameForumType = community.game?.categoryChinese ?: ""
)
viewModel.followBbs(community.id)
}
}
private fun showGame(game: GameEntity, communityId: String) {
game.exposureEvent = ExposureEvent.createEventWithSourceConcat(
game,
listOf(),
listOf(ExposureSource("其他"), ExposureSource("图文详情"))
)
binding.clBbsContainer.goneIf(true)
binding.clGameContainer.goneIf(false)
binding.ivGameIcon.displayGameIcon(game)
binding.tvGameName.text = game.name
binding.tvGameRating.goneIf(!(game.commentCount > 3 && game.star >= 7)) {
binding.tvGameRating.text = game.star.toString()
}
BindingAdapters.setGameTags(binding.gtcvGameTags, game)
setDownloadButton(game)
binding.clGameContainer.setOnClickListener {
GameDetailActivity.startGameDetailActivity(
requireContext(),
game,
"图文详情",
traceEvent = game.exposureEvent
)
}
binding.tvGoBbs.setOnClickListener {
DirectUtils.directForumDetail(requireContext(), communityId, "帖子详情")
}
}
private fun setDownloadButton(gameEntity: GameEntity?) {
if (gameEntity == null) return
DownloadItemUtils.setOnClickListener(
requireContext(), binding.btnDownload,
gameEntity, 0, null,
mEntrance,
location = "视频详情",
traceEvent = gameEntity.exposureEvent,
clickCallback = {
},
refreshCallback = { setDownloadButton(gameEntity) },
allStateClickCallback = null
)
DownloadItemUtils.updateItem(
requireContext(),
gameEntity,
GameViewHolder(binding.clGameContainer).apply {
gameDownloadBtn = binding.btnDownload
gameDownloadTips = binding.downloadTipsLottie
multiVersionDownloadTv = binding.multiVersionDownloadTv
}
)
}
private fun setBannerSize(imageInfo: ImageInfo?) {
binding.vpBanner.goneIf(imageInfo == null) {
val bannerRadio = ImageArticleEntity.getImageRadio(imageInfo, BANNER_RADIO_MIN, BANNER_RADIO_MAX)
binding.vpBanner.updateLayoutParams {
height = (resources.displayMetrics.widthPixels * bannerRadio).toInt()
}
}
}
private fun showNumberIndicator(position: Int) {
if (showIndicator) {
binding.llNumberIndicator.goneIf(false)
binding.tvIndicatorPosition.text = "${position + 1}"
handler.removeMessages(HIDE_BANNER_NUMBER_INDICATOR)
handler.sendEmptyMessageDelayed(HIDE_BANNER_NUMBER_INDICATOR, SHOW_NUMBER_INDICATOR_DURATION)
binding.dorIndicator.selectPosition(position)
}
}
private fun showMoreItemDialog() {
val imageArticle = viewModel.imageArticleDetailEntity.value ?: return
NewLogUtils.logShareEnter("图文详情页")
NewLogUtils.logClickNoteDetailMore()
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
val entities = ArrayList<MenuItemEntity>()
// 申请加精
val simplifyChoicenessStatus = imageArticle.getSimplifyChoicenessStatus()
if (imageArticle.user.id == UserManager.getInstance().userId && !imageArticle.me.isModerator && imageArticle.status == "pass") {
val isEnable =
simplifyChoicenessStatus != ArticleDetailEntity.STATUS_PASS
entities.add(
MenuItemEntity(
getString(R.string.article_detail_more_apply_select_title),
if (isEnable)
R.drawable.icon_more_panel_essence else R.drawable.icon_more_panel_essence_unenable,
isEnable = isEnable
)
)
}
// 修改
if (imageArticle.me.isContentOwner
&& imageArticle.status == ArticleDetailEntity.STATUS_PASS
) {
entities.add(
MenuItemEntity(getString(R.string.article_detail_more_edit_title), R.drawable.icon_more_panel_edit)
)
}
// 举报
if (!imageArticle.me.isContentOwner) {
entities.add(
MenuItemEntity(
getString(R.string.article_detail_more_complaint_title),
R.drawable.icon_gamedetail_copyright
)
)
}
val moderatorPermissions = imageArticle.me.moderatorPermissions
// 取消精选
if (imageArticle.me.isModerator
&& imageArticle.status == ArticleDetailEntity.STATUS_PASS
) {
if (simplifyChoicenessStatus == ArticleDetailEntity.STATUS_PASS) {
if ((moderatorPermissions.cancelChoicenessImageArticle) > Permissions.GUEST
) {
entities.add(
MenuItemEntity(
getString(R.string.article_detail_more_unselect_title),
R.drawable.icon_more_panel_essence_cancel
)
)
}
} else {
if ((moderatorPermissions.choicenessImageArticle) > Permissions.GUEST
) {
entities.add(
MenuItemEntity(
getString(R.string.article_detail_more_select_title),
R.drawable.icon_more_panel_essence
)
)
}
}
}
// 修改活动标签
if (imageArticle.me.isModerator &&
(moderatorPermissions.updateArticleActivityTag) > Permissions.GUEST &&
imageArticle.status == ArticleDetailEntity.STATUS_PASS
) {
entities.add(
MenuItemEntity(
getString(R.string.article_detail_more_edit_activity_tag_title),
R.drawable.icon_more_panel_modify_label
)
)
}
// 隐藏/删除
if (imageArticle.me.isModerator && moderatorPermissions.hideImageArticle > Permissions.GUEST
) {
entities.add(
MenuItemEntity(
getString(R.string.article_detail_more_hide_title),
R.drawable.icon_more_panel_delete
)
)
} else {
if (imageArticle.me.isContentOwner) {
entities.add(
MenuItemEntity(
getString(R.string.article_detail_more_delete_title),
R.drawable.icon_more_panel_delete
)
)
}
}
MoreFunctionPanelDialog.showMoreDialog(
requireActivity() as AppCompatActivity,
entities,
imageArticle.title.ifBlank { imageArticle.content },
getShareEntity(imageArticle),
imageArticle.status,
tag ?: ""
)
}
}
private fun getShareEntity(imageArticle: ImageArticleEntity): NormalShareEntity {
val params = AdditionalParamsEntity().apply {
contentType = "图文"
contentId = imageArticle.id
bbsId = imageArticle.communityId
bbsType = imageArticle.community?.typeChinese ?: "综合论坛"
customerType = imageArticle.user.auth?.text ?: ""
activityTagName = ""
gameForumType = imageArticle.community?.game?.categoryChinese ?: ""
refUserId = UserManager.getInstance().userId
}
return NormalShareEntity(
id = imageArticle.id,
shareUrl = requireContext().getString(
R.string.share_community_image_article_url,
imageArticle.shortId
),
shareIcon = if (imageArticle.images.isNotEmpty()) {
imageArticle.images.first()
} else {
requireContext().getString(R.string.share_ghzs_logo)
},
shareTitle = imageArticle.title.ifBlank { imageArticle.content }
.ifBlank { getString(R.string.image_article) },
shareSummary = imageArticle.content,
shareEntrance = ShareUtils.ShareEntrance.communityArticle,
additionalParams = params
)
}
override fun onResume() {
super.onResume()
DownloadManager.getInstance().addObserver(dataWatcher)
}
override fun onPause() {
super.onPause()
DownloadManager.getInstance().removeObserver(dataWatcher)
}
//下载被删除事件
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(status: EBDownloadStatus) {
if ("delete" == status.status) {
setDownloadButton(viewModel.imageArticleDetailEntity.value?.community?.game)
}
}
//安装、卸载事件
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(busFour: EBPackage) {
setDownloadButton(viewModel.imageArticleDetailEntity.value?.community?.game)
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(reuse: EBReuse) {
if (reuse.type == Constants.LOGIN_TAG) { // 登入
viewModel.loadImageArticleDetail(imageArticleId, recommendId)
}
val path = Path()
path.reset()
}
//
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(changed: EBImageArticleChanged) {
viewModel.imageArticleChanged(changed)
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
setDownloadButton(viewModel.imageArticleDetailEntity.value?.community?.game)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
when {
requestCode == CommentActivity.REQUEST_CODE && resultCode == Activity.RESULT_OK -> {
val commentCount = data?.getIntExtra(CommentActivity.COMMENT_COUNT, 0) ?: 0
val commentId = data?.getStringExtra(EntranceConsts.KEY_COMMENT_ID) ?: ""
viewModel.updateComment(commentId, commentCount)
}
requestCode == MoreFunctionPanelDialog.REQUEST_CODE && resultCode == Activity.RESULT_OK -> {
getItemClickCallback().invoke(data?.getParcelableExtra(EntranceConsts.KEY_DATA))
}
}
}
private fun getItemClickCallback(): (menuItem: MenuItemEntity?) -> Unit {
return {
when (it?.text) {
getString(R.string.article_detail_more_edit_title) -> {
val uri = Uri.Builder()
.path(RouteConsts.activity.publishImageArticleActivity)
.appendQueryParameter(EntranceConsts.KEY_ENTRANCE, "图文详情")
.build()
ARouter.getInstance().build(uri)
.withParcelable(
EntranceConsts.KEY_IMAGE_ARTICLE_ENTITY,
viewModel.imageArticleDetailEntity.value
)
.navigation()
NewLogUtils.logSharePanelClick(
"click_modification",
viewModel.imageArticleDetailEntity.value?.user?.id ?: "",
"图文",
imageArticleId,
viewModel.imageArticleDetailEntity.value?.community?.id ?: "",
viewModel.imageArticleDetailEntity.value?.community?.typeChinese ?: ""
)
}
getString(R.string.article_detail_more_complaint_title) -> {
ifLogin("图文详情") {
BbsReportHelper.showReportDialog(BbsReportHelper.ImageArticleReporter(imageArticleId))
}
NewLogUtils.logSharePanelClick(
"click_report",
viewModel.imageArticleDetailEntity.value?.user?.id ?: "",
"图文",
imageArticleId,
viewModel.imageArticleDetailEntity.value?.community?.id ?: "",
viewModel.imageArticleDetailEntity.value?.community?.typeChinese ?: ""
)
}
getString(R.string.article_detail_more_apply_select_title) -> {
if (viewModel.imageArticleDetailEntity.value?.getSimplifyChoicenessStatus() == "apply") {
ToastUtils.showToast("申请加精审核中")
} else {
viewModel.applyHighlightForImageArticle()
NewLogUtils.logSharePanelClick(
"click_apply_essence",
viewModel.imageArticleDetailEntity.value?.user?.id ?: "",
"图文",
imageArticleId,
viewModel.imageArticleDetailEntity.value?.community?.id ?: "",
viewModel.imageArticleDetailEntity.value?.community?.typeChinese ?: ""
)
}
}
getString(R.string.article_detail_more_select_title) -> {
if (viewModel.imageArticleDetailEntity.value?.getSimplifyChoicenessStatus() == "apply") {
ToastUtils.showToast("加精审核中")
} else {
showHighlightDialog(true)
NewLogUtils.logSharePanelClick(
"click_essence",
viewModel.imageArticleDetailEntity.value?.user?.id ?: "",
"图文",
imageArticleId,
viewModel.imageArticleDetailEntity.value?.community?.id ?: "",
viewModel.imageArticleDetailEntity.value?.community?.typeChinese ?: ""
)
}
}
getString(R.string.article_detail_more_unselect_title) -> {
showHighlightDialog(false)
}
getString(R.string.article_detail_more_delete_title),
getString(R.string.article_detail_more_hide_title) -> {
DialogHelper.showDialog(
requireContext(),
"提示",
"${it.text}帖子后,其中的所有评论及回复都将被${it.text}",
it.text,
"取消",
confirmClickCallback = {
viewModel.deleteOrHideImageArticle()
}, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)
)
NewLogUtils.logSharePanelClick(
"click_delete",
viewModel.imageArticleDetailEntity.value?.user?.id ?: "",
"图文",
imageArticleId,
viewModel.imageArticleDetailEntity.value?.community?.id ?: "",
viewModel.imageArticleDetailEntity.value?.community?.typeChinese ?: ""
)
}
getString(R.string.article_detail_more_top_title) -> {
// TopCommunityCategoryDialog.show(
// childFragmentManager
// ) { category ->
// mViewModel.topCommunityArticle(category.id)
// }
}
getString(R.string.article_detail_more_cancel_top_title) -> {
DialogHelper.showDialog(
requireContext(),
getString(R.string.article_detail_cancel_top_dialog_title),
getString(R.string.article_detail_cancel_top_dialog_hint),
getString(R.string.article_detail_cancel_top_dialog_confirm),
getString(R.string.article_detail_cancel_top_dialog_cancel),
confirmClickCallback = {
// mViewModel.cancelTopCommunityArticle()
},
extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)
)
}
}
}
}
private fun showHighlightDialog(isHighlight: Boolean) {
val imageArticle = viewModel.imageArticleDetailEntity.value ?: return
var highlightDialogHintContent = ""
val permissions = imageArticle.me.moderatorPermissions
if ((isHighlight && permissions.choicenessImageArticle > Permissions.GUEST) ||
(!isHighlight && permissions.cancelChoicenessImageArticle > Permissions.GUEST)
) {
highlightDialogHintContent =
if ((isHighlight && permissions.choicenessImageArticle == Permissions.REPORTER) ||
(!isHighlight && permissions.cancelChoicenessImageArticle == Permissions.REPORTER)
) {
"你的操作将提交给小编审核,确定提交吗?"
} else {
"你的操作将立即生效,确定提交吗?"
}
}
val title = if (isHighlight) "加精帖子" else "取消精选"
DialogHelper.showDialog(
requireContext(),
title,
highlightDialogHintContent,
"确定",
"取消",
{
viewModel.addHighlight(isHighlight)
}, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)
)
}
companion object {
private const val HIDE_BANNER_NUMBER_INDICATOR = 1
private const val SHOW_NUMBER_INDICATOR_DURATION = 5000L
private const val SHOW_DOT_INDICATOR_MIN = 2
private const val BANNER_RADIO_MIN = 0.5F
private const val BANNER_RADIO_MAX = 1.33F
}
}

View File

@ -0,0 +1,166 @@
package com.gh.gamecenter.forum.home.recommend.fragment
import android.graphics.Rect
import android.net.Uri
import android.os.Bundle
import android.view.View
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.alibaba.android.arouter.launcher.ARouter
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.LazyFragment
import com.gh.gamecenter.common.baselist.PageLoader
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.utils.DialogHelper
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toResString
import com.gh.gamecenter.databinding.FragmentImageArticleDraftBinding
import com.gh.gamecenter.eventbus.EBImageArticleChanged
import com.gh.gamecenter.forum.home.recommend.adapter.ImageArticleDraftAdapter
import com.gh.gamecenter.forum.home.recommend.viewmodel.ImageArticleDraftViewModel
import com.gh.gamecenter.forum.home.recommend.viewmodel.PublishImageArticleActivityViewModel
import com.gh.gamecenter.livedata.EventObserver
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class ImageArticleDraftFragment : LazyFragment() {
private val viewModel by viewModels<ImageArticleDraftViewModel>()
private val activityViewModel by activityViewModels<PublishImageArticleActivityViewModel>()
private lateinit var binding: FragmentImageArticleDraftBinding
private var isFromPublishPage = false
private val adapter by lazy {
ImageArticleDraftAdapter(requireContext(), viewModel)
}
override fun getRealLayoutId(): Int {
return R.layout.fragment_image_article_draft
}
override fun onRealLayoutInflated(inflatedView: View) {
binding = FragmentImageArticleDraftBinding.bind(inflatedView)
}
override fun initRealView() {
super.initRealView()
isFromPublishPage = arguments?.getBoolean(KEY_IS_FROM_PUBLISH_PAGE) ?: false
binding.rvDrafts.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
binding.rvDrafts.addItemDecoration(MyItemDecoration())
binding.rvDrafts.adapter = adapter
binding.srlRefresh.setOnRefreshListener {
viewModel.loadDraft(true)
}
with(viewModel) {
pageState.observe(viewLifecycleOwner) {
binding.srlRefresh.goneIf(
it == PageLoader.PageState.PageNoData || it == PageLoader.PageState.PageInitFailure
) {
binding.srlRefresh.isRefreshing = false
}
binding.reuseLlLoading.root.goneIf(it != PageLoader.PageState.PageInitLoading)
binding.reuseNoneData.root.goneIf(it != PageLoader.PageState.PageNoData)
binding.reuseDataException.root.goneIf(it != PageLoader.PageState.PageInitFailure)
}
drafts.observe(viewLifecycleOwner) {
adapter.submitList(it)
}
showDeleteDraftDialogAction.observe(viewLifecycleOwner, EventObserver {
DialogHelper.showDialog(
requireContext(),
R.string.warning.toResString(),
R.string.delete_image_article_warning.toResString(),
com.gh.gamecenter.core.R.string.confirm.toResString(),
com.gh.gamecenter.common.R.string.cancel.toResString(),
confirmClickCallback = {
viewModel.deleteDraft(it)
}, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)
)
})
publishImageArticleDestination.observe(viewLifecycleOwner, EventObserver {
it.isDraft = true
if (isFromPublishPage) {
// 如果是从发布页进入草稿箱,则回到发布页,并将草稿数据回传
activityViewModel.setEditImageArticle(it)
activity?.onBackPressedDispatcher?.onBackPressed()
} else {
// 否则,直接打开新的发布页
val uri = Uri.Builder()
.path(RouteConsts.activity.publishImageArticleActivity)
.appendQueryParameter(EntranceConsts.KEY_ENTRANCE, "图文草稿")
.build()
ARouter.getInstance().build(uri)
.withParcelable(EntranceConsts.KEY_IMAGE_ARTICLE_ENTITY, it)
.navigation()
}
})
loadDraft(false)
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(changed: EBImageArticleChanged) {
if (changed is EBImageArticleChanged.DataCreated
&& changed.draftId.isNotBlank()
) {
// 如果时选择的草稿发布,则删除当前选中草稿
val draftList = viewModel.drafts.value?.toMutableList()
if (draftList != null) {
val hasRemoved = draftList.removeAll { it.id == changed.draftId }
if (hasRemoved) {
if (draftList.isEmpty()) {
viewModel.loadDraft(true)
} else {
adapter.submitList(null)
adapter.notifyDataSetChanged()
viewModel.updateDrafts(draftList)
}
}
}
}
}
private class MyItemDecoration : RecyclerView.ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
super.getItemOffsets(outRect, view, parent, state)
val layoutParams = view.layoutParams
if (layoutParams is StaggeredGridLayoutManager.LayoutParams) {
val spanIndex = layoutParams.spanIndex
val (left, right) = if (spanIndex == 0) {
8F.dip2px() to 2F.dip2px()
} else {
2F.dip2px() to 8F.dip2px()
}
outRect.left = left
outRect.right = right
}
}
}
companion object {
private const val KEY_IS_FROM_PUBLISH_PAGE = "key_is_from_publish_page"
fun newInstance(isFromPublishPage: Boolean) =
ImageArticleDraftFragment().apply {
arguments = Bundle().apply {
putBoolean(KEY_IS_FROM_PUBLISH_PAGE, isFromPublishPage)
}
}
}
}

View File

@ -0,0 +1,282 @@
package com.gh.gamecenter.forum.home.recommend.fragment
import android.graphics.Rect
import android.net.Uri
import android.os.Handler
import android.os.Looper
import android.view.View
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.OnScrollListener
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.alibaba.android.arouter.launcher.ARouter
import com.ethanhua.skeleton.Skeleton
import com.gh.common.exposure.ExposureListener
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.LazyFragment
import com.gh.gamecenter.common.baselist.PageLoader
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.tryToClearRecycler
import com.gh.gamecenter.databinding.FragmentImageArticleHomeBinding
import com.gh.gamecenter.eventbus.EBImageArticleChanged
import com.gh.gamecenter.forum.home.recommend.ImageArticleFooterWrapperAdapter
import com.gh.gamecenter.forum.home.recommend.adapter.RecommendHomeAdapter
import com.gh.gamecenter.forum.home.recommend.viewmodel.ImageArticleHomeViewModel
import com.gh.gamecenter.livedata.EventObserver
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class ImageArticleHomeFragment : LazyFragment() {
private val viewModel: ImageArticleHomeViewModel by viewModels()
private lateinit var binding: FragmentImageArticleHomeBinding
private val handler = Handler(Looper.getMainLooper())
private var isFirstVisible = true
private val skeletonScreen by lazy {
Skeleton.bind(binding.flSkeleton)
.shimmer(true)
.angle(Constants.SHIMMER_ANGLE)
.color(com.gh.gamecenter.common.R.color.ui_skeleton_highlight)
.duration(Constants.SHIMMER_DURATION)
.maskWidth(Constants.MASK_WIDTH)
.gradientCenterColorWidth(Constants.GRADIENT_CENTER_COLOR_WIDTH)
.load(R.layout.fragment_image_article_home_skeleton)
.show()
}
private val adapter by lazy {
RecommendHomeAdapter(requireContext(), viewModel)
}
override fun getRealLayoutId(): Int = R.layout.fragment_image_article_home
override fun onRealLayoutInflated(inflatedView: View) {
binding = FragmentImageArticleHomeBinding.bind(inflatedView)
}
override fun onFragmentResume() {
super.onFragmentResume()
NewLogUtils.logRecommendFeedEvent("view_for_you_feed")
}
override fun initRealView() {
super.initRealView()
isFirstVisible = false
binding.rvRecommends.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL).also {
it.gapStrategy = StaggeredGridLayoutManager.GAP_HANDLING_NONE
}
binding.rvRecommends.adapter = adapter
binding.rvRecommends.addItemDecoration(MyItemDecoration())
binding.srlRefresh.setOnRefreshListener {
viewModel.pageLoader.pullToRefresh()
}
binding.reuseNoConnection.connectionReloadTv.setOnClickListener {
viewModel.initLoad()
}
val exposureListener = ExposureListener(this, adapter)
binding.rvRecommends.addOnScrollListener(exposureListener)
binding.rvRecommends.addOnScrollListener(object : OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
if (newState == RecyclerView.SCROLL_STATE_IDLE) {
val layoutManager = binding.rvRecommends.layoutManager
if (layoutManager is StaggeredGridLayoutManager) {
val itemPositions = layoutManager.findLastCompletelyVisibleItemPositions(null)
val lastCompletePosition = itemPositions.maxOrNull() ?: -1
val position = if (lastCompletePosition != -1) {
lastCompletePosition
} else {
val itemVisible = layoutManager.findLastVisibleItemPositions(null)
itemVisible.maxOrNull() ?: 0
}
NewLogUtils.logRecommendFeedSlide(position + 1)
}
}
}
})
with(viewModel) {
pageLoader.pageState.observe(viewLifecycleOwner) {
if (it == PageLoader.PageState.PageInitLoading) {
skeletonScreen
} else {
skeletonScreen.hide()
}
if (it is PageLoader.PageState.PagePullToRefreshLoading) {
// 正在下拉刷新,页面保持不变
binding.srlRefresh.isRefreshing = true
} else {
binding.reuseNoneData.root.goneIf(it != PageLoader.PageState.PageNoData)
binding.reuseNoConnection.root.goneIf(it != PageLoader.PageState.PageInitFailure)
binding.srlRefresh.isEnabled = it != PageLoader.PageState.PageInitLoading
&& it != PageLoader.PageState.PageInitFailure
binding.srlRefresh.isRefreshing = false
}
if (it is PageLoader.PageState.PageLoadMoreReady
&& (it.previousState is PageLoader.PageState.PagePullToRefreshLoading
|| it.previousState is PageLoader.PageState.PageInitLoading)
) {
// 加载第一页完成
if (it.previousState is PageLoader.PageState.PagePullToRefreshLoading) {
// 下拉刷新成功,先清空上一次内容
adapter.submitList(null)
adapter.notifyDataSetChanged()
}
// 延时一下,检查第一页数据是否已铺满第一页,如果未铺满,则直接请求下一页
handler.removeCallbacksAndMessages(null)
handler.postDelayed({
checkIfLoadNextPage(binding.rvRecommends, viewModel::loadMore)
}, 16)
}
adapter.setPageState(it)
}
pageLoader.dataList.observe(viewLifecycleOwner) {
adapter.submitList(it)
}
imageDetailDestination.observe(viewLifecycleOwner, EventObserver {
val imageArticleUri = Uri.Builder()
.path(RouteConsts.activity.imageArticleDetailActivity)
.appendQueryParameter(EntranceConsts.KEY_IMAGE_ARTICLE_ID, it.id)
.appendQueryParameter(EntranceConsts.KEY_SOURCE_ENTRANCE, "社区-推荐信息流")
.appendQueryParameter(EntranceConsts.KEY_RECOMMEND_ID, it.recommendId)
.build()
ARouter.getInstance().build(imageArticleUri).navigation()
})
initLoad()
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(changed: EBImageArticleChanged) {
if (isFirstVisible) {
return
}
when (changed) {
is EBImageArticleChanged.VoteChanged -> {
viewModel.voteChanged(changed)
}
is EBImageArticleChanged.DataChanged -> {
viewModel.itemChanged(changed)
}
is EBImageArticleChanged.DataDeleted -> {
itemDeleted(changed)
}
is EBImageArticleChanged.DataCreated -> {
itemCreated(changed)
}
else -> Unit
}
}
private fun itemCreated(changed: EBImageArticleChanged.DataCreated) {
val newData = viewModel.pageLoader.dataList.value?.toMutableList() ?: mutableListOf()
if (newData.isEmpty()) {
viewModel.initLoad()
} else {
// 瀑布流布局在中间添加或者移除item时会出现错位的现象这里需要先将原来的数据清空在重新添加可以避免这个问题
adapter.submitList(null)
newData.add(0, changed.imageArticle)
viewModel.pageLoader.updateData(newData)
}
}
private fun itemDeleted(changed: EBImageArticleChanged.DataDeleted) {
val oldData = viewModel.pageLoader.dataList.value ?: return
val newData = oldData.toMutableList()
val hasRemoved = newData.removeAll {
it.id == changed.imageArticleId
}
if (hasRemoved) {
if (newData.isEmpty()) {
viewModel.initLoad()
} else {
adapter.submitList(null)
viewModel.pageLoader.updateData(newData)
}
}
}
override fun onDestroyView() {
super.onDestroyView()
isFirstVisible = true
handler.removeCallbacksAndMessages(null)
}
override fun onDarkModeChanged() {
super.onDarkModeChanged()
if (::binding.isInitialized) {
binding.rvRecommends.tryToClearRecycler()
binding.rvRecommends.recycledViewPool.clear()
adapter.onDarkModeChanged()
}
}
companion object {
private const val KEY_IS_IN_MY_PUBLISH = "key_is_in_my_publish"
fun newInstance() = ImageArticleHomeFragment()
fun checkIfLoadNextPage(recyclerView: RecyclerView, loadNext: () -> Unit) {
val layoutManager = recyclerView.layoutManager
if (layoutManager is StaggeredGridLayoutManager) {
val itemPositions = layoutManager.findLastVisibleItemPositions(null)
val lastPosition = itemPositions.maxOrNull() ?: 0
val itemType = recyclerView.adapter?.getItemViewType(lastPosition) ?: 0
if (itemType == ImageArticleFooterWrapperAdapter.ITEM_TYPE_FOOTER) {
// 底部加载更多的itemView显示在屏幕内说明数据未铺满一屏:直接加载下一页
loadNext()
}
}
}
}
class MyItemDecoration : RecyclerView.ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
val layoutParams = view.layoutParams
if (layoutParams is StaggeredGridLayoutManager.LayoutParams) {
if (parent.getChildAdapterPosition(view) == state.itemCount - 1) {
layoutParams.isFullSpan = true
} else {
val spanIndex = layoutParams.spanIndex
val (left, right) = if (spanIndex % 2 == 0) {
8F.dip2px() to 2F.dip2px()
} else {
2F.dip2px() to 8F.dip2px()
}
outRect.left = left
outRect.right = right
}
}
}
}
}

View File

@ -0,0 +1,216 @@
package com.gh.gamecenter.forum.home.recommend.fragment
import android.annotation.SuppressLint
import android.graphics.Rect
import android.net.Uri
import android.os.Handler
import android.os.Looper
import android.view.View
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.alibaba.android.arouter.launcher.ARouter
import com.ethanhua.skeleton.Skeleton
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.LazyFragment
import com.gh.gamecenter.common.baselist.PageLoader
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.databinding.FragmentImageArticleHomeBinding
import com.gh.gamecenter.eventbus.EBImageArticleChanged
import com.gh.gamecenter.forum.home.recommend.adapter.MyImageArticleAdapter
import com.gh.gamecenter.forum.home.recommend.fragment.ImageArticleHomeFragment.Companion.checkIfLoadNextPage
import com.gh.gamecenter.forum.home.recommend.viewmodel.MyImageArticleListViewModel
import com.gh.gamecenter.livedata.EventObserver
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class MyImageArticleListFragment : LazyFragment() {
private val handler = Handler(Looper.getMainLooper())
private val viewModel by viewModels<MyImageArticleListViewModel>()
private lateinit var binding: FragmentImageArticleHomeBinding
private val skeletonScreen by lazy {
Skeleton.bind(binding.flSkeleton)
.shimmer(true)
.angle(Constants.SHIMMER_ANGLE)
.color(com.gh.gamecenter.common.R.color.ui_skeleton_highlight)
.duration(Constants.SHIMMER_DURATION)
.maskWidth(Constants.MASK_WIDTH)
.gradientCenterColorWidth(Constants.GRADIENT_CENTER_COLOR_WIDTH)
.load(R.layout.fragment_image_article_home_skeleton)
.show()
}
private val adapter by lazy {
MyImageArticleAdapter(requireContext(), viewModel)
}
override fun getRealLayoutId(): Int = R.layout.fragment_image_article_home
override fun onRealLayoutInflated(inflatedView: View) {
binding = FragmentImageArticleHomeBinding.bind(inflatedView)
}
@SuppressLint("NotifyDataSetChanged")
override fun initRealView() {
super.initRealView()
binding.rvRecommends.layoutManager = StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL)
binding.rvRecommends.adapter = adapter
binding.rvRecommends.addItemDecoration(MyItemDecoration())
binding.srlRefresh.setOnRefreshListener {
viewModel.pageLoader.pullToRefresh()
}
binding.reuseNoConnection.connectionReloadTv.setOnClickListener {
viewModel.initLoad()
}
with(viewModel) {
pageLoader.pageState.observe(viewLifecycleOwner) {
if (it == PageLoader.PageState.PageInitLoading) {
skeletonScreen
} else {
skeletonScreen.hide()
}
if (it is PageLoader.PageState.PagePullToRefreshLoading) {
// 正在下拉刷新,页面保持不变
binding.srlRefresh.isRefreshing = true
} else {
binding.reuseNoneData.root.goneIf(it != PageLoader.PageState.PageNoData)
binding.reuseNoConnection.root.goneIf(it != PageLoader.PageState.PageInitFailure)
binding.srlRefresh.isEnabled = it != PageLoader.PageState.PageInitLoading
&& it != PageLoader.PageState.PageInitFailure
binding.srlRefresh.isRefreshing = false
}
if (it is PageLoader.PageState.PageLoadMoreReady
&& (it.previousState is PageLoader.PageState.PagePullToRefreshLoading
|| it.previousState is PageLoader.PageState.PageInitLoading)
) {
if (it.previousState is PageLoader.PageState.PagePullToRefreshLoading) {
adapter.submitList(null)
adapter.notifyDataSetChanged()
}
// 延时一下,检查第一页数据是否已铺满第一页,如果未铺满,则直接请求下一页
handler.removeCallbacksAndMessages(null)
handler.postDelayed({
checkIfLoadNextPage(binding.rvRecommends, viewModel::loadMore)
}, 16)
}
adapter.setPageState(it)
}
pageLoader.dataList.observe(viewLifecycleOwner) {
adapter.submitList(it)
}
imageArticleDetailDestination.observe(viewLifecycleOwner, EventObserver {
val imageArticleUri = Uri.Builder()
.path(RouteConsts.activity.imageArticleDetailActivity)
.appendQueryParameter(EntranceConsts.KEY_IMAGE_ARTICLE_ID, it)
.build()
ARouter.getInstance().build(imageArticleUri).navigation()
})
initLoad()
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(changed: EBImageArticleChanged) {
when (changed) {
is EBImageArticleChanged.VoteChanged -> {
viewModel.voteChanged(changed)
}
is EBImageArticleChanged.DataChanged -> {
viewModel.itemChanged(changed)
}
is EBImageArticleChanged.DataDeleted -> {
itemDeleted(changed)
}
is EBImageArticleChanged.DataCreated -> {
itemCreated(changed)
}
else -> Unit
}
}
@SuppressLint("NotifyDataSetChanged")
private fun itemCreated(changed: EBImageArticleChanged.DataCreated) {
val newData = viewModel.pageLoader.dataList.value?.toMutableList() ?: mutableListOf()
if (newData.isEmpty()) {
viewModel.initLoad()
} else {
adapter.submitList(null)
// 请注意:这一行代码不能省
adapter.notifyDataSetChanged()
newData.add(0, changed.imageArticle.toPersonHistoryEntity())
viewModel.pageLoader.updateData(newData)
}
}
@SuppressLint("NotifyDataSetChanged")
private fun itemDeleted(changed: EBImageArticleChanged.DataDeleted) {
val oldData = viewModel.pageLoader.dataList.value ?: return
val newData = oldData.toMutableList()
val hasRemoved = newData.removeAll {
it.id == changed.imageArticleId
}
if (hasRemoved) {
adapter.submitList(null)
adapter.notifyDataSetChanged()
if (newData.isEmpty()) {
viewModel.initLoad()
} else {
viewModel.pageLoader.updateData(newData)
}
}
}
override fun onDestroyView() {
super.onDestroyView()
handler.removeCallbacksAndMessages(null)
}
companion object {
fun newInstance() = MyImageArticleListFragment()
}
private class MyItemDecoration : RecyclerView.ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
val layoutParams = view.layoutParams
if (layoutParams is StaggeredGridLayoutManager.LayoutParams) {
if (parent.getChildAdapterPosition(view) == state.itemCount - 1) {
layoutParams.isFullSpan = true
} else {
val spanIndex = layoutParams.spanIndex
val (left, right) = if (spanIndex % 2 == 0) {
8F.dip2px() to 2F.dip2px()
} else {
2F.dip2px() to 8F.dip2px()
}
outRect.left = left
outRect.right = right
}
}
}
}
}

View File

@ -0,0 +1,525 @@
package com.gh.gamecenter.forum.home.recommend.fragment
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.Rect
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.text.Editable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewTreeObserver.OnGlobalLayoutListener
import android.widget.EditText
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContract
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.updateLayoutParams
import androidx.core.widget.addTextChangedListener
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_IDLE
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.util.ErrorHelper
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.BaseFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.entity.CommunityEntity
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.core.utils.ToastUtils
import com.gh.gamecenter.databinding.FragmentPublishImageArticleBinding
import com.gh.gamecenter.entity.ForumDetailEntity
import com.gh.gamecenter.entity.ImageArticleEntity
import com.gh.gamecenter.forum.home.recommend.adapter.ImageAndTextAdapter
import com.gh.gamecenter.forum.home.recommend.viewmodel.PublishImageArticleActivityViewModel
import com.gh.gamecenter.forum.home.recommend.viewmodel.PublishImageArticleViewModel
import com.gh.gamecenter.livedata.EventObserver
import com.gh.gamecenter.qa.dialog.ChooseForumActivity
import com.gh.gamecenter.qa.dialog.ChooseSectionDialogFragment
import com.lightgame.listeners.OnBackPressedListener
import com.lightgame.utils.Util_System_Keyboard
class PublishImageArticleFragment : BaseFragment<Unit>(), OnBackPressedListener {
private val activityViewModel by activityViewModels<PublishImageArticleActivityViewModel>()
private val viewModel by viewModels<PublishImageArticleViewModel>()
private lateinit var binding: FragmentPublishImageArticleBinding
private val rootView: View?
get() = activity?.findViewById(android.R.id.content)
private val itemTouchHelper by lazy {
ItemTouchHelper(MyItemTouchCallback(activityViewModel))
}
private val adapter by lazy {
ImageAndTextAdapter(viewModel) {
itemTouchHelper.startDrag(it)
}
}
private val savingDraftLoading by lazy {
DialogHelper.getProgressDialog(requireContext(), R.string.draft_saving.toResString())
}
private val publishingLoading by lazy {
DialogHelper.getProgressDialog(requireContext(), R.string.image_article_publishing.toResString())
}
override fun getInflatedLayout(): View {
return View(requireContext())
}
private val onGlobalLayoutListener = OnGlobalLayoutListener {
onKeyboardChanged()
}
private val handler = Handler(Looper.getMainLooper())
private var isKeyBoardShow = false
private lateinit var chooseForumLauncher: ActivityResultLauncher<String>
override fun getLayoutId(): Int = 0
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
chooseForumLauncher = registerForActivityResult<String, CommunityEntity?>(object :
ActivityResultContract<String, CommunityEntity?>() {
override fun createIntent(context: Context, input: String): Intent {
val intent = Intent(context, ChooseForumActivity::class.java)
intent.putExtra(EntranceConsts.KEY_SOURCE_ENTRANCE, input)
return intent
}
override fun parseResult(resultCode: Int, intent: Intent?): CommunityEntity? {
return intent?.getParcelableExtra(EntranceConsts.KEY_COMMUNITY_DATA)
}
}
) { result -> result?.let(viewModel::changeLinkBbs) }
activityViewModel.editImageArticle.observe(this, EventObserver {
viewModel.setEditImageArticle(it)
})
activityViewModel.community.observe(this, EventObserver {
viewModel.changeLinkBbs(it)
})
}
override fun initView(view: View?) = Unit
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return FragmentPublishImageArticleBinding.inflate(inflater, container, false)
.also {
binding = it
}.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
view.post {
setContentHeight(0)
}
binding.rvImages.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
binding.rvImages.addItemDecoration(MyItemDecoration())
binding.rvImages.adapter = adapter
itemTouchHelper.attachToRecyclerView(binding.rvImages)
binding.etTitle.addTextChangedListener { text ->
if (text != null) {
checkMaxLimitReached(binding.etTitle, text, TITLE_MAX_LENGTH, R.string.image_and_text_title_limit)
var remainingSize = TITLE_MAX_LENGTH - text.length
if (remainingSize < 0) {
remainingSize = 0
}
binding.tvRemainingWords.text = "$remainingSize"
}
binding.ivClear.visibleIf(!text.isNullOrBlank() && binding.etTitle.hasFocus())
}
binding.etTitle.setOnFocusChangeListener { _, hasFocus ->
binding.ivClear.visibleIf(hasFocus && binding.etTitle.text.isNotBlank())
binding.tvRemainingWords.visibleIf(hasFocus)
}
binding.tvSelectBbs.setOnClickListener {
showSelectBbsDialog()
}
binding.ivClear.setOnClickListener {
binding.etTitle.text = null
}
binding.vCloseDragTips.setOnClickListener {
handler.removeCallbacksAndMessages(null)
binding.gDragTips.goneIf(true)
}
binding.etContent.addTextChangedListener { text ->
if (text != null) {
checkMaxLimitReached(binding.etContent, text, CONTENT_MAX_LENGTH, R.string.image_and_text_content_limit)
}
}
binding.tvBbsName.setOnClickListener {
showSelectBbsDialog()
}
binding.tvSubSection.setOnClickListener {
showSelectSubSectionDialog()
}
binding.vClearBbs.setOnClickListener {
if (viewModel.isCreating) {
viewModel.changeLinkBbs(null)
} else {
viewModel.changeLinkSection(null)
toast(R.string.cannot_be_modified_after_publishing)
}
}
binding.tvDraftBox.setOnClickListener {
activityViewModel.navigateToDraftBox()
}
binding.tvPublish.setOnClickListener {
publish()
}
activityViewModel.localImages.observe(viewLifecycleOwner) {
adapter.submitList(it)
}
activityViewModel.showDragTipsAction.observe(viewLifecycleOwner, EventObserver {
// 倒计时开始5s 后消失
binding.gDragTips.visibleIf(true)
handler.removeCallbacksAndMessages(null)
handler.postDelayed({
binding.gDragTips.goneIf(true)
}, DRAG_TIPS_SHOW_DURATION)
})
with(viewModel) {
chooseImageAction.observe(viewLifecycleOwner, EventObserver {
activityViewModel.chooseImage()
})
removeImageAction.observe(viewLifecycleOwner, EventObserver {
activityViewModel.removeImage(it)
})
linkBbs.observe(viewLifecycleOwner) {
updateLinkBbs(it)
}
draftCreated.observe(viewLifecycleOwner, EventObserver {
if (savingDraftLoading.isShowing) {
savingDraftLoading.dismiss()
}
val toastText = if (it) {
R.string.save_draft_successfully.toResString()
} else {
R.string.save_draft_failure.toResString()
}
ToastUtils.showToast(toastText)
if (it) {
activity?.finish()
}
})
imageTextPublished.observe(viewLifecycleOwner, EventObserver { (success, errorString) ->
if (publishingLoading.isShowing) {
publishingLoading.dismiss()
}
if (success) {
ToastUtils.showToast(R.string.publish_image_article_successfully.toResString())
activity?.setResult(Activity.RESULT_OK)
activity?.finish()
} else {
ErrorHelper.handleError(requireContext(), errorString, false, "发布图文", "社区实名", "图文") {
publish()
}
}
})
editImageArticle.observe(viewLifecycleOwner, EventObserver {
fillUiByDraft(it)
})
}
}
private fun fillUiByDraft(imageArticle: ImageArticleEntity) {
binding.etTitle.setText(imageArticle.title)
binding.etContent.setText(imageArticle.content)
}
/**
* 设置内容输入框的高度
*/
private fun setContentHeight(keyboardHeight: Int) {
val rootHeight = binding.root.height
val remainHeight =
if (keyboardHeight > 0) {
rootHeight - 116F.dip2px() - 50F.dip2px() - 50F.dip2px() - keyboardHeight
} else {
rootHeight - 116F.dip2px() - 50F.dip2px() - 60F.dip2px() - 50F.dip2px()
}
val contentHeight = if (remainHeight > 360F.dip2px()) {
360F.dip2px()
} else {
remainHeight
}
binding.etContent.updateLayoutParams {
height = contentHeight
}
}
private fun createDraft() {
if (!savingDraftLoading.isShowing) {
savingDraftLoading.show()
}
val title = binding.etTitle.text?.toString() ?: ""
val content = binding.etContent.text?.toString() ?: ""
val images = activityViewModel.localImages.value ?: emptyList()
viewModel.createOrEditDraft(title, content, images)
}
private fun publish() {
if (!publishingLoading.isShowing) {
publishingLoading.show()
}
val title = binding.etTitle.text?.toString() ?: ""
val content = binding.etContent.text?.toString() ?: ""
val images = activityViewModel.localImages.value ?: emptyList()
viewModel.publishImageText(title, content, images)
}
private fun updateLinkBbs(linkBbs: PublishImageArticleViewModel.LinkBbs) {
if (linkBbs.communityEntity == null) {
// 还没有关联论坛
binding.tvSelectBbs.visibleIf(true)
binding.gBbsSelected.visibleIf(false)
} else {
// 已关联论坛
binding.tvSelectBbs.visibleIf(false)
binding.gBbsSelected.visibleIf(true)
binding.ivBbsIcon.displayGameIcon(linkBbs.communityEntity.icon, null, null)
binding.tvBbsName.text = linkBbs.communityEntity.name
if (linkBbs.selectedSection == null) {
// 还没有选择子模块
if (linkBbs.hasSection) {
// 有子模块
binding.tvSubSection.visibleIf(true)
binding.ivBbsArrRight.visibleIf(true)
binding.tvSubSection.text = ""
} else {
// 没有子模块
binding.tvSubSection.visibleIf(false)
binding.ivBbsArrRight.visibleIf(false)
}
} else {
binding.tvSubSection.text = linkBbs.selectedSection.name
}
}
}
private fun checkMaxLimitReached(editText: EditText, text: Editable, maxLength: Int, toastResId: Int) {
if (text.length > maxLength) {
editText.setText(text.subSequence(0, maxLength))
editText.setSelection(maxLength) // 将光标移动到文本末尾
ToastUtils.showToast(toastResId.toResString())
}
}
private fun onKeyboardChanged() {
val rect = Rect()
rootView?.let {
it.getWindowVisibleDisplayFrame(rect)
val screenHeight = it.height
val keyboardHeight = screenHeight - rect.bottom
if (keyboardHeight > screenHeight * 0.15) {
// 键盘弹起
if (!isKeyBoardShow) {
isKeyBoardShow = true
// 当软键盘弹起时必须要保证EditText的光标和内容不被挡住
setContentHeight(keyboardHeight)
}
} else {
// 键盘隐藏
if (isKeyBoardShow) {
isKeyBoardShow = false
setContentHeight(0)
}
}
}
}
private fun showSelectBbsDialog() {
if (viewModel.isCreating) {
val delayTime = if (isKeyBoardShow) {
Util_System_Keyboard.hideSoftKeyboard(requireActivity())
200L
} else 0L
AppExecutor.uiExecutor.executeWithDelay({
chooseForumLauncher.launch("社区")
NewLogUtils.logChooseForumPanelEnter("发图文")
}, delayTime)
} else {
toast(R.string.cannot_be_modified_after_publishing)
}
}
private fun showSelectSubSectionDialog() {
val delayTime = if (isKeyBoardShow) {
Util_System_Keyboard.hideSoftKeyboard(requireActivity())
200L
} else 0L
AppExecutor.uiExecutor.executeWithDelay({
ChooseSectionDialogFragment.show(
requireActivity() as AppCompatActivity,
viewModel.linkBbs.value?.communityEntity?.id ?: "",
viewModel.linkBbs.value?.selectedSection?.id ?: "",
viewModel.isModerator,
tag ?: ""
)
}, delayTime)
}
override fun onResume() {
super.onResume()
requireActivity().findViewById<View>(android.R.id.content)?.viewTreeObserver?.addOnGlobalLayoutListener(
onGlobalLayoutListener
)
}
override fun onPause() {
super.onPause()
requireActivity().findViewById<View>(android.R.id.content)?.viewTreeObserver?.removeOnGlobalLayoutListener(
onGlobalLayoutListener
)
}
override fun onDestroyView() {
super.onDestroyView()
handler.removeCallbacksAndMessages(null)
}
override fun onHandleBackPressed(): Boolean {
showSaveDraftDialog()
return true
}
private fun showSaveDraftDialog() {
SensorsBridge.trackEvent("ArticleCancelDialogShow")
DialogHelper.showDialog(
requireContext(),
com.gh.gamecenter.common.R.string.miui_optimization_warning_dialog_title.toResString(),
R.string.save_to_draft_box_and_exit.toResString(),
confirmText = R.string.save_and_exit.toResString(),
cancelText = R.string.do_not_save.toResString(),
confirmClickCallback = {
SensorsBridge.trackEvent("ArticleCancelDialogClick", "button_name", "保存并退出")
createDraft()
},
cancelClickCallback = {
SensorsBridge.trackEvent("ArticleCancelDialogClick", "button_name", "不保存")
activity?.finish()
},
extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)
)
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == ChooseSectionDialogFragment.REQUEST_CODE) {
val section = data?.getParcelableExtra<ForumDetailEntity.Section>(EntranceConsts.KEY_DATA)
if (section != null) {
viewModel.changeLinkSection(section)
}
}
}
companion object {
private const val TITLE_MAX_LENGTH = 20
private const val CONTENT_MAX_LENGTH = 1000
private const val DRAG_TIPS_SHOW_DURATION = 5000L
fun newInstance() = PublishImageArticleFragment()
}
private class MyItemTouchCallback(
private val viewModel: PublishImageArticleActivityViewModel
) : ItemTouchHelper.Callback() {
override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int {
return makeMovementFlags(ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT, 1)
}
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
val adapter = recyclerView.adapter as? ImageAndTextAdapter
if (adapter != null) {
val position = viewHolder.bindingAdapterPosition
val targetPosition = target.bindingAdapterPosition
val targetItem = adapter.getItem(targetPosition)
if (targetItem.id != ImageAndTextAdapter.INVALID_ID) {
viewModel.swap(position, targetPosition)
return true
}
}
return false
}
override fun isLongPressDragEnabled(): Boolean = true
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) = Unit
override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {
super.onSelectedChanged(viewHolder, actionState)
if (actionState == ACTION_STATE_IDLE) {
// 拖拽完毕,刷新数据:将第一位的图片设为封面
viewModel.refreshImages()
}
}
}
class MyItemDecoration : RecyclerView.ItemDecoration() {
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
outRect.left = if (parent.getChildAdapterPosition(view) == 0) {
0
} else {
4F.dip2px()
}
}
}
}

View File

@ -0,0 +1,118 @@
package com.gh.gamecenter.forum.home.recommend.viewmodel
import android.app.Application
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.baselist.LoadType
import com.gh.gamecenter.common.baselist.PageLoader
import com.gh.gamecenter.common.utils.toArrayList
import com.gh.gamecenter.common.utils.toJson
import com.gh.gamecenter.core.utils.GsonUtils
import com.gh.gamecenter.feature.entity.CommentEntity
import com.gh.gamecenter.feature.entity.MeEntity
import com.gh.gamecenter.forum.home.recommend.ImageArticleRepository
import com.gh.gamecenter.qa.article.detail.CommentItemData
import com.gh.gamecenter.qa.comment.base.BaseCommentViewModel
import com.gh.gamecenter.retrofit.RetrofitManager
import com.google.gson.reflect.TypeToken
import io.reactivex.Observable
import io.reactivex.disposables.CompositeDisposable
class ImageArticleCommentViewModel(
application: Application,
imageArticleId: String,
topCommentId: String
) :
BaseCommentViewModel(application, "", "", "", "", "", imageArticleId, topCommentId = topCommentId) {
private val repository = ImageArticleRepository.newInstance()
override fun provideDataObservable(page: Int): Observable<MutableList<CommentEntity>> {
val map = hashMapOf<String, Any>()
if (!isHandleTopComment && topCommentId.isNotEmpty()) {
map["top_comment_id"] = topCommentId
}
return RetrofitManager.getInstance().api.getImageArticleComments(
imageArticleId,
currentSortType.value,
page,
map
).map {
mTotal = it.headers().get("total")?.toInt() ?: 0
val type = object : TypeToken<List<CommentEntity>>() {}.type
GsonUtils.gson.fromJson(it.body()?.toJson() ?: "", type)
}
}
override fun mergeResultLiveData() {
mResultLiveData.addSource(mListLiveData) {
mergeListData(mListLiveData.value)
}
}
override fun mergeListData(commentList: List<CommentEntity>?, hasFilter: Boolean) {
val mergedList = arrayListOf<CommentItemData>().apply {
if (commentList.isNullOrEmpty() && mLoadStatusLiveData.value == LoadStatus.INIT_EMPTY) {
add(CommentItemData(errorEmpty = true))
} else if (commentList.isNullOrEmpty() && mLoadStatusLiveData.value == LoadStatus.INIT_FAILED) {
add(CommentItemData(errorConnection = true))
} else {
commentList?.forEachIndexed { index, entity ->
// 没有 me 会导致不能跨页面更新点赞
if (entity.me == null) {
entity.me = MeEntity()
}
handleTopComment(index, entity)
add(CommentItemData(commentNormal = entity))
}
add(CommentItemData(footer = true))
}
}
mResultLiveData.postValue(mergedList)
}
fun insertNewestComment(newComment: CommentEntity) {
val data = (mResultLiveData.value ?: emptyList()).toArrayList()
val count = data.count { it.commentNormal != null }
if (count == 0) {
load(LoadType.REFRESH)
return
}
when (currentSortType) {
SortType.LATEST -> {
val index = data.indexOfFirst { it.commentNormal != null }
if (index >= 0) {
mTotal++
data.add(index, CommentItemData(commentNormal = newComment))
mResultLiveData.postValue(data)
} else {
load(LoadType.REFRESH)
}
}
SortType.OLDEST -> {
//根据total判断是否插入到最后
if (count >= mTotal) {
mTotal++
val index = data.indexOfLast { it.commentNormal != null }
data.add(index + 1, CommentItemData(commentNormal = newComment))
mResultLiveData.postValue(data)
} else {
load(LoadType.REFRESH)
}
}
}
}
class Factory(
private val application: Application,
private val imageArticleId: String,
private val topCommentId: String
) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ImageArticleCommentViewModel(application, imageArticleId, topCommentId) as T
}
}
}

View File

@ -0,0 +1,334 @@
package com.gh.gamecenter.forum.home.recommend.viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import androidx.lifecycle.ViewModel
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.LoadStatus
import com.gh.gamecenter.common.entity.ErrorEntity
import com.gh.gamecenter.common.retrofit.BiResponse
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.common.utils.toObject
import com.gh.gamecenter.entity.ImageArticleEntity
import com.gh.gamecenter.eventbus.EBImageArticleChanged
import com.gh.gamecenter.feature.entity.CommentEntity
import com.gh.gamecenter.feature.entity.Permissions
import com.gh.gamecenter.forum.home.recommend.ImageArticleRepository
import com.gh.gamecenter.forum.home.recommend.ImageArticleUseCase
import com.gh.gamecenter.livedata.Event
import com.gh.gamecenter.qa.comment.base.BaseCommentViewModel.SortType
import io.reactivex.CompletableObserver
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import okhttp3.ResponseBody
import retrofit2.HttpException
class ImageArticleDetailViewModel : ViewModel() {
private val repository = ImageArticleRepository.newInstance()
private val compositeDisposable = CompositeDisposable()
var imageArticleId: String = ""
private val useCase = ImageArticleUseCase(repository, compositeDisposable)
private val _imageArticleDetailEntity = MutableLiveData<ImageArticleEntity>()
val imageArticleDetailEntity: LiveData<ImageArticleEntity> = _imageArticleDetailEntity
val isUserFollowed: LiveData<Boolean> =
Transformations.distinctUntilChanged(Transformations.map(imageArticleDetailEntity) {
it.me.isFollower
})
fun followUser() {
val isFollowed = isUserFollowed.value ?: false
val userId = imageArticleDetailEntity.value?.user?.id ?: ""
SensorsBridge.trackUserFollowClick(
userId = userId,
userName = imageArticleDetailEntity.value?.user?.name ?: "",
buttonName = if (isFollowed) "已关注" else "关注"
)
repository.followUser(!isFollowed, userId)
.compose(observableToMain())
.subscribe(object : Response<ResponseBody>() {
override fun onSubscribe(d: Disposable) {
compositeDisposable.add(d)
}
override fun onResponse(response: ResponseBody?) {
updateImageArticle {
it.me.isFollower = !isFollowed
}
}
})
}
val commentCount: LiveData<Int> =
Transformations.distinctUntilChanged(Transformations.map(imageArticleDetailEntity) {
it.count.comment
})
fun updateComment(commentId: String, commentCount: Int) {
updateImageArticle {
it.count.comment = commentCount
}
ImageArticleUseCase.notifyCommentChanged(imageArticleId, commentCount)
if (commentId.isNotEmpty()) {
insertNewestComment(commentId)
}
}
private val _insertNewestCommentAction = MutableLiveData<Event<CommentEntity>>()
val insertNewestCommentAction: LiveData<Event<CommentEntity>> = _insertNewestCommentAction
private fun insertNewestComment(commentId: String) {
// 将刚刚评论的内容插入到评论列表中
repository.getSingleCommentById(commentId)
.compose(singleToMain())
.subscribe(object : BiResponse<CommentEntity>() {
override fun onSuccess(data: CommentEntity) {
_insertNewestCommentAction.value = Event(data)
}
}).let(compositeDisposable::add)
}
val vote: LiveData<Pair<Boolean, Int>> =
Transformations.distinctUntilChanged(Transformations.map(imageArticleDetailEntity) {
it.me.isCommunityArticleVote to it.count.vote
})
fun vote() {
val isVoted = vote.value?.first ?: false
useCase.voteImageArticle(imageArticleId, !isVoted)
val imageArticle = imageArticleDetailEntity.value ?: return
SensorsBridge.trackArticleLikeClick(
customerType = imageArticle.user.auth?.text ?: "",
articleId = imageArticle.id,
bbsId = imageArticle.community?.id ?: "",
bbsType = imageArticle.community?.typeChinese ?: "综合论坛",
activityTag = "",
gameForumType = imageArticle.community?.game?.categoryChinese ?: "",
articleType = "图文",
buttonName = if (isVoted) "取消点赞" else "赞同"
)
}
private val _pageStatus = MutableLiveData<LoadStatus>()
val pageStatus: LiveData<LoadStatus> = _pageStatus
fun loadImageArticleDetail(imageArticleId: String, recommendId: String) {
_pageStatus.value = LoadStatus.INIT_LOADING
this.imageArticleId = imageArticleId
repository.loadImageArticleDetail(imageArticleId)
.compose(singleToMain())
.subscribe(object : BiResponse<ImageArticleEntity>() {
override fun onSuccess(data: ImageArticleEntity) {
_pageStatus.value = LoadStatus.INIT_LOADED
setImageArticle(data)
NewLogUtils.logForumContentBrowser(imageArticleId, "bbs_article", recommendId)
}
override fun onFailure(exception: Exception) {
val error =
(exception as? HttpException)?.response()?.errorBody()?.string()?.toObject<ErrorEntity>()
if (error != null && error.code == 404001) {
_pageStatus.value = LoadStatus.INIT_EMPTY
} else {
_pageStatus.value = LoadStatus.INIT_FAILED
}
}
}).let(compositeDisposable::add)
}
private val _sortType = MutableLiveData<Event<SortType>>()
val sortType: LiveData<Event<SortType>> = _sortType
fun changeSort(newSort: SortType) {
_sortType.value = Event(newSort)
}
val isBbsFollowed: LiveData<Boolean> =
Transformations.distinctUntilChanged(Transformations.map(imageArticleDetailEntity) {
it.community?.isFollowed ?: false
})
fun followBbs(communityId: String) {
val isFollowed = isBbsFollowed.value ?: false
repository.followBbs(communityId, !isFollowed)
.compose(singleToMain())
.subscribe(object : BiResponse<ResponseBody>() {
override fun onSuccess(data: ResponseBody) {
updateImageArticle {
it.community?.isFollowed = !isFollowed
}
}
}).let(compositeDisposable::add)
}
val isFavorite: LiveData<Boolean> =
Transformations.distinctUntilChanged(Transformations.map(imageArticleDetailEntity) {
it.me.isFavorite
})
fun collect() {
val imageArticleId = imageArticleId
val isCollected = isFavorite.value ?: false
repository.collect(imageArticleId, !isCollected)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : CompletableObserver {
override fun onSubscribe(d: Disposable) {
compositeDisposable.add(d)
}
override fun onComplete() {
updateImageArticle {
it.me.isFavorite = !isCollected
}
}
override fun onError(e: Throwable) = Unit
})
val imageArticle = imageArticleDetailEntity.value ?: return
SensorsBridge.trackArticleCollectionClick(
customerType = imageArticle.user.auth?.text ?: "",
articleId = imageArticle.id,
bbsId = imageArticle.community?.id ?: "",
bbsType = imageArticle.community?.typeChinese ?: "综合论坛",
activityTag = "",
gameForumType = imageArticle.community?.game?.categoryChinese ?: "",
articleType = "图文",
buttonName = if (isCollected) "已收藏" else "收藏"
)
}
fun setImageArticle(newData: ImageArticleEntity) {
_imageArticleDetailEntity.value = newData
}
fun updateImageArticle(block: (ImageArticleEntity) -> Unit) {
val data = imageArticleDetailEntity.value ?: return
block(data)
_imageArticleDetailEntity.value = data
}
private val _applyHighlightAction = MutableLiveData<Event<Boolean>>()
val applyHighlightAction: LiveData<Event<Boolean>> = _applyHighlightAction
fun applyHighlightForImageArticle() {
repository.applyHighlightForImageArticle(imageArticleId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : CompletableObserver {
override fun onSubscribe(d: Disposable) {
compositeDisposable.add(d)
}
override fun onComplete() {
imageArticleDetailEntity.value?.choicenessStatus = "apply"
_applyHighlightAction.value = Event(true)
}
override fun onError(e: Throwable) {
_applyHighlightAction.value = Event(false)
}
})
}
private val _addHighlightAction = MutableLiveData<Event<Int>>()
val addHighlightAction: LiveData<Event<Int>> = _addHighlightAction
fun addHighlight(isAdd: Boolean) {
repository.addHighlight(isAdd, imageArticleId)
.compose(singleToMain())
.subscribe(object : BiResponse<ResponseBody>() {
override fun onSuccess(data: ResponseBody) {
val imageArticle = imageArticleDetailEntity.value ?: return
if (isAdd) {
if (imageArticle.me.moderatorPermissions.choicenessImageArticle == Permissions.REPORTER) {
_addHighlightAction.value = Event(R.string.apply_successfully)
updateImageArticle {
it.choicenessStatus = "apply"
}
} else {
_addHighlightAction.value = Event(R.string.operation_successful)
updateImageArticle {
it.choicenessStatus = "pass"
}
}
} else {
if (imageArticle.me.moderatorPermissions.cancelChoicenessImageArticle == Permissions.REPORTER) {
_addHighlightAction.value = Event(R.string.apply_successfully)
} else {
_addHighlightAction.value = Event(R.string.operation_successful)
updateImageArticle {
it.choicenessStatus = "cancel"
}
}
}
}
override fun onFailure(exception: Exception) {
_addHighlightAction.value = Event(R.string.permission_error)
}
}).let(compositeDisposable::add)
}
private val _deleteOrHideImageArticleSuccessfully = MutableLiveData<Event<Pair<Boolean, Int>>>()
val deleteOrHideImageArticleSuccessfully: LiveData<Event<Pair<Boolean, Int>>> =
_deleteOrHideImageArticleSuccessfully
fun deleteOrHideImageArticle() {
repository.deleteOrHideImageArticle(imageArticleId)
.compose(singleToMain())
.subscribe(object : BiResponse<ResponseBody>() {
override fun onSuccess(data: ResponseBody) {
val imageArticle = imageArticleDetailEntity.value ?: return
val toastResId = if (imageArticle.me.isModerator) {
if (imageArticle.me.moderatorPermissions.hideImageArticle == Permissions.REPORTER) {
R.string.apply_successfully
} else {
R.string.hidden
}
} else {
R.string.deleted
}
ImageArticleUseCase.notifyDataDeleted(imageArticleId)
_deleteOrHideImageArticleSuccessfully.value = Event(true to toastResId)
}
override fun onFailure(exception: Exception) {
super.onFailure(exception)
_deleteOrHideImageArticleSuccessfully.value = Event(false to R.string.permission_error_and_retry)
}
}).let(compositeDisposable::add)
}
fun imageArticleChanged(changed: EBImageArticleChanged) {
when {
changed is EBImageArticleChanged.VoteChanged -> {
updateImageArticle {
it.me.isCommunityArticleVote = changed.isVoted
it.count.vote = changed.count
}
}
changed is EBImageArticleChanged.DataChanged -> {
setImageArticle(changed.data)
}
}
}
}

View File

@ -0,0 +1,101 @@
package com.gh.gamecenter.forum.home.recommend.viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.gh.gamecenter.common.baselist.PageLoader
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.entity.ImageArticleEntity
import com.gh.gamecenter.forum.home.recommend.ImageArticleRepository
import com.gh.gamecenter.livedata.Event
import io.reactivex.CompletableObserver
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
class ImageArticleDraftViewModel : ViewModel() {
private val repository = ImageArticleRepository.newInstance()
private val composeDisposable = CompositeDisposable()
private val _pageState = MutableLiveData<PageLoader.PageState>()
val pageState: LiveData<PageLoader.PageState> = _pageState
private val _drafts = MutableLiveData<List<ImageArticleEntity>>()
val drafts: LiveData<List<ImageArticleEntity>> = _drafts
fun loadDraft(isPullToRefresh: Boolean) {
_pageState.value = if (isPullToRefresh) {
PageLoader.PageState.PagePullToRefreshLoading
} else {
PageLoader.PageState.PageInitLoading
}
repository.loadDraft()
.compose(singleToMain())
.subscribe(object : BiResponse<List<ImageArticleEntity>>() {
override fun onSuccess(data: List<ImageArticleEntity>) {
_pageState.value = if (data.isEmpty()) {
PageLoader.PageState.PageNoData
} else {
PageLoader.PageState.PageLoadCompleted
}
_drafts.value = data
}
override fun onFailure(exception: Exception) {
super.onFailure(exception)
_pageState.value = if (isPullToRefresh) {
PageLoader.PageState.PagePullToRefreshFailure
} else {
PageLoader.PageState.PageInitFailure
}
}
}).let(composeDisposable::add)
}
private val _showDeleteDraftDialogAction = MutableLiveData<Event<String>>()
val showDeleteDraftDialogAction: LiveData<Event<String>> = _showDeleteDraftDialogAction
fun showDeleteDraftDialog(draftId: String) {
_showDeleteDraftDialogAction.value = Event(draftId)
}
fun deleteDraft(draftId: String) {
repository.deleteDraft(draftId)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : CompletableObserver {
override fun onSubscribe(d: Disposable) {
composeDisposable.add(d)
}
override fun onComplete() {
val oldData = drafts.value ?: emptyList()
val newList = oldData.toMutableList()
newList.removeAll { it.id == draftId }
if (newList.isEmpty()) {
_pageState.value = PageLoader.PageState.PageNoData
} else {
_drafts.value = newList
}
}
override fun onError(e: Throwable) = Unit
})
}
private val _publishImageArticleDestination = MutableLiveData<Event<ImageArticleEntity>>()
val publishImageArticleDestination: LiveData<Event<ImageArticleEntity>> = _publishImageArticleDestination
fun editDraft(draftEntity: ImageArticleEntity) {
_publishImageArticleDestination.value = Event(draftEntity)
}
fun updateDrafts(draftList: MutableList<ImageArticleEntity>) {
_drafts.value = draftList
}
}

View File

@ -0,0 +1,77 @@
package com.gh.gamecenter.forum.home.recommend.viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.gh.gamecenter.common.baselist.PageLoader
import com.gh.gamecenter.entity.ImageArticleEntity
import com.gh.gamecenter.eventbus.EBImageArticleChanged
import com.gh.gamecenter.forum.home.recommend.ImageArticleRepository
import com.gh.gamecenter.forum.home.recommend.ImageArticleUseCase
import com.gh.gamecenter.livedata.Event
import io.reactivex.disposables.CompositeDisposable
class ImageArticleHomeViewModel : ViewModel() {
private val repository = ImageArticleRepository.newInstance()
private val compositeDisposable = CompositeDisposable()
val useCase = ImageArticleUseCase(repository, compositeDisposable)
val pageLoader = PageLoader(compositeDisposable = compositeDisposable) { pageNo, _ ->
repository.loadRecommends(pageNo)
}
fun initLoad() {
pageLoader.loadInit()
}
private val _imageDetailDestination = MutableLiveData<Event<ImageArticleEntity>>()
val imageDetailDestination: LiveData<Event<ImageArticleEntity>> = _imageDetailDestination
fun navigateToImageArticleDetailPage(imageArticleEntity: ImageArticleEntity) {
_imageDetailDestination.value = Event(imageArticleEntity)
}
fun loadMore() {
pageLoader.loadMore()
}
override fun onCleared() {
compositeDisposable.clear()
}
fun voteChanged(changed: EBImageArticleChanged.VoteChanged) {
val oldData = pageLoader.dataList.value ?: return
val newData = oldData.toMutableList()
val position = newData.indexOfFirst { it.id == changed.id }
if (position != -1) {
val oldItem = newData[position]
val oldCount = oldItem.count.vote
val newCount = if (changed.isVoted) {
oldCount + 1
} else {
oldCount - 1
}
val newItem = oldItem.copy(
_me = oldItem.me.copy(isCommunityArticleVote = changed.isVoted),
_count = oldItem.count.copy(vote = newCount)
)
newData[position] = newItem
pageLoader.updateData(newData)
}
}
fun itemChanged(changed: EBImageArticleChanged.DataChanged) {
val oldData = pageLoader.dataList.value ?: return
val newData = oldData.toMutableList()
val position = newData.indexOfFirst { it.id == changed.data.id }
if (position != -1) {
newData[position] = changed.data
pageLoader.updateData(newData)
}
}
}

View File

@ -0,0 +1,81 @@
package com.gh.gamecenter.forum.home.recommend.viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.gh.gamecenter.common.baselist.PageLoader
import com.gh.gamecenter.entity.PersonalHistoryEntity.Count
import com.gh.gamecenter.eventbus.EBImageArticleChanged
import com.gh.gamecenter.forum.home.recommend.ImageArticleRepository
import com.gh.gamecenter.forum.home.recommend.ImageArticleUseCase
import com.gh.gamecenter.livedata.Event
import io.reactivex.disposables.CompositeDisposable
class MyImageArticleListViewModel : ViewModel() {
private val repository = ImageArticleRepository.newInstance()
private val compositeDisposable = CompositeDisposable()
val useCase = ImageArticleUseCase(repository, compositeDisposable)
val pageLoader = PageLoader(compositeDisposable = compositeDisposable) { pageSize, _ ->
repository.loadMyImageArticleList(pageSize)
.singleOrError()
}
fun initLoad() {
pageLoader.loadInit()
}
private val _imageArticleDetailDestination = MutableLiveData<Event<String>>()
val imageArticleDetailDestination: LiveData<Event<String>> = _imageArticleDetailDestination
fun navigateToImageArticleDetailPage(imageArticleId: String) {
_imageArticleDetailDestination.value = Event(imageArticleId)
}
fun loadMore() {
pageLoader.loadMore()
}
override fun onCleared() {
compositeDisposable.clear()
}
fun voteChanged(changed: EBImageArticleChanged.VoteChanged) {
val oldData = pageLoader.dataList.value ?: return
val newData = oldData.toMutableList()
val position = newData.indexOfFirst { it.id == changed.id }
if (position != -1) {
val oldItem = newData[position]
val oldCount = oldItem.count.vote
val newCount = if (changed.isVoted) {
oldCount + 1
} else {
oldCount - 1
}
val newItem = oldItem.copy(
_me = oldItem.me.copy(isCommunityArticleVote = changed.isVoted),
_count = Count(
comment = oldItem.count.comment,
vote = newCount,
reply = oldItem.count.reply,
)
)
newData[position] = newItem
pageLoader.updateData(newData)
}
}
fun itemChanged(changed: EBImageArticleChanged.DataChanged) {
val oldData = pageLoader.dataList.value ?: return
val newData = oldData.toMutableList()
val position = newData.indexOfFirst { it.id == changed.data.id }
if (position != -1) {
newData[position] = changed.data.toPersonHistoryEntity()
pageLoader.updateData(newData)
}
}
}

View File

@ -0,0 +1,129 @@
package com.gh.gamecenter.forum.home.recommend.viewmodel
import android.net.Uri
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.gh.gamecenter.common.entity.CommunityEntity
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.common.utils.singleToMain
import com.gh.gamecenter.entity.ImageArticleEntity
import com.gh.gamecenter.forum.home.recommend.ImageArticleRepository
import com.gh.gamecenter.forum.home.recommend.adapter.ImageAndTextAdapter
import com.gh.gamecenter.livedata.Event
import io.reactivex.disposables.CompositeDisposable
import java.util.*
class PublishImageArticleActivityViewModel : ViewModel() {
private val repository = ImageArticleRepository.newInstance()
private val compositeDisposable = CompositeDisposable()
private var imageId: Int = 0
private val _showDragTipsAction = MutableLiveData<Event<Unit>>()
val showDragTipsAction: LiveData<Event<Unit>> = _showDragTipsAction
private val _localImages = MutableLiveData<List<ImageAndTextAdapter.LocalImage>>()
val localImages: LiveData<List<ImageAndTextAdapter.LocalImage>> = _localImages
fun addImages(uris: List<Uri>) {
val oldData = localImages.value?.toMutableList() ?: mutableListOf()
val newData = (oldData + uris.map {
ImageAndTextAdapter.LocalImage(id = imageId++, isCover = false, showClose = true, uri = it)
}).toMutableList()
resetFirstItem(newData)
repository.isShowDragTips()
.compose(singleToMain())
.subscribe(object : BiResponse<Boolean>() {
override fun onSuccess(data: Boolean) {
if (data && newData.size > 1) {
repository.saveHasShowDragTips()
_showDragTipsAction.value = Event(Unit)
}
}
}).let(compositeDisposable::add)
}
private val _chooseImageAction = MutableLiveData<Event<Int>>()
val chooseImageAction: LiveData<Event<Int>> = _chooseImageAction
fun chooseImage() {
val currentImageSize = localImages.value?.size ?: 0
val remainingSize = 9 - currentImageSize
_chooseImageAction.value = Event(remainingSize)
}
fun removeImage(position: Int) {
val images = localImages.value?.toMutableList() ?: mutableListOf()
images.removeAt(position)
resetFirstItem(images)
}
private fun resetFirstItem(data: MutableList<ImageAndTextAdapter.LocalImage>) {
if (data.isEmpty()) {
_localImages.value = mutableListOf()
} else {
// 给第一个设置为封面
val firstItem = data[0].copy(isCover = true)
data[0] = firstItem
_localImages.value = data
}
}
fun swap(dataPosition: Int, targetDataPosition: Int) {
val oldList = localImages.value ?: arrayListOf()
val newList = oldList.toMutableList()
Collections.swap(newList, dataPosition, targetDataPosition)
_localImages.value = newList
}
fun refreshImages() {
val oldList = localImages.value ?: arrayListOf()
val newList = oldList.toMutableList()
for (index in newList.indices) {
val item = newList[index]
if (index == 0 && !item.isCover) {
newList[0] = item.copy(isCover = true)
}
if (index != 0 && item.isCover) {
newList[index] = item.copy(isCover = false)
}
}
_localImages.value = newList
}
private val _draftBoxDestination = MutableLiveData<Event<Unit>>()
val draftBoxDestination: LiveData<Event<Unit>> = _draftBoxDestination
fun navigateToDraftBox() {
_draftBoxDestination.value = Event(Unit)
}
private val _editImageArticle = MutableLiveData<Event<ImageArticleEntity>>()
val editImageArticle: LiveData<Event<ImageArticleEntity>> = _editImageArticle
fun setEditImageArticle(imageArticleEntity: ImageArticleEntity?) {
imageArticleEntity ?: return
addImagesFromEdit(imageArticleEntity.images)
_editImageArticle.value = Event(imageArticleEntity)
}
private fun addImagesFromEdit(urls: List<String>) {
val newImages = urls.map {
ImageAndTextAdapter.LocalImage(id = imageId++, isCover = false, showClose = true, url = it)
}.toMutableList()
resetFirstItem(newImages)
}
private val _community = MutableLiveData<Event<CommunityEntity>>()
val community: LiveData<Event<CommunityEntity>> = _community
fun setCommunity(communityEntity: CommunityEntity) {
_community.value = Event(communityEntity)
}
}

View File

@ -0,0 +1,234 @@
package com.gh.gamecenter.forum.home.recommend.viewmodel
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.gh.gamecenter.common.entity.CommunityEntity
import com.gh.gamecenter.common.retrofit.BiResponse
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.common.utils.toRequestBody
import com.gh.gamecenter.entity.ForumDetailEntity
import com.gh.gamecenter.entity.ImageArticleEntity
import com.gh.gamecenter.entity.PublishImageTextRequest
import com.gh.gamecenter.forum.home.recommend.ImageArticleRepository
import com.gh.gamecenter.forum.home.recommend.ImageArticleUseCase
import com.gh.gamecenter.forum.home.recommend.adapter.ImageAndTextAdapter
import com.gh.gamecenter.livedata.Event
import com.gh.gamecenter.login.user.UserManager
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.disposables.Disposable
import okhttp3.ResponseBody
import retrofit2.HttpException
class PublishImageArticleViewModel : ViewModel() {
private val repository = ImageArticleRepository.newInstance()
private val compositeDisposable = CompositeDisposable()
var isModerator: Boolean = false
private var imageArticle: ImageArticleEntity? = null
val isCreating: Boolean
get() = imageArticle == null || imageArticle!!.isDraft
private val _chooseImageAction = MutableLiveData<Event<Unit>>()
val chooseImageAction: LiveData<Event<Unit>> = _chooseImageAction
fun chooseImage() {
_chooseImageAction.value = Event(Unit)
}
private val _removeImageAction = MutableLiveData<Event<Int>>()
val removeImageAction: LiveData<Event<Int>> = _removeImageAction
fun removeImage(position: Int) {
_removeImageAction.value = Event(position)
}
private val _linkBbs = MutableLiveData<LinkBbs>()
val linkBbs: LiveData<LinkBbs> = _linkBbs
fun changeLinkBbs(newCommunity: CommunityEntity?) {
if (newCommunity == null) {
_linkBbs.value = LinkBbs(null, false, null)
return
}
val oldCommunity = linkBbs.value?.communityEntity
if (newCommunity != oldCommunity) {
// 用户重新选择了关联论坛
_linkBbs.value = LinkBbs(newCommunity, false, null)
getForumSections(newCommunity)
getModeratorsInfo(newCommunity.id)
}
}
private fun getForumSections(newCommunity: CommunityEntity) {
repository.checkHasSections(newCommunity.id)
.compose(observableToMain())
.subscribe(object : Response<Boolean>() {
override fun onSubscribe(d: Disposable) {
compositeDisposable.add(d)
}
override fun onResponse(response: Boolean?) {
if (response == true) {
// 此论坛有子板块更新关联论坛ui状态
_linkBbs.value = LinkBbs(newCommunity, true, null)
}
}
})
}
private fun getModeratorsInfo(bbsId: String) {
repository.getModeratorsInfo(bbsId)
.compose(singleToMain())
.subscribe(object : BiResponse<Boolean>() {
override fun onSuccess(data: Boolean) {
isModerator = data
}
}).let(compositeDisposable::add)
}
fun changeLinkSection(section: ForumDetailEntity.Section?) {
val oldLink = linkBbs.value
if (oldLink != null) {
_linkBbs.value = oldLink.copy(selectedSection = section)
}
}
private val _draftCreated = MutableLiveData<Event<Boolean>>()
val draftCreated: LiveData<Event<Boolean>> = _draftCreated
fun createOrEditDraft(title: String, content: String, localImages: List<ImageAndTextAdapter.LocalImage>) {
val request = createPublishRequest(title, content)
repository.createOrEditDraft(request, localImages)
.compose(singleToMain())
.subscribe(object : BiResponse<ResponseBody>() {
override fun onSuccess(data: ResponseBody) {
_draftCreated.value = Event(true)
}
override fun onFailure(exception: Exception) {
_draftCreated.value = Event(false)
}
}).let(compositeDisposable::add)
}
private val _imageTextPublished = MutableLiveData<Event<Pair<Boolean, String>>>()
val imageTextPublished: LiveData<Event<Pair<Boolean, String>>> = _imageTextPublished
fun publishImageText(title: String, content: String, images: List<ImageAndTextAdapter.LocalImage>) {
val request = createPublishRequest(title, content)
SensorsBridge.trackEvent(
"ArticlePostClick",
"bbs_id",
request.communityId,
"bbs_type",
request.communityType,
"activity_tag",
"",
"article_type",
"图文"
)
if (isCreating) {
repository.publishImageText(request, images)
} else {
repository.editImageArticle(imageArticle!!.id, request, images)
}
.compose(singleToMain())
.subscribe(object : BiResponse<ImageArticleEntity>() {
override fun onSuccess(data: ImageArticleEntity) {
if (isCreating) {
ImageArticleUseCase.notifyDataCreated(data, request.draftId)
} else {
ImageArticleUseCase.notifyDataEdited(data)
}
_imageTextPublished.value = Event(true to "")
val community = data.community
SensorsBridge.trackArticlePostResult(
postResult = "成功",
bbsId = community?.id ?: "",
bbsType = if (community != null) {
if (data.community.type == "official_bbs") "综合论坛" else "游戏论坛"
} else {
""
},
activityTag = "",
articleType = "图文",
customerType = UserManager.getInstance().userInfoEntity?.auth?.text ?: "",
gameForumType = community?.game?.categoryChinese ?: ""
)
}
override fun onFailure(exception: Exception) {
val errorString = try {
(exception as? HttpException)?.response()?.errorBody()?.string() ?: ""
} catch (e1: Exception) {
e1.printStackTrace()
""
}
_imageTextPublished.value = Event(false to errorString)
}
}).let(compositeDisposable::add)
}
private fun createPublishRequest(title: String, content: String): PublishImageTextRequest {
val draftId = if (imageArticle?.isDraft == true) {
// 发布或者编辑草稿
imageArticle?.id ?: ""
} else {
""
}
val selectedBbs = linkBbs.value
val communityId = selectedBbs?.communityEntity?.id ?: ""
val communityType = selectedBbs?.communityEntity?.type ?: ""
val sectionId = selectedBbs?.selectedSection?.id ?: ""
val sectionIds = if (sectionId.isNotEmpty()) listOf(sectionId) else listOf()
return PublishImageTextRequest(title, content, communityType, communityId, sectionIds, emptyList(), draftId)
}
private val _editImageArticle = MutableLiveData<Event<ImageArticleEntity>>()
val editImageArticle: LiveData<Event<ImageArticleEntity>> = _editImageArticle
fun setEditImageArticle(imageArticleEntity: ImageArticleEntity?) {
imageArticleEntity ?: return
imageArticle = imageArticleEntity
val community = imageArticleEntity.community?.toCommunityEntity()
val section = imageArticleEntity.sections.firstOrNull()
val game = imageArticleEntity.community?.game
val icon = if (game == null) {
community?.icon ?: ""
} else {
game.rawIcon ?: game.icon
}
community?.icon = icon
when {
community != null && section != null -> { // 绑定了论坛和子板块
val formSection = ForumDetailEntity.Section(section.id, name = section.name)
_linkBbs.value = LinkBbs(community, hasSection = true, formSection)
}
community != null -> { // 只绑定了论坛未绑定子板块
changeLinkBbs(community)
}
else -> { // 未绑定论坛
changeLinkBbs(null)
}
}
changeLinkBbs(community)
_editImageArticle.value = Event(imageArticleEntity)
}
data class LinkBbs(
val communityEntity: CommunityEntity?,
val hasSection: Boolean,
val selectedSection: ForumDetailEntity.Section? = null
)
}

View File

@ -51,7 +51,7 @@ open class GameCollectionDetailViewModel(
gameCollectionId: String,
topCommentId: String = ""
) :
BaseCommentViewModel(application, "", "", "", "", gameCollectionId, topCommentId) {
BaseCommentViewModel(application, "", "", "", "", gameCollectionId, topCommentId = topCommentId) {
var firstItemInitOverLiveData = MutableLiveData<Boolean>()
var followLiveData = MutableLiveData<Boolean>()

View File

@ -9,6 +9,9 @@ import com.gh.common.util.NewFlatLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.fragment.BaseFragment_TabLayout
import com.gh.gamecenter.common.utils.goneIf
import com.gh.gamecenter.common.utils.toResString
import com.gh.gamecenter.forum.home.recommend.fragment.ImageArticleHomeFragment
import com.gh.gamecenter.forum.home.recommend.fragment.MyImageArticleListFragment
import com.gh.gamecenter.login.user.UserManager
import com.gh.gamecenter.personalhome.home.UserHistoryViewModel
import com.gh.gamecenter.personalhome.home.game.UserCommentHistoryFragment
@ -25,6 +28,7 @@ class MyPostWrapperFragment : BaseFragment_TabLayout() {
override fun initTabTitleList(tabTitleList: MutableList<String>) {
tabTitleList.add("评价")
tabTitleList.add(R.string.image_article.toResString())
tabTitleList.add(getString(R.string.collection_article))
tabTitleList.add(getString(R.string.ask_search_questions))
tabTitleList.add(getString(R.string.video))
@ -50,10 +54,11 @@ class MyPostWrapperFragment : BaseFragment_TabLayout() {
menuItem?.run {
if (itemId == R.id.menu_draft && mLastPosition != 0) {
val tabName = when (mLastPosition) {
1 -> getString(R.string.collection_article)
2 -> getString(R.string.ask_search_questions)
3 -> getString(R.string.video)
4 -> getString(R.string.answer)
1 -> getString(R.string.image_article)
2 -> getString(R.string.collection_article)
3 -> getString(R.string.ask_search_questions)
4 -> getString(R.string.video)
5 -> getString(R.string.answer)
else -> ""
}
NewFlatLogUtils.logHaloSelfPublishContent(tabName, "草稿箱")
@ -64,6 +69,7 @@ class MyPostWrapperFragment : BaseFragment_TabLayout() {
override fun initFragmentList(fragments: MutableList<Fragment>) {
fragments.add(UserCommentHistoryFragment.getInstance("我的发布", UserManager.getInstance().userId))
fragments.add(MyImageArticleListFragment.newInstance())
fragments.add(MyPostFragment.getInstance(UserHistoryViewModel.TYPE.COMMUNITY_ARTICLE))
fragments.add(MyPostFragment.getInstance(UserHistoryViewModel.TYPE.QUESTION))
fragments.add(MyPostFragment.getInstance(UserHistoryViewModel.TYPE.VIDEO))
@ -75,10 +81,11 @@ class MyPostWrapperFragment : BaseFragment_TabLayout() {
NewFlatLogUtils.logHaloSelfPublishTab(
when (position) {
0 -> "评价"
1 -> getString(R.string.collection_article)
2 -> getString(R.string.ask_search_questions)
3 -> getString(R.string.video)
4 -> getString(R.string.answer)
1 -> "图文"
2 -> getString(R.string.collection_article)
3 -> getString(R.string.ask_search_questions)
4 -> getString(R.string.video)
5 -> getString(R.string.answer)
else -> ""
}
)

View File

@ -36,29 +36,42 @@ class PersonalItemViewHolder(
) :
BaseAnswerOrArticleItemViewHolder(binding.root) {
fun bindPersonalItem(historyEntity: PersonalHistoryEntity, entrance: String, position: Int) {
fun bindPersonalItem(
historyEntity: PersonalHistoryEntity,
entrance: String,
position: Int,
payloads: List<Any?>? = null
) {
if (!payloads.isNullOrEmpty()) {
// 局部更新
commentCount.isClickable = true
bindCommendAndVote(historyEntity, entrance)
return
}
commentCount.isClickable = true
bindCommendAndVote(historyEntity, entrance)
if (historyEntity.community.type == "official_bbs") {
forumIcon?.displayGameIcon(historyEntity.community.icon, null)
} else {
forumIcon?.displayGameIcon(
historyEntity.community.game?.getIcon(),
historyEntity.community.game?.iconSubscript,
historyEntity.community.game?.iconFloat
)
}
forumNameContainer?.setOnClickListener {
MtaHelper.onEvent(getEventId(entrance), getKey(entrance), historyEntity.community.name)
itemView.context.startActivity(
ForumDetailActivity.getIntent(
itemView.context,
historyEntity.community.id,
entrance
forumNameContainer?.visibleIf(historyEntity.community.id.isNotBlank()) {
if (historyEntity.community.type == "official_bbs") {
forumIcon?.displayGameIcon(historyEntity.community.icon, null)
} else {
forumIcon?.displayGameIcon(
historyEntity.community.game?.getIcon(),
historyEntity.community.game?.iconSubscript,
historyEntity.community.game?.iconFloat
)
)
}
forumNameContainer?.setOnClickListener {
MtaHelper.onEvent(getEventId(entrance), getKey(entrance), historyEntity.community.name)
itemView.context.startActivity(
ForumDetailActivity.getIntent(
itemView.context,
historyEntity.community.id,
entrance
)
)
}
}
val answer = AnswerEntity()

View File

@ -1,57 +1,55 @@
package com.gh.gamecenter.personalhome.home
import android.app.Activity
import android.content.Context
import android.text.SpannableStringBuilder
import android.net.Uri
import android.util.SparseBooleanArray
import android.view.LayoutInflater
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 androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.alibaba.android.arouter.launcher.ARouter
import com.gh.gamecenter.R
import com.gh.gamecenter.adapter.viewholder.PersonalHomeRatingViewHolder
import com.gh.gamecenter.common.baselist.ListAdapter
import com.gh.gamecenter.common.callback.ConfirmListener
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.ItemViewType
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.databinding.RefreshFooterviewBinding
import com.gh.gamecenter.common.utils.TextHelper
import com.gh.gamecenter.common.utils.ifLogin
import com.gh.gamecenter.common.utils.setTextWithHighlightedTextWrappedInsideWrapper
import com.gh.gamecenter.common.utils.toBinding
import com.gh.gamecenter.common.viewholder.FooterViewHolder
import com.gh.gamecenter.core.utils.NumberUtils
import com.gh.gamecenter.core.utils.SpanBuilder
import com.gh.gamecenter.databinding.CommunityAnswerItemBinding
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.forum.home.follow.viewholder.FollowFooterViewHolder
import com.gh.gamecenter.forum.home.recommend.ImageArticleDetailActivity
import com.gh.gamecenter.forum.home.recommend.adapter.MyImageArticleAdapter.MyImageArticleViewHolder
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
import java.util.regex.Pattern
class UserHistoryAdapter(
context: Context,
private val mListViewModel: UserHistoryViewModel,
private val mEntrance: String,
private val itemClickCallback: (historyEntity: PersonalHistoryEntity, position: Int) -> Unit
private val itemClickCallback: (historyEntity: PersonalHistoryEntity, position: Int) -> Unit,
) : ListAdapter<PersonalHistoryEntity>(context) {
private var mExpandSparseBooleanArray = SparseBooleanArray()
override fun setListData(updateData: MutableList<PersonalHistoryEntity>?) {
super.setListData(updateData)
}
override fun getItemViewType(position: Int): Int {
return when {
position == itemCount - 1 -> ItemViewType.ITEM_FOOTER
mEntityList[position].type == "game_comment" -> ItemViewType.RATING_ITEM
mListViewModel.type == UserHistoryViewModel.TYPE.IMAGE_ARTICLE -> ITEM_TYPE_IMAGE_ARTICLE
else -> ItemViewType.ITEM_BODY
}
}
@ -61,6 +59,11 @@ class UserHistoryAdapter(
return when (viewType) {
ItemViewType.ITEM_FOOTER -> {
view = mLayoutInflater.inflate(com.gh.gamecenter.common.R.layout.refresh_footerview, parent, false)
val layoutParams = view.layoutParams
if (layoutParams is StaggeredGridLayoutManager.LayoutParams) {
layoutParams.isFullSpan = true
view.layoutParams = layoutParams
}
FooterViewHolder(view)
}
@ -69,6 +72,20 @@ class UserHistoryAdapter(
PersonalHomeRatingViewHolder(PersonalHomeRatingBinding.bind(view))
}
ITEM_TYPE_IMAGE_ARTICLE -> {
MyImageArticleViewHolder(parent.toBinding(),
object : MyImageArticleViewHolder.OnMyArticleImageListener {
override fun navigateToImageArticleDetailPage(item: PersonalHistoryEntity, position: Int) {
itemClickCallback(item, position)
}
override fun voteImageArticle(id: String, vote: Boolean) {
mListViewModel.useCase.voteImageArticle(id, vote)
}
})
}
else -> {
view = mLayoutInflater.inflate(R.layout.community_answer_item, parent, false)
PersonalItemViewHolder(mContext, CommunityAnswerItemBinding.bind(view), itemClickCallback)
@ -76,8 +93,26 @@ class UserHistoryAdapter(
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int, payloads: List<Any?>) {
if (payloads.isNotEmpty()) {
when (holder) {
is PersonalItemViewHolder -> {
bindNormalItem(holder, position, payloads)
}
is MyImageArticleViewHolder -> {
bindImageArticle(holder, position, payloads)
}
}
} else {
super.onBindViewHolder(holder, position, payloads)
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is MyImageArticleViewHolder -> bindImageArticle(holder, position)
is PersonalItemViewHolder -> bindNormalItem(holder, position)
is PersonalHomeRatingViewHolder -> bindRatingItem(holder, position)
is FooterViewHolder -> {
@ -87,10 +122,18 @@ class UserHistoryAdapter(
}
}
private fun bindNormalItem(holder: PersonalItemViewHolder, position: Int) {
val historyEntity = mEntityList[holder.adapterPosition]
private fun bindImageArticle(
holder: MyImageArticleViewHolder,
position: Int,
payloads: List<Any?>? = null
) {
val historyEntity = mEntityList[position]
holder.bind(historyEntity, position, payloads)
}
holder.bindPersonalItem(historyEntity, mEntrance, position)
private fun bindNormalItem(holder: PersonalItemViewHolder, position: Int, payloads: List<Any?>? = null) {
val historyEntity = mEntityList[holder.adapterPosition]
holder.bindPersonalItem(historyEntity, mEntrance, position, payloads)
}
private fun bindRatingItem(holder: PersonalHomeRatingViewHolder, position: Int) {
@ -114,7 +157,10 @@ class UserHistoryAdapter(
val m = Pattern.compile(RatingEditActivity.LABEL_REGEX).matcher(historyEntity.comment.content)
if (m.find()) {
val contents =
TextHelper.getCommentLabelSpannableStringBuilder(historyEntity.comment.content, com.gh.gamecenter.common.R.color.text_theme)
TextHelper.getCommentLabelSpannableStringBuilder(
historyEntity.comment.content,
com.gh.gamecenter.common.R.color.text_theme
)
holder.binding.content.setTextWithHighlightedTextWrappedInsideWrapper(
text = contents,
highlightedTextClickListener = TextHelper.DirectToWebViewHighlightedTextClick(mContext, mEntrance)
@ -159,4 +205,9 @@ class UserHistoryAdapter(
override fun getItemCount(): Int {
return if (mEntityList == null || mEntityList.isEmpty()) 0 else mEntityList.size + 1
}
companion object {
const val USER_HISTORY_PAYLOADS_VOTE_CHANGED = "user_history_payloads_vote_changed"
private const val ITEM_TYPE_IMAGE_ARTICLE = 110
}
}

View File

@ -2,26 +2,38 @@ package com.gh.gamecenter.personalhome.home
import android.app.Activity
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.view.View
import android.widget.TextView
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.ItemDecoration
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.alibaba.android.arouter.launcher.ARouter
import com.gh.gamecenter.core.AppExecutor
import com.gh.common.util.*
import com.gh.common.util.NewFlatLogUtils
import com.gh.gamecenter.R
import com.gh.gamecenter.common.baselist.ListAdapter
import com.gh.gamecenter.common.baselist.ListFragment
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.entity.CommunityEntity
import com.gh.gamecenter.feature.entity.CommentEntity
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.common.view.FixLinearLayoutManager
import com.gh.gamecenter.core.utils.MD5Utils
import com.gh.gamecenter.databinding.FragmentUserPublishBinding
import com.gh.gamecenter.entity.*
import com.gh.gamecenter.eventbus.EBImageArticleChanged
import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.feature.entity.PersonalEntity
import com.gh.gamecenter.forum.home.ForumScrollCalculatorHelper
import com.gh.gamecenter.forum.home.recommend.ImageArticleDetailActivity
import com.gh.gamecenter.forum.home.recommend.fragment.ImageArticleHomeFragment
import com.gh.gamecenter.gamedetail.rating.RatingReplyActivity
import com.gh.gamecenter.personalhome.home.UserHistoryAdapter.Companion.USER_HISTORY_PAYLOADS_VOTE_CHANGED
import com.gh.gamecenter.qa.article.detail.ArticleDetailActivity
import com.gh.gamecenter.qa.entity.ArticleDetailEntity
import com.gh.gamecenter.qa.entity.QuestionsDetailEntity
@ -30,6 +42,8 @@ import com.gh.gamecenter.qa.video.detail.ForumVideoDetailActivity
import com.gh.gamecenter.video.detail.CustomManager
import com.halo.assistant.HaloApp
import com.shuyu.gsyvideoplayer.video.base.GSYVideoView
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
class UserHistoryFragment : ListFragment<PersonalHistoryEntity, UserHistoryViewModel>() {
@ -79,6 +93,68 @@ class UserHistoryFragment : ListFragment<PersonalHistoryEntity, UserHistoryViewM
})
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(changed: EBImageArticleChanged) {
when {
changed is EBImageArticleChanged.VoteChanged -> {
updateImageArticleItem(changed.id, true) {
val newCount = it.count
newCount.vote = changed.count
it.count = newCount
it.me.isCommunityArticleVote = changed.isVoted
}
}
changed is EBImageArticleChanged.CommentChanged -> {
updateImageArticleItem(changed.id, true) {
val newCount = it.count
newCount.comment = changed.count
it.count = newCount
}
}
changed is EBImageArticleChanged.DataDeleted -> {
mViewModel?.deleteImageArticle(changed.imageArticleId)
}
changed is EBImageArticleChanged.DataChanged -> {
val imageArticle = changed.data
updateImageArticleItem(changed.data.id, false) {
it.title = imageArticle.title
it.brief = imageArticle.content
it.images = imageArticle.images
it.imagesInfo = imageArticle.imagesInfos
it.community = imageArticle.community?.toCommunityEntity() ?: CommunityEntity()
it.sections = imageArticle.sections
it.count = imageArticle.count
it.user = imageArticle.user
it.time = imageArticle.time.update
it.status = imageArticle.status
}
}
}
}
private fun updateImageArticleItem(
id: String,
hasPayload: Boolean,
block: (PersonalHistoryEntity) -> Unit
) {
val dataList = mAdapter?.entityList ?: return
val position =
dataList.indexOfFirst { it.type == ImageArticleEntity.IMAGE_ARTICLE_TYPE && it.id == id }
if (position != -1) {
val item = dataList[position]
block(item)
if (hasPayload) {
mAdapter?.notifyItemChanged(position, USER_HISTORY_PAYLOADS_VOTE_CHANGED)
} else {
mAdapter?.notifyItemChanged(position)
}
}
}
override fun onResume() {
resumeVideo()
super.onResume()
@ -148,12 +224,14 @@ class UserHistoryFragment : ListFragment<PersonalHistoryEntity, UserHistoryViewM
mBinding?.run {
allType.text = "全部 ${mCount.getTotalCount()}"
videoType.text = "视频 ${mCount.video}"
imageArticleType.text = getString(R.string.image_article_with_count, "${mCount.imageArticle}")
articleType.text = "帖子 ${mCount.communityArticle}"
questionType.text = "提问 ${mCount.question}"
answerType.text = "回答 ${mCount.answer}"
val typePairList = arrayListOf(
Pair(UserHistoryViewModel.TYPE.ALL, allType),
Pair(UserHistoryViewModel.TYPE.IMAGE_ARTICLE, imageArticleType),
Pair(UserHistoryViewModel.TYPE.VIDEO, videoType),
Pair(UserHistoryViewModel.TYPE.COMMUNITY_ARTICLE, articleType),
Pair(UserHistoryViewModel.TYPE.QUESTION, questionType),
@ -208,18 +286,26 @@ class UserHistoryFragment : ListFragment<PersonalHistoryEntity, UserHistoryViewM
UserHistoryViewModel.TYPE.ALL -> {
allType.text = "全部 $count"
}
UserHistoryViewModel.TYPE.IMAGE_ARTICLE -> {
imageArticleType.text = getString(R.string.image_article_with_count, "$count")
}
UserHistoryViewModel.TYPE.VIDEO -> {
mCount.video = count
videoType.text = "视频 $count"
}
UserHistoryViewModel.TYPE.COMMUNITY_ARTICLE -> {
mCount.communityArticle = count
articleType.text = "帖子 $count"
}
UserHistoryViewModel.TYPE.QUESTION -> {
mCount.question = count
questionType.text = "提问 $count"
}
UserHistoryViewModel.TYPE.ANSWER -> {
mCount.answer = count
answerType.text = "回答 $count"
@ -288,6 +374,13 @@ class UserHistoryFragment : ListFragment<PersonalHistoryEntity, UserHistoryViewM
104,
position
)
} else if (historyEntity.type == "image_article") {
val imageArticleUri = Uri.Builder()
.path(RouteConsts.activity.imageArticleDetailActivity)
.appendQueryParameter(EntranceConsts.KEY_IMAGE_ARTICLE_ID, historyEntity.id)
.appendQueryParameter(EntranceConsts.KEY_SOURCE_ENTRANCE, "个人主页-发布")
.build()
ARouter.getInstance().build(imageArticleUri).navigation()
} else if (!historyEntity.type.contains("question")) {
val intent = NewQuestionDetailActivity.getSpecifiedCommentIntent(
requireContext(),
@ -320,7 +413,7 @@ class UserHistoryFragment : ListFragment<PersonalHistoryEntity, UserHistoryViewM
NewFlatLogUtils.logClickSelfPublish(
historyEntity.type,
historyEntity.id,
historyEntity.user?.id ?: "",
historyEntity.user.id ?: "",
getCurrentTabName()
)
}
@ -329,9 +422,35 @@ class UserHistoryFragment : ListFragment<PersonalHistoryEntity, UserHistoryViewM
return mAdapter!!
}
override fun onChanged(ts: MutableList<PersonalHistoryEntity>?) {
if (ts != null) {
val layoutManager = mListRv.layoutManager ?: return
if (mViewModel?.type == UserHistoryViewModel.TYPE.IMAGE_ARTICLE) {
if (layoutManager !is StaggeredGridLayoutManager) {
mListRv.layoutManager = StaggeredGridLayoutManager(2, RecyclerView.VERTICAL)
if (mListRv.itemDecorationCount == 0) {
itemDecoration?.let(mListRv::addItemDecoration)
}
mListRv.adapter = mAdapter
}
} else {
if (layoutManager is StaggeredGridLayoutManager) {
mListRv.layoutManager = FixLinearLayoutManager(requireContext())
if (mListRv.itemDecorationCount > 0) {
mItemDecoration?.let(mListRv::removeItemDecoration)
}
mListRv.adapter = mAdapter
}
}
}
super.onChanged(ts)
}
private fun getCurrentTabName(): String {
return when (mCurrentType) {
UserHistoryViewModel.TYPE.ALL -> "全部"
UserHistoryViewModel.TYPE.IMAGE_ARTICLE -> "图文"
UserHistoryViewModel.TYPE.VIDEO -> "视频"
UserHistoryViewModel.TYPE.COMMUNITY_ARTICLE -> "帖子"
UserHistoryViewModel.TYPE.QUESTION -> "提问"
@ -339,7 +458,14 @@ class UserHistoryFragment : ListFragment<PersonalHistoryEntity, UserHistoryViewM
}
}
override fun getItemDecoration() = null
override fun getItemDecoration(): ItemDecoration? {
mItemDecoration = if (mCurrentType == UserHistoryViewModel.TYPE.IMAGE_ARTICLE) {
ImageArticleHomeFragment.MyItemDecoration()
} else {
null
}
return mItemDecoration
}
override fun onRefresh() {
mScrollCalculatorHelper?.currentPlayer?.release()
@ -398,6 +524,7 @@ class UserHistoryFragment : ListFragment<PersonalHistoryEntity, UserHistoryViewM
me.isVoted = resultData?.me?.isVoted ?: false
}
}
101 -> {
val resultData =
data.getParcelableExtra<ArticleDetailEntity>(ArticleDetailEntity::class.java.simpleName)
@ -411,6 +538,7 @@ class UserHistoryFragment : ListFragment<PersonalHistoryEntity, UserHistoryViewM
me.isCommunityArticleVote = resultData?.me?.isCommunityArticleVote ?: false
}
}
102 -> {
val resultData =
data.getParcelableExtra<CommentEntity>(CommentEntity::class.java.simpleName)
@ -421,6 +549,8 @@ class UserHistoryFragment : ListFragment<PersonalHistoryEntity, UserHistoryViewM
me.isAnswerVoted = resultData?.me?.isCommentVoted ?: false
}
}
103 -> {
val resultData =
data.getParcelableExtra<QuestionsDetailEntity>(QuestionsDetailEntity::class.java.simpleName)
@ -431,6 +561,7 @@ class UserHistoryFragment : ListFragment<PersonalHistoryEntity, UserHistoryViewM
}
}
}
104 -> {
val resultData =
data.getParcelableExtra<ForumVideoEntity>(ForumVideoEntity::class.java.simpleName)
@ -468,6 +599,7 @@ class UserHistoryFragment : ListFragment<PersonalHistoryEntity, UserHistoryViewM
const val DEFAULT_TYPE = "default_type"
const val PATH_USER_COMMENT = "个人主页-评论"
const val PATH_USER_QUESTION_ANSWER = "个人主页-问答"
fun getInstance(
userId: String,
scene: UserHistoryViewModel.SCENE,

View File

@ -16,11 +16,16 @@ import com.gh.gamecenter.common.entity.ErrorEntity
import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.entity.PersonalHistoryEntity
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.entity.ImageArticleEntity
import com.gh.gamecenter.eventbus.EBImageArticleChanged
import com.gh.gamecenter.forum.home.recommend.ImageArticleRepository
import com.gh.gamecenter.forum.home.recommend.ImageArticleUseCase
import com.gh.gamecenter.retrofit.RetrofitManager
import com.halo.assistant.HaloApp
import com.lightgame.utils.Utils
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.schedulers.Schedulers
import okhttp3.ResponseBody
import retrofit2.HttpException
@ -36,6 +41,10 @@ class UserHistoryViewModel(
var videoList = arrayListOf<ForumVideoEntity>()
var count = MutableLiveData<Int>()
private val imageArticleRepository = ImageArticleRepository.newInstance()
private val compositeDisposable = CompositeDisposable()
val useCase = ImageArticleUseCase(imageArticleRepository, compositeDisposable)
init {
setOverLimitSize(1)
}
@ -50,7 +59,6 @@ class UserHistoryViewModel(
override fun mergeResultLiveData() {
mResultLiveData.addSource(mListLiveData) { list ->
videoList = ArrayList(list.map { it.transformForumVideoEntity() })
mResultLiveData.postValue(list)
@ -132,6 +140,16 @@ class UserHistoryViewModel(
}
}
fun deleteImageArticle(imageArticleId: String) {
val oldData = mResultLiveData.value ?: return
val newData = oldData.toMutableList()
val hasRemoved =
newData.removeAll { it.type == ImageArticleEntity.IMAGE_ARTICLE_TYPE && it.id == imageArticleId }
if (hasRemoved) {
mResultLiveData.value = newData
}
}
enum class SCENE(val value: String) {
COMMENT("comment"),
QUESTION_ANSWER("question_answer")
@ -139,6 +157,7 @@ class UserHistoryViewModel(
enum class TYPE(val value: String) {
ALL("all"),
IMAGE_ARTICLE("image_article"),
VIDEO("video"),
COMMUNITY_ARTICLE("community_article"),
ANSWER("answer"),

View File

@ -1,20 +1,25 @@
package com.gh.gamecenter.qa.answer
import android.annotation.SuppressLint
import android.net.Uri
import android.view.View
import android.widget.TextView
import com.airbnb.lottie.LottieAnimationView
import com.alibaba.android.arouter.launcher.ARouter
import com.gh.common.util.*
import com.gh.common.util.LogUtils
import com.gh.common.util.NewLogUtils
import com.gh.gamecenter.feature.view.GameIconView
import com.gh.gamecenter.R
import com.gh.gamecenter.common.base.BaseRecyclerViewHolder
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.entity.CommunityEntity
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.ImageArticleEntity
import com.gh.gamecenter.entity.PersonalHistoryEntity
import com.gh.gamecenter.entity.VoteEntity
import com.gh.gamecenter.forum.detail.ForumDetailActivity
@ -24,6 +29,7 @@ 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.forum.home.recommend.ImageArticleDetailActivity
import com.gh.gamecenter.qa.entity.QuestionsDetailEntity
import com.gh.gamecenter.qa.questions.invite.QuestionsInviteActivity
import com.gh.gamecenter.qa.questions.newdetail.NewQuestionDetailActivity
@ -150,6 +156,7 @@ open class BaseAnswerOrArticleItemViewHolder(itemView: View) : BaseRecyclerViewH
open fun bindCommendAndVote(entity: CommunityItemData, entrance: String, position: Int? = null) {
binNormalView(entity)
forumNameContainer?.visibleIf(entity.community.id.isNotBlank())
if (entity.community.type == "official_bbs") {
forumIcon?.displayGameIcon(entity.community.icon, null)
} else {
@ -218,6 +225,7 @@ open class BaseAnswerOrArticleItemViewHolder(itemView: View) : BaseRecyclerViewH
val commentType = when (entity.type) {
"community_article" -> "帖子评论"
"video" -> "视频帖评论"
ImageArticleEntity.IMAGE_ARTICLE_TYPE -> "图文"
else -> "提问帖评论"
}
NewLogUtils.logRecommendFeedContentClick(
@ -267,6 +275,14 @@ open class BaseAnswerOrArticleItemViewHolder(itemView: View) : BaseRecyclerViewH
)
}
ImageArticleEntity.IMAGE_ARTICLE_TYPE -> {
val imageArticleUri = Uri.Builder()
.path(RouteConsts.activity.imageArticleDetailActivity)
.appendQueryParameter(EntranceConsts.KEY_IMAGE_ARTICLE_ID, entity.id)
.build()
ARouter.getInstance().build(imageArticleUri).navigation()
}
else -> {
val communityId = entity.community.id
val intent = ArticleDetailActivity.getCommentIntent(
@ -398,19 +414,27 @@ open class BaseAnswerOrArticleItemViewHolder(itemView: View) : BaseRecyclerViewH
}
})
} else {
val voteObservable = if (entity.type == "community_article") {
RetrofitManager.getInstance().api.postCommunityArticleVote(entity.id)
} else {
RetrofitManager.getInstance().api
.postVoteQuestionComment(entity.questions.id, entity.id)
.map { GsonUtils.fromJson(it.string(), VoteEntity::class.java) }
when (entity.type) {
"community_article" -> {
RetrofitManager.getInstance().api.postCommunityArticleVote(entity.id)
}
ImageArticleEntity.IMAGE_ARTICLE_TYPE -> {
RetrofitManager.getInstance().api.voteArticleImage(entity.id)
.toObservable()
}
else -> {
RetrofitManager.getInstance().api
.postVoteQuestionComment(entity.questions.id, entity.id)
.map { GsonUtils.fromJson(it.string(), VoteEntity::class.java) }
}
}
voteObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Response<VoteEntity>() {
override fun onResponse(response: VoteEntity?) {
if (entity.type == "community_article") {
if (entity.type == "community_article" || entity.type == ImageArticleEntity.IMAGE_ARTICLE_TYPE) {
entity.me.isCommunityArticleVote = true
} else {
entity.me.isAnswerVoted = true
@ -484,18 +508,26 @@ open class BaseAnswerOrArticleItemViewHolder(itemView: View) : BaseRecyclerViewH
}
})
} else {
val unVoteObservable = if (entity.type == "community_article") {
RetrofitManager.getInstance().api.postCommunityArticleUnVote(entity.id)
} else {
RetrofitManager.getInstance().api
.postAnswerUnvote(entity.id)
when (entity.type) {
"community_article" -> {
RetrofitManager.getInstance().api.postCommunityArticleUnVote(entity.id)
}
ImageArticleEntity.IMAGE_ARTICLE_TYPE -> {
RetrofitManager.getInstance().api.unVoteArticleImage(entity.id)
.toObservable()
}
else -> {
RetrofitManager.getInstance().api
.postAnswerUnvote(entity.id)
}
}
unVoteObservable
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(object : Response<VoteEntity>() {
override fun onResponse(response: VoteEntity?) {
if (entity.type == "community_article") {
if (entity.type == "community_article" || entity.type == ImageArticleEntity.IMAGE_ARTICLE_TYPE) {
entity.me.isCommunityArticleVote = false
} else {
entity.me.isAnswerVoted = false

View File

@ -13,7 +13,6 @@ import com.gh.gamecenter.common.utils.viewModelProvider
import com.gh.gamecenter.common.view.CustomDividerItemDecoration
import com.gh.gamecenter.qa.comment.base.BaseCommentAdapter
import com.halo.assistant.HaloApp
import splitties.views.backgroundColor
class ArticleDetailCommentListFragment : ListFragment<CommentItemData, ArticleDetailCommentListViewModel>() {

View File

@ -793,7 +793,8 @@ class ArticleDetailFragment : BaseCommentFragment<CommentItemData, ArticleDetail
getString(R.string.article_detail_more_complaint_title) -> {
ifLogin("帖子详情") {
BbsReportHelper.showReportDialog(mViewModel.detailEntity?.id ?: "")
BbsReportHelper.showReportDialog(BbsReportHelper.PostsReporter(mViewModel.detailEntity?.id ?: ""))
}
NewLogUtils.logSharePanelClick(
"click_report",
@ -991,7 +992,6 @@ class ArticleDetailFragment : BaseCommentFragment<CommentItemData, ArticleDetail
mBinding.inputContainer.bottomCommentTv.text = mViewModel.getCommentText(
mViewModel.detailEntity?.count?.comment ?: 0, "评论"
)
ImageUtils.display(mBinding.userAvatar, articleDetail.user.icon)
if (!mEntrance.contains("论坛详情")) {
@ -1041,7 +1041,7 @@ class ArticleDetailFragment : BaseCommentFragment<CommentItemData, ArticleDetail
"帖子详情页", "slide_article_detail_page", (filterView?.top ?: 0) > 0
)
if ((filterView?.top ?: 0) > 0) {
NewLogUtils.logCommentAreaEnter("帖子")
NewLogUtils.logCommentAreaEnter("图文")
}
}
}

View File

@ -1,5 +1,6 @@
package com.gh.gamecenter.qa.article.detail
import com.gh.gamecenter.entity.ImageArticleEntity
import com.gh.gamecenter.feature.entity.ArticleEntity
import com.gh.gamecenter.feature.entity.CommentEntity
import com.gh.gamecenter.feature.entity.GameEntity
@ -18,7 +19,8 @@ class CommentItemData(
var filter: Boolean? = null,
var errorConnection: Boolean? = null,
var errorEmpty: Boolean? = null,
var footer: Boolean? = null
var footer: Boolean? = null,
val imageArticleDetail: ImageArticleEntity? = null,
) {
class CommentPagerData(val totalCommentNum: Int = 0, val showRelatedContent: Boolean = true)
}

View File

@ -49,6 +49,7 @@ class CommentActivity : BaseActivity() {
val commentId = intent.getStringExtra(KEY_COMMENT_ID) ?: ""
val gameCollectionId = intent.getStringExtra(GAME_COLLECTION_ID) ?: ""
val gameCollectionTitle = intent.getStringExtra(GAME_COLLECTION_TITLE) ?: ""
val imageArticleId = intent.getStringExtra(IMAGE_ARTICLE_ID) ?: ""
val isVideoAuthor = intent.getBooleanExtra(IS_VIDEO_AUTHOR, false)
val isStairsComment = intent.getBooleanExtra(IS_STAIRS_COMMENT, false)
@ -82,9 +83,11 @@ class CommentActivity : BaseActivity() {
isCommentConversation && gameCollectionId.isNotEmpty() && commentId.isNotEmpty() -> {
GameCollectionCommentConversationFragment().with(intent.extras)
}
isCommentConversation && commentId.isNotEmpty() -> {
CommentConversationFragment().with(intent.extras)
}
answerId.isNotEmpty() -> {
NewCommentFragment.getAnswerCommentInstance(
answerId,
@ -94,6 +97,7 @@ class CommentActivity : BaseActivity() {
commentCallback
)
}
articleId.isNotEmpty() -> {
NewCommentFragment.getCommunityArticleCommentInstance(
articleId,
@ -106,6 +110,7 @@ class CommentActivity : BaseActivity() {
commentCallback
)
}
questionId.isNotEmpty() -> {
NewCommentFragment.getCommunityQuestionCommentInstance(
questionId,
@ -118,6 +123,7 @@ class CommentActivity : BaseActivity() {
commentCallback
)
}
gameCollectionId.isNotEmpty() -> {
NewCommentFragment.getGameCollectionCommentInstance(
gameCollectionId,
@ -131,6 +137,19 @@ class CommentActivity : BaseActivity() {
commentCallback
)
}
imageArticleId.isNotEmpty() -> {
NewCommentFragment.getImageArticleCommentInstance(
imageArticleId,
showKeyboard,
commentCount,
mShowInputOnly,
commentEntity,
useReplyApi,
commentCallback
)
}
else -> {
NewCommentFragment.getVideoCommentInstance(
videoId,
@ -197,6 +216,8 @@ class CommentActivity : BaseActivity() {
const val REQUEST_CODE = 8123
const val IMAGE_ARTICLE_ID = "image_article_id"
@JvmStatic
fun getAnswerCommentIntent(
context: Context,
@ -280,6 +301,7 @@ class CommentActivity : BaseActivity() {
articleId: String,
videoId: String,
questionId: String,
imageArticleId: String,
showKeyboard: Boolean = false,
position: Int = -1,
entrance: String,
@ -290,6 +312,7 @@ class CommentActivity : BaseActivity() {
intent.putExtra(EntranceConsts.KEY_COMMUNITY_ARTICLE_ID, articleId)
intent.putExtra(VIDEO_ID, videoId)
intent.putExtra(QUESTION_ID, questionId)
intent.putExtra(IMAGE_ARTICLE_ID, imageArticleId)
intent.putExtra(EntranceConsts.KEY_COMMUNITY_ID, communityId)
intent.putExtra(EntranceConsts.KEY_POSITION, position)
intent.putExtra(KEY_COMMENT_ID, commentId)
@ -446,6 +469,30 @@ class CommentActivity : BaseActivity() {
return intent
}
/**
* 回复图文评论
*/
fun getImageArticleCommentReplyIntent(
context: Context,
imageArticleId: String,
commentId: String,
commentCount: Int? = 0,
commentEntity: CommentEntity? = null
): Intent {
val intent = Intent(context, CommentActivity::class.java)
intent.putExtra(KEY_COMMENT_ID, commentId)
intent.putExtra(COMMENT_COUNT, commentCount)
intent.putExtra(SHOW_KEYBOARD, true)
intent.putExtra(SHOW_INPUT_ONLY, true)
intent.putExtra(COMMENT_ENTITY, commentEntity)
intent.putExtra(IMAGE_ARTICLE_ID, imageArticleId)
intent.putExtra(USE_REPLY_API, true)
if (context is Activity) {
context.overridePendingTransition(0, 0)
}
return intent
}
/**
* 游戏单评论对话
*/
@ -467,6 +514,30 @@ class CommentActivity : BaseActivity() {
intent.putExtra(EntranceConsts.KEY_IS_COMMENT_CONVERSATION, true)
return intent
}
/**
* 评论图文
*/
@JvmStatic
fun getImageArticleCommentIntent(
context: Context,
imageArticleId: String,
communityId: String,
commentCount: Int? = 0,
commentEntity: CommentEntity? = null,
): Intent {
val intent = Intent(context, CommentActivity::class.java)
intent.putExtra(IMAGE_ARTICLE_ID, imageArticleId)
intent.putExtra(COMMENT_COUNT, commentCount)
intent.putExtra(SHOW_KEYBOARD, true)
intent.putExtra(COMMUNITY_ID, communityId)
intent.putExtra(SHOW_INPUT_ONLY, true)
intent.putExtra(COMMENT_ENTITY, commentEntity)
if (context is Activity) {
context.overridePendingTransition(0, 0)
}
return intent
}
}
interface CommentListener {

View File

@ -172,6 +172,7 @@ class NewCommentAdapter(
mViewModel.communityId,
mViewModel.videoId,
mViewModel.questionId,
mViewModel.imageArticleId,
commentEntity,
holder.commentLikeCountTv,
holder.commentLikeIv,
@ -230,6 +231,7 @@ class NewCommentAdapter(
mCommentOptionClickListener
)
}
else -> {
//do nothing
}
@ -251,6 +253,9 @@ class NewCommentAdapter(
CommentType.GAME_COLLECTION,
CommentType.GAME_COLLECTION_CONVERSATION -> "游戏单详情-评论管理"
CommentType.IMAGE_ARTICLE,
CommentType.IMAGE_ARTICLE_CONVERSATION -> "图文详情-评论管理"
}
val userHomePageTabPosition = if (mViewModel.commentType.isVideo()) 2 else 1

View File

@ -44,9 +44,12 @@ class NewCommentConversationFragment : NewCommentFragment() {
mVideoId = arguments?.getString(CommentActivity.VIDEO_ID) ?: ""
mIsVideoAuthor = arguments?.getBoolean(CommentActivity.IS_VIDEO_AUTHOR, false) ?: false
mImageArticleId = arguments?.getString(CommentActivity.IMAGE_ARTICLE_ID) ?: ""
mCommentType = when {
mAnswerId.isNotEmpty() -> CommentType.ANSWER_CONVERSATION
mArticleId.isNotEmpty() -> CommentType.COMMUNITY_ARTICLE_CONVERSATION
mImageArticleId.isNotEmpty() -> CommentType.IMAGE_ARTICLE_CONVERSATION
else -> CommentType.VIDEO_CONVERSATION
}

View File

@ -7,6 +7,7 @@ import com.gh.gamecenter.common.base.activity.ToolBarActivity
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.utils.updateStatusBarColor
import com.gh.gamecenter.gamecollection.detail.conversation.GameCollectionCommentConversationFragment
import com.gh.gamecenter.qa.comment.CommentActivity.Companion.IMAGE_ARTICLE_ID
import com.gh.gamecenter.qa.comment.conversation.CommentConversationFragment
/**
@ -123,6 +124,26 @@ class NewCommentDetailActivity : ToolBarActivity() {
)
}
@JvmStatic
fun getImageArticleCommentIntent(
context: Context,
commentId:String,
imageArticleId:String,
topCommentId:String,
entrance: String,
path:String
):Intent{
val bundle = getCommonBundle(commentId, topCommentId, entrance, path).apply {
putString(IMAGE_ARTICLE_ID,imageArticleId)
}
return getTargetIntent(
context,
NewCommentDetailActivity::class.java,
CommentConversationFragment::class.java,
bundle
)
}
private fun getCommonBundle(
commentId: String,
topCommentId: String,

View File

@ -42,6 +42,7 @@ import com.gh.gamecenter.feature.eventbus.EBDeleteComment
import com.gh.gamecenter.feature.selector.ChooseType
import com.gh.gamecenter.qa.comment.CommentActivity.Companion.GAME_COLLECTION_ID
import com.gh.gamecenter.qa.comment.CommentActivity.Companion.GAME_COLLECTION_TITLE
import com.gh.gamecenter.qa.comment.CommentActivity.Companion.IMAGE_ARTICLE_ID
import com.gh.gamecenter.qa.comment.CommentActivity.Companion.QUESTION_ID
import com.halo.assistant.HaloApp
import com.lightgame.utils.Util_System_Keyboard
@ -82,6 +83,7 @@ open class NewCommentFragment : ListFragment<CommentEntity, NewCommentViewModel>
protected var mGameCollectionTitle: String = ""
protected var mCommentId: String = ""
protected var mRootCommentId: String = ""
protected var mImageArticleId: String = ""
protected var mShowInputOnly: Boolean = false // 是否只显示输入框,不显示列表
protected var mIsVideoAuthor: Boolean = false//是否是视频作者
protected var mCommentType = CommentType.ANSWER
@ -130,6 +132,7 @@ open class NewCommentFragment : ListFragment<CommentEntity, NewCommentViewModel>
mShowInputOnly = getBoolean(SHOW_INPUT_ONLY, false)
mCommentEntity = getParcelable(COMMENT_ENTITY)
mIsVideoAuthor = getBoolean(IS_VIDEO_AUTHOR, false)
mImageArticleId = getString(IMAGE_ARTICLE_ID) ?: ""
}
super.onCreate(savedInstanceState)
@ -229,6 +232,9 @@ open class NewCommentFragment : ListFragment<CommentEntity, NewCommentViewModel>
CommentType.GAME_COLLECTION,
CommentType.GAME_COLLECTION_CONVERSATION -> "游戏单评论及回复"
CommentType.IMAGE_ARTICLE,
CommentType.IMAGE_ARTICLE_CONVERSATION-> "图文的评论和回复"
else -> ""
}
ErrorHelper.handleError(
@ -412,7 +418,8 @@ open class NewCommentFragment : ListFragment<CommentEntity, NewCommentViewModel>
gameCollectionId = mGameCollectionId,
rootCommentId = mRootCommentId,
commentType = mCommentType,
isVideoAuthor = mIsVideoAuthor
isVideoAuthor = mIsVideoAuthor,
imageArticleId = mImageArticleId
)
)
return mViewModel
@ -435,6 +442,9 @@ open class NewCommentFragment : ListFragment<CommentEntity, NewCommentViewModel>
CommentType.GAME_COLLECTION,
CommentType.GAME_COLLECTION_CONVERSATION -> "(游戏单详情-评论列表)"
CommentType.IMAGE_ARTICLE,
CommentType.IMAGE_ARTICLE_CONVERSATION -> "图文详情-评论列表"
}
mAdapter = NewCommentAdapter(requireContext(), mViewModel, true, this, this, entrance)
}
@ -598,6 +608,16 @@ open class NewCommentFragment : ListFragment<CommentEntity, NewCommentViewModel>
"游戏单详情-评论-回复"
}
}
CommentType.IMAGE_ARTICLE,
CommentType.IMAGE_ARTICLE_CONVERSATION-> {
if(mCommentEntity == null){
"图文详情-评论-写评论"
}else{
"图文详情-评论-回复"
}
}
}
}
@ -625,7 +645,12 @@ open class NewCommentFragment : ListFragment<CommentEntity, NewCommentViewModel>
mAnswerContent.background =
ContextCompat.getDrawable(requireActivity(), R.drawable.bg_shape_white_radius_10_top_only)
} else {
mAnswerContent.setBackgroundColor(ContextCompat.getColor(requireActivity(), com.gh.gamecenter.common.R.color.white))
mAnswerContent.setBackgroundColor(
ContextCompat.getColor(
requireActivity(),
com.gh.gamecenter.common.R.color.white
)
)
}
mScrollViewParams.width = if (isPopup) LinearLayout.LayoutParams.MATCH_PARENT else 0
mScrollViewParams.height = if (isPopup) 64f.dip2px() else 28f.dip2px()
@ -693,7 +718,10 @@ open class NewCommentFragment : ListFragment<CommentEntity, NewCommentViewModel>
if (picturePath != null) {
if (File(picturePath).length() > ImageUtils.getUploadFileMaxSize()) {
val count = ImageUtils.getUploadFileMaxSize() / 1024 / 1024
Utils.toast(requireContext(), requireContext().getString(com.gh.gamecenter.common.R.string.pic_max_hint, count))
Utils.toast(
requireContext(),
requireContext().getString(com.gh.gamecenter.common.R.string.pic_max_hint, count)
)
continue
}
Utils.log("picturePath = $picturePath")
@ -945,5 +973,35 @@ open class NewCommentFragment : ListFragment<CommentEntity, NewCommentViewModel>
)
}
}
fun getImageArticleCommentInstance(
imageArticleId: String,
showSoftKeyboardOnStartUp: Boolean,
commentCount: Int,
showInputOnly: Boolean,
commentEntity: CommentEntity?,
useReplyApi: Boolean,
listener: CommentActivity.CommentListener
): NewCommentFragment {
val commentType = if (useReplyApi) {
CommentType.IMAGE_ARTICLE_CONVERSATION
} else {
CommentType.IMAGE_ARTICLE
}
return NewCommentFragment().apply {
mCommentListener = listener
with(
bundleOf(
SHOW_SOFT_KEY_BOARD_ON_STARTUP to showSoftKeyboardOnStartUp,
IMAGE_ARTICLE_ID to imageArticleId,
COMMENT_COUNT to commentCount,
COMMENT_TYPE to commentType,
SHOW_INPUT_ONLY to showInputOnly,
COMMENT_ENTITY to commentEntity
)
)
}
}
}
}

View File

@ -6,6 +6,7 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.common.baselist.ListViewModel
import com.gh.gamecenter.common.retrofit.ApiResponse
import com.gh.gamecenter.common.retrofit.BiResponse
@ -16,6 +17,7 @@ import com.gh.gamecenter.common.syncpage.SyncPageRepository
import com.gh.gamecenter.common.utils.*
import com.gh.gamecenter.core.utils.GsonUtils
import com.gh.gamecenter.entity.CommentDraft
import com.gh.gamecenter.entity.ImageArticleEntity
import com.gh.gamecenter.feature.entity.CommentEntity
import com.gh.gamecenter.feature.entity.ForumVideoEntity
import com.gh.gamecenter.qa.entity.ArticleDetailEntity
@ -24,12 +26,12 @@ import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.room.AppDatabase
import com.gh.gamecenter.room.dao.CommentDraftDao
import com.google.gson.reflect.TypeToken
import com.halo.assistant.HaloApp
import com.lightgame.utils.Utils
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import okhttp3.ResponseBody
import org.json.JSONArray
import org.json.JSONObject
import retrofit2.HttpException
@ -44,7 +46,8 @@ open class NewCommentViewModel(
var gameCollectionId: String = "",
var rootCommentId: String = "",
var commentType: CommentType = CommentType.ANSWER,
var isVideoAuthor: Boolean = false
var isVideoAuthor: Boolean = false,
var imageArticleId: String = ""
) : ListViewModel<CommentEntity, CommentEntity>(application) {
private val mPostCommentLiveData = MutableLiveData<ApiResponse<JSONObject>>()
@ -60,6 +63,7 @@ open class NewCommentViewModel(
var articleDetail: ArticleDetailEntity? = null
var questionDetail: QuestionsDetailEntity? = null
var videoDetail: ForumVideoEntity? = null
private var imageArticleDetail: ImageArticleEntity? = null
init {
commentDraftDao = AppDatabase.getInstance().commentDraftDao()
@ -138,6 +142,7 @@ open class NewCommentViewModel(
this.articleDetail = it
}, {})
}
CommentType.VIDEO,
CommentType.VIDEO_CONVERSATION -> {
api.getBbsVideoDetail(videoId)
@ -147,6 +152,7 @@ open class NewCommentViewModel(
this.videoDetail = it
}, {})
}
CommentType.COMMUNITY_QUESTION,
CommentType.COMMUNITY_QUESTION_CONVERSATION -> {
api.getQuestionsById(questionId)
@ -156,6 +162,22 @@ open class NewCommentViewModel(
this.questionDetail = it
}, {})
}
CommentType.IMAGE_ARTICLE,
CommentType.GAME_COLLECTION_CONVERSATION -> {
api.loadImageArticleDetail(
imageArticleId,
"detail",
BuildConfig.VERSION_NAME,
HaloApp.getInstance().channel
)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
this.imageArticleDetail = it
}, {})
}
else -> {}
}
}
@ -165,8 +187,7 @@ open class NewCommentViewModel(
CommentType.COMMUNITY_QUESTION,
CommentType.COMMUNITY_QUESTION_CONVERSATION -> {
questionDetail?.let {
if (reply)
{
if (reply) {
SensorsBridge.trackArticleReply(
customerType = it.user.auth?.text ?: "",
articleId = it.id ?: "",
@ -244,6 +265,33 @@ open class NewCommentViewModel(
}
}
CommentType.IMAGE_ARTICLE,
CommentType.IMAGE_ARTICLE_CONVERSATION -> {
imageArticleDetail?.let {
if (reply) {
SensorsBridge.trackArticleReply(
customerType = it.user.auth?.text ?: "",
articleId = it.id,
bbsId = it.community?.id ?: "",
bbsType = it.community?.typeChinese ?: "",
activityTag = "",
gameForumType = it.community?.game?.categoryChinese ?: "",
articleType = "图文"
)
} else {
SensorsBridge.trackArticleComment(
customerType = it.user.auth?.text ?: "",
articleId = it.id,
bbsId = it.community?.id ?: "",
bbsType = it.community?.typeChinese ?: "",
activityTag = "",
gameForumType = it.community?.game?.categoryChinese ?: "",
articleType = "图文"
)
}
}
}
else -> {}
}
}
@ -337,6 +385,36 @@ open class NewCommentViewModel(
}
}
CommentType.IMAGE_ARTICLE,
CommentType.IMAGE_ARTICLE_CONVERSATION -> {
imageArticleDetail?.let {
if (reply) {
SensorsBridge.trackArticleReplyResult(
customerType = it.user.auth?.text ?: "",
articleId = it.id,
bbsId = it.community?.id ?: "",
bbsType = it.community?.typeChinese ?: "",
activityTag = "",
gameForumType = it.community?.game?.categoryChinese ?: "",
articleType = "图文",
result = result
)
} else {
SensorsBridge.trackArticleCommentResult(
customerType = it.user.auth?.text ?: "",
articleId = it.id,
bbsId = it.community?.id ?: "",
bbsType = it.community?.typeChinese ?: "",
activityTag = "",
gameForumType = it.community?.game?.categoryChinese ?: "",
articleType = "图文",
result = result
)
}
}
}
else -> {}
}
}
@ -439,6 +517,15 @@ open class NewCommentViewModel(
api.postReplyToGameCollectionComment(gameCollectionId, commentEntity.id, body)
}
}
CommentType.IMAGE_ARTICLE, CommentType.IMAGE_ARTICLE_CONVERSATION -> {
if (commentEntity == null) {
api.postImageArticleComment(imageArticleId, body)
} else {
api.postReplyToImageArticleComment(commentEntity.id, body)
}
}
}
// TODO Remove this apiResponse crap.
@ -632,7 +719,8 @@ open class NewCommentViewModel(
private val gameCollectionId: String = "",
private val rootCommentId: String = "",
private val isVideoAuthor: Boolean = false,
private val commentType: CommentType
private val commentType: CommentType,
private val imageArticleId: String = ""
) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@ -647,7 +735,8 @@ open class NewCommentViewModel(
commentId = commentId,
rootCommentId = rootCommentId,
commentType = commentType,
isVideoAuthor = isVideoAuthor
isVideoAuthor = isVideoAuthor,
imageArticleId = imageArticleId
) as T
}
}
@ -667,7 +756,11 @@ enum class CommentType {
VIDEO_CONVERSATION,
GAME_COLLECTION,
GAME_COLLECTION_CONVERSATION;
GAME_COLLECTION_CONVERSATION,
IMAGE_ARTICLE,
IMAGE_ARTICLE_CONVERSATION;
fun isVideo(): Boolean {
return (this == VIDEO || this == VIDEO_CONVERSATION)

View File

@ -39,6 +39,9 @@ class StairsCommentFragment : NewCommentFragment() {
CommentType.GAME_COLLECTION,
CommentType.GAME_COLLECTION_CONVERSATION -> "(游戏单详情-评论列表)"
CommentType.IMAGE_ARTICLE,
CommentType.IMAGE_ARTICLE_CONVERSATION -> "(图文详情-评论列表)"
}
mAdapter = StairsCommentAdapter(requireContext(), mViewModel, true, this, this, entrance)
}
@ -58,7 +61,12 @@ class StairsCommentFragment : NewCommentFragment() {
mAnswerContent.background =
ContextCompat.getDrawable(requireActivity(), R.drawable.bg_shape_dark_radius_10_top_only)
} else {
mAnswerContent.setBackgroundColor(ContextCompat.getColor(requireActivity(), com.gh.gamecenter.common.R.color.bg_1F1F23))
mAnswerContent.setBackgroundColor(
ContextCompat.getColor(
requireActivity(),
com.gh.gamecenter.common.R.color.bg_1F1F23
)
)
mOffset = abs(height)
}

View File

@ -261,6 +261,7 @@ class StairsCommentViewHolder(
mViewModel.articleId,
mViewModel.communityId,
mViewModel.videoId,
mViewModel.imageArticleId,
commentEntity,
holder.binding.commentLikeCount,
holder.binding.commentLike,
@ -314,6 +315,9 @@ class StairsCommentViewHolder(
CommentType.GAME_COLLECTION,
CommentType.GAME_COLLECTION_CONVERSATION -> "游戏单详情-评论管理"
CommentType.IMAGE_ARTICLE,
CommentType.IMAGE_ARTICLE_CONVERSATION -> "图文详情-评论管理"
}
holder.binding.commentUserIcon.setOnClickListener {
DirectUtils.directToHomeActivity(

View File

@ -32,6 +32,7 @@ import com.gh.gamecenter.core.utils.SpanBuilder
import com.gh.gamecenter.databinding.*
import com.gh.gamecenter.feature.entity.CommentEntity
import com.gh.gamecenter.feature.entity.Permissions
import com.gh.gamecenter.forum.home.recommend.ImageArticleUseCase
import com.gh.gamecenter.qa.article.detail.*
import com.gh.gamecenter.qa.comment.CommentActivity
import com.gh.gamecenter.qa.comment.CommentPictureAdapter
@ -178,6 +179,7 @@ abstract class BaseCommentAdapter(
//刷新 ITEM_FILTER
mViewModel.commentCount -= 1
mViewModel.updateCommentCount(mViewModel.commentCount)
notifyItemChanged(1)
val isArticleDetail = this is ArticleDetailAdapter
@ -241,14 +243,17 @@ abstract class BaseCommentAdapter(
binding.progressBar.visibility = View.GONE
binding.footerTv.setText(com.gh.gamecenter.common.R.string.loading_failed_retry)
}
isOver -> {
binding.progressBar.visibility = View.GONE
binding.footerTv.setText(loadOverHint)
}
isLoading -> {
binding.progressBar.visibility = View.VISIBLE
binding.footerTv.setText(com.gh.gamecenter.common.R.string.loading)
}
else -> {
binding.progressBar.visibility = View.GONE
}
@ -277,7 +282,10 @@ abstract class BaseCommentAdapter(
orderSfv.run {
setContainerBackground(com.gh.gamecenter.common.R.drawable.button_round_f5f5f5.toDrawable(mContext))
setIndicatorBackground(R.drawable.bg_game_collection_sfv_indicator.toDrawable(mContext))
setTextColor(com.gh.gamecenter.common.R.color.text_secondary.toColor(mContext), com.gh.gamecenter.common.R.color.text_tertiary.toColor(mContext))
setTextColor(
com.gh.gamecenter.common.R.color.text_secondary.toColor(mContext),
com.gh.gamecenter.common.R.color.text_tertiary.toColor(mContext)
)
}
val commentCount = mViewModel.commentCount
@ -285,18 +293,23 @@ abstract class BaseCommentAdapter(
article != null -> {
"全部评论"
}
questions != null -> {
"全部回答"
}
comment != null -> {
"全部回复"
}
gameCollection != null -> {
"玩家评论"
}
commentDetail != null -> {
"全部讨论"
}
else -> {
""
}
@ -325,6 +338,7 @@ abstract class BaseCommentAdapter(
}
}
1 -> {
mViewModel.changeSort(BaseCommentViewModel.SortType.LATEST)
@ -380,6 +394,7 @@ abstract class BaseCommentAdapter(
}
MtaHelper.onEvent("帖子详情", "全部评论", "评论正文")
}
viewModel.videoId.isNotEmpty() -> {
CommentActivity.getVideoCommentReplyIntent(
binding.root.context,
@ -396,6 +411,7 @@ abstract class BaseCommentAdapter(
)
}
}
viewModel.questionId.isNotEmpty() -> {
CommentActivity.getQuestionCommentReplyIntent(
binding.root.context,
@ -412,6 +428,21 @@ abstract class BaseCommentAdapter(
)
}
}
viewModel.imageArticleId.isNotEmpty() ->{
CommentActivity.getImageArticleCommentIntent(
binding.root.context,
viewModel.imageArticleId,
bbsId,
viewModel.commentCount,
comment
).apply {
(binding.root.context as AppCompatActivity).startActivityForResult(
this,
CommentActivity.REQUEST_CODE
)
}
}
}
NewLogUtils.logCommentClick(
"click_comment_area_comment",
@ -426,11 +457,13 @@ abstract class BaseCommentAdapter(
viewModel.articleId,
viewModel.videoId,
viewModel.questionId,
viewModel.imageArticleId,
false,
comment.floor,
entrance,
PATH_ARTICLE_DETAIL
).apply {
PATH_ARTICLE_DETAIL,
).apply {
binding.root.context.startActivity(this)
}
MtaHelper.onEvent("帖子详情", "全部评论", "回复")
@ -466,7 +499,12 @@ abstract class BaseCommentAdapter(
com.gh.gamecenter.common.R.color.text_primary
).build()
val parentUserNameSpan = SpanBuilder(parentUserName)
.click(binding.root.context, 0, parentUserName.length, com.gh.gamecenter.common.R.color.text_tertiary) {
.click(
binding.root.context,
0,
parentUserName.length,
com.gh.gamecenter.common.R.color.text_tertiary
) {
DirectUtils.directToHomeActivity(
binding.root.context,
comment.user.id,
@ -527,9 +565,11 @@ abstract class BaseCommentAdapter(
viewModel.articleId.isNotEmpty() -> {
viewModel.topItemData?.articleDetail?.user?.id ?: ""
}
viewModel.questionId.isNotEmpty() -> {
viewModel.topItemData?.questionDetail?.user?.id ?: ""
}
else -> {
""
}
@ -577,6 +617,7 @@ abstract class BaseCommentAdapter(
viewModel.articleId,
viewModel.videoId,
viewModel.questionId,
viewModel.imageArticleId,
false,
comment.floor,
entrance,
@ -597,13 +638,20 @@ abstract class BaseCommentAdapter(
val finalName = "$name "
val colon = " "
val nameSpan = SpanBuilder(finalName).color(context, 0, finalName.length, com.gh.gamecenter.common.R.color.text_tertiary).build()
val nameSpan = SpanBuilder(finalName).color(
context,
0,
finalName.length,
com.gh.gamecenter.common.R.color.text_tertiary
).build()
val authorSpan = if (finalAuthor.isNotEmpty()) SpanBuilder(finalAuthor).image(
0,
finalAuthor.length,
R.drawable.ic_hint_author
).build() else ""
val colonSpan = SpanBuilder(colon).color(context, 0, colon.length, com.gh.gamecenter.common.R.color.text_tertiary).build()
val colonSpan =
SpanBuilder(colon).color(context, 0, colon.length, com.gh.gamecenter.common.R.color.text_tertiary)
.build()
return SpannableStringBuilder().append(nameSpan).append(authorSpan).append(colonSpan)
.append(content)
}
@ -665,12 +713,19 @@ abstract class BaseCommentAdapter(
viewModel.articleId.isNotEmpty() -> {
if (viewModel is CommentConversationViewModel) PATH_ARTICLE_DETAIL_COMMENT else PATH_ARTICLE_DETAIL
}
viewModel.questionId.isNotEmpty() -> {
if (viewModel is CommentConversationViewModel) PATH_QUESTION_DETAIL_COMMENT else PATH_QUESTION_DETAIL
}
viewModel.videoId.isNotEmpty() -> {
if (viewModel is CommentConversationViewModel) PATH_VIDEO_DETAIL_COMMENT else PATH_VIDEO_DETAIL
}
viewModel.imageArticleId.isNotEmpty() -> {
if (viewModel is CommentConversationViewModel) PATH_IMAGE_ARTICLE_DETAIL_COMMENT else PATH_IMAGE_ARTICLE_DETAIL
}
else -> ""
}
val mtaKey = if (viewModel is ArticleDetailViewModel) "全部评论" else "评论详情-全部回复"
@ -693,7 +748,13 @@ abstract class BaseCommentAdapter(
layoutManager = GridLayoutManager(context, 3)
adapter = CommentPictureAdapter(context, comment.images!!, entrance)
if (itemDecorationCount == 0) {
addItemDecoration(GridSpacingItemColorDecoration(context, 2, com.gh.gamecenter.common.R.color.transparent))
addItemDecoration(
GridSpacingItemColorDecoration(
context,
2,
com.gh.gamecenter.common.R.color.transparent
)
)
}
}
} else {
@ -843,6 +904,7 @@ abstract class BaseCommentAdapter(
deleteCallBack
)
}
viewModel.videoId.isNotEmpty() -> {
showVideoCommentOptions(
view,
@ -852,6 +914,7 @@ abstract class BaseCommentAdapter(
deleteCallBack
)
}
viewModel.questionId.isNotEmpty() -> {
showQuestionCommentOption(
view,
@ -861,6 +924,16 @@ abstract class BaseCommentAdapter(
deleteCallBack
)
}
viewModel.imageArticleId.isNotEmpty() -> {
showImageArticleOption(
view,
comment,
viewModel,
path,
deleteCallBack
)
}
}
}
@ -872,18 +945,34 @@ abstract class BaseCommentAdapter(
bbsId = viewModel.topItemData?.articleDetail?.community?.id ?: ""
type = viewModel.topItemData?.articleDetail?.type ?: ""
}
viewModel.questionId.isNotEmpty() -> {
bbsId = viewModel.topItemData?.questionDetail?.community?.id ?: ""
type = viewModel.topItemData?.questionDetail?.type ?: ""
}
viewModel.imageArticleId.isNotEmpty() -> {
bbsId = viewModel.topItemData?.imageArticleDetail?.community?.id ?: ""
type = viewModel.topItemData?.imageArticleDetail?.community?.type ?: ""
}
else -> {
bbsId = (viewModel as? VideoCommentViewModel)?.videoDetail?.bbs?.id ?: ""
type = (viewModel as? VideoCommentViewModel)?.videoDetail?.type ?: ""
}
}
val contentType =
if (viewModel.articleId.isNotEmpty()) "帖子评论" else if (viewModel.questionId.isNotEmpty()) "提问帖评论" else "视频帖评论"
val bbsType = if (type == "game_bbs") "游戏论坛" else "综合论坛"
when {
viewModel.articleId.isNotEmpty() -> "帖子评论"
viewModel.questionId.isNotEmpty() -> "提问帖评论"
viewModel.imageArticleId.isNotEmpty() -> "图文评论"
else -> "视频帖评论"
}
val bbsType = when (type) {
"game_bbs" -> "游戏论坛"
"official_bbs" -> "综合论坛"
else -> ""
}
return Triple(bbsId, contentType, bbsType)
}
@ -916,6 +1005,7 @@ abstract class BaseCommentAdapter(
}, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)
)
}
"采纳" -> {
DialogHelper.showDialog(
view.context,
@ -929,6 +1019,7 @@ abstract class BaseCommentAdapter(
extraConfig = DialogHelper.Config(centerContent = true, centerTitle = true)
)
}
"取消采纳" -> {
viewModel.acceptQuestionComment(
viewModel.questionId,
@ -936,12 +1027,15 @@ abstract class BaseCommentAdapter(
false
)
}
"加精选" -> {
showHighlightQuestionCommentDialog(comment, view.context, viewModel, true)
}
"取消精选" -> {
showHighlightQuestionCommentDialog(comment, view.context, viewModel, false)
}
"置顶" -> {
DialogHelper.showDialog(
view.context,
@ -955,6 +1049,7 @@ abstract class BaseCommentAdapter(
extraConfig = DialogHelper.Config(centerContent = true, centerTitle = true)
)
}
"取消置顶" -> {
DialogHelper.showDialog(
view.context,
@ -1048,6 +1143,7 @@ abstract class BaseCommentAdapter(
}, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)
)
}
"置顶" -> {
DialogHelper.showDialog(
view.context,
@ -1061,6 +1157,7 @@ abstract class BaseCommentAdapter(
extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)
)
}
"取消置顶" -> {
DialogHelper.showDialog(
view.context,
@ -1119,6 +1216,7 @@ abstract class BaseCommentAdapter(
}, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)
)
}
"置顶" -> {
DialogHelper.showDialog(
view.context,
@ -1132,6 +1230,7 @@ abstract class BaseCommentAdapter(
extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)
)
}
"取消置顶" -> {
DialogHelper.showDialog(
view.context,
@ -1166,6 +1265,78 @@ abstract class BaseCommentAdapter(
}
}
private fun showImageArticleOption(
view: View,
comment: CommentEntity,
viewModel: BaseCommentViewModel,
path: String,
deleteCallBack: ((comment: CommentEntity) -> Unit)?
) {
CommentHelper.showImageArticleCommentOptions(
view,
comment,
viewModel.imageArticleId,
isShowTop = path == PATH_IMAGE_ARTICLE_DETAIL,
listener = object : OnCommentOptionClickListener {
override fun onCommentOptionClick(entity: CommentEntity, option: String) {
when (option) {
"删除评论" -> {
DialogHelper.showDialog(
view.context,
"提示",
"删除评论后,评论下所有的回复都将被删除",
"删除",
"取消",
{
viewModel.deleteComment(comment) {
deleteCallBack?.invoke(comment)
}
}, extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)
)
}
"置顶" -> {
DialogHelper.showDialog(
view.context,
"提示",
"是否将此条评论置顶?",
"确认",
"取消",
confirmClickCallback = {
commentTop(view.context, viewModel, comment, false)
},
extraConfig = DialogHelper.Config(centerTitle = true, centerContent = true)
)
}
"取消置顶" -> {
DialogHelper.showDialog(
view.context,
"提示",
"是否将此条评论取消置顶?",
"确认",
"取消",
confirmClickCallback = {
viewModel.updateCommentTop(
comment.id ?: "",
top = false,
isAgain = false
) { isSuccess, _ ->
if (isSuccess) {
viewModel.onUpdateCommentTopSuccess()
}
}
},
extraConfig = DialogHelper.Config(centerContent = true, centerTitle = true)
)
}
}
}
})
}
private fun commentTop(
context: Context,
viewModel: BaseCommentViewModel,
@ -1212,6 +1383,9 @@ abstract class BaseCommentAdapter(
const val PATH_VIDEO_DETAIL = "视频详情"
const val PATH_VIDEO_DETAIL_COMMENT = "视频评论详情"
const val PATH_IMAGE_ARTICLE_DETAIL = "帖子详情"
const val PATH_IMAGE_ARTICLE_DETAIL_COMMENT = "帖子评论详情"
}
enum class AdapterType {

View File

@ -2,10 +2,12 @@ package com.gh.gamecenter.qa.comment.base
import android.annotation.SuppressLint
import android.app.Application
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import com.gh.common.util.ErrorHelper
import com.gh.common.util.NewLogUtils
import com.gh.common.util.PostCommentUtils
import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.common.baselist.ListViewModel
import com.gh.gamecenter.common.baselist.LoadParams
import com.gh.gamecenter.common.baselist.LoadStatus
@ -24,10 +26,12 @@ import com.gh.gamecenter.entity.CommentDraft
import com.gh.gamecenter.feature.entity.CommentEntity
import com.gh.gamecenter.feature.entity.MeEntity
import com.gh.gamecenter.feature.entity.Permissions
import com.gh.gamecenter.livedata.Event
import com.gh.gamecenter.qa.article.detail.CommentItemData
import com.gh.gamecenter.retrofit.RetrofitManager
import com.gh.gamecenter.retrofit.service.ApiService
import com.gh.gamecenter.room.AppDatabase
import com.halo.assistant.HaloApp
import com.lightgame.utils.Utils
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
@ -42,7 +46,8 @@ abstract class BaseCommentViewModel(
var questionId: String,
var communityId: String,
var gameCollectionId: String,
var topCommentId: String = ""
val imageArticleId: String = "",
val topCommentId: String = "",
) : ListViewModel<CommentEntity, CommentItemData>(application) {
protected val mApi: ApiService = RetrofitManager.getInstance().api
protected var mTotal: Int = 0
@ -56,20 +61,29 @@ abstract class BaseCommentViewModel(
var isHandleTopComment = false
private val _updateCommentCountAction = MutableLiveData<Event<Int>>()
val updateCommentCountAction: LiveData<Event<Int>> = _updateCommentCountAction
fun updateCommentCount(count: Int) {
_updateCommentCountAction.value = Event(count)
}
override fun loadStatusControl(size: Int) {
if (mCurLoadParams.loadOffset == LoadParams.DEFAULT_OFFSET) { // 初始化列表
when {
size == 0 -> {
mLoadStatusLiveData.setValue(LoadStatus.INIT_EMPTY)
}
size == REQUEST_FAILURE_SIZE -> {
// TODO 处理列表加载失败问题
mLoadStatusLiveData.setValue(LoadStatus.INIT_LOADED)
}
size < mOverLimitSize -> {
// 避免一个屏幕出现两次分页
mLoadStatusLiveData.setValue(LoadStatus.INIT_OVER)
}
else -> mLoadStatusLiveData.setValue(LoadStatus.INIT_LOADED)
}
} else {
@ -146,15 +160,28 @@ abstract class BaseCommentViewModel(
articleId.isNotEmpty() -> {
mApi.getCommunityArticleComment(commentId)
}
videoId.isNotEmpty() -> {
mApi.getCommunityVideoComment(commentId)
}
questionId.isNotEmpty() -> {
mApi.getCommunityQuestionComment(questionId, commentId)
}
gameCollectionId.isNotEmpty() -> {
mApi.getGameCollectionComment(gameCollectionId, commentId)
}
imageArticleId.isNotEmpty() -> {
mApi.getImageArticleCommentDetail(
commentId,
BuildConfig.VERSION_NAME,
HaloApp.getInstance().channel,
System.currentTimeMillis()
)
}
else -> null
} ?: return
single.compose(singleToMain())
@ -172,6 +199,7 @@ abstract class BaseCommentViewModel(
load(LoadType.REFRESH)
}
}
SortType.OLDEST -> {
//根据total判断是否插入到最后
if (count >= mTotal) {
@ -185,6 +213,7 @@ abstract class BaseCommentViewModel(
}
}
}
override fun onFailure(exception: Exception) {
super.onFailure(exception)
load(LoadType.REFRESH)
@ -221,6 +250,7 @@ abstract class BaseCommentViewModel(
articleId,
videoId,
questionId,
imageArticleId,
comment.id,
object : PostCommentUtils.PostCommentListener {
override fun postSuccess(response: JSONObject?) {
@ -273,7 +303,7 @@ abstract class BaseCommentViewModel(
}
fun unLike(comment: CommentEntity) {
PostCommentUtils.unLikeComment(articleId, communityId, videoId, questionId, comment.id,
PostCommentUtils.unLikeComment(articleId, communityId, videoId, questionId, imageArticleId, comment.id,
object : PostCommentUtils.PostCommentListener {
override fun postSuccess(response: JSONObject?) {
val cloneComment = comment.clone()
@ -319,12 +349,19 @@ abstract class BaseCommentViewModel(
mApi.deleteVideoComment(entity.id).toObservable()
}
}
questionId.isNotEmpty() -> {
mApi.deleteQuestionComment(questionId, entity.id).toObservable()
}
articleId.isNotEmpty() -> {
mApi.hideCommunityArticleComment(entity.id)
}
imageArticleId.isNotEmpty() -> {
mApi.deleteImageArticleComment(entity.id)
}
else -> null
} ?: return
observable.compose(observableToMain())
@ -373,12 +410,19 @@ abstract class BaseCommentViewModel(
articleId.isNotEmpty() -> {
mApi.postArticleCommentTop(commentId, map)
}
questionId.isNotEmpty() -> {
mApi.postQuestionCommentTop(questionId, commentId, map)
}
videoId.isNotEmpty() -> {
mApi.postVideoCommentTop(commentId, map)
}
imageArticleId.isNotEmpty() -> {
mApi.postImageArticleTop(commentId, map)
}
else -> null
}
} else {
@ -386,12 +430,19 @@ abstract class BaseCommentViewModel(
articleId.isNotEmpty() -> {
mApi.postArticleCommentUnTop(commentId)
}
questionId.isNotEmpty() -> {
mApi.postQuestionCommentUnTop(questionId, commentId)
}
videoId.isNotEmpty() -> {
mApi.postVideoCommentUnTop(commentId)
}
imageArticleId.isNotEmpty() -> {
mApi.postImageArticleUnTop(commentId)
}
else -> null
}
} ?: return
@ -502,6 +553,7 @@ abstract class BaseCommentViewModel(
when {
commentNormal?.id == cloneComment.id -> it[index] =
CommentItemData(commentNormal = cloneComment)
commentTop?.id == cloneComment.id -> it[index] = CommentItemData(commentTop = cloneComment)
}
}

View File

@ -1,13 +1,17 @@
package com.gh.gamecenter.qa.comment.conversation
import android.content.Context
import android.net.Uri
import android.view.View
import android.view.ViewGroup
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
import androidx.recyclerview.widget.RecyclerView
import com.alibaba.android.arouter.launcher.ARouter
import com.gh.common.util.CommentUtils
import com.gh.common.util.DirectUtils
import com.gh.gamecenter.common.constant.EntranceConsts
import com.gh.gamecenter.common.constant.RouteConsts
import com.gh.gamecenter.common.utils.TextHelper
import com.gh.gamecenter.common.utils.dip2px
import com.gh.gamecenter.common.utils.goneIf
@ -83,6 +87,7 @@ class CommentConversationAdapter(
(binding.bottomContainer.layoutParams as ConstraintLayout.LayoutParams).apply {
leftMargin = 9F.dip2px()
binding.bottomContainer.layoutParams = this
}
binding.run {
@ -123,6 +128,7 @@ class CommentConversationAdapter(
path
)
}
videoId.isNotEmpty() -> {
DirectUtils.directToVideoDetail(
mContext,
@ -131,6 +137,7 @@ class CommentConversationAdapter(
path
)
}
questionId.isNotEmpty() -> {
DirectUtils.directToQuestionDetail(
mContext,
@ -139,6 +146,7 @@ class CommentConversationAdapter(
path
)
}
gameCollectionId.isNotEmpty() -> {
DirectUtils.directToGameCollectionDetail(
mContext,
@ -147,6 +155,14 @@ class CommentConversationAdapter(
path
)
}
imageArticleId.isNotEmpty() -> {
val imageArticleUri = Uri.Builder()
.path(RouteConsts.activity.imageArticleDetailActivity)
.appendQueryParameter(EntranceConsts.KEY_IMAGE_ARTICLE_ID, imageArticleId)
.build()
ARouter.getInstance().build(imageArticleUri).navigation()
}
}
}
}

View File

@ -29,6 +29,7 @@ import com.gh.gamecenter.qa.comment.base.BaseCommentFragment
import com.gh.gamecenter.qa.comment.base.BaseCommentViewModel
import com.gh.gamecenter.qa.article.detail.CommentItemData
import com.gh.gamecenter.qa.comment.CommentActivity
import com.gh.gamecenter.qa.comment.CommentActivity.Companion.IMAGE_ARTICLE_ID
import com.halo.assistant.HaloApp
class CommentConversationFragment :
@ -118,7 +119,8 @@ class CommentConversationFragment :
// 取消掉
if (mUseAlternativeLayout) {
toolbarContainer.root.visibility = View.GONE
container.updateLayoutParams { this as FrameLayout.LayoutParams
container.updateLayoutParams {
this as FrameLayout.LayoutParams
setMargins(0, 0, 0, 0)
}
}
@ -192,6 +194,7 @@ class CommentConversationFragment :
}
}, 100)
}
else -> {
if (it == BaseCommentViewModel.LoadResult.DELETED) {
mReuseNoConn?.visibility = View.GONE
@ -228,7 +231,8 @@ class CommentConversationFragment :
arguments?.getString(EntranceConsts.KEY_COMMUNITY_ID) ?: "",
arguments?.getString(CommentActivity.GAME_COLLECTION_ID) ?: "",
arguments?.getString(EntranceConsts.KEY_COMMENT_ID) ?: "",
arguments?.getString(EntranceConsts.KEY_TOP_COMMENT_ID) ?: ""
arguments?.getString(IMAGE_ARTICLE_ID) ?: "",
arguments?.getString(EntranceConsts.KEY_TOP_COMMENT_ID) ?: "",
)
)
}
@ -284,6 +288,15 @@ class CommentConversationFragment :
comment
)
startActivityForResult(intent, CommentActivity.REQUEST_CODE)
} else if (mViewModel.imageArticleId.isNotEmpty()) {
val intent = CommentActivity.getImageArticleCommentReplyIntent(
requireContext(),
mViewModel.imageArticleId,
arguments?.getString(EntranceConsts.KEY_COMMENT_ID) ?: "",
mViewModel.commentCount,
comment
)
startActivityForResult(intent, CommentActivity.REQUEST_CODE)
}
}
}

View File

@ -4,10 +4,12 @@ import android.annotation.SuppressLint
import android.app.Application
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.common.retrofit.BiResponse
import com.gh.gamecenter.feature.entity.CommentEntity
import com.gh.gamecenter.qa.article.detail.CommentItemData
import com.gh.gamecenter.qa.comment.base.BaseCommentViewModel
import com.halo.assistant.HaloApp
import io.reactivex.Observable
import io.reactivex.Single
import io.reactivex.schedulers.Schedulers
@ -21,8 +23,18 @@ class CommentConversationViewModel(
communityId: String = "",
gameCollectionId: String = "",
var commentId: String = "",
imageArticleId: String = "",
topCommentId: String = ""
) : BaseCommentViewModel(application, articleId, videoId, questionId, communityId, topCommentId) {
) : BaseCommentViewModel(
application,
articleId,
videoId,
questionId,
communityId,
gameCollectionId,
imageArticleId,
topCommentId = topCommentId
) {
var commentDetail: CommentEntity? = null
var positionInOriginList = -1
@ -42,15 +54,31 @@ class CommentConversationViewModel(
map
)
}
videoId.isNotEmpty() -> {
mApi.getVideoCommentReply(videoId, commentId, currentSortType.value, page, map)
}
questionId.isNotEmpty() -> {
mApi.getQuestionCommentReply(questionId, commentId, currentSortType.value, page, map)
}
gameCollectionId.isNotEmpty() -> {
mApi.getGameCollectionCommentReply(gameCollectionId, commentId, page, map)
}
imageArticleId.isNotEmpty() -> {
mApi.getImageArticleDetailCommentReply(
commentId,
currentSortType.value,
page,
BuildConfig.VERSION_NAME,
HaloApp.getInstance().channel,
System.currentTimeMillis(),
map
)
}
else -> null
}
}
@ -61,15 +89,28 @@ class CommentConversationViewModel(
articleId.isNotEmpty() -> {
mApi.getCommunityArticleComment(commentId)
}
videoId.isNotEmpty() -> {
mApi.getCommunityVideoComment(commentId)
}
questionId.isNotEmpty() -> {
mApi.getCommunityQuestionComment(questionId, commentId)
}
gameCollectionId.isNotEmpty() -> {
mApi.getGameCollectionComment(gameCollectionId, commentId)
}
imageArticleId.isNotEmpty() -> {
mApi.getImageArticleCommentDetail(
commentId,
BuildConfig.VERSION_NAME,
HaloApp.getInstance().channel,
System.currentTimeMillis()
)
}
else -> null
} ?: return
single.subscribeOn(Schedulers.io())
@ -113,6 +154,7 @@ class CommentConversationViewModel(
when {
commentNormal?.id == cloneComment.id -> it[index] =
CommentItemData(commentNormal = cloneComment)
commentTop?.id == cloneComment.id -> {
commentDetail = cloneComment
it[index] = CommentItemData(commentTop = cloneComment)
@ -132,7 +174,8 @@ class CommentConversationViewModel(
private val communityId: String = "",
private val gameCollectionId: String = "",
private val commentId: String,
private val topCommentId: String
private val imageArticleId: String,
private val topCommentId: String,
) : ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
@ -144,7 +187,8 @@ class CommentConversationViewModel(
communityId = communityId,
gameCollectionId = gameCollectionId,
commentId = commentId,
topCommentId = topCommentId
imageArticleId = imageArticleId,
topCommentId = topCommentId,
) as T
}
}

View File

@ -2,12 +2,15 @@ package com.gh.gamecenter.qa.dialog
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.content.pm.ActivityInfo
import android.os.Build
import android.os.Bundle
import android.view.MotionEvent
import android.view.View
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputMethodManager
import androidx.core.widget.doOnTextChanged
import androidx.fragment.app.Fragment
import com.gh.gamecenter.common.base.activity.BaseActivity
@ -45,8 +48,8 @@ class ChooseForumActivity : BaseActivity() {
val sourceEntrance = intent.getStringExtra(EntranceConsts.KEY_SOURCE_ENTRANCE) ?: ""
initViewPager()
binding.searchEt.doOnTextChanged { _, _, _, _ ->
val searchKey = binding.searchEt.text?.toString()
binding.searchEt.doOnTextChanged { text, _, _, _ ->
val searchKey = text?.toString()
if (searchKey.isNullOrEmpty()) {
switchUI(false)
} else {
@ -61,6 +64,25 @@ class ChooseForumActivity : BaseActivity() {
}
false
}
binding.searchEt.setOnEditorActionListener { v, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_SEARCH) {
// 开始搜索
mSearchResultFragment?.setSearchKeyAndType(
binding.searchEt.text?.toString() ?: "",
SearchType.MANUAL.value
)
// 清除焦点以保持搜索按钮
binding.searchEt.clearFocus()
// 隐藏软键盘
val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
imm.hideSoftInputFromWindow(binding.searchEt.windowToken, 0)
return@setOnEditorActionListener true
} else {
return@setOnEditorActionListener false
}
}
binding.closeIv.setOnClickListener {
NewLogUtils.logChooseForumPanelClick("click_select_forum_panel_close", "", "", "")
finish()

View File

@ -5,7 +5,10 @@ import android.content.Intent
import android.os.Bundle
import androidx.fragment.app.Fragment
import com.gh.gamecenter.common.base.activity.BaseActivity_TabLayout
import com.gh.gamecenter.R
import com.gh.gamecenter.common.utils.toResString
import com.gh.gamecenter.common.utils.updateStatusBarColor
import com.gh.gamecenter.forum.home.recommend.fragment.ImageArticleDraftFragment
import com.gh.gamecenter.qa.article.draft.ArticleDraftFragment
import com.gh.gamecenter.qa.questions.draft.QuestionDraftFragment
import com.gh.gamecenter.video.videomanager.VideoDraftFragment
@ -22,15 +25,17 @@ class CommunityDraftWrapperActivity : BaseActivity_TabLayout() {
}
override fun initFragmentList(fragments: MutableList<Fragment>) {
fragments.add(ImageArticleDraftFragment())
fragments.add(ArticleDraftFragment())
fragments.add(QuestionDraftFragment())
fragments.add(VideoDraftFragment())
}
override fun initTabTitleList(tabTitleList: MutableList<String>) {
tabTitleList.add("帖子草稿")
tabTitleList.add("问题草稿")
tabTitleList.add("视频草稿")
tabTitleList.add(R.string.image_article.toResString())
tabTitleList.add("帖子")
tabTitleList.add("问题")
tabTitleList.add("视频")
}
override fun isAutoResetViewBackgroundEnabled(): Boolean = true

View File

@ -324,6 +324,7 @@ class NewQuestionDetailFragment :
mBinding.root.setBackgroundColor(Color.TRANSPARENT)
updateView()
}
else -> {
if (it == BaseCommentViewModel.LoadResult.DELETED) {
mReuseNoConn?.visibility = View.GONE
@ -430,7 +431,7 @@ class NewQuestionDetailFragment :
mAdapter?.questionDetailVH?.bindView(it)
}
mViewModel.top.observeNonNull(this) { top ->
val topSuccessToast = if(top) {
val topSuccessToast = if (top) {
R.string.article_detail_top_success_toast
} else {
R.string.article_detail_cancel_top_success_toast
@ -626,12 +627,24 @@ class NewQuestionDetailFragment :
// 置顶/取消置顶
if (questionEntity.me.isModerator
&& !questionEntity.me.isCommunityTop
&& moderatorPermissions.topQuestion > Permissions.GUEST) {
entities.add(MenuItemEntity(getString(R.string.article_detail_more_top_title), R.drawable.icon_more_panel_top))
&& moderatorPermissions.topQuestion > Permissions.GUEST
) {
entities.add(
MenuItemEntity(
getString(R.string.article_detail_more_top_title),
R.drawable.icon_more_panel_top
)
)
} else if (questionEntity.me.isModerator
&& questionEntity.me.isCommunityTop
&& moderatorPermissions.cancelTopQuestion > Permissions.GUEST) {
entities.add(MenuItemEntity(getString(R.string.article_detail_more_cancel_top_title), R.drawable.icon_more_panel_top_cancel))
&& moderatorPermissions.cancelTopQuestion > Permissions.GUEST
) {
entities.add(
MenuItemEntity(
getString(R.string.article_detail_more_cancel_top_title),
R.drawable.icon_more_panel_top_cancel
)
)
}
MoreFunctionPanelDialog.showMoreDialog(
@ -682,8 +695,7 @@ class NewQuestionDetailFragment :
"投诉" -> {
ifLogin("提问贴") {
BbsReportHelper.showReportDialog(
mViewModel.questionDetail?.id
?: ""
BbsReportHelper.PostsReporter(mViewModel.questionDetail?.id ?: "")
)
}
NewLogUtils.logSharePanelClick(
@ -695,6 +707,7 @@ class NewQuestionDetailFragment :
mViewModel.questionDetail?.community?.typeChinese ?: "综合论坛"
)
}
"编辑" -> {
val intent = if (questionEntity.me.isModerator) {
QuestionEditActivity.getManagerIntent(
@ -737,6 +750,7 @@ class NewQuestionDetailFragment :
mViewModel.questionDetail?.community?.typeChinese ?: "综合论坛"
)
}
"解决", "已解决" -> {
val content =
if (!questionEntity.finish) "该问题确定标记已解决?" else "该问题确定标记未解决?"
@ -752,6 +766,7 @@ class NewQuestionDetailFragment :
mViewModel.questionDetail?.community?.typeChinese ?: "综合论坛"
)
}
getString(R.string.article_detail_more_top_title) -> {
TopCommunityCategoryDialog.show(
childFragmentManager
@ -759,6 +774,7 @@ class NewQuestionDetailFragment :
mViewModel.topCommunityQuestion(category.id)
}
}
getString(R.string.article_detail_more_cancel_top_title) -> {
DialogHelper.showDialog(
requireContext(),

View File

@ -171,7 +171,8 @@ class ForumVideoDetailFragment : BaseLazyTabFragment() {
mVideoId = arguments?.getString(EntranceConsts.KEY_VIDEO_ID) ?: ""
mBbsId = arguments?.getString(EntranceConsts.KEY_BBS_ID) ?: ""
mTopCommentId = arguments?.getString(EntranceConsts.KEY_TOP_COMMENT_ID) ?: ""
mBasicExposureSourceList = arguments?.getParcelableArrayList(EntranceConsts.KEY_EXPOSURE_SOURCE_LIST) ?: arrayListOf()
mBasicExposureSourceList =
arguments?.getParcelableArrayList(EntranceConsts.KEY_EXPOSURE_SOURCE_LIST) ?: arrayListOf()
sourceEntrance = arguments?.getString(EntranceConsts.KEY_SOURCE_ENTRANCE) ?: ""
super.onCreate(savedInstanceState)
NewLogUtils.logVideoDetailClick("view_video_detail")
@ -189,7 +190,8 @@ class ForumVideoDetailFragment : BaseLazyTabFragment() {
}
}
if (mIsFromMainWrapper) {
(mBinding.toolbar.layoutParams as ViewGroup.MarginLayoutParams).topMargin = DisplayUtils.getStatusBarHeight(requireContext().resources)
(mBinding.toolbar.layoutParams as ViewGroup.MarginLayoutParams).topMargin =
DisplayUtils.getStatusBarHeight(requireContext().resources)
}
mViewModel = viewModelProviderFromParent(
ForumVideoDetailViewModel.Factory(
@ -704,7 +706,7 @@ class ForumVideoDetailFragment : BaseLazyTabFragment() {
}
"投诉" -> {
BbsReportHelper.showReportDialog(mForumVideoEntity?.id ?: "")
BbsReportHelper.showReportDialog(BbsReportHelper.PostsReporter(mForumVideoEntity?.id ?: ""))
NewLogUtils.logSharePanelClick(
"click_report",
mForumVideoEntity?.user?.id ?: "",
@ -746,9 +748,11 @@ class ForumVideoDetailFragment : BaseLazyTabFragment() {
)
}
}
"取消精选" -> {
showHighlightDialog(false)
}
"修改活动标签" -> {
ChooseActivityDialogFragment.show(
requireActivity() as AppCompatActivity,
@ -758,6 +762,7 @@ class ForumVideoDetailFragment : BaseLazyTabFragment() {
tag ?: ""
)
}
"删除", "隐藏" -> {
DialogHelper.showDialog(
requireContext(),
@ -770,7 +775,7 @@ class ForumVideoDetailFragment : BaseLazyTabFragment() {
)
NewLogUtils.logSharePanelClick(
"click_delete",
mForumVideoEntity?.user?.id ?: "",
mForumVideoEntity?.user?.id ?: "",
"视频帖",
mForumVideoEntity?.id ?: "",
mForumVideoEntity?.bbs?.id ?: "",
@ -785,6 +790,7 @@ class ForumVideoDetailFragment : BaseLazyTabFragment() {
mViewModel.topCommunityVideo(category.id)
}
}
getString(R.string.article_detail_more_cancel_top_title) -> {
DialogHelper.showDialog(
requireContext(),

View File

@ -20,7 +20,7 @@ class VideoCommentViewModel(
videoId: String,
bbsId: String,
topCommentId: String
) : BaseCommentViewModel(application, "", videoId, "", bbsId, "", topCommentId) {
) : BaseCommentViewModel(application, "", videoId, "", bbsId, "", topCommentId = topCommentId) {
var videoDetail: ForumVideoEntity? = null
val deleteCommentLiveData = MutableLiveData<Boolean>()

View File

@ -55,6 +55,7 @@ import com.gh.gamecenter.entity.GamesCollectionEntity;
import com.gh.gamecenter.entity.HaloAddonEntity;
import com.gh.gamecenter.entity.HomeGameCollectionEntity;
import com.gh.gamecenter.entity.HomeItemTestV2Entity;
import com.gh.gamecenter.entity.ImageArticleEntity;
import com.gh.gamecenter.entity.ImageInfoEntity;
import com.gh.gamecenter.entity.InterestedGameEntity;
import com.gh.gamecenter.entity.LibaoDetailEntity;
@ -66,6 +67,7 @@ import com.gh.gamecenter.entity.NewsDetailEntity;
import com.gh.gamecenter.entity.PackageFilter;
import com.gh.gamecenter.entity.PackageGame;
import com.gh.gamecenter.entity.PersonalHistoryEntity;
import com.gh.gamecenter.entity.PublishImageTextRequest;
import com.gh.gamecenter.entity.PullDownPush;
import com.gh.gamecenter.entity.Rating;
import com.gh.gamecenter.entity.RatingComment;
@ -154,6 +156,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;
@ -3425,4 +3428,196 @@ public interface ApiService {
@GET("game_lists/search")
Single<List<CustomPageData.LinkColumnCollection.CustomSubjectEntity>> searchGameList(@Query("keyword") String keyword, @Query("page") int page);
/**
* 发布图文
*/
@POST("communities/image_articles")
Single<JsonObject> publishImageText(@Body PublishImageTextRequest request);
/**
* 编辑图文
*/
@PUT("communities/image_articles/{id}")
Completable editImageArticle(@Path("id") String id, @Body PublishImageTextRequest request);
/**
* 新建草稿
*/
@POST("users/{user_id}/communities/image_article_drafts")
Single<ResponseBody> createImageArticleDrafts(@Path("user_id") String userId, @Body PublishImageTextRequest request);
/**
* 编辑草稿
*/
@PUT("users/{user_id}/communities/image_article_drafts/{id}")
Completable editImageArticleDrafts(@Path("user_id") String userId, @Path("id") String draftId, @Body PublishImageTextRequest request);
/**
* 草稿箱
*/
@GET("users/{user_id}/communities/image_article_drafts")
Single<List<ImageArticleEntity>> loadImageTextDrafts(@Path("user_id") String userId, @Query("version") String version, @Query("channel") String channel);
/**
* 删除草稿
*/
@DELETE("users/{user_id}/communities/image_article_drafts/{id}")
Completable deleteImageArticleDraft(@Path("user_id") String userId, @Path("id") String id);
/**
* 图文详情
*/
@GET("communities/image_articles/{id}")
Single<ImageArticleEntity> loadImageArticleDetail(
@Path("id") String imageArticleId,
@Query("view") String view,
@Query("version") String version,
@Query("channel") String channel);
/**
* 推荐-图文列表
*/
@GET("bbses/image_article_recommends")
Single<List<ImageArticleEntity>> loadImageArticleRecommends(
@Query("sort") String sort,
@Query("page") int page,
@Query("version") String version,
@Query("channel") String channel);
/**
* 评论图文
*/
@POST("communities/image_articles/{article_id}/comments")
Observable<ResponseBody> postImageArticleComment(@Path("article_id") String articleId, @Body RequestBody body);
/**
* 回复图文评论
*/
@POST("communities/image_articles/comments/{comment_id}:reply")
Observable<ResponseBody> postReplyToImageArticleComment(@Path("comment_id") String commentId, @Body RequestBody body);
/**
* 图文详情评论列表
*/
@GET("communities/image_articles/{article_id}/comments")
Observable<retrofit2.Response<JsonArray>> getImageArticleComments(@Path("article_id") String articleId,
@Query("sort") String type,
@Query("page") int page,
@QueryMap Map<String, Object> params);
/**
* 图文评论详情
*/
@GET("communities/image_articles/comments/{comment_id}")
Single<CommentEntity> getImageArticleCommentDetail(
@Path("comment_id") String commentId,
@Query("version") String version,
@Query("channel") String channel,
@Query("timestamp") long timeStamp);
/**
* 图文详情回复列表
*/
@GET("communities/image_articles/comments/{comment_id}/replies")
Single<List<CommentEntity>> getImageArticleDetailCommentReply(
@Path("comment_id") String commentId,
@Query("sort") String sort,
@Query("page") int page,
@Query("version") String version,
@Query("channel") String channel,
@Query("timestamp") long timestamp,
@QueryMap Map<String, Object> params);
/**
* 投诉图文评论
*/
@POST("communities/image_articles/comments/{comment_id}:report")
Observable<ResponseBody> reportImageArticleComment(@Path("comment_id") String commentId, @Body RequestBody body);
/**
* 删除图文评论
*/
@POST("communities/image_articles/comments/{comment_id}:hide")
Observable<ResponseBody> deleteImageArticleComment(@Path("comment_id") String commendId);
/**
* 收藏图文
*/
@POST("users/favorites/communities/image_articles/{id}")
Single<ResponseBody> collectImageArticle(@Path("id") String id);
/**
* 取消收藏图文
*/
@DELETE("users/favorites/communities/image_articles/{id}")
Completable cancelImageArticleCollection(@Path("id") String id);
/**
* 点赞图文
*/
@POST("communities/image_articles/{id}:vote")
Single<VoteEntity> voteArticleImage(@Path("id") String id);
/**
* 取消点赞图文
*/
@POST("communities/image_articles/{id}:unvote")
Single<VoteEntity> unVoteArticleImage(@Path("id") String id);
/**
* 置顶图文
*/
@POST("communities/image_articles/comments/{comment_id}:set-top")
Observable<ResponseBody> postImageArticleTop(@Path("comment_id") String commentId, @QueryMap Map<String, Object> params);
/**
* 取消置顶图文
*/
@POST("communities/image_articles/comments/{comment_id}:unset-top")
Observable<ResponseBody> postImageArticleUnTop(@Path("comment_id") String commentId);
/**
* 点赞图文
*/
@POST("communities/image_articles/comments/{comment_id}:vote")
Observable<ResponseBody> postVoteToImageArticle(@Path("comment_id") String commentId);
/**
* 取消点赞图文
*/
@POST("communities/image_articles/comments/{comment_id}:unvote")
Observable<ResponseBody> postUnVoteImageArticle(@Path("comment_id") String commentId);
/**
* 图文申请加精
*/
@POST("communities/image_articles/{id}:choiceness")
Completable applyHighlightForImageArticle(@Path("id") String id);
/**
* 图文加精选
*/
@POST("communities/image_articles/{id}:moderator_choiceness")
Single<ResponseBody> addHighlightForImageArticle(@Path("id") String imageArticleId);
/**
* 图文取消加精
*/
@POST("communities/image_articles/{id}:cancel_choiceness")
Single<ResponseBody> cancelHighlightForImageArticle(@Path("id") String imageArticleId);
/**
* 删除/隐藏图文
*/
@POST("communities/image_articles/{id}:hide")
Single<ResponseBody> deleteOrHideImageArticle(@Path("id") String imageArticleId);
/**
* 投诉图文
*/
@POST("bbses/contents/{content_id}:report")
Single<ResponseBody> postImageArticleReport(@Path("content_id") String contentId, @Body RequestBody body);
}

View File

@ -94,7 +94,8 @@ class VideoDetailContainerFragment : BaseLazyFragment(), OnBackPressedListener {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mBasicExposureSourceList = arguments?.getParcelableArrayList(EntranceConsts.KEY_EXPOSURE_SOURCE_LIST) ?: arrayListOf()
mBasicExposureSourceList =
arguments?.getParcelableArrayList(EntranceConsts.KEY_EXPOSURE_SOURCE_LIST) ?: arrayListOf()
mInitialVideoId = arguments?.getString(EntranceConsts.KEY_ID) ?: ""
mLocation = arguments?.getString(EntranceConsts.KEY_LOCATION) ?: ""
mReferer = arguments?.getString(EntranceConsts.KEY_REFERER) ?: ""
@ -154,7 +155,8 @@ class VideoDetailContainerFragment : BaseLazyFragment(), OnBackPressedListener {
mBinding.recyclerview.visibility = View.VISIBLE
mBinding.attentionNoDataContainer.visibility = View.GONE
if (!::mAdapter.isInitialized) {
mAdapter = VideoAdapter(this, mBinding.recyclerview, mBinding.refresh, mViewModel, mBasicExposureSourceList)
mAdapter =
VideoAdapter(this, mBinding.recyclerview, mBinding.refresh, mViewModel, mBasicExposureSourceList)
mExposureListener = ExposureListener(this, mAdapter)
mBinding.recyclerview.addOnScrollListener(mExposureListener!!)
mBinding.recyclerview.adapter = mAdapter
@ -574,6 +576,7 @@ class VideoDetailContainerFragment : BaseLazyFragment(), OnBackPressedListener {
CheckLoginUtils.checkLogin(context, "(视频详情)") {}
}
}
"取消收藏" -> {
if (UserManager.getInstance().isLoggedIn) {
MtaHelper.onEvent("视频详情", "更多-取消收藏", combinedTitleAndId)
@ -587,9 +590,10 @@ class VideoDetailContainerFragment : BaseLazyFragment(), OnBackPressedListener {
CheckLoginUtils.checkLogin(context, "(视频详情)") {}
}
}
"投诉" -> {
ifLogin("视频详情") {
BbsReportHelper.showReportDialog(videoEntity.id)
BbsReportHelper.showReportDialog(BbsReportHelper.PostsReporter(videoEntity.id))
}
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

View File

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="12dp" />
<solid android:color="@color/bg_mask_40" />
</shape>

View File

@ -0,0 +1,70 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="64dp"
android:height="64dp"
android:viewportWidth="64"
android:viewportHeight="64">
<group>
<clip-path
android:pathData="M0,0h64v64h-64z"/>
<path
android:pathData="M32,56C45.255,56 56,45.255 56,32C56,18.745 45.255,8 32,8C18.745,8 8,18.745 8,32C8,45.255 18.745,56 32,56Z"
android:fillColor="#3B4B64"/>
<path
android:pathData="M32,56C45.255,56 56,45.255 56,32C56,18.745 45.255,8 32,8C18.745,8 8,18.745 8,32C8,45.255 18.745,56 32,56Z"
android:fillColor="#2496FF"/>
<path
android:pathData="M32,56C45.255,56 56,45.255 56,32C56,18.745 45.255,8 32,8C18.745,8 8,18.745 8,32C8,45.255 18.745,56 32,56Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="8.307"
android:startY="8"
android:endX="55.693"
android:endY="56"
android:type="linear">
<item android:offset="0" android:color="#FF44BAFF"/>
<item android:offset="1" android:color="#FF378DFF"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M32,55.75C45.117,55.75 55.75,45.117 55.75,32C55.75,18.883 45.117,8.25 32,8.25C18.883,8.25 8.25,18.883 8.25,32C8.25,45.117 18.883,55.75 32,55.75Z"
android:strokeAlpha="0.05"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#000000"/>
<path
android:pathData="M25.493,45.973H27.059C27.473,45.973 27.536,45.784 27.581,44.731C27.734,44.83 27.986,44.938 28.175,44.974C28.094,46.216 27.896,46.558 27.104,46.558H25.439C24.575,46.558 24.305,46.36 24.305,45.577V41.743H27.563C27.563,41.743 27.563,41.932 27.554,42.013C27.464,43.561 27.383,44.164 27.176,44.389C27.041,44.542 26.888,44.596 26.663,44.623C26.483,44.632 26.141,44.641 25.781,44.614C25.763,44.434 25.7,44.2 25.619,44.038C25.943,44.074 26.249,44.074 26.384,44.074C26.501,44.074 26.573,44.065 26.645,43.993C26.744,43.858 26.834,43.426 26.906,42.337H24.953V45.577C24.953,45.91 25.034,45.973 25.493,45.973ZM25.835,38.485L26.456,38.674C26.402,38.809 26.339,38.926 26.276,39.061C27.014,39.817 27.914,40.816 28.346,41.455L27.869,41.896C27.464,41.293 26.69,40.366 25.979,39.601C25.412,40.573 24.647,41.473 23.765,42.157C23.657,42.031 23.423,41.797 23.279,41.689C24.413,40.879 25.358,39.646 25.835,38.485ZM29.417,39.52V44.479H28.778V39.52H29.417ZM30.524,38.611H31.181V45.802C31.181,46.252 31.055,46.432 30.776,46.54C30.488,46.648 29.993,46.666 29.156,46.657C29.12,46.486 29.021,46.198 28.922,46.018C29.552,46.045 30.119,46.036 30.29,46.027C30.452,46.018 30.524,45.964 30.524,45.802V38.611ZM39.263,39.781H37.913V40.438H39.263V39.781ZM39.263,41.653V40.987H37.913V41.653H39.263ZM37.283,40.438V39.781H35.555V39.25H37.283V38.512H37.913V39.25H39.866V40.438H40.487V40.987H39.866V42.193H37.913V42.868H40.037V43.381H37.913V44.11H40.37V44.659H37.913V45.55H37.283V44.659H35.051V44.11H37.283V43.381H35.429V42.868H37.283V42.193H35.492V41.653H37.283V40.987H34.97V40.438H37.283ZM34.637,41.509L35.015,41.617C34.871,42.895 34.61,43.939 34.241,44.776C35.114,45.658 36.356,45.856 37.886,45.856C38.237,45.856 40.172,45.856 40.631,45.847C40.532,46 40.415,46.288 40.379,46.468H37.868C36.203,46.468 34.898,46.234 33.971,45.325C33.647,45.874 33.278,46.306 32.855,46.639C32.747,46.504 32.513,46.27 32.36,46.18C32.819,45.856 33.215,45.397 33.539,44.83C33.206,44.38 32.945,43.813 32.729,43.102L33.224,42.904C33.377,43.435 33.575,43.876 33.809,44.236C34.052,43.633 34.232,42.931 34.349,42.121H32.891C33.278,41.464 33.773,40.492 34.178,39.628H32.603V39.034H35.132C34.754,39.826 34.277,40.78 33.881,41.527H34.52L34.637,41.509Z"
android:fillColor="#ffffff"/>
<group>
<clip-path
android:pathData="M42,16H22V36H42V16Z"/>
<path
android:pathData="M35.556,17.414C35.947,17.024 36.58,17.024 36.971,17.414L38.385,18.828C38.775,19.219 38.775,19.852 38.385,20.243L36.971,21.657C36.58,22.047 35.947,22.047 35.556,21.657L34.142,20.243C33.752,19.852 33.752,19.219 34.142,18.828L35.556,17.414Z"
android:fillColor="#ffffff"
android:fillAlpha="0.6"/>
<path
android:pathData="M38.031,22.01C38.227,21.815 38.227,21.498 38.031,21.303L34.849,18.121C34.459,17.73 33.826,17.73 33.435,18.121L23,28.556C22.437,29.119 22.121,29.882 22.121,30.677V32.677C22.121,33.23 22.569,33.677 23.121,33.677H25.121C25.917,33.677 26.68,33.361 27.243,32.799L38.031,22.01Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="36.263"
android:startY="19.535"
android:endX="22.121"
android:endY="33.677"
android:type="linear">
<item android:offset="0" android:color="#CCFFFFFF"/>
<item android:offset="1" android:color="#FFFFFFFF"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M29.899,17.414C30.29,17.024 30.923,17.024 31.314,17.414L31.667,17.768C31.862,17.963 31.862,18.279 31.667,18.475L27.218,22.924C26.903,23.239 26.364,23.016 26.364,22.571V21.364C26.364,21.099 26.469,20.844 26.657,20.657L29.899,17.414Z"
android:fillColor="#ffffff"
android:fillAlpha="0.6"/>
<path
android:pathData="M31.707,32.293C31.895,32.105 32.149,32 32.414,32H41.5C41.776,32 42,32.224 42,32.5V33.5C42,33.776 41.776,34 41.5,34H30.604C30.381,34 30.269,33.731 30.427,33.573L31.707,32.293Z"
android:fillColor="#ffffff"
android:fillAlpha="0.6"/>
</group>
</group>
</vector>

View File

@ -0,0 +1,66 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="60dp"
android:height="60dp"
android:viewportWidth="60"
android:viewportHeight="60">
<path
android:pathData="M30,26m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0"
android:fillColor="#3B4B64"/>
<path
android:pathData="M30,26m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0"
android:fillColor="#2496FF"/>
<path
android:pathData="M30,26m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0">
<aapt:attr name="android:fillColor">
<gradient
android:startX="6.307"
android:startY="2"
android:endX="53.693"
android:endY="50"
android:type="linear">
<item android:offset="0" android:color="#FF44BAFF"/>
<item android:offset="1" android:color="#FF378DFF"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M30,26m-23.75,0a23.75,23.75 0,1 1,47.5 0a23.75,23.75 0,1 1,-47.5 0"
android:strokeAlpha="0.05"
android:strokeWidth="0.5"
android:fillColor="#00000000"
android:strokeColor="#000000"/>
<path
android:pathData="M23.493,39.973H25.059C25.473,39.973 25.536,39.784 25.581,38.731C25.734,38.83 25.986,38.938 26.175,38.974C26.094,40.216 25.896,40.558 25.104,40.558H23.439C22.575,40.558 22.305,40.36 22.305,39.577V35.743H25.563C25.563,35.743 25.563,35.932 25.554,36.013C25.464,37.561 25.383,38.164 25.176,38.389C25.041,38.542 24.888,38.596 24.663,38.623C24.483,38.632 24.141,38.641 23.781,38.614C23.763,38.434 23.7,38.2 23.619,38.038C23.943,38.074 24.249,38.074 24.384,38.074C24.501,38.074 24.573,38.065 24.645,37.993C24.744,37.858 24.834,37.426 24.906,36.337H22.953V39.577C22.953,39.91 23.034,39.973 23.493,39.973ZM23.835,32.485L24.456,32.674C24.402,32.809 24.339,32.926 24.276,33.061C25.014,33.817 25.914,34.816 26.346,35.455L25.869,35.896C25.464,35.293 24.69,34.366 23.979,33.601C23.412,34.573 22.647,35.473 21.765,36.157C21.657,36.031 21.423,35.797 21.279,35.689C22.413,34.879 23.358,33.646 23.835,32.485ZM27.417,33.52V38.479H26.778V33.52H27.417ZM28.524,32.611H29.181V39.802C29.181,40.252 29.055,40.432 28.776,40.54C28.488,40.648 27.993,40.666 27.156,40.657C27.12,40.486 27.021,40.198 26.922,40.018C27.552,40.045 28.119,40.036 28.29,40.027C28.452,40.018 28.524,39.964 28.524,39.802V32.611ZM37.263,33.781H35.913V34.438H37.263V33.781ZM37.263,35.653V34.987H35.913V35.653H37.263ZM35.283,34.438V33.781H33.555V33.25H35.283V32.512H35.913V33.25H37.866V34.438H38.487V34.987H37.866V36.193H35.913V36.868H38.037V37.381H35.913V38.11H38.37V38.659H35.913V39.55H35.283V38.659H33.051V38.11H35.283V37.381H33.429V36.868H35.283V36.193H33.492V35.653H35.283V34.987H32.97V34.438H35.283ZM32.637,35.509L33.015,35.617C32.871,36.895 32.61,37.939 32.241,38.776C33.114,39.658 34.356,39.856 35.886,39.856C36.237,39.856 38.172,39.856 38.631,39.847C38.532,40 38.415,40.288 38.379,40.468H35.868C34.203,40.468 32.898,40.234 31.971,39.325C31.647,39.874 31.278,40.306 30.855,40.639C30.747,40.504 30.513,40.27 30.36,40.18C30.819,39.856 31.215,39.397 31.539,38.83C31.206,38.38 30.945,37.813 30.729,37.102L31.224,36.904C31.377,37.435 31.575,37.876 31.809,38.236C32.052,37.633 32.232,36.931 32.349,36.121H30.891C31.278,35.464 31.773,34.492 32.178,33.628H30.603V33.034H33.132C32.754,33.826 32.277,34.78 31.881,35.527H32.52L32.637,35.509Z"
android:fillColor="#ffffff"/>
<group>
<clip-path
android:pathData="M20,10h20v20h-20z"/>
<path
android:pathData="M33.556,11.414C33.947,11.024 34.58,11.024 34.971,11.414L36.385,12.828C36.775,13.219 36.775,13.852 36.385,14.243L34.971,15.657C34.58,16.047 33.947,16.047 33.556,15.657L32.142,14.243C31.752,13.852 31.752,13.219 32.142,12.828L33.556,11.414Z"
android:fillColor="#ffffff"
android:fillAlpha="0.6"/>
<path
android:pathData="M36.031,16.01C36.227,15.815 36.227,15.498 36.031,15.303L32.849,12.121C32.459,11.731 31.826,11.731 31.435,12.121L21,22.556C20.437,23.119 20.121,23.882 20.121,24.678V26.678C20.121,27.23 20.569,27.678 21.121,27.678L23.121,27.678C23.917,27.678 24.68,27.361 25.243,26.799L36.031,16.01Z">
<aapt:attr name="android:fillColor">
<gradient
android:startX="34.263"
android:startY="13.535"
android:endX="20.121"
android:endY="27.678"
android:type="linear">
<item android:offset="0" android:color="#CCFFFFFF"/>
<item android:offset="1" android:color="#FFFFFFFF"/>
</gradient>
</aapt:attr>
</path>
<path
android:pathData="M27.899,11.414C28.29,11.024 28.923,11.024 29.314,11.414L29.667,11.768C29.862,11.963 29.862,12.28 29.667,12.475L25.218,16.924C24.903,17.24 24.364,17.016 24.364,16.571L24.364,15.364C24.364,15.099 24.469,14.844 24.657,14.657L27.899,11.414Z"
android:fillColor="#ffffff"
android:fillAlpha="0.6"/>
<path
android:pathData="M29.707,26.293C29.895,26.105 30.149,26 30.414,26H39.5C39.776,26 40,26.224 40,26.5V27.5C40,27.776 39.776,28 39.5,28H28.604C28.381,28 28.269,27.731 28.427,27.573L29.707,26.293Z"
android:fillColor="#ffffff"
android:fillAlpha="0.6"/>
</group>
</vector>

View File

@ -0,0 +1,107 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:aapt="http://schemas.android.com/aapt"
android:width="60dp"
android:height="60dp"
android:viewportWidth="60"
android:viewportHeight="60">
<path
android:fillColor="#3B4B64"
android:pathData="M30,26m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0" />
<path
android:fillColor="#2496FF"
android:pathData="M30,26m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0" />
<path android:pathData="M30,26m-24,0a24,24 0,1 1,48 0a24,24 0,1 1,-48 0">
<aapt:attr name="android:fillColor">
<gradient
android:endX="53.693"
android:endY="50"
android:startX="6.307"
android:startY="2"
android:type="linear">
<item
android:color="#FF44BAFF"
android:offset="0" />
<item
android:color="#FF378DFF"
android:offset="1" />
</gradient>
</aapt:attr>
</path>
<path
android:fillColor="#00000000"
android:pathData="M30,26m-23.75,0a23.75,23.75 0,1 1,47.5 0a23.75,23.75 0,1 1,-47.5 0"
android:strokeWidth="0.5"
android:strokeAlpha="0.05"
android:strokeColor="#000000" />
<path
android:fillColor="#ffffff"
android:pathData="M28.722,33.871L28.182,34.24C27.939,33.898 27.435,33.331 27.048,32.917L27.561,32.593C27.948,32.989 28.47,33.547 28.722,33.871ZM27.669,36.751H24.852C25.194,37.462 25.68,38.092 26.292,38.605C26.868,38.101 27.345,37.48 27.669,36.751ZM28.119,36.067L28.569,36.292C28.2,37.426 27.597,38.317 26.823,39.01C27.606,39.541 28.551,39.919 29.649,40.126C29.505,40.261 29.325,40.531 29.235,40.72C28.092,40.468 27.111,40.036 26.31,39.433C25.482,40.027 24.537,40.441 23.538,40.711C23.466,40.54 23.304,40.279 23.187,40.135C24.114,39.919 25.014,39.541 25.779,39.001C25.248,38.506 24.798,37.921 24.438,37.246C23.817,38.353 22.971,39.271 21.81,39.991C21.711,39.838 21.477,39.595 21.315,39.478C22.953,38.479 23.943,37.012 24.537,35.149H23.286C22.719,35.149 22.413,35.203 22.332,35.302C22.287,35.158 22.179,34.825 22.107,34.672C22.233,34.636 22.332,34.51 22.431,34.312C22.539,34.132 22.872,33.403 23.034,32.719L23.736,32.854C23.583,33.43 23.322,34.033 23.088,34.501H24.708C24.879,33.853 25.005,33.169 25.086,32.44L25.833,32.548C25.734,33.241 25.617,33.889 25.473,34.501H29.352L29.343,35.149H25.293C25.194,35.473 25.086,35.797 24.978,36.103H27.993L28.119,36.067ZM38.442,34.492H33.573C33.303,35.068 32.988,35.617 32.628,36.13H34.59V34.924H35.256V36.13H37.947V39.01C37.947,39.37 37.866,39.559 37.587,39.658C37.317,39.766 36.84,39.775 36.066,39.766C36.039,39.586 35.94,39.343 35.85,39.163C36.426,39.181 36.948,39.172 37.083,39.172C37.236,39.163 37.281,39.127 37.281,39.001V36.76H35.256V40.702H34.59V36.76H32.682V39.865H32.016V36.913C31.629,37.354 31.197,37.75 30.711,38.083C30.621,37.921 30.45,37.669 30.315,37.525C31.404,36.787 32.232,35.68 32.835,34.492H30.576V33.844H33.123C33.321,33.385 33.474,32.926 33.6,32.467L34.293,32.629C34.167,33.034 34.014,33.439 33.861,33.844H38.442V34.492Z" />
<path
android:fillType="evenOdd"
android:pathData="M21.6,13.998C21.6,13.115 22.316,12.398 23.2,12.398H36.8C37.684,12.398 38.4,13.115 38.4,13.998V25.998C38.4,26.882 37.684,27.598 36.8,27.598H23.2C22.316,27.598 21.6,26.882 21.6,25.998V13.998ZM23.2,14.398C23.2,14.177 23.379,13.998 23.6,13.998H36.4C36.621,13.998 36.8,14.177 36.8,14.398L36.8,23.039C36.619,22.647 36.409,22.246 36.168,21.837C35.928,21.428 35.666,21.057 35.38,20.724C35.095,20.392 34.785,20.121 34.45,19.912C34.115,19.703 33.766,19.599 33.404,19.599C32.978,19.599 32.616,19.678 32.317,19.835C32.018,19.993 31.76,20.191 31.542,20.43C31.325,20.669 31.13,20.925 30.958,21.197C30.786,21.47 30.614,21.726 30.442,21.965C30.27,22.203 30.082,22.402 29.878,22.559C29.674,22.717 29.432,22.796 29.151,22.796C28.87,22.796 28.63,22.777 28.431,22.738C28.232,22.7 28.053,22.651 27.894,22.591C27.736,22.532 27.589,22.466 27.453,22.393C27.317,22.321 27.17,22.255 27.011,22.195C26.853,22.135 26.671,22.086 26.468,22.048C26.264,22.01 26.022,21.99 25.741,21.99C25.505,21.99 25.272,22.048 25.041,22.163C24.81,22.278 24.586,22.425 24.369,22.604C24.151,22.783 23.943,22.984 23.744,23.205C23.544,23.427 23.363,23.649 23.2,23.87L23.2,14.398Z">
<aapt:attr name="android:fillColor">
<gradient
android:endX="30"
android:endY="35.5"
android:startX="30"
android:startY="12.5"
android:type="linear">
<item
android:color="#CCFFFFFF"
android:offset="0" />
<item
android:color="#FFFFFFFF"
android:offset="1" />
</gradient>
</aapt:attr>
</path>
<path
android:fillAlpha="0.5"
android:pathData="M27.4,18.199m-1.8,0a1.8,1.8 0,1 1,3.6 0a1.8,1.8 0,1 1,-3.6 0"
android:strokeAlpha="0.5">
<aapt:attr name="android:fillColor">
<gradient
android:endX="27.4"
android:endY="21.871"
android:startX="27.4"
android:startY="16.424"
android:type="linear">
<item
android:color="#CCFFFFFF"
android:offset="0" />
<item
android:color="#FFFFFFFF"
android:offset="1" />
</gradient>
</aapt:attr>
</path>
</vector>

View File

@ -0,0 +1,13 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="100"
android:viewportHeight="100">
<path
android:pathData="M8,0L92,0A8,8 0,0 1,100 8L100,92A8,8 0,0 1,92 100L8,100A8,8 0,0 1,0 92L0,8A8,8 0,0 1,8 0z"
android:fillColor="#F5F5F5"/>
<path
android:pathData="M50,40C49.45,40 49,40.45 49,41V49H41C40.45,49 40,49.45 40,50C40,50.55 40.45,51 41,51H49V59C49,59.55 49.45,60 50,60C50.55,60 51,59.55 51,59V51H59C59.55,51 60,50.55 60,50C60,49.45 59.55,49 59,49H51V41C51,40.45 50.55,40 50,40Z"
android:fillColor="#CCCCCC"
android:fillType="evenOdd"/>
</vector>

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