From 8e4224bf3fe58826b661e72a1ea8756885a36f84 Mon Sep 17 00:00:00 2001 From: why Date: Sat, 23 Dec 2023 15:56:40 +0800 Subject: [PATCH] =?UTF-8?q?[Feat]:=20=E6=97=B6=E9=97=B4=E7=BA=BF=E5=90=90?= =?UTF-8?q?=E6=A7=BD=E8=AF=A6=E6=83=85=E5=92=8C=E5=9B=9E=E5=A4=8D=E5=8A=9F?= =?UTF-8?q?=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ui/timeline/page/TimelinePageAdapter.kt | 125 ++++++++++++-- .../timeline/page/TimelinePageDiffAdapter.kt | 119 ------------- .../ui/timeline/page/TimelinePageFragment.kt | 9 +- .../ui/timeline/page/TimelinePageViewModel.kt | 3 +- .../ui/timeline/page/TimelineReplyDialog.kt | 157 ++++++++++++++++++ .../page/binder/TimelineGridBinder.kt | 55 ------ .../page/binder/TimelineMediaBinder.kt | 54 ------ .../page/binder/TimelineTextBinder.kt | 47 ------ .../res/layout/fragment_timeline_dialog.xml | 45 +++++ .../layout/fragment_timeline_dialog_item.xml | 43 +++++ .../layout/fragment_timeline_page_text.xml | 18 ++ .../com/xiaoyv/common/api/BgmApiManager.kt | 2 +- .../com/xiaoyv/common/api/api/BgmWebApi.kt | 21 +++ .../api/parser/entity/TimelineEntity.kt | 3 + .../api/parser/entity/TimelineReplyEntity.kt | 21 +++ .../common/api/parser/impl/TimeParser.kt | 47 ++++-- .../common/config/annotation/TimelineType.kt | 2 +- lib-common/src/main/res/drawable/ic_reply.xml | 9 + 18 files changed, 480 insertions(+), 300 deletions(-) delete mode 100644 app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/TimelinePageDiffAdapter.kt create mode 100644 app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/TimelineReplyDialog.kt delete mode 100644 app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/binder/TimelineGridBinder.kt delete mode 100644 app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/binder/TimelineMediaBinder.kt delete mode 100644 app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/binder/TimelineTextBinder.kt create mode 100644 app/src/main/res/layout/fragment_timeline_dialog.xml create mode 100644 app/src/main/res/layout/fragment_timeline_dialog_item.xml create mode 100644 lib-common/src/main/java/com/xiaoyv/common/api/parser/entity/TimelineReplyEntity.kt create mode 100644 lib-common/src/main/res/drawable/ic_reply.xml diff --git a/app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/TimelinePageAdapter.kt b/app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/TimelinePageAdapter.kt index f63e25d4..8c0af23c 100644 --- a/app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/TimelinePageAdapter.kt +++ b/app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/TimelinePageAdapter.kt @@ -1,12 +1,21 @@ package com.xiaoyv.bangumi.ui.timeline.page -import com.chad.library.adapter.base.BaseMultiItemAdapter -import com.xiaoyv.bangumi.ui.timeline.page.binder.TimelineGridBinder -import com.xiaoyv.bangumi.ui.timeline.page.binder.TimelineMediaBinder -import com.xiaoyv.bangumi.ui.timeline.page.binder.TimelineTextBinder +import android.content.Context +import android.view.ViewGroup +import androidx.core.view.isInvisible +import androidx.core.view.isVisible +import androidx.recyclerview.widget.RecyclerView +import com.chad.library.adapter.base.BaseDifferAdapter +import com.xiaoyv.bangumi.databinding.FragmentTimelinePageGridBinding +import com.xiaoyv.bangumi.databinding.FragmentTimelinePageMediaBinding +import com.xiaoyv.bangumi.databinding.FragmentTimelinePageTextBinding import com.xiaoyv.common.api.parser.entity.TimelineEntity import com.xiaoyv.common.config.annotation.TimelineAdapterType +import com.xiaoyv.common.helper.callback.IdDiffItemCallback +import com.xiaoyv.common.kts.inflater +import com.xiaoyv.common.kts.loadImageAnimate import com.xiaoyv.common.widget.image.AnimeGridImageView +import com.xiaoyv.widget.binder.BaseQuickBindingHolder /** * Class: [TimelinePageAdapter] @@ -15,12 +24,106 @@ import com.xiaoyv.common.widget.image.AnimeGridImageView * @since 11/25/23 */ class TimelinePageAdapter( - onGridItemClickListener: ((AnimeGridImageView.Image) -> Unit)? = null -) : BaseMultiItemAdapter() { - init { - this.addItemType(TimelineAdapterType.TYPE_GRID, TimelineGridBinder(onGridItemClickListener)) - .addItemType(TimelineAdapterType.TYPE_TEXT, TimelineTextBinder()) - .addItemType(TimelineAdapterType.TYPE_MEDIA, TimelineMediaBinder()) - .onItemViewType { position, list -> list[position].adapterType } + private val onGridItemClickListener: ((AnimeGridImageView.Image) -> Unit)? = null, +) : BaseDifferAdapter(IdDiffItemCallback()) { + private val viewPool by lazy { RecyclerView.RecycledViewPool() } + + override fun onBindViewHolder( + holder: RecyclerView.ViewHolder, + position: Int, + item: TimelineEntity?, + ) { + item ?: return + when (val binding = (holder as BaseQuickBindingHolder<*>).binding) { + is FragmentTimelinePageGridBinding -> { + onBindGrid(binding, item, onGridItemClickListener) + } + + is FragmentTimelinePageTextBinding -> { + onBindText(binding, item) + } + + is FragmentTimelinePageMediaBinding -> { + onBindMedia(binding, item) + } + + else -> throw IllegalArgumentException("error item") + } + } + + override fun getItemViewType(position: Int, list: List): Int { + return list[position].adapterType + } + + override fun onCreateViewHolder( + context: Context, + parent: ViewGroup, + viewType: Int, + ): RecyclerView.ViewHolder { + return when (viewType) { + TimelineAdapterType.TYPE_GRID -> BaseQuickBindingHolder( + FragmentTimelinePageGridBinding.inflate(context.inflater, parent, false) + .apply { + imageGrid.setRecycledViewPool(viewPool) + } + ) + + TimelineAdapterType.TYPE_TEXT -> BaseQuickBindingHolder( + FragmentTimelinePageTextBinding.inflate(context.inflater, parent, false) + ) + + TimelineAdapterType.TYPE_MEDIA -> BaseQuickBindingHolder( + FragmentTimelinePageMediaBinding.inflate(context.inflater, parent, false) + ) + + else -> throw IllegalArgumentException("error type: $viewType") + } + } + + companion object { + + private fun onBindGrid( + binding: FragmentTimelinePageGridBinding, + item: TimelineEntity, + onGridItemClickListener: ((AnimeGridImageView.Image) -> Unit)?, + ) { + binding.ivAvatar.isInvisible = item.avatar.isBlank() + binding.ivAvatar.loadImageAnimate(item.avatar) + binding.tvAction.text = item.title + binding.tvContent.text = item.content + binding.tvContent.isVisible = item.content.isNotBlank() + binding.tvTime.text = item.time + + binding.imageGrid.images = item.gridCard + binding.imageGrid.onGridItemClickListener = onGridItemClickListener + } + + private fun onBindMedia(binding: FragmentTimelinePageMediaBinding, item: TimelineEntity) { + binding.ivAvatar.isInvisible = item.avatar.isBlank() + binding.ivAvatar.loadImageAnimate(item.avatar) + binding.tvAction.text = item.title + binding.tvContent.text = item.content + binding.tvContent.isVisible = item.content.isNotBlank() + binding.tvTime.text = item.time + + binding.ivMediaScore.rating = item.mediaCard.score / 2f + binding.ivMediaScore.isVisible = item.mediaCard.score != 0f + binding.tvMediaName.text = item.mediaCard.title + binding.tvMediaDesc.text = item.mediaCard.info + binding.ivMediaCover.loadImageAnimate(item.mediaCard.cover) + } + + internal fun onBindText(binding: FragmentTimelinePageTextBinding, item: TimelineEntity) { + binding.ivAvatar.isInvisible = item.avatar.isBlank() + binding.ivAvatar.loadImageAnimate(item.avatar) + binding.tvAction.text = item.title + binding.tvContent.text = item.content + binding.tvContent.isVisible = item.content.isNotBlank() + binding.tvTime.text = item.time + binding.tvReply.isVisible = item.commentAble + + binding.tvReply.text = if (item.commentCount == 0) "回复" + else String.format("回复:%d", item.commentCount) + } } } \ No newline at end of file diff --git a/app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/TimelinePageDiffAdapter.kt b/app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/TimelinePageDiffAdapter.kt deleted file mode 100644 index 9bf48a44..00000000 --- a/app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/TimelinePageDiffAdapter.kt +++ /dev/null @@ -1,119 +0,0 @@ -package com.xiaoyv.bangumi.ui.timeline.page - -import android.content.Context -import android.view.ViewGroup -import androidx.core.view.isInvisible -import androidx.core.view.isVisible -import androidx.recyclerview.widget.RecyclerView -import com.chad.library.adapter.base.BaseDifferAdapter -import com.xiaoyv.bangumi.databinding.FragmentTimelinePageGridBinding -import com.xiaoyv.bangumi.databinding.FragmentTimelinePageMediaBinding -import com.xiaoyv.bangumi.databinding.FragmentTimelinePageTextBinding -import com.xiaoyv.common.api.parser.entity.TimelineEntity -import com.xiaoyv.common.config.annotation.TimelineAdapterType -import com.xiaoyv.common.helper.callback.IdDiffItemCallback -import com.xiaoyv.common.kts.inflater -import com.xiaoyv.common.kts.loadImageAnimate -import com.xiaoyv.common.widget.image.AnimeGridImageView -import com.xiaoyv.widget.binder.BaseQuickBindingHolder - -/** - * Class: [TimelinePageDiffAdapter] - * - * @author why - * @since 11/25/23 - */ -class TimelinePageDiffAdapter( - private val onGridItemClickListener: ((AnimeGridImageView.Image) -> Unit)? = null, -) : BaseDifferAdapter(IdDiffItemCallback()) { - private val viewPool by lazy { RecyclerView.RecycledViewPool() } - - override fun onBindViewHolder( - holder: RecyclerView.ViewHolder, - position: Int, - item: TimelineEntity?, - ) { - item ?: return - when (val binding = (holder as BaseQuickBindingHolder<*>).binding) { - is FragmentTimelinePageGridBinding -> { - onBindGrid(binding, item) - } - - is FragmentTimelinePageTextBinding -> { - onBindText(binding, item) - } - - is FragmentTimelinePageMediaBinding -> { - onBindMedia(binding, item) - } - - else -> throw IllegalArgumentException("error item") - } - } - - private fun onBindGrid(binding: FragmentTimelinePageGridBinding, item: TimelineEntity) { - binding.ivAvatar.isInvisible = item.avatar.isBlank() - binding.ivAvatar.loadImageAnimate(item.avatar) - binding.tvAction.text = item.title - binding.tvContent.text = item.content - binding.tvContent.isVisible = item.content.isNotBlank() - binding.tvTime.text = item.time - - binding.imageGrid.images = item.gridCard - binding.imageGrid.onGridItemClickListener = onGridItemClickListener - } - - private fun onBindMedia(binding: FragmentTimelinePageMediaBinding, item: TimelineEntity) { - binding.ivAvatar.isInvisible = item.avatar.isBlank() - binding.ivAvatar.loadImageAnimate(item.avatar) - binding.tvAction.text = item.title - binding.tvContent.text = item.content - binding.tvContent.isVisible = item.content.isNotBlank() - binding.tvTime.text = item.time - - binding.ivMediaScore.rating = item.mediaCard.score / 2f - binding.ivMediaScore.isVisible = item.mediaCard.score != 0f - binding.tvMediaName.text = item.mediaCard.title - binding.tvMediaDesc.text = item.mediaCard.info - binding.ivMediaCover.loadImageAnimate(item.mediaCard.cover) - } - - private fun onBindText(binding: FragmentTimelinePageTextBinding, item: TimelineEntity) { - binding.ivAvatar.isInvisible = item.avatar.isBlank() - binding.ivAvatar.loadImageAnimate(item.avatar) - binding.tvAction.text = item.title - binding.tvContent.text = item.content - binding.tvContent.isVisible = item.content.isNotBlank() - binding.tvTime.text = item.time - } - - - override fun getItemViewType(position: Int, list: List): Int { - return list[position].adapterType - } - - override fun onCreateViewHolder( - context: Context, - parent: ViewGroup, - viewType: Int, - ): RecyclerView.ViewHolder { - return when (viewType) { - TimelineAdapterType.TYPE_GRID -> BaseQuickBindingHolder( - FragmentTimelinePageGridBinding.inflate(context.inflater, parent, false) - .apply { - imageGrid.setRecycledViewPool(viewPool) - } - ) - - TimelineAdapterType.TYPE_TEXT -> BaseQuickBindingHolder( - FragmentTimelinePageTextBinding.inflate(context.inflater, parent, false) - ) - - TimelineAdapterType.TYPE_MEDIA -> BaseQuickBindingHolder( - FragmentTimelinePageMediaBinding.inflate(context.inflater, parent, false) - ) - - else -> throw IllegalArgumentException("error type: $viewType") - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/TimelinePageFragment.kt b/app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/TimelinePageFragment.kt index 0b1b752c..30299f4b 100644 --- a/app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/TimelinePageFragment.kt +++ b/app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/TimelinePageFragment.kt @@ -34,7 +34,7 @@ class TimelinePageFragment : BaseListFragment { - return TimelinePageDiffAdapter { + return TimelinePageAdapter { if (it is TimelineEntity.GridTimeline && it.id.isNotBlank()) { when (it.pathType) { // 跳转媒体详情 @@ -77,6 +77,13 @@ class TimelinePageFragment : BaseListFragment { + // 是否为有回复的吐槽时间线 + if (it.commentAble) { + TimelineReplyDialog.show(childFragmentManager, it) + return@setOnDebouncedChildClickListener + } + + // 跳转 Title Id 对应的页面 if (it.titleId.isNotBlank()) when (it.titleType) { // 跳转用户详情 BgmPathType.TYPE_USER -> { diff --git a/app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/TimelinePageViewModel.kt b/app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/TimelinePageViewModel.kt index ebac11fc..76f2d432 100644 --- a/app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/TimelinePageViewModel.kt +++ b/app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/TimelinePageViewModel.kt @@ -63,7 +63,8 @@ class TimelinePageViewModel : BaseListViewModel() { require(UserHelper.isLogin) { "你还没有登录呢" } BgmApiManager.bgmWebApi.queryFriendTimeline( type = timelineType, - page = current + page = current, + ajax = 0 ).parserTimelineForms() } // 自己的时间线 diff --git a/app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/TimelineReplyDialog.kt b/app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/TimelineReplyDialog.kt new file mode 100644 index 00000000..2ce9f4db --- /dev/null +++ b/app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/TimelineReplyDialog.kt @@ -0,0 +1,157 @@ +package com.xiaoyv.bangumi.ui.timeline.page + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.os.bundleOf +import androidx.core.text.method.LinkMovementMethodCompat +import androidx.fragment.app.FragmentManager +import com.blankj.utilcode.util.SnackbarUtils +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.xiaoyv.bangumi.R +import com.xiaoyv.bangumi.databinding.FragmentTimelineDialogBinding +import com.xiaoyv.bangumi.databinding.FragmentTimelineDialogItemBinding +import com.xiaoyv.blueprint.constant.NavKey +import com.xiaoyv.blueprint.kts.launchUI +import com.xiaoyv.common.api.BgmApiManager +import com.xiaoyv.common.api.parser.entity.TimelineEntity +import com.xiaoyv.common.api.parser.entity.TimelineReplyEntity +import com.xiaoyv.common.api.parser.impl.parserTimelineSayReply +import com.xiaoyv.common.helper.UserHelper +import com.xiaoyv.common.helper.callback.IdDiffItemCallback +import com.xiaoyv.common.kts.hideSnackBar +import com.xiaoyv.common.kts.setOnDebouncedChildClickListener +import com.xiaoyv.common.kts.showInputDialog +import com.xiaoyv.common.kts.showSnackBar +import com.xiaoyv.widget.binder.BaseQuickBindingHolder +import com.xiaoyv.widget.binder.BaseQuickDiffBindingAdapter +import com.xiaoyv.widget.callback.setOnFastLimitClickListener +import com.xiaoyv.widget.kts.errorMsg +import com.xiaoyv.widget.kts.getParcelObj +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext + +/** + * Class: [TimelineReplyDialog] + * + * @author why + * @since 12/23/23 + */ +class TimelineReplyDialog : BottomSheetDialogFragment() { + private val itemAdapter by lazy { ItemAdapter() } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle?, + ) = FragmentTimelineDialogBinding.inflate(inflater, container, false).root + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + val entity = arguments?.getParcelObj(NavKey.KEY_PARCELABLE) ?: return + initView(FragmentTimelineDialogBinding.bind(view), entity) + } + + private fun initView(binding: FragmentTimelineDialogBinding, entity: TimelineEntity) { + TimelinePageAdapter.onBindText(binding.layoutTimeline, entity) + binding.rvContent.adapter = itemAdapter + binding.sectionComment.title = "回复" + binding.sectionComment.more = null + binding.stateView.showLoading() + + queryTimelineReply(binding, entity.commentUserId, entity.id) + + binding.layoutTimeline.root.setOnFastLimitClickListener { + showSubReply(binding, entity.id, entity.commentUserId, "") + } + + itemAdapter.setOnDebouncedChildClickListener(R.id.iv_comment) { + showSubReply(binding, entity.id, entity.commentUserId, it.id) + } + } + + /** + * 回复吐槽 + */ + private fun showSubReply( + binding: FragmentTimelineDialogBinding, + timelineId: String, + userId: String, + atUserId: String, + ) { + requireActivity().showInputDialog( + title = "吐槽回复", + inputHint = "吐槽回复", + default = if (atUserId.isNotBlank()) "@$atUserId " else "", + onInput = { content -> + launchUI( + error = { + it.printStackTrace() + showSnackBar(it.errorMsg, SnackbarUtils.LENGTH_SHORT) + }, + block = { + showSnackBar("正在回复中...") + withContext(Dispatchers.IO) { + BgmApiManager.bgmWebApi.postTimelineReply( + tmlId = timelineId, + content = content, + formHash = UserHelper.formHash + ) + } + + hideSnackBar() + + // 刷新 + queryTimelineReply(binding, userId, timelineId) + } + ) + } + ) + } + + /** + * 查询时间线回复 + */ + private fun queryTimelineReply( + binding: FragmentTimelineDialogBinding, + userId: String, + timelineId: String, + ) { + launchUI( + error = { + it.printStackTrace() + + binding.stateView.showTip(message = it.errorMsg) + }, + block = { + binding.stateView.showLoading() + + val list = withContext(Dispatchers.IO) { + BgmApiManager.bgmWebApi.queryTimelineReply(userId, timelineId) + .parserTimelineSayReply() + } + require(list.isNotEmpty()) { "暂时没有回复" } + + itemAdapter.submitList(list) + + binding.stateView.showContent() + } + ) + } + + private class ItemAdapter : BaseQuickDiffBindingAdapter(IdDiffItemCallback()) { + override fun BaseQuickBindingHolder.converted(item: TimelineReplyEntity) { + binding.tvContent.text = item.content + binding.tvContent.movementMethod = LinkMovementMethodCompat.getInstance() + } + } + + companion object { + fun show(childFragmentManager: FragmentManager, entity: TimelineEntity) { + TimelineReplyDialog().apply { + arguments = bundleOf(NavKey.KEY_PARCELABLE to entity) + }.show(childFragmentManager, "TimelineReplyDialog") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/binder/TimelineGridBinder.kt b/app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/binder/TimelineGridBinder.kt deleted file mode 100644 index f8eff3bc..00000000 --- a/app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/binder/TimelineGridBinder.kt +++ /dev/null @@ -1,55 +0,0 @@ -package com.xiaoyv.bangumi.ui.timeline.page.binder - -import android.content.Context -import android.view.ViewGroup -import androidx.core.view.isInvisible -import androidx.core.view.isVisible -import androidx.recyclerview.widget.RecyclerView.RecycledViewPool -import com.chad.library.adapter.base.BaseMultiItemAdapter -import com.xiaoyv.bangumi.databinding.FragmentTimelinePageGridBinding -import com.xiaoyv.common.api.parser.entity.TimelineEntity -import com.xiaoyv.common.kts.inflater -import com.xiaoyv.common.kts.loadImageAnimate -import com.xiaoyv.common.widget.image.AnimeGridImageView -import com.xiaoyv.widget.binder.BaseQuickBindingHolder - -/** - * Class: [TimelineGridBinder] - * - * @author why - * @since 12/12/23 - */ -class TimelineGridBinder( - private var onGridItemClickListener: ((AnimeGridImageView.Image) -> Unit)? = null -) : BaseMultiItemAdapter.OnMultiItemAdapterListener> { - - private val viewPool by lazy { RecycledViewPool() } - - override fun onBind( - holder: BaseQuickBindingHolder, - position: Int, - item: TimelineEntity? - ) { - val entity = item ?: return - - holder.binding.ivAvatar.isInvisible = entity.avatar.isBlank() - holder.binding.ivAvatar.loadImageAnimate(entity.avatar) - holder.binding.tvAction.text = entity.title - holder.binding.tvContent.text = entity.content - holder.binding.tvContent.isVisible = entity.content.isNotBlank() - holder.binding.tvTime.text = entity.time - - holder.binding.imageGrid.images = entity.gridCard - holder.binding.imageGrid.onGridItemClickListener = onGridItemClickListener - } - - override fun onCreate( - context: Context, - parent: ViewGroup, - viewType: Int - ): BaseQuickBindingHolder { - val binding = FragmentTimelinePageGridBinding.inflate(context.inflater, parent, false) - binding.imageGrid.setRecycledViewPool(viewPool) - return BaseQuickBindingHolder(binding) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/binder/TimelineMediaBinder.kt b/app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/binder/TimelineMediaBinder.kt deleted file mode 100644 index e4624643..00000000 --- a/app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/binder/TimelineMediaBinder.kt +++ /dev/null @@ -1,54 +0,0 @@ -package com.xiaoyv.bangumi.ui.timeline.page.binder - -import android.content.Context -import android.view.ViewGroup -import androidx.core.view.isInvisible -import androidx.core.view.isVisible -import com.chad.library.adapter.base.BaseMultiItemAdapter -import com.xiaoyv.bangumi.databinding.FragmentTimelinePageMediaBinding -import com.xiaoyv.common.api.parser.entity.TimelineEntity -import com.xiaoyv.common.kts.inflater -import com.xiaoyv.common.kts.loadImageAnimate -import com.xiaoyv.widget.binder.BaseQuickBindingHolder - -/** - * Class: [TimelineMediaBinder] - * - * @author why - * @since 12/12/23 - */ -class TimelineMediaBinder : - BaseMultiItemAdapter.OnMultiItemAdapterListener> { - - override fun onBind( - holder: BaseQuickBindingHolder, - position: Int, - item: TimelineEntity? - ) { - val entity = item ?: return - - holder.binding.ivAvatar.isInvisible = entity.avatar.isBlank() - holder.binding.ivAvatar.loadImageAnimate(entity.avatar) - holder.binding.tvAction.text = entity.title - holder.binding.tvContent.text = entity.content - holder.binding.tvContent.isVisible = entity.content.isNotBlank() - holder.binding.tvTime.text = entity.time - - holder.binding.ivMediaScore.rating = entity.mediaCard.score / 2f - holder.binding.ivMediaScore.isVisible = entity.mediaCard.score != 0f - holder.binding.tvMediaName.text = entity.mediaCard.title - holder.binding.tvMediaDesc.text = entity.mediaCard.info - holder.binding.ivMediaCover.loadImageAnimate(entity.mediaCard.cover) - } - - override fun onCreate( - context: Context, - parent: ViewGroup, - viewType: Int - ): BaseQuickBindingHolder { - return BaseQuickBindingHolder( - FragmentTimelinePageMediaBinding.inflate(context.inflater, parent, false) - ) - } -} \ No newline at end of file diff --git a/app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/binder/TimelineTextBinder.kt b/app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/binder/TimelineTextBinder.kt deleted file mode 100644 index 56c1c65a..00000000 --- a/app/src/main/java/com/xiaoyv/bangumi/ui/timeline/page/binder/TimelineTextBinder.kt +++ /dev/null @@ -1,47 +0,0 @@ -package com.xiaoyv.bangumi.ui.timeline.page.binder - -import android.content.Context -import android.view.ViewGroup -import androidx.core.view.isInvisible -import androidx.core.view.isVisible -import com.chad.library.adapter.base.BaseMultiItemAdapter -import com.xiaoyv.bangumi.databinding.FragmentTimelinePageTextBinding -import com.xiaoyv.common.api.parser.entity.TimelineEntity -import com.xiaoyv.common.kts.inflater -import com.xiaoyv.common.kts.loadImageAnimate -import com.xiaoyv.widget.binder.BaseQuickBindingHolder - -/** - * Class: [TimelineTextBinder] - * - * @author why - * @since 12/12/23 - */ -class TimelineTextBinder : BaseMultiItemAdapter.OnMultiItemAdapterListener> { - - override fun onBind( - holder: BaseQuickBindingHolder, - position: Int, - item: TimelineEntity? - ) { - val entity = item ?: return - - holder.binding.ivAvatar.isInvisible = entity.avatar.isBlank() - holder.binding.ivAvatar.loadImageAnimate(entity.avatar) - holder.binding.tvAction.text = entity.title - holder.binding.tvContent.text = entity.content - holder.binding.tvContent.isVisible = entity.content.isNotBlank() - holder.binding.tvTime.text = entity.time - } - - override fun onCreate( - context: Context, - parent: ViewGroup, - viewType: Int - ): BaseQuickBindingHolder { - return BaseQuickBindingHolder( - FragmentTimelinePageTextBinding.inflate(context.inflater, parent, false) - ) - } -} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_timeline_dialog.xml b/app/src/main/res/layout/fragment_timeline_dialog.xml new file mode 100644 index 00000000..ca201107 --- /dev/null +++ b/app/src/main/res/layout/fragment_timeline_dialog.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_timeline_dialog_item.xml b/app/src/main/res/layout/fragment_timeline_dialog_item.xml new file mode 100644 index 00000000..8a392380 --- /dev/null +++ b/app/src/main/res/layout/fragment_timeline_dialog_item.xml @@ -0,0 +1,43 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_timeline_page_text.xml b/app/src/main/res/layout/fragment_timeline_page_text.xml index 35cc014f..99142fad 100644 --- a/app/src/main/res/layout/fragment_timeline_page_text.xml +++ b/app/src/main/res/layout/fragment_timeline_page_text.xml @@ -61,6 +61,24 @@ app:layout_constraintTop_toBottomOf="@+id/tv_action" tools:text="@tools:sample/lorem/random" /> + + = emptyList(), diff --git a/lib-common/src/main/java/com/xiaoyv/common/api/parser/entity/TimelineReplyEntity.kt b/lib-common/src/main/java/com/xiaoyv/common/api/parser/entity/TimelineReplyEntity.kt new file mode 100644 index 00000000..b4cc577f --- /dev/null +++ b/lib-common/src/main/java/com/xiaoyv/common/api/parser/entity/TimelineReplyEntity.kt @@ -0,0 +1,21 @@ +package com.xiaoyv.common.api.parser.entity + +import android.os.Parcelable +import androidx.annotation.Keep +import com.xiaoyv.common.helper.callback.IdEntity +import kotlinx.parcelize.Parcelize + +/** + * Class: [TimelineReplyEntity] + * + * @author why + * @since 12/23/23 + */ +@Parcelize +@Keep +data class TimelineReplyEntity( + override var id: String = "", + var userName: String = "", + var content: CharSequence = "", + var contentHtml: String = "", +) : Parcelable, IdEntity \ No newline at end of file diff --git a/lib-common/src/main/java/com/xiaoyv/common/api/parser/impl/TimeParser.kt b/lib-common/src/main/java/com/xiaoyv/common/api/parser/impl/TimeParser.kt index 713d6c9d..7fa521fb 100644 --- a/lib-common/src/main/java/com/xiaoyv/common/api/parser/impl/TimeParser.kt +++ b/lib-common/src/main/java/com/xiaoyv/common/api/parser/impl/TimeParser.kt @@ -3,6 +3,7 @@ package com.xiaoyv.common.api.parser.impl import com.xiaoyv.common.api.parser.entity.TimelineEntity +import com.xiaoyv.common.api.parser.entity.TimelineReplyEntity import com.xiaoyv.common.api.parser.hrefId import com.xiaoyv.common.api.parser.optImageUrl import com.xiaoyv.common.api.parser.parseCount @@ -148,10 +149,6 @@ fun parserTimelineMedia(item: Element, entity: TimelineEntity) { * 解析文本类型的时间线 */ fun parserTimelineText(item: Element, entity: TimelineEntity) { - val (time, platform) = item.select(".post_actions").fetchTimeAndPlatform() - entity.time = time - entity.platform = platform - val infoA = item.select(".info > a.l") val infoFullA = item.select(".info_full > a.l") val (titleId, titleType, titleLink) = @@ -168,19 +165,31 @@ fun parserTimelineText(item: Element, entity: TimelineEntity) { entity.title = item.select(".info, .info_full").fetchHtmlTitle() entity.content = item.select("p.status, .info_sub").text() entity.deleteId = item.select(".tml_del").hrefId() + + // 解析 Action + item.select(".post_actions").fetchTimeAndPlatform(entity) } /** * 解析时间和平台解析 */ -private fun Elements.fetchTimeAndPlatform(): Pair { - val timeInfo = select(".post_actions") - .apply { select("a").remove() } - .text().trim().trim('·').trim() - .split("·") +private fun Elements.fetchTimeAndPlatform(entity: TimelineEntity) { + val actions = select(".post_actions") + val commentA = select("a").remove() + val timeInfo = actions.text().split("·").filter { it.isNotBlank() } val time = timeInfo.getOrNull(0).orEmpty().trim() val platform = timeInfo.getOrNull(1).orEmpty().trim() - return time to platform + + entity.time = time + entity.platform = platform + entity.commentAble = commentA.isNotEmpty() + entity.commentCount = commentA.text().parseCount() + entity.commentUserId = "user/(.*?)/timeline".toRegex().find(commentA.attr("href")) + ?.groupValues?.getOrNull(1).orEmpty() + + if (entity.userId.isBlank()) { + entity.userId = entity.commentUserId + } } /** @@ -226,3 +235,21 @@ private fun Element?.fetchLinkIdAndType(): Triple { } return Triple(titleId, titleType, titleLink) } + +/** + * 解析时间线回复 + */ +fun Element.parserTimelineSayReply(): List { + return select(".subReply > li[data-item-user]").map { item -> + item.select(".cmt_reply").remove() + val html = item.html() + val userName = item.select("a").firstOrNull()?.text().orEmpty() + + TimelineReplyEntity( + id = item.attr("data-item-user"), + userName = userName, + contentHtml = html, + content = html.parseHtml(true), + ) + } +} diff --git a/lib-common/src/main/java/com/xiaoyv/common/config/annotation/TimelineType.kt b/lib-common/src/main/java/com/xiaoyv/common/config/annotation/TimelineType.kt index 6d4295e1..fcb196b2 100644 --- a/lib-common/src/main/java/com/xiaoyv/common/config/annotation/TimelineType.kt +++ b/lib-common/src/main/java/com/xiaoyv/common/config/annotation/TimelineType.kt @@ -23,7 +23,7 @@ import androidx.annotation.StringDef @Retention(AnnotationRetention.SOURCE) annotation class TimelineType { companion object { - const val TYPE_ALL = "" + const val TYPE_ALL = "all" const val TYPE_SAY = "say" const val TYPE_SUBJECT = "subject" const val TYPE_PROGRESS = "process" diff --git a/lib-common/src/main/res/drawable/ic_reply.xml b/lib-common/src/main/res/drawable/ic_reply.xml new file mode 100644 index 00000000..c703dfa6 --- /dev/null +++ b/lib-common/src/main/res/drawable/ic_reply.xml @@ -0,0 +1,9 @@ + + +