extensions info + updated adapter

This commit is contained in:
reduplicated 2022-08-11 19:39:34 +02:00
parent a3d17065f4
commit 0dc8077296
7 changed files with 281 additions and 30 deletions

View File

@ -107,7 +107,7 @@ object PluginManager {
private val classLoaders: MutableMap<PathClassLoader, Plugin> = private val classLoaders: MutableMap<PathClassLoader, Plugin> =
HashMap<PathClassLoader, Plugin>() HashMap<PathClassLoader, Plugin>()
private var loadedLocalPlugins = false private var loadedLocalPlugins = false
private val gson = Gson() private val gson = Gson()
@ -126,7 +126,7 @@ object PluginManager {
// Helper class for updateAllOnlinePluginsAndLoadThem // Helper class for updateAllOnlinePluginsAndLoadThem
private data class OnlinePluginData( data class OnlinePluginData(
val savedData: PluginData, val savedData: PluginData,
val onlineData: Pair<String, SitePlugin>, val onlineData: Pair<String, SitePlugin>,
) { ) {
@ -135,6 +135,8 @@ object PluginManager {
val isDisabled = onlineData.second.status == PROVIDER_STATUS_DOWN 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 * Needs to be run before other plugin loading because plugin loading can not be overwritten
* 1. Gets all online data about the downloaded plugins * 1. Gets all online data about the downloaded plugins
@ -143,7 +145,8 @@ object PluginManager {
* 4. Else load the plugin normally * 4. Else load the plugin normally
**/ **/
fun updateAllOnlinePluginsAndLoadThem(activity: Activity) { 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 { val onlinePlugins = urls.toList().apmap {
getRepoPlugins(it.url)?.toList() ?: emptyList() getRepoPlugins(it.url)?.toList() ?: emptyList()
@ -156,6 +159,7 @@ object PluginManager {
OnlinePluginData(savedData, onlineData) OnlinePluginData(savedData, onlineData)
} }
}.flatten().distinctBy { it.onlineData.second.url } }.flatten().distinctBy { it.onlineData.second.url }
allCurrentOutDatedPlugins = outdatedPlugins.toSet()
Log.i(TAG, "Outdated plugins: ${outdatedPlugins.filter { it.isOutdated }}") Log.i(TAG, "Outdated plugins: ${outdatedPlugins.filter { it.isOutdated }}")
@ -308,7 +312,7 @@ object PluginManager {
APIHolder.allProviders.removeIf { provider: MainAPI -> provider.sourcePlugin == plugin.__filename } APIHolder.allProviders.removeIf { provider: MainAPI -> provider.sourcePlugin == plugin.__filename }
extractorApis.removeIf { provider: ExtractorApi -> 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) plugins.remove(absolutePath)
} }

View File

@ -7,16 +7,20 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.lagradost.cloudstream3.CommonActivity.showToast import com.lagradost.cloudstream3.CommonActivity.showToast
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.Some
import com.lagradost.cloudstream3.mvvm.observe import com.lagradost.cloudstream3.mvvm.observe
import com.lagradost.cloudstream3.plugins.PREBUILT_REPOSITORIES import com.lagradost.cloudstream3.plugins.PREBUILT_REPOSITORIES
import com.lagradost.cloudstream3.plugins.RepositoryManager 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.ui.settings.SettingsFragment.Companion.setUpToolbar
import com.lagradost.cloudstream3.utils.Coroutines.ioSafe import com.lagradost.cloudstream3.utils.Coroutines.ioSafe
import com.lagradost.cloudstream3.utils.Coroutines.main import com.lagradost.cloudstream3.utils.Coroutines.main
@ -35,6 +39,15 @@ class ExtensionsFragment : Fragment() {
return inflater.inflate(R.layout.fragment_extensions, container, false) 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() private val extensionViewModel: ExtensionsViewModel by activityViewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -43,7 +56,7 @@ class ExtensionsFragment : Fragment() {
setUpToolbar(R.string.extensions) setUpToolbar(R.string.extensions)
repo_recycler_view?.adapter = RepoAdapter(PREBUILT_REPOSITORIES, false, { repo_recycler_view?.adapter = RepoAdapter(false, {
findNavController().navigate( findNavController().navigate(
R.id.navigation_settings_extensions_to_navigation_settings_plugins, R.id.navigation_settings_extensions_to_navigation_settings_plugins,
Bundle().apply { Bundle().apply {
@ -60,6 +73,7 @@ class ExtensionsFragment : Fragment() {
DialogInterface.BUTTON_POSITIVE -> { DialogInterface.BUTTON_POSITIVE -> {
ioSafe { ioSafe {
RepositoryManager.removeRepository(view.context, repo) RepositoryManager.removeRepository(view.context, repo)
extensionViewModel.loadStats()
extensionViewModel.loadRepositories() extensionViewModel.loadRepositories()
} }
} }
@ -78,8 +92,32 @@ class ExtensionsFragment : Fragment() {
}) })
observe(extensionViewModel.repositories) { observe(extensionViewModel.repositories) {
(repo_recycler_view?.adapter as? RepoAdapter)?.repositories = it (repo_recycler_view?.adapter as? RepoAdapter)?.updateList(it)
(repo_recycler_view?.adapter as? RepoAdapter)?.notifyDataSetChanged() }
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 { add_repo_button?.setOnClickListener {
@ -118,6 +156,7 @@ class ExtensionsFragment : Fragment() {
val newRepo = RepositoryData(fixedName, url) val newRepo = RepositoryData(fixedName, url)
RepositoryManager.addRepository(newRepo) RepositoryManager.addRepository(newRepo)
extensionViewModel.loadStats()
extensionViewModel.loadRepositories() extensionViewModel.loadRepositories()
} }
dialog.dismissSafe(activity) dialog.dismissSafe(activity)
@ -127,7 +166,7 @@ class ExtensionsFragment : Fragment() {
} }
} }
extensionViewModel.loadStats()
extensionViewModel.loadRepositories() extensionViewModel.loadRepositories()
} }
} }

View File

@ -3,9 +3,20 @@ package com.lagradost.cloudstream3.ui.settings.extensions
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.annotation.JsonProperty
import com.lagradost.cloudstream3.AcraApplication.Companion.getKey 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.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( data class RepositoryData(
@JsonProperty("name") val name: String, @JsonProperty("name") val name: String,
@ -15,12 +26,65 @@ data class RepositoryData(
const val REPOSITORIES_KEY = "REPOSITORIES_KEY" const val REPOSITORIES_KEY = "REPOSITORIES_KEY"
class ExtensionsViewModel : ViewModel() { 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>>() private val _repositories = MutableLiveData<Array<RepositoryData>>()
val repositories: LiveData<Array<RepositoryData>> = _repositories 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() { fun loadRepositories() {
// Crashes weirdly with List<RepositoryData> val urls = repos()
val urls = (getKey<Array<RepositoryData>>(REPOSITORIES_KEY) ?: emptyArray()) + PREBUILT_REPOSITORIES
_repositories.postValue(urls) _repositories.postValue(urls)
} }
} }

View File

@ -3,19 +3,21 @@ package com.lagradost.cloudstream3.ui.settings.extensions
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.R import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.plugins.PREBUILT_REPOSITORIES import com.lagradost.cloudstream3.plugins.PREBUILT_REPOSITORIES
import kotlinx.android.synthetic.main.repository_item.view.* import kotlinx.android.synthetic.main.repository_item.view.*
class RepoAdapter( class RepoAdapter(
var repositories: Array<RepositoryData>,
val isSetup: Boolean, val isSetup: Boolean,
val clickCallback: RepoAdapter.(RepositoryData) -> Unit, val clickCallback: RepoAdapter.(RepositoryData) -> Unit,
val imageClickCallback: RepoAdapter.(RepositoryData) -> Unit, val imageClickCallback: RepoAdapter.(RepositoryData) -> Unit,
/** In setup mode the trash icons will be replaced with download icons */ /** In setup mode the trash icons will be replaced with download icons */
) : ) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() { RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private val repositories: MutableList<RepositoryData> = mutableListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return RepoViewHolder( return RepoViewHolder(
LayoutInflater.from(parent.context).inflate(R.layout.repository_item, parent, false) LayoutInflater.from(parent.context).inflate(R.layout.repository_item, parent, false)
@ -42,6 +44,17 @@ class RepoAdapter(
return repositories.size 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) : inner class RepoViewHolder(itemView: View) :
RecyclerView.ViewHolder(itemView) { RecyclerView.ViewHolder(itemView) {
fun bind( fun bind(
@ -68,4 +81,20 @@ class RepoAdapter(
itemView.sub_text?.text = repositoryData.url 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]
} }

View File

@ -41,9 +41,9 @@ class SetupFragmentExtensions : Fragment() {
with(context) { with(context) {
if (this == null) return if (this == null) return
repo_recycler_view?.adapter = RepoAdapter(PREBUILT_REPOSITORIES, true, {}, { repo_recycler_view?.adapter = RepoAdapter(true, {}, {
PluginsViewModel.downloadAll(activity, it.url, null) PluginsViewModel.downloadAll(activity, it.url, null)
}) }).apply { updateList(PREBUILT_REPOSITORIES) }
if (!isSetup) { if (!isSetup) {
next_btt.setText(R.string.setup_done) next_btt.setText(R.string.setup_done)

View File

@ -1,29 +1,141 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/extensions_root" android:id="@+id/extensions_root"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
<include layout="@layout/standard_toolbar" /> <include layout="@layout/standard_toolbar" />
<androidx.recyclerview.widget.RecyclerView <androidx.recyclerview.widget.RecyclerView
app:layout_behavior="@string/appbar_scrolling_view_behavior" android:id="@+id/repo_recycler_view"
tools:listitem="@layout/repository_item" android:layout_width="match_parent"
android:id="@+id/repo_recycler_view" 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_width="match_parent"
android:layout_height="match_parent" android:layout_height="wrap_content"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" /> 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 <com.google.android.material.floatingactionbutton.ExtendedFloatingActionButton
android:id="@+id/add_repo_button" android:id="@+id/add_repo_button"
style="@style/ExtendedFloatingActionButton" style="@style/ExtendedFloatingActionButton"
android:text="@string/add_repository" android:text="@string/add_repository"
android:textColor="?attr/textColor" android:textColor="?attr/textColor"
app:icon="@drawable/ic_baseline_add_24" android:translationY="-80dp"
tools:ignore="ContentDescription" /> app:icon="@drawable/ic_baseline_add_24"
tools:ignore="ContentDescription" />
</androidx.coordinatorlayout.widget.CoordinatorLayout> </androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -588,4 +588,7 @@
<string name="delete_repository_plugins">This will also delete all repository plugins</string> <string name="delete_repository_plugins">This will also delete all repository plugins</string>
<string name="delete_repository">Delete repository</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="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> </resources>