Merge remote-tracking branch 'origin/master'

This commit is contained in:
LagradOst 2023-09-17 20:35:11 +02:00
commit bff9727f96
12 changed files with 261 additions and 142 deletions

View file

@ -128,6 +128,7 @@ import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.Coroutines.main
import com.lagradost.cloudstream3.utils.DataStore.getKey import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.DataStore.setKey import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.DataStoreHelper.migrateResumeWatching import com.lagradost.cloudstream3.utils.DataStoreHelper.migrateResumeWatching
import com.lagradost.cloudstream3.utils.Event import com.lagradost.cloudstream3.utils.Event
import com.lagradost.cloudstream3.utils.IOnBackPressed import com.lagradost.cloudstream3.utils.IOnBackPressed
@ -305,6 +306,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
// kinda shitty solution, but cant com main->home otherwise for popups // kinda shitty solution, but cant com main->home otherwise for popups
val bookmarksUpdatedEvent = Event<Boolean>() val bookmarksUpdatedEvent = Event<Boolean>()
/**
* Used by data store helper to fully reload home when switching accounts
*/
val reloadHomeEvent = Event<Boolean>()
/** /**
@ -539,6 +544,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
val isTrueTv = isTrueTvSettings() val isTrueTv = isTrueTvSettings()
navView.menu.findItem(R.id.navigation_library)?.isVisible = !isTrueTv navView.menu.findItem(R.id.navigation_library)?.isVisible = !isTrueTv
navRailView.menu.findItem(R.id.navigation_library)?.isVisible = !isTrueTv navRailView.menu.findItem(R.id.navigation_library)?.isVisible = !isTrueTv
// Hide downloads on TV
navView.menu.findItem(R.id.navigation_downloads)?.isVisible = !isTrueTv
navRailView.menu.findItem(R.id.navigation_downloads)?.isVisible = !isTrueTv
} }
} }
@ -1092,15 +1101,19 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
updateTv() updateTv()
// backup when we update the app, I don't trust myself to not boot lock users, might want to make this a setting? // backup when we update the app, I don't trust myself to not boot lock users, might want to make this a setting?
try { normalSafeApiCall {
val appVer = BuildConfig.VERSION_NAME val appVer = BuildConfig.VERSION_NAME
val lastAppAutoBackup: String = getKey("VERSION_NAME") ?: "" val lastAppAutoBackup: String = getKey("VERSION_NAME") ?: ""
if (appVer != lastAppAutoBackup) { if (appVer != lastAppAutoBackup) {
setKey("VERSION_NAME", BuildConfig.VERSION_NAME) setKey("VERSION_NAME", BuildConfig.VERSION_NAME)
backup() normalSafeApiCall {
backup()
}
normalSafeApiCall {
// Recompile oat on new version
PluginManager.deleteAllOatFiles(this)
}
} }
} catch (t: Throwable) {
logError(t)
} }
// just in case, MAIN SHOULD *NEVER* BOOT LOOP CRASH // just in case, MAIN SHOULD *NEVER* BOOT LOOP CRASH
@ -1112,16 +1125,17 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
newLocalBinding.root.viewTreeObserver.addOnGlobalFocusChangeListener { _, newFocus -> newLocalBinding.root.viewTreeObserver.addOnGlobalFocusChangeListener { _, newFocus ->
// println("refocus $oldFocus -> $newFocus") // println("refocus $oldFocus -> $newFocus")
try { try {
val r = Rect(0,0,0,0) val r = Rect(0, 0, 0, 0)
newFocus.getDrawingRect(r) newFocus.getDrawingRect(r)
val x = r.centerX() val x = r.centerX()
val y = r.centerY() val y = r.centerY()
val dx = 0 //screenWidth / 2 val dx = 0 //screenWidth / 2
val dy = screenHeight / 2 val dy = screenHeight / 2
val r2 = Rect(x-dx,y-dy,x+dx,y+dy) val r2 = Rect(x - dx, y - dy, x + dx, y + dy)
newFocus.requestRectangleOnScreen(r2, false) newFocus.requestRectangleOnScreen(r2, false)
// TvFocus.current =TvFocus.current.copy(y=y.toFloat()) // TvFocus.current =TvFocus.current.copy(y=y.toFloat())
} catch (_ : Throwable) { } } catch (_: Throwable) {
}
TvFocus.updateFocusView(newFocus) TvFocus.updateFocusView(newFocus)
/*var focus = newFocus /*var focus = newFocus
@ -1182,7 +1196,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
} }
} else if (lastError == null) { } else if (lastError == null) {
ioSafe { ioSafe {
getKey<String>(USER_SELECTED_HOMEPAGE_API)?.let { homeApi -> DataStoreHelper.currentHomePage?.let { homeApi ->
mainPluginsLoadedEvent.invoke(loadSinglePlugin(this@MainActivity, homeApi)) mainPluginsLoadedEvent.invoke(loadSinglePlugin(this@MainActivity, homeApi))
} ?: run { } ?: run {
mainPluginsLoadedEvent.invoke(false) mainPluginsLoadedEvent.invoke(false)
@ -1543,6 +1557,11 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
migrateResumeWatching() migrateResumeWatching()
} }
getKey<String>(USER_SELECTED_HOMEPAGE_API)?.let { homepage ->
DataStoreHelper.currentHomePage = homepage
removeKey(USER_SELECTED_HOMEPAGE_API)
}
try { try {
if (getKey(HAS_DONE_SETUP_KEY, false) != true) { if (getKey(HAS_DONE_SETUP_KEY, false) != true) {
navController.navigate(R.id.navigation_setup_language) navController.navigate(R.id.navigation_setup_language)

View file

@ -137,6 +137,20 @@ object PluginManager {
} }
} }
/**
* Deletes all generated oat files which will force Android to recompile the dex extensions.
* This might fix unrecoverable SIGSEGV exceptions when old oat files are loaded in a new app update.
*/
fun deleteAllOatFiles(context: Context) {
File("${context.filesDir}/${ONLINE_PLUGINS_FOLDER}").listFiles()?.forEach { repo ->
repo.listFiles { file -> file.name == "oat" && file.isDirectory }?.forEach { file ->
val success = file.deleteRecursively()
Log.i(TAG, "Deleted oat directory: ${file.absolutePath} Success=$success")
}
}
}
fun getPluginsOnline(): Array<PluginData> { fun getPluginsOnline(): Array<PluginData> {
return getKey(PLUGINS_KEY) ?: emptyArray() return getKey(PLUGINS_KEY) ?: emptyArray()
} }

