mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
updated ui and logic for plugins
This commit is contained in:
parent
dd25523bea
commit
4e6bbf3908
11 changed files with 288 additions and 151 deletions
|
@ -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 =
|
||||
|
|
|
@ -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<Array<RepositoryData>>(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<out Plugin?>
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
||||
}
|
||||
}
|
|
@ -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<Array<RepositoryData>>(REPOSITORIES_KEY) ?: emptyArray()
|
||||
_repositories.postValue(urls)
|
||||
}
|
||||
|
||||
suspend fun getPlugins(repositoryUrl: String): List<Pair<String, SitePlugin>> {
|
||||
return RepositoryManager.getRepoPlugins(repositoryUrl) ?: emptyList()
|
||||
}
|
||||
}
|
|
@ -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<Pair<String, SitePlugin>>,
|
||||
val iconClickCallback: PluginAdapter.(repositoryUrl: String, plugin: SitePlugin, isDownloaded: Boolean) -> Unit
|
||||
val iconClickCallback: (Plugin) -> Unit
|
||||
) :
|
||||
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
private val plugins: MutableList<PluginViewData> = 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<PluginViewData>) {
|
||||
val diffResult = DiffUtil.calculateDiff(
|
||||
PluginDiffCallback(this.plugins, newList)
|
||||
)
|
||||
|
||||
plugins.clear()
|
||||
plugins.addAll(newList)
|
||||
|
||||
diffResult.dispatchUpdatesTo(this)
|
||||
}
|
||||
|
||||
/*
|
||||
private var storedPlugins: Array<PluginData> = reloadStoredPlugins()
|
||||
|
||||
fun reloadStoredPlugins(): Array<PluginData> {
|
||||
private fun reloadStoredPlugins(): Array<PluginData> {
|
||||
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<PluginViewData>,
|
||||
private val newList: List<PluginViewData>
|
||||
) :
|
||||
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]
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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<String, SitePlugin>
|
||||
|
||||
class PluginsViewModel : ViewModel() {
|
||||
private val _plugins = MutableLiveData<List<PluginViewData>>()
|
||||
val plugins: LiveData<List<PluginViewData>> = _plugins
|
||||
|
||||
private val repositoryCache: MutableMap<String, List<Plugin>> = mutableMapOf()
|
||||
|
||||
companion object {
|
||||
const val TAG = "PLG"
|
||||
}
|
||||
|
||||
private suspend fun getPlugins(
|
||||
repositoryUrl: String,
|
||||
canUseCache: Boolean = true
|
||||
): List<Plugin> {
|
||||
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<PluginData> {
|
||||
return PluginManager.getPluginsOnline()
|
||||
}
|
||||
|
||||
private fun getDownloads(): Set<String> {
|
||||
return getStoredPlugins().map { it.internalName }.toSet()
|
||||
}
|
||||
|
||||
private fun isDownloaded(plugin: Plugin, data: Set<String>? = 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)
|
||||
}
|
||||
}
|
|
@ -1,34 +1,29 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/extensions_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/extensions_root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
<include layout="@layout/standard_toolbar" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
tools:listitem="@layout/repository_item"
|
||||
android:id="@+id/repo_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager">
|
||||
|
||||
</androidx.recyclerview.widget.RecyclerView>
|
||||
|
||||
</LinearLayout>
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
|
||||
<com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
|
||||
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" />
|
||||
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" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
|
|
|
@ -9,22 +9,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:background="@android:color/transparent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/settings_toolbar"
|
||||
android:paddingTop="@dimen/navbar_height"
|
||||
tools:title="Overlord"
|
||||
android:background="?attr/primaryGrayBackground"
|
||||
app:navigationIconTint="?attr/iconColor"
|
||||
app:titleTextColor="?attr/textColor"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
<include layout="@layout/standard_toolbar" />
|
||||
|
||||
<!-- Required ViewGroup for PreferenceFragmentCompat -->
|
||||
<FrameLayout
|
||||
|
|
19
app/src/main/res/layout/standard_toolbar.xml
Normal file
19
app/src/main/res/layout/standard_toolbar.xml
Normal file
|
@ -0,0 +1,19 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.appbar.AppBarLayout android:background="@android:color/transparent"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<com.google.android.material.appbar.MaterialToolbar
|
||||
android:id="@+id/settings_toolbar"
|
||||
android:paddingTop="@dimen/navbar_height"
|
||||
tools:title="Overlord"
|
||||
android:background="?attr/primaryGrayBackground"
|
||||
app:navigationIconTint="?attr/iconColor"
|
||||
app:titleTextColor="?attr/textColor"
|
||||
app:layout_scrollFlags="scroll|enterAlways"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
Loading…
Reference in a new issue