mirror of
https://github.com/Flow-Launcher/Flow.Launcher.git
synced 2026-03-11 08:54:32 +00:00
Merge pull request #3843 from Flow-Launcher/plugin_update_dialog
Add Plugin Update Dialog
This commit is contained in:
commit
7e68d9a49b
6 changed files with 266 additions and 20 deletions
|
|
@ -281,11 +281,12 @@ public static class PluginInstaller
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Updates the plugin to the latest version available from its source.
|
/// Updates the plugin to the latest version available from its source.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
/// <param name="updateAllPlugins">Action to execute when the user chooses to update all plugins.</param>
|
||||||
/// <param name="silentUpdate">If true, do not show any messages when there is no update available.</param>
|
/// <param name="silentUpdate">If true, do not show any messages when there is no update available.</param>
|
||||||
/// <param name="usePrimaryUrlOnly">If true, only use the primary URL for updates.</param>
|
/// <param name="usePrimaryUrlOnly">If true, only use the primary URL for updates.</param>
|
||||||
/// <param name="token">Cancellation token to cancel the update operation.</param>
|
/// <param name="token">Cancellation token to cancel the update operation.</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static async Task CheckForPluginUpdatesAsync(bool silentUpdate = true, bool usePrimaryUrlOnly = false, CancellationToken token = default)
|
public static async Task CheckForPluginUpdatesAsync(Action<List<PluginUpdateInfo>> updateAllPlugins, bool silentUpdate = true, bool usePrimaryUrlOnly = false, CancellationToken token = default)
|
||||||
{
|
{
|
||||||
// Update the plugin manifest
|
// Update the plugin manifest
|
||||||
await API.UpdatePluginManifestAsync(usePrimaryUrlOnly, token);
|
await API.UpdatePluginManifestAsync(usePrimaryUrlOnly, token);
|
||||||
|
|
@ -334,14 +335,20 @@ public static class PluginInstaller
|
||||||
API.GetTranslation("updateAllPluginsButtonContent"),
|
API.GetTranslation("updateAllPluginsButtonContent"),
|
||||||
() =>
|
() =>
|
||||||
{
|
{
|
||||||
UpdateAllPlugins(resultsForUpdate);
|
updateAllPlugins(resultsForUpdate);
|
||||||
},
|
},
|
||||||
string.Join(", ", resultsForUpdate.Select(x => x.PluginExistingMetadata.Name)));
|
string.Join(", ", resultsForUpdate.Select(x => x.PluginExistingMetadata.Name)));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void UpdateAllPlugins(IEnumerable<PluginUpdateInfo> resultsForUpdate)
|
/// <summary>
|
||||||
|
/// Updates all plugins that have available updates.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="resultsForUpdate"></param>
|
||||||
|
/// <param name="restart"></param>
|
||||||
|
public static async Task UpdateAllPluginsAsync(IEnumerable<PluginUpdateInfo> resultsForUpdate, bool restart)
|
||||||
{
|
{
|
||||||
_ = Task.WhenAll(resultsForUpdate.Select(async plugin =>
|
var anyPluginSuccess = false;
|
||||||
|
await Task.WhenAll(resultsForUpdate.Select(async plugin =>
|
||||||
{
|
{
|
||||||
var downloadToFilePath = Path.Combine(Path.GetTempPath(), $"{plugin.Name}-{plugin.NewVersion}.zip");
|
var downloadToFilePath = Path.Combine(Path.GetTempPath(), $"{plugin.Name}-{plugin.NewVersion}.zip");
|
||||||
|
|
||||||
|
|
@ -363,6 +370,8 @@ public static class PluginInstaller
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
anyPluginSuccess = true;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
|
|
@ -370,6 +379,19 @@ public static class PluginInstaller
|
||||||
API.ShowMsgError(API.GetTranslation("ErrorUpdatingPlugin"));
|
API.ShowMsgError(API.GetTranslation("ErrorUpdatingPlugin"));
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
if (!anyPluginSuccess) return;
|
||||||
|
|
||||||
|
if (restart)
|
||||||
|
{
|
||||||
|
API.RestartApp();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
API.ShowMsg(
|
||||||
|
API.GetTranslation("updatebtn"),
|
||||||
|
API.GetTranslation("PluginsUpdateSuccessNoRestart"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -445,16 +467,16 @@ public static class PluginInstaller
|
||||||
x.Metadata.Website.StartsWith(constructedUrlPart)
|
x.Metadata.Website.StartsWith(constructedUrlPart)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private record PluginUpdateInfo
|
|
||||||
{
|
public record PluginUpdateInfo
|
||||||
public string ID { get; init; }
|
{
|
||||||
public string Name { get; init; }
|
public string ID { get; init; }
|
||||||
public string Author { get; init; }
|
public string Name { get; init; }
|
||||||
public string CurrentVersion { get; init; }
|
public string Author { get; init; }
|
||||||
public string NewVersion { get; init; }
|
public string CurrentVersion { get; init; }
|
||||||
public string IcoPath { get; init; }
|
public string NewVersion { get; init; }
|
||||||
public PluginMetadata PluginExistingMetadata { get; init; }
|
public string IcoPath { get; init; }
|
||||||
public UserPlugin PluginNewUserPlugin { get; init; }
|
public PluginMetadata PluginExistingMetadata { get; init; }
|
||||||
}
|
public UserPlugin PluginNewUserPlugin { get; init; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -298,11 +298,25 @@ namespace Flow.Launcher
|
||||||
{
|
{
|
||||||
// check plugin updates every 5 hour
|
// check plugin updates every 5 hour
|
||||||
var timer = new PeriodicTimer(TimeSpan.FromHours(5));
|
var timer = new PeriodicTimer(TimeSpan.FromHours(5));
|
||||||
await PluginInstaller.CheckForPluginUpdatesAsync();
|
await PluginInstaller.CheckForPluginUpdatesAsync((plugins) =>
|
||||||
|
{
|
||||||
|
Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
var pluginUpdateWindow = new PluginUpdateWindow(plugins);
|
||||||
|
pluginUpdateWindow.ShowDialog();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
while (await timer.WaitForNextTickAsync())
|
while (await timer.WaitForNextTickAsync())
|
||||||
// check updates on startup
|
// check updates on startup
|
||||||
await PluginInstaller.CheckForPluginUpdatesAsync();
|
await PluginInstaller.CheckForPluginUpdatesAsync((plugins) =>
|
||||||
|
{
|
||||||
|
Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
var pluginUpdateWindow = new PluginUpdateWindow(plugins);
|
||||||
|
pluginUpdateWindow.ShowDialog();
|
||||||
|
});
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -237,8 +237,9 @@
|
||||||
<system:String x:Key="updateNoResultTitle">No update available</system:String>
|
<system:String x:Key="updateNoResultTitle">No update available</system:String>
|
||||||
<system:String x:Key="updateNoResultSubtitle">All plugins are up to date</system:String>
|
<system:String x:Key="updateNoResultSubtitle">All plugins are up to date</system:String>
|
||||||
<system:String x:Key="updateAllPluginsTitle">Plugin updates available</system:String>
|
<system:String x:Key="updateAllPluginsTitle">Plugin updates available</system:String>
|
||||||
<system:String x:Key="updateAllPluginsButtonContent">Update all plugins</system:String>
|
<system:String x:Key="updateAllPluginsButtonContent">Update plugins</system:String>
|
||||||
<system:String x:Key="checkPluginUpdatesTooltip">Check plugin updates</system:String>
|
<system:String x:Key="checkPluginUpdatesTooltip">Check plugin updates</system:String>
|
||||||
|
<system:String x:Key="PluginsUpdateSuccessNoRestart">Plugins are successfully updated. Please restart Flow.</system:String>
|
||||||
|
|
||||||
<!-- Setting Theme -->
|
<!-- Setting Theme -->
|
||||||
<system:String x:Key="theme">Theme</system:String>
|
<system:String x:Key="theme">Theme</system:String>
|
||||||
|
|
@ -563,6 +564,11 @@
|
||||||
<system:String x:Key="update_flowlauncher_update_files">Update files</system:String>
|
<system:String x:Key="update_flowlauncher_update_files">Update files</system:String>
|
||||||
<system:String x:Key="update_flowlauncher_update_update_description">Update description</system:String>
|
<system:String x:Key="update_flowlauncher_update_update_description">Update description</system:String>
|
||||||
|
|
||||||
|
<!-- Plugin Update Window -->
|
||||||
|
<system:String x:Key="restartAfterUpdating">Restart Flow Launcher after updating plugins</system:String>
|
||||||
|
<system:String x:Key="updatePluginCheckboxContent">{0}: Update from v{1} to v{2}</system:String>
|
||||||
|
<system:String x:Key="updatePluginNoSelected">No plugin selected</system:String>
|
||||||
|
|
||||||
<!-- Welcome Window -->
|
<!-- Welcome Window -->
|
||||||
<system:String x:Key="Skip">Skip</system:String>
|
<system:String x:Key="Skip">Skip</system:String>
|
||||||
<system:String x:Key="Welcome_Page1_Title">Welcome to Flow Launcher</system:String>
|
<system:String x:Key="Welcome_Page1_Title">Welcome to Flow Launcher</system:String>
|
||||||
|
|
|
||||||
111
Flow.Launcher/PluginUpdateWindow.xaml
Normal file
111
Flow.Launcher/PluginUpdateWindow.xaml
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
<Window
|
||||||
|
x:Class="Flow.Launcher.PluginUpdateWindow"
|
||||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:flowlauncher="clr-namespace:Flow.Launcher"
|
||||||
|
Title="{DynamicResource updateAllPluginsButtonContent}"
|
||||||
|
Width="530"
|
||||||
|
Background="{DynamicResource PopuBGColor}"
|
||||||
|
DataContext="{Binding RelativeSource={RelativeSource Self}}"
|
||||||
|
Foreground="{DynamicResource PopupTextColor}"
|
||||||
|
Icon="Images\app.png"
|
||||||
|
ResizeMode="NoResize"
|
||||||
|
SizeToContent="Height"
|
||||||
|
Topmost="True"
|
||||||
|
WindowStartupLocation="CenterScreen">
|
||||||
|
<WindowChrome.WindowChrome>
|
||||||
|
<WindowChrome CaptionHeight="32" ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
|
||||||
|
</WindowChrome.WindowChrome>
|
||||||
|
<Window.InputBindings>
|
||||||
|
<KeyBinding Key="Escape" Command="Close" />
|
||||||
|
</Window.InputBindings>
|
||||||
|
<Window.CommandBindings>
|
||||||
|
<CommandBinding Command="Close" Executed="cmdEsc_OnPress" />
|
||||||
|
</Window.CommandBindings>
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition />
|
||||||
|
<RowDefinition Height="80" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<StackPanel Grid.Row="0">
|
||||||
|
<StackPanel>
|
||||||
|
<Grid>
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Button
|
||||||
|
Grid.Column="1"
|
||||||
|
Click="BtnCancel_OnClick"
|
||||||
|
Style="{StaticResource TitleBarCloseButtonStyle}">
|
||||||
|
<Path
|
||||||
|
Width="46"
|
||||||
|
Height="32"
|
||||||
|
Data="M 18,11 27,20 M 18,20 27,11"
|
||||||
|
Stroke="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
|
||||||
|
StrokeThickness="1">
|
||||||
|
<Path.Style>
|
||||||
|
<Style TargetType="Path">
|
||||||
|
<Style.Triggers>
|
||||||
|
<DataTrigger Binding="{Binding Path=IsActive, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Value="False">
|
||||||
|
<Setter Property="Opacity" Value="0.5" />
|
||||||
|
</DataTrigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
</Path.Style>
|
||||||
|
</Path>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</StackPanel>
|
||||||
|
<StackPanel Margin="26 0 26 0">
|
||||||
|
<TextBlock
|
||||||
|
Margin="0 0 0 12"
|
||||||
|
FontSize="20"
|
||||||
|
FontWeight="SemiBold"
|
||||||
|
Text="{DynamicResource updateAllPluginsButtonContent}"
|
||||||
|
TextAlignment="Left" />
|
||||||
|
|
||||||
|
<ScrollViewer
|
||||||
|
MaxHeight="300"
|
||||||
|
Margin="0 5 0 5"
|
||||||
|
HorizontalScrollBarVisibility="Disabled"
|
||||||
|
VerticalScrollBarVisibility="Auto">
|
||||||
|
<StackPanel x:Name="UpdatePluginStackPanel" />
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<Rectangle
|
||||||
|
Height="1"
|
||||||
|
Margin="0 5 0 5"
|
||||||
|
Fill="{DynamicResource SeparatorForeground}" />
|
||||||
|
|
||||||
|
<CheckBox
|
||||||
|
Margin="0 10 0 10"
|
||||||
|
Content="{DynamicResource restartAfterUpdating}"
|
||||||
|
IsChecked="{Binding Restart, Mode=TwoWay}" />
|
||||||
|
</StackPanel>
|
||||||
|
</StackPanel>
|
||||||
|
<Border
|
||||||
|
Grid.Row="1"
|
||||||
|
Margin="0 14 0 0"
|
||||||
|
Background="{DynamicResource PopupButtonAreaBGColor}"
|
||||||
|
BorderBrush="{DynamicResource PopupButtonAreaBorderColor}"
|
||||||
|
BorderThickness="0 1 0 0">
|
||||||
|
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
|
||||||
|
<Button
|
||||||
|
x:Name="btnCancel"
|
||||||
|
MinWidth="140"
|
||||||
|
Margin="10 0 5 0"
|
||||||
|
Click="BtnCancel_OnClick"
|
||||||
|
Content="{DynamicResource cancel}" />
|
||||||
|
<Button
|
||||||
|
x:Name="btnUpdate"
|
||||||
|
MinWidth="140"
|
||||||
|
Margin="5 0 10 0"
|
||||||
|
Click="btnUpdate_OnClick"
|
||||||
|
Content="{DynamicResource update}"
|
||||||
|
Style="{StaticResource AccentButtonStyle}" />
|
||||||
|
</StackPanel>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</Window>
|
||||||
85
Flow.Launcher/PluginUpdateWindow.xaml.cs
Normal file
85
Flow.Launcher/PluginUpdateWindow.xaml.cs
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Controls;
|
||||||
|
using System.Windows.Input;
|
||||||
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
|
using Flow.Launcher.Core.Plugin;
|
||||||
|
using Flow.Launcher.Infrastructure.UserSettings;
|
||||||
|
|
||||||
|
namespace Flow.Launcher
|
||||||
|
{
|
||||||
|
public partial class PluginUpdateWindow : Window
|
||||||
|
{
|
||||||
|
public List<PluginUpdateInfo> Plugins { get; set; } = new();
|
||||||
|
public bool Restart { get; set; }
|
||||||
|
|
||||||
|
private readonly Settings _settings = Ioc.Default.GetRequiredService<Settings>();
|
||||||
|
|
||||||
|
public PluginUpdateWindow(List<PluginUpdateInfo> allPlugins)
|
||||||
|
{
|
||||||
|
Restart = _settings.AutoRestartAfterChanging;
|
||||||
|
InitializeComponent();
|
||||||
|
foreach (var plugin in allPlugins)
|
||||||
|
{
|
||||||
|
var checkBox = new CheckBox
|
||||||
|
{
|
||||||
|
Content = string.Format(App.API.GetTranslation("updatePluginCheckboxContent"), plugin.Name, plugin.CurrentVersion, plugin.NewVersion),
|
||||||
|
IsChecked = true,
|
||||||
|
Margin = new Thickness(0, 5, 0, 5),
|
||||||
|
Tag = plugin,
|
||||||
|
VerticalAlignment = VerticalAlignment.Center,
|
||||||
|
};
|
||||||
|
checkBox.Checked += CheckBox_Checked;
|
||||||
|
checkBox.Unchecked += CheckBox_Unchecked;
|
||||||
|
UpdatePluginStackPanel.Children.Add(checkBox);
|
||||||
|
Plugins.Add(plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckBox_Checked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is not CheckBox cb) return;
|
||||||
|
if (cb.Tag is not PluginUpdateInfo plugin) return;
|
||||||
|
if (!Plugins.Contains(plugin))
|
||||||
|
{
|
||||||
|
Plugins.Add(plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is not CheckBox cb) return;
|
||||||
|
if (cb.Tag is not PluginUpdateInfo plugin) return;
|
||||||
|
if (Plugins.Contains(plugin))
|
||||||
|
{
|
||||||
|
Plugins.Remove(plugin);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void BtnCancel_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
DialogResult = false;
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void btnUpdate_OnClick(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
if (Plugins.Count == 0)
|
||||||
|
{
|
||||||
|
App.API.ShowMsgBox(App.API.GetTranslation("updatePluginNoSelected"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_ = PluginInstaller.UpdateAllPluginsAsync(Plugins, Restart);
|
||||||
|
|
||||||
|
DialogResult = true;
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cmdEsc_OnPress(object sender, ExecutedRoutedEventArgs e)
|
||||||
|
{
|
||||||
|
DialogResult = false;
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
using System.Windows;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using Flow.Launcher.Core.Plugin;
|
using Flow.Launcher.Core.Plugin;
|
||||||
using Flow.Launcher.Plugin;
|
using Flow.Launcher.Plugin;
|
||||||
|
|
@ -112,7 +113,14 @@ public partial class SettingsPanePluginStoreViewModel : BaseModel
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async Task CheckPluginUpdatesAsync()
|
private async Task CheckPluginUpdatesAsync()
|
||||||
{
|
{
|
||||||
await PluginInstaller.CheckForPluginUpdatesAsync(silentUpdate: false);
|
await PluginInstaller.CheckForPluginUpdatesAsync((plugins) =>
|
||||||
|
{
|
||||||
|
Application.Current.Dispatcher.Invoke(() =>
|
||||||
|
{
|
||||||
|
var pluginUpdateWindow = new PluginUpdateWindow(plugins);
|
||||||
|
pluginUpdateWindow.ShowDialog();
|
||||||
|
});
|
||||||
|
}, silentUpdate: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetFileFromDialog(string title, string filter = "")
|
private static string GetFileFromDialog(string title, string filter = "")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue