mirror of
https://github.com/recloudstream/cloudstream.git
synced 2024-08-15 01:53:11 +00:00
extensions info + updated adapter
This commit is contained in:
parent
a3d17065f4
commit
0dc8077296
7 changed files with 281 additions and 30 deletions
|
@ -107,7 +107,7 @@ object PluginManager {
|
|||
|
||||
private val classLoaders: MutableMap<PathClassLoader, Plugin> =
|
||||
HashMap<PathClassLoader, Plugin>()
|
||||
|
||||
|
||||
private var loadedLocalPlugins = false
|
||||
private val gson = Gson()
|
||||
|
||||
|
@ -126,7 +126,7 @@ object PluginManager {
|
|||
|
||||
|
||||
// Helper class for updateAllOnlinePluginsAndLoadThem
|
||||
private data class OnlinePluginData(
|
||||
data class OnlinePluginData(
|
||||
val savedData: PluginData,
|
||||
val onlineData: Pair<String, SitePlugin>,
|
||||
) {
|
||||
|
@ -135,6 +135,8 @@ object PluginManager {
|
|||
val isDisabled = onlineData.second.status == PROVIDER_STATUS_DOWN
|
||||
}
|
||||
|
||||
var allCurrentOutDatedPlugins: Set<OnlinePluginData> = emptySet()
|
||||
|
||||
/**
|
||||
* Needs to be run before other plugin loading because plugin loading can not be overwritten
|
||||
* 1. Gets all online data about the downloaded plugins
|
||||
|
@ -143,7 +145,8 @@ object PluginManager {
|
|||
* 4. Else load the plugin normally
|
||||
**/
|
||||
fun updateAllOnlinePluginsAndLoadThem(activity: Activity) {
|
||||
val urls = (getKey<Array<RepositoryData>>(REPOSITORIES_KEY) ?: emptyArray()) + PREBUILT_REPOSITORIES
|
||||
val urls = (getKey<Array<RepositoryData>>(REPOSITORIES_KEY)
|
||||
?: emptyArray()) + PREBUILT_REPOSITORIES
|
||||
|
||||
val onlinePlugins = urls.toList().apmap {
|
||||
getRepoPlugins(it.url)?.toList() ?: emptyList()
|
||||
|
@ -156,6 +159,7 @@ object PluginManager {
|
|||
OnlinePluginData(savedData, onlineData)
|
||||
}
|
||||
}.flatten().distinctBy { it.onlineData.second.url }
|
||||
allCurrentOutDatedPlugins = outdatedPlugins.toSet()
|
||||
|
||||
Log.i(TAG, "Outdated plugins: ${outdatedPlugins.filter { it.isOutdated }}")
|
||||
|
||||
|
@ -308,7 +312,7 @@ object PluginManager {
|
|||
APIHolder.allProviders.removeIf { provider: MainAPI -> provider.sourcePlugin == plugin.__filename }
|
||||
extractorApis.removeIf { provider: ExtractorApi -> provider.sourcePlugin == plugin.__filename }
|
||||
|
||||
classLoaders.values.removeIf { v -> v == plugin}
|
||||
classLoaders.values.removeIf { v -> v == plugin }
|
||||
|
||||
plugins.remove(absolutePath)
|
||||
}
|
||||
|
|
|
@ -7,16 +7,20 @@ import android.os.Bundle
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.activityViewModels
|
||||
import androidx.navigation.fragment.findNavController
|
||||
import com.lagradost.cloudstream3.CommonActivity.showToast
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.mvvm.Some
|
||||
import com.lagradost.cloudstream3.mvvm.observe
|
||||
import com.lagradost.cloudstream3.plugins.PREBUILT_REPOSITORIES
|
||||
import com.lagradost.cloudstream3.plugins.RepositoryManager
|
||||
import com.lagradost.cloudstream3.ui.result.setText
|
||||
import com.lagradost.cloudstream3.ui.settings.SettingsFragment.Companion.setUpToolbar
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
|
||||
import com.lagradost.cloudstream3.utils.Coroutines.main
|
||||
|
@ -35,6 +39,15 @@ class ExtensionsFragment : Fragment() {
|
|||
return inflater.inflate(R.layout.fragment_extensions, container, false)
|
||||
}
|
||||
|
||||
private fun View.setLayoutWidth(weight: Int) {
|
||||
val param = LinearLayout.LayoutParams(
|
||||
0,
|
||||
LinearLayout.LayoutParams.MATCH_PARENT,
|
||||
weight.toFloat()
|
||||
)
|
||||
this.layoutParams = param
|
||||
}
|
||||
|
||||
private val extensionViewModel: ExtensionsViewModel by activityViewModels()
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
|
@ -43,7 +56,7 @@ class ExtensionsFragment : Fragment() {
|
|||
|
||||
setUpToolbar(R.string.extensions)
|
||||
|
||||
repo_recycler_view?.adapter = RepoAdapter(PREBUILT_REPOSITORIES, false, {
|
||||
repo_recycler_view?.adapter = RepoAdapter(false, {
|
||||
findNavController().navigate(
|
||||
R.id.navigation_settings_extensions_to_navigation_settings_plugins,
|
||||
Bundle().apply {
|
||||
|
@ -60,6 +73,7 @@ class ExtensionsFragment : Fragment() {
|
|||
DialogInterface.BUTTON_POSITIVE -> {
|
||||
ioSafe {
|
||||
RepositoryManager.removeRepository(view.context, repo)
|
||||
extensionViewModel.loadStats()
|
||||
extensionViewModel.loadRepositories()
|
||||
}
|
||||
}
|
||||
|
@ -78,8 +92,32 @@ class ExtensionsFragment : Fragment() {
|
|||
})
|
||||
|
||||
observe(extensionViewModel.repositories) {
|
||||
(repo_recycler_view?.adapter as? RepoAdapter)?.repositories = it
|
||||
(repo_recycler_view?.adapter as? RepoAdapter)?.notifyDataSetChanged()
|
||||
(repo_recycler_view?.adapter as? RepoAdapter)?.updateList(it)
|
||||
}
|
||||
|
||||
observe(extensionViewModel.pluginStats) {
|
||||
when (it) {
|
||||
is Some.Success -> {
|
||||
val value = it.value
|
||||
|
||||
plugin_storage_appbar?.isVisible = true
|
||||
if (value.total == 0) {
|
||||
plugin_download?.setLayoutWidth(1)
|
||||
plugin_disabled?.setLayoutWidth(0)
|
||||
plugin_not_downloaded?.setLayoutWidth(0)
|
||||
} else {
|
||||
plugin_download?.setLayoutWidth(value.downloaded)
|
||||
plugin_disabled?.setLayoutWidth(value.disabled)
|
||||
plugin_not_downloaded?.setLayoutWidth(value.notDownloaded)
|
||||
}
|
||||
plugin_not_downloaded_txt.setText(value.notDownloadedText)
|
||||
plugin_disabled_txt.setText(value.disabledText)
|
||||
plugin_download_txt.setText(value.downloadedText)
|
||||
}
|
||||
is Some.None -> {
|
||||
plugin_storage_appbar?.isVisible = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
add_repo_button?.setOnClickListener {
|
||||
|
@ -118,6 +156,7 @@ class ExtensionsFragment : Fragment() {
|
|||
|
||||
val newRepo = RepositoryData(fixedName, url)
|
||||
RepositoryManager.addRepository(newRepo)
|
||||
extensionViewModel.loadStats()
|
||||
extensionViewModel.loadRepositories()
|
||||
}
|
||||
dialog.dismissSafe(activity)
|
||||
|
@ -127,7 +166,7 @@ class ExtensionsFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
extensionViewModel.loadStats()
|
||||
extensionViewModel.loadRepositories()
|
||||
}
|
||||
}
|
|
@ -3,9 +3,20 @@ package com.lagradost.cloudstream3.ui.settings.extensions
|
|||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.fasterxml.jackson.annotation.JsonProperty
|
||||
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.apmap
|
||||
import com.lagradost.cloudstream3.mvvm.Some
|
||||
import com.lagradost.cloudstream3.mvvm.debugAssert
|
||||
import com.lagradost.cloudstream3.plugins.PREBUILT_REPOSITORIES
|
||||
import com.lagradost.cloudstream3.plugins.PluginManager
|
||||
import com.lagradost.cloudstream3.plugins.PluginManager.getPluginsOnline
|
||||
import com.lagradost.cloudstream3.plugins.RepositoryManager
|
||||
import com.lagradost.cloudstream3.ui.result.UiText
|
||||
import com.lagradost.cloudstream3.ui.result.txt
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
data class RepositoryData(
|
||||
@JsonProperty("name") val name: String,
|
||||
|
@ -15,12 +26,65 @@ data class RepositoryData(
|
|||
const val REPOSITORIES_KEY = "REPOSITORIES_KEY"
|
||||
|
||||
class ExtensionsViewModel : ViewModel() {
|
||||
data class PluginStats(
|
||||
val total: Int,
|
||||
|
||||
val downloaded: Int,
|
||||
val disabled: Int,
|
||||
val notDownloaded: Int,
|
||||
|
||||
val downloadedText: UiText,
|
||||
val disabledText: UiText,
|
||||
val notDownloadedText: UiText,
|
||||
)
|
||||
|
||||
private val _repositories = MutableLiveData<Array<RepositoryData>>()
|
||||
val repositories: LiveData<Array<RepositoryData>> = _repositories
|
||||
|
||||
private val _pluginStats: MutableLiveData<Some<PluginStats>> = MutableLiveData(Some.None)
|
||||
val pluginStats: LiveData<Some<PluginStats>> = _pluginStats
|
||||
|
||||
//TODO CACHE GET REQUESTS
|
||||
fun loadStats() = viewModelScope.launch {
|
||||
val urls = (getKey<Array<RepositoryData>>(REPOSITORIES_KEY)
|
||||
?: emptyArray()) + PREBUILT_REPOSITORIES
|
||||
|
||||
val onlinePlugins = urls.toList().apmap {
|
||||
RepositoryManager.getRepoPlugins(it.url)?.toList() ?: emptyList()
|
||||
}.flatten().distinctBy { it.second.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 }
|
||||
.map { onlineData ->
|
||||
PluginManager.OnlinePluginData(savedData, onlineData)
|
||||
}
|
||||
}.flatten().distinctBy { it.onlineData.second.url }
|
||||
val total = onlinePlugins.count()
|
||||
val disabled = outdatedPlugins.count { it.isDisabled }
|
||||
val downloadedTotal = outdatedPlugins.count()
|
||||
val downloaded = downloadedTotal - disabled
|
||||
val notDownloaded = total - downloadedTotal
|
||||
val stats = PluginStats(
|
||||
total,
|
||||
downloaded,
|
||||
disabled,
|
||||
notDownloaded,
|
||||
txt(R.string.plugins_downloaded, downloaded),
|
||||
txt(R.string.plugins_disabled, disabled),
|
||||
txt(R.string.plugins_not_downloaded, notDownloaded)
|
||||
)
|
||||
debugAssert({ stats.downloaded + stats.notDownloaded + stats.disabled != stats.total }) {
|
||||
"downloaded(${stats.downloaded}) + notDownloaded(${stats.notDownloaded}) + disabled(${stats.disabled}) != total(${stats.total})"
|
||||
}
|
||||
_pluginStats.postValue(Some.Success(stats))
|
||||
}
|
||||
|
||||
private fun repos() = (getKey<Array<RepositoryData>>(REPOSITORIES_KEY)
|
||||
?: emptyArray()) + PREBUILT_REPOSITORIES
|
||||
|
||||
fun loadRepositories() {
|
||||
// Crashes weirdly with List<RepositoryData>
|
||||
val urls = (getKey<Array<RepositoryData>>(REPOSITORIES_KEY) ?: emptyArray()) + PREBUILT_REPOSITORIES
|
||||
val urls = repos()
|
||||
_repositories.postValue(urls)
|
||||
}
|
||||
}
|
|
@ -3,19 +3,21 @@ package com.lagradost.cloudstream3.ui.settings.extensions
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.lagradost.cloudstream3.R
|
||||
import com.lagradost.cloudstream3.plugins.PREBUILT_REPOSITORIES
|
||||
import kotlinx.android.synthetic.main.repository_item.view.*
|
||||
|
||||
class RepoAdapter(
|
||||
var repositories: Array<RepositoryData>,
|
||||
val isSetup: Boolean,
|
||||
val clickCallback: RepoAdapter.(RepositoryData) -> Unit,
|
||||
val imageClickCallback: RepoAdapter.(RepositoryData) -> Unit,
|
||||
/** In setup mode the trash icons will be replaced with download icons */
|
||||
) :
|
||||
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
private val repositories: MutableList<RepositoryData> = mutableListOf()
|
||||
|
||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
|
||||
return RepoViewHolder(
|
||||
LayoutInflater.from(parent.context).inflate(R.layout.repository_item, parent, false)
|
||||
|
@ -42,6 +44,17 @@ class RepoAdapter(
|
|||
return repositories.size
|
||||
}
|
||||
|
||||
fun updateList(newList: Array<RepositoryData>) {
|
||||
val diffResult = DiffUtil.calculateDiff(
|
||||
RepoDiffCallback(this.repositories, newList)
|
||||
)
|
||||
|
||||
repositories.clear()
|
||||
repositories.addAll(newList)
|
||||
|
||||
diffResult.dispatchUpdatesTo(this)
|
||||
}
|
||||
|
||||
inner class RepoViewHolder(itemView: View) :
|
||||
RecyclerView.ViewHolder(itemView) {
|
||||
fun bind(
|
||||
|
@ -68,4 +81,20 @@ class RepoAdapter(
|
|||
itemView.sub_text?.text = repositoryData.url
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RepoDiffCallback(
|
||||
private val oldList: List<RepositoryData>,
|
||||
private val newList: Array<RepositoryData>
|
||||
) :
|
||||
DiffUtil.Callback() {
|
||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) =
|
||||
oldList[oldItemPosition].url == newList[newItemPosition].url
|
||||
|
||||
override fun getOldListSize() = oldList.size
|
||||
|
||||
override fun getNewListSize() = newList.size
|
||||
|
||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int) =
|
||||
oldList[oldItemPosition] == newList[newItemPosition]
|
||||
}
|
|
@ -41,9 +41,9 @@ class SetupFragmentExtensions : Fragment() {
|
|||
with(context) {
|
||||
if (this == null) return
|
||||
|
||||
repo_recycler_view?.adapter = RepoAdapter(PREBUILT_REPOSITORIES, true, {}, {
|
||||
repo_recycler_view?.adapter = RepoAdapter(true, {}, {
|
||||
PluginsViewModel.downloadAll(activity, it.url, null)
|
||||
})
|
||||
}).apply { updateList(PREBUILT_REPOSITORIES) }
|
||||
|
||||
if (!isSetup) {
|
||||
next_btt.setText(R.string.setup_done)
|
||||
|
|
|
@ -1,29 +1,141 @@
|
|||
<?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">
|
||||
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">
|
||||
|
||||
<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:id="@+id/repo_recycler_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginBottom="80dp"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
|
||||
app:layout_constraintBottom_toTopOf="@id/download_storage_appbar"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:listitem="@layout/repository_item" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/plugin_storage_appbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="80dp"
|
||||
android:layout_gravity="bottom"
|
||||
android:background="?attr/primaryGrayBackground"
|
||||
android:orientation="vertical"
|
||||
android:padding="10dp"
|
||||
android:visibility="visible"
|
||||
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent">
|
||||
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:text="@string/extensions"
|
||||
android:textColor="?attr/textColor" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="12dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<View
|
||||
android:id="@+id/plugin_download"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
tools:layout_weight="0.5"
|
||||
android:background="?attr/white" />
|
||||
|
||||
<View
|
||||
android:id="@+id/plugin_disabled"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
tools:layout_weight="0.10"
|
||||
android:background="?attr/colorPrimary" />
|
||||
|
||||
<View
|
||||
android:id="@+id/plugin_not_downloaded"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
tools:layout_weight="0.10"
|
||||
android:background="?attr/grayTextColor" />
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<View
|
||||
android:layout_width="10dp"
|
||||
android:layout_height="10dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_marginTop="5dp"
|
||||
android:layout_marginEnd="5dp"
|
||||
android:layout_marginBottom="5dp"
|
||||
android:background="?attr/white" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/plugin_download_txt"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:textColor="?attr/textColor"
|
||||
android:textSize="12sp"
|
||||
tools:text="Downloaded: 7" />
|
||||
|
||||
<View
|
||||
android:layout_width="10dp"
|
||||
android:layout_height="10dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="5dp"
|
||||
android:background="?attr/colorPrimary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/plugin_disabled_txt"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:textColor="?attr/textColor"
|
||||
android:textSize="12sp"
|
||||
tools:text="Disabled: 3" />
|
||||
|
||||
<View
|
||||
android:layout_width="10dp"
|
||||
android:layout_height="10dp"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:layout_margin="5dp"
|
||||
android:background="?attr/grayTextColor" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/plugin_not_downloaded_txt"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:textColor="?attr/textColor"
|
||||
android:textSize="12sp"
|
||||
tools:text="Not downloaded 3" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<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"
|
||||
android:translationY="-80dp"
|
||||
app:icon="@drawable/ic_baseline_add_24"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
|
||||
|
|
|
@ -588,4 +588,7 @@
|
|||
<string name="delete_repository_plugins">This will also delete all repository plugins</string>
|
||||
<string name="delete_repository">Delete repository</string>
|
||||
<string name="setup_extensions_subtext">Download the list of sites you want to use</string>
|
||||
<string name="plugins_downloaded" formatted="true">Downloaded: %d</string>
|
||||
<string name="plugins_disabled" formatted="true">Disabled: %d</string>
|
||||
<string name="plugins_not_downloaded" formatted="true">Not downloaded: %d</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue