Skip to content

Commit 7e68d9a

Browse files
authored
Merge pull request #3843 from Flow-Launcher/plugin_update_dialog
Add Plugin Update Dialog
2 parents d400cda + c4090bb commit 7e68d9a

File tree

6 files changed

+265
-19
lines changed

6 files changed

+265
-19
lines changed

Flow.Launcher.Core/Plugin/PluginInstaller.cs

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -281,11 +281,12 @@ await DownloadFileAsync(
281281
/// <summary>
282282
/// Updates the plugin to the latest version available from its source.
283283
/// </summary>
284+
/// <param name="updateAllPlugins">Action to execute when the user chooses to update all plugins.</param>
284285
/// <param name="silentUpdate">If true, do not show any messages when there is no update available.</param>
285286
/// <param name="usePrimaryUrlOnly">If true, only use the primary URL for updates.</param>
286287
/// <param name="token">Cancellation token to cancel the update operation.</param>
287288
/// <returns></returns>
288-
public static async Task CheckForPluginUpdatesAsync(bool silentUpdate = true, bool usePrimaryUrlOnly = false, CancellationToken token = default)
289+
public static async Task CheckForPluginUpdatesAsync(Action<List<PluginUpdateInfo>> updateAllPlugins, bool silentUpdate = true, bool usePrimaryUrlOnly = false, CancellationToken token = default)
289290
{
290291
// Update the plugin manifest
291292
await API.UpdatePluginManifestAsync(usePrimaryUrlOnly, token);
@@ -334,14 +335,20 @@ where string.Compare(existingPlugin.Metadata.Version, pluginUpdateSource.Version
334335
API.GetTranslation("updateAllPluginsButtonContent"),
335336
() =>
336337
{
337-
UpdateAllPlugins(resultsForUpdate);
338+
updateAllPlugins(resultsForUpdate);
338339
},
339340
string.Join(", ", resultsForUpdate.Select(x => x.PluginExistingMetadata.Name)));
340341
}
341342

342-
private static void UpdateAllPlugins(IEnumerable<PluginUpdateInfo> resultsForUpdate)
343+
/// <summary>
344+
/// Updates all plugins that have available updates.
345+
/// </summary>
346+
/// <param name="resultsForUpdate"></param>
347+
/// <param name="restart"></param>
348+
public static async Task UpdateAllPluginsAsync(IEnumerable<PluginUpdateInfo> resultsForUpdate, bool restart)
343349
{
344-
_ = Task.WhenAll(resultsForUpdate.Select(async plugin =>
350+
var anyPluginSuccess = false;
351+
await Task.WhenAll(resultsForUpdate.Select(async plugin =>
345352
{
346353
var downloadToFilePath = Path.Combine(Path.GetTempPath(), $"{plugin.Name}-{plugin.NewVersion}.zip");
347354

@@ -363,13 +370,28 @@ await DownloadFileAsync(
363370
{
364371
return;
365372
}
373+
374+
anyPluginSuccess = true;
366375
}
367376
catch (Exception e)
368377
{
369378
API.LogException(ClassName, "Failed to update plugin", e);
370379
API.ShowMsgError(API.GetTranslation("ErrorUpdatingPlugin"));
371380
}
372381
}));
382+
383+
if (!anyPluginSuccess) return;
384+
385+
if (restart)
386+
{
387+
API.RestartApp();
388+
}
389+
else
390+
{
391+
API.ShowMsg(
392+
API.GetTranslation("updatebtn"),
393+
API.GetTranslation("PluginsUpdateSuccessNoRestart"));
394+
}
373395
}
374396

375397
/// <summary>
@@ -445,16 +467,16 @@ private static bool InstallSourceKnown(string url)
445467
x.Metadata.Website.StartsWith(constructedUrlPart)
446468
);
447469
}
470+
}
448471

