Skip to content

Commit ccd1591

Browse files
authored
Show a flyout with a warning on a package downgrade (#27)
* Show a flyout with a warning on a package downgrade * Update doc
1 parent 4eb3b13 commit ccd1591

File tree

8 files changed

+145
-33
lines changed

8 files changed

+145
-33
lines changed

Numbers/Program.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
using Shared.Services;
1010
using Shared.Services.Implementations;
1111
using Shared.Services.Implementations.WinUI;
12-
using Shared.Services.Implementations.WinuiUI;
1312
using Splat;
1413
using Splat.Microsoft.Extensions.DependencyInjection;
1514

PackageInstaller.Core/ModelViews/ActionModelView.cs

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,43 @@ namespace PackageInstaller.Core.ModelViews;
55

66
public class ActionModelView : ReactiveObject
77
{
8-
public ActionModelView(Func<Task> action, string title, string? warningText = null)
8+
public ActionModelView(
9+
Func<ActionModelView, Task> action,
10+
string title,
11+
string? warningText = null,
12+
string? tooltip = null
13+
)
914
{
1015
Title = title;
1116
WarningText = warningText;
1217
ActionCommand = ReactiveCommand.CreateFromTask(action);
18+
ToolTip = tooltip;
1319
}
1420

15-
public ActionModelView(Action action, string title, string? warningText = null)
21+
public ActionModelView(
22+
Action<ActionModelView> action,
23+
string title,
24+
string? warningText = null,
25+
string? tooltip = null
26+
)
1627
{
1728
Title = title;
1829
WarningText = warningText;
1930
ActionCommand = ReactiveCommand.CreateFromTask(
20-
() =>
31+
(ActionModelView vm) =>
2132
{
22-
action();
33+
action(vm);
2334
return Task.CompletedTask;
2435
}
2536
);
37+
ToolTip = tooltip;
2638
}
2739

2840
public string Title { get; }
2941

3042
public string? WarningText { get; }
3143

32-
public ReactiveCommand<Unit, Unit> ActionCommand { get; }
44+
public string? ToolTip { get; }
45+
46+
public ReactiveCommand<ActionModelView, Unit> ActionCommand { get; }
3347
}

PackageInstaller.Core/ModelViews/PackageActionsViewModel.cs

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
using System.Collections.ObjectModel;
33
using System.Reactive;
44
using System.Reactive.Linq;
5+
using System.Reactive.Threading.Tasks;
56
using DynamicData;
67
using Microsoft.Extensions.Hosting;
78
using PackageInstaller.Core.Services;
@@ -48,6 +49,7 @@ public class PackageActionsViewModel : ReactiveObject, IViewModel, INavigable
4849
DistributionModelView? _selectedWslDistribution;
4950
private IThreadHelpers _threadHelpers;
5051
private readonly IApplicationLifeCycle _lifeCycle;
52+
private readonly Interaction<string, bool> _actionConfirmationDialogInteraction;
5153

5254
#pragma warning disable MA0051 // Method is too long
5355
public PackageActionsViewModel(
@@ -71,6 +73,8 @@ IApplicationLifeCycle lifeCycle
7173
_threadHelpers = threadHelpers;
7274
_lifeCycle = lifeCycle;
7375

76+
_actionConfirmationDialogInteraction = new Interaction<string, bool>();
77+
7478
_progressStatusMessage = String.Empty;
7579
ProgressStatusMessage = String.Empty;
7680
_notifications = ImmutableList<NotificationModelView>.Empty;
@@ -111,35 +115,43 @@ IApplicationLifeCycle lifeCycle
111115
.SelectMany(OnSelectedDistributionChangedAsync)
112116
.Subscribe();
113117

114-
_launchActionMv = new ActionModelView(BuildCommandFunction(PackageAction.Launch), "Launch");
118+
_launchActionMv = new ActionModelView(
119+
BuildCommandFunction(PackageAction.Launch),
120+
"Launch",
121+
tooltip: "Launch the application"
122+
);
115123
_installActionMv = new ActionModelView(
116124
BuildCommandFunction(PackageAction.Install),
117-
"Install"
125+
"Install",
126+
tooltip: "Install this application in the selected distribution"
118127
);
119128
_uninstallActionMv = new ActionModelView(
120129
BuildCommandFunction(PackageAction.Uninstall),
121-
"Uninstall"
130+
"Uninstall",
131+
tooltip: "Uninstall this application in the selected distribution"
122132
);
123133
_reinstallActionMv = new ActionModelView(
124134
BuildCommandFunction(PackageAction.Install),
125-
"Reinstall"
135+
"Reinstall",
136+
tooltip: "Reinstall this application in the selected distribution"
126137
);
127138
_upgradeActionMv = new ActionModelView(
128139
BuildCommandFunction(PackageAction.Upgrade),
129-
"Upgrade"
140+
"Upgrade",
141+
tooltip: "Upgrade this application in the selected distribution"
130142
);
131143
_downgradeActionMv = new ActionModelView(
132144
BuildCommandFunction(PackageAction.Downgrade),
133145
"Downgrade",
134146
"This action may not do any dependency checking on downgrades "
135147
+ "and therefore will not warn you if the downgrade breaks the dependency "
136-
+ "of some other package.This can have serious side effects, downgrading "
148+
+ "of some other package. This can have serious side effects, downgrading "
137149
+ "essential system components can even make your whole system unusable. "
138150
+ "Use with care."
139151
);
140152
_doNothingActionMv = new ActionModelView(
141-
() => _applicationLifetime.StopApplication(),
142-
"Do nothing and exit"
153+
(_) => _applicationLifetime.StopApplication(),
154+
"Do nothing and close"
143155
);
144156

145157
_primaryAction = this.WhenAnyValue((mv) => mv.PackageInstallationStatus)
@@ -251,6 +263,9 @@ public IObservable<Unit> WhenNavigatingTo(INavigationParameter parameter)
251263

252264
public string Id { get; } = nameof(PackageActionsViewModel);
253265

266+
public Interaction<string, bool> ActionConfirmationDialogInteraction =>
267+
_actionConfirmationDialogInteraction;
268+
254269
private IImmutableList<ActionModelView> ChooseSecondaryActions(
255270
IPlatformDependentPackageManager.PackageInstallationStatus? arg
256271
)
@@ -300,10 +315,22 @@ private IImmutableList<ActionModelView> ChooseSecondaryActions(
300315
}
301316
}
302317

303-
private Action BuildCommandFunction(PackageAction action)
318+
private Func<ActionModelView, Task> BuildCommandFunction(PackageAction action)
304319
{
305-
return () =>
320+
return async (ActionModelView avm) =>
306321
{
322+
if (avm.WarningText != null)
323+
{
324+
var confirmedByUser = await _actionConfirmationDialogInteraction
325+
.Handle(avm.WarningText)
326+
.ToTask()
327+
.ConfigureAwait(true);
328+
if (!confirmedByUser)
329+
{
330+
return;
331+
}
332+
}
333+
307334
var navParms = new ActionExecutionViewModel.NavigationParameter()
308335
{
309336
Distribution = SelectedWslDistribution!,
@@ -408,7 +435,7 @@ private async Task ProcessPackageAsync()
408435

409436
if (SelectedWslDistribution == null && _distroSourceList.Count > 0)
410437
{
411-
await Task.Delay(TimeSpan.FromMilliseconds(0)).ConfigureAwait(true);
438+
await Task.Delay(TimeSpan.FromMilliseconds(10)).ConfigureAwait(true);
412439

413440
SelectedWslDistribution = _distroSourceList.Items.First();
414441
}

PackageInstaller/Pages/PackageActions.xaml

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -125,11 +125,13 @@
125125
<controls:ColumnDefinition Width="*" />
126126
<controls:ColumnDefinition Width="Auto" />
127127
</controls:Grid.ColumnDefinitions>
128-
<controls:Grid
129-
controls:Grid.Column="0"
130-
attached:FrameworkElementExtensions.Cursor="Hand"
131-
>
132-
<controls1:NotificationIcon HorizontalAlignment="Left" VerticalAlignment="Top" Padding="5" x:Name="NotificationIconGlyph" />
128+
<controls:Grid controls:Grid.Column="0" attached:FrameworkElementExtensions.Cursor="Hand">
129+
<controls1:NotificationIcon
130+
HorizontalAlignment="Left"
131+
VerticalAlignment="Top"
132+
Padding="5"
133+
x:Name="NotificationIconGlyph"
134+
/>
133135
</controls:Grid>
134136
<controls:StackPanel controls:Grid.Column="1" Orientation="Vertical" Spacing="4">
135137
<controls:StackPanel HorizontalAlignment="Right" Orientation="Horizontal" Spacing="8">
@@ -147,6 +149,19 @@
147149
</controls:ComboBox.ItemTemplate>
148150
</controls:ComboBox>
149151
<controls:SplitButton x:Name="ActionButton">
152+
<controls:SplitButton.Resources>
153+
<controls:Flyout x:Name="ActionFlyout">
154+
<controls:StackPanel Spacing="15" Orientation="Vertical" HorizontalAlignment="Stretch">
155+
<TextBlock
156+
HorizontalAlignment="Stretch"
157+
MaxWidth="250"
158+
TextWrapping="WrapWholeWords"
159+
x:Name="ActionFlyoutText"
160+
/>
161+
<controls:Button x:Name="ActionFlyoutButton" Content="Acknowledged and Continue" />
162+
</controls:StackPanel>
163+
</controls:Flyout>
164+
</controls:SplitButton.Resources>
150165
<controls:StackPanel Orientation="Horizontal">
151166
<controls:FontIcon
152167
x:Name="PrimaryActionButtonIcon"

PackageInstaller/Pages/PackageActions.xaml.cs

Lines changed: 65 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@
33
using System.Reactive;
44
using System.Reactive.Disposables;
55
using System.Reactive.Linq;
6+
using System.Threading.Tasks;
67
using DynamicData;
78
using DynamicData.Binding;
89
using Microsoft.UI.Xaml;
910
using Microsoft.UI.Xaml.Controls;
11+
using Microsoft.UI.Xaml.Controls.Primitives;
1012
using Microsoft.UI.Xaml.Input;
1113
using PackageInstaller.Core.ModelViews;
1214
using PackageInstaller.Core.Services;
1315
using ReactiveUI;
16+
using Windows.Foundation;
1417

1518
namespace PackageInstaller.Pages;
1619

@@ -105,6 +108,14 @@ public PackageActions()
105108
)
106109
.DisposeWith(disposable);
107110

111+
this.ViewModel
112+
.WhenAnyValue((vm) => vm.PrimaryAction!.ToolTip)
113+
.ObserveOn(RxApp.MainThreadScheduler)
114+
.Subscribe(
115+
(tooltip) => ToolTipService.SetToolTip(PrimaryActionButtonText, tooltip)
116+
)
117+
.DisposeWith(disposable);
118+
108119
this.ViewModel
109120
.WhenAnyValue((vm) => vm.PackageIconStream)
110121
.ObserveOn(RxApp.MainThreadScheduler)
@@ -135,7 +146,8 @@ public PackageActions()
135146
this.BindCommand(
136147
ViewModel,
137148
(vm) => vm.PrimaryAction!.ActionCommand,
138-
(v) => v.ActionButton
149+
(v) => v.ActionButton,
150+
(vm) => vm.PrimaryAction
139151
)
140152
.DisposeWith(disposable);
141153

@@ -155,13 +167,16 @@ public PackageActions()
155167
SecondaryActions.Items.AddRange(
156168
list.Select(
157169
(a) =>
158-
(
159-
new MenuFlyoutItem()
160-
{
161-
Text = a.Title,
162-
Command = a.ActionCommand,
163-
}
164-
)
170+
{
171+
var item = new MenuFlyoutItem()
172+
{
173+
Text = a.Title,
174+
Command = a.ActionCommand,
175+
CommandParameter = a
176+
};
177+
ToolTipService.SetToolTip(item, a.ToolTip);
178+
return item;
179+
}
165180
)
166181
);
167182
}
@@ -204,6 +219,48 @@ public PackageActions()
204219
.Select(x => Unit.Default)
205220
.InvokeCommand(ViewModel!.GotoNotificationHub)
206221
.DisposeWith(disposable);
222+
223+
this.ViewModel.ActionConfirmationDialogInteraction.RegisterHandler(
224+
(interaction) =>
225+
{
226+
TaskCompletionSource src = new TaskCompletionSource();
227+
RoutedEventHandler handleClick = null!;
228+
TypedEventHandler<FlyoutBase, FlyoutBaseClosingEventArgs>? handleClose =
229+
null!;
230+
231+
handleClick = (_, _) =>
232+
{
233+
ActionFlyout.Closing -= handleClose;
234+
ActionFlyoutButton.Click -= handleClick;
235+
236+
if (!interaction.IsHandled)
237+
{
238+
interaction.SetOutput(true);
239+
}
240+
241+
src.SetResult();
242+
};
243+
244+
handleClose = (_, _) =>
245+
{
246+
ActionFlyout.Closing -= handleClose;
247+
ActionFlyoutButton.Click -= handleClick;
248+
249+
if (!interaction.IsHandled)
250+
{
251+
interaction.SetOutput(false);
252+
}
253+
254+
src.SetResult();
255+
};
256+
ActionFlyout.Closing += handleClose;
257+
ActionFlyoutButton.Click += handleClick;
258+
ActionFlyout.ShowAt(ActionButton);
259+
ActionFlyoutText.Text = interaction.Input;
260+
261+
return src.Task;
262+
}
263+
);
207264
}
208265
);
209266
}

