Skip to content

Commit 85d7283

Browse files
committed
[新增] 评论数红点显示
[优化] 评论区预加载
1 parent a832953 commit 85d7283

File tree

11 files changed

+247
-12
lines changed

11 files changed

+247
-12
lines changed

app/src/main/java/com/yenaly/han1meviewer/logic/Parse.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -912,8 +912,10 @@ object Parse {
912912
funcName: String, varName: String, loginNeeded: Boolean = false,
913913
): T? = also {
914914
if (it == null) {
915-
if (loginNeeded && isAlreadyLogin) {
916-
Log.d("Parse::$funcName", "[$varName] is null. 而且處於登入狀態,這有點不正常")
915+
if (loginNeeded) {
916+
if (isAlreadyLogin) {
917+
Log.d("Parse::$funcName", "[$varName] is null. 而且處於登入狀態,這有點不正常")
918+
}
917919
} else {
918920
Log.d("Parse::$funcName", "[$varName] is null. 這有點不正常")
919921
}

app/src/main/java/com/yenaly/han1meviewer/ui/activity/PreviewActivity.kt

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,10 @@ import coil.imageLoader
3030
import coil.load
3131
import coil.request.ImageRequest
3232
import com.google.android.material.appbar.AppBarLayout
33+
import com.google.android.material.badge.BadgeDrawable
34+
import com.google.android.material.badge.BadgeUtils
3335
import com.yenaly.han1meviewer.DATE_CODE
36+
import com.yenaly.han1meviewer.PREVIEW_COMMENT_PREFIX
3437
import com.yenaly.han1meviewer.R
3538
import com.yenaly.han1meviewer.databinding.ActivityPreviewBinding
3639
import com.yenaly.han1meviewer.logic.state.WebsiteState
@@ -50,6 +53,7 @@ import com.yenaly.yenaly_libs.utils.unsafeLazy
5053
import com.yenaly.yenaly_libs.utils.view.AppBarLayoutStateChangeListener
5154
import com.yenaly.yenaly_libs.utils.view.innerRecyclerView
5255
import kotlinx.coroutines.delay
56+
import kotlinx.coroutines.flow.collectLatest
5357
import kotlinx.coroutines.launch
5458
import kotlinx.datetime.Clock
5559
import kotlinx.datetime.DateTimeUnit
@@ -76,6 +80,7 @@ class PreviewActivity : YenalyActivity<ActivityPreviewBinding, PreviewViewModel>
7680
}
7781

7882
private val dateUtils = DateUtils()
83+
private val badgeDrawable by unsafeLazy { BadgeDrawable.create(this@PreviewActivity) }
7984

8085
/**
8186
* 左右滑动 VP 时,不触发 onScrollStateChanged,防止触发两次 binding.vpNews.setCurrentItem
@@ -129,14 +134,27 @@ class PreviewActivity : YenalyActivity<ActivityPreviewBinding, PreviewViewModel>
129134
it.setHomeActionContentDescription(R.string.back)
130135
}
131136

137+
PreviewCommentPrefetcher.here().tag(PreviewCommentPrefetcher.Scope.PREVIEW_ACTIVITY)
138+
132139
// binding.newsTitle.findViewById<TextView>(R.id.title).setText(R.string.latest_hanime_news)
133140
// binding.newsTitle.findViewById<TextView>(R.id.sub_title).setText(R.string.introduction)
134141

142+
dateUtils.current.format(DateUtils.FORMATTED_FORMAT).let { format ->
143+
viewModel.getHanimePreview(format)
144+
loadComments(format)
145+
}
146+
135147
binding.fabPrevious.setOnClickListener {
136-
viewModel.getHanimePreview(dateUtils.toPrevDate().format(DateUtils.FORMATTED_FORMAT))
148+
dateUtils.toPrevDate().format(DateUtils.FORMATTED_FORMAT).let { format ->
149+
viewModel.getHanimePreview(format)
150+
loadComments(format)
151+
}
137152
}
138153
binding.fabNext.setOnClickListener {
139-
viewModel.getHanimePreview(dateUtils.toNextDate().format(DateUtils.FORMATTED_FORMAT))
154+
dateUtils.toNextDate().format(DateUtils.FORMATTED_FORMAT).let { format ->
155+
viewModel.getHanimePreview(format)
156+
loadComments(format)
157+
}
140158
}
141159

142160
binding.vpNews.adapter = newsAdapter
@@ -228,7 +246,6 @@ class PreviewActivity : YenalyActivity<ActivityPreviewBinding, PreviewViewModel>
228246

229247
is WebsiteState.Loading -> {
230248
//binding.srlPreview.autoRefresh()
231-
viewModel.getHanimePreview(dateUtils.current.format(DateUtils.FORMATTED_FORMAT))
232249
binding.fabPrevious.isEnabled = false
233250
binding.fabNext.isEnabled = false
234251
}
@@ -274,6 +291,22 @@ class PreviewActivity : YenalyActivity<ActivityPreviewBinding, PreviewViewModel>
274291
}
275292
}
276293
}
294+
295+
lifecycleScope.launch {
296+
PreviewCommentPrefetcher.here().commentFlow.collectLatest { comments ->
297+
badgeDrawable.apply {
298+
isVisible = comments.isNotEmpty()
299+
number = comments.size
300+
badgeGravity = BadgeDrawable.TOP_START
301+
}
302+
BadgeUtils.attachBadgeDrawable(badgeDrawable, binding.toolbar, R.id.tb_comment)
303+
}
304+
}
305+
}
306+
307+
override fun onDestroy() {
308+
super.onDestroy()
309+
PreviewCommentPrefetcher.bye(PreviewCommentPrefetcher.Scope.PREVIEW_ACTIVITY)
277310
}
278311

279312
override fun onCreateOptionsMenu(menu: Menu): Boolean {
@@ -298,6 +331,11 @@ class PreviewActivity : YenalyActivity<ActivityPreviewBinding, PreviewViewModel>
298331
return super.onOptionsItemSelected(item)
299332
}
300333

334+
private fun loadComments(currentFormat: String) {
335+
PreviewCommentPrefetcher.here()
336+
.fetch(PREVIEW_COMMENT_PREFIX, currentFormat)
337+
}
338+
301339
private fun handleToolbarColor(index: Int) {
302340
val data = tourSimplifiedAdapter.getItem(index)?.coverUrl
303341
val request = ImageRequest.Builder(this)

app/src/main/java/com/yenaly/han1meviewer/ui/activity/PreviewCommentActivity.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,20 @@ class PreviewCommentActivity : YenalyActivity<ActivityPreviewCommentBinding, Com
4343

4444
viewModel.code = dateCode
4545

46+
PreviewCommentPrefetcher.here().tag(PreviewCommentPrefetcher.Scope.PREVIEW_COMMENT_ACTIVITY)
47+
4648
val commentFragment =
4749
CommentFragment().makeBundle(COMMENT_TYPE to PREVIEW_COMMENT_PREFIX)
4850
supportFragmentManager.beginTransaction().apply {
4951
add(R.id.fcv_pre_comment, commentFragment)
5052
}.commit()
5153
}
5254

55+
override fun onDestroy() {
56+
super.onDestroy()
57+
PreviewCommentPrefetcher.bye(PreviewCommentPrefetcher.Scope.PREVIEW_COMMENT_ACTIVITY)
58+
}
59+
5360
override fun onOptionsItemSelected(item: MenuItem): Boolean {
5461
when (item.itemId) {
5562
android.R.id.home -> {
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package com.yenaly.han1meviewer.ui.activity
2+
3+
import android.util.Log
4+
import androidx.annotation.IntDef
5+
import com.yenaly.han1meviewer.logic.model.VideoComments
6+
import com.yenaly.han1meviewer.ui.viewmodel.CommentViewModel
7+
import com.yenaly.yenaly_libs.utils.application
8+
9+
/**
10+
* 连通 [PreviewActivity] 和 [PreviewCommentActivity] 的预取器
11+
*/
12+
class PreviewCommentPrefetcher {
13+
14+
@IntDef(flag = true, value = [Scope.PREVIEW_ACTIVITY, Scope.PREVIEW_COMMENT_ACTIVITY])
15+
annotation class Scope {
16+
companion object {
17+
const val PREVIEW_ACTIVITY = 1
18+
const val PREVIEW_COMMENT_ACTIVITY = 1 shl 1
19+
}
20+
}
21+
22+
companion object {
23+
private const val TAG = "PreviewCommentPrefetcher"
24+
25+
private var prefetcher: PreviewCommentPrefetcher? = null
26+
27+
fun here(): PreviewCommentPrefetcher {
28+
return prefetcher ?: PreviewCommentPrefetcher().also { prefetcher = it }
29+
}
30+
31+
fun bye(@Scope scope: Int) {
32+
prefetcher?.also {
33+
it.activityMask = it.activityMask and scope.inv()
34+
if (it.activityMask == 0) {
35+
prefetcher = null
36+
Log.i(TAG, "bye executed successfully")
37+
} else {
38+
if (it.activityMask and Scope.PREVIEW_ACTIVITY != 0) {
39+
Log.i(
40+
TAG, "bye executed failed: " +
41+
"prefetcher is still alive cuz of PreviewActivity"
42+
)
43+
}
44+
if (it.activityMask and Scope.PREVIEW_COMMENT_ACTIVITY != 0) {
45+
Log.i(
46+
TAG, "bye executed failed: " +
47+
"prefetcher is still alive cuz of PreviewCommentActivity"
48+
)
49+
}
50+
}
51+
}
52+
}
53+
}
54+
55+
private var activityMask = 0
56+
57+
private val commentViewModel = CommentViewModel(application)
58+
59+
val commentFlow get() = commentViewModel.videoCommentFlow
60+
61+
fun tag(@Scope scope: Int) {
62+
activityMask = activityMask or scope
63+
}
64+
65+
fun fetch(type: String, code: String) {
66+
commentViewModel.getComment(type, code)
67+
}
68+
69+
fun update(comments: List<VideoComments.VideoComment>) {
70+
commentViewModel.updateComments(comments)
71+
}
72+
}

app/src/main/java/com/yenaly/han1meviewer/ui/activity/VideoActivity.kt

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import android.content.Intent
44
import android.content.pm.ActivityInfo
55
import android.graphics.Color
66
import android.os.Bundle
7+
import android.view.Gravity
78
import android.view.ViewGroup.MarginLayoutParams
89
import androidx.activity.SystemBarStyle
910
import androidx.activity.addCallback
@@ -35,10 +36,12 @@ import com.yenaly.han1meviewer.ui.view.video.HMediaKernel
3536
import com.yenaly.han1meviewer.ui.view.video.HanimeDataSource
3637
import com.yenaly.han1meviewer.ui.viewmodel.CommentViewModel
3738
import com.yenaly.han1meviewer.ui.viewmodel.VideoViewModel
39+
import com.yenaly.han1meviewer.util.getOrCreateBadgeOnTextViewAt
3840
import com.yenaly.han1meviewer.util.showAlertDialog
3941
import com.yenaly.yenaly_libs.base.YenalyActivity
4042
import com.yenaly.yenaly_libs.utils.OrientationManager
4143
import com.yenaly.yenaly_libs.utils.browse
44+
import com.yenaly.yenaly_libs.utils.dp
4245
import com.yenaly.yenaly_libs.utils.intentExtra
4346
import com.yenaly.yenaly_libs.utils.makeBundle
4447
import com.yenaly.yenaly_libs.utils.showShortToast
@@ -220,6 +223,7 @@ class VideoActivity : YenalyActivity<ActivityVideoBinding, VideoViewModel>(),
220223
}
221224

222225
private fun initViewPager() {
226+
binding.videoVp.offscreenPageLimit = 1
223227

224228
binding.videoVp.setUpFragmentStateAdapter(this) {
225229
addFragment { VideoIntroductionFragment() }
@@ -265,4 +269,14 @@ class VideoActivity : YenalyActivity<ActivityVideoBinding, VideoViewModel>(),
265269
}
266270
}
267271
}
272+
273+
fun showRedDotCount(count: Int) {
274+
binding.videoTl.getOrCreateBadgeOnTextViewAt(
275+
tabNameArray.indexOf(R.string.comment),
276+
null, Gravity.START, 2.dp
277+
) {
278+
isVisible = count > 0
279+
number = count
280+
}
281+
}
268282
}