449-
private record PluginUpdateInfo
450-
{
451-
public string ID { get; init; }
452-
public string Name { get; init; }
453-
public string Author { get; init; }
454-
public string CurrentVersion { get; init; }
455-
public string NewVersion { get; init; }
456-
public string IcoPath { get; init; }
457-
public PluginMetadata PluginExistingMetadata { get; init; }
458-
public UserPlugin PluginNewUserPlugin { get; init; }
459-
}
472+
public record PluginUpdateInfo
473+
{
474+
public string ID { get; init; }
475+
public string Name { get; init; }
476+
public string Author { get; init; }
477+
public string CurrentVersion { get; init; }
478+
public string NewVersion { get; init; }
479+
public string IcoPath { get; init; }
480+
public PluginMetadata PluginExistingMetadata { get; init; }
481+
public UserPlugin PluginNewUserPlugin { get; init; }
460482
}

Flow.Launcher/App.xaml.cs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -298,11 +298,25 @@ private static void AutoPluginUpdates()
298298
{
299299
// check plugin updates every 5 hour
300300
var timer = new PeriodicTimer(TimeSpan.FromHours(5));
301-
await PluginInstaller.CheckForPluginUpdatesAsync();
301+
await PluginInstaller.CheckForPluginUpdatesAsync((plugins) =>
302+
{
303+
Current.Dispatcher.Invoke(() =>
304+
{
305+
var pluginUpdateWindow = new PluginUpdateWindow(plugins);
306+
pluginUpdateWindow.ShowDialog();
307+
});
308+
});
302309

303310
while (await timer.WaitForNextTickAsync())
304311
// check updates on startup
305-
await PluginInstaller.CheckForPluginUpdatesAsync();
312+
await PluginInstaller.CheckForPluginUpdatesAsync((plugins) =>
313+
{
314+
Current.Dispatcher.Invoke(() =>
315+
{
316+
var pluginUpdateWindow = new PluginUpdateWindow(plugins);
317+
pluginUpdateWindow.ShowDialog();
318+
});
319+
});
306320
}
307321
});
308322
}

Flow.Launcher/Languages/en.xaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,9 @@
237237
<system:String x:Key="updateNoResultTitle">No update available</system:String>
238238
<system:String x:Key="updateNoResultSubtitle">All plugins are up to date</system:String>
239239
<system:String x:Key="updateAllPluginsTitle">Plugin updates available</system:String>
240-
<system:String x:Key="updateAllPluginsButtonContent">Update all plugins</system:String>
240+
<system:String x:Key="updateAllPluginsButtonContent">Update plugins</system:String>
241241
<system:String x:Key="checkPluginUpdatesTooltip">Check plugin updates</system:String>
242+
<system:String x:Key="PluginsUpdateSuccessNoRestart">Plugins are successfully updated. Please restart Flow.</system:String>
242243

243244
<!-- Setting Theme -->
244245
<system:String x:Key="theme">Theme</system:String>
@@ -563,6 +564,11 @@
563564
<system:String x:Key="update_flowlauncher_update_files">Update files</system:String>
564565
<system:String x:Key="update_flowlauncher_update_update_description">Update description</system:String>
565566

567+
<!-- Plugin Update Window -->
568+
<system:String x:Key="restartAfterUpdating">Restart Flow Launcher after updating plugins</system:String>
569+
<system:String x:Key="updatePluginCheckboxContent">{0}: Update from v{1} to v{2}</system:String>
570+
<system:String x:Key="updatePluginNoSelected">No plugin selected</system:String>
571+
566572
<!-- Welcome Window -->
567573
<system:String x:Key="Skip">Skip</system:String>
568574
<system:String x:Key="Welcome_Page1_Title">Welcome to Flow Launcher</system:String>

