From 2b15657f8de3bf555f9afc0a64df32be3db66120 Mon Sep 17 00:00:00 2001 From: Robert Date: Fri, 14 Apr 2023 14:43:33 +0200 Subject: [PATCH] Use a help page URL instead of doing things in-app --- .../Exceptions/ArtemisPluginException.cs | 22 ------- src/Artemis.Core/Plugins/IPluginHelpPage.cs | 22 ------- .../Plugins/MarkdownPluginHelpPage.cs | 41 ------------ src/Artemis.Core/Plugins/Plugin.cs | 5 +- src/Artemis.Core/Plugins/PluginInfo.cs | 14 +++- .../Plugins/PluginInfoHelpPage.cs | 26 -------- .../Services/PluginManagementService.cs | 3 - src/Artemis.UI/Artemis.UI.csproj | 8 --- .../Features/PluginFeatureViewModel.cs | 28 ++++---- .../Help/MarkdownPluginHelpPageView.axaml | 15 ----- .../Help/MarkdownPluginHelpPageView.axaml.cs | 18 ----- .../Help/MarkdownPluginHelpPageViewModel.cs | 65 ------------------- .../Plugins/Help/PluginHelpWindowView.axaml | 31 --------- .../Help/PluginHelpWindowView.axaml.cs | 22 ------- .../Plugins/Help/PluginHelpWindowViewModel.cs | 32 --------- .../Screens/Plugins/PluginView.axaml | 6 +- .../Screens/Plugins/PluginViewModel.cs | 55 +++++----------- 17 files changed, 46 insertions(+), 367 deletions(-) delete mode 100644 src/Artemis.Core/Plugins/IPluginHelpPage.cs delete mode 100644 src/Artemis.Core/Plugins/MarkdownPluginHelpPage.cs delete mode 100644 src/Artemis.Core/Plugins/PluginInfoHelpPage.cs delete mode 100644 src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml delete mode 100644 src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml.cs delete mode 100644 src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageViewModel.cs delete mode 100644 src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml delete mode 100644 src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml.cs delete mode 100644 src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowViewModel.cs diff --git a/src/Artemis.Core/Exceptions/ArtemisPluginException.cs b/src/Artemis.Core/Exceptions/ArtemisPluginException.cs index 6473f0293..547ce8ead 100644 --- a/src/Artemis.Core/Exceptions/ArtemisPluginException.cs +++ b/src/Artemis.Core/Exceptions/ArtemisPluginException.cs @@ -44,31 +44,9 @@ public class ArtemisPluginException : Exception public ArtemisPluginException(string message, Exception inner) : base(message, inner) { } - - /// - /// Creates a new instance of the class - /// - public ArtemisPluginException(string message, string helpPageId) : base(message) - { - HelpPageId = helpPageId; - } - - /// - /// Creates a new instance of the class - /// - public ArtemisPluginException(string message, Exception inner, string helpPageId) : base(message, inner) - { - HelpPageId = helpPageId; - } /// /// Gets the plugin the error is related to /// public Plugin? Plugin { get; } - - /// - /// Gets the ID of the help page to display for this exception. - /// - public string? HelpPageId { get; } - } \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/IPluginHelpPage.cs b/src/Artemis.Core/Plugins/IPluginHelpPage.cs deleted file mode 100644 index 1375d6716..000000000 --- a/src/Artemis.Core/Plugins/IPluginHelpPage.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Artemis.Core; - -/// -/// Represents a plugin related help page -/// -public interface IPluginHelpPage -{ - /// - /// Gets the plugin the help page belongs to. - /// - Plugin Plugin { get; } - - /// - /// Gets the title of the help page. - /// - public string Title { get; } - - /// - /// An ID used to quickly lead users to the help page in case of an error. - /// - public string Id { get; } -} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/MarkdownPluginHelpPage.cs b/src/Artemis.Core/Plugins/MarkdownPluginHelpPage.cs deleted file mode 100644 index 09554fa29..000000000 --- a/src/Artemis.Core/Plugins/MarkdownPluginHelpPage.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System.IO; - -namespace Artemis.Core; - -/// -/// Represents a plugin related help page -/// -public class MarkdownPluginHelpPage : IPluginHelpPage -{ - /// - /// Creates a new instance of the class. - /// - /// The plugin to display the markdown for. - /// A file path relative to the plugin or absolute, pointing to the markdown to display - /// The ID of the help page, used to quickly lead users to it in case of errors. - /// - public MarkdownPluginHelpPage(Plugin plugin, string title, string id, string markdownFile) - { - Plugin = plugin; - Title = title; - Id = id; - MarkdownFile = Path.IsPathRooted(markdownFile) ? markdownFile : Plugin.ResolveRelativePath(markdownFile); - - if (!File.Exists(MarkdownFile)) - throw new FileNotFoundException($"Could not find markdown file at \"{MarkdownFile}\""); - } - - /// - public Plugin Plugin { get; } - - /// - public string Title { get; } - - /// - public string Id { get; } - - /// - /// Gets the absolute path to the markdown that is to be displayed. - /// - public string MarkdownFile { get; } -} \ No newline at end of file diff --git a/src/Artemis.Core/Plugins/Plugin.cs b/src/Artemis.Core/Plugins/Plugin.cs index 290db1224..ff90181bc 100644 --- a/src/Artemis.Core/Plugins/Plugin.cs +++ b/src/Artemis.Core/Plugins/Plugin.cs @@ -36,7 +36,6 @@ public class Plugin : CorePropertyChanged, IDisposable Features = new ReadOnlyCollection(_features); Profilers = new ReadOnlyCollection(_profilers); - HelpPages = new List(); } /// @@ -58,9 +57,7 @@ public class Plugin : CorePropertyChanged, IDisposable /// Gets or sets a configuration dialog for this plugin that is accessible in the UI under Settings > Plugins /// public IPluginConfigurationDialog? ConfigurationDialog { get; set; } - - public List HelpPages { get; } - + /// /// Indicates whether the user enabled the plugin or not /// diff --git a/src/Artemis.Core/Plugins/PluginInfo.cs b/src/Artemis.Core/Plugins/PluginInfo.cs index c571518c7..e8c3f5d50 100644 --- a/src/Artemis.Core/Plugins/PluginInfo.cs +++ b/src/Artemis.Core/Plugins/PluginInfo.cs @@ -27,6 +27,7 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject private bool _requiresAdmin; private string _version = null!; private Uri? _website; + private Uri? _helpPage; internal PluginInfo() { @@ -91,6 +92,16 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject get => _repository; set => SetAndNotify(ref _repository, value); } + + /// + /// Gets or sets the help page of this plugin + /// + [JsonProperty] + public Uri? HelpPage + { + get => _helpPage; + set => SetAndNotify(ref _helpPage, value); + } /// /// The plugins display icon that's shown in the settings see for @@ -188,9 +199,6 @@ public class PluginInfo : CorePropertyChanged, IPrerequisitesSubject } } - [JsonProperty] - internal List HelpPages { get; set; } = new(); - /// /// Gets a boolean indicating whether this plugin is compatible with the current operating system and API version /// diff --git a/src/Artemis.Core/Plugins/PluginInfoHelpPage.cs b/src/Artemis.Core/Plugins/PluginInfoHelpPage.cs deleted file mode 100644 index 8f63c1623..000000000 --- a/src/Artemis.Core/Plugins/PluginInfoHelpPage.cs +++ /dev/null @@ -1,26 +0,0 @@ -using Newtonsoft.Json; - -namespace Artemis.Core; - -/// -/// Represents basic info about a plugin and contains a reference to the instance of said plugin -/// -internal class PluginInfoHelpPage -{ - [JsonConstructor] - public PluginInfoHelpPage(string title, string markdownFile, string id) - { - Title = title; - MarkdownFile = markdownFile; - Id = id; - } - - [JsonProperty(Required = Required.Always)] - public string Title { get; } - - [JsonProperty(Required = Required.Always)] - public string MarkdownFile { get; } - - [JsonProperty(Required = Required.Always)] - public string Id { get; } -} \ No newline at end of file diff --git a/src/Artemis.Core/Services/PluginManagementService.cs b/src/Artemis.Core/Services/PluginManagementService.cs index e8a7317d3..c03aec7bb 100644 --- a/src/Artemis.Core/Services/PluginManagementService.cs +++ b/src/Artemis.Core/Services/PluginManagementService.cs @@ -350,9 +350,6 @@ internal class PluginManagementService : IPluginManagementService // Load the entity and fall back on creating a new one Plugin plugin = new(pluginInfo, directory, _pluginRepository.GetPluginByGuid(pluginInfo.Guid)); - foreach (PluginInfoHelpPage pluginInfoHelpPage in pluginInfo.HelpPages) - plugin.HelpPages.Add(new MarkdownPluginHelpPage(plugin, pluginInfoHelpPage.Title, pluginInfoHelpPage.Id, pluginInfoHelpPage.MarkdownFile)); - OnPluginLoading(new PluginEventArgs(plugin)); // Locate the main assembly entry diff --git a/src/Artemis.UI/Artemis.UI.csproj b/src/Artemis.UI/Artemis.UI.csproj index ea9845315..a1252420a 100644 --- a/src/Artemis.UI/Artemis.UI.csproj +++ b/src/Artemis.UI/Artemis.UI.csproj @@ -52,14 +52,6 @@ UpdatingTabView.axaml Code - - MarkdownPluginHelpPageView.axaml - Code - - - PluginHelpWindowView.axaml - Code - PluginFeatureView.axaml Code diff --git a/src/Artemis.UI/Screens/Plugins/Features/PluginFeatureViewModel.cs b/src/Artemis.UI/Screens/Plugins/Features/PluginFeatureViewModel.cs index 2df1c64a9..2d83d9e6c 100644 --- a/src/Artemis.UI/Screens/Plugins/Features/PluginFeatureViewModel.cs +++ b/src/Artemis.UI/Screens/Plugins/Features/PluginFeatureViewModel.cs @@ -23,7 +23,6 @@ namespace Artemis.UI.Screens.Plugins.Features; public class PluginFeatureViewModel : ActivatableViewModelBase { private readonly ICoreService _coreService; - private readonly INotificationService _notificationService; private readonly IPluginManagementService _pluginManagementService; private readonly IWindowService _windowService; private bool _enabling; @@ -32,12 +31,10 @@ public class PluginFeatureViewModel : ActivatableViewModelBase bool showShield, ICoreService coreService, IWindowService windowService, - INotificationService notificationService, IPluginManagementService pluginManagementService) { _coreService = coreService; _windowService = windowService; - _notificationService = notificationService; _pluginManagementService = pluginManagementService; FeatureInfo = pluginFeatureInfo; @@ -176,11 +173,7 @@ public class PluginFeatureViewModel : ActivatableViewModelBase if (FeatureInfo.Instance == null) { this.RaisePropertyChanged(nameof(IsEnabled)); - _notificationService.CreateNotification() - .WithMessage($"Feature '{FeatureInfo.Name}' is in a broken state and cannot enable.") - .HavingButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder)) - .WithSeverity(NotificationSeverity.Error) - .Show(); + await ShowFailureDialog($"Failed to enable '{FeatureInfo.Name}'", $"Feature '{FeatureInfo.Name}' is in a broken state and cannot enable."); return; } @@ -215,11 +208,7 @@ public class PluginFeatureViewModel : ActivatableViewModelBase } catch (Exception e) { - _notificationService.CreateNotification() - .WithMessage($"Failed to enable '{FeatureInfo.Name}'.\r\n{e.Message}") - .HavingButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder)) - .WithSeverity(NotificationSeverity.Error) - .Show(); + await ShowFailureDialog($"Failed to enable '{FeatureInfo.Name}'", e.Message); } finally { @@ -254,4 +243,17 @@ public class PluginFeatureViewModel : ActivatableViewModelBase { this.RaisePropertyChanged(nameof(CanToggleEnabled)); } + + private async Task ShowFailureDialog(string action, string message) + { + ContentDialogBuilder builder = _windowService.CreateContentDialog() + .WithTitle(action) + .WithContent(message) + .HavingPrimaryButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder)); + // If available, add a secondary button pointing to the support page + if (FeatureInfo.Plugin.Info.HelpPage != null) + builder = builder.HavingSecondaryButton(b => b.WithText("Open support page").WithAction(() => Utilities.OpenUrl(FeatureInfo.Plugin.Info.HelpPage.ToString()))); + + await builder.ShowAsync(); + } } \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml b/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml deleted file mode 100644 index e6441425b..000000000 --- a/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - diff --git a/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml.cs b/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml.cs deleted file mode 100644 index a151841c2..000000000 --- a/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageView.axaml.cs +++ /dev/null @@ -1,18 +0,0 @@ -using Avalonia.Controls; -using Avalonia.Markup.Xaml; -using Avalonia.ReactiveUI; - -namespace Artemis.UI.Screens.Plugins.Help; - -public partial class MarkdownPluginHelpPageView : ReactiveUserControl -{ - public MarkdownPluginHelpPageView() - { - InitializeComponent(); - } - - private void InitializeComponent() - { - AvaloniaXamlLoader.Load(this); - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageViewModel.cs b/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageViewModel.cs deleted file mode 100644 index 69dee5d11..000000000 --- a/src/Artemis.UI/Screens/Plugins/Help/MarkdownPluginHelpPageViewModel.cs +++ /dev/null @@ -1,65 +0,0 @@ -using System; -using System.IO; -using System.Reactive.Disposables; -using System.Threading.Tasks; -using Artemis.Core; -using Artemis.UI.Shared; -using ReactiveUI; - -namespace Artemis.UI.Screens.Plugins.Help; - -public class MarkdownPluginHelpPageViewModel : ActivatableViewModelBase, IPluginHelpPage -{ - private readonly MarkdownPluginHelpPage _helpPage; - private string? _markdownText; - - public MarkdownPluginHelpPageViewModel(MarkdownPluginHelpPage helpPage) - { - _helpPage = helpPage; - this.WhenActivated(d => - { - FileSystemWatcher watcher = new(); - watcher.Path = Path.GetDirectoryName(_helpPage.MarkdownFile) ?? throw new InvalidOperationException($"Path \"{_helpPage.MarkdownFile}\" does not contain a directory"); - watcher.Filter = Path.GetFileName(_helpPage.MarkdownFile); - watcher.EnableRaisingEvents = true; - watcher.Changed += WatcherOnChanged; - watcher.DisposeWith(d); - - LoadMarkdown().DisposeWith(d); - }); - } - - public string? MarkdownText - { - get => _markdownText; - set => RaiseAndSetIfChanged(ref _markdownText, value); - } - - private async void WatcherOnChanged(object sender, FileSystemEventArgs e) - { - await LoadMarkdown(); - } - - private async Task LoadMarkdown() - { - try - { - await using FileStream stream = new(_helpPage.MarkdownFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - using StreamReader reader = new(stream); - MarkdownText = await reader.ReadToEndAsync(); - } - catch (Exception e) - { - MarkdownText = e.Message; - } - } - - /// - public Plugin Plugin => _helpPage.Plugin; - - /// - public string Title => _helpPage.Title; - - /// - public string Id => _helpPage.Id; -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml b/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml deleted file mode 100644 index 0935be744..000000000 --- a/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml.cs b/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml.cs deleted file mode 100644 index c821716bb..000000000 --- a/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowView.axaml.cs +++ /dev/null @@ -1,22 +0,0 @@ -using Artemis.UI.Shared; -using Avalonia; -using Avalonia.Controls; -using Avalonia.Markup.Xaml; - -namespace Artemis.UI.Screens.Plugins.Help; - -public partial class PluginHelpWindowView : ReactiveAppWindow -{ - public PluginHelpWindowView() - { - InitializeComponent(); -#if DEBUG - this.AttachDevTools(); -#endif - } - - private void InitializeComponent() - { - AvaloniaXamlLoader.Load(this); - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowViewModel.cs b/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowViewModel.cs deleted file mode 100644 index b9ad2b37c..000000000 --- a/src/Artemis.UI/Screens/Plugins/Help/PluginHelpWindowViewModel.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System.Collections.ObjectModel; -using System.Linq; -using Artemis.Core; -using Artemis.UI.Shared; - -namespace Artemis.UI.Screens.Plugins.Help; - -public class PluginHelpWindowViewModel : ActivatableViewModelBase -{ - private IPluginHelpPage? _selectedHelpPage; - - public PluginHelpWindowViewModel(Plugin plugin, string? preselectId) - { - Plugin = plugin; - DisplayName = $"{Plugin.Info.Name} | Help"; - - // Populate help pages by wrapping MarkdownHelpPages into a MarkdownHelpPageViewModel - // other types are used directly, up to them to implement a VM directly as well - HelpPages = new ReadOnlyCollection(plugin.HelpPages.Select(p => p is MarkdownPluginHelpPage m ? new MarkdownPluginHelpPageViewModel(m) : p).ToList()); - - _selectedHelpPage = preselectId != null ? HelpPages.FirstOrDefault(p => p.Id == preselectId) : HelpPages.FirstOrDefault(); - } - - public Plugin Plugin { get; } - public ReadOnlyCollection HelpPages { get; } - - public IPluginHelpPage? SelectedHelpPage - { - get => _selectedHelpPage; - set => RaiseAndSetIfChanged(ref _selectedHelpPage, value); - } -} \ No newline at end of file diff --git a/src/Artemis.UI/Screens/Plugins/PluginView.axaml b/src/Artemis.UI/Screens/Plugins/PluginView.axaml index 605a44a59..640279366 100644 --- a/src/Artemis.UI/Screens/Plugins/PluginView.axaml +++ b/src/Artemis.UI/Screens/Plugins/PluginView.axaml @@ -92,9 +92,9 @@ + IsVisible="{CompiledBinding Plugin.Info.HelpPage, Converter={x:Static StringConverters.IsNotNullOrEmpty}}" + NavigateUri="{CompiledBinding Plugin.Info.HelpPage}" + ToolTip.Tip="{CompiledBinding Plugin.Info.HelpPage}"> ? reload, @@ -59,7 +56,6 @@ public class PluginViewModel : ActivatableViewModelBase Reload = reload; OpenSettings = ReactiveCommand.Create(ExecuteOpenSettings, this.WhenAnyValue(vm => vm.IsEnabled, e => e && Plugin.ConfigurationDialog != null)); - OpenHelp = ReactiveCommand.Create(ExecuteOpenHelp, this.WhenAnyValue(vm => vm.HasHelpPages)); RemoveSettings = ReactiveCommand.CreateFromTask(ExecuteRemoveSettings); Remove = ReactiveCommand.CreateFromTask(ExecuteRemove); InstallPrerequisites = ReactiveCommand.CreateFromTask(ExecuteInstallPrerequisites, this.WhenAnyValue(x => x.CanInstallPrerequisites)); @@ -77,14 +73,12 @@ public class PluginViewModel : ActivatableViewModelBase Plugin.Enabled -= OnPluginToggled; Plugin.Disabled -= OnPluginToggled; _settingsWindow?.Close(); - _helpWindow?.Close(); }).DisposeWith(d); }); } public ReactiveCommand? Reload { get; } public ReactiveCommand OpenSettings { get; } - public ReactiveCommand OpenHelp { get; } public ReactiveCommand RemoveSettings { get; } public ReactiveCommand Remove { get; } public ReactiveCommand InstallPrerequisites { get; } @@ -108,7 +102,6 @@ public class PluginViewModel : ActivatableViewModelBase public string Type => Plugin.GetType().BaseType?.Name ?? Plugin.GetType().Name; public bool IsEnabled => Plugin.IsEnabled; - public bool HasHelpPages => Plugin.HelpPages.Any(); public bool CanInstallPrerequisites { @@ -135,11 +128,7 @@ public class PluginViewModel : ActivatableViewModelBase } catch (Exception e) { - await Dispatcher.UIThread.InvokeAsync(() => _notificationService.CreateNotification() - .WithSeverity(NotificationSeverity.Error) - .WithMessage($"Failed to disable plugin {Plugin.Info.Name}\r\n{e.Message}") - .HavingButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder)) - .Show()); + await ShowUpdateEnableFailure(enable, e); } finally { @@ -174,11 +163,7 @@ public class PluginViewModel : ActivatableViewModelBase } catch (Exception e) { - await Dispatcher.UIThread.InvokeAsync(() => _notificationService.CreateNotification() - .WithSeverity(NotificationSeverity.Error) - .WithMessage($"Failed to enable plugin {Plugin.Info.Name}\r\n{e.Message}") - .HavingButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder)) - .Show()); + await ShowUpdateEnableFailure(enable, e); } finally { @@ -187,7 +172,6 @@ public class PluginViewModel : ActivatableViewModelBase } } - public void CheckPrerequisites() { CanInstallPrerequisites = false; @@ -228,27 +212,6 @@ public class PluginViewModel : ActivatableViewModelBase } } - private void ExecuteOpenHelp() - { - if (_helpWindow != null) - { - _helpWindow.WindowState = WindowState.Normal; - _helpWindow.Activate(); - return; - } - - try - { - _helpWindow = _windowService.ShowWindow(new PluginHelpWindowViewModel(Plugin, null)); - _helpWindow.Closed += (_, _) => _helpWindow = null; - } - catch (Exception e) - { - _windowService.ShowExceptionDialog("An exception occured while trying to show the plugin's help window", e); - throw; - } - } - private void ExecuteOpenPluginDirectory() { try @@ -334,6 +297,20 @@ public class PluginViewModel : ActivatableViewModelBase _windowService.ShowExceptionDialog("Welp, we couldn\'t open the logs folder for you", e); } } + + private async Task ShowUpdateEnableFailure(bool enable, Exception e) + { + string action = enable ? "enable" : "disable"; + ContentDialogBuilder builder = _windowService.CreateContentDialog() + .WithTitle($"Failed to {action} plugin {Plugin.Info.Name}") + .WithContent(e.Message) + .HavingPrimaryButton(b => b.WithText("View logs").WithCommand(ShowLogsFolder)); + // If available, add a secondary button pointing to the support page + if (Plugin.Info.HelpPage != null) + builder = builder.HavingSecondaryButton(b => b.WithText("Open support page").WithAction(() => Utilities.OpenUrl(Plugin.Info.HelpPage.ToString()))); + + await builder.ShowAsync(); + } private void OnPluginToggled(object? sender, EventArgs e) {