diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt index 329ed4ba..473dc5a9 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainAPI.kt @@ -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? { diff --git a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt index b830d5f3..b57bda4e 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/MainActivity.kt @@ -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) diff --git a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt index 72573274..d218accc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/plugins/RepositoryManager.kt @@ -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, diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt index 07dd5c81..ceba7e6f 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/ExtensionsFragment.kt @@ -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 { diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt index 5f3499f9..e02c58bc 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsFragment.kt @@ -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 -> {} } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt index 19f2d04a..25b9fb21 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/PluginsViewModel.kt @@ -29,75 +29,82 @@ class PluginsViewModel : ViewModel() { companion object { private val repositoryCache: MutableMap> = mutableMapOf() const val TAG = "PLG" - } - private suspend fun getPlugins( - repositoryUrl: String, - canUseCache: Boolean = true - ): List { - Log.i(TAG, "getPlugins = $repositoryUrl") - if (canUseCache && repositoryCache.containsKey(repositoryUrl)) { - repositoryCache[repositoryUrl]?.let { - return it - } + private fun isDownloaded(plugin: Plugin, data: Set? = null): Boolean { + return (data ?: getDownloads()).contains(plugin.second.internalName) } - return RepositoryManager.getRepoPlugins(repositoryUrl) - ?.also { repositoryCache[repositoryUrl] = it } ?: emptyList() - } - private fun getStoredPlugins(): Array { - return PluginManager.getPluginsOnline() - } - - private fun getDownloads(): Set { - return getStoredPlugins().map { it.internalName }.toSet() - } - - private fun isDownloaded(plugin: Plugin, data: Set? = 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 { + 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 { + return PluginManager.getPluginsOnline() + } + + private fun getDownloads(): Set { + 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) + } } } } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/RepoAdapter.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/RepoAdapter.kt index f71aec81..52a975b7 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/RepoAdapter.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/settings/extensions/RepoAdapter.kt @@ -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, + 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() { 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) } diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentExtensions.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentExtensions.kt new file mode 100644 index 00000000..324db565 --- /dev/null +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentExtensions.kt @@ -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() + } + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt index bc047a06..f982e6fa 100644 --- a/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt +++ b/app/src/main/java/com/lagradost/cloudstream3/ui/setup/SetupFragmentLanguage.kt @@ -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 { diff --git a/app/src/main/res/layout/fragment_setup_extensions.xml b/app/src/main/res/layout/fragment_setup_extensions.xml new file mode 100644 index 00000000..b51cc9df --- /dev/null +++ b/app/src/main/res/layout/fragment_setup_extensions.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml index 746d1096..ea27c59e 100644 --- a/app/src/main/res/navigation/mobile_navigation.xml +++ b/app/src/main/res/navigation/mobile_navigation.xml @@ -5,30 +5,30 @@ android:id="@+id/mobile_navigation" app:startDestination="@+id/navigation_home"> + 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:name="url" + app:argType="string" /> + android:name="apiName" + app:argType="string" /> + android:name="startAction" + android:defaultValue="0" + app:argType="integer" /> + android:name="startValue" + android:defaultValue="0" + app:argType="integer" /> + android:name="restart" + android:defaultValue="false" + app:argType="boolean" /> + app:popExitAnim="@anim/exit_anim" + tools:layout="@layout/fragment_extensions"> + app:popExitAnim="@anim/exit_anim" + tools:layout="@layout/fragment_plugins" /> - + tools:layout="@layout/fragment_search" /> + + 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" /> + + 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" />