diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt index 39fcec462..de41cbc74 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -224,17 +224,17 @@ object PluginManager { // Helper class for updateAllOnlinePluginsAndLoadThem data class OnlinePluginData( val savedData: PluginData, - val onlineData: Pair, + val onlineData: PluginWrapper, ) { val isOutdated = - onlineData.second.version > savedData.version || onlineData.second.version == PLUGIN_VERSION_ALWAYS_UPDATE - val isDisabled = onlineData.second.status == PROVIDER_STATUS_DOWN + onlineData.plugin.version > savedData.version || onlineData.plugin.version == PLUGIN_VERSION_ALWAYS_UPDATE + val isDisabled = onlineData.plugin.status == PROVIDER_STATUS_DOWN fun validOnlineData(context: Context): Boolean { return getPluginPath( context, savedData.internalName, - onlineData.first + onlineData.repositoryData.url ).absolutePath == savedData.filePath } } @@ -282,19 +282,19 @@ object PluginManager { ?: emptyArray()) + PREBUILT_REPOSITORIES val onlinePlugins = urls.toList().amap { - getRepoPlugins(it.url)?.toList() ?: emptyList() - }.flatten().distinctBy { it.second.url } + getRepoPlugins(it) ?: emptyList() + }.flatten().distinctBy { it.plugin.url } // Iterates over all offline plugins, compares to remote repo and returns the plugins which are outdated val outdatedPlugins = getPluginsOnline().map { savedData -> onlinePlugins - .filter { onlineData -> savedData.internalName == onlineData.second.internalName } + .filter { onlineData -> savedData.internalName == onlineData.plugin.internalName } .map { onlineData -> OnlinePluginData(savedData, onlineData) }.filter { it.validOnlineData(activity) } - }.flatten().distinctBy { it.onlineData.second.url } + }.flatten().distinctBy { it.onlineData.plugin.url } debugPrint { "Outdated plugins: ${outdatedPlugins.filter { it.isOutdated }}" @@ -309,14 +309,14 @@ object PluginManager { } else if (pluginData.isOutdated) { downloadPlugin( activity, - pluginData.onlineData.second.url, - pluginData.onlineData.second.fileHash, + pluginData.onlineData.plugin.url, + pluginData.onlineData.plugin.fileHash, pluginData.savedData.internalName, File(pluginData.savedData.filePath), true ).let { success -> if (success) - updatedPlugins.add(pluginData.onlineData.second.name) + updatedPlugins.add(pluginData.onlineData.plugin.name) } } } @@ -359,15 +359,15 @@ object PluginManager { val urls = (getKey>(REPOSITORIES_KEY) ?: emptyArray()) + PREBUILT_REPOSITORIES val onlinePlugins = urls.toList().amap { - getRepoPlugins(it.url)?.toList() ?: emptyList() - }.flatten().distinctBy { it.second.url } + getRepoPlugins(it)?.toList() ?: emptyList() + }.flatten().distinctBy { it.plugin.url } val providerLang = activity.getApiProviderLangSettings() //Log.i(TAG, "providerLang => ${providerLang.toJson()}") // Iterate online repos and returns not downloaded plugins val notDownloadedPlugins = onlinePlugins.mapNotNull { onlineData -> - val sitePlugin = onlineData.second + val sitePlugin = onlineData.plugin val tvtypes = sitePlugin.tvTypes ?: listOf() //Don't include empty urls @@ -379,7 +379,7 @@ object PluginManager { } //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}") return@mapNotNull null } @@ -421,14 +421,14 @@ object PluginManager { notDownloadedPlugins.amap { pluginData -> downloadPlugin( activity, - pluginData.onlineData.second.url, - pluginData.onlineData.second.fileHash, + pluginData.onlineData.plugin.url, + pluginData.onlineData.plugin.fileHash, pluginData.savedData.internalName, - pluginData.onlineData.first, + pluginData.onlineData.repositoryData.url, !pluginData.isDisabled ).let { success -> if (success) - newDownloadPlugins.add(pluginData.onlineData.second.name) + newDownloadPlugins.add(pluginData.onlineData.plugin.name) } } @@ -835,16 +835,16 @@ object PluginManager { val urls = (getKey>(REPOSITORIES_KEY) ?: emptyArray()) + PREBUILT_REPOSITORIES val onlinePlugins = urls.toList().amap { - getRepoPlugins(it.url)?.toList() ?: emptyList() - }.flatten().distinctBy { it.second.url } + getRepoPlugins(it) ?: emptyList() + }.flatten().distinctBy { it.plugin.url } val allPlugins = getPluginsOnline().flatMap { savedData -> onlinePlugins - .filter { it.second.internalName == savedData.internalName } + .filter { it.plugin.internalName == savedData.internalName } .mapNotNull { onlineData -> OnlinePluginData(savedData, onlineData).takeIf { it.validOnlineData(activity) } } - }.distinctBy { it.onlineData.second.url } + }.distinctBy { it.onlineData.plugin.url } val updatedPlugins = mutableListOf() @@ -852,7 +852,7 @@ object PluginManager { if (pluginData.isDisabled) { Log.e( "PluginManager", - "Unloading disabled plugin: ${pluginData.onlineData.second.name}" + "Unloading disabled plugin: ${pluginData.onlineData.plugin.name}" ) unloadPlugin(pluginData.savedData.filePath) } else { @@ -861,14 +861,14 @@ object PluginManager { if (downloadPlugin( activity, - pluginData.onlineData.second.url, - pluginData.onlineData.second.fileHash, + pluginData.onlineData.plugin.url, + pluginData.onlineData.plugin.fileHash, pluginData.savedData.internalName, existingFile, true ) ) { - updatedPlugins.add(pluginData.onlineData.second.name) + updatedPlugins.add(pluginData.onlineData.plugin.name) } } }.also { diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt index c83c694f7..523798b6e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt @@ -75,6 +75,26 @@ data class SitePlugin( @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 { const val ONLINE_PLUGINS_FOLDER = "Extensions" val PREBUILT_REPOSITORIES: Array by lazy { @@ -135,7 +155,8 @@ object RepositoryManager { suspend fun parseRepository(url: String): Repository? { return safeAsync { // Take manifestVersion and such into account later - app.get(convertRawGitUrl(url), cacheTime = 5, cacheUnit = TimeUnit.MINUTES).parsedSafe() + app.get(convertRawGitUrl(url), cacheTime = 5, cacheUnit = TimeUnit.MINUTES) + .parsedSafe() } } @@ -153,13 +174,14 @@ object RepositoryManager { /** * Gets all plugins from repositories and pairs them with the repository url */ - suspend fun getRepoPlugins(repositoryUrl: String): List>? { - val repo = parseRepository(repositoryUrl) ?: return null - return repo.pluginLists.amap { url -> + suspend fun getRepoPlugins(repositoryData: RepositoryData): List? { + val repo = parseRepository(repositoryData.url) ?: return null + val list = repo.pluginLists.amap { url -> parsePlugins(url).map { - repositoryUrl to it + PluginWrapper(repo, repositoryData, it) } }.flatten() + return list } suspend fun downloadPluginToFile( diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt index 8decb0662..9b3f381d0 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt @@ -114,11 +114,7 @@ class ExtensionsFragment : BaseFragment( adapter = RepoAdapter(false, { findNavController().navigate( R.id.navigation_settings_extensions_to_navigation_settings_plugins, - PluginsFragment.newInstance( - it.name, - it.url, - false - ) + PluginsFragment.newInstance(it) ) }, { repo -> // Prompt user before deleting repo @@ -156,7 +152,7 @@ class ExtensionsFragment : BaseFragment( binding.repoRecyclerView.isVisible = repos.isNotEmpty() binding.blankRepoScreen.isVisible = repos.isEmpty() (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 -> @@ -185,10 +181,8 @@ class ExtensionsFragment : BaseFragment( binding.pluginStorageAppbar.setOnClickListener { findNavController().navigate( R.id.navigation_settings_extensions_to_navigation_settings_plugins, - PluginsFragment.newInstance( + PluginsFragment.newLocalInstance( getString(R.string.extensions), - "", - true ) ) } @@ -201,9 +195,8 @@ class ExtensionsFragment : BaseFragment( ) setRecycledViewPool(PluginAdapter.sharedPool) adapter = - PluginAdapter { - val urls = extensionViewModel.repositories.value?.map { repo -> repo.url } - ?: emptyList() + PluginAdapter(true) { + val urls = extensionViewModel.repositories.value?.toList() ?: emptyList() pluginViewModel.handlePluginAction(activity, urls, it, false) } } @@ -307,13 +300,12 @@ class ExtensionsFragment : BaseFragment( extensionViewModel.loadStats() extensionViewModel.loadRepositories() - val plugins = RepositoryManager.getRepoPlugins(url) + val plugins = RepositoryManager.getRepoPlugins(newRepo) if (plugins.isNullOrEmpty()) { showToast(R.string.no_plugins_found_error, Toast.LENGTH_LONG) } else { this@ExtensionsFragment.activity?.addRepositoryDialog( - fixedName, - url, + newRepo ) } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt index af76b85ef..d30962598 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsViewModel.kt @@ -55,16 +55,16 @@ class ExtensionsViewModel : ViewModel() { ?: emptyArray()) + PREBUILT_REPOSITORIES val onlinePlugins = urls.toList().amap { - RepositoryManager.getRepoPlugins(it.url)?.toList() ?: emptyList() - }.flatten().distinctBy { it.second.url } + RepositoryManager.getRepoPlugins(it)?.toList() ?: emptyList() + }.flatten().distinctBy { it.plugin.url } // Iterates over all offline plugins, compares to remote repo and returns the plugins which are outdated - val outdatedPlugins = getPluginsOnline().map { savedData -> - onlinePlugins.filter { onlineData -> savedData.internalName == onlineData.second.internalName } + val outdatedPlugins = getPluginsOnline().flatMap { savedData -> + onlinePlugins.filter { onlineData -> savedData.internalName == onlineData.plugin.internalName } .map { onlineData -> PluginManager.OnlinePluginData(savedData, onlineData) } - }.flatten().distinctBy { it.onlineData.second.url } + }.distinctBy { it.onlineData.plugin.url } val total = onlinePlugins.count() val disabled = outdatedPlugins.count { it.isDisabled } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt index d0f9ff565..2ff06cd6a 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginAdapter.kt @@ -15,6 +15,7 @@ import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.TvType import com.lagradost.cloudstream3.databinding.RepositoryItemBinding import com.lagradost.cloudstream3.plugins.PluginManager +import com.lagradost.cloudstream3.plugins.PluginWrapper import com.lagradost.cloudstream3.ui.BaseDiffCallback import com.lagradost.cloudstream3.ui.NoStateAdapter import com.lagradost.cloudstream3.ui.ViewHolderState @@ -34,7 +35,7 @@ import kotlin.math.log10 import kotlin.math.pow data class PluginViewData( - val plugin: Plugin, + val pluginWrapper: PluginWrapper, val isDownloaded: Boolean, ) @@ -44,9 +45,9 @@ class RepositoryViewHolderState(view: ViewBinding) : ViewHolderState(view) } class PluginAdapter( - val iconClickCallback: (Plugin) -> Unit + val showRepositoryNames: Boolean = false, val iconClickCallback: (PluginWrapper) -> Unit, ) : NoStateAdapter(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 { 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 itemView = holder.itemView - val metadata = item.plugin.second + val metadata = item.pluginWrapper.plugin val disabled = metadata.status == PROVIDER_STATUS_DOWN val name = metadata.name.removeSuffix("Provider") 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.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) R.drawable.ic_baseline_delete_outline_24 else R.drawable.netflix_download @@ -89,7 +98,7 @@ class PluginAdapter( binding.actionButton.setImageResource(drawableInt) binding.actionButton.setOnClickListener { - iconClickCallback.invoke(item.plugin) + iconClickCallback.invoke(item.pluginWrapper) } itemView.setOnClickListener { if (isLocal) return@setOnClickListener diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginDetailsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginDetailsFragment.kt index 0dcbece6c..06a95d527 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginDetailsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginDetailsFragment.kt @@ -54,7 +54,7 @@ class PluginDetailsFragment(val data: PluginViewData) : BaseBottomSheetDialogFra } override fun onBindingCreated(binding: FragmentPluginDetailsBinding) { - val metadata = data.plugin.second + val metadata = data.pluginWrapper.plugin binding.apply { pluginIcon.loadImage(metadata.iconUrl?.replace("%size%", "$iconSize") ?.replace("%exact_size%", "$iconSizeExact")) { @@ -135,7 +135,7 @@ class PluginDetailsFragment(val data: PluginViewData) : BaseBottomSheetDialogFra } private fun updateVoting(value: Int) { - val metadata = data.plugin.second + val metadata = data.pluginWrapper.plugin binding?.apply { pluginVotes.text = value.toString() if (metadata.hasVoted()) { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt index 2e8549a41..a7b24d3ae 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt @@ -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.setUpToolbar 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.SubtitleHelper.getNameNextToFlagEmoji import com.lagradost.cloudstream3.utils.UIHelper.toPx -const val PLUGINS_BUNDLE_NAME = "name" -const val PLUGINS_BUNDLE_URL = "url" +const val PLUGINS_BUNDLE_DATA = "data" const val PLUGINS_BUNDLE_LOCAL = "isLocal" class PluginsFragment : BaseFragment( @@ -61,24 +62,25 @@ class PluginsFragment : BaseFragment( } } - val name = arguments?.getString(PLUGINS_BUNDLE_NAME) - val url = arguments?.getString(PLUGINS_BUNDLE_URL) + val repositoryData = arguments?.getString(PLUGINS_BUNDLE_DATA)?.let { data -> + tryParseJson(data) + } val isLocal = arguments?.getBoolean(PLUGINS_BUNDLE_LOCAL) == true // download all extensions button val downloadAllButton = binding.settingsToolbar.menu?.findItem(R.id.download_all) - if (url == null || name == null) { + if (repositoryData == null) { dispatchBackPressed() return } setToolBarScrollFlags() - setUpToolbar(name) + setUpToolbar(repositoryData.name) binding.settingsToolbar.apply { setOnMenuItemClickListener { menuItem -> when (menuItem?.itemId) { R.id.download_all -> { - PluginsViewModel.downloadAll(activity, url, pluginViewModel) + PluginsViewModel.downloadAll(activity, repositoryData, pluginViewModel) } R.id.lang_filter -> { @@ -159,7 +161,7 @@ class PluginsFragment : BaseFragment( setRecycledViewPool(PluginAdapter.sharedPool) adapter = PluginAdapter { - pluginViewModel.handlePluginAction(activity, listOf(url), it, isLocal) + pluginViewModel.handlePluginAction(activity, listOf(repositoryData), it, isLocal) } } @@ -183,7 +185,7 @@ class PluginsFragment : BaseFragment( binding.tvtypesChipsScroll.root.isVisible = false } else { - pluginViewModel.updatePluginList(context, listOf(url)) + pluginViewModel.updatePluginList(context, listOf(repositoryData)) binding.tvtypesChipsScroll.root.isVisible = true // not needed for users but may be useful for devs downloadAllButton?.isVisible = BuildConfig.DEBUG @@ -204,21 +206,17 @@ class PluginsFragment : BaseFragment( } companion object { - fun newInstance(name: String, url: String, isLocal: Boolean): Bundle { + fun newInstance(repositoryData: RepositoryData): Bundle { return Bundle().apply { - putString(PLUGINS_BUNDLE_NAME, name) - putString(PLUGINS_BUNDLE_URL, url) - putBoolean(PLUGINS_BUNDLE_LOCAL, isLocal) + putString(PLUGINS_BUNDLE_DATA, repositoryData.toJson()) + putBoolean(PLUGINS_BUNDLE_LOCAL, false) + } + } + 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() -// } -// } - } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt index 059e24b12..c4f5ac396 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt @@ -17,8 +17,8 @@ import com.lagradost.cloudstream3.amap import com.lagradost.cloudstream3.mvvm.launchSafe import com.lagradost.cloudstream3.plugins.PluginManager import com.lagradost.cloudstream3.plugins.PluginManager.getPluginPath +import com.lagradost.cloudstream3.plugins.PluginWrapper import com.lagradost.cloudstream3.plugins.RepositoryManager -import com.lagradost.cloudstream3.plugins.SitePlugin import com.lagradost.cloudstream3.utils.txt import com.lagradost.cloudstream3.utils.Coroutines.ioSafe 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 java.io.File -// String => repository url -typealias Plugin = Pair /** * 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) { // Also set all the plugin languages for easier filtering value.map { pluginViewData -> - val language = pluginViewData.plugin.second.language?.lowercase() + val language = pluginViewData.pluginWrapper.plugin.language?.lowercase() pluginLanguages.add( when { language.isNullOrBlank() -> "none" @@ -62,7 +60,7 @@ class PluginsViewModel : ViewModel() { private var currentQuery: String? = null companion object { - private val repositoryCache: MutableMap> = mutableMapOf() + private val repositoryCache: MutableMap> = mutableMapOf() const val TAG = "PLG" private fun isDownloaded( @@ -74,32 +72,33 @@ class PluginsViewModel : ViewModel() { } private suspend fun getPlugins( - repositoryUrl: String, + repository: RepositoryData, canUseCache: Boolean = true - ): List { - Log.i(TAG, "getPlugins = $repositoryUrl") - if (canUseCache && repositoryCache.containsKey(repositoryUrl)) { - repositoryCache[repositoryUrl]?.let { + ): List { + Log.i(TAG, "getPlugins = $repository") + if (canUseCache && repositoryCache.containsKey(repository.url)) { + repositoryCache[repository.url]?.let { 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 * */ - fun downloadAll(activity: Activity?, repositoryUrl: String, viewModel: PluginsViewModel?) = + fun downloadAll(activity: Activity?, repository: RepositoryData, viewModel: PluginsViewModel?) = ioSafe { if (activity == null) return@ioSafe - val plugins = getPlugins(repositoryUrl) + val plugins = getPlugins(repository) - plugins.filter { plugin -> + plugins.filter { pluginWrapper -> !isDownloaded( activity, - plugin.second.internalName, - repositoryUrl + pluginWrapper.plugin.internalName, + repository.url ) }.also { list -> main { @@ -124,13 +123,13 @@ class PluginsViewModel : ViewModel() { Toast.LENGTH_SHORT ) } - }.amap { (repo, metadata) -> + }.amap { (_, repo, metadata) -> PluginManager.downloadPlugin( activity, metadata.url, metadata.fileHash, metadata.internalName, - repo, + repo.url, metadata.status != PROVIDER_STATUS_DOWN ) }.main { list -> @@ -143,7 +142,7 @@ class PluginsViewModel : ViewModel() { ), Toast.LENGTH_SHORT ) - viewModel?.updatePluginListPrivate(activity, listOf(repositoryUrl)) + viewModel?.updatePluginListPrivate(activity, listOf(repository)) } else if (list.isNotEmpty()) { showToast(R.string.download_failed, Toast.LENGTH_SHORT) } @@ -157,32 +156,32 @@ class PluginsViewModel : ViewModel() { * */ fun handlePluginAction( activity: Activity?, - repositoryUrls: List, - plugin: Plugin, + repositoryUrls: List, + pluginWrapper: PluginWrapper, isLocal: Boolean ) = ioSafe { - Log.i(TAG, "handlePluginAction = ${repositoryUrls}, $plugin, $isLocal") + Log.i(TAG, "handlePluginAction = ${repositoryUrls}, $pluginWrapper, $isLocal") 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, - plugin.second.internalName, - plugin.first + pluginWrapper.plugin.internalName, + pluginWrapper.repositoryData.url ) val (success, message) = if (file.exists()) { PluginManager.deletePlugin(file) to R.string.plugin_deleted } 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 PluginManager.downloadPlugin( activity, metadata.url, metadata.fileHash, metadata.internalName, - repo, + repositoryData.url, isEnabled ) to message } @@ -201,20 +200,20 @@ class PluginsViewModel : ViewModel() { updatePluginListPrivate(activity, repositoryUrls) } - private suspend fun updatePluginListPrivate(context: Context, repositoryUrls: List) { + private suspend fun updatePluginListPrivate(context: Context, repositories: List) { val isAdult = PreferenceManager.getDefaultSharedPreferences(context) .getStringSet(context.getString(R.string.prefer_media_type_key), emptySet()) ?.contains(TvType.NSFW.ordinal.toString()) == true - val plugins = repositoryUrls.flatMap { repositoryUrl -> + val plugins = repositories.flatMap { repositoryUrl -> getPlugins(repositoryUrl) } val list = plugins.filter { // 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 -> - PluginViewData(plugin, isDownloaded(context, plugin.second.internalName, plugin.first)) + PluginViewData(plugin, isDownloaded(context, plugin.plugin.internalName, plugin.repositoryData.url)) } this.plugins = list @@ -227,8 +226,8 @@ class PluginsViewModel : ViewModel() { private fun List.filterTvTypes(): List { if (tvTypes.isEmpty()) return this return this.filter { - (it.plugin.second.tvTypes?.any { type -> tvTypes.contains(type) } == true) || - (tvTypes.contains(TvType.Others.name) && (it.plugin.second.tvTypes + (it.pluginWrapper.plugin.tvTypes?.any { type -> tvTypes.contains(type) } == true) || + (tvTypes.contains(TvType.Others.name) && (it.pluginWrapper.plugin.tvTypes ?: emptyList()).isEmpty()) } } @@ -236,26 +235,26 @@ class PluginsViewModel : ViewModel() { private fun List.filterLang(): List { if (selectedLanguages.isEmpty()) return this // do not filter return this.filter { - if (it.plugin.second.language == null) { + if (it.pluginWrapper.plugin.language == null) { return@filter selectedLanguages.contains("none") } - selectedLanguages.contains(it.plugin.second.language?.lowercase()) + selectedLanguages.contains(it.pluginWrapper.plugin.language?.lowercase()) } } private fun List.sortByQuery(query: String?): List { return if (query.isNullOrBlank()) { // Return list to base state if no query - this.sortedBy { it.plugin.second.name } + this.sortedBy { it.pluginWrapper.plugin.name } } else { this.mapNotNull { // Try matching name val score = Levenshtein.partialRatio( - it.plugin.second.name.lowercase(), + it.pluginWrapper.plugin.name.lowercase(), query.lowercase() ).takeIf { score -> score > 80 } ?: // 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 -> Levenshtein.partialRatio( description, @@ -282,11 +281,11 @@ class PluginsViewModel : ViewModel() { ) } - fun updatePluginList(context: Context?, repositoryUrls: List) = + fun updatePluginList(context: Context?, repositories: List) = viewModelScope.launchSafe { if (context == null) return@launchSafe - Log.i(TAG, "updatePluginList = $repositoryUrls") - updatePluginListPrivate(context, repositoryUrls) + Log.i(TAG, "updatePluginList = $repositories") + updatePluginListPrivate(context, repositories) } fun search(query: String?) { @@ -305,7 +304,7 @@ class PluginsViewModel : ViewModel() { val downloadedPlugins = (PluginManager.getPluginsOnline() + PluginManager.getPluginsLocal()) .distinctBy { it.filePath } .map { - PluginViewData("" to it.toSitePlugin(), true) + PluginViewData(PluginWrapper.getLocalPluginWrapper(it.toSitePlugin()), true) } plugins = downloadedPlugins diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentExtensions.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentExtensions.kt index 8c2e8e344..544dd9335 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentExtensions.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentExtensions.kt @@ -56,7 +56,7 @@ class SetupFragmentExtensions : BaseFragment( if (hasRepos) { binding?.repoRecyclerView?.adapter = RepoAdapter(true, {}, { - PluginsViewModel.downloadAll(activity, it.url, null) + PluginsViewModel.downloadAll(activity, it, null) }).apply { submitList(repositories.toList()) } } // else { diff --git a/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt b/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt index 7278fcdd7..65519c0f1 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/utils/AppContextUtils.kt @@ -147,6 +147,7 @@ object AppContextUtils { text.toSpanned() } } + /** Get channel ID by name */ @SuppressLint("RestrictedApi") private fun buildWatchNextProgramUri( @@ -372,7 +373,8 @@ object AppContextUtils { val hashSet = HashSet() val activeLangs = getApiProviderLangSettings() 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 } @@ -463,7 +465,8 @@ object AppContextUtils { } ?: default val langs = this.getApiProviderLangSettings() 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()) { allApis } else { @@ -517,13 +520,12 @@ object AppContextUtils { fun Activity.loadRepository(url: String) { ioSafe { val repo = RepositoryManager.parseRepository(url) ?: return@ioSafe - RepositoryManager.addRepository( - RepositoryData( - repo.iconUrl ?: "", - repo.name, - url - ) + val data = RepositoryData( + repo.iconUrl ?: "", + repo.name, + url ) + RepositoryManager.addRepository(data) main { showToast( getString(R.string.player_loaded_subtitles, repo.name), @@ -531,13 +533,12 @@ object AppContextUtils { ) } afterRepositoryLoadedEvent.invoke(true) - addRepositoryDialog(repo.name, url) + addRepositoryDialog(data) } } fun Activity.addRepositoryDialog( - repositoryName: String, - repositoryURL: String, + repositoryData: RepositoryData ) { val repos = RepositoryManager.getRepositories() @@ -547,9 +548,7 @@ object AppContextUtils { navigate( R.id.global_to_navigation_settings_plugins, PluginsFragment.newInstance( - repositoryName, - repositoryURL, - false, + repositoryData, ) ) } @@ -557,7 +556,7 @@ object AppContextUtils { runOnUiThread { AlertDialog.Builder(this).apply { - setTitle(repositoryName) + setTitle(repositoryData.name) setMessage(R.string.download_all_plugins_from_repo) setPositiveButton(R.string.open_downloaded_repo) { _, _ -> openAddedRepo() @@ -684,7 +683,7 @@ object AppContextUtils { "$seasonNameShort${rSeason}:$episodeNameShort${rEpisode}" } else if (rEpisode != null) { "$episodeNameShort$rEpisode" - }else null + } else null } fun Activity?.loadCache() { @@ -707,7 +706,7 @@ object AppContextUtils { fun loadResult( url: String, apiName: String, - name : String, + name: String, startAction: Int = 0, startValue: Int = 0 ) { @@ -717,7 +716,7 @@ object AppContextUtils { fun FragmentActivity.loadResult( url: String, apiName: String, - name : String, + name: String, startAction: Int = 0, startValue: Int = 0 ) { @@ -843,7 +842,8 @@ object AppContextUtils { } 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) { val activeNetwork: Network? = connectionManager.activeNetwork val networkCapabilities = connectionManager.getNetworkCapabilities(activeNetwork) diff --git a/app/src/main/res/layout/fragment_extensions.xml b/app/src/main/res/layout/fragment_extensions.xml index 965b13569..ccb56df15 100644 --- a/app/src/main/res/layout/fragment_extensions.xml +++ b/app/src/main/res/layout/fragment_extensions.xml @@ -53,7 +53,7 @@ android:layout_height="match_parent" android:background="?attr/primaryBlackBackground" android:clipToPadding="false" - + android:layout_marginBottom="80dp" android:nextFocusLeft="@id/nav_rail_view" android:nextFocusUp="@id/tvtypes_chips" app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" diff --git a/app/src/main/res/layout/repository_item.xml b/app/src/main/res/layout/repository_item.xml index 801ec75e5..0550b5943 100644 --- a/app/src/main/res/layout/repository_item.xml +++ b/app/src/main/res/layout/repository_item.xml @@ -80,8 +80,8 @@ android:layout_gravity="center_vertical" android:layout_marginEnd="5dp" android:textColor="?attr/grayTextColor" - tools:text="Votes: 10K" android:visibility="gone" + tools:text="Votes: 10K" tools:visibility="visible" /> + +