alert fix + synchronized + bump + homepage load fix + small focus change

This commit is contained in:
LagradOst 2023-07-30 05:05:13 +02:00
parent c987f7581e
commit 3bdbb35754
26 changed files with 265 additions and 156 deletions

View File

@ -52,7 +52,7 @@ android {
targetSdk = 33
versionCode = 59
versionName = "4.0.1"
versionName = "4.1.1"
resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}")

View File

@ -46,9 +46,9 @@ class TestApplication : Activity() {
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
private fun getAllProviders(): List<MainAPI> {
private fun getAllProviders(): Array<MainAPI> {
println("Providers: ${APIHolder.allProviders.size}")
return APIHolder.allProviders //.filter { !it.usesWebView }
return APIHolder.allProviders.toTypedArray() //.filter { !it.usesWebView }
}
@Test
@ -147,7 +147,7 @@ class ExampleInstrumentedTest {
@Test
fun providerCorrectHomepage() {
runBlocking {
getAllProviders().amap { api ->
getAllProviders().toList().amap { api ->
TestingUtils.testHomepage(api, ::println)
}
}

View File

@ -9,6 +9,7 @@ import android.content.res.Resources
import android.os.Build
import android.util.Log
import android.view.*
import android.view.View.NO_ID
import android.widget.TextView
import android.widget.Toast
import androidx.activity.ComponentActivity
@ -295,6 +296,30 @@ object CommonActivity {
) // THEME IS SET BEFORE VIEW IS CREATED TO APPLY THE THEME TO THE MAIN VIEW
}
/** because we want closes find, aka when multiple have the same id, we go to parent
until the correct one is found */
private fun localLook(from: View, id: Int): View? {
if (id == NO_ID) return null
var currentLook: View = from
while (true) {
currentLook.findViewById<View?>(id)?.let { return it }
currentLook = (currentLook.parent as? View) ?: break
}
return null
}
/*var currentLook: View = view
while (true) {
val tmpNext = currentLook.findViewById<View?>(nextId)
if (tmpNext != null) {
next = tmpNext
break
}
currentLook = currentLook.parent as? View ?: break
}*/
/** recursively looks for a next focus up to a depth of 10,
* this is used to override the normal shit focus system
* because this application has a lot of invisible views that messes with some tv devices*/
private fun getNextFocus(
act: Activity?,
view: View?,
@ -306,7 +331,7 @@ object CommonActivity {
return null
}
val nextId = when (direction) {
var nextId = when (direction) {
FocusDirection.Start -> {
if (view.isRtl())
view.nextFocusRightId
@ -330,22 +355,16 @@ object CommonActivity {
}
}
// if view not found then return
if (nextId == -1) return null
if (nextId == NO_ID) {
// if not specified then use forward id
nextId = view.nextFocusForwardId
// if view is still not found to next focus then return and let android decide
if (nextId == NO_ID) return null
}
var next = act.findViewById<View?>(nextId) ?: return null
// because we want closes find, aka when multiple have the same id, we go to parent
// until the correct one is found
/*var currentLook: View = view
while (true) {
val tmpNext = currentLook.findViewById<View?>(nextId)
if (tmpNext != null) {
next = tmpNext
break
}
currentLook = currentLook.parent as? View ?: break
}*/
next = localLook(view, nextId) ?: next
var currentLook: View = view
while (currentLook.findViewById<View?>(nextId)?.also { next = it } == null) {
@ -362,7 +381,7 @@ object CommonActivity {
return next
}
enum class FocusDirection {
private enum class FocusDirection {
Start,
End,
Up,
@ -463,6 +482,7 @@ object CommonActivity {
//}
}
/** overrides focus and custom key events */
fun dispatchKeyEvent(act: Activity?, event: KeyEvent?): Boolean? {
if (act == null) return null
val currentFocus = act.currentFocus
@ -503,7 +523,9 @@ object CommonActivity {
return true
}
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER && (act.currentFocus is SearchView || act.currentFocus is SearchView.SearchAutoComplete)) {
if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER &&
(act.currentFocus is SearchView || act.currentFocus is SearchView.SearchAutoComplete)
) {
UIHelper.showInputMethod(act.currentFocus?.findFocus())
}
@ -516,6 +538,8 @@ object CommonActivity {
}
// if someone else want to override the focus then don't handle the event as it is already
// consumed. used in video player
if (keyEventListener?.invoke(Pair(event, false)) == true) {
return true
}

View File

@ -50,8 +50,10 @@ object APIHolder {
val allProviders = threadSafeListOf<MainAPI>()
fun initAll() {
for (api in allProviders) {
api.init()
synchronized(allProviders) {
for (api in allProviders) {
api.init()
}
}
apiMap = null
}
@ -64,27 +66,35 @@ object APIHolder {
var apiMap: Map<String, Int>? = null
fun addPluginMapping(plugin: MainAPI) {
apis = apis + plugin
synchronized(apis) {
apis = apis + plugin
}
initMap(true)
}
fun removePluginMapping(plugin: MainAPI) {
apis = apis.filter { it != plugin }
synchronized(apis) {
apis = apis.filter { it != plugin }
}
initMap(true)
}
private fun initMap(forcedUpdate: Boolean = false) {
if (apiMap == null || forcedUpdate)
apiMap = apis.mapIndexed { index, api -> api.name to index }.toMap()
synchronized(apis) {
if (apiMap == null || forcedUpdate)
apiMap = apis.mapIndexed { index, api -> api.name to index }.toMap()
}
}
fun getApiFromNameNull(apiName: String?): MainAPI? {
if (apiName == null) return null
synchronized(allProviders) {
initMap()
return apiMap?.get(apiName)?.let { apis.getOrNull(it) }
// Leave the ?. null check, it can crash regardless
?: allProviders.firstOrNull { it.name == apiName }
synchronized(apis) {
return apiMap?.get(apiName)?.let { apis.getOrNull(it) }
// Leave the ?. null check, it can crash regardless
?: allProviders.firstOrNull { it.name == apiName }
}
}
}
@ -215,7 +225,7 @@ object APIHolder {
val hashSet = HashSet<String>()
val activeLangs = getApiProviderLangSettings()
val hasUniversal = activeLangs.contains(AllLanguagesName)
hashSet.addAll(apis.filter { hasUniversal || activeLangs.contains(it.lang) }
hashSet.addAll(synchronized(apis) { apis.filter { hasUniversal || activeLangs.contains(it.lang) } }
.map { it.name })
/*val set = settingsManager.getStringSet(
@ -314,8 +324,9 @@ object APIHolder {
} ?: default
val langs = this.getApiProviderLangSettings()
val hasUniversal = langs.contains(AllLanguagesName)
val allApis = apis.filter { hasUniversal || langs.contains(it.lang) }
.filter { api -> api.hasMainPage || !hasHomePageIsRequired }
val allApis = synchronized(apis) {
apis.filter { api -> (hasUniversal || langs.contains(api.lang)) && (api.hasMainPage || !hasHomePageIsRequired) }
}
return if (currentPrefMedia.isEmpty()) {
allApis
} else {
@ -736,6 +747,7 @@ fun fixTitle(str: String): String {
.replaceFirstChar { char -> if (char.isLowerCase()) char.titlecase(Locale.getDefault()) else it }
}
}
/**
* Get rhino context in a safe way as it needs to be initialized on the main thread.
* Make sure you get the scope using: val scope: Scriptable = rhino.initSafeStandardObjects()

View File

@ -382,10 +382,12 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
this.navigate(R.id.navigation_downloads)
return true
} else {
for (api in apis) {
if (str.startsWith(api.mainUrl)) {
loadResult(str, api.name)
return true
synchronized(apis) {
for (api in apis) {
if (str.startsWith(api.mainUrl)) {
loadResult(str, api.name)
return true
}
}
}
}
@ -464,9 +466,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
binding?.navHostFragment?.apply {
val params = layoutParams as ConstraintLayout.LayoutParams
val push = if (!dontPush && isTvSettings()) resources.getDimensionPixelSize(R.dimen.navbar_width) else 0
val push =
if (!dontPush && isTvSettings()) resources.getDimensionPixelSize(R.dimen.navbar_width) else 0
if(!this.isLtr()) {
if (!this.isLtr()) {
params.setMargins(
params.leftMargin,
params.topMargin,
@ -695,27 +698,29 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
private fun onAllPluginsLoaded(success: Boolean = false) {
ioSafe {
pluginsLock.withLock {
// Load cloned sites after plugins have been loaded since clones depend on plugins.
try {
getKey<Array<SettingsGeneral.CustomSite>>(USER_PROVIDER_API)?.let { list ->
list.forEach { custom ->
allProviders.firstOrNull { it.javaClass.simpleName == custom.parentJavaClass }
?.let {
allProviders.add(it.javaClass.newInstance().apply {
name = custom.name
lang = custom.lang
mainUrl = custom.url.trimEnd('/')
canBeOverridden = false
})
}
synchronized(allProviders) {
// Load cloned sites after plugins have been loaded since clones depend on plugins.
try {
getKey<Array<SettingsGeneral.CustomSite>>(USER_PROVIDER_API)?.let { list ->
list.forEach { custom ->
allProviders.firstOrNull { it.javaClass.simpleName == custom.parentJavaClass }
?.let {
allProviders.add(it.javaClass.newInstance().apply {
name = custom.name
lang = custom.lang
mainUrl = custom.url.trimEnd('/')
canBeOverridden = false
})
}
}
}
// it.hashCode() is not enough to make sure they are distinct
apis =
allProviders.distinctBy { it.lang + it.name + it.mainUrl + it.javaClass.name }
APIHolder.apiMap = null
} catch (e: Exception) {
logError(e)
}
// it.hashCode() is not enough to make sure they are distinct
apis =
allProviders.distinctBy { it.lang + it.name + it.mainUrl + it.javaClass.name }
APIHolder.apiMap = null
} catch (e: Exception) {
logError(e)
}
}
}
@ -814,6 +819,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
translationX = target.x
translationY = target.y
bringToFront()
}
}
@ -839,10 +845,10 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
val out = IntArray(2)
newFocus.getLocationInWindow(out)
val (screenX, screenY) = out
var (x,y) = screenX.toFloat() to screenY.toFloat()
var (x, y) = screenX.toFloat() to screenY.toFloat()
val (currentX, currentY) = focusOutline.translationX to focusOutline.translationY
// println(">><<< $x $y $currentX $currentY")
if(!newFocus.isLtr()) {
// println(">><<< $x $y $currentX $currentY")
if (!newFocus.isLtr()) {
x = x - focusOutline.rootView.width + newFocus.measuredWidth
}
@ -1195,7 +1201,9 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
ioSafe {
initAll()
// No duplicates (which can happen by registerMainAPI)
apis = allProviders.distinctBy { it }
apis = synchronized(allProviders) {
allProviders.distinctBy { it }
}
}
// val navView: BottomNavigationView = findViewById(R.id.nav_view)
@ -1347,8 +1355,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
}*/
if (BuildConfig.DEBUG) {
try {
var providersAndroidManifestString = "Current androidmanifest should be:\n"
var providersAndroidManifestString = "Current androidmanifest should be:\n"
synchronized(allProviders) {
for (api in allProviders) {
providersAndroidManifestString += "<data android:scheme=\"https\" android:host=\"${
api.mainUrl.removePrefix(
@ -1356,12 +1364,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
)
}\" android:pathPrefix=\"/\"/>\n"
}
println(providersAndroidManifestString)
} catch (t: Throwable) {
logError(t)
}
println(providersAndroidManifestString)
}
handleAppIntent(intent)

View File

@ -21,10 +21,11 @@ class CrossTmdbProvider : TmdbProvider() {
return Regex("""[^a-zA-Z0-9-]""").replace(name, "")
}
private val validApis by lazy {
apis.filter { it.lang == this.lang && it::class.java != this::class.java }
//.distinctBy { it.uniqueId }
}
private val validApis
get() =
synchronized(apis) { apis.filter { it.lang == this.lang && it::class.java != this::class.java } }
//.distinctBy { it.uniqueId }
data class CrossMetaData(
@JsonProperty("isSuccess") val isSuccess: Boolean,
@ -60,7 +61,8 @@ class CrossTmdbProvider : TmdbProvider() {
override suspend fun load(url: String): LoadResponse? {
val base = super.load(url)?.apply {
this.recommendations = this.recommendations?.filterIsInstance<MovieSearchResponse>() // TODO REMOVE
this.recommendations =
this.recommendations?.filterIsInstance<MovieSearchResponse>() // TODO REMOVE
val matchName = filterName(this.name)
when (this) {
is MovieLoadResponse -> {
@ -98,6 +100,7 @@ class CrossTmdbProvider : TmdbProvider() {
this.dataUrl =
CrossMetaData(true, data.map { it.apiName to it.dataUrl }).toJson()
}
else -> {
throw ErrorLoadingException("Nothing besides movies are implemented for this provider")
}

View File

@ -25,13 +25,16 @@ class MultiAnimeProvider : MainAPI() {
}
}
private val validApis by lazy {
APIHolder.apis.filter {
it.lang == this.lang && it::class.java != this::class.java && it.supportedTypes.contains(
TvType.Anime
)
}
}
private val validApis
get() =
synchronized(APIHolder.apis) {
APIHolder.apis.filter {
it.lang == this.lang && it::class.java != this::class.java && it.supportedTypes.contains(
TvType.Anime
)
}
}
private fun filterName(name: String): String {
return Regex("""[^a-zA-Z0-9-]""").replace(name, "")

View File

@ -36,7 +36,9 @@ abstract class Plugin {
Log.i(PLUGIN_TAG, "Adding ${element.name} (${element.mainUrl}) MainAPI")
element.sourcePlugin = this.__filename
// Race condition causing which would case duplicates if not for distinctBy
APIHolder.allProviders.add(element)
synchronized(APIHolder.allProviders) {
APIHolder.allProviders.add(element)
}
APIHolder.addPluginMapping(element)
}
@ -51,10 +53,14 @@ abstract class Plugin {
}
class Manifest {
@JsonProperty("name") var name: String? = null
@JsonProperty("pluginClassName") var pluginClassName: String? = null
@JsonProperty("version") var version: Int? = null
@JsonProperty("requiresResources") var requiresResources: Boolean = false
@JsonProperty("name")
var name: String? = null
@JsonProperty("pluginClassName")
var pluginClassName: String? = null
@JsonProperty("version")
var version: Int? = null
@JsonProperty("requiresResources")
var requiresResources: Boolean = false
}
/**

View File

@ -163,7 +163,8 @@ object PluginManager {
private val classLoaders: MutableMap<PathClassLoader, Plugin> =
HashMap<PathClassLoader, Plugin>()
private var loadedLocalPlugins = false
var loadedLocalPlugins = false
private set
private val gson = Gson()
private suspend fun maybeLoadPlugin(context: Context, file: File) {
@ -531,10 +532,14 @@ object PluginManager {
}
// remove all registered apis
APIHolder.apis.filter { api -> api.sourcePlugin == plugin.__filename }.forEach {
removePluginMapping(it)
synchronized(APIHolder.apis) {
APIHolder.apis.filter { api -> api.sourcePlugin == plugin.__filename }.forEach {
removePluginMapping(it)
}
}
synchronized(APIHolder.allProviders) {
APIHolder.allProviders.removeIf { provider: MainAPI -> provider.sourcePlugin == plugin.__filename }
}
APIHolder.allProviders.removeIf { provider: MainAPI -> provider.sourcePlugin == plugin.__filename }
extractorApis.removeIf { provider: ExtractorApi -> provider.sourcePlugin == plugin.__filename }
classLoaders.values.removeIf { v -> v == plugin }

View File

@ -462,7 +462,7 @@ class HomeFragment : Fragment() {
private val apiChangeClickListener = View.OnClickListener { view ->
view.context.selectHomepage(currentApiName) { api ->
homeViewModel.loadAndCancel(api)
homeViewModel.loadAndCancel(api, forceReload = true,fromUI = true)
}
/*val validAPIs = view.context?.filterProviderByPreferredMedia()?.toMutableList() ?: mutableListOf()
@ -652,6 +652,7 @@ class HomeFragment : Fragment() {
}
homeViewModel.reloadStored()
homeViewModel.loadAndCancel(getKey(USER_SELECTED_HOMEPAGE_API), false)
//loadHomePage(false)
// nice profile pic on homepage

View File

@ -447,12 +447,12 @@ class HomeParentItemAdapterPreview(
(binding as? FragmentHomeHeadTvBinding)?.apply {
homePreviewChangeApi.setOnClickListener { view ->
view.context.selectHomepage(viewModel.repo?.name) { api ->
viewModel.loadAndCancel(api)
viewModel.loadAndCancel(api, forceReload = true, fromUI = true)
}
}
homePreviewChangeApi2.setOnClickListener { view ->
view.context.selectHomepage(viewModel.repo?.name) { api ->
viewModel.loadAndCancel(api)
viewModel.loadAndCancel(api, forceReload = true, fromUI = true)
}
}

View File

@ -5,7 +5,6 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.lagradost.cloudstream3.*
import com.lagradost.cloudstream3.APIHolder.apis
import com.lagradost.cloudstream3.APIHolder.filterHomePageListByFilmQuality
import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia
@ -15,12 +14,22 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.context
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.CommonActivity.activity
import com.lagradost.cloudstream3.mvvm.*
import com.lagradost.cloudstream3.HomePageList
import com.lagradost.cloudstream3.LoadResponse
import com.lagradost.cloudstream3.MainAPI
import com.lagradost.cloudstream3.MainActivity
import com.lagradost.cloudstream3.SearchResponse
import com.lagradost.cloudstream3.amap
import com.lagradost.cloudstream3.mvvm.Resource
import com.lagradost.cloudstream3.mvvm.debugAssert
import com.lagradost.cloudstream3.mvvm.debugWarning
import com.lagradost.cloudstream3.mvvm.launchSafe
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.plugins.PluginManager
import com.lagradost.cloudstream3.ui.APIRepository
import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi
import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi
import com.lagradost.cloudstream3.ui.WatchType
import com.lagradost.cloudstream3.ui.home.HomeFragment.Companion.loadHomepageList
import com.lagradost.cloudstream3.ui.quicksearch.QuickSearchFragment
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_FOCUSED
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
@ -30,8 +39,6 @@ import com.lagradost.cloudstream3.utils.AppUtils.addProgramsToContinueWatching
import com.lagradost.cloudstream3.utils.AppUtils.loadResult
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE
import com.lagradost.cloudstream3.utils.DataStore.getKey
import com.lagradost.cloudstream3.utils.DataStore.setKey
import com.lagradost.cloudstream3.utils.DataStoreHelper
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllResumeStateIds
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllWatchStateIds
@ -44,7 +51,7 @@ import com.lagradost.cloudstream3.utils.VideoDownloadHelper
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.withContext
import java.util.*
import java.util.EnumSet
import kotlin.collections.set
class HomeViewModel : ViewModel() {
@ -95,7 +102,7 @@ class HomeViewModel : ViewModel() {
private var currentShuffledList: List<SearchResponse> = listOf()
private fun autoloadRepo(): APIRepository {
return APIRepository(apis.first { it.hasMainPage })
return APIRepository(synchronized(apis) { apis.first { it.hasMainPage }})
}
private val _availableWatchStatusTypes =
@ -177,8 +184,10 @@ class HomeViewModel : ViewModel() {
}
private var onGoingLoad: Job? = null
private fun loadAndCancel(api: MainAPI?) {
private var isCurrentlyLoadingName : String? = null
private fun loadAndCancel(api: MainAPI) {
onGoingLoad?.cancel()
isCurrentlyLoadingName = api.name
onGoingLoad = load(api)
}
@ -280,12 +289,12 @@ class HomeViewModel : ViewModel() {
}
}
private fun load(api: MainAPI?) = ioSafe {
repo = if (api != null) {
private fun load(api: MainAPI) : Job = ioSafe {
repo = //if (api != null) {
APIRepository(api)
} else {
autoloadRepo()
}
//} else {
// autoloadRepo()
//}
_apiName.postValue(repo?.name)
_randomItems.postValue(listOf())
@ -299,6 +308,7 @@ class HomeViewModel : ViewModel() {
_page.postValue(Resource.Loading())
_preview.postValue(Resource.Loading())
// cancel the current preview expand as that is no longer relevant
addJob?.cancel()
when (val data = repo?.getMainPage(1, null)) {
@ -370,7 +380,7 @@ class HomeViewModel : ViewModel() {
else -> Unit
}
onGoingLoad = null
isCurrentlyLoadingName = null
}
fun click(callback: SearchClickCallback) {
@ -437,33 +447,51 @@ class HomeViewModel : ViewModel() {
loadResult(load.response.url, load.response.apiName, load.action)
}
fun loadAndCancel(preferredApiName: String?, forceReload: Boolean = true) =
viewModelScope.launchSafe {
// only save the key if it is from UI, as we don't want internal functions changing the setting
fun loadAndCancel(
preferredApiName: String?,
forceReload: Boolean = true,
fromUI: Boolean = false
) =
ioSafe {
// Since plugins are loaded in stages this function can get called multiple times.
// The issue with this is that the homepage may be fetched multiple times while the first request is loading
val api = getApiFromNameNull(preferredApiName)
if (!forceReload && api?.let { expandable[it.name]?.list?.list?.isNotEmpty() } == true) {
return@launchSafe
// api?.let { expandable[it.name]?.list?.list?.isNotEmpty() } == true
val currentPage = page.value
// if we don't need to reload and we have a valid homepage or currently loading the same thing then return
val currentLoading = isCurrentlyLoadingName
if (!forceReload && (currentPage is Resource.Success && currentPage.value.isNotEmpty() || (currentLoading != null && currentLoading == preferredApiName))) {
return@ioSafe
}
if (preferredApiName == noneApi.name) {
setKey(USER_SELECTED_HOMEPAGE_API, noneApi.name)
// just set to random
if (fromUI) setKey(USER_SELECTED_HOMEPAGE_API, noneApi.name)
loadAndCancel(noneApi)
} else if (preferredApiName == randomApi.name) {
// randomize the api, if none exist like if not loaded or not installed
// then use nothing
val validAPIs = context?.filterProviderByPreferredMedia()
if (validAPIs.isNullOrEmpty()) {
// Do not set USER_SELECTED_HOMEPAGE_API when there is no plugins loaded
loadAndCancel(noneApi)
} else {
val apiRandom = validAPIs.random()
loadAndCancel(apiRandom)
setKey(USER_SELECTED_HOMEPAGE_API, apiRandom.name)
if (fromUI) setKey(USER_SELECTED_HOMEPAGE_API, apiRandom.name)
}
// If the plugin isn't loaded yet. (Does not set the key)
} else if (api == null) {
loadAndCancel(noneApi)
// API is not found aka not loaded or removed, post the loading
// progress if waiting for plugins, otherwise nothing
if(PluginManager.loadedLocalPlugins) {
loadAndCancel(noneApi)
} else {
_page.postValue(Resource.Loading())
}
} else {
setKey(USER_SELECTED_HOMEPAGE_API, api.name)
// if the api is found, then set it to it and save key
if (fromUI) setKey(USER_SELECTED_HOMEPAGE_API, api.name)
loadAndCancel(api)
}
}

View File

@ -163,12 +163,14 @@ class LibraryFragment : Fragment() {
syncId: SyncIdName,
apiName: String? = null,
) {
val availableProviders = allProviders.filter {
it.supportedSyncNames.contains(syncId)
}.map { it.name } +
// Add the api if it exists
(APIHolder.getApiFromNameNull(apiName)?.let { listOf(it.name) } ?: emptyList())
val availableProviders = synchronized(allProviders) {
allProviders.filter {
it.supportedSyncNames.contains(syncId)
}.map { it.name } +
// Add the api if it exists
(APIHolder.getApiFromNameNull(apiName)?.let { listOf(it.name) }
?: emptyList())
}
val baseOptions = listOf(
LibraryOpenerType.Default,
LibraryOpenerType.None,

View File

@ -321,7 +321,7 @@ data class ExtractedTrailerData(
class ResultViewModel2 : ViewModel() {
private var currentResponse: LoadResponse? = null
var EPISODE_RANGE_SIZE : Int = 20
var EPISODE_RANGE_SIZE: Int = 20
fun clear() {
currentResponse = null
_page.postValue(null)
@ -456,7 +456,7 @@ class ResultViewModel2 : ViewModel() {
currentResponse.year
)
)
if(currentWatchType != status) {
if (currentWatchType != status) {
MainActivity.bookmarksUpdatedEvent(true)
}
}
@ -477,7 +477,10 @@ class ResultViewModel2 : ViewModel() {
)
)
private fun getRanges(allEpisodes: Map<EpisodeIndexer, List<ResultEpisode>>, EPISODE_RANGE_SIZE : Int): Map<EpisodeIndexer, List<EpisodeRange>> {
private fun getRanges(
allEpisodes: Map<EpisodeIndexer, List<ResultEpisode>>,
EPISODE_RANGE_SIZE: Int
): Map<EpisodeIndexer, List<EpisodeRange>> {
return allEpisodes.keys.mapNotNull { index ->
val episodes =
allEpisodes[index] ?: return@mapNotNull null // this should never happened
@ -1505,13 +1508,14 @@ class ResultViewModel2 : ViewModel() {
}
val realRecommendations = ArrayList<SearchResponse>()
val apiNames = apis.filter {
it.name.contains("gogoanime", true) ||
it.name.contains("9anime", true)
}.map {
it.name
val apiNames = synchronized(apis) {
apis.filter {
it.name.contains("gogoanime", true) ||
it.name.contains("9anime", true)
}.map {
it.name
}
}
meta.recommendations?.forEach { rec ->
apiNames.forEach { name ->
realRecommendations.add(rec.copy(apiName = name))

View File

@ -37,7 +37,7 @@ class SearchViewModel : ViewModel() {
private val _currentHistory: MutableLiveData<List<SearchHistoryItem>> = MutableLiveData()
val currentHistory: LiveData<List<SearchHistoryItem>> get() = _currentHistory
private var repos = apis.map { APIRepository(it) }
private var repos = synchronized(apis) { apis.map { APIRepository(it) } }
fun clearSearch() {
_searchResponse.postValue(Resource.Success(ArrayList()))
@ -48,7 +48,7 @@ class SearchViewModel : ViewModel() {
private var onGoingSearch: Job? = null
fun reloadRepos() {
repos = apis.map { APIRepository(it) }
repos = synchronized(apis) { apis.map { APIRepository(it) } }
}
fun searchAndCancel(

View File

@ -8,7 +8,9 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageView
import androidx.annotation.StringRes
import androidx.core.view.children
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.preference.Preference
@ -74,6 +76,7 @@ class SettingsFragment : Fragment() {
settingsToolbar.apply {
setTitle(title)
setNavigationIcon(R.drawable.ic_baseline_arrow_back_24)
children.firstOrNull { it is ImageView }?.tag = getString(R.string.tv_no_focus_tag)
setNavigationOnClickListener {
activity?.onBackPressed()
}

View File

@ -20,6 +20,7 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
import com.lagradost.cloudstream3.CommonActivity
import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.MainActivity
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.app
import com.lagradost.cloudstream3.databinding.AddRemoveSitesBinding
@ -188,7 +189,7 @@ class SettingsGeneral : PreferenceFragmentCompat() {
fun showAdd() {
val providers = allProviders.distinctBy { it.javaClass }.sortedBy { it.name }
val providers = synchronized(allProviders) { allProviders.distinctBy { it.javaClass }.sortedBy { it.name } }
activity?.showDialog(
providers.map { "${it.name} (${it.mainUrl})" },
-1,
@ -221,6 +222,8 @@ class SettingsGeneral : PreferenceFragmentCompat() {
val newSite = CustomSite(provider.javaClass.simpleName, name, url, realLang)
current.add(newSite)
setKey(USER_PROVIDER_API, current.toTypedArray())
// reload apis
MainActivity.afterPluginsLoadedEvent.invoke(false)
dialog.dismissSafe(activity)
}

View File

@ -105,8 +105,10 @@ class SettingsProviders : PreferenceFragmentCompat() {
getPref(R.string.provider_lang_key)?.setOnPreferenceClickListener {
activity?.getApiProviderLangSettings()?.let { current ->
val languages = APIHolder.apis.map { it.lang }.toSet()
.sortedBy { SubtitleHelper.fromTwoLettersToLanguage(it) } + AllLanguagesName
val languages = synchronized(APIHolder.apis) {
APIHolder.apis.map { it.lang }.toSet()
.sortedBy { SubtitleHelper.fromTwoLettersToLanguage(it) } + AllLanguagesName
}
val currentList = current.map {
languages.indexOf(it)

View File

@ -10,6 +10,7 @@ import com.lagradost.cloudstream3.utils.TestingUtils
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import okhttp3.internal.toImmutableList
class TestViewModel : ViewModel() {
data class TestProgress(
@ -81,15 +82,14 @@ class TestViewModel : ViewModel() {
}
fun init() {
val apis = APIHolder.allProviders
total = apis.size
total = synchronized(APIHolder.allProviders) { APIHolder.allProviders.size }
updateProgress()
}
fun startTest() {
scope = CoroutineScope(Dispatchers.Default)
val apis = APIHolder.allProviders
val apis = synchronized(APIHolder.allProviders) { APIHolder.allProviders.toTypedArray() }
total = apis.size
failed = 0
passed = 0

View File

@ -107,7 +107,7 @@ class SetupFragmentExtensions : Fragment() {
if (isSetup)
if (
// If any available languages
apis.distinctBy { it.lang }.size > 1
synchronized(apis) { apis.distinctBy { it.lang }.size > 1 }
) {
findNavController().navigate(R.id.action_navigation_setup_extensions_to_navigation_setup_provider_languages)
} else {

View File

@ -51,8 +51,8 @@ class SetupFragmentProviderLanguage : Fragment() {
ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
val current = ctx.getApiProviderLangSettings()
val langs = APIHolder.apis.map { it.lang }.toSet()
.sortedBy { SubtitleHelper.fromTwoLettersToLanguage(it) } + AllLanguagesName
val langs = synchronized(APIHolder.apis) { APIHolder.apis.map { it.lang }.toSet()
.sortedBy { SubtitleHelper.fromTwoLettersToLanguage(it) } + AllLanguagesName}
val currentList =
current.map { langs.indexOf(it) }.filter { it != -1 } // TODO LOOK INTO

View File

@ -18,6 +18,8 @@ const val USER_PROVIDER_API = "user_custom_sites"
const val PREFERENCES_NAME = "rebuild_preference"
// TODO degelgate by value for get & set
object DataStore {
val mapper: JsonMapper = JsonMapper.builder().addModule(KotlinModule())
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()

View File

@ -96,8 +96,10 @@ object SyncUtil {
.mapNotNull { it.url }.toMutableList()
if (type == "anilist") { // TODO MAKE BETTER
apis.filter { it.name.contains("Aniflix", ignoreCase = true) }.forEach {
current.add("${it.mainUrl}/anime/$id")
synchronized(apis) {
apis.filter { it.name.contains("Aniflix", ignoreCase = true) }.forEach {
current.add("${it.mainUrl}/anime/$id")
}
}
}
return current

View File

@ -211,7 +211,7 @@ object TestingUtils {
fun getDeferredProviderTests(
scope: CoroutineScope,
providers: List<MainAPI>,
providers: Array<MainAPI>,
logger: (String) -> Unit,
callback: (MainAPI, TestResultProvider) -> Unit
) {

View File

@ -1,13 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:orientation="vertical"
android:id="@+id/home_header"
<LinearLayout 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:id="@+id/home_header"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="?attr/primaryBlackBackground"
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:orientation="vertical">
<View
android:id="@+id/home_none_padding"
@ -20,10 +20,10 @@
android:layout_height="wrap_content">
<androidx.viewpager2.widget.ViewPager2
android:descendantFocusability="blocksDescendants"
android:id="@+id/home_preview_viewpager"
android:layout_width="match_parent"
android:layout_height="400dp"
android:descendantFocusability="blocksDescendants"
android:orientation="horizontal">
</androidx.viewpager2.widget.ViewPager2>
@ -39,7 +39,9 @@
android:layout_width="wrap_content"
android:layout_gravity="top|start"
android:layout_marginStart="@dimen/navbar_width"
android:minWidth="150dp" />
android:minWidth="150dp"
android:nextFocusLeft="@id/nav_rail_view"
android:nextFocusDown="@id/home_preview_play_btt" />
</FrameLayout>
<LinearLayout
@ -131,12 +133,14 @@
<com.google.android.material.button.MaterialButton
android:id="@+id/home_preview_change_api2"
style="@style/BlackButton"
style="@style/RegularButtonTV"
android:layout_width="wrap_content"
android:layout_gravity="top|start"
android:layout_marginStart="@dimen/navbar_width"
android:backgroundTint="@color/semiWhite"
android:minWidth="150dp" />
android:minWidth="150dp"
android:nextFocusLeft="@id/nav_rail_view"
android:nextFocusDown="@id/home_watch_child_recyclerview" />
</FrameLayout>
<LinearLayout

View File

@ -75,7 +75,7 @@
<style name="ListViewStyle" parent="Widget.AppCompat.ListView">
<item name="android:divider">@null</item>
<item name="android:listSelector">@drawable/outline_drawable_less</item>
<item name="android:listSelector">@drawable/outline_drawable_forced</item>
<item name="android:drawSelectorOnTop">false</item>
</style>
@ -572,6 +572,7 @@
<item name="drawableStartCompat">@drawable/ic_baseline_check_24_listview</item>
</style>
<style name="NoCheckLabel" parent="@style/AppTextViewStyle">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>