Flow.Launcher/PluginUpdateWindow.xaml

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
<Window
2+
x:Class="Flow.Launcher.PluginUpdateWindow"
3+
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
4+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
5+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
6+
xmlns:flowlauncher="clr-namespace:Flow.Launcher"
7+
Title="{DynamicResource updateAllPluginsButtonContent}"
8+
Width="530"
9+
Background="{DynamicResource PopuBGColor}"
10+
DataContext="{Binding RelativeSource={RelativeSource Self}}"
11+
Foreground="{DynamicResource PopupTextColor}"
12+
Icon="Images\app.png"
13+
ResizeMode="NoResize"
14+
SizeToContent="Height"
15+
Topmost="True"
16+
WindowStartupLocation="CenterScreen">
17+
<WindowChrome.WindowChrome>
18+
<WindowChrome CaptionHeight="32" ResizeBorderThickness="{x:Static SystemParameters.WindowResizeBorderThickness}" />
19+
</WindowChrome.WindowChrome>
20+
<Window.InputBindings>
21+
<KeyBinding Key="Escape" Command="Close" />
22+
</Window.InputBindings>
23+
<Window.CommandBindings>
24+
<CommandBinding Command="Close" Executed="cmdEsc_OnPress" />
25+
</Window.CommandBindings>
26+
<Grid>
27+
<Grid.RowDefinitions>
28+
<RowDefinition />
29+
<RowDefinition Height="80" />
30+
</Grid.RowDefinitions>
31+
<StackPanel Grid.Row="0">
32+
<StackPanel>
33+
<Grid>
34+
<Grid.ColumnDefinitions>
35+
<ColumnDefinition Width="*" />
36+
<ColumnDefinition Width="Auto" />
37+
</Grid.ColumnDefinitions>
38+
<Button
39+
Grid.Column="1"
40+
Click="BtnCancel_OnClick"
41+
Style="{StaticResource TitleBarCloseButtonStyle}">
42+
<Path
43+
Width="46"
44+
Height="32"
45+
Data="M 18,11 27,20 M 18,20 27,11"
46+
Stroke="{Binding Path=Foreground, RelativeSource={RelativeSource AncestorType={x:Type Button}}}"
47+
StrokeThickness="1">
48+
<Path.Style>
49+
<Style TargetType="Path">
50+
<Style.Triggers>
51+
<DataTrigger Binding="{Binding Path=IsActive, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" Value="False">
52+
<Setter Property="Opacity" Value="0.5" />
53+
</DataTrigger>
54+
</Style.Triggers>
55+
</Style>
56+
</Path.Style>
57+
</Path>
58+
</Button>
59+
</Grid>
60+
</StackPanel>
61+
<StackPanel Margin="26 0 26 0">
62+
<TextBlock
63+
Margin="0 0 0 12"
64+
FontSize="20"
65+
FontWeight="SemiBold"
66+
Text="{DynamicResource updateAllPluginsButtonContent}"
67+
TextAlignment="Left" />
68+
69+
<ScrollViewer
70+
MaxHeight="300"
71+
Margin="0 5 0 5"
72+
HorizontalScrollBarVisibility="Disabled"
73+
VerticalScrollBarVisibility="Auto">
74+
<StackPanel x:Name="UpdatePluginStackPanel" />
75+
</ScrollViewer>
76+
77+
<Rectangle
78+
Height="1"
79+
Margin="0 5 0 5"
80+
Fill="{DynamicResource SeparatorForeground}" />
81+
82+
<CheckBox
83+
Margin="0 10 0 10"
84+
Content="{DynamicResource restartAfterUpdating}"
85+
IsChecked="{Binding Restart, Mode=TwoWay}" />
86+
</StackPanel>
87+
</StackPanel>
88+
<Border
89+
Grid.Row="1"
90+
Margin="0 14 0 0"
91+
Background="{DynamicResource PopupButtonAreaBGColor}"
92+
BorderBrush="{DynamicResource PopupButtonAreaBorderColor}"
93+
BorderThickness="0 1 0 0">
94+
<StackPanel HorizontalAlignment="Center" Orientation="Horizontal">
95+
<Button
96+
x:Name="btnCancel"
97+
MinWidth="140"
98+
Margin="10 0 5 0"
99+
Click="BtnCancel_OnClick"
100+
Content="{DynamicResource cancel}" />
101+
<Button
102+
x:Name="btnUpdate"
103+
MinWidth="140"
104+
Margin="5 0 10 0"
105+
Click="btnUpdate_OnClick"
106+
Content="{DynamicResource update}"
107+
Style="{StaticResource AccentButtonStyle}" />
108+
</StackPanel>
109+
</Border>
110+
</Grid>
111+
</Window>
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
using System.Collections.Generic;
2+
using System.Windows;
3+
using System.Windows.Controls;
4+
using System.Windows.Input;
5+
using CommunityToolkit.Mvvm.DependencyInjection;
6+
using Flow.Launcher.Core.Plugin;
7+
using Flow.Launcher.Infrastructure.UserSettings;
8+
9+
namespace Flow.Launcher
10+
{
11+
public partial class PluginUpdateWindow : Window
12+
{
13+
public List<PluginUpdateInfo> Plugins { get; set; } = new();
14+
public bool Restart { get; set; }
15+
16+
private readonly Settings _settings = Ioc.Default.GetRequiredService<Settings>();
17+
18+
public PluginUpdateWindow(List<PluginUpdateInfo> allPlugins)
19+
{
20+
Restart = _settings.AutoRestartAfterChanging;
21+
InitializeComponent();
22+
foreach (var plugin in allPlugins)
23+
{
24+
var checkBox = new CheckBox
25+
{
26+
Content = string.Format(App.API.GetTranslation("updatePluginCheckboxContent"), plugin.Name, plugin.CurrentVersion, plugin.NewVersion),
27+
IsChecked = true,
28+
Margin = new Thickness(0, 5, 0, 5),
29+
Tag = plugin,
30+
VerticalAlignment = VerticalAlignment.Center,
31+
};
32+
checkBox.Checked += CheckBox_Checked;
33+
checkBox.Unchecked += CheckBox_Unchecked;
34+
UpdatePluginStackPanel.Children.Add(checkBox);
35+
Plugins.Add(plugin);
36+
}
37+
}
38+
39+
private void CheckBox_Checked(object sender, RoutedEventArgs e)
40+
{
41+
if (sender is not CheckBox cb) return;
42+
if (cb.Tag is not PluginUpdateInfo plugin) return;
43+
if (!Plugins.Contains(plugin))
44+
{
45+
Plugins.Add(plugin);
46+
}
47+
}
48+
49+
private void CheckBox_Unchecked(object sender, RoutedEventArgs e)
50+
{
51+
if (sender is not CheckBox cb) return;
52+
if (cb.Tag is not PluginUpdateInfo plugin) return;
53+
if (Plugins.Contains(plugin))
54+
{
55+
Plugins.Remove(plugin);
56+
}
57+
}
58+
59+
private void BtnCancel_OnClick(object sender, RoutedEventArgs e)
60+
{
61+
DialogResult = false;
62+
Close();
63+
}
64+
65+
private void btnUpdate_OnClick(object sender, RoutedEventArgs e)
66+
{
67+
if (Plugins.Count == 0)
68+
{
69+
App.API.ShowMsgBox(App.API.GetTranslation("updatePluginNoSelected"));
70+
return;
71+
}
72+
73+
_ = PluginInstaller.UpdateAllPluginsAsync(Plugins, Restart);
74+
75+
DialogResult = true;
76+
Close();
77+
}
78+
79+
private void cmdEsc_OnPress(object sender, ExecutedRoutedEventArgs e)
80+
{
81+
DialogResult = false;
82+
Close();
83+
}
84+
}
85+
}

Flow.Launcher/SettingPages/ViewModels/SettingsPanePluginStoreViewModel.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.Generic;
33
using System.Linq;
44
using System.Threading.Tasks;
5+
using System.Windows;
56
using CommunityToolkit.Mvvm.Input;
67
using Flow.Launcher.Core.Plugin;
78
using Flow.Launcher.Plugin;
@@ -112,7 +113,14 @@ private async Task InstallPluginAsync()
112113
[RelayCommand]
113114
private async Task CheckPluginUpdatesAsync()
114115
{
115-
await PluginInstaller.CheckForPluginUpdatesAsync(silentUpdate: false);
116+
await PluginInstaller.CheckForPluginUpdatesAsync((plugins) =>
117+
{
118+
Application.Current.Dispatcher.Invoke(() =>
119+
{
120+
var pluginUpdateWindow = new PluginUpdateWindow(plugins);
121+
pluginUpdateWindow.ShowDialog();
122+
});
123+
}, silentUpdate: false);
116124
}
117125

118126
private static string GetFileFromDialog(string title, string filter = "")

0 commit comments

Comments
 (0)