app/src/main/java/com/yenaly/han1meviewer/ui/fragment/video/ChildCommentPopupFragment.kt

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,24 @@ package com.yenaly.han1meviewer.ui.fragment.video
22

33
import android.app.Dialog
44
import android.os.Bundle
5+
import android.view.Gravity
56
import androidx.fragment.app.viewModels
67
import androidx.lifecycle.lifecycleScope
78
import androidx.recyclerview.widget.LinearLayoutManager
9+
import com.google.android.material.badge.BadgeDrawable
10+
import com.google.android.material.badge.BadgeUtils
811
import com.yenaly.han1meviewer.COMMENT_ID
912
import com.yenaly.han1meviewer.CSRF_TOKEN
1013
import com.yenaly.han1meviewer.R
1114
import com.yenaly.han1meviewer.databinding.PopUpFragmentChildCommentBinding
1215
import com.yenaly.han1meviewer.logic.state.WebsiteState
1316
import com.yenaly.han1meviewer.ui.adapter.VideoCommentRvAdapter
1417
import com.yenaly.han1meviewer.ui.viewmodel.CommentViewModel
18+
import com.yenaly.han1meviewer.util.setGravity
1519
import com.yenaly.yenaly_libs.base.YenalyBottomSheetDialogFragment
1620
import com.yenaly.yenaly_libs.utils.appScreenHeight
1721
import com.yenaly.yenaly_libs.utils.arguments
22+
import com.yenaly.yenaly_libs.utils.dp
1823
import com.yenaly.yenaly_libs.utils.showShortToast
1924
import com.yenaly.yenaly_libs.utils.unsafeLazy
2025
import kotlinx.coroutines.flow.collectLatest
@@ -63,11 +68,11 @@ class ChildCommentPopupFragment :
6368
lifecycleScope.launch {
6469
viewModel.videoReplyFlow.collectLatest { list ->
6570
replyAdapter.submitList(list)
71+
attachRedDotCount(list.size)
6672
}
6773
}
6874

6975
lifecycleScope.launch {
70-
7176
viewModel.postReplyFlow.collect { state ->
7277
when (state) {
7378
is WebsiteState.Error -> {
@@ -85,11 +90,9 @@ class ChildCommentPopupFragment :
8590
}
8691
}
8792
}
88-
8993
}
9094

9195
lifecycleScope.launch {
92-
9396
viewModel.commentLikeFlow.collect { state ->
9497
when (state) {
9598
is WebsiteState.Error -> showShortToast(state.throwable.message)
@@ -100,7 +103,14 @@ class ChildCommentPopupFragment :
100103
}
101104
}
102105
}
103-
104106
}
105107
}
108+
109+
private fun attachRedDotCount(count: Int) {
110+
val badgeDrawable = BadgeDrawable.create(requireContext())
111+
badgeDrawable.isVisible = count > 0
112+
badgeDrawable.number = count
113+
BadgeUtils.attachBadgeDrawable(badgeDrawable, binding.tvChildComment)
114+
badgeDrawable.setGravity(binding.tvChildComment, Gravity.END, 8.dp)
115+
}
106116
}

app/src/main/java/com/yenaly/han1meviewer/ui/fragment/video/CommentFragment.kt

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ import com.yenaly.han1meviewer.VIDEO_COMMENT_PREFIX
2121
import com.yenaly.han1meviewer.databinding.FragmentCommentBinding
2222
import com.yenaly.han1meviewer.logic.state.WebsiteState
2323
import com.yenaly.han1meviewer.ui.StateLayoutMixin
24+
import com.yenaly.han1meviewer.ui.activity.PreviewCommentActivity
25+
import com.yenaly.han1meviewer.ui.activity.PreviewCommentPrefetcher
26+
import com.yenaly.han1meviewer.ui.activity.VideoActivity
2427
import com.yenaly.han1meviewer.ui.adapter.VideoCommentRvAdapter
2528
import com.yenaly.han1meviewer.ui.popup.ReplyPopup
2629
import com.yenaly.han1meviewer.ui.viewmodel.CommentViewModel
@@ -47,6 +50,11 @@ class CommentFragment : YenalyFragment<FragmentCommentBinding, CommentViewModel>
4750
ReplyPopup(requireContext()).also { it.hint = getString(R.string.comment) }
4851
}
4952

