Compare commits

..

41 Commits

Author SHA1 Message Date
681e2cd4ee build: 添加 3.7.0 版本的 sentry plugin 缓存
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-10-24 10:15:39 +08:00
883dad8f06 build: 添加 3.7.0 版本的 sentry plugin 缓存
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-10-23 15:51:23 +08:00
0d650d2d00 build: 添加 3.7.0 版本的 sentry plugin 缓存
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-10-23 15:50:47 +08:00
8f8ac99dae chore: 版本更新至 5.38.2
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-10-23 12:37:29 +08:00
0be7db94ad Merge branch 'hotfix/v5.38.1-1111/crashes' into 'release'
修复 5.38.1 的部分闪退问题

See merge request halo/android/assistant-android!1947
2024-10-23 11:52:09 +08:00
ae80359b48 Merge branch 'fix/catch_view_lifecycle_owner_crash' into 'release'
fix: 捕获加载图片时获取ViewLifecycleOwner出现的闪退问题

See merge request halo/android/assistant-android!1946
2024-10-23 11:47:53 +08:00
81281855a1 fix: 修复继续下载 VA 插件异常时的闪退问题(遇到异常尝试重下) https://sentry.shanqu.cc/organizations/lightgame/issues/416793
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-10-23 11:28:58 +08:00
a5174c6931 fix: 捕获加载图片时获取ViewLifecycleOwner出现的闪退问题 2024-10-23 11:16:36 +08:00
a3cc74afb3 fix: 修复部分位置在处理图片选择回调时处理不合理造成的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/416811
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-10-23 10:13:39 +08:00
84e78de6fc fix: 当 PackageName 为空时的闪退问题 https://sentry.shanqu.cc/organizations/lightgame/issues/416770
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-10-23 09:54:50 +08:00
9bf4c73250 chore: 版本更新至 5.38.1
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-10-22 15:13:34 +08:00
57a222b87a Merge branch 'hotfix/v5.38.0-1110/get_install_list_anr' into 'release'
fix: 修复错误使用全量已安装应用列表进行应用是否已安装判断依据的问题

See merge request halo/android/assistant-android!1945
2024-10-22 10:42:53 +08:00
3e125b90a2 fix: 修复错误使用全量已安装应用列表进行应用是否已安装判断依据的问题
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-10-22 10:32:33 +08:00
1809265d4f Merge branch 'fix/sentry-415534' into 'release'
fixed: Sentry415534 VA启动游戏弹窗报“Can not perform this action after onSaveInstanceState”的异常问题

See merge request halo/android/assistant-android!1944
2024-10-22 10:10:36 +08:00
3b3774596d fixed: Sentry415534 VA启动游戏弹窗报“Can not perform this action after onSaveInstanceState”的异常问题 2024-10-22 09:58:40 +08:00
bded49c366 Merge branch 'hotfix/v5.38.0-1110/remove_duplicated_sentry_log' into 'release'
fix: 移除部分 Sentry 日志上报,避免数据量过大

See merge request halo/android/assistant-android!1943
2024-10-21 18:04:47 +08:00
841711b5f1 fix: 移除部分 Sentry 日志上报,避免数据量过大
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-10-21 18:02:17 +08:00
7a080115a7 Merge branch 'hotfix/v5.38.0-1110/remove_duplicated_sentry_log' into 'release'
fix: 移除 AndroidException 类型的 Sentry 日志上报,避免数据量过大

See merge request halo/android/assistant-android!1942
2024-10-21 17:31:24 +08:00
337c4724a7 fix: 移除 AndroidException 类型的 Sentry 日志上报,避免数据量过大
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-10-21 17:28:44 +08:00
2119691bf6 Merge branch 'fix/GHZSCY-6799' into 'release'
fix: 【光环助手】V5.38版本群测汇总(1) https://jira.shanqu.cc/browse/GHZSCY-6799

See merge request halo/android/assistant-android!1941
2024-10-18 16:12:03 +08:00
caf50055c9 fix: 【光环助手】V5.38版本群测汇总(1) https://jira.shanqu.cc/browse/GHZSCY-6799 2024-10-18 16:11:13 +08:00
3ee3c2453f Merge branch 'fix/GHZSCY-6799' into 'release'
fix: 【光环助手】V5.38版本群测汇总(3) https://jira.shanqu.cc/browse/GHZSCY-6799

See merge request halo/android/assistant-android!1940
2024-10-18 15:27:05 +08:00
5e431e8a61 fix: 【光环助手】V5.38版本群测汇总(3) https://jira.shanqu.cc/browse/GHZSCY-6799 2024-10-18 15:14:18 +08:00
ff72c7cac8 Merge branch 'fix/navigation_bar_view_issue' into 'release'
fix: 修复 navigationBarView 的高度占位问题 https://jira.shanqu.cc/browse/GHZSCY-6799

See merge request halo/android/assistant-android!1939
2024-10-18 14:54:48 +08:00
047325e9bc fix: 修复 navigationBarView 的高度占位问题
Signed-off-by: chenjuntao <chenjuntao@ghzhushou.com>
2024-10-18 11:27:31 +08:00
afbb758740 Merge branch 'refactor/sync-va' into 'dev'
feat: 更新插件1.0.3 正式版本 url

See merge request halo/android/assistant-android!1938
2024-10-17 15:43:21 +08:00
5de629cfdd feat: 更新插件1.0.3 正式版本 url 2024-10-17 15:42:59 +08:00
f026623600 Merge branch 'refactor/sync-va' into 'dev'
chore: va组件升级到2.0.6

See merge request halo/android/assistant-android!1937
2024-10-17 11:24:35 +08:00
2f5ee0eb91 chore: va组件升级到2.0.6
refactor: update commit id
2024-10-17 11:24:12 +08:00
e7651e8092 Merge branch 'feat/GHZSCY-6708-patch' into 'dev'
feat: CPM微信小游戏API接入兼容OkHttp 4.x

See merge request halo/android/assistant-android!1936
2024-10-17 11:23:47 +08:00
82d51d6375 feat: CPM微信小游戏API接入兼容OkHttp 4.x 2024-10-17 11:23:24 +08:00
6080edfd8a Merge branch 'feat/GHZSCY-6708' into 'dev'
feat: CPM微信小游戏API接入工作—客户端 https://jira.shanqu.cc/browse/GHZSCY-6708

See merge request halo/android/assistant-android!1911
2024-10-17 10:17:19 +08:00
d9713571c9 feat: CPM微信小游戏API接入工作—客户端 https://jira.shanqu.cc/browse/GHZSCY-6708 2024-10-17 10:17:19 +08:00
b466525e8b Merge branch 'feat/GHZSCY-6790' into 'dev'
feat: 更新 OkHttp 至 4.X 最新版本并启用 brotli 压缩 https://jira.shanqu.cc/browse/GHZSCY-6790

See merge request halo/android/assistant-android!1935
2024-10-16 16:56:07 +08:00
2a25675dce feat: 更新 OkHttp 至 4.X 最新版本并启用 brotli 压缩 https://jira.shanqu.cc/browse/GHZSCY-6790 2024-10-16 16:56:06 +08:00
5486ad8818 Merge branch 'fix/home_appbar_style' into 'dev'
fix: 修复多tab导航页在无Tab样式下AppBar变色异常的问题

See merge request halo/android/assistant-android!1934
2024-10-16 13:47:17 +08:00
5552fcf7bc Merge branch 'fix/stop_banner_play_on_pause' into 'dev'
fix: 修复部分轮播图没有在页面暂停时停止播放的问题

See merge request halo/android/assistant-android!1933
2024-10-16 13:47:10 +08:00
782a0af13c fix: 修复多tab导航页在无Tab样式下AppBar变色异常的问题 2024-10-16 11:44:44 +08:00
c6c2d9cd12 fix: 修复部分轮播图没有在页面暂停时停止播放的问题 2024-10-16 10:09:51 +08:00
f929a08e46 Merge branch 'fix/GHZSCY-6685' into 'dev'
fix: 修复 Fresco 不能根据 ViewPager/ViewPager2 页面可见性变更动图播放状态的问题 https://jira.shanqu.cc/browse/GHZSCY-6685