PackageInstaller/Program.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
using Shared.Services;
1313
using Shared.Services.Implementations;
1414
using Shared.Services.Implementations.WinUI;
15-
using Shared.Services.Implementations.WinuiUI;
1615
using Splat;
1716
using Splat.Microsoft.Extensions.DependencyInjection;
1817

@@ -41,8 +40,8 @@ private static void ConfigureServices(WindowsAppSdkHostBuilder<App> builder)
4140
{
4241
ConfigureSplatIntegration(collection);
4342
ConfigureModelViews(collection);
44-
ConfigureComplexServices(collection);
4543
ConfigureServiceDiscovery(collection);
44+
ConfigureComplexServices(collection);
4645
}
4746
);
4847
}

Shared.Misc/ObservableAsync.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public static class ObservableAsync
1111
/// Converts an asynchronous action into an observable sequence. Each subscription to the resulting sequence causes the action to be started.
1212
/// </summary>
1313
/// <param name="actionAsync">Asynchronous action to convert.</param>
14+
/// <param name="scheduler">The scheduler which will be used.</param>
1415
/// <returns>An observable sequence exposing a Unit value upon completion of the action, or an exception.</returns>
1516
/// <exception cref="ArgumentNullException"><paramref name="actionAsync"/> is null.</exception>
1617
public static IObservable<Unit> From(Func<Task> actionAsync, IScheduler scheduler)

Shared.Services.Implementations.Winui/ThreadHelpers.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System;
22
using Microsoft.UI.Dispatching;
33

4-
namespace Shared.Services.Implementations.WinuiUI;
4+
namespace Shared.Services.Implementations.WinUI;
55

66
public class ThreadHelpers : IThreadHelpers
77
{

0 commit comments

Comments
 (0)