more result bindings + player

This commit is contained in:
LagradOst 2023-07-18 03:55:00 +02:00
parent 4f28aef8f2
commit d5c42f7d5a
14 changed files with 1321 additions and 916 deletions

View file

@ -11,6 +11,8 @@ import com.lagradost.cloudstream3.databinding.FragmentHomeBinding
import com.lagradost.cloudstream3.databinding.FragmentHomeTvBinding
import com.lagradost.cloudstream3.databinding.FragmentPlayerBinding
import com.lagradost.cloudstream3.databinding.FragmentPlayerTvBinding
import com.lagradost.cloudstream3.databinding.FragmentResultBinding
import com.lagradost.cloudstream3.databinding.FragmentResultTvBinding
import com.lagradost.cloudstream3.databinding.FragmentSearchBinding
import com.lagradost.cloudstream3.databinding.FragmentSearchTvBinding
import com.lagradost.cloudstream3.databinding.HomeResultGridBinding
@ -22,6 +24,7 @@ import com.lagradost.cloudstream3.databinding.RepositoryItemBinding
import com.lagradost.cloudstream3.databinding.RepositoryItemTvBinding
import com.lagradost.cloudstream3.databinding.SearchResultGridBinding
import com.lagradost.cloudstream3.databinding.SearchResultGridExpandedBinding
import com.lagradost.cloudstream3.databinding.TrailerCustomLayoutBinding
import com.lagradost.cloudstream3.utils.SubtitleHelper
import com.lagradost.cloudstream3.utils.TestingUtils
import kotlinx.coroutines.runBlocking
@ -85,8 +88,12 @@ class ExampleInstrumentedTest {
testAllLayouts<FragmentPlayerBinding>(activity, R.layout.fragment_player,R.layout.fragment_player_tv)
testAllLayouts<FragmentPlayerTvBinding>(activity, R.layout.fragment_player,R.layout.fragment_player_tv)
testAllLayouts<PlayerCustomLayoutBinding>(activity, R.layout.player_custom_layout,R.layout.player_custom_layout_tv)
testAllLayouts<PlayerCustomLayoutTvBinding>(activity, R.layout.player_custom_layout,R.layout.player_custom_layout_tv)
// testAllLayouts<FragmentResultBinding>(activity, R.layout.fragment_result,R.layout.fragment_result_tv)
// testAllLayouts<FragmentResultTvBinding>(activity, R.layout.fragment_result,R.layout.fragment_result_tv)
testAllLayouts<PlayerCustomLayoutBinding>(activity, R.layout.player_custom_layout,R.layout.player_custom_layout_tv, R.layout.trailer_custom_layout)
testAllLayouts<PlayerCustomLayoutTvBinding>(activity, R.layout.player_custom_layout,R.layout.player_custom_layout_tv, R.layout.trailer_custom_layout)
testAllLayouts<TrailerCustomLayoutBinding>(activity, R.layout.player_custom_layout,R.layout.player_custom_layout_tv, R.layout.trailer_custom_layout)
testAllLayouts<RepositoryItemBinding>(activity, R.layout.repository_item_tv, R.layout.repository_item)
testAllLayouts<RepositoryItemTvBinding>(activity, R.layout.repository_item_tv, R.layout.repository_item)

View file

@ -92,7 +92,6 @@ class GeneratorPlayer : FullScreenPlayer() {
private var preferredAutoSelectSubtitles: String? = null // null means do nothing, "" means none
private var binding: FragmentPlayerBinding? = null
private var playerBinding: PlayerCustomLayoutBinding? = null
private fun startLoading() {
player.release()
@ -1236,15 +1235,11 @@ class GeneratorPlayer : FullScreenPlayer() {
unwrapBundle(arguments)
val root = super.onCreateView(inflater, container, savedInstanceState) ?: return null
binding = FragmentPlayerBinding.bind(root).also { b ->
playerBinding = PlayerCustomLayoutBinding.bind(b.playerView.findViewById(R.id.player_holder))
}
binding = FragmentPlayerBinding.bind(root)
return root
}
override fun onDestroyView() {
playerBinding = null
binding = null
super.onDestroyView()
}

View file

@ -4,26 +4,19 @@ import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.content.Intent.*
import android.content.res.ColorStateList
import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.text.Editable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AbsListView
import android.widget.ArrayAdapter
import android.widget.ImageView
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged
import androidx.lifecycle.ViewModelProvider
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.RecyclerView
import com.discord.panels.OverlappingPanelsLayout
import com.google.android.material.chip.Chip
import com.google.android.material.chip.ChipDrawable
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
@ -38,7 +31,6 @@ import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.mvvm.*
import com.lagradost.cloudstream3.services.SubscriptionWorkManager
import com.lagradost.cloudstream3.syncproviders.providers.Kitsu
import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.download.DOWNLOAD_ACTION_DOWNLOAD
import com.lagradost.cloudstream3.ui.download.DownloadButtonSetup.handleDownloadClick
import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment
@ -53,7 +45,6 @@ import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.DataStoreHelper.getVideoWatchState
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
@ -364,8 +355,9 @@ open class ResultFragment : ResultTrailerPlayer() {
return@setOnLongClickListener true
}
val show = viewModel.currentRepo?.api?.hasDownloadSupport == true && !isTvSettings()
if(show) {
val show =
viewModel.currentRepo?.api?.hasDownloadSupport == true && !isTvSettings()
if (show) {
download_button?.setDefaultClickListener(
VideoDownloadHelper.DownloadEpisodeCached(
ep.name,
@ -379,7 +371,6 @@ open class ResultFragment : ResultTrailerPlayer() {
System.currentTimeMillis(),
)
) { click ->
println("Click:$click")
when (click.action) {
DOWNLOAD_ACTION_DOWNLOAD -> {
viewModel.handleAction(
@ -393,7 +384,6 @@ open class ResultFragment : ResultTrailerPlayer() {
}
}
download_button?.isVisible = show
}
}
@ -593,42 +583,6 @@ open class ResultFragment : ResultTrailerPlayer() {
}
}
observe(viewModel.watchStatus) { watchType ->
result_bookmark_button?.text = getString(watchType.stringRes)
result_bookmark_fab?.text = getString(watchType.stringRes)
if (watchType == WatchType.NONE) {
result_bookmark_fab?.context?.colorFromAttribute(R.attr.white)
} else {
result_bookmark_fab?.context?.colorFromAttribute(R.attr.colorPrimary)
}?.let {
val colorState = ColorStateList.valueOf(it)
result_bookmark_fab?.iconTint = colorState
result_bookmark_fab?.setTextColor(colorState)
}
result_bookmark_fab?.setOnClickListener { fab ->
activity?.showBottomDialog(
WatchType.values().map { fab.context.getString(it.stringRes) }.toList(),
watchType.ordinal,
fab.context.getString(R.string.action_add_to_bookmarks),
showApply = false,
{}) {
viewModel.updateWatchStatus(WatchType.values()[it])
}
}
result_bookmark_button?.setOnClickListener { fab ->
activity?.showBottomDialog(
WatchType.values().map { fab.context.getString(it.stringRes) }.toList(),
watchType.ordinal,
fab.context.getString(R.string.action_add_to_bookmarks),
showApply = false,
{}) {
viewModel.updateWatchStatus(WatchType.values()[it])
}
}
}
// This is to band-aid FireTV navigation
val isTv = isTvSettings()
@ -636,152 +590,6 @@ open class ResultFragment : ResultTrailerPlayer() {
result_episode_select?.isFocusableInTouchMode = isTv
result_dub_select?.isFocusableInTouchMode = isTv
context?.let { ctx ->
val arrayAdapter = ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
/*
-1 -> None
0 -> Watching
1 -> Completed
2 -> OnHold
3 -> Dropped
4 -> PlanToWatch
5 -> ReWatching
*/
val items = listOf(
R.string.none,
R.string.type_watching,
R.string.type_completed,
R.string.type_on_hold,
R.string.type_dropped,
R.string.type_plan_to_watch,
R.string.type_re_watching
).map { ctx.getString(it) }
arrayAdapter.addAll(items)
result_sync_check?.choiceMode = AbsListView.CHOICE_MODE_SINGLE
result_sync_check?.adapter = arrayAdapter
UIHelper.setListViewHeightBasedOnItems(result_sync_check)
result_sync_check?.setOnItemClickListener { _, _, which, _ ->
syncModel.setStatus(which - 1)
}
result_sync_rating?.addOnChangeListener { _, value, _ ->
syncModel.setScore(value.toInt())
}
result_sync_add_episode?.setOnClickListener {
syncModel.setEpisodesDelta(1)
}
result_sync_sub_episode?.setOnClickListener {
syncModel.setEpisodesDelta(-1)
}
result_sync_current_episodes?.doOnTextChanged { text, _, before, count ->
if (count == before) return@doOnTextChanged
text?.toString()?.toIntOrNull()?.let { ep ->
syncModel.setEpisodes(ep)
}
}
}
observe(syncModel.synced) { list ->
result_sync_names?.text =
list.filter { it.isSynced && it.hasAccount }.joinToString { it.name }
val newList = list.filter { it.isSynced && it.hasAccount }
result_mini_sync?.isVisible = newList.isNotEmpty()
(result_mini_sync?.adapter as? ImageAdapter)?.updateList(newList.mapNotNull { it.icon })
}
var currentSyncProgress = 0
fun setSyncMaxEpisodes(totalEpisodes: Int?) {
result_sync_episodes?.max = (totalEpisodes ?: 0) * 1000
normalSafeApiCall {
val ctx = result_sync_max_episodes?.context
result_sync_max_episodes?.text =
totalEpisodes?.let { episodes ->
ctx?.getString(R.string.sync_total_episodes_some)?.format(episodes)
} ?: run {
ctx?.getString(R.string.sync_total_episodes_none)
}
}
}
observe(syncModel.metadata) { meta ->
when (meta) {
is Resource.Success -> {
val d = meta.value
result_sync_episodes?.progress = currentSyncProgress * 1000
setSyncMaxEpisodes(d.totalEpisodes)
viewModel.setMeta(d, syncModel.getSyncs())
}
is Resource.Loading -> {
result_sync_max_episodes?.text =
result_sync_max_episodes?.context?.getString(R.string.sync_total_episodes_none)
}
else -> {}
}
}
observe(syncModel.userData) { status ->
var closed = false
when (status) {
is Resource.Failure -> {
result_sync_loading_shimmer?.stopShimmer()
result_sync_loading_shimmer?.isVisible = false
result_sync_holder?.isVisible = false
closed = true
}
is Resource.Loading -> {
result_sync_loading_shimmer?.startShimmer()
result_sync_loading_shimmer?.isVisible = true
result_sync_holder?.isVisible = false
}
is Resource.Success -> {
result_sync_loading_shimmer?.stopShimmer()
result_sync_loading_shimmer?.isVisible = false
result_sync_holder?.isVisible = true
val d = status.value
result_sync_rating?.value = d.score?.toFloat() ?: 0.0f
result_sync_check?.setItemChecked(d.status + 1, true)
val watchedEpisodes = d.watchedEpisodes ?: 0
currentSyncProgress = watchedEpisodes
d.maxEpisodes?.let {
// don't directly call it because we don't want to override metadata observe
setSyncMaxEpisodes(it)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
result_sync_episodes?.setProgress(watchedEpisodes * 1000, true)
} else {
result_sync_episodes?.progress = watchedEpisodes * 1000
}
result_sync_current_episodes?.text =
Editable.Factory.getInstance()?.newEditable(watchedEpisodes.toString())
normalSafeApiCall { // format might fail
context?.getString(R.string.sync_score_format)?.format(d.score ?: 0)?.let {
result_sync_score_text?.text = it
}
}
}
null -> {
closed = false
}
}
result_overlapping_panels?.setStartPanelLockState(if (closed) OverlappingPanelsLayout.LockState.CLOSE else OverlappingPanelsLayout.LockState.UNLOCKED)
}
observeNullable(viewModel.resumeWatching) { resume ->
if (resume == null) {
@ -841,10 +649,6 @@ open class ResultFragment : ResultTrailerPlayer() {
if (hasFocus) result_bookmark_button?.requestFocus()
}
result_sync_set_score?.setOnClickListener {
syncModel.publishUserData()
}
observe(viewModel.trailers) { trailers ->
setTrailers(trailers.flatMap { it.mirros }) // I dont care about subtitles yet!
}

View file

@ -1,28 +1,47 @@
package com.lagradost.cloudstream3.ui.result
import android.app.Dialog
import android.content.res.ColorStateList
import android.graphics.Rect
import android.os.Build
import android.os.Bundle
import android.text.Editable
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.animation.AlphaAnimation
import android.view.animation.Animation
import android.view.animation.DecelerateInterpolator
import android.widget.AbsListView
import android.widget.ArrayAdapter
import android.widget.Toast
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.widget.NestedScrollView
import androidx.core.widget.doOnTextChanged
import com.discord.panels.OverlappingPanelsLayout
import com.discord.panels.PanelsChildGestureRegionObserver
import com.google.android.gms.cast.framework.CastButtonFactory
import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.CastState
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder
import com.lagradost.cloudstream3.APIHolder.updateHasTrailers
import com.lagradost.cloudstream3.CommonActivity
import com.lagradost.cloudstream3.DubStatus
import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.databinding.FragmentResultBinding
import com.lagradost.cloudstream3.databinding.FragmentResultSwipeBinding
import com.lagradost.cloudstream3.databinding.ResultRecommendationsBinding
import com.lagradost.cloudstream3.databinding.ResultSyncBinding
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.mvvm.observeNullable
import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.player.CSPlayerEvent
import com.lagradost.cloudstream3.ui.search.SearchAdapter
import com.lagradost.cloudstream3.ui.search.SearchHelper
@ -32,25 +51,35 @@ import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showDialog
import com.lagradost.cloudstream3.utils.UIHelper
import com.lagradost.cloudstream3.utils.UIHelper.colorFromAttribute
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
import kotlinx.android.synthetic.main.fragment_result.*
import kotlinx.android.synthetic.main.fragment_result.result_cast_items
import kotlinx.android.synthetic.main.fragment_result.result_episodes_text
import kotlinx.android.synthetic.main.fragment_result.result_resume_parent
import kotlinx.android.synthetic.main.fragment_result.result_scroll
import kotlinx.android.synthetic.main.fragment_result.result_smallscreen_holder
import kotlinx.android.synthetic.main.fragment_result_swipe.*
import kotlinx.android.synthetic.main.fragment_result_swipe.result_back
import kotlinx.android.synthetic.main.fragment_result_tv.*
import kotlinx.android.synthetic.main.fragment_trailer.*
import kotlinx.android.synthetic.main.result_recommendations.*
import kotlinx.android.synthetic.main.result_recommendations.result_recommendations
import kotlinx.android.synthetic.main.trailer_custom_layout.*
class ResultFragmentPhone : ResultFragment() {
private var binding: FragmentResultSwipeBinding? = null
private var resultBinding: FragmentResultBinding? = null
private var recommendationBinding: ResultRecommendationsBinding? = null
private var syncBinding: ResultSyncBinding? = null
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val root = super.onCreateView(inflater, container, savedInstanceState) ?: return null
FragmentResultSwipeBinding.bind(root).let { bind ->
resultBinding =
bind.fragmentResult//FragmentResultBinding.bind(binding.root.findViewById(R.id.fragment_result))
recommendationBinding = bind.resultRecommendations
syncBinding = bind.resultSync
binding = bind
}
return root
}
var currentTrailers: List<ExtractorLink> = emptyList()
var currentTrailerIndex = 0
@ -96,19 +125,30 @@ class ResultFragmentPhone : ResultFragment() {
//result_trailer_thumbnail?.setImageBitmap(result_poster_background?.drawable?.toBitmap())
result_trailer_loading?.isVisible = isSuccess
// result_trailer_loading?.isVisible = isSuccess
val turnVis = !isSuccess && !isFullScreenPlayer
result_smallscreen_holder?.isVisible = turnVis
result_poster_background_holder?.apply {
val fadeIn: Animation = AlphaAnimation(alpha, if (turnVis) 1.0f else 0.0f).apply {
interpolator = DecelerateInterpolator()
duration = 200
fillAfter = true
resultBinding?.apply {
resultSmallscreenHolder.isVisible = turnVis
resultPosterBackgroundHolder.apply {
val fadeIn: Animation = AlphaAnimation(alpha, if (turnVis) 1.0f else 0.0f).apply {
interpolator = DecelerateInterpolator()
duration = 200
fillAfter = true
}
clearAnimation()
startAnimation(fadeIn)
}
clearAnimation()
startAnimation(fadeIn)
// We don't want the trailer to be focusable if it's not visible
resultSmallscreenHolder.descendantFocusability = if (isSuccess) {
ViewGroup.FOCUS_AFTER_DESCENDANTS
} else {
ViewGroup.FOCUS_BLOCK_DESCENDANTS
}
binding?.resultFullscreenHolder?.isVisible = !isSuccess && isFullScreenPlayer
}
//player_view?.apply {
//alpha = 0.0f
//ObjectAnimator.ofFloat(player_view, "alpha", 1f).apply {
@ -124,13 +164,7 @@ class ResultFragmentPhone : ResultFragment() {
//startAnimation(fadeIn)
// }
// We don't want the trailer to be focusable if it's not visible
result_smallscreen_holder?.descendantFocusability = if (isSuccess) {
ViewGroup.FOCUS_AFTER_DESCENDANTS
} else {
ViewGroup.FOCUS_BLOCK_DESCENDANTS
}
result_fullscreen_holder?.isVisible = !isSuccess && isFullScreenPlayer
}
override fun setTrailers(trailers: List<ExtractorLink>?) {
@ -144,12 +178,15 @@ class ResultFragmentPhone : ResultFragment() {
//somehow this still leaks and I dont know why????
// todo look at https://github.com/discord/OverlappingPanels/blob/70b4a7cf43c6771873b1e091029d332896d41a1a/sample_app/src/main/java/com/discord/sampleapp/MainActivity.kt
PanelsChildGestureRegionObserver.Provider.get().let { obs ->
result_cast_items?.let {
resultBinding?.resultCastItems?.let {
obs.unregister(it)
}
obs.removeGestureRegionsUpdateListener(this)
}
binding = null
resultBinding = null
syncBinding = null
recommendationBinding = null
super.onDestroyView()
}
@ -173,30 +210,35 @@ class ResultFragmentPhone : ResultFragment() {
super.onViewCreated(view, savedInstanceState)
player_open_source?.setOnClickListener {
playerBinding?.playerOpenSource?.setOnClickListener {
currentTrailers.getOrNull(currentTrailerIndex)?.let {
context?.openBrowser(it.url)
}
}
result_overlapping_panels?.setStartPanelLockState(OverlappingPanelsLayout.LockState.CLOSE)
result_overlapping_panels?.setEndPanelLockState(OverlappingPanelsLayout.LockState.CLOSE)
binding?.resultOverlappingPanels?.setStartPanelLockState(OverlappingPanelsLayout.LockState.CLOSE)
binding?.resultOverlappingPanels?.setEndPanelLockState(OverlappingPanelsLayout.LockState.CLOSE)
recommendationBinding?.resultRecommendationsList?.apply {
spanCount = 3
adapter =
SearchAdapter(
ArrayList(),
this,
) { callback ->
SearchHelper.handleSearchClickCallback(callback)
}
}
result_recommendations?.spanCount = 3
result_recommendations?.adapter =
SearchAdapter(
ArrayList(),
result_recommendations,
) { callback ->
SearchHelper.handleSearchClickCallback(callback)
}
PanelsChildGestureRegionObserver.Provider.get().addGestureRegionsUpdateListener(this)
result_cast_items?.let {
resultBinding?.resultCastItems?.let {
PanelsChildGestureRegionObserver.Provider.get().register(it)
}
result_back?.setOnClickListener {
binding?.resultBack?.setOnClickListener {
activity?.popCurrentPage()
}
@ -211,28 +253,30 @@ class ResultFragmentPhone : ResultFragment() {
}
}*/
result_mini_sync?.adapter = ImageAdapter(
binding?.resultMiniSync?.adapter = ImageAdapter(
nextFocusDown = R.id.result_sync_set_score,
clickCallback = { action ->
if (action == IMAGE_CLICK || action == IMAGE_LONG_CLICK) {
if (result_overlapping_panels?.getSelectedPanel()?.ordinal == 1) {
result_overlapping_panels?.openStartPanel()
if (binding?.resultOverlappingPanels?.getSelectedPanel()?.ordinal == 1) {
binding?.resultOverlappingPanels?.openStartPanel()
} else {
result_overlapping_panels?.closePanels()
binding?.resultOverlappingPanels?.closePanels()
}
}
})
result_scroll?.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
resultBinding?.resultScroll?.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
val dy = scrollY - oldScrollY
if (dy > 0) { //check for scroll down
result_bookmark_fab?.shrink()
binding?.resultBookmarkFab?.shrink()
} else if (dy < -5) {
result_bookmark_fab?.extend()
binding?.resultBookmarkFab?.extend()
}
if (!isFullScreenPlayer && player.getIsPlaying()) {
if (scrollY > (player_background?.height ?: scrollY)) {
if (scrollY > (resultBinding?.fragmentTrailer?.playerBackground?.height
?: scrollY)
) {
player.handleEvent(CSPlayerEvent.Pause)
}
}
@ -240,11 +284,11 @@ class ResultFragmentPhone : ResultFragment() {
})
val api = APIHolder.getApiFromNameNull(apiName)
if (media_route_button != null) {
binding?.mediaRouteButton?.apply {
val chromecastSupport = api?.hasChromecastSupport == true
media_route_button?.alpha = if (chromecastSupport) 1f else 0.3f
alpha = if (chromecastSupport) 1f else 0.3f
if (!chromecastSupport) {
media_route_button?.setOnClickListener {
setOnClickListener {
CommonActivity.showToast(
R.string.no_chromecast_support_toast,
Toast.LENGTH_LONG
@ -254,10 +298,9 @@ class ResultFragmentPhone : ResultFragment() {
activity?.let { act ->
if (act.isCastApiAvailable()) {
try {
CastButtonFactory.setUpMediaRouteButton(act, media_route_button)
CastButtonFactory.setUpMediaRouteButton(act, this)
val castContext = CastContext.getSharedInstance(act.applicationContext)
media_route_button?.isGone =
castContext.castState == CastState.NO_DEVICES_AVAILABLE
isGone = castContext.castState == CastState.NO_DEVICES_AVAILABLE
// this shit leaks for some reason
//castContext.addCastStateListener { state ->
// media_route_button?.isGone = state == CastState.NO_DEVICES_AVAILABLE
@ -270,7 +313,7 @@ class ResultFragmentPhone : ResultFragment() {
}
observeNullable(viewModel.episodesCountText) { count ->
result_episodes_text.setText(count)
resultBinding?.resultEpisodesText.setText(count)
}
observeNullable(viewModel.selectPopup) { popup ->
@ -295,10 +338,189 @@ class ResultFragmentPhone : ResultFragment() {
}
)
}
}
observe(syncModel.synced) { list ->
syncBinding?.resultSyncNames?.text =
list.filter { it.isSynced && it.hasAccount }.joinToString { it.name }
val newList = list.filter { it.isSynced && it.hasAccount }
binding?.resultMiniSync?.isVisible = newList.isNotEmpty()
(binding?.resultMiniSync?.adapter as? ImageAdapter)?.updateList(newList.mapNotNull { it.icon })
}
var currentSyncProgress = 0
fun setSyncMaxEpisodes(totalEpisodes: Int?) {
syncBinding?.resultSyncEpisodes?.max = (totalEpisodes ?: 0) * 1000
normalSafeApiCall {
val ctx = syncBinding?.resultSyncEpisodes?.context
syncBinding?.resultSyncMaxEpisodes?.text =
totalEpisodes?.let { episodes ->
ctx?.getString(R.string.sync_total_episodes_some)?.format(episodes)
} ?: run {
ctx?.getString(R.string.sync_total_episodes_none)
}
}
}
observe(syncModel.metadata) { meta ->
when (meta) {
is Resource.Success -> {
val d = meta.value
syncBinding?.resultSyncEpisodes?.progress = currentSyncProgress * 1000
setSyncMaxEpisodes(d.totalEpisodes)
viewModel.setMeta(d, syncModel.getSyncs())
}
is Resource.Loading -> {
syncBinding?.resultSyncMaxEpisodes?.text =
syncBinding?.resultSyncMaxEpisodes?.context?.getString(R.string.sync_total_episodes_none)
}
else -> {}
}
}
observe(syncModel.userData) { status ->
var closed = false
syncBinding?.apply {
when (status) {
is Resource.Failure -> {
resultSyncLoadingShimmer.stopShimmer()
resultSyncLoadingShimmer.isVisible = false
resultSyncHolder.isVisible = false
closed = true
}
is Resource.Loading -> {
resultSyncLoadingShimmer.startShimmer()
resultSyncLoadingShimmer.isVisible = true
resultSyncHolder.isVisible = false
}
is Resource.Success -> {
resultSyncLoadingShimmer.stopShimmer()
resultSyncLoadingShimmer.isVisible = false
resultSyncHolder.isVisible = true
val d = status.value
resultSyncRating.value = d.score?.toFloat() ?: 0.0f
resultSyncCheck.setItemChecked(d.status + 1, true)
val watchedEpisodes = d.watchedEpisodes ?: 0
currentSyncProgress = watchedEpisodes
d.maxEpisodes?.let {
// don't directly call it because we don't want to override metadata observe
setSyncMaxEpisodes(it)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
resultSyncEpisodes.setProgress(watchedEpisodes * 1000, true)
} else {
resultSyncEpisodes.progress = watchedEpisodes * 1000
}
resultSyncCurrentEpisodes.text =
Editable.Factory.getInstance()?.newEditable(watchedEpisodes.toString())
normalSafeApiCall { // format might fail
context?.getString(R.string.sync_score_format)?.format(d.score ?: 0)
?.let {
resultSyncScoreText.text = it
}
}
}
null -> {
closed = false
}
}
}
binding?.resultOverlappingPanels?.setStartPanelLockState(if (closed) OverlappingPanelsLayout.LockState.CLOSE else OverlappingPanelsLayout.LockState.UNLOCKED)
}
context?.let { ctx ->
val arrayAdapter = ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
/*
-1 -> None
0 -> Watching
1 -> Completed
2 -> OnHold
3 -> Dropped
4 -> PlanToWatch
5 -> ReWatching
*/
val items = listOf(
R.string.none,
R.string.type_watching,
R.string.type_completed,
R.string.type_on_hold,
R.string.type_dropped,
R.string.type_plan_to_watch,
R.string.type_re_watching
).map { ctx.getString(it) }
arrayAdapter.addAll(items)
syncBinding?.apply {
resultSyncCheck.choiceMode = AbsListView.CHOICE_MODE_SINGLE
resultSyncCheck.adapter = arrayAdapter
UIHelper.setListViewHeightBasedOnItems(resultSyncCheck)
resultSyncCheck.setOnItemClickListener { _, _, which, _ ->
syncModel.setStatus(which - 1)
}
resultSyncRating.addOnChangeListener { _, value, _ ->
syncModel.setScore(value.toInt())
}
resultSyncAddEpisode.setOnClickListener {
syncModel.setEpisodesDelta(1)
}
resultSyncSubEpisode.setOnClickListener {
syncModel.setEpisodesDelta(-1)
}
resultSyncCurrentEpisodes.doOnTextChanged { text, _, before, count ->
if (count == before) return@doOnTextChanged
text?.toString()?.toIntOrNull()?.let { ep ->
syncModel.setEpisodes(ep)
}
}
}
}
syncBinding?.resultSyncSetScore?.setOnClickListener {
syncModel.publishUserData()
}
observe(viewModel.watchStatus) { watchType ->
binding?.resultBookmarkFab?.apply {
if (watchType == WatchType.NONE) {
context?.colorFromAttribute(R.attr.white)
} else {
context?.colorFromAttribute(R.attr.colorPrimary)
}?.let {
val colorState = ColorStateList.valueOf(it)
iconTint = colorState
setTextColor(colorState)
}
setOnClickListener { fab ->
activity?.showBottomDialog(
WatchType.values().map { fab.context.getString(it.stringRes) }.toList(),
watchType.ordinal,
fab.context.getString(R.string.action_add_to_bookmarks),
showApply = false,
{}) {
viewModel.updateWatchStatus(WatchType.values()[it])
}
}
}
}
observe(viewModel.loadedLinks) { load ->
if (load == null) {
loadingDialog?.dismissSafe(activity)
@ -327,46 +549,41 @@ class ResultFragmentPhone : ResultFragment() {
}
observeNullable(viewModel.selectedSeason) { text ->
result_season_button.setText(text)
resultBinding?.apply {
resultSeasonButton.setText(text)
selectSeason =
text?.asStringNull(result_season_button?.context)
// If the season button is visible the result season button will be next focus down
if (result_season_button?.isVisible == true)
if (result_resume_parent?.isVisible == true)
setFocusUpAndDown(result_resume_series_button, result_season_button)
//else
// setFocusUpAndDown(result_bookmark_button, result_season_button)
selectSeason =
text?.asStringNull(resultSeasonButton.context)
// If the season button is visible the result season button will be next focus down
if (resultSeasonButton.isVisible && resultResumeParent.isVisible) {
setFocusUpAndDown(resultResumeSeriesButton, resultSeasonButton)
}
}
}
observeNullable(viewModel.selectedDubStatus) { status ->
result_dub_select?.setText(status)
resultBinding?.apply {
resultDubSelect.setText(status)
if (result_dub_select?.isVisible == true)
if (result_season_button?.isVisible != true && result_episode_select?.isVisible != true) {
if (result_resume_parent?.isVisible == true)
setFocusUpAndDown(result_resume_series_button, result_dub_select)
//else
// setFocusUpAndDown(result_bookmark_button, result_dub_select)
if (resultDubSelect.isVisible && !resultSeasonButton.isVisible && !resultEpisodeSelect.isVisible && resultResumeParent.isVisible) {
setFocusUpAndDown(resultResumeSeriesButton, resultDubSelect)
}
}
}
observeNullable(viewModel.selectedRange) { range ->
result_episode_select.setText(range)
// If Season button is invisible then the bookmark button next focus is episode select
if (result_episode_select?.isVisible == true)
if (result_season_button?.isVisible != true) {
if (result_resume_parent?.isVisible == true)
setFocusUpAndDown(result_resume_series_button, result_episode_select)
//else
// setFocusUpAndDown(result_bookmark_button, result_episode_select)
resultBinding?.apply {
resultEpisodeSelect.setText(range)
// If Season button is invisible then the bookmark button next focus is episode select
if (resultEpisodeSelect.isVisible && !resultSeasonButton.isVisible && resultResumeParent.isVisible) {
setFocusUpAndDown(resultResumeSeriesButton, resultEpisodeSelect)
}
}
}
// val preferDub = context?.getApiDubstatusSettings()?.all { it == DubStatus.Dubbed } == true
observe(viewModel.dubSubSelections) { range ->
result_dub_select.setOnClickListener { view ->
resultBinding?.resultDubSelect?.setOnClickListener { view ->
view?.context?.let { ctx ->
view.popupMenuNoIconsAndNoStringRes(range
.mapNotNull { (text, status) ->
@ -382,7 +599,7 @@ class ResultFragmentPhone : ResultFragment() {
}
observe(viewModel.rangeSelections) { range ->
result_episode_select?.setOnClickListener { view ->
resultBinding?.resultEpisodeSelect?.setOnClickListener { view ->
view?.context?.let { ctx ->
val names = range
.mapNotNull { (text, r) ->
@ -399,7 +616,7 @@ class ResultFragmentPhone : ResultFragment() {
}
observe(viewModel.seasonSelections) { seasonList ->
result_season_button?.setOnClickListener { view ->
resultBinding?.resultSeasonButton?.setOnClickListener { view ->
view?.context?.let { ctx ->
val names = seasonList
@ -433,50 +650,58 @@ class ResultFragmentPhone : ResultFragment() {
}
override fun onGestureRegionsUpdate(gestureRegions: List<Rect>) {
result_overlapping_panels?.setChildGestureRegions(gestureRegions)
binding?.resultOverlappingPanels?.setChildGestureRegions(gestureRegions)
}
override fun setRecommendations(rec: List<SearchResponse>?, validApiName: String?) {
val isInvalid = rec.isNullOrEmpty()
result_recommendations?.isGone = isInvalid
result_recommendations_btt?.isGone = isInvalid
result_recommendations_btt?.setOnClickListener {
val nextFocusDown = if (result_overlapping_panels?.getSelectedPanel()?.ordinal == 1) {
result_overlapping_panels?.openEndPanel()
R.id.result_recommendations
} else {
result_overlapping_panels?.closePanels()
R.id.result_description
}
result_recommendations_btt?.nextFocusDownId = nextFocusDown
result_search?.nextFocusDownId = nextFocusDown
result_open_in_browser?.nextFocusDownId = nextFocusDown
result_share?.nextFocusDownId = nextFocusDown
}
result_overlapping_panels?.setEndPanelLockState(if (isInvalid) OverlappingPanelsLayout.LockState.CLOSE else OverlappingPanelsLayout.LockState.UNLOCKED)
val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName
rec?.map { it.apiName }?.distinct()?.let { apiNames ->
// very dirty selection
result_recommendations_filter_button?.isVisible = apiNames.size > 1
result_recommendations_filter_button?.text = matchAgainst
result_recommendations_filter_button?.setOnClickListener { _ ->
activity?.showBottomDialog(
apiNames,
apiNames.indexOf(matchAgainst),
getString(R.string.home_change_provider_img_des), false, {}
) {
setRecommendations(rec, apiNames[it])
recommendationBinding?.apply {
root.isGone = isInvalid
root.post {
rec?.let { list ->
(resultRecommendationsList.adapter as? SearchAdapter)?.updateList(list.filter { it.apiName == matchAgainst })
}
}
} ?: run {
result_recommendations_filter_button?.isVisible = false
}
result_recommendations?.post {
rec?.let { list ->
(result_recommendations?.adapter as? SearchAdapter)?.updateList(list.filter { it.apiName == matchAgainst })
binding?.apply {
resultRecommendationsBtt.isGone = isInvalid
resultRecommendationsBtt.setOnClickListener {
val nextFocusDown = if (resultOverlappingPanels.getSelectedPanel().ordinal == 1) {
resultOverlappingPanels.openEndPanel()
R.id.result_recommendations
} else {
resultOverlappingPanels.closePanels()
R.id.result_description
}
resultBinding?.apply {
resultRecommendationsBtt.nextFocusDownId = nextFocusDown
resultSearch.nextFocusDownId = nextFocusDown
resultOpenInBrowser.nextFocusDownId = nextFocusDown
resultShare.nextFocusDownId = nextFocusDown
}
}
resultOverlappingPanels.setEndPanelLockState(if (isInvalid) OverlappingPanelsLayout.LockState.CLOSE else OverlappingPanelsLayout.LockState.UNLOCKED)
rec?.map { it.apiName }?.distinct()?.let { apiNames ->
// very dirty selection
recommendationBinding?.resultRecommendationsFilterButton?.apply {
isVisible = apiNames.size > 1
text = matchAgainst
setOnClickListener { _ ->
activity?.showBottomDialog(
apiNames,
apiNames.indexOf(matchAgainst),
getString(R.string.home_change_provider_img_des), false, {}
) {
setRecommendations(rec, apiNames[it])
}
}
}
} ?: run {
recommendationBinding?.resultRecommendationsFilterButton?.isVisible = false
}
}
}

View file

@ -1,8 +1,11 @@
package com.lagradost.cloudstream3.ui.result
import android.app.Dialog
import android.content.res.ColorStateList
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
@ -12,23 +15,43 @@ import com.lagradost.cloudstream3.DubStatus
import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.databinding.FragmentResultTvBinding
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.mvvm.observeNullable
import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.player.ExtractorLinkGenerator
import com.lagradost.cloudstream3.ui.player.GeneratorPlayer
import com.lagradost.cloudstream3.ui.search.SearchAdapter
import com.lagradost.cloudstream3.ui.search.SearchHelper
import com.lagradost.cloudstream3.utils.ExtractorLink
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialogInstant
import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.UIHelper.popCurrentPage
import kotlinx.android.synthetic.main.fragment_result_tv.*
class ResultFragmentTv : ResultFragment() {
override val resultLayout = R.layout.fragment_result_tv
private var binding: FragmentResultTvBinding? = null
override fun onDestroyView() {
binding = null
super.onDestroyView()
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val root = super.onCreateView(inflater, container, savedInstanceState) ?: return null
binding = FragmentResultTvBinding.bind(root)
return root
}
private var currentRecommendations: List<SearchResponse> = emptyList()
private fun handleSelection(data: Any) {
@ -36,12 +59,15 @@ class ResultFragmentTv : ResultFragment() {
is EpisodeRange -> {
viewModel.changeRange(data)
}
is Int -> {
viewModel.changeSeason(data)
}
is DubStatus -> {
viewModel.changeDubStatus(data)
}
is String -> {
setRecommendations(currentRecommendations, data)
}
@ -66,57 +92,60 @@ class ResultFragmentTv : ResultFragment() {
private fun hasNoFocus(): Boolean {
val focus = activity?.currentFocus
if (focus == null || !focus.isVisible) return true
return focus == this.result_root
return focus == binding?.resultRoot
}
override fun updateEpisodes(episodes: Resource<List<ResultEpisode>>?) {
super.updateEpisodes(episodes)
if (episodes is Resource.Success && hasNoFocus()) {
result_episodes?.requestFocus()
binding?.resultEpisodes?.requestFocus()
}
}
override fun updateMovie(data: Resource<Pair<UiText, ResultEpisode>>?) {
super.updateMovie(data)
if (data is Resource.Success && hasNoFocus()) {
result_play_movie?.requestFocus()
binding?.resultPlayMovie?.requestFocus()
}
}
override fun setTrailers(trailers: List<ExtractorLink>?) {
context?.updateHasTrailers()
if (!LoadResponse.isTrailersEnabled) return
result_play_trailer?.isGone = trailers.isNullOrEmpty()
result_play_trailer?.setOnClickListener {
if (trailers.isNullOrEmpty()) return@setOnClickListener
activity.navigate(
R.id.global_to_navigation_player, GeneratorPlayer.newInstance(
ExtractorLinkGenerator(
trailers,
emptyList()
binding?.resultPlayTrailer?.apply {
isGone = trailers.isNullOrEmpty()
setOnClickListener {
if (trailers.isNullOrEmpty()) return@setOnClickListener
activity.navigate(
R.id.global_to_navigation_player, GeneratorPlayer.newInstance(
ExtractorLinkGenerator(
trailers,
emptyList()
)
)
)
)
}
}
}
override fun setRecommendations(rec: List<SearchResponse>?, validApiName: String?) {
currentRecommendations = rec ?: emptyList()
val isInvalid = rec.isNullOrEmpty()
result_recommendations?.isGone = isInvalid
result_recommendations_holder?.isGone = isInvalid
val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName
(result_recommendations?.adapter as? SearchAdapter)?.updateList(rec?.filter { it.apiName == matchAgainst }
?: emptyList())
binding?.apply {
resultRecommendationsList.isGone = isInvalid
resultRecommendationsHolder.isGone = isInvalid
val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName
(resultRecommendationsList.adapter as? SearchAdapter)?.updateList(rec?.filter { it.apiName == matchAgainst }
?: emptyList())
rec?.map { it.apiName }?.distinct()?.let { apiNames ->
// very dirty selection
result_recommendations_filter_selection?.isVisible = apiNames.size > 1
result_recommendations_filter_selection?.update(apiNames.map { txt(it) to it })
result_recommendations_filter_selection?.select(apiNames.indexOf(matchAgainst))
} ?: run {
result_recommendations_filter_selection?.isVisible = false
rec?.map { it.apiName }?.distinct()?.let { apiNames ->
// very dirty selection
resultRecommendationsFilterSelection.isVisible = apiNames.size > 1
resultRecommendationsFilterSelection.update(apiNames.map { txt(it) to it })
resultRecommendationsFilterSelection.select(apiNames.indexOf(matchAgainst))
} ?: run {
resultRecommendationsFilterSelection.isVisible = false
}
}
}
@ -124,24 +153,37 @@ class ResultFragmentTv : ResultFragment() {
var popupDialog: Dialog? = null
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding?.apply {
resultEpisodes.layoutManager =
LinearListLayout(resultEpisodes.context).apply {
setHorizontal()
}
result_episodes?.layoutManager =
//LinearListLayout(result_episodes ?: return, result_episodes?.context).apply {
LinearListLayout(result_episodes?.context).apply {
setHorizontal()
resultSeasonSelection.setAdapter()
resultRangeSelection.setAdapter()
resultDubSelection.setAdapter()
resultRecommendationsFilterSelection.setAdapter()
}
observe(viewModel.watchStatus) { watchType ->
binding?.resultBookmarkButton?.apply {
setText(watchType.stringRes)
setOnClickListener { view ->
activity?.showBottomDialog(
WatchType.values().map { view.context.getString(it.stringRes) }.toList(),
watchType.ordinal,
view.context.getString(R.string.action_add_to_bookmarks),
showApply = false,
{}) {
viewModel.updateWatchStatus(WatchType.values()[it])
}
}
}
// (result_episodes?.adapter as EpisodeAdapter?)?.apply {
// layout = R.layout.result_episode_both_tv
// }
//result_episodes?.setMaxViewPoolSize(0, Int.MAX_VALUE)
}
result_season_selection.setAdapter()
result_range_selection.setAdapter()
result_dub_selection.setAdapter()
result_recommendations_filter_selection.setAdapter()
observeNullable(viewModel.selectPopup) { popup ->
if(popup == null) {
if (popup == null) {
popupDialog?.dismissSafe(activity)
popupDialog = null
return@observeNullable
@ -166,67 +208,70 @@ class ResultFragmentTv : ResultFragment() {
}
observeNullable(viewModel.loadedLinks) { load ->
if(load == null) {
if (load == null) {
loadingDialog?.dismissSafe(activity)
loadingDialog = null
return@observeNullable
}
if (loadingDialog?.isShowing != true) {
loadingDialog?.dismissSafe(activity)
loadingDialog = null
}
loadingDialog = loadingDialog ?: context?.let { ctx ->
val builder = BottomSheetDialog(ctx)
builder.setContentView(R.layout.bottom_loading)
builder.setOnDismissListener {
loadingDialog = null
viewModel.cancelLinks()
}
//builder.setOnCancelListener {
// it?.dismiss()
//}
builder.setCanceledOnTouchOutside(true)
builder.show()
builder
}
if (loadingDialog?.isShowing != true) {
loadingDialog?.dismissSafe(activity)
loadingDialog = null
}
loadingDialog = loadingDialog ?: context?.let { ctx ->
val builder = BottomSheetDialog(ctx)
builder.setContentView(R.layout.bottom_loading)
builder.setOnDismissListener {
loadingDialog = null
viewModel.cancelLinks()
}
//builder.setOnCancelListener {
// it?.dismiss()
//}
builder.setCanceledOnTouchOutside(true)
builder.show()
builder
}
}
observeNullable(viewModel.episodesCountText) { count ->
result_episodes_text.setText(count)
binding?.resultEpisodesText.setText(count)
}
observe(viewModel.selectedRangeIndex) { selected ->
result_range_selection.select(selected)
binding?.resultRangeSelection.select(selected)
}
observe(viewModel.selectedSeasonIndex) { selected ->
result_season_selection.select(selected)
binding?.resultSeasonSelection.select(selected)
}
observe(viewModel.selectedDubStatusIndex) { selected ->
result_dub_selection.select(selected)
binding?.resultDubSelection.select(selected)
}
observe(viewModel.rangeSelections) {
result_range_selection.update(it)
binding?.resultRangeSelection.update(it)
}
observe(viewModel.dubSubSelections) {
result_dub_selection.update(it)
binding?.resultDubSelection.update(it)
}
observe(viewModel.seasonSelections) {
result_season_selection.update(it)
binding?.resultSeasonSelection.update(it)
}
result_back?.setOnClickListener {
activity?.popCurrentPage()
}
result_recommendations?.spanCount = 8
result_recommendations?.adapter =
SearchAdapter(
ArrayList(),
result_recommendations,
) { callback ->
SearchHelper.handleSearchClickCallback(callback)
binding?.apply {
resultBack.setOnClickListener {
activity?.popCurrentPage()
}
resultRecommendationsList.spanCount = 8
resultRecommendationsList.adapter =
SearchAdapter(
ArrayList(),
resultRecommendationsList,
) { callback ->
SearchHelper.handleSearchClickCallback(callback)
}
}
}
}

View file

@ -13,6 +13,7 @@ import androidx.core.view.isVisible
import com.discord.panels.PanelsChildGestureRegionObserver
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.ui.player.CSPlayerEvent
import com.lagradost.cloudstream3.ui.player.FullScreenPlayer
import com.lagradost.cloudstream3.ui.player.SubtitleData
import com.lagradost.cloudstream3.utils.IOnBackPressed
import kotlinx.android.synthetic.main.fragment_result.*
@ -20,10 +21,9 @@ import kotlinx.android.synthetic.main.fragment_result.result_smallscreen_holder
import kotlinx.android.synthetic.main.fragment_result_swipe.*
import kotlinx.android.synthetic.main.fragment_result_tv.*
import kotlinx.android.synthetic.main.fragment_trailer.*
import kotlinx.android.synthetic.main.trailer_custom_layout.*
open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreenPlayer(),
open class ResultTrailerPlayer : FullScreenPlayer(),
PanelsChildGestureRegionObserver.GestureRegionsListener, IOnBackPressed {
override var lockRotation = false
@ -75,7 +75,7 @@ open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreen
)
}
player_intro_play?.apply {
playerBinding?.playerIntroPlay?.apply {
layoutParams =
FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT,
@ -83,7 +83,7 @@ open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreen
)
}
if (player_intro_play?.isGone == true) {
if (playerBinding?.playerIntroPlay?.isGone == true) {
result_top_holder?.apply {
val anim = ValueAnimator.ofInt(
@ -131,7 +131,8 @@ open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreen
private fun updateFullscreen(fullscreen: Boolean) {
isFullScreenPlayer = fullscreen
lockRotation = fullscreen
player_fullscreen?.setImageResource(if (fullscreen) R.drawable.baseline_fullscreen_exit_24 else R.drawable.baseline_fullscreen_24)
playerBinding?.playerFullscreen?.setImageResource(if (fullscreen) R.drawable.baseline_fullscreen_exit_24 else R.drawable.baseline_fullscreen_24)
if (fullscreen) {
enterFullscreen()
result_top_bar?.isVisible = false
@ -157,14 +158,14 @@ open class ResultTrailerPlayer : com.lagradost.cloudstream3.ui.player.FullScreen
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
player_fullscreen?.setOnClickListener {
playerBinding?.playerFullscreen?.setOnClickListener {
updateFullscreen(!isFullScreenPlayer)
}
updateFullscreen(isFullScreenPlayer)
uiReset()
player_intro_play?.setOnClickListener {
player_intro_play?.isGone = true
playerBinding?.playerIntroPlay?.setOnClickListener {
playerBinding?.playerIntroPlay?.isGone = true
player.handleEvent(CSPlayerEvent.Play)
updateUIVisibility()
fixPlayerSize()

View file

@ -277,7 +277,9 @@
android:layout_height="wrap_content"
android:descendantFocusability="blocksDescendants">
<include layout="@layout/fragment_trailer" />
<include
android:id="@+id/fragment_trailer"
layout="@layout/fragment_trailer" />
</FrameLayout>
<FrameLayout
@ -721,7 +723,6 @@
<LinearLayout
android:id="@+id/result_episodes_tab"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">

View file

@ -189,7 +189,7 @@
android:layout_height="match_parent"
android:layout_gravity="start">
<include layout="@layout/result_sync"/>
<include layout="@layout/result_sync" android:id="@+id/result_sync"/>
</FrameLayout>
<FrameLayout
@ -198,7 +198,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
<include layout="@layout/fragment_result" />
<include layout="@layout/fragment_result" android:id="@+id/fragment_result" />
</FrameLayout>
@ -209,7 +209,7 @@
android:layout_height="match_parent"
android:layout_gravity="end">
<include layout="@layout/result_recommendations" />
<include layout="@layout/result_recommendations" android:id="@+id/result_recommendations" />
</FrameLayout>
</com.discord.panels.OverlappingPanelsLayout>

View file

@ -746,7 +746,7 @@
android:layout_marginEnd="10dp"
android:nextFocusUp="@id/result_episodes"
android:nextFocusDown="@id/result_recommendations"
android:nextFocusDown="@id/result_recommendations_list"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:itemCount="2"
@ -763,7 +763,7 @@
</LinearLayout>
<com.lagradost.cloudstream3.ui.AutofitRecyclerView
android:id="@+id/result_recommendations"
android:id="@+id/result_recommendations_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"

View file

@ -84,6 +84,33 @@
style="@style/SmallBlackButton"
android:text="@string/filler" />
</FrameLayout>
<!-- atm this is useless, however it might be used for PIP one day? -->
<ImageView
android:visibility="gone"
android:id="@+id/player_fullscreen"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="20dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/baseline_fullscreen_24"
app:tint="@color/white" />
<FrameLayout
android:id="@+id/player_intro_play"
android:layout_width="0dp"
android:layout_height="0dp"
android:visibility="gone" />
<ImageView
android:id="@+id/player_open_source"
android:layout_width="0dp"
android:layout_height="0dp"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"
android:visibility="gone"
android:importantForAccessibility="no" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/player_video_holder"

View file

@ -167,6 +167,34 @@
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<FrameLayout
android:visibility="gone"
android:id="@+id/player_intro_play"
android:layout_width="0dp"
android:layout_height="0dp" />
<ImageView
android:visibility="gone"
android:id="@+id/player_open_source"
android:layout_width="0dp"
android:layout_height="0dp"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"
android:importantForAccessibility="no" />
<!-- atm this is useless, however it might be used for PIP one day? -->
<ImageView
android:visibility="gone"
android:id="@+id/player_fullscreen"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center_vertical"
android:layout_marginEnd="20dp"
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/baseline_fullscreen_24"
app:tint="@color/white" />
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/player_video_holder"
android:layout_width="match_parent"

View file

@ -20,7 +20,7 @@
android:layout_marginBottom="10dp"
android:layout_marginStart="0dp"
android:layout_marginEnd="0dp"
app:layout_constraintBottom_toTopOf="@id/result_recommendations" />
app:layout_constraintBottom_toTopOf="@id/result_recommendations_list" />
<com.lagradost.cloudstream3.ui.AutofitRecyclerView
android:descendantFocusability="afterDescendants"
@ -30,7 +30,7 @@
android:layout_height="wrap_content"
android:clipToPadding="false"
app:spanCount="3"
android:id="@+id/result_recommendations"
android:id="@+id/result_recommendations_list"
tools:listitem="@layout/search_result_grid"
android:orientation="vertical" />
</LinearLayout>

View file

@ -75,11 +75,118 @@
android:src="@drawable/play_button" />
</FrameLayout>
<FrameLayout
android:visibility="gone"
android:id="@+id/player_top_holder"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="80dp"
android:layout_marginTop="20dp"
android:layout_marginEnd="32dp"
android:orientation="vertical">
<TextView
android:id="@+id/player_video_title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:textColor="@color/white"
android:textSize="16sp"
android:textStyle="bold"
tools:text="Hello world" />
<TextView
android:id="@+id/player_video_title_rez"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="end"
android:textColor="@color/white"
android:textSize="16sp"
tools:text="1920x1080" />
<FrameLayout
android:id="@+id/player_episode_filler_holder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="2dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/player_episode_filler"
style="@style/SmallBlackButton"
android:text="@string/filler" />
</FrameLayout>
</LinearLayout>
<!-- Removed as it has no use anymore-->
<!--<androidx.mediarouter.app.MediaRouteButton
android:id="@+id/player_media_route_button"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_gravity="end"
android:layout_margin="5dp"
android:mediaRouteTypes="user"
android:visibility="visible"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />-->
<FrameLayout
android:id="@+id/player_go_back_holder"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="5dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<ImageView
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center"
android:contentDescription="@string/go_back_img_des"
android:src="@drawable/ic_baseline_arrow_back_24"
app:tint="@android:color/white" />
<ImageView
android:id="@+id/player_go_back"
android:layout_width="70dp"
android:layout_height="70dp"
android:layout_gravity="center"
android:background="@drawable/video_tap_button_always_white"
android:clickable="true"
android:contentDescription="@string/go_back_img_des"
android:focusable="true" />
</FrameLayout>
</FrameLayout>
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/player_video_holder"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.google.android.material.button.MaterialButton
android:id="@+id/skip_chapter_button"
style="@style/NiceButton"
android:layout_width="150dp"
android:layout_height="40dp"
android:layout_marginEnd="100dp"
android:backgroundTint="@color/skipOpTransparent"
android:maxLines="1"
android:padding="10dp"
android:textColor="@color/white"
android:visibility="gone"
app:cornerRadius="@dimen/rounded_button_radius"
app:layout_constraintBottom_toTopOf="@+id/bottom_player_bar"
app:layout_constraintEnd_toEndOf="parent"
app:strokeColor="@color/white"
app:strokeWidth="1dp"
tools:text="Skip Opening"
tools:visibility="visible" />
<!--use for thinner app:trackThickness="3dp" com.google.android.material.progressindicator.CircularProgressIndicator-->
<ProgressBar
android:id="@+id/player_buffering"
@ -282,6 +389,11 @@
tools:ignore="ContentDescription" />
</LinearLayout>
<FrameLayout
android:id="@+id/piphide"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<ImageView
android:id="@+id/player_open_source"
android:layout_width="24dp"
@ -366,7 +478,110 @@
android:background="?android:attr/selectableItemBackgroundBorderless"
android:src="@drawable/baseline_fullscreen_24"
app:tint="@color/white" />
<HorizontalScrollView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="60dp"
android:gravity="center"
android:orientation="horizontal"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<com.google.android.material.button.MaterialButton
android:id="@+id/player_lock"
style="@style/VideoButton"
android:nextFocusLeft="@id/player_skip_episode"
android:nextFocusRight="@id/player_resize_btt"
android:text="@string/video_lock"
app:icon="@drawable/video_locked"
app:iconSize="30dp" />
<LinearLayout
android:id="@+id/player_lock_holder"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/player_resize_btt"
style="@style/VideoButton"
android:nextFocusLeft="@id/player_lock"
android:nextFocusRight="@id/player_speed_btt"
android:text="@string/video_aspect_ratio_resize"
app:icon="@drawable/ic_baseline_aspect_ratio_24" />
<com.google.android.material.button.MaterialButton
android:id="@+id/player_speed_btt"
style="@style/VideoButton"
android:nextFocusLeft="@id/player_resize_btt"
android:nextFocusRight="@id/player_subtitle_offset_btt"
app:icon="@drawable/ic_baseline_speed_24"
tools:text="Speed" />
<com.google.android.material.button.MaterialButton
android:id="@+id/player_subtitle_offset_btt"
style="@style/VideoButton"
android:nextFocusLeft="@id/player_speed_btt"
android:nextFocusRight="@id/player_sources_btt"
android:text="@string/subtitle_offset"
android:visibility="gone"
app:icon="@drawable/ic_outline_subtitles_24"
tools:visibility="visible" />
<com.google.android.material.button.MaterialButton
android:id="@+id/player_sources_btt"
style="@style/VideoButton"
android:layout_height="40dp"
android:nextFocusLeft="@id/player_subtitle_offset_btt"
android:nextFocusRight="@id/player_tracks_btt"
android:text="@string/video_source"
app:icon="@drawable/ic_baseline_playlist_play_24" />
<com.google.android.material.button.MaterialButton
android:id="@+id/player_tracks_btt"
style="@style/VideoButton"
android:layout_height="40dp"
android:nextFocusLeft="@id/player_sources_btt"
android:nextFocusRight="@id/player_skip_op"
android:text="@string/tracks"
app:icon="@drawable/ic_baseline_playlist_play_24" />
<com.google.android.material.button.MaterialButton
android:id="@+id/player_skip_op"
style="@style/VideoButton"
android:nextFocusLeft="@id/player_sources_btt"
android:nextFocusRight="@id/player_skip_episode"
android:text="@string/video_skip_op"
app:icon="@drawable/ic_baseline_fast_forward_24" />
<com.google.android.material.button.MaterialButton
android:id="@+id/player_skip_episode"
style="@style/VideoButton"
android:nextFocusLeft="@id/player_skip_op"
android:nextFocusRight="@id/player_lock"
android:text="@string/next_episode"
app:icon="@drawable/ic_baseline_skip_next_24" />
</LinearLayout>
</LinearLayout>
</HorizontalScrollView>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<LinearLayout