View file

@ -3,6 +3,7 @@ package com.lagradost.cloudstream3.ui
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View import android.view.View
import androidx.core.view.children
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlin.math.abs import kotlin.math.abs
@ -70,8 +71,8 @@ class GrdLayoutManager(val context: Context, _spanCount: Int) :
val orientation = this.orientation val orientation = this.orientation
// fixes arabic by inverting left and right layout focus // fixes arabic by inverting left and right layout focus
val correctDirection = if(this.isLayoutRTL) { val correctDirection = if (this.isLayoutRTL) {
when(direction) { when (direction) {
View.FOCUS_RIGHT -> View.FOCUS_LEFT View.FOCUS_RIGHT -> View.FOCUS_LEFT
View.FOCUS_LEFT -> View.FOCUS_RIGHT View.FOCUS_LEFT -> View.FOCUS_RIGHT
else -> direction else -> direction
@ -83,12 +84,15 @@ class GrdLayoutManager(val context: Context, _spanCount: Int) :
View.FOCUS_DOWN -> { View.FOCUS_DOWN -> {
return spanCount return spanCount
} }
View.FOCUS_UP -> { View.FOCUS_UP -> {
return -spanCount return -spanCount
} }
View.FOCUS_RIGHT -> { View.FOCUS_RIGHT -> {
return 1 return 1
} }
View.FOCUS_LEFT -> { View.FOCUS_LEFT -> {
return -1 return -1
} }
@ -98,12 +102,15 @@ class GrdLayoutManager(val context: Context, _spanCount: Int) :
View.FOCUS_DOWN -> { View.FOCUS_DOWN -> {
return 1 return 1
} }
View.FOCUS_UP -> { View.FOCUS_UP -> {
return -1 return -1
} }
View.FOCUS_RIGHT -> { View.FOCUS_RIGHT -> {
return spanCount return spanCount
} }
View.FOCUS_LEFT -> { View.FOCUS_LEFT -> {
return -spanCount return -spanCount
} }
@ -156,3 +163,31 @@ class AutofitRecyclerView @JvmOverloads constructor(context: Context, attrs: Att
layoutManager = manager layoutManager = manager
} }
} }
/**
* Recyclerview wherein the max item width or height is set by the biggest view to prevent inconsistent view sizes.
*/
class MaxRecyclerView(ctx: Context, attrs: AttributeSet) : RecyclerView(ctx, attrs) {
private var biggestObserved: Int = 0
private val orientation = LayoutManager.getProperties(context, attrs, 0, 0).orientation
private val isHorizontal = orientation == HORIZONTAL
private fun View.updateMaxSize() {
if (isHorizontal) {
this.minimumHeight = biggestObserved
} else {
this.minimumWidth = biggestObserved
}
}
override fun onChildAttachedToWindow(child: View) {
child.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
val observed = if (isHorizontal) child.measuredHeight else child.measuredWidth
if (observed > biggestObserved) {
biggestObserved = observed
children.forEach { it.updateMaxSize() }
} else {
child.updateMaxSize()
}
super.onChildAttachedToWindow(child)
}
}

View file

@ -69,7 +69,6 @@ import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount import com.lagradost.cloudstream3.utils.UIHelper.getSpanCount
import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes import com.lagradost.cloudstream3.utils.UIHelper.popupMenuNoIconsAndNoStringRes
import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
import java.util.* import java.util.*
@ -669,7 +668,7 @@ class HomeFragment : Fragment() {
} }
homeViewModel.reloadStored() homeViewModel.reloadStored()
homeViewModel.loadAndCancel(getKey(USER_SELECTED_HOMEPAGE_API), false) homeViewModel.loadAndCancel(DataStoreHelper.currentHomePage, false)
//loadHomePage(false) //loadHomePage(false)
// nice profile pic on homepage // nice profile pic on homepage

