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> =
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)
}

View File

@ -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()
}
}

View File

@ -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)
}
}

View File

@ -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]
}

View File

@ -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)

View File

@ -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>

View File

@ -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>