tv layout stuff

This commit is contained in:
reduplicated 2022-08-06 01:41:35 +02:00
parent 33045c9115
commit fdaeffed0c
25 changed files with 710 additions and 188 deletions

View File

@ -260,10 +260,10 @@ object CommonActivity {
KeyEvent.KEYCODE_A, KeyEvent.KEYCODE_MEDIA_SKIP_BACKWARD, KeyEvent.KEYCODE_MEDIA_REWIND -> {
PlayerEventType.SeekBack
}
KeyEvent.KEYCODE_MEDIA_NEXT, KeyEvent.KEYCODE_BUTTON_R1 -> {
KeyEvent.KEYCODE_MEDIA_NEXT, KeyEvent.KEYCODE_BUTTON_R1, KeyEvent.KEYCODE_N -> {
PlayerEventType.NextEpisode
}
KeyEvent.KEYCODE_MEDIA_PREVIOUS, KeyEvent.KEYCODE_BUTTON_L1 -> {
KeyEvent.KEYCODE_MEDIA_PREVIOUS, KeyEvent.KEYCODE_BUTTON_L1, KeyEvent.KEYCODE_B -> {
PlayerEventType.PrevEpisode
}
KeyEvent.KEYCODE_MEDIA_PAUSE -> {
@ -294,6 +294,9 @@ object CommonActivity {
KeyEvent.KEYCODE_R, KeyEvent.KEYCODE_NUMPAD_0 -> {
PlayerEventType.Resize
}
KeyEvent.KEYCODE_C, KeyEvent.KEYCODE_NUMPAD_4 -> {
PlayerEventType.SkipOp
}
KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, KeyEvent.KEYCODE_P, KeyEvent.KEYCODE_SPACE, KeyEvent.KEYCODE_NUMPAD_ENTER, KeyEvent.KEYCODE_ENTER -> { // space is not captured due to navigation
PlayerEventType.PlayPauseToggle
}

View File

@ -98,7 +98,7 @@ class ParentItemAdapter(
recyclerView?.apply {
// this loops every viewHolder in the recycle view and checks the position to see if it is within the update range
val missingUpdates = (position until (position + count)).toMutableSet()
for (i in 0 until mAdapter.itemCount) {
for (i in 0 until itemCount) {
val viewHolder = getChildViewHolder(getChildAt(i))
val absolutePosition = viewHolder.absoluteAdapterPosition
if (absolutePosition >= position && absolutePosition < position + count) {

View File

@ -1164,6 +1164,9 @@ open class FullScreenPlayer : AbstractPlayerFragment() {
openOnlineSubPicker(view.context, null) {}
}
}
PlayerEventType.SkipOp -> {
skipOp()
}
}
}

View File

@ -23,6 +23,7 @@ enum class PlayerEventType(val value: Int) {
ShowMirrors(12),
Resize(13),
SearchSubtitlesOnline(14),
SkipOp(15),
}
enum class CSPlayerEvent(val value: Int) {

View File

@ -57,11 +57,11 @@ const val ACTION_DOWNLOAD_EPISODE_SUBTITLE_MIRROR = 14
data class EpisodeClickEvent(val action: Int, val data: ResultEpisode)
class EpisodeAdapter(
private var cardList: MutableList<ResultEpisode>,
private val hasDownloadSupport: Boolean,
private val clickCallback: (EpisodeClickEvent) -> Unit,
private val downloadClickCallback: (DownloadClickEvent) -> Unit,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var cardList: MutableList<ResultEpisode> = mutableListOf()
private val mBoundViewHolders: HashSet<DownloadButtonViewHolder> = HashSet()
private fun getAllBoundViewHolders(): Set<DownloadButtonViewHolder?>? {
@ -104,6 +104,8 @@ class EpisodeAdapter(
diffResult.dispatchUpdatesTo(this)
}
var layout = R.layout.result_episode_both
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
/*val layout = if (cardList.filter { it.poster != null }.size >= cardList.size / 2)
R.layout.result_episode_large
@ -111,7 +113,7 @@ class EpisodeAdapter(
return EpisodeCardViewHolder(
LayoutInflater.from(parent.context)
.inflate(R.layout.result_episode_both, parent, false),
.inflate(layout, parent, false),
hasDownloadSupport,
clickCallback,
downloadClickCallback
@ -149,6 +151,8 @@ class EpisodeAdapter(
fun bind(card: ResultEpisode) {
localCard = card
val isTrueTv = itemView.context?.isTrueTvSettings() == true
val (parentView, otherView) = if (card.poster == null) {
itemView.episode_holder to itemView.episode_holder_large
} else {
@ -199,20 +203,22 @@ class EpisodeAdapter(
}
}
episodePoster?.setOnClickListener {
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
}
if (!isTrueTv) {
episodePoster?.setOnClickListener {
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
}
episodePoster?.setOnLongClickListener {
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_TOAST, card))
return@setOnLongClickListener true
episodePoster?.setOnLongClickListener {
clickCallback.invoke(EpisodeClickEvent(ACTION_SHOW_TOAST, card))
return@setOnLongClickListener true
}
}
parentView.setOnClickListener {
clickCallback.invoke(EpisodeClickEvent(ACTION_CLICK_DEFAULT, card))
}
if (parentView.context.isTrueTvSettings()) {
if (isTrueTv) {
parentView.isFocusable = true
parentView.isFocusableInTouchMode = true
parentView.touchscreenBlocksFocus = false

View File

@ -328,6 +328,98 @@ open class ResultFragment : ResultTrailerPlayer() {
viewModel.reloadEpisodes()
}
open fun updateMovie(data : ResourceSome<Pair<UiText, ResultEpisode>>) {
when (data) {
is ResourceSome.Success -> {
data.value.let { (text, ep) ->
result_play_movie.setText(text)
result_play_movie?.setOnClickListener {
viewModel.handleAction(
activity,
EpisodeClickEvent(ACTION_CLICK_DEFAULT, ep)
)
}
result_play_movie?.setOnLongClickListener {
viewModel.handleAction(
activity,
EpisodeClickEvent(ACTION_SHOW_OPTIONS, ep)
)
return@setOnLongClickListener true
}
main {
val file =
ioWork {
context?.let {
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(
it,
ep.id
)
}
}
downloadButton?.dispose()
downloadButton = EasyDownloadButton()
downloadButton?.setUpMoreButton(
file?.fileLength,
file?.totalBytes,
result_movie_progress_downloaded,
result_movie_download_icon,
result_movie_download_text,
result_movie_download_text_precentage,
result_download_movie,
true,
VideoDownloadHelper.DownloadEpisodeCached(
ep.name,
ep.poster,
0,
null,
ep.id,
ep.id,
null,
null,
System.currentTimeMillis(),
)
) { click ->
when(click.action) {
DOWNLOAD_ACTION_DOWNLOAD -> {
viewModel.handleAction(
activity,
EpisodeClickEvent(ACTION_DOWNLOAD_EPISODE, ep)
)
}
else -> handleDownloadClick(activity, click)
}
}
result_movie_progress_downloaded_holder?.isVisible = true
}
}
}
else -> {
result_movie_progress_downloaded_holder?.isVisible = false
result_play_movie?.isVisible = false
}
}
}
open fun updateEpisodes(episodes : ResourceSome<List<ResultEpisode>>) {
when (episodes) {
is ResourceSome.None -> {
result_episode_loading?.isVisible = false
result_episodes?.isVisible = false
}
is ResourceSome.Loading -> {
result_episode_loading?.isVisible = true
result_episodes?.isVisible = false
}
is ResourceSome.Success -> {
result_episodes?.isVisible = true
result_episode_loading?.isVisible = false
(result_episodes?.adapter as? EpisodeAdapter?)?.updateList(episodes.value)
}
}
}
@SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
@ -401,24 +493,8 @@ open class ResultFragment : ResultTrailerPlayer() {
}
}
result_scroll.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
val dy = scrollY - oldScrollY
if (dy > 0) { //check for scroll down
result_bookmark_fab?.shrink()
} else if (dy < -5) {
result_bookmark_fab?.extend()
}
if (!isFullScreenPlayer && player.getIsPlaying()) {
if (scrollY > (player_background?.height ?: scrollY)) {
player.handleEvent(CSPlayerEvent.Pause)
}
}
//result_poster_blur_holder?.translationY = -scrollY.toFloat()
})
result_episodes.adapter =
EpisodeAdapter(
ArrayList(),
api.hasDownloadSupport,
{ episodeClick ->
viewModel.handleAction(activity, episodeClick)
@ -456,9 +532,10 @@ open class ResultFragment : ResultTrailerPlayer() {
}
// This is to band-aid FireTV navigation
result_season_button?.isFocusableInTouchMode = context?.isTvSettings() == true
result_episode_select?.isFocusableInTouchMode = context?.isTvSettings() == true
result_dub_select?.isFocusableInTouchMode = context?.isTvSettings() == true
val isTv = context?.isTvSettings() == true
result_season_button?.isFocusableInTouchMode = isTv
result_episode_select?.isFocusableInTouchMode = isTv
result_dub_select?.isFocusableInTouchMode = isTv
context?.let { ctx ->
val arrayAdapter = ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
@ -653,21 +730,7 @@ open class ResultFragment : ResultTrailerPlayer() {
}
observe(viewModel.episodes) { episodes ->
when (episodes) {
is ResourceSome.None -> {
result_episode_loading?.isVisible = false
result_episodes?.isVisible = false
}
is ResourceSome.Loading -> {
result_episode_loading?.isVisible = true
result_episodes?.isVisible = false
}
is ResourceSome.Success -> {
result_episodes?.isVisible = true
result_episode_loading?.isVisible = false
(result_episodes?.adapter as? EpisodeAdapter?)?.updateList(episodes.value)
}
}
updateEpisodes(episodes)
}
result_cast_items?.setOnFocusChangeListener { _, hasFocus ->
@ -692,77 +755,7 @@ open class ResultFragment : ResultTrailerPlayer() {
}
observe(viewModel.movie) { data ->
when (data) {
is ResourceSome.Success -> {
data.value.let { (text, ep) ->
result_play_movie.setText(text)
result_play_movie?.setOnClickListener {
viewModel.handleAction(
activity,
EpisodeClickEvent(ACTION_CLICK_DEFAULT, ep)
)
}
result_play_movie?.setOnLongClickListener {
viewModel.handleAction(
activity,
EpisodeClickEvent(ACTION_SHOW_OPTIONS, ep)
)
return@setOnLongClickListener true
}
main {
val file =
ioWork {
context?.let {
VideoDownloadManager.getDownloadFileInfoAndUpdateSettings(
it,
ep.id
)
}
}
downloadButton?.dispose()
downloadButton = EasyDownloadButton()
downloadButton?.setUpMoreButton(
file?.fileLength,
file?.totalBytes,
result_movie_progress_downloaded,
result_movie_download_icon,
result_movie_download_text,
result_movie_download_text_precentage,
result_download_movie,
true,
VideoDownloadHelper.DownloadEpisodeCached(
ep.name,
ep.poster,
0,
null,
ep.id,
ep.id,
null,
null,
System.currentTimeMillis(),
)
) { click ->
when(click.action) {
DOWNLOAD_ACTION_DOWNLOAD -> {
viewModel.handleAction(
activity,
EpisodeClickEvent(ACTION_DOWNLOAD_EPISODE, ep)
)
}
else -> handleDownloadClick(activity, click)
}
}
result_movie_progress_downloaded_holder?.isVisible = true
}
}
}
else -> {
result_movie_progress_downloaded_holder?.isVisible = false
result_play_movie?.isVisible = false
}
}
updateMovie(data)
}
observe(viewModel.page) { data ->

View File

@ -7,6 +7,7 @@ import android.view.View
import android.view.ViewGroup
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.core.widget.NestedScrollView
import com.discord.panels.OverlappingPanelsLayout
import com.discord.panels.PanelsChildGestureRegionObserver
import com.google.android.material.bottomsheet.BottomSheetDialog
@ -18,6 +19,7 @@ import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.mvvm.Some
import com.lagradost.cloudstream3.mvvm.observe
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
import com.lagradost.cloudstream3.utils.AppUtils.openBrowser
@ -30,6 +32,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIcons
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
import kotlinx.android.synthetic.main.fragment_result.*
import kotlinx.android.synthetic.main.fragment_result_swipe.*
import kotlinx.android.synthetic.main.fragment_trailer.*
import kotlinx.android.synthetic.main.result_recommendations.*
import kotlinx.android.synthetic.main.trailer_custom_layout.*
@ -129,10 +132,10 @@ class ResultFragmentPhone : ResultFragment() {
context?.openBrowser(it.url)
}
}
result_recommendations?.spanCount = 3
result_overlapping_panels?.setStartPanelLockState(OverlappingPanelsLayout.LockState.CLOSE)
result_overlapping_panels?.setEndPanelLockState(OverlappingPanelsLayout.LockState.CLOSE)
result_recommendations?.spanCount = 3
result_recommendations?.adapter =
SearchAdapter(
ArrayList(),
@ -175,6 +178,21 @@ class ResultFragmentPhone : ResultFragment() {
})
result_scroll?.setOnScrollChangeListener(NestedScrollView.OnScrollChangeListener { _, _, scrollY, _, oldScrollY ->
val dy = scrollY - oldScrollY
if (dy > 0) { //check for scroll down
result_bookmark_fab?.shrink()
} else if (dy < -5) {
result_bookmark_fab?.extend()
}
if (!isFullScreenPlayer && player.getIsPlaying()) {
if (scrollY > (player_background?.height ?: scrollY)) {
player.handleEvent(CSPlayerEvent.Pause)
}
}
//result_poster_blur_holder?.translationY = -scrollY.toFloat()
})
observe(viewModel.selectPopup) { popup ->
when (popup) {
is Some.Success -> {

View File

@ -1,11 +1,129 @@
package com.lagradost.cloudstream3.ui.result
import android.os.Bundle
import android.view.View
import androidx.core.view.children
import androidx.core.view.isGone
import androidx.core.view.isVisible
import androidx.recyclerview.widget.RecyclerView
import com.discord.panels.OverlappingPanelsLayout
import com.lagradost.cloudstream3.DubStatus
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.mvvm.ResourceSome
import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.ui.search.SearchAdapter
import com.lagradost.cloudstream3.ui.search.SearchHelper
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showBottomDialog
import kotlinx.android.synthetic.main.fragment_result_swipe.*
import kotlinx.android.synthetic.main.fragment_result_tv.*
import kotlinx.android.synthetic.main.result_recommendations.*
class ResultFragmentTv : ResultFragment() {
override val resultLayout = R.layout.fragment_result_tv
override fun setRecommendations(rec: List<SearchResponse>?, validApiName: String?) {
private fun handleSelection(data: Any) {
when (data) {
is EpisodeRange -> {
viewModel.changeRange(data)
}
is Int -> {
viewModel.changeSeason(data)
}
is DubStatus -> {
viewModel.changeDubStatus(data)
}
}
}
private fun RecyclerView?.select(index: Int) {
(this?.adapter as? SelectAdaptor?)?.select(index, this)
}
private fun RecyclerView?.update(data: List<SelectData>) {
(this?.adapter as? SelectAdaptor?)?.updateSelectionList(data)
this?.isVisible = data.size > 1
}
private fun RecyclerView?.setAdapter() {
this?.adapter = SelectAdaptor { data ->
handleSelection(data)
}
}
private fun hasNoFocus(): Boolean {
val focus = activity?.currentFocus
if (focus == null || !focus.isVisible) return true
return focus == this.result_root
}
override fun updateEpisodes(episodes: ResourceSome<List<ResultEpisode>>) {
super.updateEpisodes(episodes)
if (episodes is ResourceSome.Success && hasNoFocus()) {
result_episodes?.requestFocus()
}
}
override fun updateMovie(data: ResourceSome<Pair<UiText, ResultEpisode>>) {
super.updateMovie(data)
if (data is ResourceSome.Success && hasNoFocus()) {
result_play_movie?.requestFocus()
}
}
override fun setRecommendations(rec: List<SearchResponse>?, validApiName: String?) {
val isInvalid = rec.isNullOrEmpty()
result_recommendations?.isGone = isInvalid
result_recommendations_btt?.isGone = isInvalid
val matchAgainst = validApiName ?: rec?.firstOrNull()?.apiName
(result_recommendations?.adapter as SearchAdapter?)?.updateList(rec?.filter { it.apiName == matchAgainst } ?: emptyList())
rec?.map { it.apiName }?.distinct()?.let { apiNames ->
// very dirty selection
result_recommendations_filter_button?.isVisible = apiNames.size > 1
result_recommendations_filter_button?.text = matchAgainst
} ?: run {
result_recommendations_filter_button?.isVisible = false
}
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
(result_episodes?.adapter as EpisodeAdapter?)?.apply {
layout = R.layout.result_episode_both_tv
}
result_season_selection.setAdapter()
result_range_selection.setAdapter()
result_dub_selection.setAdapter()
observe(viewModel.selectedRangeIndex) { selected ->
result_range_selection.select(selected)
}
observe(viewModel.selectedSeasonIndex) { selected ->
result_season_selection.select(selected)
}
observe(viewModel.selectedDubStatusIndex) { selected ->
result_dub_selection.select(selected)
}
observe(viewModel.rangeSelections) {
result_range_selection.update(it)
}
observe(viewModel.dubSubSelections) {
result_dub_selection.update(it)
}
observe(viewModel.seasonSelections) {
result_season_selection.update(it)
}
result_recommendations?.spanCount = 8
result_recommendations?.adapter =
SearchAdapter(
ArrayList(),
result_recommendations,
) { callback ->
SearchHelper.handleSearchClickCallback(activity, callback)
}
}
}

View File

@ -57,6 +57,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.navigate
import com.lagradost.cloudstream3.utils.UIHelper.requestRW
import kotlinx.coroutines.*
import java.io.File
import java.lang.Math.abs
import java.util.concurrent.TimeUnit
@ -311,8 +312,8 @@ class ResultViewModel2 : ViewModel() {
/** map<dub, map<season, List<episode>>> */
private var currentEpisodes: Map<EpisodeIndexer, List<ResultEpisode>> = mapOf()
private var currentRanges: Map<EpisodeIndexer, List<EpisodeRange>> = mapOf()
private var currentSeasons: Set<Int> = setOf()
private var currentDubStatus: Set<DubStatus> = setOf()
private var currentSeasons: List<Int> = listOf()
private var currentDubStatus: List<DubStatus> = listOf()
private var currentMeta: SyncAPI.SyncResult? = null
private var currentSync: Map<String, String>? = null
private var currentIndex: EpisodeIndexer? = null
@ -376,6 +377,18 @@ class ResultViewModel2 : ViewModel() {
private val _selectedDubStatus: MutableLiveData<Some<UiText>> = MutableLiveData(Some.None)
val selectedDubStatus: LiveData<Some<UiText>> = _selectedDubStatus
private val _selectedRangeIndex: MutableLiveData<Int> =
MutableLiveData(-1)
val selectedRangeIndex: LiveData<Int> = _selectedRangeIndex
private val _selectedSeasonIndex: MutableLiveData<Int> =
MutableLiveData(-1)
val selectedSeasonIndex: LiveData<Int> = _selectedSeasonIndex
private val _selectedDubStatusIndex: MutableLiveData<Int> = MutableLiveData(-1)
val selectedDubStatusIndex: LiveData<Int> = _selectedDubStatusIndex
private val _loadedLinks: MutableLiveData<Some<LinkProgress>> = MutableLiveData(Some.None)
val loadedLinks: LiveData<Some<LinkProgress>> = _loadedLinks
@ -1414,6 +1427,16 @@ class ResultViewModel2 : ViewModel() {
val episodes = currentEpisodes[indexer]
val ranges = currentRanges[indexer]
if (ranges?.contains(range) != true) {
// if the current ranges does not include the range then select the range with the closest matching start episode
// this usually happends when dub has less episodes then sub -> the range does not exist
ranges?.minByOrNull { abs(it.startEpisode - range.startEpisode) }?.let { r ->
postEpisodeRange(indexer, r)
return
}
}
val size = episodes?.size
val isMovie = currentResponse?.isMovie() == true
currentIndex = indexer
@ -1435,6 +1458,10 @@ class ResultViewModel2 : ViewModel() {
)
)
_selectedSeasonIndex.postValue(
currentSeasons.indexOf(indexer.season)
)
_selectedSeason.postValue(
some(
if (isMovie || currentSeasons.size <= 1) null else
@ -1449,6 +1476,10 @@ class ResultViewModel2 : ViewModel() {
)
)
_selectedRangeIndex.postValue(
ranges?.indexOf(range) ?: -1
)
_selectedRange.postValue(
some(
if (isMovie) null else if ((currentRanges[indexer]?.size ?: 0) > 1) {
@ -1458,6 +1489,11 @@ class ResultViewModel2 : ViewModel() {
}
)
)
_selectedDubStatusIndex.postValue(
currentDubStatus.indexOf(indexer.dubStatus)
)
_selectedDubStatus.postValue(
some(
if (isMovie || currentDubStatus.size <= 1) null else
@ -1487,6 +1523,12 @@ class ResultViewModel2 : ViewModel() {
postMovie()
} else {
val ret = getEpisodes(indexer, range)
/*if (ret.isEmpty()) {
val index = ranges?.indexOf(range)
if(index != null && index > 0) {
}
}*/
_episodes.postValue(ResourceSome.Success(ret))
}
}
@ -1675,8 +1717,8 @@ class ResultViewModel2 : ViewModel() {
seasonsSelection += key.season
dubSelection += key.dubStatus
}
currentDubStatus = dubSelection
currentSeasons = seasonsSelection
currentDubStatus = dubSelection.toList()
currentSeasons = seasonsSelection.toList()
_dubSubSelections.postValue(dubSelection.map { txt(it) to it })
if (loadResponse is EpisodeResponse) {
_seasonSelections.postValue(seasonsSelection.map { seasonNumber ->

View File

@ -0,0 +1,121 @@
package com.lagradost.cloudstream3.ui.result
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.button.MaterialButton
import com.lagradost.cloudstream3.ActorData
import com.lagradost.cloudstream3.ActorRole
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.ui.home.ParentItemAdapter
import com.lagradost.cloudstream3.ui.settings.AccountAdapter
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import com.lagradost.cloudstream3.utils.UIHelper.setImage
import kotlinx.android.synthetic.main.cast_item.view.*
import org.schabi.newpipe.extractor.timeago.patterns.it
typealias SelectData = Pair<UiText?, Any>
class SelectAdaptor(val callback: (Any) -> Unit) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val selection: MutableList<SelectData> = mutableListOf()
private var selectedIndex: Int = -1
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return SelectViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.result_selection, parent, false),
)
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
when (holder) {
is SelectViewHolder -> {
holder.bind(selection[position], position == selectedIndex, callback)
}
}
}
override fun getItemCount(): Int {
return selection.size
}
fun select(newIndex: Int, recyclerView: RecyclerView?) {
if(recyclerView == null) return
if(newIndex == selectedIndex) return
val oldIndex = selectedIndex
selectedIndex = newIndex
recyclerView.apply {
for (i in 0 until itemCount) {
val viewHolder = getChildViewHolder( getChildAt(i) ?: continue) ?: continue
val pos = viewHolder.absoluteAdapterPosition
if (viewHolder is SelectViewHolder) {
if (pos == oldIndex) {
viewHolder.update(false)
} else if (pos == newIndex) {
viewHolder.update(true)
}
}
}
}
}
fun updateSelectionList(newList: List<SelectData>) {
val diffResult = DiffUtil.calculateDiff(
SelectDataCallback(this.selection, newList)
)
selection.clear()
selection.addAll(newList)
diffResult.dispatchUpdatesTo(this)
}
private class SelectViewHolder
constructor(
itemView: View,
) :
RecyclerView.ViewHolder(itemView) {
private val item: MaterialButton = itemView as MaterialButton
fun update(isSelected: Boolean) {
item.isSelected = isSelected
}
fun bind(
data: SelectData, isSelected: Boolean, callback: (Any) -> Unit
) {
val isTrueTv = itemView.context?.isTrueTvSettings() == true
if (isTrueTv) {
item.isFocusable = true
item.isFocusableInTouchMode = true
}
item.isSelected = isSelected
item.setText(data.first)
item.setOnClickListener {
callback.invoke(data.second)
}
}
}
}
class SelectDataCallback(
private val oldList: List<SelectData>,
private val newList: List<SelectData>
) :
DiffUtil.Callback() {
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
oldList[oldItemPosition].second == newList[newItemPosition].second
override fun getOldListSize() = oldList.size
override fun getNewListSize() = newList.size
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
oldList[oldItemPosition] == newList[newItemPosition]
}

View File

@ -44,6 +44,7 @@ import com.lagradost.cloudstream3.isMovieType
import com.lagradost.cloudstream3.mapper
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.ui.result.ResultFragment
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTrueTvSettings
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.isTvSettings
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
@ -316,12 +317,11 @@ object AppUtils {
//private val viewModel: ResultViewModel by activityViewModels()
private fun getResultsId(context: Context) : Int {
return R.id.global_to_navigation_results_phone
//return if(context.isTvSettings()) {
// R.id.global_to_navigation_results_tv
//} else {
// R.id.global_to_navigation_results_phone
//}
return if(context.isTrueTvSettings()) {
R.id.global_to_navigation_results_tv
} else {
R.id.global_to_navigation_results_phone
}
}
fun AppCompatActivity.loadResult(

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?attr/textColor"/>
<item android:state_focused="true" android:color="?attr/iconGrayBackground"/>
<item android:state_selected="true" android:color="?attr/iconGrayBackground" />
<item android:color="?attr/textColor" />
</selector>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true" android:color="?attr/textColor"/>
<item android:state_selected="true" android:color="?attr/textColor"/>
<item android:color="?attr/iconGrayBackground"/>
</selector>

View File

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_focused="true" android:drawable="@drawable/outline_less" /> <!-- focused -->
</selector>

View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" >
<stroke android:width="2dp"
android:color="?attr/white"/>
<corners
android:bottomLeftRadius="@dimen/rounded_button_radius"
android:bottomRightRadius="@dimen/rounded_button_radius"
android:topLeftRadius="@dimen/rounded_button_radius"
android:topRightRadius="@dimen/rounded_button_radius" />
</shape>

View File

@ -6,9 +6,7 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
style="@style/DarkFragment"
android:background="?attr/primaryBlackBackground"
android:clickable="true"
android:focusable="true">
android:background="?attr/primaryBlackBackground">
<com.facebook.shimmer.ShimmerFrameLayout
android:id="@+id/result_loading"
@ -271,7 +269,7 @@
tools:text="Cast: Joe Ligma" />
<androidx.recyclerview.widget.RecyclerView
tools:visibility="gone"
tools:visibility="visible"
android:nextFocusUp="@id/result_bookmark_button"
android:nextFocusDown="@id/result_play_movie"
@ -305,6 +303,7 @@
android:textColor="?attr/grayTextColor"
android:textSize="15sp"
tools:text="@string/provider_info_meta" />
<TextView
android:id="@+id/result_no_episodes"
android:layout_width="match_parent"
@ -373,6 +372,10 @@
tools:visibility="visible">
<com.google.android.material.button.MaterialButton
android:nextFocusRight="@id/result_download_movie"
android:nextFocusUp="@id/result_cast_items"
android:nextFocusDown="@id/result_resume_series_button_play"
android:id="@+id/result_play_movie"
style="@style/WhiteButton"
android:layout_width="0dp"
@ -381,14 +384,10 @@
android:layout_marginStart="0dp"
android:layout_marginEnd="5dp"
android:layout_marginBottom="10dp"
android:nextFocusUp="@id/result_bookmark_button"
android:nextFocusDown="@id/result_download_movie"
android:text="@string/play_movie_button"
android:visibility="visible"
app:icon="@drawable/ic_baseline_play_arrow_24">
<requestFocus />
</com.google.android.material.button.MaterialButton>
@ -400,6 +399,10 @@
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:nextFocusLeft="@id/result_play_movie"
android:nextFocusUp="@id/result_cast_items"
android:nextFocusDown="@id/result_resume_series_button_play"
android:id="@+id/result_download_movie"
style="@style/BlackButton"
@ -410,8 +413,6 @@
android:clickable="true"
android:focusable="true"
android:nextFocusUp="@id/result_play_movie"
android:nextFocusDown="@id/result_season_button"
android:visibility="visible" />
<LinearLayout
@ -508,8 +509,10 @@
android:layout_height="wrap_content">
<ImageView
android:layout_marginEnd="10dp"
android:id="@+id/result_resume_series_button_play"
android:nextFocusUp="@id/result_play_movie"
android:nextFocusDown="@id/result_season_selection"
android:layout_width="30dp"
android:layout_height="30dp"
android:layout_gravity="center"
@ -522,6 +525,8 @@
<TextView
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:layout_gravity="center"
android:gravity="center"
android:id="@+id/result_resume_series_title"
@ -540,7 +545,6 @@
android:layout_gravity="center"
android:layout_weight="0"
android:gravity="center"
android:paddingStart="10dp"
android:textColor="?attr/grayTextColor"
tools:ignore="RtlSymmetry"
tools:text="69m remaining" />
@ -574,42 +578,46 @@
</LinearLayout>
<LinearLayout
android:id="@+id/result_episodes_tab"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:paddingBottom="10dp"
android:nextFocusUp="@id/result_resume_series_button_play"
android:nextFocusDown="@id/result_range_selection"
android:id="@+id/result_season_selection"
android:paddingBottom="10dp"
tools:listitem="@layout/result_selection"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
android:layout_height="wrap_content" />
</androidx.recyclerview.widget.RecyclerView>
<androidx.recyclerview.widget.RecyclerView
android:paddingBottom="10dp"
android:nextFocusUp="@id/result_season_selection"
android:nextFocusDown="@id/result_dub_selection"
android:id="@+id/result_range_selection"
tools:listitem="@layout/result_selection"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</androidx.recyclerview.widget.RecyclerView>
<androidx.recyclerview.widget.RecyclerView
android:paddingBottom="10dp"
android:id="@+id/result_dub_selection"
tools:listitem="@layout/result_selection"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</androidx.recyclerview.widget.RecyclerView>
android:paddingBottom="10dp"
tools:listitem="@layout/result_selection"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<androidx.recyclerview.widget.RecyclerView
android:nextFocusUp="@id/result_range_selection"
android:nextFocusDown="@id/result_episodes"
android:id="@+id/result_dub_selection"
android:paddingBottom="10dp"
tools:listitem="@layout/result_selection"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<LinearLayout
android:id="@+id/result_next_airing_holder"
@ -643,6 +651,7 @@
</LinearLayout>
<com.facebook.shimmer.ShimmerFrameLayout
tools:visibility="gone"
android:id="@+id/result_episode_loading"
android:layout_width="match_parent"
android:layout_height="match_parent"
@ -652,8 +661,7 @@
app:shimmer_auto_start="true"
app:shimmer_base_alpha="0.2"
app:shimmer_duration="@integer/loading_time"
app:shimmer_highlight_alpha="0.3"
tools:visibility="visible">
app:shimmer_highlight_alpha="0.3">
<LinearLayout
android:layout_width="match_parent"
@ -678,22 +686,21 @@
android:layout_height="50dp" />-->
<androidx.recyclerview.widget.RecyclerView
android:nextFocusUp="@id/result_dub_selection"
android:id="@+id/result_episodes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="0dp"
android:clipToPadding="false"
android:orientation="horizontal"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:descendantFocusability="afterDescendants"
android:paddingBottom="100dp"
tools:listitem="@layout/result_episode" />
</LinearLayout>
</LinearLayout>
<include layout="@layout/result_recommendations" />
</LinearLayout>
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</LinearLayout>
</FrameLayout>

View File

@ -2,8 +2,6 @@
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:nextFocusLeft="@id/result_episode_download"
android:nextFocusRight="@id/result_episode_download"
android:id="@+id/episode_holder"
@ -15,6 +13,10 @@
app:cardElevation="0dp"
android:foreground="@drawable/outline_drawable"
android:layout_marginBottom="5dp">
<!--
android:nextFocusLeft="@id/result_episode_download"
-->
<!-- IDK BUT THIS DOES NOT SEAM LIKE A GOOD WAY OF DOING IT -->
<!--<LinearLayout
android:layout_width="fill_parent"
@ -110,9 +112,10 @@
android:progress="0"
android:visibility="visible" />
<!--
android:nextFocusRight="@id/episode_holder"-->
<ImageView
android:nextFocusLeft="@id/episode_holder"
android:nextFocusRight="@id/episode_holder"
app:tint="?attr/white"
android:id="@+id/result_episode_download"
android:visibility="visible"

View File

@ -4,5 +4,5 @@
android:layout_height="wrap_content">
<include android:visibility="gone" layout="@layout/result_episode" />
<include android:visibility="gone" layout="@layout/result_episode_large" />
<include android:visibility="visible" layout="@layout/result_episode_large" />
</FrameLayout>

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
xmlns:tools="http://schemas.android.com/tools">
<include
tools:visibility="visible"
android:visibility="gone"
android:layout_width="wrap_content"
android:layout_height="50dp"
layout="@layout/result_episode_tv" />
<include
tools:visibility="gone"
android:visibility="gone"
android:layout_width="450dp"
android:layout_height="wrap_content"
layout="@layout/result_episode_large_tv" />
</FrameLayout>

View File

@ -3,11 +3,10 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:nextFocusLeft="@id/episode_poster"
android:nextFocusRight="@id/result_episode_download"
android:id="@+id/episode_holder_large"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardCornerRadius="@dimen/rounded_image_radius"
app:cardBackgroundColor="?attr/boxItemBackground"
@ -19,7 +18,7 @@
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:padding="10dp"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
@ -33,8 +32,7 @@
android:foreground="@drawable/outline_drawable">
<ImageView
android:nextFocusLeft="@id/result_episode_download"
android:nextFocusRight="@id/episode_holder"
android:nextFocusRight="@id/result_episode_download"
android:id="@+id/episode_poster"
tools:src="@drawable/example_poster"
@ -126,7 +124,6 @@
<ImageView
android:nextFocusLeft="@id/episode_poster"
android:nextFocusRight="@id/episode_holder"
android:id="@+id/result_episode_download"
android:visibility="visible"

View File

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:nextFocusRight="@id/result_episode_download"
android:id="@+id/episode_holder_large"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:cardCornerRadius="@dimen/rounded_image_radius"
app:cardBackgroundColor="?attr/boxItemBackground"
android:foreground="@drawable/outline_drawable"
android:layout_marginBottom="10dp">
<LinearLayout
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:padding="10dp"
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:orientation="horizontal"
android:layout_height="wrap_content">
<!--app:cardCornerRadius="@dimen/roundedImageRadius"-->
<androidx.cardview.widget.CardView
android:layout_width="126dp"
android:layout_height="72dp"
android:foreground="@drawable/outline_drawable">
<ImageView
android:id="@+id/episode_poster"
tools:src="@drawable/example_poster"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:scaleType="centerCrop"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:contentDescription="@string/episode_poster_img_des" />
<ImageView
android:src="@drawable/play_button"
android:layout_gravity="center"
android:layout_width="36dp"
android:layout_height="36dp"
android:contentDescription="@string/play_episode" />
<androidx.core.widget.ContentLoadingProgressBar
android:layout_marginBottom="-1.5dp"
android:id="@+id/episode_progress"
android:progressTint="?attr/colorPrimary"
android:progressBackgroundTint="?attr/colorPrimary"
style="@android:style/Widget.Material.ProgressBar.Horizontal"
android:layout_width="match_parent"
tools:progress="50"
android:layout_gravity="bottom"
android:layout_height="5dp" />
</androidx.cardview.widget.CardView>
<LinearLayout
android:layout_marginStart="15dp"
android:orientation="vertical"
android:layout_gravity="center"
android:layout_width="match_parent"
android:layout_marginEnd="50dp"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<com.google.android.material.button.MaterialButton
android:layout_gravity="start"
style="@style/SmallBlackButton"
android:layout_marginEnd="10dp"
android:text="@string/filler"
android:id="@+id/episode_filler" />
<TextView
android:layout_gravity="center_vertical"
android:id="@+id/episode_text"
tools:text="1. Jobless"
android:textStyle="bold"
android:textColor="?attr/textColor"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<TextView
android:id="@+id/episode_rating"
tools:text="Rated: 8.8"
android:textColor="?attr/grayTextColor"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
</LinearLayout>
<TextView
android:maxLines="4"
android:ellipsize="end"
android:paddingTop="10dp"
android:paddingBottom="10dp"
android:id="@+id/episode_descript"
android:textColor="?attr/grayTextColor"
tools:text="Jon and Daenerys arrive in Winterfell and are met with skepticism. Sam learns about the fate of his family. Cersei gives Euron the reward he aims for. Theon follows his heart. Jon and Daenerys arrive in Winterfell and are met with skepticism. Sam learns about the fate of his family. Cersei gives Euron the reward he aims for. Theon follows his heart."
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:nextFocusRight="@id/result_episode_download"
android:id="@+id/episode_holder"
android:layout_width="wrap_content"
android:layout_height="50dp"
app:cardCornerRadius="@dimen/rounded_image_radius"
app:cardBackgroundColor="@color/transparent"
app:cardElevation="0dp"
android:foreground="@drawable/outline_drawable"
android:layout_marginBottom="5dp">
<androidx.core.widget.ContentLoadingProgressBar
android:layout_marginBottom="-1.5dp"
android:id="@+id/episode_progress"
android:progressTint="?attr/colorPrimary"
android:progressBackgroundTint="?attr/colorPrimary"
style="@android:style/Widget.Material.ProgressBar.Horizontal"
android:layout_width="match_parent"
tools:progress="50"
android:layout_gravity="bottom"
android:layout_height="5dp" />
<LinearLayout
android:paddingStart="20dp"
android:paddingEnd="20dp"
android:gravity="center"
android:foreground="?android:attr/selectableItemBackgroundBorderless"
android:layout_width="match_parent"
android:layout_height="match_parent">
<!--marquee_forever-->
<com.google.android.material.button.MaterialButton
android:layout_marginEnd="10dp"
tools:visibility="visible"
android:gravity="center"
android:layout_gravity="center"
style="@style/SmallBlackButton"
android:text="@string/filler"
android:id="@+id/episode_filler" />
<TextView
android:id="@+id/episode_text"
android:layout_gravity="center_vertical"
android:gravity="center"
tools:text="Episode 1"
android:textColor="?attr/textColor"
android:scrollHorizontally="true"
android:ellipsize="marquee"
android:marqueeRepeatLimit="0"
android:singleLine="true"
android:layout_width="wrap_content"
android:layout_height="match_parent" />
</LinearLayout>
</androidx.cardview.widget.CardView>

View File

@ -1,8 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<com.google.android.material.button.MaterialButton android:id="@+id/result_season_button"
<com.google.android.material.button.MaterialButton xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
style="@style/SelectableButton"
android:layout_marginStart="0dp"
android:layout_marginEnd="10dp"
tools:text="Season 1"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" />
tools:text="Season 1" />

View File

@ -3,6 +3,8 @@
<dimen name="activity_horizontal_margin">16dp</dimen>
<dimen name="activity_vertical_margin">16dp</dimen>
<dimen name="rounded_image_radius">10dp</dimen>
<dimen name="rounded_button_radius">4dp</dimen>
<dimen name="navbar_height">0dp</dimen>
<dimen name="card_corner_radius">2dp</dimen>
<dimen name="result_padding">15dp</dimen>

View File

@ -432,12 +432,12 @@
<item name="android:textAllCaps">false</item>
<item name="iconGravity">textStart</item>
<item name="iconSize">20dp</item>
<item name="cornerRadius">4dp</item>
<item name="cornerRadius">@dimen/rounded_button_radius</item>
<item name="android:textSize">15sp</item>
<item name="android:insetTop">0dp</item>
<item name="android:insetBottom">0dp</item>
<item name="android:foreground">@drawable/outline_drawable</item>
<item name="android:foreground">@drawable/outline_drawable_less</item>
</style>
<style name="WhiteButton" parent="NiceButton">