heavily improved android tv anime page navigation

This commit is contained in:
Blatzar 2022-03-27 18:45:02 +02:00
parent ad2f2ec412
commit 83f3f7ff2e
7 changed files with 667 additions and 579 deletions

View file

@ -333,7 +333,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
}
// this pulls the latest data so ppl don't have to update to simply change provider url
if(downloadFromGithub) {
if (downloadFromGithub) {
try {
runBlocking {
withContext(Dispatchers.IO) {
@ -379,10 +379,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
apis = allProviders.filter { api ->
val name = api.javaClass.simpleName
// if the provider does not exist in the json file, then it is shown by default
!providersJsonMap.containsKey(name) || acceptableProviders.contains(name) || restrictedApis.contains(name)
!providersJsonMap.containsKey(name) || acceptableProviders.contains(
name
) || restrictedApis.contains(name)
}
}
} catch (e : Exception) {
} catch (e: Exception) {
logError(e)
}
}

View file

@ -39,6 +39,7 @@ import com.google.android.gms.cast.framework.CastContext
import com.google.android.gms.cast.framework.CastState
import com.google.android.material.button.MaterialButton
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.getApiDubstatusSettings
import com.lagradost.cloudstream3.APIHolder.getApiFromName
import com.lagradost.cloudstream3.APIHolder.getId
import com.lagradost.cloudstream3.AcraApplication.Companion.context
@ -583,7 +584,6 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
@SuppressLint("SetTextI18n")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
result_cast_items?.let {
PanelsChildGestureRegionObserver.Provider.get().register(it)
}
@ -1077,6 +1077,65 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
}
}
/**
* Sets next focus to allow navigation up and down between 2 views
* if either of them is null nothing happens.
**/
fun setFocusUpAndDown(upper: View?, down: View?) {
if (upper == null || down == null) return
upper.nextFocusDownId = down.id
down.nextFocusUpId = upper.id
}
// 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
observe(viewModel.selectedSeason) { season ->
result_season_button?.text = fromIndexToSeasonText(season)
}
observe(viewModel.seasonSelections) { seasonList ->
result_season_button?.visibility = if (seasonList.size <= 1) GONE else VISIBLE.also {
// If the season button is visible the result season button will be next focus down
if (result_series_parent?.isVisible == true)
setFocusUpAndDown(result_resume_series_button, result_season_button)
else
setFocusUpAndDown(result_bookmark_button, result_season_button)
}
result_season_button?.setOnClickListener {
result_season_button?.popupMenuNoIconsAndNoStringRes(
items = seasonList
.map { Pair(it ?: -2, fromIndexToSeasonText(it)) },
) {
val id = this.itemId
viewModel.changeSeason(if (id == -2) null else id)
}
}
}
observe(viewModel.selectedRange) { range ->
result_episode_select?.text = range
}
observe(viewModel.rangeOptions) { range ->
episodeRanges = range
result_episode_select?.visibility = if (range.size <= 1) GONE else VISIBLE.also {
// If Season button is invisible then the bookmark button next focus is episode select
if (result_season_button?.isVisible != true) {
if (result_series_parent?.isVisible == true)
setFocusUpAndDown(result_resume_series_button, result_episode_select)
else
setFocusUpAndDown(result_bookmark_button, result_episode_select)
}
}
}
observe(viewModel.episodes) { episodeList ->
lateFixDownloadButton(episodeList.size <= 1) // movies can have multible parts but still be *movies* this will fix this
var isSeriesVisible = false
@ -1118,6 +1177,17 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
}
result_series_parent?.isVisible = isSeriesVisible
if (isSeriesVisible) {
val down = when {
result_season_button?.isVisible == true -> result_season_button
result_episode_select?.isVisible == true -> result_episode_select
result_dub_select?.isVisible == true -> result_dub_select
else -> null
}
setFocusUpAndDown(result_resume_series_button, down)
setFocusUpAndDown(result_bookmark_button, result_resume_series_button)
}
result_resume_progress_holder?.isVisible = isProgressVisible
context?.getString(if (isProgressVisible) R.string.resume else R.string.play_movie_button)
?.let {
@ -1150,28 +1220,7 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
startValue = null
}
observe(viewModel.selectedSeason)
{ season ->
result_season_button?.text = fromIndexToSeasonText(season)
}
observe(viewModel.seasonSelections)
{ seasonList ->
result_season_button?.visibility = if (seasonList.size <= 1) GONE else VISIBLE
result_season_button?.setOnClickListener {
result_season_button?.popupMenuNoIconsAndNoStringRes(
items = seasonList
.map { Pair(it ?: -2, fromIndexToSeasonText(it)) },
) {
val id = this.itemId
viewModel.changeSeason(if (id == -2) null else id)
}
}
}
observe(viewModel.publicEpisodes)
{ episodes ->
observe(viewModel.publicEpisodes) { episodes ->
when (episodes) {
is Resource.Failure -> {
result_episode_loading?.isVisible = false
@ -1193,42 +1242,50 @@ class ResultFragment : Fragment(), PanelsChildGestureRegionObserver.GestureRegio
}
}
observe(viewModel.dubStatus)
{ status ->
observe(viewModel.dubStatus) { status ->
result_dub_select?.text = status.toString()
}
observe(viewModel.dubSubSelections)
{ range ->
val preferDub = context?.getApiDubstatusSettings()?.all { it == DubStatus.Dubbed } == true
observe(viewModel.dubSubSelections) { range ->
dubRange = range
if (preferDub && dubRange?.contains(DubStatus.Dubbed) == true){
viewModel.changeDubStatus(DubStatus.Dubbed)
}
result_dub_select?.visibility = if (range.size <= 1) GONE else VISIBLE
if (result_season_button?.isVisible != true && result_episode_select?.isVisible != true) {
if (result_series_parent?.isVisible == true)
setFocusUpAndDown(result_resume_series_button, result_dub_select)
else
setFocusUpAndDown(result_bookmark_button, result_dub_select)
}
}
result_cast_items?.setOnFocusChangeListener { v, hasFocus ->
// Always escape focus
if (hasFocus) result_bookmark_button?.requestFocus()
}
result_dub_select.setOnClickListener {
val ranges = dubRange
if (ranges != null) {
it.popupMenuNoIconsAndNoStringRes(ranges.map { status ->
Pair(
status.ordinal,
status.toString()
)
}
it.popupMenuNoIconsAndNoStringRes(ranges
.map { status ->
Pair(
status.ordinal,
status.toString()
)
}
.toList()) {
viewModel.changeDubStatus(DubStatus.values()[itemId])
}
}
}
observe(viewModel.selectedRange)
{ range ->
result_episode_select?.text = range
}
observe(viewModel.rangeOptions) { range ->
episodeRanges = range
result_episode_select?.visibility = if (range.size <= 1) GONE else VISIBLE
}
result_episode_select.setOnClickListener {
val ranges = episodeRanges
if (ranges != null) {

View file

@ -44,7 +44,6 @@ class ResultViewModel : ViewModel() {
private var repo: APIRepository? = null
private var generator: IGenerator? = null
private val _resultResponse: MutableLiveData<Resource<Any?>> = MutableLiveData()
private val _episodes: MutableLiveData<List<ResultEpisode>> = MutableLiveData()
private val episodeById: MutableLiveData<HashMap<Int, Int>> =
@ -190,7 +189,7 @@ class ResultViewModel : ViewModel() {
}
fun changeDubStatus(status: DubStatus?) {
if(status == null) return
if (status == null) return
dubSubEpisodes.value?.get(status)?.let { episodes ->
id.value?.let {
setDub(it, status)
@ -220,7 +219,10 @@ class ResultViewModel : ViewModel() {
currentSubs.add(sub)
})
return@safeApiCall Pair(currentLinks.toSet(), currentSubs.toSet()) as Pair<Set<ExtractorLink>, Set<SubtitleData>>
return@safeApiCall Pair(
currentLinks.toSet(),
currentSubs.toSet()
)
}
}
@ -247,13 +249,15 @@ class ResultViewModel : ViewModel() {
generator = RepoLinkGenerator(list)
val set = HashMap<Int, Int>()
val range = selectedRangeInt.value
list.withIndex().forEach { set[it.value.id] = it.index }
episodeById.postValue(set)
filterEpisodes(
list,
if (selection == -1) getResultSeason(localId ?: id.value ?: return) else selection, null
if (selection == -1) getResultSeason(localId ?: id.value ?: return) else selection,
range
)
}
@ -330,9 +334,7 @@ class ResultViewModel : ViewModel() {
val status = getDub(mainId)
val statuses = d.episodes.map { it.key }
val dubStatus = if (statuses.contains(status)) status else statuses.first()
_dubStatus.postValue(dubStatus)
_dubSubSelections.postValue(d.episodes.keys)
val fillerEpisodes =
if (showFillers) safeApiCall { getFillerEpisodes(d.name) } else null
@ -366,10 +368,14 @@ class ResultViewModel : ViewModel() {
Pair(ep.key, episodes)
}.toMap()
// These posts needs to be in this order as to make the preferDub in ResultFragment work
_dubSubEpisodes.postValue(res)
res[dubStatus]?.let { episodes ->
updateEpisodes(mainId, episodes, -1)
}
_dubStatus.postValue(dubStatus)
_dubSubSelections.postValue(d.episodes.keys)
}
is TvSeriesLoadResponse -> {

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:color="?attr/colorPrimary" android:state_checked="true"/>
<item android:color="?attr/colorPrimary" android:state_focused="true"/>
<item android:alpha="0.12" android:color="?attr/colorOnSurface" android:state_checked="false"/>
</selector>

File diff suppressed because it is too large Load diff

View file

@ -1,28 +1,28 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingLeft="2dp"
android:paddingRight="2dp"
android:paddingTop="-10dp"
android:layout_marginTop="-4dp"
android:layout_marginBottom="-4dp">
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="-4dp"
android:layout_marginBottom="-4dp"
android:paddingLeft="2dp"
android:paddingTop="-10dp"
android:paddingRight="2dp">
<!--app:strokeColor="@color/colorAccent"-->
<com.google.android.material.button.MaterialButton
android:minHeight="0dp"
android:minWidth="0dp"
android:layout_height="37dp"
android:textAllCaps="false"
android:textSize="12sp"
android:layout_width="wrap_content"
app:cornerRadius="100dp"
tools:text="Test"
android:textColor="?attr/textColor"
style="@style/Widget.MaterialComponents.Button.OutlinedButton"
android:id="@+id/result_tag_card">
android:id="@+id/result_tag_card"
style="@style/Tag"
android:layout_width="wrap_content"
android:layout_height="37dp"
android:minWidth="0dp"
android:minHeight="0dp"
android:textAllCaps="false"
android:textColor="?attr/textColor"
android:textSize="12sp"
app:cornerRadius="100dp"
tools:text="Test">
</com.google.android.material.button.MaterialButton>
</FrameLayout>

View file

@ -186,8 +186,7 @@
<item name="android:windowBackground">?attr/primaryBlackBackground</item>
</style>
<style name="AppSearchViewStyle"
parent="Theme.MaterialComponents.NoActionBar">
<style name="AppSearchViewStyle" parent="Theme.MaterialComponents.NoActionBar">
<item name="android:searchIcon">@drawable/search_icon</item>
<item name="android:queryHint">@string/search_hint</item>
<item name="android:background">@color/transparent</item>
@ -212,8 +211,7 @@
<item name="behavior_peekHeight">512dp</item>
</style>
<style name="PreferenceTheme" parent="@style/AppTheme">
</style>
<style name="PreferenceTheme" parent="@style/AppTheme"></style>
<style name="Theme.AlertDialog" parent="ThemeOverlay.MaterialComponents.Dialog.Alert">
<item name="android:windowMinWidthMajor">@android:dimen/dialog_min_width_major</item>
@ -229,6 +227,7 @@
<style name="AppButtonStyle" parent="android:Widget.Holo.Button">
<item name="android:fontFamily">@font/google_sans</item>
</style>
<style name="AppEditStyle" parent="android:Widget.EditText">
<item name="android:fontFamily">@font/google_sans</item>
</style>
@ -242,8 +241,8 @@
<item name="android:layout_height">wrap_content</item>
<item name="android:minHeight">24dp</item>
<item name="android:minWidth">0dp</item>
<!-- <item name="android:paddingStart">5dp</item>
<item name="android:paddingEnd">5dp</item>-->
<!-- <item name="android:paddingStart">5dp</item>
<item name="android:paddingEnd">5dp</item>-->
</style>
<style name="AppMaterialButtonStyle" parent="Widget.MaterialComponents.Button">
@ -262,6 +261,7 @@
<item name="tabMinWidth">75dp</item>
<item name="tabMode">scrollable</item>-->
</style>
<style name="AlertDialogCustom" parent="Theme.AppCompat.Dialog.Alert">
<item name="android:windowFullscreen">true</item>
<item name="android:textColor">?attr/textColor</item>
@ -278,9 +278,11 @@
<item name="android:windowMinWidthMinor">@android:dimen/dialog_min_width_minor</item>
<item name="android:windowBackground">@drawable/dialog__window_background</item>
</style>
<style name="AlertDialogCustomTransparent" parent="Theme.AppCompat.Dialog.Alert">
<item name="android:windowBackground">@color/transparent</item>
</style>
<style name="AlertDialogCustomBlack" parent="Theme.AppCompat.Dialog.Alert">
<item name="android:windowBackground">?attr/primaryBlackBackground</item>
<item name="android:layout_width">fill_parent</item>
@ -451,6 +453,10 @@
<item name="strokeColor">@color/toggle_button_outline</item>
</style>
<style name="Tag" parent="Widget.MaterialComponents.Button.OutlinedButton">
<item name="strokeColor">@color/tag_stroke_color</item>
</style>
<style name="VideoButtonTV">
<item name="android:stateListAnimator">@null</item>
<item name="strokeColor">@color/transparent</item>
@ -495,26 +501,34 @@
<item name="castPlayButtonDrawable">@drawable/ic_baseline_play_arrow_24</item>
<item name="castPauseButtonDrawable">@drawable/netflix_pause</item>
<item name="castStopButtonDrawable">@drawable/cast_ic_expanded_controller_stop</item>
<item name="castSkipPreviousButtonDrawable">@drawable/cast_ic_expanded_controller_skip_previous</item>
<item name="castSkipNextButtonDrawable">@drawable/cast_ic_expanded_controller_skip_next</item>
<item name="castSkipPreviousButtonDrawable">
@drawable/cast_ic_expanded_controller_skip_previous
</item>
<item name="castSkipNextButtonDrawable">@drawable/cast_ic_expanded_controller_skip_next
</item>
<item name="castRewind30ButtonDrawable">@drawable/go_back_30</item>
<item name="castForward30ButtonDrawable">@drawable/go_forward_30</item>
</style>
<style name="CustomCastMiniController" parent="CastMiniController">
<item name="castMiniControllerLoadingIndicatorColor">?attr/colorPrimary</item>
<item name="castShowImageThumbnail">true</item>
<item name="castTitleTextAppearance">@style/TextAppearance.AppCompat.Subhead</item>
<item name="castSubtitleTextAppearance">@style/TextAppearance.AppCompat.Caption</item>
<item name="castBackground">@color/transparent</item> <!--CHECK bitDarkerGrayBackground darkBackground-->
<item name="castBackground">@color/transparent
</item> <!--CHECK bitDarkerGrayBackground darkBackground-->
<item name="castProgressBarColor">?attr/colorPrimary</item>
<item name="castStopButtonDrawable">@drawable/cast_ic_mini_controller_stop</item>'
<item name="castLargeStopButtonDrawable">@drawable/cast_ic_mini_controller_stop_large</item>
<item name="castSkipPreviousButtonDrawable">@drawable/cast_ic_mini_controller_skip_prev</item>
<item name="castSkipPreviousButtonDrawable">@drawable/cast_ic_mini_controller_skip_prev
</item>
<item name="castSkipNextButtonDrawable">@drawable/cast_ic_mini_controller_skip_next</item>
<item name="castRewind30ButtonDrawable">@drawable/go_back_30</item>
<item name="castForward30ButtonDrawable">@drawable/go_forward_30</item>
<item name="castMuteToggleButtonDrawable">@drawable/cast_ic_mini_controller_mute</item>
<item name="castClosedCaptionsButtonDrawable">@drawable/cast_ic_mini_controller_closed_caption</item>
<item name="castClosedCaptionsButtonDrawable">
@drawable/cast_ic_mini_controller_closed_caption
</item>
</style>
</resources>