53+
/**
54+
* 是否已经预加载了预览评论
55+
*/
56+
private var isPreviewCommentPrefetched = false
57+
5058
override fun initData(savedInstanceState: Bundle?) {
5159
binding.state.init {
5260
onEmpty {
@@ -57,6 +65,14 @@ class CommentFragment : YenalyFragment<FragmentCommentBinding, CommentViewModel>
5765
binding.rvComment.layoutManager = LinearLayoutManager(context)
5866
binding.rvComment.adapter = commentAdapter
5967
binding.rvComment.clipToPadding = false
68+
69+
if (context is PreviewCommentActivity) {
70+
val comments = PreviewCommentPrefetcher.here().commentFlow.value
71+
if (comments.isNotEmpty()) {
72+
isPreviewCommentPrefetched = true
73+
commentAdapter.submitList(comments)
74+
}
75+
}
6076
ViewCompat.setOnApplyWindowInsetsListener(binding.rvComment) { v, insets ->
6177
val navBar = insets.getInsets(WindowInsetsCompat.Type.navigationBars())
6278
v.updatePadding(bottom = navBar.bottom)
@@ -100,13 +116,16 @@ class CommentFragment : YenalyFragment<FragmentCommentBinding, CommentViewModel>
100116
}
101117

102118
is WebsiteState.Loading -> {
103-
binding.srlComment.autoRefresh()
119+
if (!isPreviewCommentPrefetched) {
120+
binding.srlComment.autoRefresh()
121+
}
104122
}
105123

106124
is WebsiteState.Success -> {
107125
binding.srlComment.finishRefresh()
108126
viewModel.csrfToken = state.info.csrfToken
109127
viewModel.currentUserId = state.info.currentUserId
128+
showRedDotCount(state.info.videoComment.size)
110129
binding.rvComment.isGone = state.info.videoComment.isEmpty()
111130
if (state.info.videoComment.isEmpty()) {
112131
binding.state.showEmpty()
@@ -122,7 +141,10 @@ class CommentFragment : YenalyFragment<FragmentCommentBinding, CommentViewModel>
122141
lifecycleScope.launch {
123142
repeatOnLifecycle(Lifecycle.State.CREATED) {
124143
viewModel.videoCommentFlow.collectLatest { list ->
125-
commentAdapter.submitList(list)
144+
if (!isPreviewCommentPrefetched) {
145+
commentAdapter.submitList(list)
146+
PreviewCommentPrefetcher.here().update(list)
147+
}
126148
}
127149
}
128150
}
@@ -179,4 +201,9 @@ class CommentFragment : YenalyFragment<FragmentCommentBinding, CommentViewModel>
179201
}
180202
}
181203
}
204+
205+
private fun showRedDotCount(count: Int) {
206+
val parent = context as? VideoActivity
207+
parent?.showRedDotCount(count)
208+
}
182209
}

0 commit comments

Comments
 (0)