View file

@ -49,7 +49,6 @@ import com.lagradost.cloudstream3.utils.DataStoreHelper.getBookmarkedData
import com.lagradost.cloudstream3.utils.DataStoreHelper.getLastWatched import com.lagradost.cloudstream3.utils.DataStoreHelper.getLastWatched
import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState import com.lagradost.cloudstream3.utils.DataStoreHelper.getResultWatchState
import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos import com.lagradost.cloudstream3.utils.DataStoreHelper.getViewPos
import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
import com.lagradost.cloudstream3.utils.VideoDownloadHelper import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
@ -426,23 +425,29 @@ class HomeViewModel : ViewModel() {
} }
private fun afterPluginsLoaded(forceReload: Boolean) { private fun afterPluginsLoaded(forceReload: Boolean) {
loadAndCancel(getKey(USER_SELECTED_HOMEPAGE_API), forceReload) loadAndCancel(DataStoreHelper.currentHomePage, forceReload)
} }
private fun afterMainPluginsLoaded(unused: Boolean = false) { private fun afterMainPluginsLoaded(unused: Boolean = false) {
loadAndCancel(getKey(USER_SELECTED_HOMEPAGE_API), false) loadAndCancel(DataStoreHelper.currentHomePage, false)
}
private fun reloadHome(unused: Boolean = false) {
loadAndCancel(DataStoreHelper.currentHomePage, true)
} }
init { init {
MainActivity.bookmarksUpdatedEvent += ::bookmarksUpdated MainActivity.bookmarksUpdatedEvent += ::bookmarksUpdated
MainActivity.afterPluginsLoadedEvent += ::afterPluginsLoaded MainActivity.afterPluginsLoadedEvent += ::afterPluginsLoaded
MainActivity.mainPluginsLoadedEvent += ::afterMainPluginsLoaded MainActivity.mainPluginsLoadedEvent += ::afterMainPluginsLoaded
MainActivity.reloadHomeEvent += ::reloadHome
} }
override fun onCleared() { override fun onCleared() {
MainActivity.bookmarksUpdatedEvent -= ::bookmarksUpdated MainActivity.bookmarksUpdatedEvent -= ::bookmarksUpdated
MainActivity.afterPluginsLoadedEvent -= ::afterPluginsLoaded MainActivity.afterPluginsLoadedEvent -= ::afterPluginsLoaded
MainActivity.mainPluginsLoadedEvent -= ::afterMainPluginsLoaded MainActivity.mainPluginsLoadedEvent -= ::afterMainPluginsLoaded
MainActivity.reloadHomeEvent -= ::reloadHome
super.onCleared() super.onCleared()
} }
@ -495,7 +500,7 @@ class HomeViewModel : ViewModel() {
val api = getApiFromNameNull(preferredApiName) val api = getApiFromNameNull(preferredApiName)
if (preferredApiName == noneApi.name) { if (preferredApiName == noneApi.name) {
// just set to random // just set to random
if (fromUI) setKey(USER_SELECTED_HOMEPAGE_API, noneApi.name) if (fromUI) DataStoreHelper.currentHomePage = noneApi.name
loadAndCancel(noneApi) loadAndCancel(noneApi)
} else if (preferredApiName == randomApi.name) { } else if (preferredApiName == randomApi.name) {
// randomize the api, if none exist like if not loaded or not installed // randomize the api, if none exist like if not loaded or not installed
@ -506,7 +511,7 @@ class HomeViewModel : ViewModel() {
} else { } else {
val apiRandom = validAPIs.random() val apiRandom = validAPIs.random()
loadAndCancel(apiRandom) loadAndCancel(apiRandom)
if (fromUI) setKey(USER_SELECTED_HOMEPAGE_API, apiRandom.name) if (fromUI) DataStoreHelper.currentHomePage = apiRandom.name
} }
} else if (api == null) { } else if (api == null) {
// API is not found aka not loaded or removed, post the loading // API is not found aka not loaded or removed, post the loading
@ -520,7 +525,7 @@ class HomeViewModel : ViewModel() {
} }
} else { } else {
// if the api is found, then set it to it and save key // if the api is found, then set it to it and save key
if (fromUI) setKey(USER_SELECTED_HOMEPAGE_API, api.name) if (fromUI) DataStoreHelper.currentHomePage = api.name
loadAndCancel(api) loadAndCancel(api)
} }
} }

