mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
alert fix + synchronized + bump + homepage load fix + small focus change
This commit is contained in:
parent
c987f7581e
commit
3bdbb35754
26 changed files with 265 additions and 156 deletions
|
@ -52,7 +52,7 @@ android {
|
||||||
targetSdk = 33
|
targetSdk = 33
|
||||||
|
|
||||||
versionCode = 59
|
versionCode = 59
|
||||||
versionName = "4.0.1"
|
versionName = "4.1.1"
|
||||||
|
|
||||||
resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}")
|
resValue("string", "app_version", "${defaultConfig.versionName}${versionNameSuffix ?: ""}")
|
||||||
|
|
||||||
|
|
|
@ -46,9 +46,9 @@ class TestApplication : Activity() {
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class ExampleInstrumentedTest {
|
class ExampleInstrumentedTest {
|
||||||
private fun getAllProviders(): List<MainAPI> {
|
private fun getAllProviders(): Array<MainAPI> {
|
||||||
println("Providers: ${APIHolder.allProviders.size}")
|
println("Providers: ${APIHolder.allProviders.size}")
|
||||||
return APIHolder.allProviders //.filter { !it.usesWebView }
|
return APIHolder.allProviders.toTypedArray() //.filter { !it.usesWebView }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -147,7 +147,7 @@ class ExampleInstrumentedTest {
|
||||||
@Test
|
@Test
|
||||||
fun providerCorrectHomepage() {
|
fun providerCorrectHomepage() {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
getAllProviders().amap { api ->
|
getAllProviders().toList().amap { api ->
|
||||||
TestingUtils.testHomepage(api, ::println)
|
TestingUtils.testHomepage(api, ::println)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import android.content.res.Resources
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.util.Log
|
import android.util.Log
|
||||||
import android.view.*
|
import android.view.*
|
||||||
|
import android.view.View.NO_ID
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.ComponentActivity
|
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
|
) // 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(
|
private fun getNextFocus(
|
||||||
act: Activity?,
|
act: Activity?,
|
||||||
view: View?,
|
view: View?,
|
||||||
|
@ -306,7 +331,7 @@ object CommonActivity {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
val nextId = when (direction) {
|
var nextId = when (direction) {
|
||||||
FocusDirection.Start -> {
|
FocusDirection.Start -> {
|
||||||
if (view.isRtl())
|
if (view.isRtl())
|
||||||
view.nextFocusRightId
|
view.nextFocusRightId
|
||||||
|
@ -330,22 +355,16 @@ object CommonActivity {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// if view not found then return
|
if (nextId == NO_ID) {
|
||||||
if (nextId == -1) return null
|
// if not specified then use forward id
|
||||||
var next = act.findViewById<View?>(nextId) ?: return null
|
nextId = view.nextFocusForwardId
|
||||||
|
// if view is still not found to next focus then return and let android decide
|
||||||
// because we want closes find, aka when multiple have the same id, we go to parent
|
if (nextId == NO_ID) return null
|
||||||
// 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
|
var next = act.findViewById<View?>(nextId) ?: return null
|
||||||
}*/
|
|
||||||
|
next = localLook(view, nextId) ?: next
|
||||||
|
|
||||||
var currentLook: View = view
|
var currentLook: View = view
|
||||||
while (currentLook.findViewById<View?>(nextId)?.also { next = it } == null) {
|
while (currentLook.findViewById<View?>(nextId)?.also { next = it } == null) {
|
||||||
|
@ -362,7 +381,7 @@ object CommonActivity {
|
||||||
return next
|
return next
|
||||||
}
|
}
|
||||||
|
|
||||||
enum class FocusDirection {
|
private enum class FocusDirection {
|
||||||
Start,
|
Start,
|
||||||
End,
|
End,
|
||||||
Up,
|
Up,
|
||||||
|
@ -463,6 +482,7 @@ object CommonActivity {
|
||||||
//}
|
//}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** overrides focus and custom key events */
|
||||||
fun dispatchKeyEvent(act: Activity?, event: KeyEvent?): Boolean? {
|
fun dispatchKeyEvent(act: Activity?, event: KeyEvent?): Boolean? {
|
||||||
if (act == null) return null
|
if (act == null) return null
|
||||||
val currentFocus = act.currentFocus
|
val currentFocus = act.currentFocus
|
||||||
|
@ -503,7 +523,9 @@ object CommonActivity {
|
||||||
return true
|
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())
|
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) {
|
if (keyEventListener?.invoke(Pair(event, false)) == true) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,9 +50,11 @@ object APIHolder {
|
||||||
val allProviders = threadSafeListOf<MainAPI>()
|
val allProviders = threadSafeListOf<MainAPI>()
|
||||||
|
|
||||||
fun initAll() {
|
fun initAll() {
|
||||||
|
synchronized(allProviders) {
|
||||||
for (api in allProviders) {
|
for (api in allProviders) {
|
||||||
api.init()
|
api.init()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
apiMap = null
|
apiMap = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,29 +66,37 @@ object APIHolder {
|
||||||
var apiMap: Map<String, Int>? = null
|
var apiMap: Map<String, Int>? = null
|
||||||
|
|
||||||
fun addPluginMapping(plugin: MainAPI) {
|
fun addPluginMapping(plugin: MainAPI) {
|
||||||
|
synchronized(apis) {
|
||||||
apis = apis + plugin
|
apis = apis + plugin
|
||||||
|
}
|
||||||
initMap(true)
|
initMap(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removePluginMapping(plugin: MainAPI) {
|
fun removePluginMapping(plugin: MainAPI) {
|
||||||
|
synchronized(apis) {
|
||||||
apis = apis.filter { it != plugin }
|
apis = apis.filter { it != plugin }
|
||||||
|
}
|
||||||
initMap(true)
|
initMap(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun initMap(forcedUpdate: Boolean = false) {
|
private fun initMap(forcedUpdate: Boolean = false) {
|
||||||
|
synchronized(apis) {
|
||||||
if (apiMap == null || forcedUpdate)
|
if (apiMap == null || forcedUpdate)
|
||||||
apiMap = apis.mapIndexed { index, api -> api.name to index }.toMap()
|
apiMap = apis.mapIndexed { index, api -> api.name to index }.toMap()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getApiFromNameNull(apiName: String?): MainAPI? {
|
fun getApiFromNameNull(apiName: String?): MainAPI? {
|
||||||
if (apiName == null) return null
|
if (apiName == null) return null
|
||||||
synchronized(allProviders) {
|
synchronized(allProviders) {
|
||||||
initMap()
|
initMap()
|
||||||
|
synchronized(apis) {
|
||||||
return apiMap?.get(apiName)?.let { apis.getOrNull(it) }
|
return apiMap?.get(apiName)?.let { apis.getOrNull(it) }
|
||||||
// Leave the ?. null check, it can crash regardless
|
// Leave the ?. null check, it can crash regardless
|
||||||
?: allProviders.firstOrNull { it.name == apiName }
|
?: allProviders.firstOrNull { it.name == apiName }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun getApiFromUrlNull(url: String?): MainAPI? {
|
fun getApiFromUrlNull(url: String?): MainAPI? {
|
||||||
if (url == null) return null
|
if (url == null) return null
|
||||||
|
@ -215,7 +225,7 @@ object APIHolder {
|
||||||
val hashSet = HashSet<String>()
|
val hashSet = HashSet<String>()
|
||||||
val activeLangs = getApiProviderLangSettings()
|
val activeLangs = getApiProviderLangSettings()
|
||||||
val hasUniversal = activeLangs.contains(AllLanguagesName)
|
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 })
|
.map { it.name })
|
||||||
|
|
||||||
/*val set = settingsManager.getStringSet(
|
/*val set = settingsManager.getStringSet(
|
||||||
|
@ -314,8 +324,9 @@ object APIHolder {
|
||||||
} ?: default
|
} ?: default
|
||||||
val langs = this.getApiProviderLangSettings()
|
val langs = this.getApiProviderLangSettings()
|
||||||
val hasUniversal = langs.contains(AllLanguagesName)
|
val hasUniversal = langs.contains(AllLanguagesName)
|
||||||
val allApis = apis.filter { hasUniversal || langs.contains(it.lang) }
|
val allApis = synchronized(apis) {
|
||||||
.filter { api -> api.hasMainPage || !hasHomePageIsRequired }
|
apis.filter { api -> (hasUniversal || langs.contains(api.lang)) && (api.hasMainPage || !hasHomePageIsRequired) }
|
||||||
|
}
|
||||||
return if (currentPrefMedia.isEmpty()) {
|
return if (currentPrefMedia.isEmpty()) {
|
||||||
allApis
|
allApis
|
||||||
} else {
|
} else {
|
||||||
|
@ -736,6 +747,7 @@ fun fixTitle(str: String): String {
|
||||||
.replaceFirstChar { char -> if (char.isLowerCase()) char.titlecase(Locale.getDefault()) else it }
|
.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.
|
* 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()
|
* Make sure you get the scope using: val scope: Scriptable = rhino.initSafeStandardObjects()
|
||||||
|
|
|
@ -382,6 +382,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
this.navigate(R.id.navigation_downloads)
|
this.navigate(R.id.navigation_downloads)
|
||||||
return true
|
return true
|
||||||
} else {
|
} else {
|
||||||
|
synchronized(apis) {
|
||||||
for (api in apis) {
|
for (api in apis) {
|
||||||
if (str.startsWith(api.mainUrl)) {
|
if (str.startsWith(api.mainUrl)) {
|
||||||
loadResult(str, api.name)
|
loadResult(str, api.name)
|
||||||
|
@ -391,6 +392,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -464,7 +466,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
|
|
||||||
binding?.navHostFragment?.apply {
|
binding?.navHostFragment?.apply {
|
||||||
val params = layoutParams as ConstraintLayout.LayoutParams
|
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.setMargins(
|
||||||
|
@ -695,6 +698,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
private fun onAllPluginsLoaded(success: Boolean = false) {
|
private fun onAllPluginsLoaded(success: Boolean = false) {
|
||||||
ioSafe {
|
ioSafe {
|
||||||
pluginsLock.withLock {
|
pluginsLock.withLock {
|
||||||
|
synchronized(allProviders) {
|
||||||
// Load cloned sites after plugins have been loaded since clones depend on plugins.
|
// Load cloned sites after plugins have been loaded since clones depend on plugins.
|
||||||
try {
|
try {
|
||||||
getKey<Array<SettingsGeneral.CustomSite>>(USER_PROVIDER_API)?.let { list ->
|
getKey<Array<SettingsGeneral.CustomSite>>(USER_PROVIDER_API)?.let { list ->
|
||||||
|
@ -720,6 +724,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
lateinit var viewModel: ResultViewModel2
|
lateinit var viewModel: ResultViewModel2
|
||||||
|
|
||||||
|
@ -814,6 +819,7 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
|
|
||||||
translationX = target.x
|
translationX = target.x
|
||||||
translationY = target.y
|
translationY = target.y
|
||||||
|
bringToFront()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1195,7 +1201,9 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
ioSafe {
|
ioSafe {
|
||||||
initAll()
|
initAll()
|
||||||
// No duplicates (which can happen by registerMainAPI)
|
// 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)
|
// val navView: BottomNavigationView = findViewById(R.id.nav_view)
|
||||||
|
@ -1347,8 +1355,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
if (BuildConfig.DEBUG) {
|
if (BuildConfig.DEBUG) {
|
||||||
try {
|
|
||||||
var providersAndroidManifestString = "Current androidmanifest should be:\n"
|
var providersAndroidManifestString = "Current androidmanifest should be:\n"
|
||||||
|
synchronized(allProviders) {
|
||||||
for (api in allProviders) {
|
for (api in allProviders) {
|
||||||
providersAndroidManifestString += "<data android:scheme=\"https\" android:host=\"${
|
providersAndroidManifestString += "<data android:scheme=\"https\" android:host=\"${
|
||||||
api.mainUrl.removePrefix(
|
api.mainUrl.removePrefix(
|
||||||
|
@ -1356,12 +1364,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
|
||||||
)
|
)
|
||||||
}\" android:pathPrefix=\"/\"/>\n"
|
}\" android:pathPrefix=\"/\"/>\n"
|
||||||
}
|
}
|
||||||
println(providersAndroidManifestString)
|
|
||||||
|
|
||||||
} catch (t: Throwable) {
|
|
||||||
logError(t)
|
|
||||||
}
|
}
|
||||||
|
println(providersAndroidManifestString)
|
||||||
}
|
}
|
||||||
|
|
||||||
handleAppIntent(intent)
|
handleAppIntent(intent)
|
||||||
|
|
|
@ -21,10 +21,11 @@ class CrossTmdbProvider : TmdbProvider() {
|
||||||
return Regex("""[^a-zA-Z0-9-]""").replace(name, "")
|
return Regex("""[^a-zA-Z0-9-]""").replace(name, "")
|
||||||
}
|
}
|
||||||
|
|
||||||
private val validApis by lazy {
|
private val validApis
|
||||||
apis.filter { it.lang == this.lang && it::class.java != this::class.java }
|
get() =
|
||||||
|
synchronized(apis) { apis.filter { it.lang == this.lang && it::class.java != this::class.java } }
|
||||||
//.distinctBy { it.uniqueId }
|
//.distinctBy { it.uniqueId }
|
||||||
}
|
|
||||||
|
|
||||||
data class CrossMetaData(
|
data class CrossMetaData(
|
||||||
@JsonProperty("isSuccess") val isSuccess: Boolean,
|
@JsonProperty("isSuccess") val isSuccess: Boolean,
|
||||||
|
@ -60,7 +61,8 @@ class CrossTmdbProvider : TmdbProvider() {
|
||||||
|
|
||||||
override suspend fun load(url: String): LoadResponse? {
|
override suspend fun load(url: String): LoadResponse? {
|
||||||
val base = super.load(url)?.apply {
|
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)
|
val matchName = filterName(this.name)
|
||||||
when (this) {
|
when (this) {
|
||||||
is MovieLoadResponse -> {
|
is MovieLoadResponse -> {
|
||||||
|
@ -98,6 +100,7 @@ class CrossTmdbProvider : TmdbProvider() {
|
||||||
this.dataUrl =
|
this.dataUrl =
|
||||||
CrossMetaData(true, data.map { it.apiName to it.dataUrl }).toJson()
|
CrossMetaData(true, data.map { it.apiName to it.dataUrl }).toJson()
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
else -> {
|
||||||
throw ErrorLoadingException("Nothing besides movies are implemented for this provider")
|
throw ErrorLoadingException("Nothing besides movies are implemented for this provider")
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,9 @@ class MultiAnimeProvider : MainAPI() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val validApis by lazy {
|
private val validApis
|
||||||
|
get() =
|
||||||
|
synchronized(APIHolder.apis) {
|
||||||
APIHolder.apis.filter {
|
APIHolder.apis.filter {
|
||||||
it.lang == this.lang && it::class.java != this::class.java && it.supportedTypes.contains(
|
it.lang == this.lang && it::class.java != this::class.java && it.supportedTypes.contains(
|
||||||
TvType.Anime
|
TvType.Anime
|
||||||
|
@ -33,6 +35,7 @@ class MultiAnimeProvider : MainAPI() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
private fun filterName(name: String): String {
|
private fun filterName(name: String): String {
|
||||||
return Regex("""[^a-zA-Z0-9-]""").replace(name, "")
|
return Regex("""[^a-zA-Z0-9-]""").replace(name, "")
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,9 @@ abstract class Plugin {
|
||||||
Log.i(PLUGIN_TAG, "Adding ${element.name} (${element.mainUrl}) MainAPI")
|
Log.i(PLUGIN_TAG, "Adding ${element.name} (${element.mainUrl}) MainAPI")
|
||||||
element.sourcePlugin = this.__filename
|
element.sourcePlugin = this.__filename
|
||||||
// Race condition causing which would case duplicates if not for distinctBy
|
// Race condition causing which would case duplicates if not for distinctBy
|
||||||
|
synchronized(APIHolder.allProviders) {
|
||||||
APIHolder.allProviders.add(element)
|
APIHolder.allProviders.add(element)
|
||||||
|
}
|
||||||
APIHolder.addPluginMapping(element)
|
APIHolder.addPluginMapping(element)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,10 +53,14 @@ abstract class Plugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
class Manifest {
|
class Manifest {
|
||||||
@JsonProperty("name") var name: String? = null
|
@JsonProperty("name")
|
||||||
@JsonProperty("pluginClassName") var pluginClassName: String? = null
|
var name: String? = null
|
||||||
@JsonProperty("version") var version: Int? = null
|
@JsonProperty("pluginClassName")
|
||||||
@JsonProperty("requiresResources") var requiresResources: Boolean = false
|
var pluginClassName: String? = null
|
||||||
|
@JsonProperty("version")
|
||||||
|
var version: Int? = null
|
||||||
|
@JsonProperty("requiresResources")
|
||||||
|
var requiresResources: Boolean = false
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -163,7 +163,8 @@ object PluginManager {
|
||||||
private val classLoaders: MutableMap<PathClassLoader, Plugin> =
|
private val classLoaders: MutableMap<PathClassLoader, Plugin> =
|
||||||
HashMap<PathClassLoader, Plugin>()
|
HashMap<PathClassLoader, Plugin>()
|
||||||
|
|
||||||
private var loadedLocalPlugins = false
|
var loadedLocalPlugins = false
|
||||||
|
private set
|
||||||
private val gson = Gson()
|
private val gson = Gson()
|
||||||
|
|
||||||
private suspend fun maybeLoadPlugin(context: Context, file: File) {
|
private suspend fun maybeLoadPlugin(context: Context, file: File) {
|
||||||
|
@ -531,10 +532,14 @@ object PluginManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove all registered apis
|
// remove all registered apis
|
||||||
|
synchronized(APIHolder.apis) {
|
||||||
APIHolder.apis.filter { api -> api.sourcePlugin == plugin.__filename }.forEach {
|
APIHolder.apis.filter { api -> api.sourcePlugin == plugin.__filename }.forEach {
|
||||||
removePluginMapping(it)
|
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 }
|
extractorApis.removeIf { provider: ExtractorApi -> provider.sourcePlugin == plugin.__filename }
|
||||||
|
|
||||||
classLoaders.values.removeIf { v -> v == plugin }
|
classLoaders.values.removeIf { v -> v == plugin }
|
||||||
|
|
|
@ -462,7 +462,7 @@ class HomeFragment : Fragment() {
|
||||||
|
|
||||||
private val apiChangeClickListener = View.OnClickListener { view ->
|
private val apiChangeClickListener = View.OnClickListener { view ->
|
||||||
view.context.selectHomepage(currentApiName) { api ->
|
view.context.selectHomepage(currentApiName) { api ->
|
||||||
homeViewModel.loadAndCancel(api)
|
homeViewModel.loadAndCancel(api, forceReload = true,fromUI = true)
|
||||||
}
|
}
|
||||||
/*val validAPIs = view.context?.filterProviderByPreferredMedia()?.toMutableList() ?: mutableListOf()
|
/*val validAPIs = view.context?.filterProviderByPreferredMedia()?.toMutableList() ?: mutableListOf()
|
||||||
|
|
||||||
|
@ -652,6 +652,7 @@ class HomeFragment : Fragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
homeViewModel.reloadStored()
|
homeViewModel.reloadStored()
|
||||||
|
homeViewModel.loadAndCancel(getKey(USER_SELECTED_HOMEPAGE_API), false)
|
||||||
//loadHomePage(false)
|
//loadHomePage(false)
|
||||||
|
|
||||||
// nice profile pic on homepage
|
// nice profile pic on homepage
|
||||||
|
|
|
@ -447,12 +447,12 @@ class HomeParentItemAdapterPreview(
|
||||||
(binding as? FragmentHomeHeadTvBinding)?.apply {
|
(binding as? FragmentHomeHeadTvBinding)?.apply {
|
||||||
homePreviewChangeApi.setOnClickListener { view ->
|
homePreviewChangeApi.setOnClickListener { view ->
|
||||||
view.context.selectHomepage(viewModel.repo?.name) { api ->
|
view.context.selectHomepage(viewModel.repo?.name) { api ->
|
||||||
viewModel.loadAndCancel(api)
|
viewModel.loadAndCancel(api, forceReload = true, fromUI = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
homePreviewChangeApi2.setOnClickListener { view ->
|
homePreviewChangeApi2.setOnClickListener { view ->
|
||||||
view.context.selectHomepage(viewModel.repo?.name) { api ->
|
view.context.selectHomepage(viewModel.repo?.name) { api ->
|
||||||
viewModel.loadAndCancel(api)
|
viewModel.loadAndCancel(api, forceReload = true, fromUI = true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@ import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.lagradost.cloudstream3.*
|
|
||||||
import com.lagradost.cloudstream3.APIHolder.apis
|
import com.lagradost.cloudstream3.APIHolder.apis
|
||||||
import com.lagradost.cloudstream3.APIHolder.filterHomePageListByFilmQuality
|
import com.lagradost.cloudstream3.APIHolder.filterHomePageListByFilmQuality
|
||||||
import com.lagradost.cloudstream3.APIHolder.filterProviderByPreferredMedia
|
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.getKey
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||||
import com.lagradost.cloudstream3.CommonActivity.activity
|
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
|
||||||
import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi
|
import com.lagradost.cloudstream3.ui.APIRepository.Companion.noneApi
|
||||||
import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi
|
import com.lagradost.cloudstream3.ui.APIRepository.Companion.randomApi
|
||||||
import com.lagradost.cloudstream3.ui.WatchType
|
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.quicksearch.QuickSearchFragment
|
||||||
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_FOCUSED
|
import com.lagradost.cloudstream3.ui.search.SEARCH_ACTION_FOCUSED
|
||||||
import com.lagradost.cloudstream3.ui.search.SearchClickCallback
|
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.AppUtils.loadResult
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
import com.lagradost.cloudstream3.utils.DOWNLOAD_HEADER_CACHE
|
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
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllResumeStateIds
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllResumeStateIds
|
||||||
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllWatchStateIds
|
import com.lagradost.cloudstream3.utils.DataStoreHelper.getAllWatchStateIds
|
||||||
|
@ -44,7 +51,7 @@ import com.lagradost.cloudstream3.utils.VideoDownloadHelper
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import java.util.*
|
import java.util.EnumSet
|
||||||
import kotlin.collections.set
|
import kotlin.collections.set
|
||||||
|
|
||||||
class HomeViewModel : ViewModel() {
|
class HomeViewModel : ViewModel() {
|
||||||
|
@ -95,7 +102,7 @@ class HomeViewModel : ViewModel() {
|
||||||
private var currentShuffledList: List<SearchResponse> = listOf()
|
private var currentShuffledList: List<SearchResponse> = listOf()
|
||||||
|
|
||||||
private fun autoloadRepo(): APIRepository {
|
private fun autoloadRepo(): APIRepository {
|
||||||
return APIRepository(apis.first { it.hasMainPage })
|
return APIRepository(synchronized(apis) { apis.first { it.hasMainPage }})
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _availableWatchStatusTypes =
|
private val _availableWatchStatusTypes =
|
||||||
|
@ -177,8 +184,10 @@ class HomeViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private var onGoingLoad: Job? = null
|
private var onGoingLoad: Job? = null
|
||||||
private fun loadAndCancel(api: MainAPI?) {
|
private var isCurrentlyLoadingName : String? = null
|
||||||
|
private fun loadAndCancel(api: MainAPI) {
|
||||||
onGoingLoad?.cancel()
|
onGoingLoad?.cancel()
|
||||||
|
isCurrentlyLoadingName = api.name
|
||||||
onGoingLoad = load(api)
|
onGoingLoad = load(api)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -280,12 +289,12 @@ class HomeViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun load(api: MainAPI?) = ioSafe {
|
private fun load(api: MainAPI) : Job = ioSafe {
|
||||||
repo = if (api != null) {
|
repo = //if (api != null) {
|
||||||
APIRepository(api)
|
APIRepository(api)
|
||||||
} else {
|
//} else {
|
||||||
autoloadRepo()
|
// autoloadRepo()
|
||||||
}
|
//}
|
||||||
|
|
||||||
_apiName.postValue(repo?.name)
|
_apiName.postValue(repo?.name)
|
||||||
_randomItems.postValue(listOf())
|
_randomItems.postValue(listOf())
|
||||||
|
@ -299,6 +308,7 @@ class HomeViewModel : ViewModel() {
|
||||||
|
|
||||||
_page.postValue(Resource.Loading())
|
_page.postValue(Resource.Loading())
|
||||||
_preview.postValue(Resource.Loading())
|
_preview.postValue(Resource.Loading())
|
||||||
|
// cancel the current preview expand as that is no longer relevant
|
||||||
addJob?.cancel()
|
addJob?.cancel()
|
||||||
|
|
||||||
when (val data = repo?.getMainPage(1, null)) {
|
when (val data = repo?.getMainPage(1, null)) {
|
||||||
|
@ -370,7 +380,7 @@ class HomeViewModel : ViewModel() {
|
||||||
|
|
||||||
else -> Unit
|
else -> Unit
|
||||||
}
|
}
|
||||||
onGoingLoad = null
|
isCurrentlyLoadingName = null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun click(callback: SearchClickCallback) {
|
fun click(callback: SearchClickCallback) {
|
||||||
|
@ -437,33 +447,51 @@ class HomeViewModel : ViewModel() {
|
||||||
loadResult(load.response.url, load.response.apiName, load.action)
|
loadResult(load.response.url, load.response.apiName, load.action)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun loadAndCancel(preferredApiName: String?, forceReload: Boolean = true) =
|
// only save the key if it is from UI, as we don't want internal functions changing the setting
|
||||||
viewModelScope.launchSafe {
|
fun loadAndCancel(
|
||||||
|
preferredApiName: String?,
|
||||||
|
forceReload: Boolean = true,
|
||||||
|
fromUI: Boolean = false
|
||||||
|
) =
|
||||||
|
ioSafe {
|
||||||
// Since plugins are loaded in stages this function can get called multiple times.
|
// 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
|
// The issue with this is that the homepage may be fetched multiple times while the first request is loading
|
||||||
val api = getApiFromNameNull(preferredApiName)
|
val api = getApiFromNameNull(preferredApiName)
|
||||||
if (!forceReload && api?.let { expandable[it.name]?.list?.list?.isNotEmpty() } == true) {
|
// api?.let { expandable[it.name]?.list?.list?.isNotEmpty() } == true
|
||||||
return@launchSafe
|
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) {
|
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)
|
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
|
||||||
|
// then use nothing
|
||||||
val validAPIs = context?.filterProviderByPreferredMedia()
|
val validAPIs = context?.filterProviderByPreferredMedia()
|
||||||
if (validAPIs.isNullOrEmpty()) {
|
if (validAPIs.isNullOrEmpty()) {
|
||||||
// Do not set USER_SELECTED_HOMEPAGE_API when there is no plugins loaded
|
|
||||||
loadAndCancel(noneApi)
|
loadAndCancel(noneApi)
|
||||||
} else {
|
} else {
|
||||||
val apiRandom = validAPIs.random()
|
val apiRandom = validAPIs.random()
|
||||||
loadAndCancel(apiRandom)
|
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) {
|
} else if (api == null) {
|
||||||
|
// API is not found aka not loaded or removed, post the loading
|
||||||
|
// progress if waiting for plugins, otherwise nothing
|
||||||
|
if(PluginManager.loadedLocalPlugins) {
|
||||||
loadAndCancel(noneApi)
|
loadAndCancel(noneApi)
|
||||||
} else {
|
} else {
|
||||||
setKey(USER_SELECTED_HOMEPAGE_API, api.name)
|
_page.postValue(Resource.Loading())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// if the api is found, then set it to it and save key
|
||||||
|
if (fromUI) setKey(USER_SELECTED_HOMEPAGE_API, api.name)
|
||||||
loadAndCancel(api)
|
loadAndCancel(api)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -163,12 +163,14 @@ class LibraryFragment : Fragment() {
|
||||||
syncId: SyncIdName,
|
syncId: SyncIdName,
|
||||||
apiName: String? = null,
|
apiName: String? = null,
|
||||||
) {
|
) {
|
||||||
val availableProviders = allProviders.filter {
|
val availableProviders = synchronized(allProviders) {
|
||||||
|
allProviders.filter {
|
||||||
it.supportedSyncNames.contains(syncId)
|
it.supportedSyncNames.contains(syncId)
|
||||||
}.map { it.name } +
|
}.map { it.name } +
|
||||||
// Add the api if it exists
|
// Add the api if it exists
|
||||||
(APIHolder.getApiFromNameNull(apiName)?.let { listOf(it.name) } ?: emptyList())
|
(APIHolder.getApiFromNameNull(apiName)?.let { listOf(it.name) }
|
||||||
|
?: emptyList())
|
||||||
|
}
|
||||||
val baseOptions = listOf(
|
val baseOptions = listOf(
|
||||||
LibraryOpenerType.Default,
|
LibraryOpenerType.Default,
|
||||||
LibraryOpenerType.None,
|
LibraryOpenerType.None,
|
||||||
|
|
|
@ -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 ->
|
return allEpisodes.keys.mapNotNull { index ->
|
||||||
val episodes =
|
val episodes =
|
||||||
allEpisodes[index] ?: return@mapNotNull null // this should never happened
|
allEpisodes[index] ?: return@mapNotNull null // this should never happened
|
||||||
|
@ -1505,13 +1508,14 @@ class ResultViewModel2 : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
val realRecommendations = ArrayList<SearchResponse>()
|
val realRecommendations = ArrayList<SearchResponse>()
|
||||||
val apiNames = apis.filter {
|
val apiNames = synchronized(apis) {
|
||||||
|
apis.filter {
|
||||||
it.name.contains("gogoanime", true) ||
|
it.name.contains("gogoanime", true) ||
|
||||||
it.name.contains("9anime", true)
|
it.name.contains("9anime", true)
|
||||||
}.map {
|
}.map {
|
||||||
it.name
|
it.name
|
||||||
}
|
}
|
||||||
|
}
|
||||||
meta.recommendations?.forEach { rec ->
|
meta.recommendations?.forEach { rec ->
|
||||||
apiNames.forEach { name ->
|
apiNames.forEach { name ->
|
||||||
realRecommendations.add(rec.copy(apiName = name))
|
realRecommendations.add(rec.copy(apiName = name))
|
||||||
|
|
|
@ -37,7 +37,7 @@ class SearchViewModel : ViewModel() {
|
||||||
private val _currentHistory: MutableLiveData<List<SearchHistoryItem>> = MutableLiveData()
|
private val _currentHistory: MutableLiveData<List<SearchHistoryItem>> = MutableLiveData()
|
||||||
val currentHistory: LiveData<List<SearchHistoryItem>> get() = _currentHistory
|
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() {
|
fun clearSearch() {
|
||||||
_searchResponse.postValue(Resource.Success(ArrayList()))
|
_searchResponse.postValue(Resource.Success(ArrayList()))
|
||||||
|
@ -48,7 +48,7 @@ class SearchViewModel : ViewModel() {
|
||||||
private var onGoingSearch: Job? = null
|
private var onGoingSearch: Job? = null
|
||||||
|
|
||||||
fun reloadRepos() {
|
fun reloadRepos() {
|
||||||
repos = apis.map { APIRepository(it) }
|
repos = synchronized(apis) { apis.map { APIRepository(it) } }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun searchAndCancel(
|
fun searchAndCancel(
|
||||||
|
|
|
@ -8,7 +8,9 @@ import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ImageView
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.core.view.children
|
||||||
import androidx.core.view.isVisible
|
import androidx.core.view.isVisible
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.preference.Preference
|
import androidx.preference.Preference
|
||||||
|
@ -74,6 +76,7 @@ class SettingsFragment : Fragment() {
|
||||||
settingsToolbar.apply {
|
settingsToolbar.apply {
|
||||||
setTitle(title)
|
setTitle(title)
|
||||||
setNavigationIcon(R.drawable.ic_baseline_arrow_back_24)
|
setNavigationIcon(R.drawable.ic_baseline_arrow_back_24)
|
||||||
|
children.firstOrNull { it is ImageView }?.tag = getString(R.string.tv_no_focus_tag)
|
||||||
setNavigationOnClickListener {
|
setNavigationOnClickListener {
|
||||||
activity?.onBackPressed()
|
activity?.onBackPressed()
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||||
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
import com.lagradost.cloudstream3.AcraApplication.Companion.setKey
|
||||||
import com.lagradost.cloudstream3.CommonActivity
|
import com.lagradost.cloudstream3.CommonActivity
|
||||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||||
|
import com.lagradost.cloudstream3.MainActivity
|
||||||
import com.lagradost.cloudstream3.R
|
import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.app
|
import com.lagradost.cloudstream3.app
|
||||||
import com.lagradost.cloudstream3.databinding.AddRemoveSitesBinding
|
import com.lagradost.cloudstream3.databinding.AddRemoveSitesBinding
|
||||||
|
@ -188,7 +189,7 @@ class SettingsGeneral : PreferenceFragmentCompat() {
|
||||||
|
|
||||||
|
|
||||||
fun showAdd() {
|
fun showAdd() {
|
||||||
val providers = allProviders.distinctBy { it.javaClass }.sortedBy { it.name }
|
val providers = synchronized(allProviders) { allProviders.distinctBy { it.javaClass }.sortedBy { it.name } }
|
||||||
activity?.showDialog(
|
activity?.showDialog(
|
||||||
providers.map { "${it.name} (${it.mainUrl})" },
|
providers.map { "${it.name} (${it.mainUrl})" },
|
||||||
-1,
|
-1,
|
||||||
|
@ -221,6 +222,8 @@ class SettingsGeneral : PreferenceFragmentCompat() {
|
||||||
val newSite = CustomSite(provider.javaClass.simpleName, name, url, realLang)
|
val newSite = CustomSite(provider.javaClass.simpleName, name, url, realLang)
|
||||||
current.add(newSite)
|
current.add(newSite)
|
||||||
setKey(USER_PROVIDER_API, current.toTypedArray())
|
setKey(USER_PROVIDER_API, current.toTypedArray())
|
||||||
|
// reload apis
|
||||||
|
MainActivity.afterPluginsLoadedEvent.invoke(false)
|
||||||
|
|
||||||
dialog.dismissSafe(activity)
|
dialog.dismissSafe(activity)
|
||||||
}
|
}
|
||||||
|
|
|
@ -105,8 +105,10 @@ class SettingsProviders : PreferenceFragmentCompat() {
|
||||||
|
|
||||||
getPref(R.string.provider_lang_key)?.setOnPreferenceClickListener {
|
getPref(R.string.provider_lang_key)?.setOnPreferenceClickListener {
|
||||||
activity?.getApiProviderLangSettings()?.let { current ->
|
activity?.getApiProviderLangSettings()?.let { current ->
|
||||||
val languages = APIHolder.apis.map { it.lang }.toSet()
|
val languages = synchronized(APIHolder.apis) {
|
||||||
|
APIHolder.apis.map { it.lang }.toSet()
|
||||||
.sortedBy { SubtitleHelper.fromTwoLettersToLanguage(it) } + AllLanguagesName
|
.sortedBy { SubtitleHelper.fromTwoLettersToLanguage(it) } + AllLanguagesName
|
||||||
|
}
|
||||||
|
|
||||||
val currentList = current.map {
|
val currentList = current.map {
|
||||||
languages.indexOf(it)
|
languages.indexOf(it)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import com.lagradost.cloudstream3.utils.TestingUtils
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.cancel
|
import kotlinx.coroutines.cancel
|
||||||
|
import okhttp3.internal.toImmutableList
|
||||||
|
|
||||||
class TestViewModel : ViewModel() {
|
class TestViewModel : ViewModel() {
|
||||||
data class TestProgress(
|
data class TestProgress(
|
||||||
|
@ -81,15 +82,14 @@ class TestViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun init() {
|
fun init() {
|
||||||
val apis = APIHolder.allProviders
|
total = synchronized(APIHolder.allProviders) { APIHolder.allProviders.size }
|
||||||
total = apis.size
|
|
||||||
updateProgress()
|
updateProgress()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startTest() {
|
fun startTest() {
|
||||||
scope = CoroutineScope(Dispatchers.Default)
|
scope = CoroutineScope(Dispatchers.Default)
|
||||||
|
|
||||||
val apis = APIHolder.allProviders
|
val apis = synchronized(APIHolder.allProviders) { APIHolder.allProviders.toTypedArray() }
|
||||||
total = apis.size
|
total = apis.size
|
||||||
failed = 0
|
failed = 0
|
||||||
passed = 0
|
passed = 0
|
||||||
|
|
|
@ -107,7 +107,7 @@ class SetupFragmentExtensions : Fragment() {
|
||||||
if (isSetup)
|
if (isSetup)
|
||||||
if (
|
if (
|
||||||
// If any available languages
|
// 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)
|
findNavController().navigate(R.id.action_navigation_setup_extensions_to_navigation_setup_provider_languages)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -51,8 +51,8 @@ class SetupFragmentProviderLanguage : Fragment() {
|
||||||
ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
|
ArrayAdapter<String>(ctx, R.layout.sort_bottom_single_choice)
|
||||||
|
|
||||||
val current = ctx.getApiProviderLangSettings()
|
val current = ctx.getApiProviderLangSettings()
|
||||||
val langs = APIHolder.apis.map { it.lang }.toSet()
|
val langs = synchronized(APIHolder.apis) { APIHolder.apis.map { it.lang }.toSet()
|
||||||
.sortedBy { SubtitleHelper.fromTwoLettersToLanguage(it) } + AllLanguagesName
|
.sortedBy { SubtitleHelper.fromTwoLettersToLanguage(it) } + AllLanguagesName}
|
||||||
|
|
||||||
val currentList =
|
val currentList =
|
||||||
current.map { langs.indexOf(it) }.filter { it != -1 } // TODO LOOK INTO
|
current.map { langs.indexOf(it) }.filter { it != -1 } // TODO LOOK INTO
|
||||||
|
|
|
@ -18,6 +18,8 @@ const val USER_PROVIDER_API = "user_custom_sites"
|
||||||
|
|
||||||
const val PREFERENCES_NAME = "rebuild_preference"
|
const val PREFERENCES_NAME = "rebuild_preference"
|
||||||
|
|
||||||
|
// TODO degelgate by value for get & set
|
||||||
|
|
||||||
object DataStore {
|
object DataStore {
|
||||||
val mapper: JsonMapper = JsonMapper.builder().addModule(KotlinModule())
|
val mapper: JsonMapper = JsonMapper.builder().addModule(KotlinModule())
|
||||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()
|
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false).build()
|
||||||
|
|
|
@ -96,10 +96,12 @@ object SyncUtil {
|
||||||
.mapNotNull { it.url }.toMutableList()
|
.mapNotNull { it.url }.toMutableList()
|
||||||
|
|
||||||
if (type == "anilist") { // TODO MAKE BETTER
|
if (type == "anilist") { // TODO MAKE BETTER
|
||||||
|
synchronized(apis) {
|
||||||
apis.filter { it.name.contains("Aniflix", ignoreCase = true) }.forEach {
|
apis.filter { it.name.contains("Aniflix", ignoreCase = true) }.forEach {
|
||||||
current.add("${it.mainUrl}/anime/$id")
|
current.add("${it.mainUrl}/anime/$id")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
return current
|
return current
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -211,7 +211,7 @@ object TestingUtils {
|
||||||
|
|
||||||
fun getDeferredProviderTests(
|
fun getDeferredProviderTests(
|
||||||
scope: CoroutineScope,
|
scope: CoroutineScope,
|
||||||
providers: List<MainAPI>,
|
providers: Array<MainAPI>,
|
||||||
logger: (String) -> Unit,
|
logger: (String) -> Unit,
|
||||||
callback: (MainAPI, TestResultProvider) -> Unit
|
callback: (MainAPI, TestResultProvider) -> Unit
|
||||||
) {
|
) {
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout android:orientation="vertical"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:id="@+id/home_header"
|
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_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:background="?attr/primaryBlackBackground"
|
android:background="?attr/primaryBlackBackground"
|
||||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
android:orientation="vertical">
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
|
||||||
|
|
||||||
<View
|
<View
|
||||||
android:id="@+id/home_none_padding"
|
android:id="@+id/home_none_padding"
|
||||||
|
@ -20,10 +20,10 @@
|
||||||
android:layout_height="wrap_content">
|
android:layout_height="wrap_content">
|
||||||
|
|
||||||
<androidx.viewpager2.widget.ViewPager2
|
<androidx.viewpager2.widget.ViewPager2
|
||||||
android:descendantFocusability="blocksDescendants"
|
|
||||||
android:id="@+id/home_preview_viewpager"
|
android:id="@+id/home_preview_viewpager"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="400dp"
|
android:layout_height="400dp"
|
||||||
|
android:descendantFocusability="blocksDescendants"
|
||||||
android:orientation="horizontal">
|
android:orientation="horizontal">
|
||||||
|
|
||||||
</androidx.viewpager2.widget.ViewPager2>
|
</androidx.viewpager2.widget.ViewPager2>
|
||||||
|
@ -39,7 +39,9 @@
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_gravity="top|start"
|
android:layout_gravity="top|start"
|
||||||
android:layout_marginStart="@dimen/navbar_width"
|
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>
|
</FrameLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
@ -131,12 +133,14 @@
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/home_preview_change_api2"
|
android:id="@+id/home_preview_change_api2"
|
||||||
style="@style/BlackButton"
|
style="@style/RegularButtonTV"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_gravity="top|start"
|
android:layout_gravity="top|start"
|
||||||
android:layout_marginStart="@dimen/navbar_width"
|
android:layout_marginStart="@dimen/navbar_width"
|
||||||
android:backgroundTint="@color/semiWhite"
|
android:backgroundTint="@color/semiWhite"
|
||||||
android:minWidth="150dp" />
|
android:minWidth="150dp"
|
||||||
|
android:nextFocusLeft="@id/nav_rail_view"
|
||||||
|
android:nextFocusDown="@id/home_watch_child_recyclerview" />
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
|
|
|
@ -75,7 +75,7 @@
|
||||||
|
|
||||||
<style name="ListViewStyle" parent="Widget.AppCompat.ListView">
|
<style name="ListViewStyle" parent="Widget.AppCompat.ListView">
|
||||||
<item name="android:divider">@null</item>
|
<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>
|
<item name="android:drawSelectorOnTop">false</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
@ -572,6 +572,7 @@
|
||||||
<item name="drawableStartCompat">@drawable/ic_baseline_check_24_listview</item>
|
<item name="drawableStartCompat">@drawable/ic_baseline_check_24_listview</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|
||||||
<style name="NoCheckLabel" parent="@style/AppTextViewStyle">
|
<style name="NoCheckLabel" parent="@style/AppTextViewStyle">
|
||||||
<item name="android:layout_width">match_parent</item>
|
<item name="android:layout_width">match_parent</item>
|
||||||
<item name="android:layout_height">wrap_content</item>
|
<item name="android:layout_height">wrap_content</item>
|
||||||
|
|
Loading…
Reference in a new issue