cloudstream/app/src/main/java/com/lagradost/cloudstream3/ui/home/HomeParentItemAdapterPrevie...

645 lines
26 KiB
Kotlin

package com.lagradost.cloudstream3.ui.home
import android.os.Bundle
import android.os.Parcelable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.SearchView
import androidx.core.content.ContextCompat
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.findViewTreeLifecycleOwner
import androidx.recyclerview.widget.RecyclerView
import androidx.viewbinding.ViewBinding
import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipGroup
import com.lagradost.cloudstream3.APIHolder.getId
import com.lagradost.cloudstream3.AcraApplication.Companion.getActivity
import com.lagradost.cloudstream3.CommonActivity.activity
import com.lagradost.cloudstream3.HomePageList
import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.MainActivity
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.databinding.FragmentHomeHeadBinding
import com.lagradost.cloudstream3.databinding.FragmentHomeHeadTvBinding
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.debugException
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.ui.ViewHolderState
import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.account.AccountHelper.showAccountSelectLinear
import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.selectHomepage
import com.lagradost.cloudstream3.ui.result.FOCUS_SELF
import com.lagradost.cloudstream3.ui.result.ResultViewModel2
import com.lagradost.cloudstream3.ui.result.START_ACTION_RESUME_LATEST
import com.lagradost.cloudstream3.ui.result.setLinearListLayout
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_LOAD
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_SHOW_METADATA
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
import com.lagradost.cloudstream3.ui.settings.Globals.EMULATOR
import com.lagradost.cloudstream3.ui.settings.Globals.TV
import com.lagradost.cloudstream3.ui.settings.Globals.isLayout
import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showOptionSelectStringRes
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbarMargin
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbarView
import com.lagradost.cloudstream3.utils.UIHelper.populateChips
class HomeParentItemAdapterPreview(
override val fragment: Fragment,
private val viewModel: HomeViewModel,
) : ParentItemAdapter(fragment, id = "HomeParentItemAdapterPreview".hashCode(),
clickCallback = {
viewModel.click(it)
}, moreInfoClickCallback = {
viewModel.popup(it)
}, expandCallback = {
viewModel.expand(it)
}) {
override val headers = 1
override fun onCreateHeader(parent: ViewGroup): ViewHolderState<Bundle> {
val inflater = LayoutInflater.from(parent.context)
val binding = if (isLayout(TV or EMULATOR)) FragmentHomeHeadTvBinding.inflate(
inflater,
parent,
false
) else FragmentHomeHeadBinding.inflate(inflater, parent, false)
if (binding is FragmentHomeHeadTvBinding && isLayout(EMULATOR)) {
binding.homeBookmarkParentItemMoreInfo.isVisible = true
val marginInDp = 50
val density = binding.horizontalScrollChips.context.resources.displayMetrics.density
val marginInPixels = (marginInDp * density).toInt()
val params = binding.horizontalScrollChips.layoutParams as ViewGroup.MarginLayoutParams
params.marginEnd = marginInPixels
binding.horizontalScrollChips.layoutParams = params
binding.homeWatchParentItemTitle.setCompoundDrawablesWithIntrinsicBounds(
null,
null,
ContextCompat.getDrawable(
parent.context,
R.drawable.ic_baseline_arrow_forward_24
),
null
)
}
return HeaderViewHolder(binding, viewModel, fragment = fragment)
}
override fun onBindHeader(holder: ViewHolderState<Bundle>) {
(holder as? HeaderViewHolder)?.bind()
}
private class HeaderViewHolder(
val binding: ViewBinding, val viewModel: HomeViewModel, fragment: Fragment,
) :
ViewHolderState<Bundle>(binding) {
override fun save(): Bundle =
Bundle().apply {
putParcelable(
"resumeRecyclerView",
resumeRecyclerView.layoutManager?.onSaveInstanceState()
)
putParcelable(
"bookmarkRecyclerView",
bookmarkRecyclerView.layoutManager?.onSaveInstanceState()
)
//putInt("previewViewpager", previewViewpager.currentItem)
}
override fun restore(state: Bundle) {
state.getParcelable<Parcelable>("resumeRecyclerView")?.let { recycle ->
resumeRecyclerView.layoutManager?.onRestoreInstanceState(recycle)
}
state.getParcelable<Parcelable>("bookmarkRecyclerView")?.let { recycle ->
bookmarkRecyclerView.layoutManager?.onRestoreInstanceState(recycle)
}
//state.getInt("previewViewpager").let { recycle ->
// previewViewpager.setCurrentItem(recycle,true)
//}
}
val previewAdapter = HomeScrollAdapter(fragment = fragment)
private val resumeAdapter = HomeChildItemAdapter(
fragment,
id = "resumeAdapter".hashCode(),
nextFocusUp = itemView.nextFocusUpId,
nextFocusDown = itemView.nextFocusDownId
) { callback ->
if (callback.action != SEARCH_ACTION_SHOW_METADATA) {
viewModel.click(callback)
return@HomeChildItemAdapter
}
callback.view.context?.getActivity()?.showOptionSelectStringRes(
callback.view,
callback.card.posterUrl,
listOf(
R.string.action_open_watching,
R.string.action_remove_watching
),
listOf(
R.string.action_open_play,
R.string.action_open_watching,
R.string.action_remove_watching
)
) { (isTv, actionId) ->
when (actionId + if (isTv) 0 else 1) {
// play
0 -> {
viewModel.click(
SearchClickCallback(
START_ACTION_RESUME_LATEST,
callback.view,
-1,
callback.card
)
)
}
//info
1 -> {
viewModel.click(
SearchClickCallback(
SEARCH_ACTION_LOAD,
callback.view,
-1,
callback.card
)
)
}
// remove
2 -> {
val card = callback.card
if (card is DataStoreHelper.ResumeWatchingResult) {
DataStoreHelper.removeLastWatched(card.parentId)
viewModel.reloadStored()
}
}
}
}
}
private val bookmarkAdapter = HomeChildItemAdapter(
fragment,
id = "bookmarkAdapter".hashCode(),
nextFocusUp = itemView.nextFocusUpId,
nextFocusDown = itemView.nextFocusDownId
) { callback ->
if (callback.action != SEARCH_ACTION_SHOW_METADATA) {
viewModel.click(callback)
return@HomeChildItemAdapter
}
(callback.view.context?.getActivity() as? MainActivity)?.loadPopup(
callback.card,
load = false
)
/*
callback.view.context?.getActivity()?.showOptionSelectStringRes(
callback.view,
callback.card.posterUrl,
listOf(
R.string.action_open_watching,
R.string.action_remove_from_bookmarks,
),
listOf(
R.string.action_open_play,
R.string.action_open_watching,
R.string.action_remove_from_bookmarks
)
) { (isTv, actionId) ->
when (actionId + if (isTv) 0 else 1) { // play
0 -> {
viewModel.click(
SearchClickCallback(
START_ACTION_RESUME_LATEST,
callback.view,
-1,
callback.card
)
)
}
1 -> { // info
viewModel.click(
SearchClickCallback(
SEARCH_ACTION_LOAD,
callback.view,
-1,
callback.card
)
)
}
2 -> { // remove
DataStoreHelper.setResultWatchState(
callback.card.id,
WatchType.NONE.internalId
)
viewModel.reloadStored()
}
}
}
*/
}
private val previewViewpager: ViewPager2 =
itemView.findViewById(R.id.home_preview_viewpager)
private val previewViewpagerText: ViewGroup =
itemView.findViewById(R.id.home_preview_viewpager_text)
// private val previewHeader: FrameLayout = itemView.findViewById(R.id.home_preview)
private val resumeHolder: View = itemView.findViewById(R.id.home_watch_holder)
private val resumeRecyclerView: RecyclerView =
itemView.findViewById(R.id.home_watch_child_recyclerview)
private val bookmarkHolder: View = itemView.findViewById(R.id.home_bookmarked_holder)
private val bookmarkRecyclerView: RecyclerView =
itemView.findViewById(R.id.home_bookmarked_child_recyclerview)
private val homeAccount: View? = itemView.findViewById(R.id.home_preview_switch_account)
private val alternativeHomeAccount: View? =
itemView.findViewById(R.id.alternative_switch_account)
private val topPadding: View? = itemView.findViewById(R.id.home_padding)
private val alternativeAccountPadding: View? =
itemView.findViewById(R.id.alternative_account_padding)
private val homeNonePadding: View = itemView.findViewById(R.id.home_none_padding)
fun onSelect(item: LoadResponse, position: Int) {
(binding as? FragmentHomeHeadTvBinding)?.apply {
homePreviewDescription.isGone =
item.plot.isNullOrBlank()
homePreviewDescription.text =
item.plot ?: ""
homePreviewText.text = item.name
populateChips(
homePreviewTags,
item.tags?.take(6) ?: emptyList(),
R.style.ChipFilledSemiTransparent
)
homePreviewTags.isGone =
item.tags.isNullOrEmpty()
homePreviewPlayBtt.setOnClickListener { view ->
viewModel.click(
LoadClickCallback(
START_ACTION_RESUME_LATEST,
view,
position,
item
)
)
}
homePreviewInfoBtt.setOnClickListener { view ->
viewModel.click(
LoadClickCallback(0, view, position, item)
)
}
}
(binding as? FragmentHomeHeadBinding)?.apply {
//homePreviewImage.setImage(item.posterUrl, item.posterHeaders)
homePreviewPlay.setOnClickListener { view ->
viewModel.click(
LoadClickCallback(
START_ACTION_RESUME_LATEST,
view,
position,
item
)
)
}
homePreviewInfo.setOnClickListener { view ->
viewModel.click(
LoadClickCallback(0, view, position, item)
)
}
// very ugly code, but I don't care
val id = item.getId()
val watchType =
DataStoreHelper.getResultWatchState(id)
homePreviewBookmark.setText(watchType.stringRes)
homePreviewBookmark.setCompoundDrawablesWithIntrinsicBounds(
null,
ContextCompat.getDrawable(
homePreviewBookmark.context,
watchType.iconRes
),
null,
null
)
homePreviewBookmark.setOnClickListener { fab ->
fab.context.getActivity()?.showBottomDialog(
WatchType.entries
.map { fab.context.getString(it.stringRes) }
.toList(),
DataStoreHelper.getResultWatchState(id).ordinal,
fab.context.getString(R.string.action_add_to_bookmarks),
showApply = false,
{}) {
val newValue = WatchType.entries[it]
ResultViewModel2().updateWatchStatus(
newValue,
fab.context,
item
) { statusChanged: Boolean ->
if (!statusChanged) return@updateWatchStatus
homePreviewBookmark.setCompoundDrawablesWithIntrinsicBounds(
null,
ContextCompat.getDrawable(
homePreviewBookmark.context,
newValue.iconRes
),
null,
null
)
homePreviewBookmark.setText(newValue.stringRes)
}
}
}
}
}
private val previewCallback: ViewPager2.OnPageChangeCallback =
object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
previewAdapter.apply {
if (position >= itemCount - 1 && hasMoreItems) {
hasMoreItems = false // don't make two requests
viewModel.loadMoreHomeScrollResponses()
}
}
val item = previewAdapter.getItemOrNull(position) ?: return
onSelect(item, position)
}
}
override fun onViewDetachedFromWindow() {
previewViewpager.unregisterOnPageChangeCallback(previewCallback)
}
private val toggleList = listOf<Pair<Chip, WatchType>>(
Pair(itemView.findViewById(R.id.home_type_watching_btt), WatchType.WATCHING),
Pair(itemView.findViewById(R.id.home_type_completed_btt), WatchType.COMPLETED),
Pair(itemView.findViewById(R.id.home_type_dropped_btt), WatchType.DROPPED),
Pair(itemView.findViewById(R.id.home_type_on_hold_btt), WatchType.ONHOLD),
Pair(itemView.findViewById(R.id.home_plan_to_watch_btt), WatchType.PLANTOWATCH),
)
private val toggleListHolder: ChipGroup? = itemView.findViewById(R.id.home_type_holder)
fun bind() = Unit
init {
previewViewpager.setPageTransformer(HomeScrollTransformer())
previewViewpager.adapter = previewAdapter
resumeRecyclerView.adapter = resumeAdapter
bookmarkRecyclerView.adapter = bookmarkAdapter
resumeRecyclerView.setLinearListLayout(
nextLeft = R.id.nav_rail_view,
nextRight = FOCUS_SELF
)
bookmarkRecyclerView.setLinearListLayout(
nextLeft = R.id.nav_rail_view,
nextRight = FOCUS_SELF
)
fixPaddingStatusbarMargin(topPadding)
for ((chip, watch) in toggleList) {
chip.isChecked = false
chip.setOnCheckedChangeListener { _, isChecked ->
if (isChecked) {
viewModel.loadStoredData(setOf(watch))
}
// Else if all are unchecked -> Do not load data
else if (toggleList.all { !it.first.isChecked }) {
viewModel.loadStoredData(emptySet())
}
}
}
homeAccount?.setOnClickListener {
activity?.showAccountSelectLinear()
}
alternativeHomeAccount?.setOnClickListener {
activity?.showAccountSelectLinear()
}
(binding as? FragmentHomeHeadTvBinding)?.apply {
homePreviewChangeApi.setOnClickListener { view ->
view.context.selectHomepage(viewModel.repo?.name) { api ->
viewModel.loadAndCancel(api, forceReload = true, fromUI = true)
}
}
homePreviewSearchButton.setOnClickListener { _ ->
// Open blank screen.
viewModel.queryTextSubmit("")
}
// This makes the hidden next buttons only available when on the info button
// Otherwise you might be able to go to the next item without being at the info button
homePreviewInfoBtt.setOnFocusChangeListener { _, hasFocus ->
homePreviewHiddenNextFocus.isFocusable = hasFocus
}
homePreviewPlayBtt.setOnFocusChangeListener { _, hasFocus ->
homePreviewHiddenPrevFocus.isFocusable = hasFocus
}
homePreviewHiddenNextFocus.setOnFocusChangeListener { _, hasFocus ->
if (!hasFocus) return@setOnFocusChangeListener
previewViewpager.setCurrentItem(previewViewpager.currentItem + 1, true)
homePreviewInfoBtt.requestFocus()
}
homePreviewHiddenPrevFocus.setOnFocusChangeListener { _, hasFocus ->
if (!hasFocus) return@setOnFocusChangeListener
if (previewViewpager.currentItem <= 0) {
(activity as? MainActivity)?.binding?.navRailView?.requestFocus()
} else {
previewViewpager.setCurrentItem(previewViewpager.currentItem - 1, true)
binding.homePreviewPlayBtt.requestFocus()
}
}
}
(binding as? FragmentHomeHeadBinding)?.apply {
homeSearch.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
override fun onQueryTextSubmit(query: String): Boolean {
viewModel.queryTextSubmit(query)
return true
}
override fun onQueryTextChange(newText: String): Boolean {
viewModel.queryTextChange(newText)
return true
}
})
}
}
private fun updatePreview(preview: Resource<Pair<Boolean, List<LoadResponse>>>) {
if (preview is Resource.Success) {
homeNonePadding.apply {
val params = layoutParams
params.height = 0
layoutParams = params
}
} else {
fixPaddingStatusbarView(homeNonePadding)
}
when (preview) {
is Resource.Success -> {
previewAdapter.submitList(preview.value.second)
previewAdapter.hasMoreItems = preview.value.first
/*if (!.setItems(
preview.value.second,
preview.value.first
)
) {
// this might seam weird and useless, however this prevents a very weird andrid bug were the viewpager is not rendered properly
// I have no idea why that happens, but this is my ducktape solution
previewViewpager.setCurrentItem(0, false)
previewViewpager.beginFakeDrag()
previewViewpager.fakeDragBy(1f)
previewViewpager.endFakeDrag()
previewCallback.onPageSelected(0)
//previewHeader.isVisible = true
}*/
previewViewpager.isVisible = true
previewViewpagerText.isVisible = true
alternativeAccountPadding?.isVisible = false
}
else -> {
previewAdapter.submitList(listOf())
previewViewpager.setCurrentItem(0, false)
previewViewpager.isVisible = false
previewViewpagerText.isVisible = false
alternativeAccountPadding?.isVisible = true
//previewHeader.isVisible = false
}
}
}
private fun updateResume(resumeWatching: List<SearchResponse>) {
resumeHolder.isVisible = resumeWatching.isNotEmpty()
resumeAdapter.submitList(resumeWatching)
if (
binding is FragmentHomeHeadBinding ||
binding is FragmentHomeHeadTvBinding &&
isLayout(EMULATOR)
) {
val title = (binding as? FragmentHomeHeadBinding)?.homeWatchParentItemTitle
?: (binding as? FragmentHomeHeadTvBinding)?.homeWatchParentItemTitle
title?.setOnClickListener {
viewModel.popup(
HomeViewModel.ExpandableHomepageList(
HomePageList(
title.text.toString(),
resumeWatching,
false
), 1, false
),
deleteCallback = {
viewModel.deleteResumeWatching()
}
)
}
}
}
private fun updateBookmarks(data: Pair<Boolean, List<SearchResponse>>) {
val (visible, list) = data
bookmarkHolder.isVisible = visible
bookmarkAdapter.submitList(list)
if (
binding is FragmentHomeHeadBinding ||
binding is FragmentHomeHeadTvBinding &&
isLayout(EMULATOR)
) {
val title = (binding as? FragmentHomeHeadBinding)?.homeBookmarkParentItemTitle
?: (binding as? FragmentHomeHeadTvBinding)?.homeBookmarkParentItemTitle
title?.setOnClickListener {
val items = toggleList.map { it.first }.filter { it.isChecked }
if (items.isEmpty()) return@setOnClickListener // we don't want to show an empty dialog
val textSum = items
.mapNotNull { it.text }.joinToString()
viewModel.popup(
HomeViewModel.ExpandableHomepageList(
HomePageList(
textSum,
list,
false
), 1, false
), deleteCallback = {
viewModel.deleteBookmarks(list)
}
)
}
}
}
override fun onViewAttachedToWindow() {
previewViewpager.registerOnPageChangeCallback(previewCallback)
binding.root.findViewTreeLifecycleOwner()?.apply {
observe(viewModel.preview) {
updatePreview(it)
}
if (binding is FragmentHomeHeadTvBinding) {
observe(viewModel.apiName) { name ->
binding.homePreviewChangeApi.text = name
}
}
observe(viewModel.resumeWatching) {
updateResume(it)
}
observe(viewModel.bookmarks) {
updateBookmarks(it)
}
observe(viewModel.availableWatchStatusTypes) { (checked, visible) ->
for ((chip, watch) in toggleList) {
chip.apply {
isVisible = visible.contains(watch)
isChecked = checked.contains(watch)
}
}
toggleListHolder?.isGone = visible.isEmpty()
}
} ?: debugException { "Expected findViewTreeLifecycleOwner" }
}
}
}