View file

@ -177,7 +177,7 @@ class ResultFragmentTv : Fragment() {
isVisible = true isVisible = true
} }
this.animate().alpha(if (turnVisible) 1.0f else 0.0f).apply { this.animate().alpha(if (turnVisible) 0.97f else 0.0f).apply {
duration = 200 duration = 200
interpolator = DecelerateInterpolator() interpolator = DecelerateInterpolator()
setListener(object : Animator.AnimatorListener { setListener(object : Animator.AnimatorListener {
@ -294,9 +294,9 @@ class ResultFragmentTv : Fragment() {
toggleEpisodes(true) toggleEpisodes(true)
binding?.apply { binding?.apply {
val views = listOf( val views = listOf(
resultDubSelection,
resultSeasonSelection, resultSeasonSelection,
resultRangeSelection, resultRangeSelection,
resultDubSelection,
resultEpisodes, resultEpisodes,
resultPlayTrailer, resultPlayTrailer,
) )

View file

@ -518,7 +518,8 @@ class ResultViewModel2 : ViewModel() {
val episodeNumber = episodes[currentIndex].episode val episodeNumber = episodes[currentIndex].episode
if (episodeNumber < currentMin) { if (episodeNumber < currentMin) {
currentMin = episodeNumber currentMin = episodeNumber
} else if (episodeNumber > currentMax) { }
if (episodeNumber > currentMax) {
currentMax = episodeNumber currentMax = episodeNumber
} }
++currentIndex ++currentIndex

View file

@ -14,7 +14,7 @@ import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.getPref
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setPaddingBottom
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog
import com.lagradost.cloudstream3.utils.SubtitleHelper import com.lagradost.cloudstream3.utils.SubtitleHelper
import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard import com.lagradost.cloudstream3.utils.UIHelper.hideKeyboard
@ -96,7 +96,7 @@ class SettingsProviders : PreferenceFragmentCompat() {
this.getString(R.string.prefer_media_type_key), this.getString(R.string.prefer_media_type_key),
selectedList.map { it.toString() }.toMutableSet() selectedList.map { it.toString() }.toMutableSet()
).apply() ).apply()
removeKey(USER_SELECTED_HOMEPAGE_API) DataStoreHelper.currentHomePage = null
//(context ?: AcraApplication.context)?.let { ctx -> app.initClient(ctx) } //(context ?: AcraApplication.context)?.let { ctx -> app.initClient(ctx) }
} }

View file

@ -15,8 +15,8 @@ import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.TvType
import com.lagradost.cloudstream3.databinding.FragmentSetupMediaBinding import com.lagradost.cloudstream3.databinding.FragmentSetupMediaBinding
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import com.lagradost.cloudstream3.utils.USER_SELECTED_HOMEPAGE_API
class SetupFragmentMedia : Fragment() { class SetupFragmentMedia : Fragment() {
@ -77,7 +77,7 @@ class SetupFragmentMedia : Fragment() {
.apply() .apply()
// Regenerate set homepage // Regenerate set homepage
removeKey(USER_SELECTED_HOMEPAGE_API) DataStoreHelper.currentHomePage = null
} }
} }

View file

@ -77,10 +77,28 @@ object DataStoreHelper {
var selectedKeyIndex by PreferenceDelegate("$TAG/account_key_index", 0) var selectedKeyIndex by PreferenceDelegate("$TAG/account_key_index", 0)
val currentAccount: String get() = selectedKeyIndex.toString() val currentAccount: String get() = selectedKeyIndex.toString()
private fun setAccount(account: Account) { /**
* Get or set the current account homepage.
* Setting this does not automatically reload the homepage.
*/
var currentHomePage: String?
get() = getKey("$currentAccount/$USER_SELECTED_HOMEPAGE_API")
set(value) {
val key = "$currentAccount/$USER_SELECTED_HOMEPAGE_API"
if (value == null) {
removeKey(key)
} else {
setKey(key, value)
}
}
private fun setAccount(account: Account, refreshHomePage: Boolean) {
selectedKeyIndex = account.keyIndex selectedKeyIndex = account.keyIndex
showToast(account.name) showToast(account.name)
MainActivity.bookmarksUpdatedEvent(true) MainActivity.bookmarksUpdatedEvent(true)
if (refreshHomePage) {
MainActivity.reloadHomeEvent(true)
}
} }
private fun editAccount(context: Context, account: Account, isNewAccount: Boolean) { private fun editAccount(context: Context, account: Account, isNewAccount: Boolean) {
@ -112,7 +130,7 @@ object DataStoreHelper {
accounts = currentAccounts.toTypedArray() accounts = currentAccounts.toTypedArray()
// update UI // update UI
setAccount(getDefaultAccount(context)) setAccount(getDefaultAccount(context), true)
MainActivity.bookmarksUpdatedEvent(true) MainActivity.bookmarksUpdatedEvent(true)
dialog?.dismissSafe() dialog?.dismissSafe()
} }
@ -161,8 +179,13 @@ object DataStoreHelper {
currentAccounts.add(currentEditAccount) currentAccounts.add(currentEditAccount)
} }
// Save the current homepage for new accounts
val currentHomePage = DataStoreHelper.currentHomePage
// set the new default account as well as add the key for the new account // set the new default account as well as add the key for the new account
setAccount(currentEditAccount) setAccount(currentEditAccount, false)
DataStoreHelper.currentHomePage = currentHomePage
accounts = currentAccounts.toTypedArray() accounts = currentAccounts.toTypedArray()
dialog.dismissSafe() dialog.dismissSafe()
@ -204,7 +227,7 @@ object DataStoreHelper {
) )
binding.profilesRecyclerview.adapter = WhoIsWatchingAdapter( binding.profilesRecyclerview.adapter = WhoIsWatchingAdapter(
selectCallBack = { account -> selectCallBack = { account ->
setAccount(account) setAccount(account, true)
builder.dismissSafe() builder.dismissSafe()
}, },
addAccountCallback = { addAccountCallback = {
@ -353,7 +376,7 @@ object DataStoreHelper {
removeKeys(folder2) removeKeys(folder2)
} }
fun deleteBookmarkedData(id : Int?) { fun deleteBookmarkedData(id: Int?) {
if (id == null) return if (id == null) return
removeKey("$currentAccount/$RESULT_WATCH_STATE", id.toString()) removeKey("$currentAccount/$RESULT_WATCH_STATE", id.toString())
removeKey("$currentAccount/$RESULT_WATCH_STATE_DATA", id.toString()) removeKey("$currentAccount/$RESULT_WATCH_STATE_DATA", id.toString())

View file

@ -1,6 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"> <shape xmlns:android="http://schemas.android.com/apk/res/android">
<gradient <gradient
android:startColor="@color/transparent" android:centerColor="?attr/primaryBlackBackground"
android:endColor="?attr/primaryBlackBackground"/> android:centerX="0.2"
android:endColor="?attr/primaryBlackBackground"
android:startColor="@color/transparent" />
</shape> </shape>

View file

@ -535,129 +535,150 @@ https://developer.android.com/design/ui/tv/samples/jet-fit
</LinearLayout> </LinearLayout>
</androidx.core.widget.NestedScrollView> </androidx.core.widget.NestedScrollView>
<FrameLayout <androidx.constraintlayout.widget.ConstraintLayout
android:visibility="gone"
tools:visibility="visible"
android:id="@+id/episodes_shadow" android:id="@+id/episodes_shadow"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="end"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"
android:importantForAccessibility="no"
android:src="@drawable/episodes_shadow"/>
<ImageView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="end"
android:clickable="false"
android:focusable="false"
android:focusableInTouchMode="false"
android:importantForAccessibility="no"
android:src="@drawable/episodes_shadow"/>
</FrameLayout>
<LinearLayout
android:id="@+id/episode_holder_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end" android:layout_gravity="end"
android:orientation="horizontal"
android:paddingStart="@dimen/result_padding"
android:paddingEnd="@dimen/result_padding"
android:visibility="gone" android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent" tools:visibility="visible">
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="RtlHardcoded"
tools:visibility="gone">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/result_season_selection"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:descendantFocusability="afterDescendants"
android:nextFocusLeft="@id/result_episodes_show"
android:nextFocusRight="@id/result_range_selection"
android:orientation="vertical"
android:paddingVertical="@dimen/result_padding"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/result_selection" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/result_range_selection"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:descendantFocusability="afterDescendants"
android:nextFocusLeft="@id/result_season_selection"
android:nextFocusRight="@id/result_dub_selection"
android:orientation="vertical"
android:paddingVertical="@dimen/result_padding"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/result_selection"
tools:visibility="visible" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/result_dub_selection"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:descendantFocusability="afterDescendants"
android:nextFocusLeft="@id/result_range_selection"
android:nextFocusRight="@id/result_episodes"
android:orientation="vertical"
android:paddingVertical="@dimen/result_padding"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/result_selection" />
<!--<androidx.core.widget.ContentLoadingProgressBar <!--
android:id="@+id/result_episode_loading" These two shadow spaces are used to create a view which always x% bigger
than the episode_holder_tv. This is required for creating a consistent shadow.
-->
<Space
android:id="@+id/shadow_space_1"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="1:1"
app:layout_constraintEnd_toEndOf="@+id/episode_holder_tv"
app:layout_constraintStart_toStartOf="@+id/episode_holder_tv" />
style="@style/Widget.AppCompat.ProgressBar" <!--
android:layout_gravity="center" The dimension ratio should and episodes_shadow centerX should add up to
android:layout_width="50dp" 100% for the best results. For example (100:80 + 0.2) or (100:70 + 0.3).
android:layout_height="50dp" />--> Bigger centerX => Larger fade distance.
-->
<Space
android:id="@+id/shadow_space_2"
android:layout_width="0dp"
android:layout_height="0dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintDimensionRatio="100:80"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="@+id/shadow_space_1" />
<androidx.recyclerview.widget.RecyclerView <ImageView
android:id="@+id/result_episodes" android:layout_width="0dp"
android:layout_width="match_parent" android:layout_height="match_parent"
android:layout_height="wrap_content" android:layout_gravity="end"
android:clipToPadding="false" android:clickable="false"
android:descendantFocusability="afterDescendants"
android:nextFocusLeft="@id/result_dub_selection"
android:orientation="vertical"
android:paddingVertical="@dimen/result_padding"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/result_episode" />
<View
android:id="@+id/temporary_no_focus"
android:layout_width="1dp"
android:layout_height="1dp"
android:focusable="false" android:focusable="false"
android:nextFocusLeft="@id/temporary_no_focus" android:focusableInTouchMode="false"
android:nextFocusRight="@id/temporary_no_focus" android:importantForAccessibility="no"
android:nextFocusUp="@id/temporary_no_focus" android:src="@drawable/episodes_shadow"
android:nextFocusDown="@id/temporary_no_focus" /> app:layout_constraintEnd_toEndOf="parent"
</LinearLayout> app:layout_constraintStart_toStartOf="@+id/shadow_space_2"
app:layout_constraintTop_toTopOf="parent" />
<LinearLayout
android:id="@+id/episode_holder_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:orientation="horizontal"
android:paddingStart="@dimen/result_padding"
android:paddingEnd="@dimen/result_padding"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:ignore="RtlHardcoded"
tools:visibility="visible">
<com.lagradost.cloudstream3.ui.MaxRecyclerView
android:id="@+id/result_dub_selection"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:descendantFocusability="afterDescendants"
android:nextFocusLeft="@id/result_episodes_show"
android:nextFocusRight="@id/result_season_selection"
android:orientation="vertical"
android:paddingVertical="@dimen/result_padding"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/result_selection"
tools:visibility="gone" />
<com.lagradost.cloudstream3.ui.MaxRecyclerView
android:id="@+id/result_season_selection"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:descendantFocusability="afterDescendants"
android:nextFocusLeft="@id/result_dub_selection"
android:nextFocusRight="@id/result_range_selection"
android:orientation="vertical"
android:paddingVertical="@dimen/result_padding"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/result_selection"
tools:visibility="gone" />
<com.lagradost.cloudstream3.ui.MaxRecyclerView
android:id="@+id/result_range_selection"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:descendantFocusability="afterDescendants"
android:nextFocusLeft="@id/result_season_selection"
android:nextFocusRight="@id/result_episodes"
android:orientation="vertical"
android:paddingVertical="@dimen/result_padding"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/result_selection"
tools:visibility="visible" />
<!--<androidx.core.widget.ContentLoadingProgressBar
android:id="@+id/result_episode_loading"
style="@style/Widget.AppCompat.ProgressBar"
android:layout_gravity="center"
android:layout_width="50dp"
android:layout_height="50dp" />-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/result_episodes"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:clipToPadding="false"
android:descendantFocusability="afterDescendants"
android:nextFocusLeft="@id/result_range_selection"
android:orientation="vertical"
android:paddingVertical="@dimen/result_padding"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="@layout/result_episode" />
<View
android:id="@+id/temporary_no_focus"
android:layout_width="1dp"
android:layout_height="1dp"
android:focusable="false"
android:nextFocusLeft="@id/temporary_no_focus"
android:nextFocusRight="@id/temporary_no_focus"
android:nextFocusUp="@id/temporary_no_focus"
android:nextFocusDown="@id/temporary_no_focus" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<androidx.core.widget.NestedScrollView <androidx.core.widget.NestedScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"