diff --git a/app/src/main/java/com/futo/platformplayer/UIDialogs.kt b/app/src/main/java/com/futo/platformplayer/UIDialogs.kt index ec6664622a5f52068d0f95be15d892a5137cffc2..c9169091ef41388f7dda2f950a136fcbe84422c7 100644 --- a/app/src/main/java/com/futo/platformplayer/UIDialogs.kt +++ b/app/src/main/java/com/futo/platformplayer/UIDialogs.kt @@ -18,6 +18,7 @@ import android.widget.Toast import androidx.core.content.ContextCompat import com.futo.platformplayer.activities.MainActivity import com.futo.platformplayer.api.media.models.comments.IPlatformComment +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig import com.futo.platformplayer.casting.StateCasting import com.futo.platformplayer.dialogs.AutoUpdateDialog import com.futo.platformplayer.dialogs.AutomaticBackupDialog @@ -31,12 +32,17 @@ import com.futo.platformplayer.dialogs.ConnectedCastingDialog import com.futo.platformplayer.dialogs.ImportDialog import com.futo.platformplayer.dialogs.ImportOptionsDialog import com.futo.platformplayer.dialogs.MigrateDialog +import com.futo.platformplayer.dialogs.PluginUpdateDialog import com.futo.platformplayer.dialogs.ProgressDialog import com.futo.platformplayer.engine.exceptions.PluginException +import com.futo.platformplayer.fragment.mainactivity.main.MainFragment +import com.futo.platformplayer.fragment.mainactivity.main.SourceDetailFragment +import com.futo.platformplayer.fragment.mainactivity.main.VideoDetailFragment import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.models.ImportCache import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StateBackup +import com.futo.platformplayer.states.StatePlugins import com.futo.platformplayer.stores.v2.ManagedStore import com.futo.platformplayer.views.ToastView import kotlinx.coroutines.CoroutineScope @@ -184,6 +190,14 @@ class UIDialogs { dialog.show(); } + fun showPluginUpdateDialog(context: Context, oldConfig: SourcePluginConfig, newConfig: SourcePluginConfig) { + val dialog = PluginUpdateDialog(context, oldConfig, newConfig); + registerDialogOpened(dialog); + dialog.setOnDismissListener { registerDialogClosed(dialog) }; + dialog.show(); + } + + fun showDialog(context: Context, icon: Int, text: String, textDetails: String? = null, code: String? = null, defaultCloseAction: Int, vararg actions: Action) { val builder = AlertDialog.Builder(context); val view = LayoutInflater.from(context).inflate(R.layout.dialog_multi_button, null); @@ -269,22 +283,48 @@ class UIDialogs { }, UIDialogs.ActionStyle.PRIMARY) ); } - fun showGeneralRetryErrorDialog(context: Context, msg: String, ex: Throwable? = null, retryAction: (() -> Unit)? = null, closeAction: (() -> Unit)? = null) { + fun showGeneralRetryErrorDialog(context: Context, msg: String, ex: Throwable? = null, retryAction: (() -> Unit)? = null, closeAction: (() -> Unit)? = null, mainFragment: MainFragment? = null) { + val pluginConfig = if(ex is PluginException) ex.config else null; val pluginInfo = if(ex is PluginException) "\nPlugin [${ex.config.name}]" else ""; - showDialog(context, - R.drawable.ic_error_pred, - "${msg}${pluginInfo}", - (if(ex != null ) "${ex.message}" else ""), - if(ex is PluginException) ex.code else null, - 0, - UIDialogs.Action(context.getString(R.string.retry), { - retryAction?.invoke(); - }, UIDialogs.ActionStyle.PRIMARY), - UIDialogs.Action(context.getString(R.string.close), { - closeAction?.invoke() - }, UIDialogs.ActionStyle.NONE) - ); + + var exMsg = if(ex != null ) "${ex.message}" else ""; + if(pluginConfig != null && pluginConfig is SourcePluginConfig && StatePlugins.instance.hasUpdateAvailable(pluginConfig)) + exMsg += "\n\nAn update is available" + + if(mainFragment != null && pluginConfig != null && pluginConfig is SourcePluginConfig && StatePlugins.instance.hasUpdateAvailable(pluginConfig)) + showDialog(context, + R.drawable.ic_error_pred, + "${msg}${pluginInfo}", + exMsg, + if(ex is PluginException) ex.code else null, + 1, + UIDialogs.Action(context.getString(R.string.update), { + mainFragment.navigate<SourceDetailFragment>(pluginConfig); + if(mainFragment is VideoDetailFragment) + mainFragment.minimizeVideoDetail(); + }, UIDialogs.ActionStyle.ACCENT), + UIDialogs.Action(context.getString(R.string.close), { + closeAction?.invoke() + }, UIDialogs.ActionStyle.NONE), + UIDialogs.Action(context.getString(R.string.retry), { + retryAction?.invoke(); + }, UIDialogs.ActionStyle.PRIMARY) + ); + else + showDialog(context, + R.drawable.ic_error_pred, + "${msg}${pluginInfo}", + exMsg, + if(ex is PluginException) ex.code else null, + 0, + UIDialogs.Action(context.getString(R.string.close), { + closeAction?.invoke() + }, UIDialogs.ActionStyle.NONE), + UIDialogs.Action(context.getString(R.string.retry), { + retryAction?.invoke(); + }, UIDialogs.ActionStyle.PRIMARY) + ); } fun showSingleButtonDialog(context: Context, icon: Int, text: String, buttonText: String, action: (() -> Unit)) { diff --git a/app/src/main/java/com/futo/platformplayer/activities/AddSourceActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/AddSourceActivity.kt index 85873d28a7d518fc9407b95e2547a3370d8a37d7..81be84eaf485d627eb5f9883f6f36d9ab820df71 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/AddSourceActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/AddSourceActivity.kt @@ -224,7 +224,7 @@ class AddSourceActivity : AppCompatActivity() { val isNew = !StatePlatform.instance.getAvailableClients().any { it.id == config.id }; StatePlugins.instance.installPlugin(this, lifecycleScope, config, script) { if(it) { - StatePlatform.instance.clearUpdateAvailable(config) + StatePlugins.instance.clearUpdateAvailable(config) if(isNew) lifecycleScope.launch { StatePlatform.instance.enableClient(listOf(config.id)); diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt index 64ceb31aa6e8e5d1c8c106bc1721278ad486732e..0a3498bb956b72b0c512fc8b43e93e31f6fe70ae 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt @@ -46,6 +46,7 @@ import com.futo.platformplayer.constructs.Event2 import com.futo.platformplayer.engine.V8Plugin import com.futo.platformplayer.engine.exceptions.PluginEngineException import com.futo.platformplayer.engine.exceptions.ScriptCaptchaRequiredException +import com.futo.platformplayer.engine.exceptions.ScriptException import com.futo.platformplayer.engine.exceptions.ScriptImplementationException import com.futo.platformplayer.engine.exceptions.ScriptValidationException import com.futo.platformplayer.logging.Logger @@ -56,6 +57,7 @@ import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.states.StatePlugins import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +import java.lang.Exception import java.time.OffsetDateTime import kotlin.reflect.full.findAnnotations import kotlin.reflect.jvm.kotlinFunction diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt index 30196913c5d5f264d1d0363ab3c941cefdaddee3..e279fc0f041707b475374c9cede7621c54fe1c40 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginConfig.kt @@ -80,6 +80,41 @@ class SourcePluginConfig( return _allowUrlsLowerVal!!; }; + fun isLowRiskUpdate(oldScript: String, newConfig: SourcePluginConfig, newScript: String): Boolean{ + + //All urls should already be allowed + for(url in newConfig.allowUrls) { + if(!allowUrls.contains(url)) + return false; + } + //All packages should already be allowed + for(pack in newConfig.packages) { + if(!packages.contains(pack)) + return false; + } + //Developer Submit Url should be same or empty + if(!newConfig.developerSubmitUrl.isNullOrEmpty() && developerSubmitUrl != newConfig.developerSubmitUrl) + return false; + + //Should have a public key + if(scriptPublicKey.isNullOrEmpty() || scriptSignature.isNullOrEmpty()) + return false; + + //Should be same public key + if(scriptPublicKey != newConfig.scriptPublicKey) + return false; + + //Old signature should be valid + if(!validate(oldScript)) + return false; + + //New signature should be valid + if(!newConfig.validate(newScript)) + return false; + + return true; + } + fun getWarnings(scriptToCheck: String? = null) : List<Pair<String,String>> { val list = mutableListOf<Pair<String,String>>(); diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginDescriptor.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginDescriptor.kt index bea18399fd1899d7202278d083cdc0f9de92cc99..add531314368178dd963d7949cbde29210990613 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginDescriptor.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/SourcePluginDescriptor.kt @@ -91,8 +91,10 @@ class SourcePluginDescriptor { @Serializable class AppPluginSettings { - @FormField(R.string.check_for_updates_setting, FieldForm.TOGGLE, R.string.check_for_updates_setting_description, 0) + @FormField(R.string.check_for_updates_setting, FieldForm.TOGGLE, R.string.check_for_updates_setting_description, -1) var checkForUpdates: Boolean = true; + @FormField(R.string.automatic_update_setting, FieldForm.TOGGLE, R.string.automatic_update_setting_description, 0) + var automaticUpdate: Boolean = false; @FormField(R.string.visibility, "group", R.string.enable_where_this_plugins_content_are_visible, 2) var tabEnabled = TabEnabled(); diff --git a/app/src/main/java/com/futo/platformplayer/dialogs/PluginUpdateDialog.kt b/app/src/main/java/com/futo/platformplayer/dialogs/PluginUpdateDialog.kt new file mode 100644 index 0000000000000000000000000000000000000000..b7109a815100ada164d156a80112d501676d76bb --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/dialogs/PluginUpdateDialog.kt @@ -0,0 +1,253 @@ +package com.futo.platformplayer.dialogs + +import android.app.AlertDialog +import android.content.Context +import android.content.Intent +import android.graphics.Color +import android.graphics.drawable.Animatable +import android.media.MediaCas.PluginDescriptor +import android.net.Uri +import android.os.Bundle +import android.text.Spannable +import android.text.SpannableString +import android.text.method.ScrollingMovementMethod +import android.text.style.ForegroundColorSpan +import android.view.LayoutInflater +import android.view.View +import android.view.WindowManager +import android.widget.Button +import android.widget.FrameLayout +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.view.isVisible +import com.bumptech.glide.Glide +import com.futo.platformplayer.R +import com.futo.platformplayer.UIDialogs +import com.futo.platformplayer.activities.AddSourceActivity +import com.futo.platformplayer.api.http.ManagedHttpClient +import com.futo.platformplayer.api.media.exceptions.NoPlatformClientException +import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig +import com.futo.platformplayer.api.media.platforms.js.SourcePluginDescriptor +import com.futo.platformplayer.assume +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.models.ImportCache +import com.futo.platformplayer.states.StateApp +import com.futo.platformplayer.states.StateBackup +import com.futo.platformplayer.states.StatePlugins +import com.futo.platformplayer.stores.v2.ManagedStore +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext + +class PluginUpdateDialog : AlertDialog { + companion object { + private val TAG = "PluginUpdateDialog"; + } + private val _context: Context; + + private lateinit var _buttonCancel1: Button; + private lateinit var _buttonCancel2: Button; + private lateinit var _buttonUpdate: LinearLayout; + + private lateinit var _buttonOk: LinearLayout; + private lateinit var _buttonInstall: LinearLayout; + + private lateinit var _textPlugin: TextView; + private lateinit var _textProgres: TextView; + private lateinit var _textError: TextView; + private lateinit var _textResult: TextView; + + private lateinit var _uiChoiceTop: FrameLayout; + private lateinit var _uiProgressTop: FrameLayout; + private lateinit var _uiRiskTop: FrameLayout; + + private lateinit var _uiChoiceBot: LinearLayout; + private lateinit var _uiResultBot: LinearLayout; + private lateinit var _uiRiskBot: LinearLayout; + private lateinit var _uiProgressBot: LinearLayout; + + private lateinit var _iconPlugin: ImageView; + private lateinit var _updateSpinner: ImageView; + + private var _isUpdating = false; + + private val _oldConfig: SourcePluginConfig; + private val _newConfig: SourcePluginConfig; + + + constructor(context: Context, oldConfig: SourcePluginConfig, newConfig: SourcePluginConfig): super(context) { + _context = context; + _oldConfig = oldConfig; + _newConfig = newConfig; + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState); + setContentView(LayoutInflater.from(context).inflate(R.layout.dialog_plugin_update, null)); + + _buttonCancel1 = findViewById(R.id.button_cancel_1); + _buttonCancel2 = findViewById(R.id.button_cancel_2); + _buttonUpdate = findViewById(R.id.button_update); + + _buttonOk = findViewById(R.id.button_ok); + _buttonInstall = findViewById(R.id.button_install); + + _textPlugin = findViewById(R.id.text_plugin); + _textProgres = findViewById(R.id.text_progress); + _textError = findViewById(R.id.text_error); + _textResult = findViewById(R.id.text_result); + + _uiChoiceTop = findViewById(R.id.dialog_ui_choice_top); + _uiProgressTop = findViewById(R.id.dialog_ui_progress_top); + _uiRiskTop = findViewById(R.id.dialog_ui_risk_top); + + _uiChoiceBot = findViewById(R.id.dialog_ui_bottom_choice); + _uiResultBot = findViewById(R.id.dialog_ui_bottom_result); + _uiRiskBot = findViewById(R.id.dialog_ui_bottom_risk); + _uiProgressBot = findViewById(R.id.dialog_ui_bottom_progress); + + _updateSpinner = findViewById(R.id.update_spinner); + _iconPlugin = findViewById(R.id.icon_plugin); + + _buttonCancel1.setOnClickListener { + dismiss(); + }; + _buttonCancel2.setOnClickListener { + dismiss(); + }; + _buttonUpdate.setOnClickListener { + if (_isUpdating) + return@setOnClickListener; + _isUpdating = true; + update(); + }; + + Glide.with(_iconPlugin) + .load(_oldConfig.absoluteIconUrl) + .fallback(R.drawable.ic_sources) + .into(_iconPlugin); + _textPlugin.text = _oldConfig.name; + + val descriptor = StatePlugins.instance.getPlugin(_oldConfig.id); + if(descriptor != null) { + if(descriptor.appSettings.automaticUpdate) { + if (_isUpdating) + return; + _isUpdating = true; + update(); + } + } + } + + override fun dismiss() { + super.dismiss(); + } + + private fun update() { + _uiChoiceTop.visibility = View.GONE; + _uiRiskTop.visibility = View.GONE; + _uiChoiceBot.visibility = View.GONE; + _uiResultBot.visibility = View.GONE; + _uiRiskBot.visibility = View.GONE; + _uiProgressTop.visibility = View.VISIBLE; + _uiProgressBot.visibility = View.VISIBLE; + + setCancelable(false); + setCanceledOnTouchOutside(false); + + Logger.i(TAG, "Keep screen on set import") + window?.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + _updateSpinner.drawable?.assume<Animatable>()?.start(); + + val scope = StateApp.instance.scopeOrNull; + scope?.launch(Dispatchers.IO) { + try { + val client = ManagedHttpClient(); + val script = StatePlugins.instance.getScript(_oldConfig.id) ?: ""; + + val newScript = client.get(_newConfig.absoluteScriptUrl)?.body?.string(); + if(newScript.isNullOrEmpty()) + throw IllegalStateException("No script found"); + + if(_oldConfig.isLowRiskUpdate(script, _newConfig, newScript)){ + + StatePlugins.instance.installPluginBackground(context, StateApp.instance.scope, _newConfig, newScript, + { text: String, progress: Double -> + _textProgres.setText(text); + }, + { ex -> + if(ex == null) { + StatePlugins.instance.clearUpdateAvailable(_newConfig); + _iconPlugin.setImageResource(R.drawable.ic_check); + _textError.visibility = View.GONE; + _textResult.visibility = View.VISIBLE; + } + else { + _iconPlugin.setImageResource(R.drawable.ic_error_pred); + _textError.text = ex.message + "\n\nYou can retry inside the sources tab"; + _textError.visibility = View.VISIBLE; + _textResult.visibility = View.GONE; + } + try { + _buttonOk.setOnClickListener { + dismiss(); + } + _uiProgressTop.visibility = View.GONE; + _uiProgressBot.visibility = View.GONE; + _uiChoiceTop.visibility = View.VISIBLE; + _uiResultBot.visibility = View.VISIBLE; + } catch (e: Throwable) { + Logger.e(TAG, "Failed to update UI.", e) + } finally { + Logger.i(TAG, "Keep screen on unset update") + window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + }); + + } + else { + withContext(Dispatchers.Main) { + try { + _buttonInstall.setOnClickListener { + dismiss(); + + val intent = Intent(_context, AddSourceActivity::class.java).apply { + data = Uri.parse(_newConfig.sourceUrl) + }; + + _context.startActivity(intent); + } + + _uiProgressTop.visibility = View.GONE; + _uiProgressBot.visibility = View.GONE; + _uiRiskTop.visibility = View.VISIBLE; + _uiRiskBot.visibility = View.VISIBLE; + } catch (e: Throwable) { + Logger.e(TAG, "Failed to update UI.", e) + } finally { + Logger.i(TAG, "Keep screen on unset update") + window?.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + } + } + } catch (e: Throwable) { + Logger.e(TAG, "Failed to update.", e); + withContext(Dispatchers.Main) { + _buttonOk.setOnClickListener { + dismiss(); + } + _iconPlugin.setImageResource(R.drawable.ic_error_pred); + _textResult.visibility = View.GONE; + _uiProgressTop.visibility = View.GONE; + _uiProgressBot.visibility = View.GONE; + _uiChoiceTop.visibility = View.VISIBLE; + _uiResultBot.visibility = View.VISIBLE; + _textError.visibility = View.VISIBLE; + _textError.text = e.message + "\n\nYou can retry inside the sources tab" + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt index 937e2d23bcb05272e4c9b71886f38fa7bdd54eab..b1b7f4609b7ac32eacb465c51513161d3ce69c3a 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/packages/PackageHttp.kt @@ -316,7 +316,7 @@ class PackageHttp: V8Package { return result } - /*private fun logRequest(method: String, url: String, headers: Map<String, String> = HashMap(), body: String?) { + private fun logRequest(method: String, url: String, headers: Map<String, String> = HashMap(), body: String?) { Logger.v(TAG) { val stringBuilder = StringBuilder(); stringBuilder.appendLine("HTTP request (useAuth = )"); @@ -333,7 +333,7 @@ class PackageHttp: V8Package { return@v stringBuilder.toString(); }; - }*/ + } /*private fun logResponse(method: String, url: String, responseCode: Int? = null, responseHeaders: Map<String, List<String>> = HashMap(), responseBody: String? = null) { Logger.v(TAG) { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt index 602620ad892edb09d8f291c719cd49fd35bab974..3359119fa6ffb614afc0bf1c5388293cc15ec847 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt @@ -152,7 +152,7 @@ class ChannelFragment : MainFragment() { } .exception<Throwable> { Logger.e(TAG, "Failed to load channel.", it); - UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, { loadChannel() }); + UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, { loadChannel() }, null, fragment); } val tabs: TabLayout = findViewById(R.id.tabs); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt index 828316869af96d8299f3854edc8ed53e368b4e85..f7a347771d76648ebe12174060e0224d286ba4c0 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ContentSearchResultsFragment.kt @@ -99,7 +99,7 @@ class ContentSearchResultsFragment : MainFragment() { .success { loadedResult(it); }.exception<ScriptCaptchaRequiredException> { } .exception<Throwable> { Logger.w(TAG, "Failed to load results.", it); - UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, { loadResults() }); + UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, { loadResults() }, null, fragment); } setPreviewsEnabled(Settings.instance.search.previewFeedItems); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorSearchResultsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorSearchResultsFragment.kt index eac18ba9b5116f07356f1cec8a915926b61ab6cc..a7570846ac5b0ddd85e75433e818e7f27ee2539b 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorSearchResultsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/CreatorSearchResultsFragment.kt @@ -60,7 +60,7 @@ class CreatorSearchResultsFragment : MainFragment() { .exception<ScriptCaptchaRequiredException> { } .exception<Throwable> { Logger.w(ChannelFragment.TAG, "Failed to load results.", it); - UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, { loadResults() }); + UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, { loadResults() }, null, fragment); } } diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/FeedView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/FeedView.kt index 78459af492ea8ffcbb179fb6bc615b2fb66bf59d..7f0e1ccc976ba7d285fa88419e1ca8b17dc10133 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/FeedView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/FeedView.kt @@ -144,7 +144,7 @@ abstract class FeedView<TFragment, TResult, TConverted, TPager, TViewHolder> : L Logger.w(TAG, "Failed to load next page.", it); UIDialogs.showGeneralRetryErrorDialog(context, context.getString(R.string.failed_to_load_next_page), it, { loadNextPage(); - }); + }, null, fragment); //UIDialogs.showDataRetryDialog(layoutInflater, it.message, { loadNextPage() }); }; diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HistoryFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HistoryFragment.kt index fcbec07c12c629d45a42d58c7173e88db1db6f8f..2da46620f9175958a514046a4f4403bbb004e0a1 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HistoryFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HistoryFragment.kt @@ -174,7 +174,7 @@ class HistoryFragment : MainFragment() { Logger.w(TAG, "Failed to load next page.", it); UIDialogs.showGeneralRetryErrorDialog(context, context.getString(R.string.failed_to_load_next_page), it, { loadNextPage(); - }); + }, null, fragment); }; } diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt index 3e60c4ebb95a00507881080a46591892c88d6297..d09947837f4685eac6dc2d80c50161e1f867c12c 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/HomeFragment.kt @@ -126,10 +126,10 @@ class HomeFragment : MainFragment() { Logger.w(TAG, "Failed to load channel.", it); UIDialogs.showGeneralRetryErrorDialog(context, context.getString(R.string.failed_to_get_home), it, { loadResults() - }) { + }, { finishRefreshLayoutLoader(); setLoading(false); - }; + }, fragment); }; setPreviewsEnabled(Settings.instance.home.previewFeedItems); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistFragment.kt index ca7a57fe2330f70e4ab8b92c92ea378b9d2e977a..d938e97033716bbdc247122f7d95ad6552c37946 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistFragment.kt @@ -146,7 +146,7 @@ class PlaylistFragment : MainFragment() { .exception<Throwable> { Logger.w(TAG, "Failed to load playlist.", it); val c = context ?: return@exception; - UIDialogs.showGeneralRetryErrorDialog(c, context.getString(R.string.failed_to_load_playlist), it, ::fetchPlaylist); + UIDialogs.showGeneralRetryErrorDialog(c, context.getString(R.string.failed_to_load_playlist), it, ::fetchPlaylist, null, fragment); }; } diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistSearchResultsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistSearchResultsFragment.kt index ec01bb80f17d25c8e118138905577a532871171c..7900ff16dfb17aef9cc839ea86958739d70684e3 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistSearchResultsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PlaylistSearchResultsFragment.kt @@ -69,7 +69,7 @@ class PlaylistSearchResultsFragment : MainFragment() { .success { loadedResult(it); } .exception<Throwable> { Logger.w(ChannelFragment.TAG, "Failed to load results.", it); - UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, { loadResults() }); + UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, { loadResults() }, null, fragment); } } diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PostDetailFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PostDetailFragment.kt index f149b675ba19a755cdbe81a96cdc2953fb48ddfc..22ad5e93a773753e0aba1d3ab13808a7e2b2534d 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PostDetailFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/PostDetailFragment.kt @@ -162,7 +162,7 @@ class PostDetailFragment : MainFragment { .success { setPostDetails(it) } .exception<Throwable> { Logger.w(ChannelFragment.TAG, context.getString(R.string.failed_to_load_post), it); - UIDialogs.showGeneralRetryErrorDialog(context, context.getString(R.string.failed_to_load_post), it, ::fetchPost); + UIDialogs.showGeneralRetryErrorDialog(context, context.getString(R.string.failed_to_load_post), it, ::fetchPost, null, _fragment); } else TaskHandler(IPlatformPostDetails::class.java) { _fragment.lifecycleScope }; private val _taskLoadPolycentricProfile = TaskHandler<PlatformID, PolycentricCache.CachedPolycentricProfile?>(StateApp.instance.scopeGetter, { PolycentricCache.instance.getProfileAsync(it) }) diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt index 3099638f6fbbbb556bd97d4e5a936a445dfb440d..03d9a6269731ccafb36573bf1dcfe15bacbb5ea0 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SubscriptionsFeedFragment.kt @@ -262,7 +262,7 @@ class SubscriptionsFeedFragment : MainFragment() { .exception<Throwable> { Logger.w(ChannelFragment.TAG, "Failed to load channel.", it); if(it !is CancellationException) - UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, { loadResults(true) }); + UIDialogs.showGeneralRetryErrorDialog(context, it.message ?: "", it, { loadResults(true) }, null, fragment); else { finishRefreshLayoutLoader(); setLoading(false); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SuggestionsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SuggestionsFragment.kt index e5499062cb36b13ee456128eea5c8b7e07c14c21..2f7254dc9e072c51b008dddceb54967698d8b342 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SuggestionsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/SuggestionsFragment.kt @@ -40,7 +40,7 @@ class SuggestionsFragment : MainFragment { .success { suggestions -> updateSuggestions(suggestions, false) } .exception<Throwable> { Logger.w(ChannelFragment.TAG, "Failed to load suggestions.", it); - UIDialogs.showGeneralRetryErrorDialog(requireContext(), it.message ?: "", it, { loadSuggestions() }); + UIDialogs.showGeneralRetryErrorDialog(requireContext(), it.message ?: "", it, { loadSuggestions() }, null, this); }; constructor(): super() { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt index 9017ebc49a2ce122a8b5f74a99a9a91ff0bbd1da..6d0901e3333a9bba19ee3215cead8313b57ade78 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt @@ -2476,7 +2476,7 @@ class VideoDetailView : ConstraintLayout { Logger.w(TAG, "exception<ScriptImplementationException>", it) if (!nextVideo()) { - UIDialogs.showGeneralRetryErrorDialog(context, context.getString(R.string.failed_to_load_video_scriptimplementationexception), it, ::fetchVideo); + UIDialogs.showGeneralRetryErrorDialog(context, context.getString(R.string.failed_to_load_video_scriptimplementationexception), it, ::fetchVideo, null, fragment); } else { StateAnnouncement.instance.registerAnnouncement(video?.id?.value + "_Q_INVALIDVIDEO", context.getString(R.string.invalid_video), context.getString( R.string.there_was_an_invalid_video_in_your_queue_videoname_by_authorname_playback_was_skipped).replace("{videoName}", video?.name ?: "").replace("{authorName}", video?.author?.name ?: ""), AnnouncementType.SESSION) @@ -2512,7 +2512,7 @@ class VideoDetailView : ConstraintLayout { _retryJob = null; _liveTryJob?.cancel(); _liveTryJob = null; - UIDialogs.showGeneralRetryErrorDialog(context, context.getString(R.string.failed_to_load_video_scriptexception), it, ::fetchVideo); + UIDialogs.showGeneralRetryErrorDialog(context, context.getString(R.string.failed_to_load_video_scriptexception), it, ::fetchVideo, null, fragment); } } .exception<Throwable> { @@ -2524,7 +2524,7 @@ class VideoDetailView : ConstraintLayout { _retryJob = null; _liveTryJob?.cancel(); _liveTryJob = null; - UIDialogs.showGeneralRetryErrorDialog(context, context.getString(R.string.failed_to_load_video), it, ::fetchVideo); + UIDialogs.showGeneralRetryErrorDialog(context, context.getString(R.string.failed_to_load_video), it, ::fetchVideo, null, fragment); } } else TaskHandler(IPlatformVideoDetails::class.java, {fragment.lifecycleScope}); diff --git a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt index 1142310c542c649a6258e6e3b1e1d29cce3181ed..7fd26d6384a06929ef13695f546d0149fa64ee99 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt @@ -571,18 +571,22 @@ class StateApp { StateAnnouncement.instance.deleteAnnouncement("plugin-update") scopeOrNull?.launch(Dispatchers.IO) { - val updateAvailable = StatePlatform.instance.checkForUpdates() + val updateAvailable = StatePlugins.instance.checkForUpdates() withContext(Dispatchers.Main) { if (updateAvailable.isNotEmpty()) { UIDialogs.appToast( ToastView.Toast(updateAvailable - .map { " - " + it.name } + .map { " - " + it.first.name } .joinToString("\n"), true, null, "Plugin updates available" )); + + for(update in updateAvailable) + if(StatePlatform.instance.isClientEnabled(update.first.id)) + UIDialogs.showPluginUpdateDialog(context, update.first, update.second); } } } diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt index 1a3132a302fdcb70502db9a64abf383fbd386d55..0b03bab16b715f18e7369acadc0d5d52d09a0086 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlatform.kt @@ -80,7 +80,6 @@ class StatePlatform { private val _clientsLock = Object(); private val _availableClients : ArrayList<IPlatformClient> = ArrayList(); private val _enabledClients : ArrayList<IPlatformClient> = ArrayList(); - private var _updatesAvailableMap: HashSet<String> = hashSetOf(); //ClientPools are used to isolate plugin usage of certain components from others //This prevents for example a background task like subscriptions from blocking a user from opening a video @@ -925,66 +924,7 @@ class StatePlatform { } } - fun hasUpdateAvailable(c: SourcePluginConfig): Boolean { - val updatesAvailableMap = _updatesAvailableMap - synchronized(updatesAvailableMap) { - return updatesAvailableMap.contains(c.id) - } - } - - suspend fun checkForUpdates(): List<SourcePluginConfig> = withContext(Dispatchers.IO) { - var configs = mutableListOf<SourcePluginConfig>() - val updatesAvailableFor = hashSetOf<String>() - for (availableClient in getAvailableClients().filter { it is JSClient && it.descriptor.appSettings.checkForUpdates }) { - if (availableClient !is JSClient) { - continue - } - - if (checkForUpdates(availableClient.config)) { - configs.add(availableClient.config); - updatesAvailableFor.add(availableClient.config.id) - } - } - - _updatesAvailableMap = updatesAvailableFor - return@withContext configs; - } - - fun clearUpdateAvailable(c: SourcePluginConfig) { - val updatesAvailableMap = _updatesAvailableMap - synchronized(updatesAvailableMap) { - updatesAvailableMap.remove(c.id) - } - } - private suspend fun checkForUpdates(c: SourcePluginConfig): Boolean = withContext(Dispatchers.IO) { - val sourceUrl = c.sourceUrl ?: return@withContext false; - - Logger.i(TAG, "Check for source updates '${c.name}'."); - try { - val client = ManagedHttpClient(); - val response = client.get(sourceUrl); - Logger.i(TAG, "Downloading source config '$sourceUrl'."); - - if (!response.isOk || response.body == null) { - return@withContext false; - } - - val configJson = response.body.string(); - Logger.i(TAG, "Downloaded source config ($sourceUrl):\n${configJson}"); - - val config = SourcePluginConfig.fromJson(configJson); - if (config.version <= c.version) { - return@withContext false; - } - - Logger.i(TAG, "Update is available (config.version=${config.version}, source.config.version=${c.version})."); - return@withContext true; - } catch (e: Throwable) { - Logger.e(TAG, "Failed to check for updates.", e); - return@withContext false; - } - } companion object { private var _instance : StatePlatform? = null; diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt b/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt index d0c2d8bebd00aee396a2c649393fcfe4033b6f2f..420a3721efad24bee6e03d55736262a6b72a5e75 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePlugins.kt @@ -43,6 +43,7 @@ class StatePlugins { private var _embeddedSourcesDefault: List<String>? = null private var _sourcesUnderConstruction: Map<String, ImageVariable>? = null + private var _updatesAvailableMap: HashSet<String> = hashSetOf(); fun getPluginIconOrNull(id: String): ImageVariable? { if(iconsDir.hasIcon(id)) @@ -55,6 +56,70 @@ class StatePlugins { .load(); } + + suspend fun checkForUpdates(): List<Pair<SourcePluginConfig, SourcePluginConfig>> = withContext(Dispatchers.IO) { + var configs = mutableListOf<Pair<SourcePluginConfig, SourcePluginConfig>>() + val updatesAvailableFor = hashSetOf<String>() + for (availableClient in StatePlatform.instance.getAvailableClients().filter { it is JSClient && it.descriptor.appSettings.checkForUpdates }) { + if (availableClient !is JSClient) { + continue + } + + val newConfig = checkForUpdates(availableClient.config); + if (newConfig != null) { + configs.add(Pair(availableClient.config, newConfig)); + updatesAvailableFor.add(availableClient.config.id) + } + } + + _updatesAvailableMap = updatesAvailableFor + return@withContext configs; + } + private suspend fun checkForUpdates(c: SourcePluginConfig): SourcePluginConfig? = withContext(Dispatchers.IO) { + val sourceUrl = c.sourceUrl ?: return@withContext null; + + Logger.i(TAG, "Check for source updates '${c.name}'."); + try { + val client = ManagedHttpClient(); + val response = client.get(sourceUrl); + Logger.i(TAG, "Downloading source config '$sourceUrl'."); + + if (!response.isOk || response.body == null) { + return@withContext null; + } + + val configJson = response.body.string(); + Logger.i(TAG, "Downloaded source config ($sourceUrl):\n${configJson}"); + + val config = SourcePluginConfig.fromJson(configJson); + if (config.version <= c.version) { + return@withContext null; + } + + Logger.i(TAG, "Update is available (config.version=${config.version}, source.config.version=${c.version})."); + return@withContext config; + } catch (e: Throwable) { + Logger.e(TAG, "Failed to check for updates.", e); + return@withContext null; + } + } + fun hasUpdateAvailable(c: SourcePluginConfig): Boolean { + val updatesAvailableMap = _updatesAvailableMap + synchronized(updatesAvailableMap) { + return updatesAvailableMap.contains(c.id) + } + } + fun clearUpdateAvailable(c: SourcePluginConfig) { + val updatesAvailableMap = _updatesAvailableMap + synchronized(updatesAvailableMap) { + updatesAvailableMap.remove(c.id) + } + } + + + + + fun loginPlugin(context: Context, id: String, afterLogin: ()->Unit): Boolean { val descriptor = getPlugin(id) ?: return false; val config = descriptor.config; @@ -353,6 +418,49 @@ class StatePlugins { else verifyCanInstall(); } + fun installPluginBackground(context: Context, scope: CoroutineScope, config: SourcePluginConfig, script: String, onProgress: (text: String, progress: Double)->Unit, onConcluded: (ex: Throwable?)->Unit) { + scope.launch(Dispatchers.IO) { + val client = ManagedHttpClient(); + try { + withContext(Dispatchers.Main) { + onProgress.invoke("Validating script", 0.25); + } + + val tempDescriptor = SourcePluginDescriptor(config); + val plugin = JSClient(context, tempDescriptor, null, script); + plugin.validate(); + + withContext(Dispatchers.Main) { + onProgress.invoke("Downloading Icon", 0.5); + } + + val icon = config.absoluteIconUrl?.let { absIconUrl -> + withContext(Dispatchers.Main) { + onProgress.invoke("Saving plugin", 0.75); + } + val iconResp = client.get(absIconUrl); + if(iconResp.isOk) + return@let iconResp.body?.byteStream()?.use { it.readBytes() }; + return@let null; + } + val installEx = StatePlugins.instance.createPlugin(config, script, icon, true); + if(installEx != null) + throw installEx; + StatePlatform.instance.updateAvailableClients(context); + + withContext(Dispatchers.Main) { + onProgress.invoke("Finished", 1.0) + onConcluded.invoke(null); + } + } catch (ex: Exception) { + Logger.e(TAG, ex.message ?: "null", ex); + withContext(Dispatchers.Main) { + onConcluded.invoke(ex); + } + } + } + } + fun getPlugin(id: String): SourcePluginDescriptor? { if(id == StateDeveloper.DEV_ID) throw IllegalStateException("Attempted to retrieve a persistent developer plugin, this is not allowed"); diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/DisabledSourceView.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/DisabledSourceView.kt index a7194ae73eacfbbc5bdb7aeb20ea21811645d502..b2768d5e6fc2f57739ef2a2343ad4ce80f3ece1d 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/DisabledSourceView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/DisabledSourceView.kt @@ -10,6 +10,7 @@ import com.futo.platformplayer.api.media.platforms.js.JSClient import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.states.StatePlugins class DisabledSourceView : LinearLayout { private val _root: LinearLayout; @@ -37,7 +38,7 @@ class DisabledSourceView : LinearLayout { _textSource.text = client.name; - if (client is JSClient && StatePlatform.instance.hasUpdateAvailable(client.config)) { + if (client is JSClient && StatePlugins.instance.hasUpdateAvailable(client.config)) { _textSourceSubtitle.text = context.getString(R.string.update_available_exclamation) _textSourceSubtitle.setTextColor(context.getColor(R.color.light_blue_400)) _textSourceSubtitle.typeface = resources.getFont(R.font.inter_regular) diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/EnabledSourceViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/EnabledSourceViewHolder.kt index 9eed743bde8ff14cb4c566b821cabce4d2e322c6..d2cc258bbdf9e266d30f41a1d3786469bfbd06f2 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/EnabledSourceViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/EnabledSourceViewHolder.kt @@ -13,6 +13,7 @@ import com.futo.platformplayer.api.media.IPlatformClient import com.futo.platformplayer.api.media.platforms.js.JSClient import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.states.StatePlatform +import com.futo.platformplayer.states.StatePlugins class EnabledSourceViewHolder : ViewHolder { private val _imageSource: ImageView; @@ -61,7 +62,7 @@ class EnabledSourceViewHolder : ViewHolder { _textSource.text = client.name - if (client is JSClient && StatePlatform.instance.hasUpdateAvailable(client.config)) { + if (client is JSClient && StatePlugins.instance.hasUpdateAvailable(client.config)) { _textSourceSubtitle.text = itemView.context.getString(R.string.update_available_exclamation) _textSourceSubtitle.setTextColor(itemView.context.getColor(R.color.light_blue_400)) _textSourceSubtitle.typeface = itemView.resources.getFont(R.font.inter_regular) diff --git a/app/src/main/res/layout/dialog_plugin_update.xml b/app/src/main/res/layout/dialog_plugin_update.xml new file mode 100644 index 0000000000000000000000000000000000000000..7c89e8bc05d4d69a8f5c6d9b551b355ff9cac7dd --- /dev/null +++ b/app/src/main/res/layout/dialog_plugin_update.xml @@ -0,0 +1,315 @@ +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout 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:orientation="vertical" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:gravity="center" + android:background="@color/gray_1d"> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="vertical" + android:gravity="center" + android:paddingTop="30dp" + android:paddingBottom="30dp"> + + <FrameLayout + android:id="@+id/dialog_ui_choice_top" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="visible"> + <ImageView + android:id="@+id/icon_plugin" + android:layout_width="100dp" + android:layout_height="100dp" + app:srcCompat="@drawable/ic_sources" /> + + </FrameLayout> + <FrameLayout + android:id="@+id/dialog_ui_risk_top" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone"> + <ImageView + android:layout_width="100dp" + android:layout_height="100dp" + app:srcCompat="@drawable/ic_warning_yellow" /> + + </FrameLayout> + <FrameLayout + android:id="@+id/dialog_ui_progress_top" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:visibility="gone"> + + <ImageView + android:id="@+id/update_spinner" + android:layout_width="100dp" + android:layout_height="100dp" + app:srcCompat="@drawable/ic_update_animated" + android:visibility="visible" /> + + </FrameLayout> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Plugin Update" + android:textSize="15sp" + android:textColor="@color/white" + android:fontFamily="@font/inter_extra_light" + android:layout_marginTop="15dp" + android:layout_marginStart="30dp" + android:layout_marginEnd="30dp" /> + <TextView + android:id="@+id/text_plugin" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Some Plugin Name" + android:textSize="18sp" + android:textColor="@color/white" + android:fontFamily="@font/inter_regular" + android:layout_marginStart="30dp" + android:layout_marginEnd="30dp" /> + + <LinearLayout + android:id="@+id/dialog_ui_bottom_choice" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="visible" + android:orientation="vertical"> + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAlignment="center" + android:text="A new update is available.\nWould you like to update this plugin?" + android:textSize="14sp" + android:textColor="@color/white" + android:fontFamily="@font/inter_regular" + android:layout_marginTop="10dp" + android:layout_marginStart="30dp" + android:layout_marginEnd="30dp" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAlignment="center" + android:text="Updates may be critical to functionality" + android:textSize="13sp" + android:textColor="@color/pastel_red" + android:fontFamily="@font/inter_regular" + android:layout_marginTop="10dp" + android:layout_marginStart="30dp" + android:layout_marginEnd="30dp" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="center" + android:layout_marginTop="28dp"> + + <Space + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" /> + + <Button + android:id="@+id/button_cancel_1" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/cancel" + android:textSize="14dp" + android:fontFamily="@font/inter_regular" + android:textColor="@color/colorPrimary" + android:background="@color/transparent" /> + + <LinearLayout + android:id="@+id/button_update" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@drawable/background_button_primary" + android:layout_marginEnd="28dp" + android:clickable="true"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Update" + android:textSize="14dp" + android:textColor="@color/white" + android:fontFamily="@font/inter_regular" + android:paddingTop="10dp" + android:paddingBottom="10dp" + android:paddingStart="28dp" + android:paddingEnd="28dp"/> + </LinearLayout> + </LinearLayout> + </LinearLayout> + <LinearLayout + android:id="@+id/dialog_ui_bottom_progress" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" + android:orientation="vertical"> + <TextView + android:layout_width="match_parent" + android:id="@+id/text_progress" + android:layout_height="wrap_content" + android:textAlignment="center" + android:text="This plugin has modified its permissions" + android:textSize="14sp" + android:textColor="@color/white" + android:fontFamily="@font/inter_regular" + android:layout_marginTop="10dp" + android:layout_marginStart="30dp" + android:layout_marginEnd="30dp" /> + + </LinearLayout> + <LinearLayout + android:id="@+id/dialog_ui_bottom_risk" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" + android:orientation="vertical"> + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAlignment="center" + android:text="This plugin has modified its permissions" + android:textSize="14sp" + android:textColor="@color/white" + android:fontFamily="@font/inter_regular" + android:layout_marginTop="10dp" + android:layout_marginStart="30dp" + android:layout_marginEnd="30dp" /> + + <TextView + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAlignment="center" + android:text="Make sure you read the installation screen" + android:textSize="13sp" + android:textColor="@color/pastel_red" + android:fontFamily="@font/inter_regular" + android:layout_marginTop="10dp" + android:layout_marginStart="30dp" + android:layout_marginEnd="30dp" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="center" + android:layout_marginTop="28dp"> + + <Space + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" /> + + <Button + android:id="@+id/button_cancel_2" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/cancel" + android:textSize="14dp" + android:fontFamily="@font/inter_regular" + android:textColor="@color/colorPrimary" + android:background="@color/transparent" /> + + <LinearLayout + android:id="@+id/button_install" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@drawable/background_button_primary" + android:layout_marginEnd="28dp" + android:clickable="true"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Reinstall" + android:textSize="14dp" + android:textColor="@color/white" + android:fontFamily="@font/inter_regular" + android:paddingTop="10dp" + android:paddingBottom="10dp" + android:paddingStart="28dp" + android:paddingEnd="28dp"/> + </LinearLayout> + </LinearLayout> + </LinearLayout> + <LinearLayout + android:id="@+id/dialog_ui_bottom_result" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:visibility="gone" + android:orientation="vertical"> + <TextView + android:id="@+id/text_error" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAlignment="center" + android:text="" + android:textSize="13sp" + android:textColor="@color/pastel_red" + android:fontFamily="@font/inter_regular" + android:layout_marginTop="10dp" + android:layout_marginStart="30dp" + android:layout_marginEnd="30dp" /> + + <TextView + android:id="@+id/text_result" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:textAlignment="center" + android:text="Succesfully updated plugin." + android:textSize="13sp" + android:textColor="@color/white" + android:fontFamily="@font/inter_regular" + android:layout_marginTop="10dp" + android:layout_marginStart="30dp" + android:layout_marginEnd="30dp" /> + + <LinearLayout + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:orientation="horizontal" + android:gravity="center" + android:layout_marginTop="28dp"> + + <Space + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_weight="1" /> + + <LinearLayout + android:id="@+id/button_ok" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:background="@drawable/background_button_primary" + android:layout_marginEnd="28dp" + android:clickable="true"> + + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Ok" + android:textSize="14dp" + android:textColor="@color/white" + android:fontFamily="@font/inter_regular" + android:paddingTop="10dp" + android:paddingBottom="10dp" + android:paddingStart="28dp" + android:paddingEnd="28dp"/> + </LinearLayout> + </LinearLayout> + </LinearLayout> + + + </LinearLayout> +</LinearLayout> \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index caf59143862163dacb51d5463757581d96646e26..786fbd9941d8cb91fd1a4e44c6107f45295a9287 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -489,6 +489,8 @@ <string name="visibility">Visibility</string> <string name="check_for_updates_setting">Check for updates</string> <string name="check_for_updates_setting_description">If a plugin should be checked for updates on startup</string> + <string name="automatic_update_setting">Automatic Update</string> + <string name="automatic_update_setting_description">Update automatically on boot if no permissions changed and plugin is enabled</string> <string name="allow_developer_submit">Allow Developer Submissions</string> <string name="allow_developer_submit_description">Allows the developer to send data to their server, be careful as this might include sensitive data.</string> <string name="allow_developer_submit_warning">Make sure you trust the developer. They may gain access to sensitive data. Only enable this when you are trying to help the developer fix a bug.</string> diff --git a/app/src/unstable/assets/sources/test/TestConfig.json b/app/src/unstable/assets/sources/test/TestConfig.json deleted file mode 100644 index 86eed6ee1d7d7589c9e6c316c5b279a1eacbb9d1..0000000000000000000000000000000000000000 --- a/app/src/unstable/assets/sources/test/TestConfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "name": "Testing", - "description": "Just for testing.", - "author": "FUTO", - "authorUrl": "https://futo.org", - - "platformUrl": "https://odysee.com", - "sourceUrl": "https://plugins.grayjay.app/Test/TestConfig.json", - "repositoryUrl": "https://futo.org", - "scriptUrl": "./TestScript.js", - "version": 31, - - "iconUrl": "./odysee.png", - "id": "1c05bfc3-08b9-42d0-93d3-6d52e0fd34d8", - - "scriptSignature": "", - "scriptPublicKey": "", - "packages": ["Http"], - - "allowEval": false, - "allowUrls": [], - - "supportedClaimTypes": [] -} diff --git a/app/src/unstable/assets/sources/test/TestScript.js b/app/src/unstable/assets/sources/test/TestScript.js deleted file mode 100644 index 45c47d8f076d1d29931c8149f2a87e4c2723219b..0000000000000000000000000000000000000000 --- a/app/src/unstable/assets/sources/test/TestScript.js +++ /dev/null @@ -1,45 +0,0 @@ -var config = {}; - -//Source Methods -source.enable = function(conf){ - config = conf ?? {}; - //log(config); -} -source.getHome = function() { - return new ContentPager([ - source.getContentDetails("whatever") - ]); -}; - -//Video -source.isContentDetailsUrl = function(url) { - return REGEX_DETAILS_URL.test(url) -}; -source.getContentDetails = function(url) { - return new PlatformVideoDetails({ - id: new PlatformID("Test", "Something", config.id), - name: "Test Video", - thumbnails: new Thumbnails([]), - author: new PlatformAuthorLink(new PlatformID("Test", "TestID", config.id), - "TestAuthor", - "None", - ""), - datetime: parseInt(new Date().getTime() / 1000), - duration: 0, - viewCount: 0, - url: "", - isLive: false, - description: "", - rating: new RatingLikes(0), - video: new VideoSourceDescriptor([ - new HLSSource({ - name: "HLS", - url: "", - duration: 0, - priority: true - }) - ]) - }); -}; - -log("LOADED"); \ No newline at end of file diff --git a/app/src/unstable/assets/sources/test/odysee.png b/app/src/unstable/assets/sources/test/odysee.png deleted file mode 100644 index 472960d00a49401c1d97199ff6fa90cc337bd9bc..0000000000000000000000000000000000000000 Binary files a/app/src/unstable/assets/sources/test/odysee.png and /dev/null differ diff --git a/app/src/unstable/assets/sources/youtube b/app/src/unstable/assets/sources/youtube index cac27408440f5586c1c68d846456792041403d35..d1058f0b6ccf8cbebe4eed2afba145899e6dba00 160000 --- a/app/src/unstable/assets/sources/youtube +++ b/app/src/unstable/assets/sources/youtube @@ -1 +1 @@ -Subproject commit cac27408440f5586c1c68d846456792041403d35 +Subproject commit d1058f0b6ccf8cbebe4eed2afba145899e6dba00