See merge request halo/android/assistant-android!1930
2024-10-15 18:16:41 +08:00
e48f96d7d7 fix: 修复 Fresco 不能根据 ViewPager/ViewPager2 页面可见性变更动图播放状态的问题 https://jira.shanqu.cc/browse/GHZSCY-6685 2024-10-15 18:16:41 +08:00
273 changed files with 1815 additions and 10741 deletions

View File

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

View File

@ -101,6 +101,8 @@ android {
buildConfigField "String", "NEW_API_HOST", "\"${NEW_API_HOST}\""
buildConfigField "String", "LOG_HUB_PROJECT", "\"${LOG_HUB_PROJECT}\""
buildConfigField "String", "VAPI_HOST", "\"${VAPI_HOST}\""
buildConfigField "String", "WGAME_CPM_BUSIAPPID", "\"${WGAME_CPM_BUSIAPPID}\""
buildConfigField "String", "WGAME_CPM_API_HOST", "\"${WGAME_CPM_API_HOST}\""
buildConfigField "String", "WECHAT_APPID", "\"${WECHAT_APPID}\""
buildConfigField "String", "WECHAT_SECRET", "\"${WECHAT_SECRET}\""
buildConfigField "String", "TENCENT_APPID", "\"${TENCENT_APPID}\""

View File

@ -815,14 +815,6 @@
<!-- 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

@ -588,7 +588,7 @@ document.addEventListener("selectionchange", function(e) {
});
document.addEventListener("selectionchange", function(e) {
RE.enabledEditingItems(e)
setTimeout(() => RE.enabledEditingItems(e), 10)
});
RE.recursion = function(dom) {

View File

@ -374,7 +374,7 @@ object DefaultUrlHandler {
val iconSubscript = uri.getQueryParameter("game_icon_subscript") ?: ""
val gameEntity =
if (forumType == BbsType.OFFICIAL_BBS.value && gameId.isNotEmpty() && gameName.isNotEmpty() && icon.isNotEmpty()) {
GameEntity(id = gameId, mName = gameName, mIcon = icon, mIconSubscript = iconSubscript)
GameEntity(_id = gameId, mName = gameName, mIcon = icon, mIconSubscript = iconSubscript)
} else null
val activityLabelEntity = if (activityId.isNotEmpty() && activityName.isNotEmpty()) {
ActivityLabelEntity(

View File

@ -63,6 +63,9 @@ public class Config {
public static final String NEW_API_HOST = EnvHelper.getNewHost();
public static final String VAPI_HOST = EnvHelper.getVHost();
public static final String WGAME_CPM_BUSIAPPID = BuildConfig.WGAME_CPM_BUSIAPPID;
public static final String WGAME_CPM_API_HOST = EnvHelper.getWGameCPMHost();
// Third-Party confs
public static final String WECHAT_APPID = BuildConfig.WECHAT_APPID;
public static final String WECHAT_SECRET = BuildConfig.WECHAT_SECRET;

View File

@ -6,6 +6,7 @@ import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.LayoutManager
import androidx.recyclerview.widget.StaggeredGridLayoutManager
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.feature.exposure.ExposureEvent
import io.reactivex.functions.Consumer
@ -16,8 +17,9 @@ class ExposureListener(var fragment: Fragment, var exposable: IExposable) : Recy
val throttleBus: ExposureThrottleBus by lazy {
ExposureThrottleBus(
Consumer { commitExposure(it) },
Consumer(Throwable::printStackTrace)
{ commitExposure(it) },
Consumer(Throwable::printStackTrace),
{ commitWXCPMExposure(it) }
)
}
var layoutManager: LayoutManager? = null
@ -93,4 +95,32 @@ class ExposureListener(var fragment: Fragment, var exposable: IExposable) : Recy
ExposureManager.log(eventList)
}
/**
* 微信小游戏CPM曝光事件接口上报由于普通曝光事件的上报通道存在节流特性不符合CPM接口对于数据上报的实时性和准确性的要求所以使用额外的通道进行上报
*/
private fun commitWXCPMExposure(visibleState: ExposureThrottleBus.VisibleState) {
val eventList = arrayListOf<ExposureEvent>()
for (pos in visibleState.firstVisiblePosition..visibleState.lastVisiblePosition) {
try {
exposable.getEventByPosition(pos)?.let {
if (it.payload.miniGameType == Constants.WECHAT_MINI_GAME_CPM) {
eventList.add(it)
}
}
exposable.getEventListByPosition(pos)?.let { list ->
list.forEach {
if (it.payload.miniGameType == Constants.WECHAT_MINI_GAME_CPM) {
eventList.add(it)
}
}
}
} catch (ignore: Exception) {
// Just ignore the error.
}
}
ExposureManager.logCPM(eventList)
}
}

View File

@ -1,12 +1,13 @@
package com.gh.common.exposure
import com.gh.gamecenter.BuildConfig
import com.gh.gamecenter.common.loghub.LoghubUtils
import com.gh.gamecenter.common.constant.Constants
import com.gh.gamecenter.common.loghub.TLogHubHelper
import com.gh.gamecenter.common.utils.FixedSizeLinkedHashSet
import com.gh.gamecenter.common.utils.toJson
import com.gh.gamecenter.core.AppExecutor
import com.gh.gamecenter.feature.exposure.ExposureEvent
import com.gh.gamecenter.feature.minigame.wechat.WGameSubjectCPMListReportHelper
import com.lightgame.utils.Utils
import com.volcengine.model.tls.LogItem
@ -33,6 +34,9 @@ object ExposureManager {
*/
fun log(event: ExposureEvent) {
AppExecutor.logExecutor.execute {
if (event.payload.miniGameType == Constants.WECHAT_MINI_GAME_CPM) {
WGameSubjectCPMListReportHelper.reportExposure(event)
}
if (!exposureCache.contains(event.id)) {
exposureSet.add(event)
exposureCache.add(event.id)
@ -59,6 +63,17 @@ object ExposureManager {
}
}
/**
* Log a wechat mini game cpm collection of exposure event.
*/
fun logCPM(eventList: List<ExposureEvent>) {
AppExecutor.logExecutor.execute {
if (eventList.isNotEmpty()) {
WGameSubjectCPMListReportHelper.reportExposure(eventList.toSet())
}
}
}
/**
* @param forcedUpload Ignore all restrictions.
*/
@ -79,12 +94,7 @@ object ExposureManager {
private fun uploadExposures(eventSet: HashSet<ExposureEvent>, forced: Boolean) {
eventSet.forEach {
val jsonExposure = it.jsonExposure
if (jsonExposure == null) {
TLogHubHelper.sendLog(buildLog(it), LOG_STORE)
} else {
LoghubUtils.log(jsonExposure, it.logStore, true)
}
TLogHubHelper.sendLog(buildLog(it), LOG_STORE)
// LoghubHelper.uploadLog(buildLog(it), LOG_STORE, forced)
// it.recycle()
}

View File

@ -3,16 +3,20 @@ package com.gh.common.exposure
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.functions.Consumer
import io.reactivex.schedulers.Schedulers
import io.reactivex.subjects.PublishSubject
import io.reactivex.subjects.BehaviorSubject
import java.util.concurrent.TimeUnit
class ExposureThrottleBus(var onSuccess: Consumer<VisibleState>, var onError: Consumer<Throwable>) {
class ExposureThrottleBus(
var onSuccess: Consumer<VisibleState>,
var onError: Consumer<Throwable>,
onPreSuccess: Consumer<VisibleState>
) {
companion object {
private const val THRESHOLD_TIME = 300L
}
private val mPublishSubject: PublishSubject<VisibleState> = PublishSubject.create()
private val mPublishSubject: BehaviorSubject<VisibleState> = BehaviorSubject.create()
private val mCompositeDisposable: CompositeDisposable = CompositeDisposable()
init {
@ -24,6 +28,7 @@ class ExposureThrottleBus(var onSuccess: Consumer<VisibleState>, var onError: Co
*/
val disposable = mPublishSubject
.distinctUntilChanged()
.doOnNext(onPreSuccess)
.throttleWithTimeout(THRESHOLD_TIME, TimeUnit.MILLISECONDS)
.subscribeOn(Schedulers.io())
.subscribe(onSuccess, onError)

View File

@ -33,5 +33,9 @@ class BuildConfigImpl : IBuildConfigProvider {
override fun getVDevApiHost(): String = BuildConfig.DEV_VAPI_HOST
override fun getWGameCPMApiHost(): String = BuildConfig.WGAME_CPM_API_HOST
override fun getWGameCPMBusiAppId(): String = BuildConfig.WGAME_CPM_BUSIAPPID
override fun getLogProducerProject(): String = BuildConfig.LOG_HUB_PROJECT
}

View File

@ -62,7 +62,13 @@ class DirectProviderImpl : IDirectProvider {
DirectUtils.directToCommunityArticle(context, articleId, communityId, entrance, path, sourceEntrance)
}
override fun directToVideoDetail(context: Context, videoId: String, entrance: String?, path: String?, sourceEntrance: String) {
override fun directToVideoDetail(
context: Context,
videoId: String,
entrance: String?,
path: String?,
sourceEntrance: String
) {
DirectUtils.directToVideoDetail(context, videoId, entrance, path, sourceEntrance)
}
@ -78,8 +84,13 @@ class DirectProviderImpl : IDirectProvider {
DirectUtils.directToQQGameById(activity, qqAppId)
}
override fun directToWechatGameById(activity: Activity, qqAppId: String) {
DirectUtils.directToWechatGameById(activity, qqAppId)
override fun directToWechatGameById(
activity: Activity,
wechatAppId: String,
wechatAppPath: String,
wechatAppExtData: String
) {
DirectUtils.directToWechatGameById(activity, wechatAppId, wechatAppPath, wechatAppExtData)
}
override fun directToExternalBrowser(context: Context, url: String) {

View File

@ -77,19 +77,6 @@ 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,7 +14,6 @@ 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
@ -25,17 +24,15 @@ 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(report: CommunityReporter) {
fun showReportDialog(contentId: String) {
val sp = PreferenceManager.getDefaultSharedPreferences(HaloApp.getInstance())
val suggestion: SettingsEntity.Suggestion? =
sp.getString(CommonConsts.SUGGESTION_HINT_TYPE, null)?.toObject()
@ -55,7 +52,7 @@ object BbsReportHelper {
binding.normalReasonContainer.visibility = View.GONE
binding.otherReasonContainer.visibility = View.VISIBLE
} else {
report.postReport(json {
postReport(contentId, json {
"reason" to reason
})
dialog.cancel()
@ -85,7 +82,7 @@ object BbsReportHelper {
) {
ToastUtils.showToast("请填写举报原因")
} else {
report.postReport(json {
postReport(contentId, json {
"reason" to "其它"
"description" to binding.otherReasonEt.text.toString()
})
@ -108,45 +105,28 @@ object BbsReportHelper {
}
}
interface CommunityReporter {
@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("举报成功")
}
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()
)
}
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,23 +119,6 @@ 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,
@ -146,7 +129,6 @@ object CommentHelper {
questionId: String? = null,
videoId: String? = null,
gameCollectionId: String? = null,
imageArticleId: String? = null,
isShowTopOrAccept: Boolean = false,
ignoreModerator: Boolean = false,
isVideoAuthor: Boolean = false,
@ -155,7 +137,8 @@ object CommentHelper {
val context = view.context
val dialogOptions = ArrayList<String>()
val isContentAuthor = commentEntity.me?.isContentAuthor == true
if (isShowTopOrAccept && (articleId != null || questionId != null || videoId != null || imageArticleId != null) && isContentAuthor) {
if (isShowTopOrAccept && (articleId != null || questionId != null || videoId != null) && isContentAuthor) {
dialogOptions.add(if (commentEntity.isTop) "取消置顶" else "置顶")
}
if (isShowTopOrAccept && questionId != null && isContentAuthor) {
@ -246,7 +229,6 @@ object CommentHelper {
commentListener
)
}
articleId != null -> {
PostCommentUtils.reportCommunityArticleComment(
commentEntity.id,
@ -254,7 +236,6 @@ object CommentHelper {
commentListener
)
}
questionId != null -> {
PostCommentUtils.reportQuestionComment(
questionId,
@ -263,7 +244,6 @@ object CommentHelper {
commentListener
)
}
gameCollectionId != null -> {
PostCommentUtils.reportGameCollectionComment(
gameCollectionId,
@ -272,15 +252,6 @@ object CommentHelper {
commentListener
)
}
imageArticleId != null -> {
PostCommentUtils.reportImageArticleComment(
commentEntity.id,
reportType,
commentListener
)
}
else -> {
PostCommentUtils.reportVideoComment(
videoId,
@ -316,17 +287,6 @@ object CommentHelper {
null
)
)
} else if (imageArticleId != null) {
context.startActivity(
CommentDetailActivity
.getImageArticleCommentIntent(
context,
imageArticleId,
commentEntity.id,
communityId,
null
)
)
} else {
context.startActivity(
CommentDetailActivity

View File

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

View File

@ -485,11 +485,13 @@ object DirectUtils {
ColumnCollectionDetailFragment.TYPE_QQ_MINI_GAME_COLUMN -> directToQGameHome(context)
// QQ游戏专题详情页
ViewPagerFragmentHelper.TYPE_QQ_MINI_GAME_COLUMN, ViewPagerFragmentHelper.TYPE_WECHAT_GAME_COLUMN -> {
val subjectType = if (linkEntity.type == ViewPagerFragmentHelper.TYPE_QQ_MINI_GAME_COLUMN) {
SubjectData.SubjectType.QQ_GAME
} else {
SubjectData.SubjectType.WECHAT_GAME
ViewPagerFragmentHelper.TYPE_QQ_MINI_GAME_COLUMN,
ViewPagerFragmentHelper.TYPE_WECHAT_GAME_COLUMN,
ViewPagerFragmentHelper.TYPE_WECHAT_GAME_CPM_COLUMN -> {
val subjectType = when (linkEntity.type) {
ViewPagerFragmentHelper.TYPE_QQ_MINI_GAME_COLUMN -> SubjectData.SubjectType.QQ_GAME
ViewPagerFragmentHelper.TYPE_WECHAT_GAME_CPM_COLUMN -> SubjectData.SubjectType.WECHAT_GAME_CPM
else -> SubjectData.SubjectType.WECHAT_GAME
}
directToSubject(
context = context,
@ -568,16 +570,6 @@ 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
}
@ -2019,6 +2011,8 @@ object DirectUtils {
fun directToWechatGameById(
activity: Activity,
wechatAppId: String,
wechatAppPath: String = "",
wechatAppExtData: String = ""
) {
val wxApiProxy = WXAPIFactory.createWXAPI(
@ -2033,8 +2027,9 @@ object DirectUtils {
wxApiProxy.sendReq(
WXLaunchMiniProgram.Req().apply {
userName = wechatAppId
path = Constants.WECHAT_MINI_GAME_PCS
miniprogramType = WXLaunchMiniProgram.Req.MINIPTOGRAM_TYPE_RELEASE;
path = wechatAppPath.ifEmpty { Constants.WECHAT_MINI_GAME_PCS }
extData = wechatAppExtData
miniprogramType = WXLaunchMiniProgram.Req.MINIPTOGRAM_TYPE_RELEASE
}
)

View File

@ -880,7 +880,7 @@ object DownloadItemUtils {
}
if (gameEntity.isMiniGame()) {
downloadBtn.setOnClickListener {
MiniGameItemHelper.launchMiniGame(gameEntity.miniGameAppId, gameEntity.miniGameType)
MiniGameItemHelper.launchMiniGame(gameEntity)
clickCallback?.onCallback()
allStateClickCallback?.onCallback()
}

View File

@ -494,7 +494,7 @@ object DownloadObserver {
java.lang.Boolean.parseBoolean(downloadEntity.getMetaExtra(Constants.IS_PLATFORM_RECOMMEND))
val exposureEvent = ExposureUtils.logADownloadCompleteExposureEvent(
GameEntity(
id = downloadEntity.gameId,
_id = downloadEntity.gameId,
mName = downloadEntity.name.removeSuffix(Constants.GAME_NAME_DECORATOR),
gameVersion = downloadEntity.versionName ?: "",
isPlatformRecommend = isPlatformRecommend,

View File

@ -1,9 +1,6 @@
package com.gh.common.util;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Handler;
import android.os.Message;
@ -19,10 +16,8 @@ import org.greenrobot.eventbus.EventBus;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
* 下载完成跳安装,
*/
@ -49,15 +44,15 @@ public class InstallUtils {
@Override
public void handleMessage(Message msg) {
if (msg.what == INSTALL_WHAT && packageManager != null) {
List<String> packageNameList = PackageHelper.INSTANCE.getInstalledPackageNameList(context, 0);
ArrayList<String> list = new ArrayList<>(packageNameList);
if (installMap != null && installMap.size() != 0) {
ArrayList<String> keys = new ArrayList<>();
for (String packageName : installMap.keySet()) {
if (TextUtils.isEmpty(packageName)) continue;
long time = installMap.get(packageName);
if (System.currentTimeMillis() - time >= MAX_TIME) {
keys.add(packageName);
} else if (list.contains(packageName)) {
} else if (PackageUtils.isInstalled(context, packageName)) {
keys.add(packageName);
DownloadEntity downloadEntity = DownloadManager.getInstance().getDownloadEntityByPackageName(packageName);
@ -80,7 +75,7 @@ public class InstallUtils {
long time = uninstallMap.get(packageName);
if (System.currentTimeMillis() - time >= MAX_TIME) {
keys.add(packageName);
} else if (!list.contains(packageName)) {
} else if (!PackageUtils.isInstalled(context, packageName)) {
keys.add(packageName);
EventBus.getDefault().post(new EBPackage("卸载", packageName, "", false));
}
@ -105,6 +100,8 @@ public class InstallUtils {
}
public void addInstall(String packageName) {
if (TextUtils.isEmpty(packageName)) return;
if (installMap == null) {
installMap = Collections.synchronizedMap(new HashMap<String, Long>());
}

View File

@ -33,8 +33,6 @@ 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)
@ -98,12 +96,7 @@ 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
@ -153,12 +146,7 @@ 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
@ -752,7 +740,7 @@ object NewFlatLogUtils {
"search_key" to searchKey
parseAndPutMeta().invoke(this)
}
log(json, LOG_STORE_BBS_COMMUNITY)
log(json)
}
//社区搜索结果浏览
@ -764,7 +752,7 @@ object NewFlatLogUtils {
"search_key" to searchKey
parseAndPutMeta().invoke(this)
}
log(json, LOG_STORE_BBS_COMMUNITY)
log(json)
}
//社区搜索tab结果切换事件
@ -776,7 +764,7 @@ object NewFlatLogUtils {
"tab_type" to tab
parseAndPutMeta().invoke(this)
}
log(json, LOG_STORE_BBS_COMMUNITY)
log(json)
}
//点击论坛-搜索页返回按钮
@ -788,7 +776,7 @@ object NewFlatLogUtils {
"bbs_id" to bbsId
parseAndPutMeta().invoke(this)
}
log(json, LOG_STORE_BBS_COMMUNITY)
log(json)
}
//论坛搜索结果浏览
@ -801,7 +789,7 @@ object NewFlatLogUtils {
"bbs_id" to bbsId
parseAndPutMeta().invoke(this)
}
log(json, LOG_STORE_BBS_COMMUNITY)
log(json)
}
//新版我的光环触发登录
@ -881,7 +869,7 @@ object NewFlatLogUtils {
"bbs_id" to bbsId
parseAndPutMeta().invoke(this)
}
log(json, LOG_STORE_BBS_COMMUNITY)
log(json)
}
//个人主页-内容
@ -894,7 +882,7 @@ object NewFlatLogUtils {
"tab_name" to tabName
parseAndPutMeta().invoke(this)
}
log(json, LOG_STORE_BBS_COMMUNITY)
log(json)
}
//游戏详情页-视频合集内容
@ -906,7 +894,7 @@ object NewFlatLogUtils {
"ref_user_id" to refUserId
parseAndPutMeta().invoke(this)
}
log(json, LOG_STORE_BBS_COMMUNITY)
log(json)
}
//进入游戏详情

View File

@ -31,10 +31,9 @@ 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"
const val LOG_STORE_BBS = "bbs_community"
private const val LOG_STORE_BBS = "bbs_community"
private fun log(jsonObject: JSONObject, logStore: String, uploadImmediately: Boolean = false) {
Utils.log("NewLogUtils", jsonObject.toString(4))
@ -2105,13 +2104,7 @@ 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
@ -2645,183 +2638,4 @@ 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

@ -814,7 +814,7 @@ object PackageHelper {
uploadUIDGapLog = false
val uidGap = (android.os.Process.LAST_APPLICATION_UID - lastValidUid) / 100 * 100
SentryHelper.onEvent("UID_GAP", "gap", uidGap.toString())
// SentryHelper.onEvent("UID_GAP", "gap", uidGap.toString())
}
return packageList

View File

@ -225,17 +225,6 @@ public class PackageUtils {
}
} catch (Exception e) {
e.printStackTrace();
if (e instanceof AndroidException) {
// 有些设备会出现 DeadSystemException
SentryHelper.INSTANCE.onEvent(
"GET_META_DATA_ERROR",
"packageName",
packageName,
"exception_digest",
e.getLocalizedMessage()
);
}
}
return null;
}
@ -497,7 +486,6 @@ public class PackageUtils {
}
} catch (Exception e) {
e.printStackTrace();
SentryHelper.INSTANCE.onEvent("GET_PACKAGE_INFO_ERROR", "path", path);
}
return null;
@ -640,17 +628,6 @@ public class PackageUtils {
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT).versionName;
} catch (Exception e) {
e.printStackTrace();
if (e instanceof AndroidException) {
// 有些设备会出现 DeadSystemException
SentryHelper.INSTANCE.onEvent(
"GET_VERSION_NAME_ERROR",
"packageName",
packageName,
"exception_digest",
e.getLocalizedMessage()
);
}
}
return null;
}
@ -664,16 +641,6 @@ public class PackageUtils {
PackageManager.COMPONENT_ENABLED_STATE_DEFAULT).versionCode;
} catch (Exception e) {
e.printStackTrace();
if (e instanceof AndroidException) {
// 有些设备会出现 DeadSystemException
SentryHelper.INSTANCE.onEvent(
"GET_VERSION_CODE_ERROR",
"packageName",
packageName,
"exception_digest",
e.getLocalizedMessage()
);
}
}
return 0;
}
@ -688,16 +655,6 @@ public class PackageUtils {
return packageManager.getApplicationIcon(packageName);
} catch (Exception e) {
e.printStackTrace();
if (e instanceof AndroidException) {
// 有些设备会出现 DeadSystemException
SentryHelper.INSTANCE.onEvent(
"GET_ICON_ERROR",
"packageName",
packageName,
"exception_digest",
e.getLocalizedMessage()
);
}
}
return null;
}
@ -731,17 +688,6 @@ public class PackageUtils {
return jsonObject;
} catch (Exception e) {
e.printStackTrace();
if (e instanceof AndroidException) {
// 有些设备会出现 DeadSystemException
SentryHelper.INSTANCE.onEvent(
"GET_APP_BASIC_INFO_BY_PACKAGE_NAME",
"packageName",
packageName,
"exception_digest",
e.getLocalizedMessage()
);
}
return jsonObject;
}
}

View File

@ -77,7 +77,6 @@ public class PostCommentUtils {
String articleId,
String videoId,
String questionId,
final String imageArticleId,
final String commentId,
final PostCommentListener listener) {
@ -89,8 +88,6 @@ 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);
}
@ -119,7 +116,6 @@ public class PostCommentUtils {
String articleCommunityId,
String videoId,
String questionId,
String imageArticleId,
final String commentId,
final PostCommentListener listener) {
Observable<ResponseBody> observable;
@ -128,8 +124,6 @@ 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);
}
@ -306,25 +300,6 @@ 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

@ -27,7 +27,9 @@ import com.gh.gamecenter.retrofit.RetrofitManager
import com.halo.assistant.HaloApp
import com.lightgame.utils.Utils
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.ResponseBody
import org.json.JSONArray
import org.json.JSONObject
@ -176,7 +178,7 @@ object UsageStatsHelper {
return
}
val body = RequestBody.create(MediaType.parse("application/json"), postBody.toString())
val body = postBody.toString().toRequestBody("application/json".toMediaTypeOrNull())
mApi.postUsageStatus(body, UserManager.getInstance().userId)
.subscribe(object : BiResponse<ResponseBody>() {
override fun onSuccess(data: ResponseBody) {

View File

@ -65,6 +65,7 @@ object ViewPagerFragmentHelper {
const val TYPE_COLUMN = "column" // 游戏专题详情页
const val TYPE_QQ_MINI_GAME_COLUMN = "qq_mini_game_column_detail" // QQ小游戏专题详情页
const val TYPE_WECHAT_GAME_COLUMN = "wechat_game_column_detail" // 微信小游戏专题详情页
const val TYPE_WECHAT_GAME_CPM_COLUMN = "wechat_game_cpm_column_detail" // 微信小游戏CPM专题详情页
const val TYPE_COLUMN_COLLECTION = "column_collection" // 专题合集详情页
const val TYPE_SERVER = "server" // 开服表
const val TYPE_COLUMN_TEST = "column_test_v2" // 新游开测
@ -163,10 +164,11 @@ object ViewPagerFragmentHelper {
className = GameCollectionSquareFragment::class.java.name
}
// 游戏专题详情页/QQ游戏专题详情页
TYPE_COLUMN, TYPE_QQ_MINI_GAME_COLUMN, TYPE_WECHAT_GAME_COLUMN -> {
TYPE_COLUMN, TYPE_QQ_MINI_GAME_COLUMN, TYPE_WECHAT_GAME_COLUMN, TYPE_WECHAT_GAME_CPM_COLUMN -> {
val subjectType = when(entity.type) {
TYPE_QQ_MINI_GAME_COLUMN -> SubjectData.SubjectType.QQ_GAME
TYPE_WECHAT_GAME_COLUMN -> SubjectData.SubjectType.WECHAT_GAME
TYPE_WECHAT_GAME_CPM_COLUMN -> SubjectData.SubjectType.WECHAT_GAME_CPM
else -> SubjectData.SubjectType.NORMAL
}
className = SubjectFragment::class.java.name

View File

@ -14,7 +14,6 @@ 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
@ -312,7 +311,7 @@ class ImageContainerView : LinearLayout {
|| images.isNullOrEmpty())
return ImageContainerData(
status = status,
isPostCard = type == COMMUNITY_ARTICLE || type == ImageArticleEntity.IMAGE_ARTICLE_TYPE,
isPostCard = type == COMMUNITY_ARTICLE,
images = imageInfoList,
video = video,
show

View File

@ -1,277 +0,0 @@
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

@ -1,107 +0,0 @@
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

@ -34,7 +34,9 @@ import com.lightgame.utils.Utils
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONException
import org.json.JSONObject
import java.util.*
@ -216,7 +218,7 @@ object PackageObserver {
try {
jsonObject.put("game_id", gameId)
jsonObject.put("package", packageName)
val rBody = RequestBody.create(MediaType.parse("application/json"), jsonObject.toString())
val rBody = jsonObject.toString().toRequestBody("application/json".toMediaTypeOrNull())
RetrofitManager.getInstance().api
.postPlayedGame(UserManager.getInstance().userId, rBody)
.subscribeOn(Schedulers.io())

View File

@ -94,8 +94,8 @@ object ExoCacheManager {
response =
OkHttpClient.Builder().connectTimeout(5, TimeUnit.SECONDS).readTimeout(5, TimeUnit.SECONDS).build()
.newCall(request).execute()
if (response!!.isSuccessful && response.body() != null) {
val length = response.body()!!.contentLength()
if (response!!.isSuccessful && response.body != null) {
val length = response.body!!.contentLength()
contentLength = if (length == 0L) -1L else length
}
} catch (e: Exception) {

View File

@ -27,12 +27,24 @@ object SimpleDownloadManager {
val downloadStatus = mDownloadQueue.getStatus(config.uniqueId)
if (downloadStatus != DownloadStatus.PAUSED) {
ExecutorProvider.getInstance().backgroundExecutor.execute {
DownloadMessageHandler.insertDownloadToDatabase(getDownloadEntity(config))
mDownloadQueue.submitNewTask(config)
}
createNewTaskAndDownload(config)
} else {
resume(config.uniqueId)
try {
resume(config.uniqueId)
} catch (e: IllegalArgumentException) {
createNewTaskAndDownload(config)
}
}
}
/**
* 创建新任务并下载
*/
private fun createNewTaskAndDownload(config: DownloadConfig) {
ExecutorProvider.getInstance().backgroundExecutor.execute {
mDownloadQueue.cancel(config.uniqueId)
DownloadMessageHandler.insertDownloadToDatabase(getDownloadEntity(config))
mDownloadQueue.submitNewTask(config)
}
}

View File

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

@ -408,7 +408,7 @@ public class SkipActivity extends BaseActivity {
try {
JSONObject extJsonObject = new JSONObject(extJson);
String qqGameId = extJsonObject.optString("aid");
MiniGameItemHelper.INSTANCE.launchMiniGame(qqGameId, Constants.QQ_MINI_GAME);
MiniGameItemHelper.INSTANCE.launchMiniGame(qqGameId, Constants.QQ_MINI_GAME, "", "");
} catch (JSONException ignored) {
}
break;

View File

@ -27,7 +27,7 @@ open class BaseCloudArchiveViewModel(application: Application, private val mConf
)
.enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
mArchiveConfigStr = response.body()?.string() ?: ""
mArchiveConfigStr = response.body?.string() ?: ""
callback?.invoke(mArchiveConfigStr)
}

View File

@ -93,7 +93,7 @@ class AdGameBannerAdapter(
it.name ?: ""
)
if (it.isMiniGame()) {
MiniGameItemHelper.launchMiniGame(it.miniGameAppId, it.miniGameType)
MiniGameItemHelper.launchMiniGame(it)
} else {
GameDetailActivity.startGameDetailActivity(
mContext,

View File

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

@ -1,230 +0,0 @@
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,13 +36,12 @@ data class PersonalHistoryEntity(
var length: Long = 0,
@SerializedName("count")
private var _count: Count = Count(),
var time: Long = 0,
val time: Long = 0,
@SerializedName("title")
private var _title: String = "",
var description: String = "",
@SerializedName("community")
private var _community: CommunityEntity? = null,
private var _community: CommunityEntity = CommunityEntity(),
var videos: List<CommunityVideoEntity> = ArrayList(),
@SerializedName("user")
private var _user: UserEntity? = null,
@ -64,17 +63,8 @@ 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) {
@ -114,7 +104,7 @@ data class PersonalHistoryEntity(
}
override var community: CommunityEntity
get() = _community ?: CommunityEntity()
get() = _community
set(value) {
_community = value
}

View File

@ -1,17 +0,0 @@
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

@ -9,14 +9,16 @@ import kotlinx.parcelize.Parcelize
@Parcelize
data class SearchSubjectEntity(
val name: String = "",
val games: List<GameEntity> = listOf(),
var games: List<GameEntity> = listOf(),
val location: Int = 0,
@SerializedName("column_id")
val columnId: String = "",
val adId: String = "", // 广告ID(本地字段),不为空时为广告专题
val codeId: String = "", // 广告CODE_ID(本地字段),不为空时为广告专题
@SerializedName("ad_icon_active")
val adIconActive: Boolean = false
val adIconActive: Boolean = false,
// 本地字段标记是否为微信小游戏CPM专题
var isWGameSubjectCPM: Boolean = false
) : Parcelable {
fun getFilterGame() = RegionSettingHelper.filterGame(games)
}

View File

@ -63,6 +63,11 @@ class SubjectData(
/**
* 微信小游戏专题
*/
WECHAT_GAME
WECHAT_GAME,
/**
* 微信小游戏CPM专题
*/
WECHAT_GAME_CPM,
}
}

View File

@ -104,6 +104,8 @@ data class SubjectEntity(
@SerializedName("is_wechat_column")
var isWechatColumn: Boolean = false,
var isWechatColumnCPM: Boolean = false,
var explain: String = "", // 游戏单合集说明
@SerializedName("show_star")
@ -130,6 +132,7 @@ data class SubjectEntity(
val subjectType: SubjectData.SubjectType get() = when {
isQQColumn -> SubjectData.SubjectType.QQ_GAME
isWechatColumnCPM -> SubjectData.SubjectType.WECHAT_GAME_CPM
isWechatColumn -> SubjectData.SubjectType.WECHAT_GAME
else -> SubjectData.SubjectType.NORMAL
}

View File

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

View File

@ -1,16 +0,0 @@
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,13 +1,11 @@
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
@ -16,8 +14,6 @@ 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
@ -25,7 +21,6 @@ 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
@ -52,7 +47,7 @@ class ForumArticleAskListAdapter(
}
override fun areItemsTheSame(oldItem: AnswerEntity?, newItem: AnswerEntity?): Boolean {
return oldItem?.id == newItem?.id && oldItem?.type == newItem?.type
return oldItem?.id == newItem?.id
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
@ -60,17 +55,9 @@ 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(
@ -108,8 +95,7 @@ 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"
@ -162,7 +148,6 @@ class ForumArticleAskListAdapter(
)
)
}
"video" -> {
NewLogUtils.logForumDetailFeedContentClick(
"click_forum_detail_content",
@ -179,16 +164,8 @@ 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",
@ -211,7 +188,6 @@ class ForumArticleAskListAdapter(
)
)
}
"answer" -> {
NewLogUtils.logForumDetailFeedContentClick(
"click_forum_detail_content",
@ -239,63 +215,29 @@ 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,7 +10,6 @@ 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
@ -19,13 +18,10 @@ 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
@ -136,72 +132,6 @@ 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,13 +4,11 @@ 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
@ -60,11 +58,9 @@ class ForumArticleAskListViewModel(application: Application, val bbsId: String =
}
}
"精华" -> {
RetrofitManager.getInstance().api.getEssenceForumList(bbsId, page)
}
"问答" -> {
RetrofitManager.getInstance().api.getAskForumList(
bbsId,
@ -72,7 +68,6 @@ class ForumArticleAskListViewModel(application: Application, val bbsId: String =
page
)
}
else -> {
RetrofitManager.getInstance().api.getVideoForumList(
bbsId,
@ -99,28 +94,6 @@ 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,7 +8,6 @@ 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
@ -29,7 +28,6 @@ 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
@ -37,7 +35,6 @@ 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
@ -59,7 +56,6 @@ 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
@ -83,7 +79,6 @@ 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
@ -270,14 +265,12 @@ 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)
@ -311,12 +304,7 @@ 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 {
@ -642,10 +630,7 @@ 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()) {
@ -896,7 +881,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(
@ -1035,31 +1020,14 @@ 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
@ -1121,35 +1089,6 @@ 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 {
@ -1275,12 +1214,10 @@ 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,13 +17,11 @@ 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
@ -38,7 +36,6 @@ 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
@ -55,8 +52,6 @@ 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
@ -73,7 +68,6 @@ 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
@ -133,7 +127,6 @@ class CommunityHomeFragment : LazyFragment() {
}
override fun getRealLayoutId(): Int {
return R.layout.fragment_community_home
}
@ -142,6 +135,7 @@ class CommunityHomeFragment : LazyFragment() {
mBinding = FragmentCommunityHomeBinding.bind(inflatedView)
}
override fun onFragmentFirstVisible() {
ArticleDetailWebCacheManager.init(requireContext().applicationContext)
@ -168,8 +162,7 @@ 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)
}
@ -313,14 +306,14 @@ class CommunityHomeFragment : LazyFragment() {
?: FollowHomeFragment()
mFragmentList.add(followFragment)
val recommendFragment = childFragmentManager.findFragmentByTag("$tag$TAB_RECOMMEND_INDEX")
?: ImageArticleHomeFragment.newInstance().with(
val forumArticleListFragment = childFragmentManager.findFragmentByTag("$tag$TAB_RECOMMEND_INDEX")
?: ForumArticleListFragment().with(
bundleOf(
EntranceConsts.KEY_ENTRANCE to "社区",
EntranceConsts.KEY_PATH to "推荐"
)
)
mFragmentList.add(recommendFragment)
mFragmentList.add(forumArticleListFragment)
val forumFragment = childFragmentManager.findFragmentByTag("${tag}$TAB_FORUM_INDEX")
?: ForumFragment().with(bundleOf(EntranceConsts.KEY_ENTRANCE to "社区"))
@ -341,18 +334,9 @@ 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")
@ -360,11 +344,7 @@ 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")
@ -372,11 +352,7 @@ 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)
@ -386,11 +362,7 @@ 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)
@ -615,20 +587,6 @@ 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 {
@ -679,7 +637,6 @@ class CommunityHomeFragment : LazyFragment() {
}
}
private fun resetFollowTab() {
mBinding?.tabLayout?.run {
if (selectedTabPosition == TAB_FOLLOW_INDEX) {
@ -696,11 +653,6 @@ 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
@ -723,13 +675,13 @@ class CommunityHomeFragment : LazyFragment() {
}
private fun insertDataToRecommendTab(entity: ArticleEntity) {
// 废弃 后续会逐渐将 activityForResult迁移到 launcher
(mFragmentList[TAB_RECOMMEND_INDEX] as? ForumArticleListFragment)?.insertDataToFirstIndex(entity)
}
override fun onBackPressed(): Boolean {
mBinding?.viewPager?.run {
if (currentItem == 1) {
return (mFragmentList[1] as ImageArticleHomeFragment).onBackPressed()
return (mFragmentList[1] as ForumArticleListFragment).onBackPressed()
}
}
return super.onBackPressed()
@ -838,11 +790,7 @@ 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()))
)
}
}
@ -851,9 +799,7 @@ 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()
)
)
@ -874,12 +820,10 @@ 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)
@ -912,7 +856,6 @@ 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,7 +8,6 @@ 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,10 +4,8 @@ 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
@ -19,8 +17,6 @@ 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.*
@ -28,7 +24,6 @@ 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
@ -111,14 +106,9 @@ class ForumArticleAskItemViewHolder(
else -> R.drawable.icon_forum_fail
}
)
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
}
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
popularAnswerContainer.background = GradientDrawable().apply {
setColor(Color.parseColor("#F5F6F7"))
cornerRadius = 4F.dip2px().toFloat()
@ -148,8 +138,11 @@ 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()) {
@ -209,7 +202,6 @@ class ForumArticleAskItemViewHolder(
"community_article" -> "帖子"
"video" -> "视频帖"
"question" -> "提问帖"
ImageArticleEntity.IMAGE_ARTICLE_TYPE -> "图文"
else -> "提问帖评论"
}
val userId = user.id ?: ""
@ -388,7 +380,6 @@ 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 {
@ -458,17 +449,15 @@ class ForumArticleAskItemViewHolder(
"community_article" -> "帖子"
"video" -> "视频帖"
"question" -> "提问帖"
ImageArticleEntity.IMAGE_ARTICLE_TYPE -> "图文"
else -> "提问帖评论"
}
val sequence = position + 1
val bbsId = entity.community.id
val bbsType = entity.community.typeChinese
val bbsType = if (entity.community.type == "official_bbs") "综合论坛" else "游戏论坛"
val tabInfo = "${path}tab"
val commentType = when (entity.type) {
"community_article" -> "帖子评论"
"video" -> "视频帖评论"
ImageArticleEntity.IMAGE_ARTICLE_TYPE ->"图文评论"
else -> "提问帖评论"
}
NewLogUtils.logForumDetailFeedContentClick(
@ -539,14 +528,6 @@ 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()
}
}
}
@ -563,12 +544,11 @@ class ForumArticleAskItemViewHolder(
"community_article" -> "帖子"
"video" -> "视频帖"
"question" -> "提问帖"
ImageArticleEntity.IMAGE_ARTICLE_TYPE -> "图文"
else -> "提问帖评论"
}
val sequence = position + 1
val bbsId = entity.community.id
val bbsType = entity.community.typeChinese
val bbsType = if (entity.community.type == "official_bbs") "综合论坛" else "游戏论坛"
val tabInfo = "${path}tab"
NewLogUtils.logForumDetailFeedContentClick(
"click_forum_detail_like",

View File

@ -16,7 +16,6 @@ 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
@ -72,10 +71,7 @@ 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))
}
@ -102,7 +98,6 @@ class ForumArticleListAdapter(
"community_article" -> "帖子"
"video" -> "视频帖"
"question" -> "提问帖"
ImageArticleEntity.IMAGE_ARTICLE_TYPE -> "图文"
else -> "提问帖评论"
}
val bbsType = if (community.type == "official_bbs") "综合论坛" else "游戏论坛"
@ -141,7 +136,6 @@ class ForumArticleListAdapter(
)
)
}
"video" -> {
MtaHelper.onEvent(
"论坛首页",
@ -163,7 +157,6 @@ class ForumArticleListAdapter(
)
)
}
"question" -> {
MtaHelper.onEvent(
"论坛首页",
@ -187,7 +180,6 @@ class ForumArticleListAdapter(
)
)
}
"answer" -> {
MtaHelper.onEvent(
"论坛首页",
@ -221,12 +213,7 @@ 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,19 +2,14 @@ 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
@ -240,15 +235,6 @@ 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))
}
@ -272,14 +258,6 @@ 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,9 +1,8 @@
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 {
@ -20,14 +19,6 @@ 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
@ -48,21 +39,6 @@ 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(
@ -77,20 +53,6 @@ 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,6 +28,8 @@ 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,8 +3,11 @@ 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
@ -13,29 +16,23 @@ 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>() {
@ -270,99 +267,10 @@ 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,7 +35,6 @@ 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
@ -70,7 +69,6 @@ class FollowHomeFragment : LazyFragment(), IScrollable {
ownerProducer = { requireParentFragment() }
)
private lateinit var userViewModel: UserViewModel
private var userId: String? = null
@ -456,30 +454,6 @@ 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,7 +7,6 @@ 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,17 +2,12 @@ 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
@ -34,13 +29,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))
}
@ -123,15 +118,6 @@ 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,5 +1,6 @@
package com.gh.gamecenter.forum.home.follow.viewmodel
import android.view.ViewGroup
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
@ -7,7 +8,6 @@ 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,15 +119,5 @@ 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,7 +8,6 @@ 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
@ -17,8 +16,6 @@ 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
@ -315,71 +312,5 @@ 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

@ -1,59 +0,0 @@
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

@ -1,154 +0,0 @@
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

@ -1,241 +0,0 @@
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

@ -1,11 +0,0 @@
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

@ -1,51 +0,0 @@
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

@ -1,209 +0,0 @@
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

@ -1,107 +0,0 @@
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

@ -1,42 +0,0 @@
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

@ -1,15 +0,0 @@
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

@ -1,79 +0,0 @@
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

@ -1,193 +0,0 @@
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

@ -1,269 +0,0 @@
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

@ -1,136 +0,0 @@
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

@ -1,938 +0,0 @@
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

@ -1,166 +0,0 @@
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

@ -1,282 +0,0 @@
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

@ -1,216 +0,0 @@
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

@ -1,525 +0,0 @@
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

@ -1,118 +0,0 @@
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

@ -1,334 +0,0 @@
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

@ -1,101 +0,0 @@
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

@ -1,77 +0,0 @@
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

@ -1,81 +0,0 @@
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

@ -1,129 +0,0 @@
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

@ -1,234 +0,0 @@
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

@ -611,7 +611,7 @@ class GameFragmentAdapter(
val subjectData = gameEntity.subjectData
DataCollectionUtils.uploadClick(mContext, subjectData?.name + "-列表", "游戏-专题", gameEntity.name)
if (gameEntity.isMiniGame()) {
MiniGameItemHelper.launchMiniGame(gameEntity.miniGameAppId, gameEntity.miniGameType)
MiniGameItemHelper.launchMiniGame(gameEntity)
} else {
GameDetailActivity.startGameDetailActivity(
mContext, gameEntity,
@ -1322,7 +1322,7 @@ class GameFragmentAdapter(
DataCollectionUtils.uploadClick(mContext, subjectData.name + "-列表", "游戏-专题", gameEntity.name)
if (gameEntity.isMiniGame()) {
MiniGameItemHelper.launchMiniGame(gameEntity.miniGameAppId, gameEntity.miniGameType)
MiniGameItemHelper.launchMiniGame(gameEntity)
} else if (gameEntity.isPluggable) {
GameDetailActivity.startGameDetailActivity(
mContext,

View File

@ -135,7 +135,7 @@ class GameGallerySlideViewHolder(val binding: GameGallerySlideItemBinding) : Bas
binding.iconIv.displayGameIcon(gameEntity)
binding.iconIv.setOnClickListener {
if (gameEntity.isMiniGame()) {
MiniGameItemHelper.launchMiniGame(gameEntity.miniGameAppId, gameEntity.miniGameType)
MiniGameItemHelper.launchMiniGame(gameEntity)
} else {
GameDetailActivity.startGameDetailActivity(
binding.root.context,

View File

@ -55,7 +55,7 @@ class GameGalleryViewHolder(val cell: GameGalleryItemCell) :
if (subjectEntity.isMiniGame) {
gameIcon.setOnClickListener {
MiniGameItemHelper.launchMiniGame(gameEntity.miniGameAppId, gameEntity.miniGameType)
MiniGameItemHelper.launchMiniGame(gameEntity)
}
} else {
gameIcon.setOnClickListener(null)

View File

@ -140,7 +140,7 @@ class GameHorizontalAdapter(
}
if (gameEntity.isMiniGame()) {
MiniGameItemHelper.launchMiniGame(gameEntity.miniGameAppId, gameEntity.miniGameType)
MiniGameItemHelper.launchMiniGame(gameEntity)
} else {
GameDetailActivity.startGameDetailActivity(
mContext,

View File

@ -68,7 +68,7 @@ class GameHorizontalSlideAdapter(
holder.bindGameHorizontalItem(gameEntity, mSubjectEntity)
holder.itemView.setOnClickListener {
if (gameEntity.isMiniGame()) {
MiniGameItemHelper.launchMiniGame(gameEntity.miniGameAppId, gameEntity.miniGameType)
MiniGameItemHelper.launchMiniGame(gameEntity)
} else {
val exposureEvent = exposureEventList?.safelyGetInRelease(position)
if (exposureEvent != null) {

View File

@ -45,7 +45,9 @@ import com.zhihu.matisse.Matisse
import com.zhihu.matisse.internal.utils.PathUtils
import io.reactivex.disposables.Disposable
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import java.io.File
import kotlin.collections.component1
import kotlin.collections.component2
@ -420,10 +422,8 @@ class GameUploadFragment : ToolbarFragment() {
requestMap["type"] = mGameType
}
val body = RequestBody.create(
MediaType.parse("application/json"),
GsonUtils.toJson(requestMap)
)
val body = GsonUtils.toJson(requestMap)
.toRequestBody("application/json".toMediaTypeOrNull())
mViewModel.uploadGames(body)
}

View File

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

View File

@ -7,6 +7,9 @@ import android.os.Handler
import android.os.Looper
import android.os.Message
import android.view.animation.AccelerateDecelerateInterpolator
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import androidx.viewpager2.widget.ViewPager2
import com.gh.common.util.DirectUtils
@ -21,6 +24,17 @@ class GameCollectionAmwayViewHolder(var binding: GameCollectionSquareAmwayItemBi
private val mAdapter = GameCollectionAmwayAdapter(binding.root.context)
private val mLooperHandle = LooperHandle(this)
private val mSlideLooperKey = 333
private val mLifecycleObserver = object : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
startAutoPlay()
}
override fun onPause(owner: LifecycleOwner) {
super.onPause(owner)
stopAutoPlay()
}
}
fun bindAmway(amwayList: List<AmwayCommentEntity>, viewModel: GameCollectionSquareViewModel) {
mAdapter.setAmwayList(amwayList)
@ -54,6 +68,14 @@ class GameCollectionAmwayViewHolder(var binding: GameCollectionSquareAmwayItemBi
mLooperHandle.removeMessages(mSlideLooperKey)
}
fun onViewAttach(lifecycle: Lifecycle) {
lifecycle.addObserver(mLifecycleObserver)
}
fun onViewDetach(lifecycle: Lifecycle) {
lifecycle.removeObserver(mLifecycleObserver)
}
fun ViewPager2.setCurrentItem(
item: Int,
duration: Long,

View File

@ -2,6 +2,7 @@ package com.gh.gamecenter.gamecollection.square
import android.content.Context
import android.view.ViewGroup
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import com.gh.common.exposure.ExposureManager
import com.gh.common.util.DirectUtils
@ -23,6 +24,7 @@ import com.lightgame.adapter.BaseRecyclerAdapter
class GameCollectionBannerAdapter(
context: Context,
private val mLifecycleOwner: LifecycleOwner,
private val mViewModel: GameCollectionSquareViewModel,
var mBannerList: List<CarouselEntity> = emptyList(),
private var mAmwayListItem: List<AmwayCommentEntity>? = null,
@ -78,6 +80,20 @@ class GameCollectionBannerAdapter(
return index
}
override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) {
super.onViewAttachedToWindow(holder)
if (holder is GameCollectionAmwayViewHolder) {
holder.onViewAttach(mLifecycleOwner.lifecycle)
}
}
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
super.onViewDetachedFromWindow(holder)
if (holder is GameCollectionAmwayViewHolder) {
holder.onViewDetach(mLifecycleOwner.lifecycle)
}
}
companion object {
const val ITEM_AMWAY = 100
const val ITEM_BANNER = 101

View File

@ -12,6 +12,9 @@ import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.lifecycle.DefaultLifecycleObserver
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.PagerSnapHelper
import androidx.recyclerview.widget.RecyclerView
@ -47,6 +50,7 @@ import java.lang.ref.WeakReference
class GameCollectionSquareAdapter(
context: Context,
private val mLifecycleOwner: LifecycleOwner,
private val mIsHome: Boolean = false,
private val mFragment: GameCollectionSquareFragment,
private val mViewModel: GameCollectionSquareViewModel,
@ -157,7 +161,7 @@ class GameCollectionSquareAdapter(
when (holder) {
is GameCollectionHeaderItemViewHolder -> {
val itemData = mEntityList[position]
holder.bindHeader(itemData, mBasicExposureSource, mViewModel)
holder.bindHeader(mLifecycleOwner, itemData, mBasicExposureSource, mViewModel)
}
is GameCollectionFilterItemViewHolder -> holder.bindFilter(mFragment, mViewModel, mRefreshCallback)
@ -211,6 +215,20 @@ class GameCollectionSquareAdapter(
override fun getItemCount() =
if (mEntityList.isNullOrEmpty()) 0 else if (mIsHome && mEntityList.size == 2) mEntityList.size else mEntityList.size + 1
override fun onViewAttachedToWindow(holder: RecyclerView.ViewHolder) {
super.onViewAttachedToWindow(holder)
if (holder is GameCollectionHeaderItemViewHolder) {
holder.onViewAttach(mLifecycleOwner.lifecycle)
}
}
override fun onViewDetachedFromWindow(holder: RecyclerView.ViewHolder) {
super.onViewDetachedFromWindow(holder)
if (holder is GameCollectionHeaderItemViewHolder) {
holder.onViewDetach(mLifecycleOwner.lifecycle)
}
}
class GameCollectionHeaderItemViewHolder(val binding: ItemGameCollectionHeaderBinding) :
RecyclerView.ViewHolder(binding.root) {
private lateinit var mBannerAdapter: GameCollectionBannerAdapter
@ -218,8 +236,20 @@ class GameCollectionSquareAdapter(
private lateinit var mBannerLayoutManager: LinearLayoutManager
private val mLooperHandle = LooperHandle(this)
private val mSlideLooperKey = 111
private val mLifecycleObserver = object : DefaultLifecycleObserver {
override fun onResume(owner: LifecycleOwner) {
super.onResume(owner)
startAutoPlay()
}
override fun onPause(owner: LifecycleOwner) {
super.onPause(owner)
stopAutoPlay()
}
}
fun bindHeader(
lifecycleOwner: LifecycleOwner,
itemData: GameCollectionListItemData,
mBasicExposureSource: List<ExposureSource>,
viewModel: GameCollectionSquareViewModel
@ -249,6 +279,7 @@ class GameCollectionSquareAdapter(
mBannerLayoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
mBannerAdapter = GameCollectionBannerAdapter(
context,
lifecycleOwner,
viewModel,
bannerList,
amwayListItem,
@ -345,6 +376,14 @@ class GameCollectionSquareAdapter(
mLooperHandle.removeMessages(mSlideLooperKey)
}
fun onViewAttach(lifecycle: Lifecycle) {
lifecycle.addObserver(mLifecycleObserver)
}
fun onViewDetach(lifecycle: Lifecycle) {
lifecycle.removeObserver(mLifecycleObserver)
}
class LooperHandle(viewHolder: GameCollectionHeaderItemViewHolder) : Handler(Looper.getMainLooper()) {
private val mWeakReference: WeakReference<GameCollectionHeaderItemViewHolder> = WeakReference(viewHolder)

View File

@ -138,6 +138,7 @@ class GameCollectionSquareFragment : LazyListFragment<GamesCollectionEntity, Gam
mGuideContainer?.visibility = View.GONE
} else {
if (mExposureEventList.isNotEmpty()) ExposureManager.log(mExposureEventList)
stopAutoPlay()
}
val stayTime = (System.currentTimeMillis() - startPageTime) / 1000
NewFlatLogUtils.logGameCollectSquareStayTime(stayTime, if (mUseAlternativeLayout) "首页tab栏" else "游戏单广场")
@ -175,6 +176,8 @@ class GameCollectionSquareFragment : LazyListFragment<GamesCollectionEntity, Gam
)
)
}
} else {
startAutoPlay()
}
mElapsedHelper.resetCounting()
mElapsedHelper.resumeCounting()
@ -270,6 +273,7 @@ class GameCollectionSquareFragment : LazyListFragment<GamesCollectionEntity, Gam
mAdapter =
GameCollectionSquareAdapter(
requireContext(),
viewLifecycleOwner,
mUseAlternativeLayout,
this,
mViewModel,
@ -382,6 +386,7 @@ class GameCollectionSquareFragment : LazyListFragment<GamesCollectionEntity, Gam
val bannerAdapter = GameCollectionBannerAdapter(
requireContext(),
viewLifecycleOwner,
mViewModel,
mEntrance = "游戏单广场",
mBasicExposureSource = mBasicExposureSourceList
@ -515,7 +520,8 @@ class GameCollectionSquareFragment : LazyListFragment<GamesCollectionEntity, Gam
}
private fun startAutoPlay() {
if ((mDefaultBinding.headerContainer.bannerRv.adapter as GameCollectionBannerAdapter).getActualSize() <= 1) return
val bannerAdapter = mDefaultBinding.headerContainer.bannerRv.adapter as? GameCollectionBannerAdapter
if (bannerAdapter == null || bannerAdapter.getActualSize() <= 1) return
stopAutoPlay()
mLooperHandle.sendEmptyMessageDelayed(mSlideLooperKey, BANNER_LOOP_TIME)
}

View File

@ -45,7 +45,9 @@ import com.gh.gamecenter.login.user.UserManager
import com.halo.assistant.HaloApp
import com.lightgame.utils.Utils
import okhttp3.MediaType
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.RequestBody
import okhttp3.RequestBody.Companion.toRequestBody
import org.json.JSONObject
import retrofit2.HttpException
@ -541,7 +543,7 @@ class RatingEditActivity : ToolBarActivity(), KeyboardHeightObserver {
jsonObject.put("rom", MetaUtil.getRom().name + " " + MetaUtil.getRom().versionName)
jsonObject.put("again", again)
val body = RequestBody.create(MediaType.parse("application/json"), jsonObject.toString())
val body = jsonObject.toString().toRequestBody("application/json".toMediaTypeOrNull())
CheckLoginUtils.checkLogin(this, mEntrance) {
mViewModel.postGameComment(mComment?.id ?: "", body)

View File

@ -97,7 +97,7 @@ class HomeGameItemViewHolder(val binding: HomeGameItemBinding) : BaseRecyclerVie
}
holder.itemView.setOnClickListener {
if (game.isMiniGame()) {
MiniGameItemHelper.launchMiniGame(game.miniGameAppId, game.miniGameType)
MiniGameItemHelper.launchMiniGame(game)
} else {
GameDetailActivity.startGameDetailActivity(
binding.root.context,

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