mirror of
https://github.com/recloudstream/cloudstream.git
synced 2026-07-13 00:13:17 +00:00
Compare commits
1 commit
master
...
pluginrefa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a73e4f5abe |
12 changed files with 176 additions and 148 deletions
|
|
@ -224,17 +224,17 @@ object PluginManager {
|
||||||
// Helper class for updateAllOnlinePluginsAndLoadThem
|
// Helper class for updateAllOnlinePluginsAndLoadThem
|
||||||
data class OnlinePluginData(
|
data class OnlinePluginData(
|
||||||
val savedData: PluginData,
|
val savedData: PluginData,
|
||||||
val onlineData: Pair<String, SitePlugin>,
|
val onlineData: PluginWrapper,
|
||||||
) {
|
) {
|
||||||
val isOutdated =
|
val isOutdated =
|
||||||
onlineData.second.version > savedData.version || onlineData.second.version == PLUGIN_VERSION_ALWAYS_UPDATE
|
onlineData.plugin.version > savedData.version || onlineData.plugin.version == PLUGIN_VERSION_ALWAYS_UPDATE
|
||||||
val isDisabled = onlineData.second.status == PROVIDER_STATUS_DOWN
|
val isDisabled = onlineData.plugin.status == PROVIDER_STATUS_DOWN
|
||||||
|
|
||||||
fun validOnlineData(context: Context): Boolean {
|
fun validOnlineData(context: Context): Boolean {
|
||||||
return getPluginPath(
|
return getPluginPath(
|
||||||
context,
|
context,
|
||||||
savedData.internalName,
|
savedData.internalName,
|
||||||
onlineData.first
|
onlineData.repositoryData.url
|
||||||
).absolutePath == savedData.filePath
|
).absolutePath == savedData.filePath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -282,19 +282,19 @@ object PluginManager {
|
||||||
?: emptyArray()) + PREBUILT_REPOSITORIES
|
?: emptyArray()) + PREBUILT_REPOSITORIES
|
||||||
|
|
||||||
val onlinePlugins = urls.toList().amap {
|
val onlinePlugins = urls.toList().amap {
|
||||||
getRepoPlugins(it.url)?.toList() ?: emptyList()
|
getRepoPlugins(it) ?: emptyList()
|
||||||
}.flatten().distinctBy { it.second.url }
|
}.flatten().distinctBy { it.plugin.url }
|
||||||
|
|
||||||
// Iterates over all offline plugins, compares to remote repo and returns the plugins which are outdated
|
// Iterates over all offline plugins, compares to remote repo and returns the plugins which are outdated
|
||||||
val outdatedPlugins = getPluginsOnline().map { savedData ->
|
val outdatedPlugins = getPluginsOnline().map { savedData ->
|
||||||
onlinePlugins
|
onlinePlugins
|
||||||
.filter { onlineData -> savedData.internalName == onlineData.second.internalName }
|
.filter { onlineData -> savedData.internalName == onlineData.plugin.internalName }
|
||||||
.map { onlineData ->
|
.map { onlineData ->
|
||||||
OnlinePluginData(savedData, onlineData)
|
OnlinePluginData(savedData, onlineData)
|
||||||
}.filter {
|
}.filter {
|
||||||
it.validOnlineData(activity)
|
it.validOnlineData(activity)
|
||||||
}
|
}
|
||||||
}.flatten().distinctBy { it.onlineData.second.url }
|
}.flatten().distinctBy { it.onlineData.plugin.url }
|
||||||
|
|
||||||
debugPrint {
|
debugPrint {
|
||||||
"Outdated plugins: ${outdatedPlugins.filter { it.isOutdated }}"
|
"Outdated plugins: ${outdatedPlugins.filter { it.isOutdated }}"
|
||||||
|
|
@ -309,14 +309,14 @@ object PluginManager {
|
||||||
} else if (pluginData.isOutdated) {
|
} else if (pluginData.isOutdated) {
|
||||||
downloadPlugin(
|
downloadPlugin(
|
||||||
activity,
|
activity,
|
||||||
pluginData.onlineData.second.url,
|
pluginData.onlineData.plugin.url,
|
||||||
pluginData.onlineData.second.fileHash,
|
pluginData.onlineData.plugin.fileHash,
|
||||||
pluginData.savedData.internalName,
|
pluginData.savedData.internalName,
|
||||||
File(pluginData.savedData.filePath),
|
File(pluginData.savedData.filePath),
|
||||||
true
|
true
|
||||||
).let { success ->
|
).let { success ->
|
||||||
if (success)
|
if (success)
|
||||||
updatedPlugins.add(pluginData.onlineData.second.name)
|
updatedPlugins.add(pluginData.onlineData.plugin.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -359,15 +359,15 @@ object PluginManager {
|
||||||
val urls = (getKey<Array<RepositoryData>>(REPOSITORIES_KEY)
|
val urls = (getKey<Array<RepositoryData>>(REPOSITORIES_KEY)
|
||||||
?: emptyArray()) + PREBUILT_REPOSITORIES
|
?: emptyArray()) + PREBUILT_REPOSITORIES
|
||||||
val onlinePlugins = urls.toList().amap {
|
val onlinePlugins = urls.toList().amap {
|
||||||
getRepoPlugins(it.url)?.toList() ?: emptyList()
|
getRepoPlugins(it)?.toList() ?: emptyList()
|
||||||
}.flatten().distinctBy { it.second.url }
|
}.flatten().distinctBy { it.plugin.url }
|
||||||
|
|
||||||
val providerLang = activity.getApiProviderLangSettings()
|
val providerLang = activity.getApiProviderLangSettings()
|
||||||
//Log.i(TAG, "providerLang => ${providerLang.toJson()}")
|
//Log.i(TAG, "providerLang => ${providerLang.toJson()}")
|
||||||
|
|
||||||
// Iterate online repos and returns not downloaded plugins
|
// Iterate online repos and returns not downloaded plugins
|
||||||
val notDownloadedPlugins = onlinePlugins.mapNotNull { onlineData ->
|
val notDownloadedPlugins = onlinePlugins.mapNotNull { onlineData ->
|
||||||
val sitePlugin = onlineData.second
|
val sitePlugin = onlineData.plugin
|
||||||
val tvtypes = sitePlugin.tvTypes ?: listOf()
|
val tvtypes = sitePlugin.tvTypes ?: listOf()
|
||||||
|
|
||||||
//Don't include empty urls
|
//Don't include empty urls
|
||||||
|
|
@ -379,7 +379,7 @@ object PluginManager {
|
||||||
}
|
}
|
||||||
|
|
||||||
//Omit already existing plugins
|
//Omit already existing plugins
|
||||||
if (getPluginPath(activity, sitePlugin.internalName, onlineData.first).exists()) {
|
if (getPluginPath(activity, sitePlugin.internalName, onlineData.repositoryData.url).exists()) {
|
||||||
Log.i(TAG, "Skip > ${sitePlugin.internalName}")
|
Log.i(TAG, "Skip > ${sitePlugin.internalName}")
|
||||||
return@mapNotNull null
|
return@mapNotNull null
|
||||||
}
|
}
|
||||||
|
|
@ -421,14 +421,14 @@ object PluginManager {
|
||||||
notDownloadedPlugins.amap { pluginData ->
|
notDownloadedPlugins.amap { pluginData ->
|
||||||
downloadPlugin(
|
downloadPlugin(
|
||||||
activity,
|
activity,
|
||||||
pluginData.onlineData.second.url,
|
pluginData.onlineData.plugin.url,
|
||||||
pluginData.onlineData.second.fileHash,
|
pluginData.onlineData.plugin.fileHash,
|
||||||
pluginData.savedData.internalName,
|
pluginData.savedData.internalName,
|
||||||
pluginData.onlineData.first,
|
pluginData.onlineData.repositoryData.url,
|
||||||
!pluginData.isDisabled
|
!pluginData.isDisabled
|
||||||
).let { success ->
|
).let { success ->
|
||||||
if (success)
|
if (success)
|
||||||
newDownloadPlugins.add(pluginData.onlineData.second.name)
|
newDownloadPlugins.add(pluginData.onlineData.plugin.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -835,16 +835,16 @@ object PluginManager {
|
||||||
val urls = (getKey<Array<RepositoryData>>(REPOSITORIES_KEY)
|
val urls = (getKey<Array<RepositoryData>>(REPOSITORIES_KEY)
|
||||||
?: emptyArray()) + PREBUILT_REPOSITORIES
|
?: emptyArray()) + PREBUILT_REPOSITORIES
|
||||||
val onlinePlugins = urls.toList().amap {
|
val onlinePlugins = urls.toList().amap {
|
||||||
getRepoPlugins(it.url)?.toList() ?: emptyList()
|
getRepoPlugins(it) ?: emptyList()
|
||||||
}.flatten().distinctBy { it.second.url }
|
}.flatten().distinctBy { it.plugin.url }
|
||||||
|
|
||||||
val allPlugins = getPluginsOnline().flatMap { savedData ->
|
val allPlugins = getPluginsOnline().flatMap { savedData ->
|
||||||
onlinePlugins
|
onlinePlugins
|
||||||
.filter { it.second.internalName == savedData.internalName }
|
.filter { it.plugin.internalName == savedData.internalName }
|
||||||
.mapNotNull { onlineData ->
|
.mapNotNull { onlineData ->
|
||||||
OnlinePluginData(savedData, onlineData).takeIf { it.validOnlineData(activity) }
|
OnlinePluginData(savedData, onlineData).takeIf { it.validOnlineData(activity) }
|
||||||
}
|
}
|
||||||
}.distinctBy { it.onlineData.second.url }
|
}.distinctBy { it.onlineData.plugin.url }
|
||||||
|
|
||||||
val updatedPlugins = mutableListOf<String>()
|
val updatedPlugins = mutableListOf<String>()
|
||||||
|
|
||||||
|
|
@ -852,7 +852,7 @@ object PluginManager {
|
||||||
if (pluginData.isDisabled) {
|
if (pluginData.isDisabled) {
|
||||||
Log.e(
|
Log.e(
|
||||||
"PluginManager",
|
"PluginManager",
|
||||||
"Unloading disabled plugin: ${pluginData.onlineData.second.name}"
|
"Unloading disabled plugin: ${pluginData.onlineData.plugin.name}"
|
||||||
)
|
)
|
||||||
unloadPlugin(pluginData.savedData.filePath)
|
unloadPlugin(pluginData.savedData.filePath)
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -861,14 +861,14 @@ object PluginManager {
|
||||||
|
|
||||||
if (downloadPlugin(
|
if (downloadPlugin(
|
||||||
activity,
|
activity,
|
||||||
pluginData.onlineData.second.url,
|
pluginData.onlineData.plugin.url,
|
||||||
pluginData.onlineData.second.fileHash,
|
pluginData.onlineData.plugin.fileHash,
|
||||||
pluginData.savedData.internalName,
|
pluginData.savedData.internalName,
|
||||||
existingFile,
|
existingFile,
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
updatedPlugins.add(pluginData.onlineData.second.name)
|
updatedPlugins.add(pluginData.onlineData.plugin.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.also {
|
}.also {
|
||||||
|
|
|
||||||
|
|
@ -75,6 +75,26 @@ data class SitePlugin(
|
||||||
@JsonProperty("fileHash") @SerialName("fileHash") val fileHash: String?,
|
@JsonProperty("fileHash") @SerialName("fileHash") val fileHash: String?,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class PluginWrapper(
|
||||||
|
@JsonProperty("repository") @SerialName("repository") val repository: Repository,
|
||||||
|
@JsonProperty("repositoryData") @SerialName("repositoryData") val repositoryData: RepositoryData,
|
||||||
|
@JsonProperty("plugin") @SerialName("plugin") val plugin: SitePlugin
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
private val localRepository = Repository("", "", "", 1, emptyList())
|
||||||
|
private val localRepositoryData = RepositoryData("", "", "")
|
||||||
|
fun getLocalPluginWrapper(plugin: SitePlugin): PluginWrapper {
|
||||||
|
return PluginWrapper(
|
||||||
|
localRepository,
|
||||||
|
localRepositoryData,
|
||||||
|
plugin
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
object RepositoryManager {
|
object RepositoryManager {
|
||||||
const val ONLINE_PLUGINS_FOLDER = "Extensions"
|
const val ONLINE_PLUGINS_FOLDER = "Extensions"
|
||||||
val PREBUILT_REPOSITORIES: Array<RepositoryData> by lazy {
|
val PREBUILT_REPOSITORIES: Array<RepositoryData> by lazy {
|
||||||
|
|
@ -135,7 +155,8 @@ object RepositoryManager {
|
||||||
suspend fun parseRepository(url: String): Repository? {
|
suspend fun parseRepository(url: String): Repository? {
|
||||||
return safeAsync {
|
return safeAsync {
|
||||||
// Take manifestVersion and such into account later
|
// Take manifestVersion and such into account later
|
||||||
app.get(convertRawGitUrl(url), cacheTime = 5, cacheUnit = TimeUnit.MINUTES).parsedSafe<Repository>()
|
app.get(convertRawGitUrl(url), cacheTime = 5, cacheUnit = TimeUnit.MINUTES)
|
||||||
|
.parsedSafe<Repository>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -153,13 +174,14 @@ object RepositoryManager {
|
||||||
/**
|
/**
|
||||||
* Gets all plugins from repositories and pairs them with the repository url
|
* Gets all plugins from repositories and pairs them with the repository url
|
||||||
*/
|
*/
|
||||||
suspend fun getRepoPlugins(repositoryUrl: String): List<Pair<String, SitePlugin>>? {
|
suspend fun getRepoPlugins(repositoryData: RepositoryData): List<PluginWrapper>? {
|
||||||
val repo = parseRepository(repositoryUrl) ?: return null
|
val repo = parseRepository(repositoryData.url) ?: return null
|
||||||
return repo.pluginLists.amap { url ->
|
val list = repo.pluginLists.amap { url ->
|
||||||
parsePlugins(url).map {
|
parsePlugins(url).map {
|
||||||
repositoryUrl to it
|
PluginWrapper(repo, repositoryData, it)
|
||||||
}
|
}
|
||||||
}.flatten()
|
}.flatten()
|
||||||
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun downloadPluginToFile(
|
suspend fun downloadPluginToFile(
|
||||||
|
|
|
||||||
|
|
@ -114,11 +114,7 @@ class ExtensionsFragment : BaseFragment<FragmentExtensionsBinding>(
|
||||||
adapter = RepoAdapter(false, {
|
adapter = RepoAdapter(false, {
|
||||||
findNavController().navigate(
|
findNavController().navigate(
|
||||||
R.id.navigation_settings_extensions_to_navigation_settings_plugins,
|
R.id.navigation_settings_extensions_to_navigation_settings_plugins,
|
||||||
PluginsFragment.newInstance(
|
PluginsFragment.newInstance(it)
|
||||||
it.name,
|
|
||||||
it.url,
|
|
||||||
false
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
}, { repo ->
|
}, { repo ->
|
||||||
// Prompt user before deleting repo
|
// Prompt user before deleting repo
|
||||||
|
|
@ -156,7 +152,7 @@ class ExtensionsFragment : BaseFragment<FragmentExtensionsBinding>(
|
||||||
binding.repoRecyclerView.isVisible = repos.isNotEmpty()
|
binding.repoRecyclerView.isVisible = repos.isNotEmpty()
|
||||||
binding.blankRepoScreen.isVisible = repos.isEmpty()
|
binding.blankRepoScreen.isVisible = repos.isEmpty()
|
||||||
(binding.repoRecyclerView.adapter as? RepoAdapter)?.submitList(repos.toList())
|
(binding.repoRecyclerView.adapter as? RepoAdapter)?.submitList(repos.toList())
|
||||||
pluginViewModel.updatePluginList(binding.root.context, repos.map { it.url })
|
pluginViewModel.updatePluginList(binding.root.context, repos.toList())
|
||||||
}
|
}
|
||||||
|
|
||||||
observeNullable(extensionViewModel.pluginStats) { value ->
|
observeNullable(extensionViewModel.pluginStats) { value ->
|
||||||
|
|
@ -185,10 +181,8 @@ class ExtensionsFragment : BaseFragment<FragmentExtensionsBinding>(
|
||||||
binding.pluginStorageAppbar.setOnClickListener {
|
binding.pluginStorageAppbar.setOnClickListener {
|
||||||
findNavController().navigate(
|
findNavController().navigate(
|
||||||
R.id.navigation_settings_extensions_to_navigation_settings_plugins,
|
R.id.navigation_settings_extensions_to_navigation_settings_plugins,
|
||||||
PluginsFragment.newInstance(
|
PluginsFragment.newLocalInstance(
|
||||||
getString(R.string.extensions),
|
getString(R.string.extensions),
|
||||||
"",
|
|
||||||
true
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -201,9 +195,8 @@ class ExtensionsFragment : BaseFragment<FragmentExtensionsBinding>(
|
||||||
)
|
)
|
||||||
setRecycledViewPool(PluginAdapter.sharedPool)
|
setRecycledViewPool(PluginAdapter.sharedPool)
|
||||||
adapter =
|
adapter =
|
||||||
PluginAdapter {
|
PluginAdapter(true) {
|
||||||
val urls = extensionViewModel.repositories.value?.map { repo -> repo.url }
|
val urls = extensionViewModel.repositories.value?.toList() ?: emptyList()
|
||||||
?: emptyList()
|
|
||||||
pluginViewModel.handlePluginAction(activity, urls, it, false)
|
pluginViewModel.handlePluginAction(activity, urls, it, false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -307,13 +300,12 @@ class ExtensionsFragment : BaseFragment<FragmentExtensionsBinding>(
|
||||||
extensionViewModel.loadStats()
|
extensionViewModel.loadStats()
|
||||||
extensionViewModel.loadRepositories()
|
extensionViewModel.loadRepositories()
|
||||||
|
|
||||||
val plugins = RepositoryManager.getRepoPlugins(url)
|
val plugins = RepositoryManager.getRepoPlugins(newRepo)
|
||||||
if (plugins.isNullOrEmpty()) {
|
if (plugins.isNullOrEmpty()) {
|
||||||
showToast(R.string.no_plugins_found_error, Toast.LENGTH_LONG)
|
showToast(R.string.no_plugins_found_error, Toast.LENGTH_LONG)
|
||||||
} else {
|
} else {
|
||||||
this@ExtensionsFragment.activity?.addRepositoryDialog(
|
this@ExtensionsFragment.activity?.addRepositoryDialog(
|
||||||
fixedName,
|
newRepo
|
||||||
url,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -55,16 +55,16 @@ class ExtensionsViewModel : ViewModel() {
|
||||||
?: emptyArray()) + PREBUILT_REPOSITORIES
|
?: emptyArray()) + PREBUILT_REPOSITORIES
|
||||||
|
|
||||||
val onlinePlugins = urls.toList().amap {
|
val onlinePlugins = urls.toList().amap {
|
||||||
RepositoryManager.getRepoPlugins(it.url)?.toList() ?: emptyList()
|
RepositoryManager.getRepoPlugins(it)?.toList() ?: emptyList()
|
||||||
}.flatten().distinctBy { it.second.url }
|
}.flatten().distinctBy { it.plugin.url }
|
||||||
|
|
||||||
// Iterates over all offline plugins, compares to remote repo and returns the plugins which are outdated
|
// Iterates over all offline plugins, compares to remote repo and returns the plugins which are outdated
|
||||||
val outdatedPlugins = getPluginsOnline().map { savedData ->
|
val outdatedPlugins = getPluginsOnline().flatMap { savedData ->
|
||||||
onlinePlugins.filter { onlineData -> savedData.internalName == onlineData.second.internalName }
|
onlinePlugins.filter { onlineData -> savedData.internalName == onlineData.plugin.internalName }
|
||||||
.map { onlineData ->
|
.map { onlineData ->
|
||||||
PluginManager.OnlinePluginData(savedData, onlineData)
|
PluginManager.OnlinePluginData(savedData, onlineData)
|
||||||
}
|
}
|
||||||
}.flatten().distinctBy { it.onlineData.second.url }
|
}.distinctBy { it.onlineData.plugin.url }
|
||||||
|
|
||||||
val total = onlinePlugins.count()
|
val total = onlinePlugins.count()
|
||||||
val disabled = outdatedPlugins.count { it.isDisabled }
|
val disabled = outdatedPlugins.count { it.isDisabled }
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import com.lagradost.cloudstream3.R
|
||||||
import com.lagradost.cloudstream3.TvType
|
import com.lagradost.cloudstream3.TvType
|
||||||
import com.lagradost.cloudstream3.databinding.RepositoryItemBinding
|
import com.lagradost.cloudstream3.databinding.RepositoryItemBinding
|
||||||
import com.lagradost.cloudstream3.plugins.PluginManager
|
import com.lagradost.cloudstream3.plugins.PluginManager
|
||||||
|
import com.lagradost.cloudstream3.plugins.PluginWrapper
|
||||||
import com.lagradost.cloudstream3.ui.BaseDiffCallback
|
import com.lagradost.cloudstream3.ui.BaseDiffCallback
|
||||||
import com.lagradost.cloudstream3.ui.NoStateAdapter
|
import com.lagradost.cloudstream3.ui.NoStateAdapter
|
||||||
import com.lagradost.cloudstream3.ui.ViewHolderState
|
import com.lagradost.cloudstream3.ui.ViewHolderState
|
||||||
|
|
@ -34,7 +35,7 @@ import kotlin.math.log10
|
||||||
import kotlin.math.pow
|
import kotlin.math.pow
|
||||||
|
|
||||||
data class PluginViewData(
|
data class PluginViewData(
|
||||||
val plugin: Plugin,
|
val pluginWrapper: PluginWrapper,
|
||||||
val isDownloaded: Boolean,
|
val isDownloaded: Boolean,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -44,9 +45,9 @@ class RepositoryViewHolderState(view: ViewBinding) : ViewHolderState<Any>(view)
|
||||||
}
|
}
|
||||||
|
|
||||||
class PluginAdapter(
|
class PluginAdapter(
|
||||||
val iconClickCallback: (Plugin) -> Unit
|
val showRepositoryNames: Boolean = false, val iconClickCallback: (PluginWrapper) -> Unit,
|
||||||
) : NoStateAdapter<PluginViewData>(diffCallback = BaseDiffCallback(itemSame = { a, b ->
|
) : NoStateAdapter<PluginViewData>(diffCallback = BaseDiffCallback(itemSame = { a, b ->
|
||||||
a.plugin.second.internalName == b.plugin.second.internalName && a.plugin.first == b.plugin.first
|
a.pluginWrapper.plugin.internalName == b.pluginWrapper.plugin.internalName && a.pluginWrapper.repositoryData.url == b.pluginWrapper.repositoryData.url
|
||||||
})) {
|
})) {
|
||||||
override fun onCreateContent(parent: ViewGroup): ViewHolderState<Any> {
|
override fun onCreateContent(parent: ViewGroup): ViewHolderState<Any> {
|
||||||
val layout = if (isLayout(TV)) R.layout.repository_item_tv else R.layout.repository_item
|
val layout = if (isLayout(TV)) R.layout.repository_item_tv else R.layout.repository_item
|
||||||
|
|
@ -73,14 +74,22 @@ class PluginAdapter(
|
||||||
val binding = holder.view as? RepositoryItemBinding ?: return
|
val binding = holder.view as? RepositoryItemBinding ?: return
|
||||||
val itemView = holder.itemView
|
val itemView = holder.itemView
|
||||||
|
|
||||||
val metadata = item.plugin.second
|
val metadata = item.pluginWrapper.plugin
|
||||||
val disabled = metadata.status == PROVIDER_STATUS_DOWN
|
val disabled = metadata.status == PROVIDER_STATUS_DOWN
|
||||||
val name = metadata.name.removeSuffix("Provider")
|
val name = metadata.name.removeSuffix("Provider")
|
||||||
val alpha = if (disabled) 0.6f else 1f
|
val alpha = if (disabled) 0.6f else 1f
|
||||||
val isLocal = !item.plugin.second.url.startsWith("http")
|
val isLocal = !item.pluginWrapper.plugin.url.startsWith("http")
|
||||||
binding.mainText.alpha = alpha
|
binding.mainText.alpha = alpha
|
||||||
binding.subText.alpha = alpha
|
binding.subText.alpha = alpha
|
||||||
|
|
||||||
|
binding.repositoryNameText.isVisible = showRepositoryNames
|
||||||
|
if (showRepositoryNames) {
|
||||||
|
val name = item.pluginWrapper.repositoryData.name
|
||||||
|
binding.repositoryNameText.text = name
|
||||||
|
} else {
|
||||||
|
binding.repositoryNameText.text = ""
|
||||||
|
}
|
||||||
|
|
||||||
val drawableInt = if (item.isDownloaded)
|
val drawableInt = if (item.isDownloaded)
|
||||||
R.drawable.ic_baseline_delete_outline_24
|
R.drawable.ic_baseline_delete_outline_24
|
||||||
else R.drawable.netflix_download
|
else R.drawable.netflix_download
|
||||||
|
|
@ -89,7 +98,7 @@ class PluginAdapter(
|
||||||
binding.actionButton.setImageResource(drawableInt)
|
binding.actionButton.setImageResource(drawableInt)
|
||||||
|
|
||||||
binding.actionButton.setOnClickListener {
|
binding.actionButton.setOnClickListener {
|
||||||
iconClickCallback.invoke(item.plugin)
|
iconClickCallback.invoke(item.pluginWrapper)
|
||||||
}
|
}
|
||||||
itemView.setOnClickListener {
|
itemView.setOnClickListener {
|
||||||
if (isLocal) return@setOnClickListener
|
if (isLocal) return@setOnClickListener
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ class PluginDetailsFragment(val data: PluginViewData) : BaseBottomSheetDialogFra
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBindingCreated(binding: FragmentPluginDetailsBinding) {
|
override fun onBindingCreated(binding: FragmentPluginDetailsBinding) {
|
||||||
val metadata = data.plugin.second
|
val metadata = data.pluginWrapper.plugin
|
||||||
binding.apply {
|
binding.apply {
|
||||||
pluginIcon.loadImage(metadata.iconUrl?.replace("%size%", "$iconSize")
|
pluginIcon.loadImage(metadata.iconUrl?.replace("%size%", "$iconSize")
|
||||||
?.replace("%exact_size%", "$iconSizeExact")) {
|
?.replace("%exact_size%", "$iconSizeExact")) {
|
||||||
|
|
@ -135,7 +135,7 @@ class PluginDetailsFragment(val data: PluginViewData) : BaseBottomSheetDialogFra
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateVoting(value: Int) {
|
private fun updateVoting(value: Int) {
|
||||||
val metadata = data.plugin.second
|
val metadata = data.pluginWrapper.plugin
|
||||||
binding?.apply {
|
binding?.apply {
|
||||||
pluginVotes.text = value.toString()
|
pluginVotes.text = value.toString()
|
||||||
if (metadata.hasVoted()) {
|
if (metadata.hasVoted()) {
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,13 @@ import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setSyst
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setToolBarScrollFlags
|
||||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
||||||
import com.lagradost.cloudstream3.utils.AppContextUtils.getApiProviderLangSettings
|
import com.lagradost.cloudstream3.utils.AppContextUtils.getApiProviderLangSettings
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.toJson
|
||||||
|
import com.lagradost.cloudstream3.utils.AppUtils.tryParseJson
|
||||||
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog
|
import com.lagradost.cloudstream3.utils.SingleSelectionHelper.showMultiDialog
|
||||||
import com.lagradost.cloudstream3.utils.SubtitleHelper.getNameNextToFlagEmoji
|
import com.lagradost.cloudstream3.utils.SubtitleHelper.getNameNextToFlagEmoji
|
||||||
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
import com.lagradost.cloudstream3.utils.UIHelper.toPx
|
||||||
|
|
||||||
const val PLUGINS_BUNDLE_NAME = "name"
|
const val PLUGINS_BUNDLE_DATA = "data"
|
||||||
const val PLUGINS_BUNDLE_URL = "url"
|
|
||||||
const val PLUGINS_BUNDLE_LOCAL = "isLocal"
|
const val PLUGINS_BUNDLE_LOCAL = "isLocal"
|
||||||
|
|
||||||
class PluginsFragment : BaseFragment<FragmentPluginsBinding>(
|
class PluginsFragment : BaseFragment<FragmentPluginsBinding>(
|
||||||
|
|
@ -61,24 +62,25 @@ class PluginsFragment : BaseFragment<FragmentPluginsBinding>(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val name = arguments?.getString(PLUGINS_BUNDLE_NAME)
|
val repositoryData = arguments?.getString(PLUGINS_BUNDLE_DATA)?.let { data ->
|
||||||
val url = arguments?.getString(PLUGINS_BUNDLE_URL)
|
tryParseJson<RepositoryData>(data)
|
||||||
|
}
|
||||||
val isLocal = arguments?.getBoolean(PLUGINS_BUNDLE_LOCAL) == true
|
val isLocal = arguments?.getBoolean(PLUGINS_BUNDLE_LOCAL) == true
|
||||||
// download all extensions button
|
// download all extensions button
|
||||||
val downloadAllButton = binding.settingsToolbar.menu?.findItem(R.id.download_all)
|
val downloadAllButton = binding.settingsToolbar.menu?.findItem(R.id.download_all)
|
||||||
|
|
||||||
if (url == null || name == null) {
|
if (repositoryData == null) {
|
||||||
dispatchBackPressed()
|
dispatchBackPressed()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
setToolBarScrollFlags()
|
setToolBarScrollFlags()
|
||||||
setUpToolbar(name)
|
setUpToolbar(repositoryData.name)
|
||||||
binding.settingsToolbar.apply {
|
binding.settingsToolbar.apply {
|
||||||
setOnMenuItemClickListener { menuItem ->
|
setOnMenuItemClickListener { menuItem ->
|
||||||
when (menuItem?.itemId) {
|
when (menuItem?.itemId) {
|
||||||
R.id.download_all -> {
|
R.id.download_all -> {
|
||||||
PluginsViewModel.downloadAll(activity, url, pluginViewModel)
|
PluginsViewModel.downloadAll(activity, repositoryData, pluginViewModel)
|
||||||
}
|
}
|
||||||
|
|
||||||
R.id.lang_filter -> {
|
R.id.lang_filter -> {
|
||||||
|
|
@ -159,7 +161,7 @@ class PluginsFragment : BaseFragment<FragmentPluginsBinding>(
|
||||||
setRecycledViewPool(PluginAdapter.sharedPool)
|
setRecycledViewPool(PluginAdapter.sharedPool)
|
||||||
adapter =
|
adapter =
|
||||||
PluginAdapter {
|
PluginAdapter {
|
||||||
pluginViewModel.handlePluginAction(activity, listOf(url), it, isLocal)
|
pluginViewModel.handlePluginAction(activity, listOf(repositoryData), it, isLocal)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -183,7 +185,7 @@ class PluginsFragment : BaseFragment<FragmentPluginsBinding>(
|
||||||
|
|
||||||
binding.tvtypesChipsScroll.root.isVisible = false
|
binding.tvtypesChipsScroll.root.isVisible = false
|
||||||
} else {
|
} else {
|
||||||
pluginViewModel.updatePluginList(context, listOf(url))
|
pluginViewModel.updatePluginList(context, listOf(repositoryData))
|
||||||
binding.tvtypesChipsScroll.root.isVisible = true
|
binding.tvtypesChipsScroll.root.isVisible = true
|
||||||
// not needed for users but may be useful for devs
|
// not needed for users but may be useful for devs
|
||||||
downloadAllButton?.isVisible = BuildConfig.DEBUG
|
downloadAllButton?.isVisible = BuildConfig.DEBUG
|
||||||
|
|
@ -204,21 +206,17 @@ class PluginsFragment : BaseFragment<FragmentPluginsBinding>(
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
fun newInstance(name: String, url: String, isLocal: Boolean): Bundle {
|
fun newInstance(repositoryData: RepositoryData): Bundle {
|
||||||
return Bundle().apply {
|
return Bundle().apply {
|
||||||
putString(PLUGINS_BUNDLE_NAME, name)
|
putString(PLUGINS_BUNDLE_DATA, repositoryData.toJson())
|
||||||
putString(PLUGINS_BUNDLE_URL, url)
|
putBoolean(PLUGINS_BUNDLE_LOCAL, false)
|
||||||
putBoolean(PLUGINS_BUNDLE_LOCAL, isLocal)
|
}
|
||||||
|
}
|
||||||
|
fun newLocalInstance(name: String): Bundle {
|
||||||
|
return Bundle().apply {
|
||||||
|
putString(PLUGINS_BUNDLE_DATA, RepositoryData("", name, "").toJson())
|
||||||
|
putBoolean(PLUGINS_BUNDLE_LOCAL, true)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// class RepoSearchView(context: Context) : android.widget.SearchView(context) {
|
|
||||||
// var onActionViewCollapsed = {}
|
|
||||||
//
|
|
||||||
// override fun onActionViewCollapsed() {
|
|
||||||
// onActionViewCollapsed()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -17,8 +17,8 @@ import com.lagradost.cloudstream3.amap
|
||||||
import com.lagradost.cloudstream3.mvvm.launchSafe
|
import com.lagradost.cloudstream3.mvvm.launchSafe
|
||||||
import com.lagradost.cloudstream3.plugins.PluginManager
|
import com.lagradost.cloudstream3.plugins.PluginManager
|
||||||
import com.lagradost.cloudstream3.plugins.PluginManager.getPluginPath
|
import com.lagradost.cloudstream3.plugins.PluginManager.getPluginPath
|
||||||
|
import com.lagradost.cloudstream3.plugins.PluginWrapper
|
||||||
import com.lagradost.cloudstream3.plugins.RepositoryManager
|
import com.lagradost.cloudstream3.plugins.RepositoryManager
|
||||||
import com.lagradost.cloudstream3.plugins.SitePlugin
|
|
||||||
import com.lagradost.cloudstream3.utils.txt
|
import com.lagradost.cloudstream3.utils.txt
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||||
|
|
@ -26,8 +26,6 @@ import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread
|
||||||
import com.lagradost.cloudstream3.utils.Levenshtein
|
import com.lagradost.cloudstream3.utils.Levenshtein
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
// String => repository url
|
|
||||||
typealias Plugin = Pair<String, SitePlugin>
|
|
||||||
/**
|
/**
|
||||||
* The boolean signifies if the plugin list should be scrolled to the top, used for searching.
|
* The boolean signifies if the plugin list should be scrolled to the top, used for searching.
|
||||||
* */
|
* */
|
||||||
|
|
@ -40,7 +38,7 @@ class PluginsViewModel : ViewModel() {
|
||||||
set(value) {
|
set(value) {
|
||||||
// Also set all the plugin languages for easier filtering
|
// Also set all the plugin languages for easier filtering
|
||||||
value.map { pluginViewData ->
|
value.map { pluginViewData ->
|
||||||
val language = pluginViewData.plugin.second.language?.lowercase()
|
val language = pluginViewData.pluginWrapper.plugin.language?.lowercase()
|
||||||
pluginLanguages.add(
|
pluginLanguages.add(
|
||||||
when {
|
when {
|
||||||
language.isNullOrBlank() -> "none"
|
language.isNullOrBlank() -> "none"
|
||||||
|
|
@ -62,7 +60,7 @@ class PluginsViewModel : ViewModel() {
|
||||||
private var currentQuery: String? = null
|
private var currentQuery: String? = null
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val repositoryCache: MutableMap<String, List<Plugin>> = mutableMapOf()
|
private val repositoryCache: MutableMap<String, List<PluginWrapper>> = mutableMapOf()
|
||||||
const val TAG = "PLG"
|
const val TAG = "PLG"
|
||||||
|
|
||||||
private fun isDownloaded(
|
private fun isDownloaded(
|
||||||
|
|
@ -74,32 +72,33 @@ class PluginsViewModel : ViewModel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun getPlugins(
|
private suspend fun getPlugins(
|
||||||
repositoryUrl: String,
|
repository: RepositoryData,
|
||||||
canUseCache: Boolean = true
|
canUseCache: Boolean = true
|
||||||
): List<Plugin> {
|
): List<PluginWrapper> {
|
||||||
Log.i(TAG, "getPlugins = $repositoryUrl")
|
Log.i(TAG, "getPlugins = $repository")
|
||||||
if (canUseCache && repositoryCache.containsKey(repositoryUrl)) {
|
if (canUseCache && repositoryCache.containsKey(repository.url)) {
|
||||||
repositoryCache[repositoryUrl]?.let {
|
repositoryCache[repository.url]?.let {
|
||||||
return it
|
return it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return RepositoryManager.getRepoPlugins(repositoryUrl)
|
|
||||||
?.also { repositoryCache[repositoryUrl] = it } ?: emptyList()
|
return RepositoryManager.getRepoPlugins(repository)
|
||||||
|
?.also { repositoryCache[repository.url] = it } ?: emptyList()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param viewModel optional, updates the plugins livedata for that viewModel if included
|
* @param viewModel optional, updates the plugins livedata for that viewModel if included
|
||||||
* */
|
* */
|
||||||
fun downloadAll(activity: Activity?, repositoryUrl: String, viewModel: PluginsViewModel?) =
|
fun downloadAll(activity: Activity?, repository: RepositoryData, viewModel: PluginsViewModel?) =
|
||||||
ioSafe {
|
ioSafe {
|
||||||
if (activity == null) return@ioSafe
|
if (activity == null) return@ioSafe
|
||||||
val plugins = getPlugins(repositoryUrl)
|
val plugins = getPlugins(repository)
|
||||||
|
|
||||||
plugins.filter { plugin ->
|
plugins.filter { pluginWrapper ->
|
||||||
!isDownloaded(
|
!isDownloaded(
|
||||||
activity,
|
activity,
|
||||||
plugin.second.internalName,
|
pluginWrapper.plugin.internalName,
|
||||||
repositoryUrl
|
repository.url
|
||||||
)
|
)
|
||||||
}.also { list ->
|
}.also { list ->
|
||||||
main {
|
main {
|
||||||
|
|
@ -124,13 +123,13 @@ class PluginsViewModel : ViewModel() {
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}.amap { (repo, metadata) ->
|
}.amap { (_, repo, metadata) ->
|
||||||
PluginManager.downloadPlugin(
|
PluginManager.downloadPlugin(
|
||||||
activity,
|
activity,
|
||||||
metadata.url,
|
metadata.url,
|
||||||
metadata.fileHash,
|
metadata.fileHash,
|
||||||
metadata.internalName,
|
metadata.internalName,
|
||||||
repo,
|
repo.url,
|
||||||
metadata.status != PROVIDER_STATUS_DOWN
|
metadata.status != PROVIDER_STATUS_DOWN
|
||||||
)
|
)
|
||||||
}.main { list ->
|
}.main { list ->
|
||||||
|
|
@ -143,7 +142,7 @@ class PluginsViewModel : ViewModel() {
|
||||||
),
|
),
|
||||||
Toast.LENGTH_SHORT
|
Toast.LENGTH_SHORT
|
||||||
)
|
)
|
||||||
viewModel?.updatePluginListPrivate(activity, listOf(repositoryUrl))
|
viewModel?.updatePluginListPrivate(activity, listOf(repository))
|
||||||
} else if (list.isNotEmpty()) {
|
} else if (list.isNotEmpty()) {
|
||||||
showToast(R.string.download_failed, Toast.LENGTH_SHORT)
|
showToast(R.string.download_failed, Toast.LENGTH_SHORT)
|
||||||
}
|
}
|
||||||
|
|
@ -157,32 +156,32 @@ class PluginsViewModel : ViewModel() {
|
||||||
* */
|
* */
|
||||||
fun handlePluginAction(
|
fun handlePluginAction(
|
||||||
activity: Activity?,
|
activity: Activity?,
|
||||||
repositoryUrls: List<String>,
|
repositoryUrls: List<RepositoryData>,
|
||||||
plugin: Plugin,
|
pluginWrapper: PluginWrapper,
|
||||||
isLocal: Boolean
|
isLocal: Boolean
|
||||||
) = ioSafe {
|
) = ioSafe {
|
||||||
Log.i(TAG, "handlePluginAction = ${repositoryUrls}, $plugin, $isLocal")
|
Log.i(TAG, "handlePluginAction = ${repositoryUrls}, $pluginWrapper, $isLocal")
|
||||||
|
|
||||||
if (activity == null) return@ioSafe
|
if (activity == null) return@ioSafe
|
||||||
val (repo, metadata) = plugin
|
val (_, repositoryData, metadata) = pluginWrapper
|
||||||
|
|
||||||
val file = if (isLocal) File(plugin.second.url) else getPluginPath(
|
val file = if (isLocal) File(pluginWrapper.plugin.url) else getPluginPath(
|
||||||
activity,
|
activity,
|
||||||
plugin.second.internalName,
|
pluginWrapper.plugin.internalName,
|
||||||
plugin.first
|
pluginWrapper.repositoryData.url
|
||||||
)
|
)
|
||||||
|
|
||||||
val (success, message) = if (file.exists()) {
|
val (success, message) = if (file.exists()) {
|
||||||
PluginManager.deletePlugin(file) to R.string.plugin_deleted
|
PluginManager.deletePlugin(file) to R.string.plugin_deleted
|
||||||
} else {
|
} else {
|
||||||
val isEnabled = plugin.second.status != PROVIDER_STATUS_DOWN
|
val isEnabled = pluginWrapper.plugin.status != PROVIDER_STATUS_DOWN
|
||||||
val message = if (isEnabled) R.string.plugin_loaded else R.string.plugin_downloaded
|
val message = if (isEnabled) R.string.plugin_loaded else R.string.plugin_downloaded
|
||||||
PluginManager.downloadPlugin(
|
PluginManager.downloadPlugin(
|
||||||
activity,
|
activity,
|
||||||
metadata.url,
|
metadata.url,
|
||||||
metadata.fileHash,
|
metadata.fileHash,
|
||||||
metadata.internalName,
|
metadata.internalName,
|
||||||
repo,
|
repositoryData.url,
|
||||||
isEnabled
|
isEnabled
|
||||||
) to message
|
) to message
|
||||||
}
|
}
|
||||||
|
|
@ -201,20 +200,20 @@ class PluginsViewModel : ViewModel() {
|
||||||
updatePluginListPrivate(activity, repositoryUrls)
|
updatePluginListPrivate(activity, repositoryUrls)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updatePluginListPrivate(context: Context, repositoryUrls: List<String>) {
|
private suspend fun updatePluginListPrivate(context: Context, repositories: List<RepositoryData>) {
|
||||||
val isAdult = PreferenceManager.getDefaultSharedPreferences(context)
|
val isAdult = PreferenceManager.getDefaultSharedPreferences(context)
|
||||||
.getStringSet(context.getString(R.string.prefer_media_type_key), emptySet())
|
.getStringSet(context.getString(R.string.prefer_media_type_key), emptySet())
|
||||||
?.contains(TvType.NSFW.ordinal.toString()) == true
|
?.contains(TvType.NSFW.ordinal.toString()) == true
|
||||||
|
|
||||||
val plugins = repositoryUrls.flatMap { repositoryUrl ->
|
val plugins = repositories.flatMap { repositoryUrl ->
|
||||||
getPlugins(repositoryUrl)
|
getPlugins(repositoryUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
val list = plugins.filter {
|
val list = plugins.filter {
|
||||||
// Show all non-nsfw plugins or all if nsfw is enabled
|
// Show all non-nsfw plugins or all if nsfw is enabled
|
||||||
it.second.tvTypes?.contains(TvType.NSFW.name) != true || isAdult
|
it.plugin.tvTypes?.contains(TvType.NSFW.name) != true || isAdult
|
||||||
}.map { plugin ->
|
}.map { plugin ->
|
||||||
PluginViewData(plugin, isDownloaded(context, plugin.second.internalName, plugin.first))
|
PluginViewData(plugin, isDownloaded(context, plugin.plugin.internalName, plugin.repositoryData.url))
|
||||||
}
|
}
|
||||||
|
|
||||||
this.plugins = list
|
this.plugins = list
|
||||||
|
|
@ -227,8 +226,8 @@ class PluginsViewModel : ViewModel() {
|
||||||
private fun List<PluginViewData>.filterTvTypes(): List<PluginViewData> {
|
private fun List<PluginViewData>.filterTvTypes(): List<PluginViewData> {
|
||||||
if (tvTypes.isEmpty()) return this
|
if (tvTypes.isEmpty()) return this
|
||||||
return this.filter {
|
return this.filter {
|
||||||
(it.plugin.second.tvTypes?.any { type -> tvTypes.contains(type) } == true) ||
|
(it.pluginWrapper.plugin.tvTypes?.any { type -> tvTypes.contains(type) } == true) ||
|
||||||
(tvTypes.contains(TvType.Others.name) && (it.plugin.second.tvTypes
|
(tvTypes.contains(TvType.Others.name) && (it.pluginWrapper.plugin.tvTypes
|
||||||
?: emptyList()).isEmpty())
|
?: emptyList()).isEmpty())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -236,26 +235,26 @@ class PluginsViewModel : ViewModel() {
|
||||||
private fun List<PluginViewData>.filterLang(): List<PluginViewData> {
|
private fun List<PluginViewData>.filterLang(): List<PluginViewData> {
|
||||||
if (selectedLanguages.isEmpty()) return this // do not filter
|
if (selectedLanguages.isEmpty()) return this // do not filter
|
||||||
return this.filter {
|
return this.filter {
|
||||||
if (it.plugin.second.language == null) {
|
if (it.pluginWrapper.plugin.language == null) {
|
||||||
return@filter selectedLanguages.contains("none")
|
return@filter selectedLanguages.contains("none")
|
||||||
}
|
}
|
||||||
selectedLanguages.contains(it.plugin.second.language?.lowercase())
|
selectedLanguages.contains(it.pluginWrapper.plugin.language?.lowercase())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun List<PluginViewData>.sortByQuery(query: String?): List<PluginViewData> {
|
private fun List<PluginViewData>.sortByQuery(query: String?): List<PluginViewData> {
|
||||||
return if (query.isNullOrBlank()) {
|
return if (query.isNullOrBlank()) {
|
||||||
// Return list to base state if no query
|
// Return list to base state if no query
|
||||||
this.sortedBy { it.plugin.second.name }
|
this.sortedBy { it.pluginWrapper.plugin.name }
|
||||||
} else {
|
} else {
|
||||||
this.mapNotNull {
|
this.mapNotNull {
|
||||||
// Try matching name
|
// Try matching name
|
||||||
val score = Levenshtein.partialRatio(
|
val score = Levenshtein.partialRatio(
|
||||||
it.plugin.second.name.lowercase(),
|
it.pluginWrapper.plugin.name.lowercase(),
|
||||||
query.lowercase()
|
query.lowercase()
|
||||||
).takeIf { score -> score > 80 } ?:
|
).takeIf { score -> score > 80 } ?:
|
||||||
// Fallback to description, but limit characters to reduce lag
|
// Fallback to description, but limit characters to reduce lag
|
||||||
it.plugin.second.description?.lowercase()?.take(64)
|
it.pluginWrapper.plugin.description?.lowercase()?.take(64)
|
||||||
?.let { description ->
|
?.let { description ->
|
||||||
Levenshtein.partialRatio(
|
Levenshtein.partialRatio(
|
||||||
description,
|
description,
|
||||||
|
|
@ -282,11 +281,11 @@ class PluginsViewModel : ViewModel() {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updatePluginList(context: Context?, repositoryUrls: List<String>) =
|
fun updatePluginList(context: Context?, repositories: List<RepositoryData>) =
|
||||||
viewModelScope.launchSafe {
|
viewModelScope.launchSafe {
|
||||||
if (context == null) return@launchSafe
|
if (context == null) return@launchSafe
|
||||||
Log.i(TAG, "updatePluginList = $repositoryUrls")
|
Log.i(TAG, "updatePluginList = $repositories")
|
||||||
updatePluginListPrivate(context, repositoryUrls)
|
updatePluginListPrivate(context, repositories)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun search(query: String?) {
|
fun search(query: String?) {
|
||||||
|
|
@ -305,7 +304,7 @@ class PluginsViewModel : ViewModel() {
|
||||||
val downloadedPlugins = (PluginManager.getPluginsOnline() + PluginManager.getPluginsLocal())
|
val downloadedPlugins = (PluginManager.getPluginsOnline() + PluginManager.getPluginsLocal())
|
||||||
.distinctBy { it.filePath }
|
.distinctBy { it.filePath }
|
||||||
.map {
|
.map {
|
||||||
PluginViewData("" to it.toSitePlugin(), true)
|
PluginViewData(PluginWrapper.getLocalPluginWrapper(it.toSitePlugin()), true)
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins = downloadedPlugins
|
plugins = downloadedPlugins
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ class SetupFragmentExtensions : BaseFragment<FragmentSetupExtensionsBinding>(
|
||||||
|
|
||||||
if (hasRepos) {
|
if (hasRepos) {
|
||||||
binding?.repoRecyclerView?.adapter = RepoAdapter(true, {}, {
|
binding?.repoRecyclerView?.adapter = RepoAdapter(true, {}, {
|
||||||
PluginsViewModel.downloadAll(activity, it.url, null)
|
PluginsViewModel.downloadAll(activity, it, null)
|
||||||
}).apply { submitList(repositories.toList()) }
|
}).apply { submitList(repositories.toList()) }
|
||||||
}
|
}
|
||||||
// else {
|
// else {
|
||||||
|
|
|
||||||
|
|
@ -147,6 +147,7 @@ object AppContextUtils {
|
||||||
text.toSpanned()
|
text.toSpanned()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get channel ID by name */
|
/** Get channel ID by name */
|
||||||
@SuppressLint("RestrictedApi")
|
@SuppressLint("RestrictedApi")
|
||||||
private fun buildWatchNextProgramUri(
|
private fun buildWatchNextProgramUri(
|
||||||
|
|
@ -372,7 +373,8 @@ object AppContextUtils {
|
||||||
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) }.map { it.name })
|
hashSet.addAll(apis.filter { hasUniversal || activeLangs.contains(it.lang) }
|
||||||
|
.map { it.name })
|
||||||
return hashSet
|
return hashSet
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -463,7 +465,8 @@ object AppContextUtils {
|
||||||
} ?: default
|
} ?: default
|
||||||
val langs = this.getApiProviderLangSettings()
|
val langs = this.getApiProviderLangSettings()
|
||||||
val hasUniversal = langs.contains(AllLanguagesName)
|
val hasUniversal = langs.contains(AllLanguagesName)
|
||||||
val allApis = apis.filter { api -> (hasUniversal || langs.contains(api.lang)) && (api.hasMainPage || !hasHomePageIsRequired) }
|
val allApis =
|
||||||
|
apis.filter { api -> (hasUniversal || langs.contains(api.lang)) && (api.hasMainPage || !hasHomePageIsRequired) }
|
||||||
return if (currentPrefMedia.isEmpty()) {
|
return if (currentPrefMedia.isEmpty()) {
|
||||||
allApis
|
allApis
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -517,13 +520,12 @@ object AppContextUtils {
|
||||||
fun Activity.loadRepository(url: String) {
|
fun Activity.loadRepository(url: String) {
|
||||||
ioSafe {
|
ioSafe {
|
||||||
val repo = RepositoryManager.parseRepository(url) ?: return@ioSafe
|
val repo = RepositoryManager.parseRepository(url) ?: return@ioSafe
|
||||||
RepositoryManager.addRepository(
|
val data = RepositoryData(
|
||||||
RepositoryData(
|
repo.iconUrl ?: "",
|
||||||
repo.iconUrl ?: "",
|
repo.name,
|
||||||
repo.name,
|
url
|
||||||
url
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
|
RepositoryManager.addRepository(data)
|
||||||
main {
|
main {
|
||||||
showToast(
|
showToast(
|
||||||
getString(R.string.player_loaded_subtitles, repo.name),
|
getString(R.string.player_loaded_subtitles, repo.name),
|
||||||
|
|
@ -531,13 +533,12 @@ object AppContextUtils {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
afterRepositoryLoadedEvent.invoke(true)
|
afterRepositoryLoadedEvent.invoke(true)
|
||||||
addRepositoryDialog(repo.name, url)
|
addRepositoryDialog(data)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Activity.addRepositoryDialog(
|
fun Activity.addRepositoryDialog(
|
||||||
repositoryName: String,
|
repositoryData: RepositoryData
|
||||||
repositoryURL: String,
|
|
||||||
) {
|
) {
|
||||||
val repos = RepositoryManager.getRepositories()
|
val repos = RepositoryManager.getRepositories()
|
||||||
|
|
||||||
|
|
@ -547,9 +548,7 @@ object AppContextUtils {
|
||||||
navigate(
|
navigate(
|
||||||
R.id.global_to_navigation_settings_plugins,
|
R.id.global_to_navigation_settings_plugins,
|
||||||
PluginsFragment.newInstance(
|
PluginsFragment.newInstance(
|
||||||
repositoryName,
|
repositoryData,
|
||||||
repositoryURL,
|
|
||||||
false,
|
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -557,7 +556,7 @@ object AppContextUtils {
|
||||||
|
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
AlertDialog.Builder(this).apply {
|
AlertDialog.Builder(this).apply {
|
||||||
setTitle(repositoryName)
|
setTitle(repositoryData.name)
|
||||||
setMessage(R.string.download_all_plugins_from_repo)
|
setMessage(R.string.download_all_plugins_from_repo)
|
||||||
setPositiveButton(R.string.open_downloaded_repo) { _, _ ->
|
setPositiveButton(R.string.open_downloaded_repo) { _, _ ->
|
||||||
openAddedRepo()
|
openAddedRepo()
|
||||||
|
|
@ -684,7 +683,7 @@ object AppContextUtils {
|
||||||
"$seasonNameShort${rSeason}:$episodeNameShort${rEpisode}"
|
"$seasonNameShort${rSeason}:$episodeNameShort${rEpisode}"
|
||||||
} else if (rEpisode != null) {
|
} else if (rEpisode != null) {
|
||||||
"$episodeNameShort$rEpisode"
|
"$episodeNameShort$rEpisode"
|
||||||
}else null
|
} else null
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Activity?.loadCache() {
|
fun Activity?.loadCache() {
|
||||||
|
|
@ -707,7 +706,7 @@ object AppContextUtils {
|
||||||
fun loadResult(
|
fun loadResult(
|
||||||
url: String,
|
url: String,
|
||||||
apiName: String,
|
apiName: String,
|
||||||
name : String,
|
name: String,
|
||||||
startAction: Int = 0,
|
startAction: Int = 0,
|
||||||
startValue: Int = 0
|
startValue: Int = 0
|
||||||
) {
|
) {
|
||||||
|
|
@ -717,7 +716,7 @@ object AppContextUtils {
|
||||||
fun FragmentActivity.loadResult(
|
fun FragmentActivity.loadResult(
|
||||||
url: String,
|
url: String,
|
||||||
apiName: String,
|
apiName: String,
|
||||||
name : String,
|
name: String,
|
||||||
startAction: Int = 0,
|
startAction: Int = 0,
|
||||||
startValue: Int = 0
|
startValue: Int = 0
|
||||||
) {
|
) {
|
||||||
|
|
@ -843,7 +842,8 @@ object AppContextUtils {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.isUsingMobileData(): Boolean {
|
fun Context.isUsingMobileData(): Boolean {
|
||||||
val connectionManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
val connectionManager =
|
||||||
|
getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
val activeNetwork: Network? = connectionManager.activeNetwork
|
val activeNetwork: Network? = connectionManager.activeNetwork
|
||||||
val networkCapabilities = connectionManager.getNetworkCapabilities(activeNetwork)
|
val networkCapabilities = connectionManager.getNetworkCapabilities(activeNetwork)
|
||||||
|
|
|
||||||
|
|
@ -53,7 +53,7 @@
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="?attr/primaryBlackBackground"
|
android:background="?attr/primaryBlackBackground"
|
||||||
android:clipToPadding="false"
|
android:clipToPadding="false"
|
||||||
|
android:layout_marginBottom="80dp"
|
||||||
android:nextFocusLeft="@id/nav_rail_view"
|
android:nextFocusLeft="@id/nav_rail_view"
|
||||||
android:nextFocusUp="@id/tvtypes_chips"
|
android:nextFocusUp="@id/tvtypes_chips"
|
||||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
|
|
||||||
|
|
@ -80,8 +80,8 @@
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:layout_marginEnd="5dp"
|
android:layout_marginEnd="5dp"
|
||||||
android:textColor="?attr/grayTextColor"
|
android:textColor="?attr/grayTextColor"
|
||||||
tools:text="Votes: 10K"
|
|
||||||
android:visibility="gone"
|
android:visibility="gone"
|
||||||
|
tools:text="Votes: 10K"
|
||||||
tools:visibility="visible" />
|
tools:visibility="visible" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
|
@ -103,6 +103,14 @@
|
||||||
android:textColor="?attr/grayTextColor"
|
android:textColor="?attr/grayTextColor"
|
||||||
android:textSize="12sp"
|
android:textSize="12sp"
|
||||||
tools:text="https://github.com/..." />
|
tools:text="https://github.com/..." />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:layout_marginTop="5dp"
|
||||||
|
tools:visibility="visible"
|
||||||
|
android:visibility="gone"
|
||||||
|
android:id="@+id/repository_name_text"
|
||||||
|
style="@style/SmallBlackButton"
|
||||||
|
tools:text="Repository name" />
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
<ImageView
|
<ImageView
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue