From 4e6bbf3908cd2987df4f9fdcf8a4c392937575fc Mon Sep 17 00:00:00 2001 From: reduplicated <110570621+reduplicated@users.noreply.github.com> Date: Sun, 7 Aug 2022 23:11:13 +0200 Subject: [PATCH] updated ui and logic for plugins --- .../lagradost/cloudstream3/MainActivity.kt | 8 +- .../cloudstream3/plugins/PluginManager.kt | 113 ++++++++++-------- .../ui/settings/SettingsFragment.kt | 21 +++- .../settings/extensions/ExtensionsFragment.kt | 7 +- .../extensions/ExtensionsViewModel.kt | 6 - .../ui/settings/extensions/PluginAdapter.kt | 65 +++++++--- .../ui/settings/extensions/PluginsFragment.kt | 46 ++----- .../settings/extensions/PluginsViewModel.kt | 102 ++++++++++++++++ .../main/res/layout/fragment_extensions.xml | 35 +++--- .../main/res/layout/settings_title_top.xml | 17 +-- app/src/main/res/layout/standard_toolbar.xml | 19 +++ 11 files changed, 288 insertions(+), 151 deletions(-) create mode 100644 app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt create mode 100644 app/src/main/res/layout/standard_toolbar.xml diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index 3e5b7d16..d1946d2f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -173,6 +173,8 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { R.id.navigation_settings_account, R.id.navigation_settings_lang, R.id.navigation_settings_general, + R.id.navigation_settings_extensions, + R.id.navigation_settings_plugins, ).contains(destination.id) val landscape = when (resources.configuration.orientation) { @@ -422,9 +424,9 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener { override fun onCreate(savedInstanceState: Bundle?) { app.initClient(this) - PluginManager.updateAllOnlinePlugins(applicationContext) - PluginManager.loadAllLocalPlugins(applicationContext) - PluginManager.loadAllOnlinePlugins(applicationContext) + PluginManager.updateAllOnlinePlugins(this) + PluginManager.loadAllLocalPlugins(this) + PluginManager.loadAllOnlinePlugins(this) // ioSafe { // val plugins = 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 2acc0eec..c52ca3c9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/PluginManager.kt @@ -8,6 +8,7 @@ import android.content.res.Resources import android.os.Environment import android.widget.Toast import android.app.Activity +import android.util.Log import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.AcraApplication.Companion.getKey import com.lagradost.cloudstream3.AcraApplication.Companion.removeKey @@ -52,33 +53,31 @@ object PluginManager { // Prevent multiple writes at once val lock = Mutex() + const val TAG = "PluginManager" + /** * Store data about the plugin for fetching later * */ - private fun setPluginData(data: PluginData) { - ioSafe { - lock.withLock { - if (data.isOnline) { - val plugins = getPluginsOnline() - setKey(PLUGINS_KEY, plugins + data) - } else { - val plugins = getPluginsLocal() - setKey(PLUGINS_KEY_LOCAL, plugins + data) - } + private suspend fun setPluginData(data: PluginData) { + lock.withLock { + if (data.isOnline) { + val plugins = getPluginsOnline() + setKey(PLUGINS_KEY, plugins + data) + } else { + val plugins = getPluginsLocal() + setKey(PLUGINS_KEY_LOCAL, plugins + data) } } } - private fun deletePluginData(data: PluginData) { - ioSafe { - lock.withLock { - if (data.isOnline) { - val plugins = getPluginsOnline().filter { it.url != data.url } - setKey(PLUGINS_KEY, plugins) - } else { - val plugins = getPluginsLocal().filter { it.filePath != data.filePath } - setKey(PLUGINS_KEY_LOCAL, plugins + data) - } + private suspend fun deletePluginData(data: PluginData) { + lock.withLock { + if (data.isOnline) { + val plugins = getPluginsOnline().filter { it.url != data.url } + setKey(PLUGINS_KEY, plugins) + } else { + val plugins = getPluginsLocal().filter { it.filePath != data.filePath } + setKey(PLUGINS_KEY_LOCAL, plugins + data) } } } @@ -105,11 +104,11 @@ object PluginManager { private var loadedLocalPlugins = false private val gson = Gson() - private fun maybeLoadPlugin(context: Context, file: File) { + private suspend fun maybeLoadPlugin(activity: Activity, file: File) { val name = file.name if (file.extension == "zip" || file.extension == "cs3") { loadPlugin( - context, + activity, file, PluginData(name, null, false, file.absolutePath, PLUGIN_VERSION_NOT_SET) ) @@ -119,7 +118,7 @@ object PluginManager { /** * Needs to be run before other plugin loading because plugin loading can not be overwritten **/ - fun updateAllOnlinePlugins(context: Context) { + fun updateAllOnlinePlugins(activity: Activity) { val urls = getKey>(REPOSITORIES_KEY) ?: emptyArray() val onlinePlugins = urls.toList().apmap { @@ -136,28 +135,28 @@ object PluginManager { } }.flatten() - println("Outdated plugins: $outdatedPlugins") + Log.i(TAG, "Outdated plugins: $outdatedPlugins") outdatedPlugins.apmap { downloadAndLoadPlugin( - context, + activity, it.second.second.url, it.first.internalName, it.second.first ) } - println("Plugin update done!") + Log.i(TAG, "Plugin update done!") } - fun loadAllOnlinePlugins(context: Context) { - File(context.filesDir, ONLINE_PLUGINS_FOLDER).listFiles()?.sortedBy { it.name } - ?.forEach { file -> - maybeLoadPlugin(context, file) + fun loadAllOnlinePlugins(activity: Activity) { + File(activity.filesDir, ONLINE_PLUGINS_FOLDER).listFiles()?.sortedBy { it.name } + ?.apmap { file -> + maybeLoadPlugin(activity, file) } } - fun loadAllLocalPlugins(context: Context) { + fun loadAllLocalPlugins(activity: Activity) { val dir = File(LOCAL_PLUGINS_PATH) removeKey(PLUGINS_KEY_LOCAL) @@ -172,8 +171,8 @@ object PluginManager { val sortedPlugins = dir.listFiles() // Always sort plugins alphabetically for reproducible results - sortedPlugins?.sortedBy { it.name }?.forEach { file -> - maybeLoadPlugin(context, file) + sortedPlugins?.sortedBy { it.name }?.apmap { file -> + maybeLoadPlugin(activity, file) } loadedLocalPlugins = true @@ -182,14 +181,14 @@ object PluginManager { /** * @return True if successful, false if not * */ - private fun loadPlugin(context: Context, file: File, data: PluginData): Boolean { + private suspend fun loadPlugin(activity: Activity, file: File, data: PluginData): Boolean { val fileName = file.nameWithoutExtension val filePath = file.absolutePath - println("Loading plugin: $data") + Log.i(TAG, "Loading plugin: $data") //logger.info("Loading plugin: " + fileName); return try { - val loader = PathClassLoader(filePath, context.classLoader) + val loader = PathClassLoader(filePath, activity.classLoader) var manifest: Plugin.Manifest loader.getResourceAsStream("manifest.json").use { stream -> if (stream == null) { @@ -211,14 +210,15 @@ object PluginManager { loader.loadClass(manifest.pluginClassName) as Class val pluginInstance: Plugin = pluginClass.newInstance() as Plugin - if (plugins.containsKey(filePath)) { - println("Plugin with name $name already exists") - return true - } // Sets with the proper version setPluginData(data.copy(version = version)) + if (plugins.containsKey(filePath)) { + Log.i(TAG, "Plugin with name $name already exists") + return true + } + pluginInstance.__filename = fileName if (pluginInstance.needsResources) { // based on https://stackoverflow.com/questions/7483568/dynamic-resource-loading-from-other-apk @@ -228,21 +228,21 @@ object PluginManager { addAssetPath.invoke(assets, file.absolutePath) pluginInstance.resources = Resources( assets, - context.resources.displayMetrics, - context.resources.configuration + activity.resources.displayMetrics, + activity.resources.configuration ) } plugins[filePath] = pluginInstance classLoaders[loader] = pluginInstance - pluginInstance.load(context) - println("Loaded plugin ${data.internalName} successfully") + pluginInstance.load(activity) + Log.i(TAG, "Loaded plugin ${data.internalName} successfully") true } catch (e: Throwable) { failedToLoad[file] = e e.printStackTrace() showToast( - context as Activity, - context.getString(R.string.plugin_load_fail).format(fileName), + activity, + activity.getString(R.string.plugin_load_fail).format(fileName), Toast.LENGTH_LONG ) false @@ -250,7 +250,7 @@ object PluginManager { } suspend fun downloadAndLoadPlugin( - context: Context, + activity: Activity, pluginUrl: String, internalName: String, repositoryUrl: String @@ -260,21 +260,28 @@ object PluginManager { true ) + "." + repositoryUrl.hashCode()) // Guaranteed unique val fileName = (sanitizeFilename(internalName, true) + "." + internalName.hashCode()) - println("Downloading plugin: $pluginUrl to $folderName/$fileName") + Log.i(TAG, "Downloading plugin: $pluginUrl to $folderName/$fileName") // The plugin file needs to be salted with the repository url hash as to allow multiple repositories with the same internal plugin names - val file = downloadPluginToFile(context, pluginUrl, fileName, folderName) + val file = downloadPluginToFile(activity, pluginUrl, fileName, folderName) return loadPlugin( - context, + activity, file ?: return false, PluginData(internalName, pluginUrl, true, file.absolutePath, PLUGIN_VERSION_NOT_SET) ) } - fun deletePlugin(context: Context, pluginUrl: String, name: String): Boolean { + suspend fun deletePlugin(pluginUrl: String): Boolean { val data = getPluginsOnline() .firstOrNull { it.url == pluginUrl } ?: return false - deletePluginData(data) - return File(data.filePath).delete() + return try { + if (File(data.filePath).delete()) { + deletePluginData(data) + return true + } + false + } catch (e: Exception) { + false + } } } \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt index b0e0c476..1e5fc87b 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/SettingsFragment.kt @@ -11,7 +11,6 @@ import android.view.ViewGroup import androidx.annotation.StringRes import androidx.core.view.isVisible import androidx.fragment.app.Fragment -import androidx.navigation.fragment.findNavController import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import androidx.preference.PreferenceManager @@ -24,6 +23,7 @@ import com.lagradost.cloudstream3.utils.UIHelper.navigate import com.lagradost.cloudstream3.utils.UIHelper.setImage import kotlinx.android.synthetic.main.main_settings.* import kotlinx.android.synthetic.main.settings_title_top.* +import kotlinx.android.synthetic.main.standard_toolbar.* import java.io.File class SettingsFragment : Fragment() { @@ -41,7 +41,19 @@ class SettingsFragment : Fragment() { } } - fun PreferenceFragmentCompat?.setUpToolbar(@StringRes title: Int) { + fun Fragment?.setUpToolbar(title: String) { + if (this == null) return + settings_toolbar?.apply { + setTitle(title) + setNavigationIcon(R.drawable.ic_baseline_arrow_back_24) + setNavigationOnClickListener { + activity?.onBackPressed() + } + } + context.fixPaddingStatusbar(settings_toolbar) + } + + fun Fragment?.setUpToolbar(@StringRes title: Int) { if (this == null) return settings_toolbar?.apply { setTitle(title) @@ -138,7 +150,10 @@ class SettingsFragment : Fragment() { Pair(settings_ui, R.id.action_navigation_settings_to_navigation_settings_ui), Pair(settings_lang, R.id.action_navigation_settings_to_navigation_settings_lang), Pair(settings_updates, R.id.action_navigation_settings_to_navigation_settings_updates), - Pair(settings_extensions, R.id.action_navigation_settings_to_navigation_settings_extensions), + Pair( + settings_extensions, + R.id.action_navigation_settings_to_navigation_settings_extensions + ), ).forEach { (view, navigationId) -> view?.apply { setOnClickListener { 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 b7f00e73..4136c782 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 @@ -15,6 +15,7 @@ import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.plugins.RepositoryManager +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.UIHelper.dismissSafe import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar @@ -37,7 +38,9 @@ class ExtensionsFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - context?.fixPaddingStatusbar(extensions_root) + //context?.fixPaddingStatusbar(extensions_root) + + setUpToolbar(R.string.extensions) repo_recycler_view?.adapter = RepoAdapter(emptyArray(), { findNavController().navigate( @@ -94,7 +97,5 @@ class ExtensionsFragment : Fragment() { extensionViewModel.loadRepositories() - - } } \ No newline at end of file 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 d25dc4ad..dedc4d93 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 @@ -5,8 +5,6 @@ import androidx.lifecycle.MutableLiveData import androidx.lifecycle.ViewModel import com.fasterxml.jackson.annotation.JsonProperty import com.lagradost.cloudstream3.AcraApplication.Companion.getKey -import com.lagradost.cloudstream3.plugins.RepositoryManager -import com.lagradost.cloudstream3.plugins.SitePlugin data class RepositoryData( @JsonProperty("name") val name: String, @@ -24,8 +22,4 @@ class ExtensionsViewModel : ViewModel() { val urls = getKey>(REPOSITORIES_KEY) ?: emptyArray() _repositories.postValue(urls) } - - suspend fun getPlugins(repositoryUrl: String): List> { - return RepositoryManager.getRepoPlugins(repositoryUrl) ?: emptyList() - } } \ No newline at end of file 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 a8746852..5c8895d0 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 @@ -4,18 +4,29 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.core.view.isVisible +import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.RecyclerView import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.plugins.PluginData import com.lagradost.cloudstream3.plugins.PluginManager import com.lagradost.cloudstream3.plugins.SitePlugin +import com.lagradost.cloudstream3.ui.result.ActorAdaptor +import com.lagradost.cloudstream3.ui.result.DiffCallback +import com.lagradost.cloudstream3.ui.result.UiText import kotlinx.android.synthetic.main.repository_item.view.* + +data class PluginViewData( + val plugin: Plugin, + val isDownloaded: Boolean, +) + class PluginAdapter( - var plugins: List>, - val iconClickCallback: PluginAdapter.(repositoryUrl: String, plugin: SitePlugin, isDownloaded: Boolean) -> Unit + val iconClickCallback: (Plugin) -> Unit ) : RecyclerView.Adapter() { + private val plugins: MutableList = mutableListOf() + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return PluginViewHolder( LayoutInflater.from(parent.context).inflate(R.layout.repository_item, parent, false) @@ -23,10 +34,9 @@ class PluginAdapter( } override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { - val (repositoryUrl, plugin) = plugins[position] when (holder) { is PluginViewHolder -> { - holder.bind(repositoryUrl, plugin) + holder.bind(plugins[position]) } } } @@ -35,34 +45,61 @@ class PluginAdapter( return plugins.size } + fun updateList(newList: List) { + val diffResult = DiffUtil.calculateDiff( + PluginDiffCallback(this.plugins, newList) + ) + + plugins.clear() + plugins.addAll(newList) + + diffResult.dispatchUpdatesTo(this) + } + + /* private var storedPlugins: Array = reloadStoredPlugins() - fun reloadStoredPlugins(): Array { + private fun reloadStoredPlugins(): Array { return PluginManager.getPluginsOnline().also { storedPlugins = it } - } + }*/ inner class PluginViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { fun bind( - repositoryUrl: String, - plugin: SitePlugin, + data: PluginViewData, ) { - val isDownloaded = storedPlugins.any { it.url == plugin.url } + val metadata = data.plugin.second - val drawableInt = if (isDownloaded) + val drawableInt = if (data.isDownloaded) R.drawable.ic_baseline_delete_outline_24 else R.drawable.netflix_download - itemView.nsfw_marker?.isVisible = plugin.isAdult == true + itemView.nsfw_marker?.isVisible = metadata.isAdult == true itemView.action_button?.setImageResource(drawableInt) itemView.action_button?.setOnClickListener { - iconClickCallback.invoke(this@PluginAdapter, repositoryUrl, plugin, isDownloaded) + iconClickCallback.invoke(data.plugin) } - itemView.main_text?.text = plugin.name - itemView.sub_text?.text = plugin.description + itemView.main_text?.text = metadata.name + itemView.sub_text?.text = metadata.description } } +} + +class PluginDiffCallback( + private val oldList: List, + private val newList: List +) : + DiffUtil.Callback() { + override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = + oldList[oldItemPosition].plugin.second.internalName == newList[newItemPosition].plugin.second.internalName && oldList[oldItemPosition].plugin.first == newList[newItemPosition].plugin.first + + override fun getOldListSize() = oldList.size + + override fun getNewListSize() = newList.size + + override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) = + oldList[oldItemPosition] == newList[newItemPosition] } \ No newline at end of file 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 0b8d9247..d57e7275 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 @@ -9,7 +9,9 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.plugins.PluginManager +import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar @@ -27,53 +29,31 @@ class PluginsFragment : Fragment() { return inflater.inflate(R.layout.fragment_extensions, container, false) } - private val extensionViewModel: ExtensionsViewModel by activityViewModels() + private val pluginViewModel: PluginsViewModel by activityViewModels() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - context?.fixPaddingStatusbar(extensions_root) val name = arguments?.getString(PLUGINS_BUNDLE_NAME) val url = arguments?.getString(PLUGINS_BUNDLE_URL) + if (url == null) { activity?.onBackPressed() return } - ioSafe { - val plugins = extensionViewModel.getPlugins(url) - main { - repo_recycler_view?.adapter = - PluginAdapter(plugins) { repositoryUrl, plugin, isDownloaded -> - ioSafe { - val (success, message) = if (isDownloaded) { - PluginManager.deletePlugin( - view.context, - plugin.url, - plugin.name - ) to R.string.plugin_deleted - } else { - PluginManager.downloadAndLoadPlugin( - view.context, - plugin.url, - plugin.name, - repositoryUrl - ) to R.string.plugin_loaded - } + setUpToolbar(name ?: "Unknown") - println("Success: $success") - if (success) { - main { - showToast(activity, message, Toast.LENGTH_SHORT) - this@PluginAdapter.reloadStoredPlugins() - // Dirty and needs a fix - repo_recycler_view?.adapter?.notifyDataSetChanged() - } - } - } - } + repo_recycler_view?.adapter = + PluginAdapter { + pluginViewModel.handlePluginAction(activity, url, it) } + + observe(pluginViewModel.plugins) { + (repo_recycler_view?.adapter as? PluginAdapter?)?.updateList(it) } + + pluginViewModel.updatePluginList(url) } companion object { 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 new file mode 100644 index 00000000..0912c3ba --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt @@ -0,0 +1,102 @@ +package com.lagradost.cloudstream3.ui.settings.extensions + +import android.app.Activity +import android.util.Log +import android.widget.Toast +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.lagradost.cloudstream3.CommonActivity.showToast +import com.lagradost.cloudstream3.R +import com.lagradost.cloudstream3.plugins.PluginData +import com.lagradost.cloudstream3.plugins.PluginManager +import com.lagradost.cloudstream3.plugins.RepositoryManager +import com.lagradost.cloudstream3.plugins.SitePlugin +import com.lagradost.cloudstream3.utils.Coroutines.ioSafe +import com.lagradost.cloudstream3.utils.Coroutines.runOnMainThread +import kotlinx.coroutines.launch + +typealias Plugin = Pair + +class PluginsViewModel : ViewModel() { + private val _plugins = MutableLiveData>() + val plugins: LiveData> = _plugins + + private val repositoryCache: MutableMap> = mutableMapOf() + + companion object { + const val TAG = "PLG" + } + + private suspend fun getPlugins( + repositoryUrl: String, + canUseCache: Boolean = true + ): List { + Log.i(TAG, "getPlugins = $repositoryUrl") + if (canUseCache && repositoryCache.containsKey(repositoryUrl)) { + repositoryCache[repositoryUrl]?.let { + return it + } + } + return RepositoryManager.getRepoPlugins(repositoryUrl) + ?.also { repositoryCache[repositoryUrl] = it } ?: emptyList() + } + + private fun getStoredPlugins(): Array { + return PluginManager.getPluginsOnline() + } + + private fun getDownloads(): Set { + return getStoredPlugins().map { it.internalName }.toSet() + } + + private fun isDownloaded(plugin: Plugin, data: Set? = null): Boolean { + return (data ?: getDownloads()).contains(plugin.second.internalName) + } + + fun handlePluginAction(activity: Activity?, repositoryUrl: String, plugin: Plugin) = ioSafe { + Log.i(TAG, "handlePluginAction = $repositoryUrl, $plugin") + + if (activity == null) return@ioSafe + val (repo, metadata) = plugin + + val (success, message) = if (isDownloaded(plugin)) { + PluginManager.deletePlugin( + metadata.url, + ) to R.string.plugin_deleted + } else { + PluginManager.downloadAndLoadPlugin( + activity, + metadata.url, + metadata.name, + repo + ) to R.string.plugin_loaded + } + + runOnMainThread { + if (success) + showToast(activity, message, Toast.LENGTH_SHORT) + else + showToast(activity, R.string.error, Toast.LENGTH_SHORT) + } + + if (success) + updatePluginListPrivate(repositoryUrl) + } + + private suspend fun updatePluginListPrivate(repositoryUrl: String) { + val stored = getDownloads() + val plugins = getPlugins(repositoryUrl) + val list = plugins.map { plugin -> + PluginViewData(plugin, isDownloaded(plugin, stored)) + } + + _plugins.postValue(list) + } + + fun updatePluginList(repositoryUrl: String) = viewModelScope.launch { + Log.i(TAG, "updatePluginList = $repositoryUrl") + updatePluginListPrivate(repositoryUrl) + } +} \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_extensions.xml b/app/src/main/res/layout/fragment_extensions.xml index 15bf3833..f3cccf91 100644 --- a/app/src/main/res/layout/fragment_extensions.xml +++ b/app/src/main/res/layout/fragment_extensions.xml @@ -1,34 +1,29 @@ - - - + + - - - - + app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /> + android:id="@+id/add_repo_button" + style="@style/ExtendedFloatingActionButton" + android:text="@string/add_repository" + android:textColor="?attr/textColor" + app:icon="@drawable/ic_baseline_add_24" + tools:ignore="ContentDescription" /> diff --git a/app/src/main/res/layout/settings_title_top.xml b/app/src/main/res/layout/settings_title_top.xml index f0dd44dc..1e3671a6 100644 --- a/app/src/main/res/layout/settings_title_top.xml +++ b/app/src/main/res/layout/settings_title_top.xml @@ -9,22 +9,7 @@ android:layout_width="match_parent" android:layout_height="match_parent"> - - - - + + + + + \ No newline at end of file