Plugin setup screen and hopefully fix critical error

This commit is contained in:
Blatzar 2022-08-09 04:53:24 +02:00
parent 2c399e6916
commit 6e819e3b96
12 changed files with 333 additions and 128 deletions

View file

@ -10,6 +10,7 @@ import com.fasterxml.jackson.annotation.JsonProperty
import com.fasterxml.jackson.databind.DeserializationFeature
import com.fasterxml.jackson.databind.json.JsonMapper
import com.fasterxml.jackson.module.kotlin.KotlinModule
import com.lagradost.cloudstream3.mvvm.debugWarning
import com.lagradost.cloudstream3.mvvm.logError
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.aniListApi
import com.lagradost.cloudstream3.syncproviders.AccountManager.Companion.malApi
@ -69,7 +70,11 @@ object APIHolder {
fun getApiFromNameNull(apiName: String?): MainAPI? {
if (apiName == null) return null
initMap()
return apiMap?.get(apiName)?.let { apis.getOrNull(it) }
// Fuck it load from allProviders since they're dynamically loaded
// This is required right now because apiMap might be outdated
// TODO FIX when we switch to LoadPlugin()
debugWarning { "FIX LoadPlugin! getApiFromNameNull sucks right now 💀" }
return apiMap?.get(apiName)?.let { apis.getOrNull(it) } ?: allProviders.firstOrNull { it.name == apiName }
}
fun getApiFromUrlNull(url: String?): MainAPI? {

View file

@ -670,6 +670,11 @@ class MainActivity : AppCompatActivity(), ColorPickerDialogListener {
try {
if (getKey(HAS_DONE_SETUP_KEY, false) != true) {
navController.navigate(R.id.navigation_setup_language)
// If no plugins bring up extensions screen
} else if (PluginManager.getPluginsOnline().isEmpty()
&& PluginManager.getPluginsLocal().isEmpty()
) {
navController.navigate(R.id.navigation_setup_extensions)
}
} catch (e: Exception) {
logError(e)

View file

@ -18,6 +18,16 @@ import java.io.File
import java.io.InputStream
import java.io.OutputStream
/**
* Comes with the app, always available in the app, non removable.
* */
val PREBUILT_REPOSITORIES = arrayOf(
// TODO FIX
RepositoryData(
"Testing repository",
"https://raw.githubusercontent.com/recloudstream/cs-repos/master/test.json"
)
)
data class Repository(
@JsonProperty("name") val name: String,

View file

@ -42,7 +42,7 @@ class ExtensionsFragment : Fragment() {
setUpToolbar(R.string.extensions)
repo_recycler_view?.adapter = RepoAdapter(emptyArray(), {
repo_recycler_view?.adapter = RepoAdapter(emptyArray(), false, {
findNavController().navigate(
R.id.navigation_settings_extensions_to_navigation_settings_plugins,
Bundle().apply {

View file

@ -40,7 +40,7 @@ class PluginsFragment : Fragment() {
settings_toolbar?.setOnMenuItemClickListener { menuItem ->
when (menuItem?.itemId) {
R.id.download_all -> {
pluginViewModel.downloadAll(activity, url)
PluginsViewModel.downloadAll(activity, url, pluginViewModel)
}
else -> {}
}

View file

@ -29,75 +29,82 @@ class PluginsViewModel : ViewModel() {
companion object {
private val repositoryCache: MutableMap<String, List<Plugin>> = mutableMapOf()
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
}
private fun isDownloaded(plugin: Plugin, data: Set<String>? = null): Boolean {
return (data ?: getDownloads()).contains(plugin.second.internalName)
}
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 downloadAll(activity: Activity?, repositoryUrl: String) = ioSafe {
if (activity == null) return@ioSafe
val stored = getDownloads()
val plugins = getPlugins(repositoryUrl)
plugins.filter { plugin -> !isDownloaded(plugin, stored) }.also { list ->
main {
showToast(
activity,
if (list.isEmpty()) {
txt(
R.string.batch_download_nothing_to_download_format,
txt(R.string.plugin)
)
} else {
txt(R.string.batch_download_start_format, list.size, txt(if(list.size == 1) R.string.plugin_singular else R.string.plugin))
},
Toast.LENGTH_SHORT
)
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
}
}
}.apmap { (repo, metadata) ->
PluginManager.downloadAndLoadPlugin(
activity,
metadata.url,
metadata.name,
repo
)
}.main { list ->
if (list.any { it }) {
showToast(
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()
}
/**
* @param viewModel optional, updates the plugins livedata for that viewModel if included
* */
fun downloadAll(activity: Activity?, repositoryUrl: String, viewModel: PluginsViewModel?) = ioSafe {
if (activity == null) return@ioSafe
val stored = getDownloads()
val plugins = getPlugins(repositoryUrl)
plugins.filter { plugin -> !isDownloaded(plugin, stored) }.also { list ->
main {
showToast(
activity,
if (list.isEmpty()) {
txt(
R.string.batch_download_nothing_to_download_format,
txt(R.string.plugin)
)
} else {
txt(
R.string.batch_download_start_format,
list.size,
txt(if (list.size == 1) R.string.plugin_singular else R.string.plugin)
)
},
Toast.LENGTH_SHORT
)
}
}.apmap { (repo, metadata) ->
PluginManager.downloadAndLoadPlugin(
activity,
txt(
R.string.batch_download_finish_format,
list.count { it },
txt(if(list.size == 1) R.string.plugin_singular else R.string.plugin)
),
Toast.LENGTH_SHORT
metadata.url,
metadata.name,
repo
)
updatePluginListPrivate(repositoryUrl)
} else if (list.isNotEmpty()) {
showToast(activity, R.string.download_failed, Toast.LENGTH_SHORT)
}.main { list ->
if (list.any { it }) {
showToast(
activity,
txt(
R.string.batch_download_finish_format,
list.count { it },
txt(if (list.size == 1) R.string.plugin_singular else R.string.plugin)
),
Toast.LENGTH_SHORT
)
viewModel?.updatePluginListPrivate(repositoryUrl)
} else if (list.isNotEmpty()) {
showToast(activity, R.string.download_failed, Toast.LENGTH_SHORT)
}
}
}
}

View file

@ -5,13 +5,16 @@ import android.view.View
import android.view.ViewGroup
import androidx.recyclerview.widget.RecyclerView
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.plugins.PREBUILT_REPOSITORIES
import com.lagradost.cloudstream3.ui.settings.AccountClickCallback
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
val imageClickCallback: RepoAdapter.(RepositoryData) -> Unit,
/** In setup mode the trash icons will be replaced with download icons */
) :
RecyclerView.Adapter<RecyclerView.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
@ -37,7 +40,16 @@ class RepoAdapter(
fun bind(
repositoryData: RepositoryData
) {
itemView.action_button?.setImageResource(R.drawable.ic_baseline_delete_outline_24)
val isPrebuilt = PREBUILT_REPOSITORIES.contains(repositoryData)
val drawable =
if (isSetup) R.drawable.netflix_download else R.drawable.ic_baseline_delete_outline_24
// Only shows icon if on setup or if it isn't a prebuilt repo.
// No delete buttons on prebuilt repos.
if (!isPrebuilt || isSetup) {
itemView.action_button?.setImageResource(drawable)
}
itemView.action_button?.setOnClickListener {
imageClickCallback(repositoryData)
}

View file

@ -0,0 +1,68 @@
package com.lagradost.cloudstream3.ui.setup
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.plugins.PREBUILT_REPOSITORIES
import com.lagradost.cloudstream3.ui.settings.extensions.PluginsViewModel
import com.lagradost.cloudstream3.ui.settings.extensions.RepoAdapter
import com.lagradost.cloudstream3.utils.UIHelper.fixPaddingStatusbar
import kotlinx.android.synthetic.main.fragment_extensions.*
import kotlinx.android.synthetic.main.fragment_setup_media.*
class SetupFragmentExtensions : Fragment() {
companion object {
const val SETUP_EXTENSION_BUNDLE_IS_SETUP = "isSetup"
fun newInstance(isSetup: Boolean): Bundle {
return Bundle().apply {
putBoolean(SETUP_EXTENSION_BUNDLE_IS_SETUP, isSetup)
}
}
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_setup_extensions, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
context?.fixPaddingStatusbar(setup_root)
val isSetup = arguments?.getBoolean(SETUP_EXTENSION_BUNDLE_IS_SETUP) ?: false
with(context) {
if (this == null) return
repo_recycler_view?.adapter = RepoAdapter(PREBUILT_REPOSITORIES, true, {}, {
PluginsViewModel.downloadAll(activity, it.url, null)
})
if (!isSetup) {
next_btt.setText(R.string.setup_done)
}
prev_btt?.isVisible = isSetup
next_btt?.setOnClickListener {
// Continue setup
if (isSetup)
findNavController().navigate(R.id.action_navigation_setup_extensions_to_navigation_setup_provider_languages)
else
findNavController().popBackStack()
}
prev_btt?.setOnClickListener {
findNavController().popBackStack()
}
}
}
}

View file

@ -14,6 +14,7 @@ import com.lagradost.cloudstream3.BuildConfig
import com.lagradost.cloudstream3.CommonActivity
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.mvvm.normalSafeApiCall
import com.lagradost.cloudstream3.plugins.PluginManager
import com.lagradost.cloudstream3.ui.settings.appLanguages
import com.lagradost.cloudstream3.ui.settings.getCurrentLocale
import com.lagradost.cloudstream3.utils.SubtitleHelper
@ -23,6 +24,7 @@ import kotlinx.android.synthetic.main.fragment_setup_media.listview1
import kotlinx.android.synthetic.main.fragment_setup_media.next_btt
const val HAS_DONE_SETUP_KEY = "HAS_DONE_SETUP"
class SetupFragmentLanguage : Fragment() {
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
@ -76,7 +78,13 @@ class SetupFragmentLanguage : Fragment() {
}
next_btt?.setOnClickListener {
findNavController().navigate(R.id.action_navigation_setup_language_to_navigation_setup_provider_languages)
// If no plugins go to plugins page
val nextDestination = if (PluginManager.getPluginsOnline()
.isEmpty()
) R.id.action_navigation_global_to_navigation_setup_extensions
else R.id.action_navigation_setup_language_to_navigation_setup_provider_languages
findNavController().navigate(nextDestination, SetupFragmentExtensions.newInstance(true))
}
skip_btt?.setOnClickListener {

View file

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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"
android:id="@+id/setup_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:gravity="center"
android:text="@string/extensions"
android:textSize="18sp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:gravity="center"
android:text="@string/setup_extensions_subtext" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/repo_recycler_view"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:listitem="@layout/repository_item" />
<LinearLayout
android:id="@+id/apply_btt_holder"
android:layout_width="match_parent"
android:layout_height="60dp"
android:layout_gravity="bottom"
android:layout_marginTop="-60dp"
android:gravity="bottom|end"
android:orientation="horizontal">
<com.google.android.material.button.MaterialButton
android:id="@+id/next_btt"
style="@style/WhiteButton"
android:layout_width="wrap_content"
android:layout_gravity="center_vertical|end"
android:text="@string/next" />
<com.google.android.material.button.MaterialButton
android:id="@+id/prev_btt"
style="@style/BlackButton"
android:layout_width="wrap_content"
android:layout_gravity="center_vertical|end"
android:text="@string/previous" />
</LinearLayout>
</LinearLayout>

View file

@ -5,30 +5,30 @@
android:id="@+id/mobile_navigation"
app:startDestination="@+id/navigation_home">
<action
android:id="@+id/global_to_navigation_results_tv"
app:destination="@id/navigation_results_tv"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim">
android:id="@+id/global_to_navigation_results_tv"
app:destination="@id/navigation_results_tv"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim">
<argument
android:name="url"
app:argType="string" />
android:name="url"
app:argType="string" />
<argument
android:name="apiName"
app:argType="string" />
android:name="apiName"
app:argType="string" />
<argument
android:name="startAction"
android:defaultValue="0"
app:argType="integer" />
android:name="startAction"
android:defaultValue="0"
app:argType="integer" />
<argument
android:name="startValue"
android:defaultValue="0"
app:argType="integer" />
android:name="startValue"
android:defaultValue="0"
app:argType="integer" />
<argument
android:name="restart"
android:defaultValue="false"
app:argType="boolean" />
android:name="restart"
android:defaultValue="false"
app:argType="boolean" />
</action>
<action
android:id="@+id/global_to_navigation_results_phone"
@ -160,7 +160,8 @@
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim">
app:popExitAnim="@anim/exit_anim"
tools:layout="@layout/fragment_extensions">
<action
android:id="@+id/navigation_settings_extensions_to_navigation_settings_plugins"
app:destination="@id/navigation_settings_plugins"
@ -186,7 +187,8 @@
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim" />
app:popExitAnim="@anim/exit_anim"
tools:layout="@layout/fragment_plugins" />
<fragment
android:id="@+id/navigation_settings_lang"
@ -258,8 +260,7 @@
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim"
tools:layout="@layout/fragment_search">
</fragment>
tools:layout="@layout/fragment_search" />
<fragment
android:id="@+id/navigation_downloads"
@ -406,53 +407,53 @@
</fragment>
<fragment
android:id="@+id/navigation_results_phone"
android:name="com.lagradost.cloudstream3.ui.result.ResultFragmentPhone"
android:layout_height="match_parent"
android:id="@+id/navigation_results_phone"
android:name="com.lagradost.cloudstream3.ui.result.ResultFragmentPhone"
android:layout_height="match_parent"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim"
tools:layout="@layout/fragment_result_swipe">
<action
android:id="@+id/action_navigation_results_phone_to_navigation_quick_search"
app:destination="@id/navigation_quick_search"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim"
tools:layout="@layout/fragment_result_swipe">
app:popExitAnim="@anim/exit_anim" />
<action
android:id="@+id/action_navigation_results_phone_to_navigation_quick_search"
app:destination="@id/navigation_quick_search"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim" />
<action
android:id="@+id/action_navigation_results_phone_to_navigation_player"
app:destination="@id/navigation_player"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim" />
android:id="@+id/action_navigation_results_phone_to_navigation_player"
app:destination="@id/navigation_player"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim" />
</fragment>
<fragment
android:id="@+id/navigation_results_tv"
android:name="com.lagradost.cloudstream3.ui.result.ResultFragmentTv"
android:layout_height="match_parent"
android:id="@+id/navigation_results_tv"
android:name="com.lagradost.cloudstream3.ui.result.ResultFragmentTv"
android:layout_height="match_parent"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim"
tools:layout="@layout/fragment_result_swipe">
<action
android:id="@+id/action_navigation_results_tv_to_navigation_quick_search"
app:destination="@id/navigation_quick_search"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim"
tools:layout="@layout/fragment_result_swipe">
app:popExitAnim="@anim/exit_anim" />
<action
android:id="@+id/action_navigation_results_tv_to_navigation_quick_search"
app:destination="@id/navigation_quick_search"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim" />
<action
android:id="@+id/action_navigation_results_tv_to_navigation_player"
app:destination="@id/navigation_player"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim" />
android:id="@+id/action_navigation_results_tv_to_navigation_player"
app:destination="@id/navigation_player"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim" />
</fragment>
<!--<fragment
@ -508,6 +509,38 @@
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim" />
</fragment>
<action
android:id="@+id/action_navigation_global_to_navigation_setup_extensions"
app:destination="@id/navigation_setup_extensions"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim">
<argument
android:name="isSetup"
android:defaultValue="false"
app:argType="boolean" />
</action>
<fragment
android:id="@+id/navigation_setup_extensions"
android:name="com.lagradost.cloudstream3.ui.setup.SetupFragmentExtensions"
android:layout_height="match_parent"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim"
tools:layout="@layout/fragment_setup_extensions">
<action
android:id="@+id/action_navigation_setup_extensions_to_navigation_setup_provider_languages"
app:destination="@id/navigation_setup_provider_languages"
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim" />
</fragment>
<fragment
android:id="@+id/navigation_setup_provider_languages"
android:name="com.lagradost.cloudstream3.ui.setup.SetupFragmentProviderLanguage"

View file

@ -585,4 +585,5 @@
<string name="plugin">plugins</string>
<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>
</resources>