From ca28e4ae66d37fffa449c97a8fdbe1a43b34deca Mon Sep 17 00:00:00 2001 From: why Date: Wed, 6 Dec 2023 02:55:56 +0800 Subject: [PATCH] =?UTF-8?q?[Feat]:=20=E4=BA=BA=E7=89=A9=E8=A7=92=E8=89=B2?= =?UTF-8?q?=E3=80=81=E4=BD=9C=E5=93=81Tab?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bangumi/base/BaseListStubFragment.kt | 101 ++++++++++++++++ .../character/PersonCharacterAdapter.kt | 30 ++--- .../character/PersonCharacterFragment.kt | 41 ++++++- .../character/PersonCharacterViewModel.kt | 15 ++- .../person/collect/PersonCollectFragment.kt | 2 +- .../person/collect/PersonCollectViewModel.kt | 6 +- .../cooperate/PersonCooperateAdapter.kt | 2 +- .../cooperate/PersonCooperateFragment.kt | 4 +- .../feature/person/opus/PersonOpusAdapter.kt | 26 ++-- .../feature/person/opus/PersonOpusFragment.kt | 40 ++++++- .../person/opus/PersonOpusViewModel.kt | 15 ++- .../person/overview/PersonOverviewFragment.kt | 4 +- .../binder/OverviewCharacterBinder.kt | 4 +- .../ui/media/type/MediaPageFragment.kt | 6 +- app/src/main/res/layout/activity_person.xml | 2 +- app/src/main/res/layout/fragment_list.xml | 1 - .../main/res/layout/fragment_list_stub.xml | 15 +++ .../res/layout/fragment_media_page_item.xml | 111 ++++++++---------- .../res/layout/fragment_person_character.xml | 18 --- .../layout/fragment_person_collect_item.xml | 15 ++- .../res/layout/fragment_person_cooperate.xml | 6 - .../layout/fragment_person_cooperate_item.xml | 18 ++- .../main/res/layout/fragment_person_opus.xml | 6 - .../res/layout/fragment_person_overview.xml | 4 + .../com/xiaoyv/common/api/api/BgmWebApi.kt | 9 ++ .../api/parser/entity/CharacterEntity.kt | 21 ++++ .../api/parser/entity/MediaDetailEntity.kt | 3 +- .../common/api/parser/entity/PersonEntity.kt | 19 ++- .../api/parser/impl/MediaDetailParser.kt | 2 +- .../common/api/parser/impl/PersonParser.kt | 100 ++++++++++++---- .../common/widget/menu/SlideMenuView.kt | 100 ++++++++++++++++ .../res/drawable/shape_bottom_edge_corner.xml | 6 +- .../src/main/res/layout/view_menu_slide.xml | 14 +++ 33 files changed, 578 insertions(+), 188 deletions(-) create mode 100644 app/src/main/java/com/xiaoyv/bangumi/base/BaseListStubFragment.kt create mode 100644 app/src/main/res/layout/fragment_list_stub.xml delete mode 100644 app/src/main/res/layout/fragment_person_character.xml delete mode 100644 app/src/main/res/layout/fragment_person_cooperate.xml delete mode 100644 app/src/main/res/layout/fragment_person_opus.xml create mode 100644 lib-common/src/main/java/com/xiaoyv/common/api/parser/entity/CharacterEntity.kt create mode 100644 lib-common/src/main/java/com/xiaoyv/common/widget/menu/SlideMenuView.kt create mode 100644 lib-common/src/main/res/layout/view_menu_slide.xml diff --git a/app/src/main/java/com/xiaoyv/bangumi/base/BaseListStubFragment.kt b/app/src/main/java/com/xiaoyv/bangumi/base/BaseListStubFragment.kt new file mode 100644 index 00000000..ddd0fbbb --- /dev/null +++ b/app/src/main/java/com/xiaoyv/bangumi/base/BaseListStubFragment.kt @@ -0,0 +1,101 @@ +package com.xiaoyv.bangumi.base + +import androidx.annotation.CallSuper +import androidx.lifecycle.LifecycleOwner +import androidx.recyclerview.widget.LinearLayoutManager +import com.chad.library.adapter.base.BaseDifferAdapter +import com.chad.library.adapter.base.QuickAdapterHelper +import com.chad.library.adapter.base.loadState.trailing.TrailingLoadStateAdapter +import com.xiaoyv.bangumi.databinding.FragmentListBinding +import com.xiaoyv.blueprint.base.mvvm.normal.BaseViewModelFragment +import com.xiaoyv.blueprint.kts.toJson +import com.xiaoyv.common.kts.GoogleAttr +import com.xiaoyv.common.kts.debugLog +import com.xiaoyv.common.widget.scroll.AnimeLinearLayoutManager +import com.xiaoyv.widget.binder.BaseQuickDiffBindingAdapter +import com.xiaoyv.widget.kts.getAttrColor + +/** + * Class: [BaseListStubFragment] + * + * @author why + * @since 11/29/23 + */ +abstract class BaseListStubFragment> : + BaseViewModelFragment() { + + abstract val isOnlyOnePage: Boolean + + internal val contentAdapter: BaseDifferAdapter by lazy { + onCreateContentAdapter() + } + + private val adapterHelper by lazy { + QuickAdapterHelper.Builder(contentAdapter) + .setTrailingLoadStateAdapter(object : TrailingLoadStateAdapter.OnTrailingListener { + override fun isAllowLoading(): Boolean { + return binding.srlRefresh.isRefreshing.not() && isOnlyOnePage.not() + } + + override fun onFailRetry() { + viewModel.loadMore() + } + + override fun onLoad() { + viewModel.loadMore() + } + }) + .build() + } + + internal open val hasFixedSize = false + + internal open val layoutManager: LinearLayoutManager? + get() = binding.rvContent.layoutManager as? LinearLayoutManager + + abstract fun onCreateContentAdapter(): BaseQuickDiffBindingAdapter + + @CallSuper + override fun initView() { + binding.rvContent.setHasFixedSize(hasFixedSize) + binding.srlRefresh.initRefresh { viewModel.isRefresh } + binding.srlRefresh.setColorSchemeColors(hostActivity.getAttrColor(GoogleAttr.colorPrimary)) + } + + @CallSuper + override fun initData() { + binding.rvContent.layoutManager = onCreateLayoutManager() + if (isOnlyOnePage) { + binding.rvContent.adapter = contentAdapter + } else { + binding.rvContent.adapter = adapterHelper.adapter + } + viewModel.refresh() + } + + open fun onCreateLayoutManager(): LinearLayoutManager { + return AnimeLinearLayoutManager(requireContext(), LinearLayoutManager.VERTICAL, false) + } + + @CallSuper + override fun initListener() { + binding.srlRefresh.setOnRefreshListener { + viewModel.refresh() + } + } + + @CallSuper + override fun LifecycleOwner.initViewObserver() { + viewModel.onListLiveData.observe(this) { + debugLog { "List:\n " + it.toJson(true) } + + contentAdapter.submitList(it.orEmpty()) { + if (viewModel.isRefresh) { + layoutManager?.scrollToPositionWithOffset(0, 0) + } + + adapterHelper.trailingLoadState = viewModel.loadingMoreState + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/character/PersonCharacterAdapter.kt b/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/character/PersonCharacterAdapter.kt index 045bb5cf..acbd877f 100644 --- a/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/character/PersonCharacterAdapter.kt +++ b/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/character/PersonCharacterAdapter.kt @@ -1,8 +1,10 @@ package com.xiaoyv.bangumi.ui.feature.person.character +import androidx.core.view.isVisible import androidx.recyclerview.widget.DiffUtil -import com.xiaoyv.bangumi.databinding.FragmentMediaBoardItemBinding -import com.xiaoyv.common.api.parser.entity.MediaBoardEntity +import com.xiaoyv.bangumi.databinding.FragmentMediaPageItemBinding +import com.xiaoyv.common.api.parser.entity.CharacterEntity +import com.xiaoyv.common.kts.loadImageAnimate import com.xiaoyv.widget.binder.BaseQuickBindingHolder import com.xiaoyv.widget.binder.BaseQuickDiffBindingAdapter @@ -12,27 +14,27 @@ import com.xiaoyv.widget.binder.BaseQuickDiffBindingAdapter * @author why * @since 12/5/23 */ -class PersonCharacterAdapter : BaseQuickDiffBindingAdapter(ItemDiffItemCallback) { +class PersonCharacterAdapter : BaseQuickDiffBindingAdapter(ItemDiffItemCallback) { - override fun BaseQuickBindingHolder.converted(item: MediaBoardEntity) { - binding.tvTitle.text = String.format("用户:%s", item.userName) - binding.tvContent.text = item.content - binding.tvReplay.text = item.replies - binding.tvTime.text = item.time + override fun BaseQuickBindingHolder.converted(item: CharacterEntity) { + binding.ivCover.loadImageAnimate(item.avatar) + binding.tvTitle.text = item.nameCn.ifBlank { item.nameNative } + binding.tvTag.text = String.format("x%d", item.from.size) + binding.tvSource.isVisible = false } - private object ItemDiffItemCallback : DiffUtil.ItemCallback() { + private object ItemDiffItemCallback : DiffUtil.ItemCallback() { override fun areItemsTheSame( - oldItem: MediaBoardEntity, - newItem: MediaBoardEntity + oldItem: CharacterEntity, + newItem: CharacterEntity ): Boolean { return oldItem.id == newItem.id } override fun areContentsTheSame( - oldItem: MediaBoardEntity, - newItem: MediaBoardEntity + oldItem: CharacterEntity, + newItem: CharacterEntity ): Boolean { return oldItem == newItem } diff --git a/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/character/PersonCharacterFragment.kt b/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/character/PersonCharacterFragment.kt index 560a9605..f133131b 100644 --- a/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/character/PersonCharacterFragment.kt +++ b/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/character/PersonCharacterFragment.kt @@ -1,9 +1,19 @@ package com.xiaoyv.bangumi.ui.feature.person.character +import android.os.Bundle import androidx.core.os.bundleOf -import com.xiaoyv.bangumi.databinding.FragmentPersonCharacterBinding -import com.xiaoyv.blueprint.base.mvvm.normal.BaseViewModelFragment +import androidx.core.view.updatePadding +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager +import com.chad.library.adapter.base.layoutmanager.QuickGridLayoutManager +import com.xiaoyv.bangumi.R +import com.xiaoyv.bangumi.base.BaseListFragment +import com.xiaoyv.bangumi.helper.RouteHelper import com.xiaoyv.blueprint.constant.NavKey +import com.xiaoyv.common.api.parser.entity.CharacterEntity +import com.xiaoyv.common.kts.setOnDebouncedChildClickListener +import com.xiaoyv.widget.binder.BaseQuickDiffBindingAdapter +import com.xiaoyv.widget.kts.dpi /** * Class: [PersonCharacterFragment] @@ -11,14 +21,35 @@ import com.xiaoyv.blueprint.constant.NavKey * @author why * @since 12/4/23 */ -class PersonCharacterFragment : - BaseViewModelFragment() { +class PersonCharacterFragment : BaseListFragment() { + + override fun initArgumentsData(arguments: Bundle) { + viewModel.personId = arguments.getString(NavKey.KEY_STRING).orEmpty() + viewModel.isVirtual = arguments.getBoolean(NavKey.KEY_BOOLEAN) + } + + override val isOnlyOnePage: Boolean + get() = true + override fun initView() { + super.initView() + binding.rvContent.updatePadding(8.dpi, 8.dpi, 8.dpi, 8.dpi) + } + + override fun initListener() { + super.initListener() + contentAdapter.setOnDebouncedChildClickListener(R.id.iv_cover) { + RouteHelper.jumpPerson(it.id, true) + } } - override fun initData() { + override fun onCreateContentAdapter(): BaseQuickDiffBindingAdapter { + return PersonCharacterAdapter() + } + override fun onCreateLayoutManager(): LinearLayoutManager { + return QuickGridLayoutManager(requireContext(), 4, GridLayoutManager.VERTICAL, false) } companion object { diff --git a/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/character/PersonCharacterViewModel.kt b/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/character/PersonCharacterViewModel.kt index e979ea15..6188bc57 100644 --- a/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/character/PersonCharacterViewModel.kt +++ b/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/character/PersonCharacterViewModel.kt @@ -1,6 +1,9 @@ package com.xiaoyv.bangumi.ui.feature.person.character -import com.xiaoyv.blueprint.base.mvvm.normal.BaseViewModel +import com.xiaoyv.bangumi.base.BaseListViewModel +import com.xiaoyv.common.api.BgmApiManager +import com.xiaoyv.common.api.parser.entity.CharacterEntity +import com.xiaoyv.common.api.parser.impl.parserPersonVoices /** * Class: [PersonCharacterViewModel] @@ -8,6 +11,14 @@ import com.xiaoyv.blueprint.base.mvvm.normal.BaseViewModel * @author why * @since 12/4/23 */ -class PersonCharacterViewModel : BaseViewModel() { +class PersonCharacterViewModel : BaseListViewModel() { + /** + * 人物ID和是否为虚拟人物 + */ + internal var personId: String = "" + internal var isVirtual: Boolean = false + override suspend fun onRequestListImpl(): List { + return BgmApiManager.bgmWebApi.queryPersonWorkVoices(personId).parserPersonVoices() + } } \ No newline at end of file diff --git a/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/collect/PersonCollectFragment.kt b/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/collect/PersonCollectFragment.kt index 7fe684fc..77f256a3 100644 --- a/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/collect/PersonCollectFragment.kt +++ b/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/collect/PersonCollectFragment.kt @@ -50,7 +50,7 @@ class PersonCollectFragment : } override fun onCreateLayoutManager(): LinearLayoutManager { - return QuickGridLayoutManager(requireContext(), 6, GridLayoutManager.VERTICAL, false) + return QuickGridLayoutManager(requireContext(), 5, GridLayoutManager.VERTICAL, false) } companion object { diff --git a/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/collect/PersonCollectViewModel.kt b/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/collect/PersonCollectViewModel.kt index e56c643c..52c063ca 100644 --- a/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/collect/PersonCollectViewModel.kt +++ b/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/collect/PersonCollectViewModel.kt @@ -3,7 +3,7 @@ package com.xiaoyv.bangumi.ui.feature.person.collect import com.xiaoyv.bangumi.base.BaseListViewModel import com.xiaoyv.common.api.BgmApiManager import com.xiaoyv.common.api.parser.entity.MediaDetailEntity -import com.xiaoyv.common.api.parser.impl.parserPersonCollect +import com.xiaoyv.common.api.parser.impl.parserPersonCollector /** * Class: [PersonCollectViewModel] @@ -25,12 +25,12 @@ class PersonCollectViewModel : BaseListViewModel() { BgmApiManager.bgmWebApi.queryCharacterCollectUser( personId = personId, page = current - ).parserPersonCollect() + ).parserPersonCollector() } else { BgmApiManager.bgmWebApi.queryPersonCollectUser( personId = personId, page = current - ).parserPersonCollect() + ).parserPersonCollector() } } } \ No newline at end of file diff --git a/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/cooperate/PersonCooperateAdapter.kt b/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/cooperate/PersonCooperateAdapter.kt index 6a968f54..eec20f41 100644 --- a/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/cooperate/PersonCooperateAdapter.kt +++ b/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/cooperate/PersonCooperateAdapter.kt @@ -1,7 +1,6 @@ package com.xiaoyv.bangumi.ui.feature.person.cooperate import androidx.recyclerview.widget.DiffUtil -import com.xiaoyv.bangumi.databinding.FragmentPersonCollectItemBinding import com.xiaoyv.bangumi.databinding.FragmentPersonCooperateItemBinding import com.xiaoyv.common.api.parser.entity.PersonEntity import com.xiaoyv.common.kts.loadImageAnimate @@ -21,6 +20,7 @@ class PersonCooperateAdapter : BaseQuickDiffBindingAdapter() { diff --git a/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/cooperate/PersonCooperateFragment.kt b/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/cooperate/PersonCooperateFragment.kt index 72ef159c..d94f0325 100644 --- a/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/cooperate/PersonCooperateFragment.kt +++ b/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/cooperate/PersonCooperateFragment.kt @@ -51,14 +51,14 @@ class PersonCooperateFragment : } override fun onCreateLayoutManager(): LinearLayoutManager { - return QuickGridLayoutManager(requireContext(), 2, GridLayoutManager.VERTICAL, false) + return QuickGridLayoutManager(requireContext(), 3, GridLayoutManager.VERTICAL, false) } companion object { fun newInstance(personId: String, isVirtual: Boolean): PersonCooperateFragment { return PersonCooperateFragment().apply { arguments = bundleOf( - NavKey.KEY_STRING to "17491", + NavKey.KEY_STRING to personId, NavKey.KEY_BOOLEAN to isVirtual ) } diff --git a/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/opus/PersonOpusAdapter.kt b/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/opus/PersonOpusAdapter.kt index fc71d59e..f092dce0 100644 --- a/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/opus/PersonOpusAdapter.kt +++ b/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/opus/PersonOpusAdapter.kt @@ -1,7 +1,8 @@ package com.xiaoyv.bangumi.ui.feature.person.opus +import androidx.core.view.isVisible import androidx.recyclerview.widget.DiffUtil -import com.xiaoyv.bangumi.databinding.FragmentPersonCollectItemBinding +import com.xiaoyv.bangumi.databinding.FragmentMediaPageItemBinding import com.xiaoyv.common.api.parser.entity.PersonEntity import com.xiaoyv.common.kts.loadImageAnimate import com.xiaoyv.widget.binder.BaseQuickBindingHolder @@ -13,25 +14,28 @@ import com.xiaoyv.widget.binder.BaseQuickDiffBindingAdapter * @author why * @since 12/5/23 */ -class PersonOpusAdapter : BaseQuickDiffBindingAdapter(ItemDiffItemCallback) { +class PersonOpusAdapter : BaseQuickDiffBindingAdapter(ItemDiffItemCallback) { - override fun BaseQuickBindingHolder.converted(item: PersonEntity.RecentCooperate) { - binding.ivAvatar.loadImageAnimate(item.avatar) - binding.tvTip.text = item.name + override fun BaseQuickBindingHolder.converted(item: PersonEntity.RecentlyOpus) { + binding.ivCover.loadImageAnimate(item.cover) + binding.tvTitle.text = item.titleCn + binding.tvTag.text = item.jobs.joinToString("、") + binding.tvSource.text = String.format("%.1f", item.rateInfo.rate) + binding.tvSource.isVisible = item.rateInfo.rate != 0f } - private object ItemDiffItemCallback : DiffUtil.ItemCallback() { + private object ItemDiffItemCallback : DiffUtil.ItemCallback() { override fun areItemsTheSame( - oldItem: PersonEntity.RecentCooperate, - newItem: PersonEntity.RecentCooperate + oldItem: PersonEntity.RecentlyOpus, + newItem: PersonEntity.RecentlyOpus ): Boolean { return oldItem.id == newItem.id } override fun areContentsTheSame( - oldItem: PersonEntity.RecentCooperate, - newItem: PersonEntity.RecentCooperate + oldItem: PersonEntity.RecentlyOpus, + newItem: PersonEntity.RecentlyOpus ): Boolean { return oldItem == newItem } diff --git a/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/opus/PersonOpusFragment.kt b/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/opus/PersonOpusFragment.kt index 9041c0b3..179aa21e 100644 --- a/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/opus/PersonOpusFragment.kt +++ b/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/opus/PersonOpusFragment.kt @@ -1,9 +1,19 @@ package com.xiaoyv.bangumi.ui.feature.person.opus +import android.os.Bundle import androidx.core.os.bundleOf -import com.xiaoyv.bangumi.databinding.FragmentPersonCharacterBinding -import com.xiaoyv.blueprint.base.mvvm.normal.BaseViewModelFragment +import androidx.core.view.updatePadding +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager +import com.chad.library.adapter.base.layoutmanager.QuickGridLayoutManager +import com.xiaoyv.bangumi.R +import com.xiaoyv.bangumi.base.BaseListFragment +import com.xiaoyv.bangumi.helper.RouteHelper import com.xiaoyv.blueprint.constant.NavKey +import com.xiaoyv.common.api.parser.entity.PersonEntity +import com.xiaoyv.common.kts.setOnDebouncedChildClickListener +import com.xiaoyv.widget.binder.BaseQuickDiffBindingAdapter +import com.xiaoyv.widget.kts.dpi /** * Class: [PersonOpusFragment] @@ -12,13 +22,35 @@ import com.xiaoyv.blueprint.constant.NavKey * @since 12/4/23 */ class PersonOpusFragment : - BaseViewModelFragment() { + BaseListFragment() { + + override fun initArgumentsData(arguments: Bundle) { + viewModel.personId = arguments.getString(NavKey.KEY_STRING).orEmpty() + viewModel.isVirtual = arguments.getBoolean(NavKey.KEY_BOOLEAN) + } + + override val isOnlyOnePage: Boolean + get() = false + override fun initView() { + super.initView() + binding.rvContent.updatePadding(8.dpi, 8.dpi, 8.dpi, 8.dpi) + } + + override fun initListener() { + super.initListener() + contentAdapter.setOnDebouncedChildClickListener(R.id.iv_cover) { + RouteHelper.jumpMediaDetail(it.id, it.mediaType) + } } - override fun initData() { + override fun onCreateContentAdapter(): BaseQuickDiffBindingAdapter { + return PersonOpusAdapter() + } + override fun onCreateLayoutManager(): LinearLayoutManager { + return QuickGridLayoutManager(requireContext(), 3, GridLayoutManager.VERTICAL, false) } companion object { diff --git a/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/opus/PersonOpusViewModel.kt b/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/opus/PersonOpusViewModel.kt index a255c35c..44bfdf03 100644 --- a/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/opus/PersonOpusViewModel.kt +++ b/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/opus/PersonOpusViewModel.kt @@ -1,6 +1,9 @@ package com.xiaoyv.bangumi.ui.feature.person.opus -import com.xiaoyv.blueprint.base.mvvm.normal.BaseViewModel +import com.xiaoyv.bangumi.base.BaseListViewModel +import com.xiaoyv.common.api.BgmApiManager +import com.xiaoyv.common.api.parser.entity.PersonEntity +import com.xiaoyv.common.api.parser.impl.parserPersonOpus /** * Class: [PersonOpusViewModel] @@ -8,6 +11,14 @@ import com.xiaoyv.blueprint.base.mvvm.normal.BaseViewModel * @author why * @since 12/4/23 */ -class PersonOpusViewModel : BaseViewModel() { +class PersonOpusViewModel : BaseListViewModel() { + /** + * 人物ID和是否为虚拟人物 + */ + internal var personId: String = "" + internal var isVirtual: Boolean = false + override suspend fun onRequestListImpl(): List { + return BgmApiManager.bgmWebApi.queryPersonWorks(personId, current).parserPersonOpus() + } } \ No newline at end of file diff --git a/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/overview/PersonOverviewFragment.kt b/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/overview/PersonOverviewFragment.kt index 3e74048c..2f759840 100644 --- a/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/overview/PersonOverviewFragment.kt +++ b/app/src/main/java/com/xiaoyv/bangumi/ui/feature/person/overview/PersonOverviewFragment.kt @@ -3,7 +3,7 @@ package com.xiaoyv.bangumi.ui.feature.person.overview import androidx.core.os.bundleOf import androidx.fragment.app.activityViewModels import androidx.lifecycle.LifecycleOwner -import com.xiaoyv.bangumi.databinding.FragmentPersonCharacterBinding +import com.xiaoyv.bangumi.databinding.FragmentPersonOverviewBinding import com.xiaoyv.bangumi.ui.feature.person.PersonViewModel import com.xiaoyv.blueprint.base.mvvm.normal.BaseViewModelFragment import com.xiaoyv.blueprint.constant.NavKey @@ -15,7 +15,7 @@ import com.xiaoyv.blueprint.constant.NavKey * @since 12/4/23 */ class PersonOverviewFragment : - BaseViewModelFragment() { + BaseViewModelFragment() { private val personViewModel: PersonViewModel by activityViewModels() diff --git a/app/src/main/java/com/xiaoyv/bangumi/ui/media/detail/overview/binder/OverviewCharacterBinder.kt b/app/src/main/java/com/xiaoyv/bangumi/ui/media/detail/overview/binder/OverviewCharacterBinder.kt index 85b9c47d..89fc04ea 100644 --- a/app/src/main/java/com/xiaoyv/bangumi/ui/media/detail/overview/binder/OverviewCharacterBinder.kt +++ b/app/src/main/java/com/xiaoyv/bangumi/ui/media/detail/overview/binder/OverviewCharacterBinder.kt @@ -58,8 +58,8 @@ class OverviewCharacterBinder( override fun BaseQuickBindingHolder.converted(item: MediaDetailEntity.MediaCharacter) { binding.ivAvatar.loadImageAnimate(item.avatar) binding.tvName.text = item.characterNameCn.ifBlank { item.characterName } - binding.tvJob.text = item.job - binding.tvComment.text = "+" + item.saveCount + binding.tvJob.text = item.jobs.joinToString(";") + binding.tvComment.text = String.format("+%d", item.saveCount) binding.tvComment.isVisible = item.saveCount != 0 } diff --git a/app/src/main/java/com/xiaoyv/bangumi/ui/media/type/MediaPageFragment.kt b/app/src/main/java/com/xiaoyv/bangumi/ui/media/type/MediaPageFragment.kt index 2cd849fb..e93ce2ed 100644 --- a/app/src/main/java/com/xiaoyv/bangumi/ui/media/type/MediaPageFragment.kt +++ b/app/src/main/java/com/xiaoyv/bangumi/ui/media/type/MediaPageFragment.kt @@ -67,11 +67,7 @@ class MediaPageFragment : BaseViewModelFragment + app:srcCompat="@drawable/ic_filter" /> \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_list.xml b/app/src/main/res/layout/fragment_list.xml index c7d1a545..20af18af 100644 --- a/app/src/main/res/layout/fragment_list.xml +++ b/app/src/main/res/layout/fragment_list.xml @@ -1,6 +1,5 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_media_page_item.xml b/app/src/main/res/layout/fragment_media_page_item.xml index 60cfeab3..e1d81a89 100644 --- a/app/src/main/res/layout/fragment_media_page_item.xml +++ b/app/src/main/res/layout/fragment_media_page_item.xml @@ -4,88 +4,75 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" + android:padding="@dimen/ui_size_8" tools:showIn="@layout/fragment_media_page"> - + app:shapeAppearance="@style/Theme.Bangumi.ImageCornerSmall" /> - - - - + - - + diff --git a/app/src/main/res/layout/fragment_person_character.xml b/app/src/main/res/layout/fragment_person_character.xml deleted file mode 100644 index 553bf918..00000000 --- a/app/src/main/res/layout/fragment_person_character.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_person_collect_item.xml b/app/src/main/res/layout/fragment_person_collect_item.xml index 80eef7e2..1d8de434 100644 --- a/app/src/main/res/layout/fragment_person_collect_item.xml +++ b/app/src/main/res/layout/fragment_person_collect_item.xml @@ -12,23 +12,30 @@ android:layout_height="@dimen/ui_size_0" android:layout_margin="@dimen/ui_size_8" android:foreground="?attr/selectableItemBackground" - app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintDimensionRatio="1:1" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:shapeAppearance="@style/Theme.Bangumi.ImageCornerSmall" /> + + - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_person_cooperate_item.xml b/app/src/main/res/layout/fragment_person_cooperate_item.xml index 98f03726..b47a164e 100644 --- a/app/src/main/res/layout/fragment_person_cooperate_item.xml +++ b/app/src/main/res/layout/fragment_person_cooperate_item.xml @@ -12,12 +12,28 @@ android:layout_height="@dimen/ui_size_0" android:layout_margin="@dimen/ui_size_8" android:foreground="?attr/selectableItemBackground" - app:layout_constraintDimensionRatio="1:1" + app:layout_constraintDimensionRatio="3:4" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" app:shapeAppearance="@style/Theme.Bangumi.ImageCornerSmall" /> + + - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_person_overview.xml b/app/src/main/res/layout/fragment_person_overview.xml index 77d9ef65..4ad0ec3c 100644 --- a/app/src/main/res/layout/fragment_person_overview.xml +++ b/app/src/main/res/layout/fragment_person_overview.xml @@ -3,4 +3,8 @@ android:layout_width="match_parent" android:layout_height="match_parent"> + \ No newline at end of file diff --git a/lib-common/src/main/java/com/xiaoyv/common/api/api/BgmWebApi.kt b/lib-common/src/main/java/com/xiaoyv/common/api/api/BgmWebApi.kt index df25ac4f..72d90d3c 100644 --- a/lib-common/src/main/java/com/xiaoyv/common/api/api/BgmWebApi.kt +++ b/lib-common/src/main/java/com/xiaoyv/common/api/api/BgmWebApi.kt @@ -286,6 +286,15 @@ interface BgmWebApi { @Query("page") page: Int? = null, ): Document + @GET("/person/{personId}/works") + suspend fun queryPersonWorks( + @Path("personId", encoded = true) personId: String, + @Query("page") page: Int? = null, + ): Document + + @GET("/person/{personId}/works/voice") + suspend fun queryPersonWorkVoices(@Path("personId", encoded = true) personId: String): Document + companion object { /** diff --git a/lib-common/src/main/java/com/xiaoyv/common/api/parser/entity/CharacterEntity.kt b/lib-common/src/main/java/com/xiaoyv/common/api/parser/entity/CharacterEntity.kt new file mode 100644 index 00000000..f3384f54 --- /dev/null +++ b/lib-common/src/main/java/com/xiaoyv/common/api/parser/entity/CharacterEntity.kt @@ -0,0 +1,21 @@ +package com.xiaoyv.common.api.parser.entity + +import android.os.Parcelable +import androidx.annotation.Keep +import kotlinx.parcelize.Parcelize + +/** + * Class: [CharacterEntity] + * + * @author why + * @since 12/6/23 + */ +@Keep +@Parcelize +data class CharacterEntity( + var id: String = "", + var nameCn: String = "", + var nameNative: String = "", + var avatar: String = "", + var from: List = emptyList() +) : Parcelable \ No newline at end of file diff --git a/lib-common/src/main/java/com/xiaoyv/common/api/parser/entity/MediaDetailEntity.kt b/lib-common/src/main/java/com/xiaoyv/common/api/parser/entity/MediaDetailEntity.kt index 85ca0cbd..288d69ae 100644 --- a/lib-common/src/main/java/com/xiaoyv/common/api/parser/entity/MediaDetailEntity.kt +++ b/lib-common/src/main/java/com/xiaoyv/common/api/parser/entity/MediaDetailEntity.kt @@ -93,6 +93,7 @@ data class MediaDetailEntity( @SerializedName("titleCn") var titleCn: String = "", @SerializedName("titleNative") var titleNative: String = "", @SerializedName("type") var type: String = "", + @SerializedName("characterJobs") var characterJobs: List = emptyList() ) : Parcelable @Parcelize @@ -103,7 +104,7 @@ data class MediaDetailEntity( @SerializedName("characterName") var characterName: String = "", @SerializedName("characterNameCn") var characterNameCn: String = "", @SerializedName("saveCount") var saveCount: Int = 0, - @SerializedName("job") var job: String = "", + @SerializedName("jobs") var jobs: List = emptyList(), @SerializedName("personJob") var personJob: String = "", @SerializedName("persons") var persons: List = emptyList() ) : Parcelable diff --git a/lib-common/src/main/java/com/xiaoyv/common/api/parser/entity/PersonEntity.kt b/lib-common/src/main/java/com/xiaoyv/common/api/parser/entity/PersonEntity.kt index 36765edb..513dc8ad 100644 --- a/lib-common/src/main/java/com/xiaoyv/common/api/parser/entity/PersonEntity.kt +++ b/lib-common/src/main/java/com/xiaoyv/common/api/parser/entity/PersonEntity.kt @@ -28,8 +28,8 @@ data class PersonEntity( @SerializedName("infos") var infos: List = emptyList(), @SerializedName("recommendIndexes") var recommendIndexes: List = emptyList(), @SerializedName("whoCollects") var whoCollects: List = emptyList(), - @SerializedName("performers") var performers: List = emptyList(), - @SerializedName("recentCharacters") var recentCharacters: List = emptyList(), + @SerializedName("performers") var performers: List = emptyList(), + @SerializedName("recentCharacters") var recentCharacters: List = emptyList(), @SerializedName("recentOpuses") var recentOpuses: List = emptyList(), @SerializedName("recentCooperates") var recentCooperates: List = emptyList(), @SerializedName("comments") var comments: List = emptyList(), @@ -55,15 +55,24 @@ data class PersonEntity( @Parcelize data class RecentlyOpus( @SerializedName("cover") var cover: String = "", - @SerializedName("title") var title: String = "", + @SerializedName("titleNative") var titleNative: String = "", + @SerializedName("titleCn") var titleCn: String = "", @SerializedName("id") var id: String = "", - @SerializedName("job") var job: String = "", + @SerializedName("job") var jobs: List = emptyList(), @SerializedName("mediaType") @MediaType var mediaType: String = MediaType.TYPE_UNKNOWN, + @SerializedName("rateInfo") var rateInfo: RateInfo = RateInfo() ) : Parcelable @Keep @Parcelize - data class RecentlyCharacter( + data class RateInfo( + var count: Int = 0, + var rate: Float = 0f + ) : Parcelable + + @Keep + @Parcelize + data class RecentlyPerformer( @SerializedName("character") var character: MediaDetailEntity.MediaCharacter = MediaDetailEntity.MediaCharacter(), @SerializedName("media") var media: MediaDetailEntity.MediaRelative = MediaDetailEntity.MediaRelative() ) : Parcelable diff --git a/lib-common/src/main/java/com/xiaoyv/common/api/parser/impl/MediaDetailParser.kt b/lib-common/src/main/java/com/xiaoyv/common/api/parser/impl/MediaDetailParser.kt index 81bc549c..7f33a5f9 100644 --- a/lib-common/src/main/java/com/xiaoyv/common/api/parser/impl/MediaDetailParser.kt +++ b/lib-common/src/main/java/com/xiaoyv/common/api/parser/impl/MediaDetailParser.kt @@ -279,7 +279,7 @@ fun Document.parserMediaDetail(): MediaDetailEntity { .fetchStyleBackgroundUrl().optImageUrl() } item.select(".info .tip_j").apply { - mediaCharacter.job = select(".badge_job_tip").text() + mediaCharacter.jobs = select(".badge_job_tip").map { it.text() } mediaCharacter.characterNameCn = select("span.tip").text() mediaCharacter.persons = select("a").map { person -> val characterPerson = MediaDetailEntity.MediaCharacterPerson() diff --git a/lib-common/src/main/java/com/xiaoyv/common/api/parser/impl/PersonParser.kt b/lib-common/src/main/java/com/xiaoyv/common/api/parser/impl/PersonParser.kt index 92e5b734..a95453d0 100644 --- a/lib-common/src/main/java/com/xiaoyv/common/api/parser/impl/PersonParser.kt +++ b/lib-common/src/main/java/com/xiaoyv/common/api/parser/impl/PersonParser.kt @@ -1,5 +1,6 @@ package com.xiaoyv.common.api.parser.impl +import com.xiaoyv.common.api.parser.entity.CharacterEntity import com.xiaoyv.common.api.parser.entity.MediaDetailEntity import com.xiaoyv.common.api.parser.entity.PersonEntity import com.xiaoyv.common.api.parser.fetchStyleBackgroundUrl @@ -113,7 +114,7 @@ fun Document.parserPerson(personId: String, isVirtual: Boolean): PersonEntity { else -> MediaType.TYPE_UNKNOWN } } - character.job = select(".badge_job").text() + character.jobs = select(".badge_job").map { it.text() } } item.select(".innerRightList").apply { @@ -123,32 +124,12 @@ fun Document.parserPerson(personId: String, isVirtual: Boolean): PersonEntity { character.characterNameCn = select("h3 a.l").text() character.personJob = select("small.grey").text() } - PersonEntity.RecentlyCharacter(character, media) + PersonEntity.RecentlyPerformer(character, media) } } key.contains("最近演出角色") -> { - entity.recentCharacters = - value.select("ul.browserList > li").orEmpty().map { item -> - val character = MediaDetailEntity.MediaCharacter() - val media = MediaDetailEntity.MediaRelative() - - item.select(".innerLeftItem").apply { - character.id = select("a.avatar").attr("href").substringAfterLast("/") - character.avatar = select("img.avatar").attr("src").optImageUrl() - character.characterName = select("h3 a.l").text() - character.characterNameCn = select("h3 .tip").text() - } - - item.select(".innerRightList").apply { - media.id = select("a.subjectCover").attr("href").substringAfterLast("/") - media.cover = select("img.cover").attr("src").optImageUrl() - media.titleNative = select("h3").text() - media.titleCn = select("small.grey").text() - character.job = select(".badge_job").text() - } - PersonEntity.RecentlyCharacter(character, media) - } + entity.recentCharacters = value.parserPersonVoices() } key.contains("最近参与") -> { @@ -157,8 +138,8 @@ fun Document.parserPerson(personId: String, isVirtual: Boolean): PersonEntity { item.select(".innerLeftItem").apply { opus.id = select("a.cover").attr("href").substringAfterLast("/") opus.cover = select("img.cover").attr("src").optImageUrl() - opus.title = select("h3 a.l").text() - opus.job = select(".badge_job").text() + opus.titleNative = select("h3 a.l").text() + opus.jobs = select(".badge_job").map { it.text() } opus.mediaType = select(".ico_subject_type").toString().let { when { it.contains("subject_type_1") -> MediaType.TYPE_BOOK @@ -189,7 +170,10 @@ fun Document.parserPerson(personId: String, isVirtual: Boolean): PersonEntity { return entity } -fun Element.parserPersonCollect(): List { +/** + * 解析收藏者者数据 + */ +fun Element.parserPersonCollector(): List { return select("#memberUserList > li.user").map { item -> val who = MediaDetailEntity.MediaWho() item.select(".userImage img").apply { @@ -202,6 +186,9 @@ fun Element.parserPersonCollect(): List { } } +/** + * 解析合作者数据 + */ fun Element.parserPersonCooperate(): List { return select(".browserCrtList > div").map { item -> val cooperate = PersonEntity.RecentCooperate() @@ -237,6 +224,67 @@ fun Element.parserPersonCooperate(): List { } } +/** + * 解析作品条目数据 + */ +fun Element.parserPersonOpus(): List { + return select("#browserItemList > li").map { item -> + val opus = PersonEntity.RecentlyOpus() + opus.id = item.select("a.subjectCover").attr("href").substringAfterLast("/") + opus.cover = item.select("img.cover").attr("src").optImageUrl() + opus.titleCn = item.select("h3 a").text() + opus.titleNative = item.select("small.grey").text() + opus.mediaType = item.select(".ico_subject_type").toString().let { + when { + it.contains("subject_type_1") -> MediaType.TYPE_BOOK + it.contains("subject_type_2") -> MediaType.TYPE_ANIME + it.contains("subject_type_3") -> MediaType.TYPE_MUSIC + it.contains("subject_type_4") -> MediaType.TYPE_GAME + it.contains("subject_type_6") -> MediaType.TYPE_REAL + else -> MediaType.TYPE_UNKNOWN + } + } + opus.jobs = item.select(".badge_job").map { it.text() } + opus.rateInfo = item.select(".rateInfo").let { subItem -> + val rateInfo = PersonEntity.RateInfo() + rateInfo.rate = subItem.select(".fade").text().toFloatOrNull() ?: 0f + rateInfo.count = subItem.select(".tip_j").text().parseCount() + rateInfo + } + + opus + } +} + +/** + * 解析角色的条目数据 + */ +fun Element.parserPersonVoices(): List { + return select(".browserList > li").map { item -> + val entity = CharacterEntity() + + item.select(".innerLeftItem").apply { + entity.id = select("a.avatar").attr("href").substringAfterLast("/") + entity.avatar = select("img.avatar").attr("src").optImageUrl() + entity.nameNative = select("h3 a.l").text() + entity.nameCn = select("h3 .tip").text() + } + + entity.from = item.select(".innerRightList > li").map { subItem -> + val relative = MediaDetailEntity.MediaRelative() + relative.id = subItem.select("a.subjectCover").attr("href").substringAfterLast("/") + relative.cover = subItem.select("img.cover").attr("src").optImageUrl() + relative.titleNative = subItem.select("h3").text() + relative.titleCn = subItem.select("small.grey").text() + relative.characterJobs = subItem.select(".badge_job").map { it.text() } + relative + } + entity + } +} + + + diff --git a/lib-common/src/main/java/com/xiaoyv/common/widget/menu/SlideMenuView.kt b/lib-common/src/main/java/com/xiaoyv/common/widget/menu/SlideMenuView.kt new file mode 100644 index 00000000..6ede0729 --- /dev/null +++ b/lib-common/src/main/java/com/xiaoyv/common/widget/menu/SlideMenuView.kt @@ -0,0 +1,100 @@ +package com.xiaoyv.common.widget.menu + +import android.content.Context +import android.graphics.Color +import android.util.AttributeSet +import androidx.recyclerview.widget.DiffUtil +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.xiaoyv.common.R +import com.xiaoyv.common.databinding.ViewMenuSlideBinding +import com.xiaoyv.common.kts.GoogleAttr +import com.xiaoyv.common.kts.setOnDebouncedChildClickListener +import com.xiaoyv.common.kts.tint +import com.xiaoyv.common.widget.scroll.AnimeLinearLayoutManager +import com.xiaoyv.widget.binder.BaseQuickBindingHolder +import com.xiaoyv.widget.binder.BaseQuickDiffBindingAdapter +import com.xiaoyv.widget.kts.getAttrColor + +/** + * Class: [SlideMenuView] + * + * @author why + * @since 12/6/23 + */ +class SlideMenuView @JvmOverloads constructor( + context: Context, attrs: AttributeSet? = null +) : RecyclerView(context, attrs) { + private val itemAdapter = ItemAdapter() + + var menus: List = emptyList() + set(value) { + field = value + itemAdapter.submitList(value) + } + + var onItemSelectedListener: (SlideMenu) -> Unit = {} + + var selected: Int = -1 + set(value) { + field = value + itemAdapter.selected(value) + } + + init { + adapter = itemAdapter + itemAnimator = null + layoutManager = AnimeLinearLayoutManager(context, LinearLayoutManager.VERTICAL, false) + itemAdapter.setOnDebouncedChildClickListener(R.id.tv_menu) { + onItemSelectedListener(it) + } + } + + private class ItemAdapter : + BaseQuickDiffBindingAdapter(SlideMenuItemCallback) { + private var selectedIndex = -1 + + override fun onBindViewHolder( + holder: BaseQuickBindingHolder, + position: Int, + item: SlideMenu? + ) { + super.onBindViewHolder(holder, position, item) + if (position == selectedIndex) { + holder.binding.tvMenu.backgroundTintList = + context.getAttrColor(GoogleAttr.colorPrimarySurface).tint + holder.binding.tvMenu.setTextColor( + context.getAttrColor(GoogleAttr.colorOnPrimarySurface) + ) + } else { + holder.binding.tvMenu.backgroundTintList = Color.TRANSPARENT.tint + holder.binding.tvMenu.setTextColor( + context.getAttrColor(GoogleAttr.colorOnSurfaceVariant) + ) + } + } + + override fun BaseQuickBindingHolder.converted(item: SlideMenu) { + binding.tvMenu.text = item.title + } + + fun selected(value: Int) { + val old = selectedIndex + selectedIndex = value + if (old in 0..() { + override fun areItemsTheSame(oldItem: SlideMenu, newItem: SlideMenu): Boolean { + return oldItem.title == newItem.title + } + + override fun areContentsTheSame(oldItem: SlideMenu, newItem: SlideMenu): Boolean { + return oldItem == newItem + } + } +} \ No newline at end of file diff --git a/lib-common/src/main/res/drawable/shape_bottom_edge_corner.xml b/lib-common/src/main/res/drawable/shape_bottom_edge_corner.xml index d0b533bd..140d7e1b 100644 --- a/lib-common/src/main/res/drawable/shape_bottom_edge_corner.xml +++ b/lib-common/src/main/res/drawable/shape_bottom_edge_corner.xml @@ -4,10 +4,12 @@ android:useLevel="false"> + android:bottomRightRadius="@dimen/ui_layout_radius" + android:topLeftRadius="@dimen/ui_layout_radius" + android:topRightRadius="@dimen/ui_layout_radius" /> \ No newline at end of file diff --git a/lib-common/src/main/res/layout/view_menu_slide.xml b/lib-common/src/main/res/layout/view_menu_slide.xml new file mode 100644 index 00000000..e5a65446 --- /dev/null +++ b/lib-common/src/main/res/layout/view_menu_slide.xml @@ -0,0 +1,14 @@ + +