Skip to content

Commit 690c5ee

Browse files
authored
Improved update experience (#43)
1 parent 70e70c9 commit 690c5ee

File tree

161 files changed

+1792
-1072
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

161 files changed

+1792
-1072
lines changed

SecureFolderFS.AvaloniaUI/App.axaml.cs

+8-5
Original file line numberDiff line numberDiff line change
@@ -76,20 +76,23 @@ private IServiceProvider ConfigureServices(IModifiableFolder settingsFolder)
7676
.AddSingleton<ISettingsService, SettingsService>(_ => new SettingsService(settingsFolder))
7777
.AddSingleton<IVaultPersistenceService, VaultPersistenceService>(_ => new VaultPersistenceService(settingsFolder))
7878
.AddSingleton<IVaultService, VaultService>()
79-
.AddSingleton<IStorageService, NativeStorageService>()
8079
.AddSingleton<IDialogService, DialogService>()
81-
.AddSingleton<IApplicationService, ApplicationService>()
80+
.AddSingleton<IClipboardService, ClipboardService>()
8281
.AddSingleton<IThreadingService, ThreadingService>()
82+
.AddSingleton<IStorageService, NativeStorageService>()
83+
.AddSingleton<IApplicationService, ApplicationService>()
8384
.AddSingleton<ILocalizationService, LocalizationService>()
8485
.AddSingleton<IFileExplorerService, FileExplorerService>()
85-
.AddSingleton<IClipboardService, ClipboardService>()
86-
.AddSingleton<IUpdateService, AvaloniaUpdateService>()
86+
.AddSingleton<IChangelogService, GitHubChangelogService>()
8787

8888
// Conditional (singleton) services
8989
#if DEBUG
90+
.AddSingleton<IIapService, DebugIapService>()
91+
.AddSingleton<IUpdateService, DebugUpdateService>()
9092
.AddSingleton<ITelemetryService, DebugTelemetryService>()
91-
9293
#else
94+
.AddSingleton<IIapService, DebugIapService>()
95+
.AddSingleton<IUpdateService, AvaloniaUpdateService>()
9396
.AddSingleton<ITelemetryService, TelemetryService>()
9497
#endif
9598

SecureFolderFS.AvaloniaUI/Dialogs/LicensesDialog.axaml

+10-15
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,10 @@
2828
</Style>
2929
</ui:ContentDialog.Styles>
3030

31-
<Grid Height="500" Margin="-8" RowDefinitions="Auto,*">
31+
<Grid
32+
Height="500"
33+
Margin="-8"
34+
RowDefinitions="Auto,*">
3235
<Grid
3336
Grid.Row="0"
3437
Margin="-16,-56,-16,8"
@@ -58,9 +61,7 @@
5861

5962

6063
<ScrollViewer Grid.Row="1">
61-
<ItemsControl
62-
ItemsSource="{Binding ViewModel.Licenses, RelativeSource={RelativeSource AncestorType={x:Type ui:ContentDialog}}}"
63-
Loaded="Licenses_Loaded">
64+
<ItemsControl ItemsSource="{Binding ViewModel.Licenses, RelativeSource={RelativeSource AncestorType={x:Type ui:ContentDialog}}}" Loaded="Licenses_Loaded">
6465
<ItemsControl.ItemTemplate>
6566
<DataTemplate DataType="{x:Type vm:LicenseViewModel}">
6667
<ui:SettingsExpander
@@ -69,23 +70,17 @@
6970
Header="{Binding PackageName}">
7071
<ui:SettingsExpander.Footer>
7172
<StackPanel Orientation="Horizontal" Spacing="8">
72-
<!-- Text must be in TextBlocks in order to remove underline -->
73-
<ui:HyperlinkButton
74-
IsVisible="{Binding ProjectWebsiteUri, Converter={StaticResource NullToBoolConverter}}"
75-
NavigateUri="{Binding ProjectWebsiteUri}">
76-
<TextBlock Text="Project website" />
73+
<!-- Text must be in TextBlocks in order to remove underline -->
74+
<ui:HyperlinkButton IsVisible="{Binding ProjectWebsiteUri, Converter={StaticResource NullToBoolConverter}}" NavigateUri="{Binding ProjectWebsiteUri}">
75+
<TextBlock Text="Website" />
7776
</ui:HyperlinkButton>
78-
<ui:HyperlinkButton
79-
IsVisible="{Binding NavigateUri, RelativeSource={RelativeSource Self}, Converter={StaticResource NullToBoolConverter}}"
80-
NavigateUri="{Binding LicenseUris, Converter={StaticResource OnlyFirstElementOrNullConverter}}">
77+
<ui:HyperlinkButton IsVisible="{Binding NavigateUri, RelativeSource={RelativeSource Self}, Converter={StaticResource NullToBoolConverter}}" NavigateUri="{Binding LicenseUris, Converter={StaticResource OnlyFirstElementOrNullConverter}}">
8178
<TextBlock Text="License" />
8279
</ui:HyperlinkButton>
8380
</StackPanel>
8481
</ui:SettingsExpander.Footer>
8582

86-
<SelectableTextBlock
87-
Text="{Binding DataContext.LicenseContent, RelativeSource={RelativeSource AncestorType={x:Type ui:SettingsExpander}}}"
88-
TextWrapping="Wrap" />
83+
<SelectableTextBlock Text="{Binding DataContext.LicenseContent, RelativeSource={RelativeSource AncestorType={x:Type ui:SettingsExpander}}}" TextWrapping="Wrap" />
8984
</ui:SettingsExpander>
9085
</DataTemplate>
9186
</ItemsControl.ItemTemplate>

SecureFolderFS.AvaloniaUI/Dialogs/LicensesDialog.axaml.cs

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
1-
using System;
2-
using System.Threading.Tasks;
31
using Avalonia.Interactivity;
42
using Avalonia.Markup.Xaml;
53
using Avalonia.Styling;
64
using CommunityToolkit.Mvvm.Messaging;
75
using FluentAvalonia.UI.Controls;
86
using SecureFolderFS.AvaloniaUI.Messages;
97
using SecureFolderFS.Sdk.Enums;
8+
using SecureFolderFS.Sdk.Extensions;
109
using SecureFolderFS.Sdk.Models;
1110
using SecureFolderFS.Sdk.ViewModels.Dialogs;
11+
using SecureFolderFS.Shared.Utils;
12+
using System;
13+
using System.Threading.Tasks;
1214

1315
namespace SecureFolderFS.AvaloniaUI.Dialogs
1416
{
@@ -29,7 +31,7 @@ public LicensesDialog()
2931
public Type StyleKey => typeof(ContentDialog);
3032

3133
/// <inheritdoc/>
32-
public async Task<DialogResult> ShowAsync() => (DialogResult)await base.ShowAsync();
34+
public new async Task<IResult> ShowAsync() => DialogExtensions.ResultFromDialogOption((DialogOption)await base.ShowAsync());
3335

3436
private void CloseButton_Click(object? sender, RoutedEventArgs e)
3537
{

SecureFolderFS.AvaloniaUI/Dialogs/PasswordChangeDialog.axaml.cs

+5-3
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,17 @@
11
using Avalonia.Interactivity;
22
using Avalonia.Markup.Xaml;
33
using Avalonia.Styling;
4+
using CommunityToolkit.Mvvm.Messaging;
45
using FluentAvalonia.UI.Controls;
6+
using SecureFolderFS.AvaloniaUI.Messages;
57
using SecureFolderFS.Sdk.Enums;
8+
using SecureFolderFS.Sdk.Extensions;
69
using SecureFolderFS.Sdk.Models;
710
using SecureFolderFS.Sdk.ViewModels.Dialogs;
11+
using SecureFolderFS.Shared.Utils;
812
using System;
913
using System.Threading;
1014
using System.Threading.Tasks;
11-
using CommunityToolkit.Mvvm.Messaging;
12-
using SecureFolderFS.AvaloniaUI.Messages;
1315

1416
namespace SecureFolderFS.AvaloniaUI.Dialogs
1517
{
@@ -30,7 +32,7 @@ public PasswordChangeDialog()
3032
public Type StyleKey => typeof(ContentDialog);
3133

3234
/// <inheritdoc/>
33-
public async Task<DialogResult> ShowAsync() => (DialogResult)await base.ShowAsync();
35+
public new async Task<IResult> ShowAsync() => DialogExtensions.ResultFromDialogOption((DialogOption)await base.ShowAsync());
3436

3537
private async void PasswordChangeDialog_PrimaryButtonClick(ContentDialog sender, ContentDialogButtonClickEventArgs args)
3638
{

SecureFolderFS.AvaloniaUI/Dialogs/SettingsDialog.axaml.cs

+5-6
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,20 @@
11
using Avalonia.Interactivity;
22
using Avalonia.Markup.Xaml;
33
using Avalonia.Styling;
4+
using CommunityToolkit.Mvvm.Messaging;
45
using FluentAvalonia.UI.Controls;
6+
using SecureFolderFS.AvaloniaUI.Messages;
57
using SecureFolderFS.AvaloniaUI.UserControls.Navigation;
68
using SecureFolderFS.Sdk.Enums;
79
using SecureFolderFS.Sdk.Extensions;
810
using SecureFolderFS.Sdk.Models;
911
using SecureFolderFS.Sdk.Services;
1012
using SecureFolderFS.Sdk.ViewModels.Dialogs;
1113
using SecureFolderFS.Sdk.ViewModels.Views.Settings;
14+
using SecureFolderFS.Shared.Utils;
1215
using SecureFolderFS.UI.Helpers;
1316
using System;
1417
using System.Threading.Tasks;
15-
using CommunityToolkit.Mvvm.Messaging;
16-
using SecureFolderFS.AvaloniaUI.Messages;
17-
using SecureFolderFS.AvaloniaUI.UserControls.Navigation;
18-
using SecureFolderFS.Sdk.Services;
1918

2019
namespace SecureFolderFS.AvaloniaUI.Dialogs
2120
{
@@ -36,7 +35,7 @@ public SettingsDialog()
3635
}
3736

3837
/// <inheritdoc/>
39-
public async Task<DialogResult> ShowAsync() => (DialogResult)await base.ShowAsync();
38+
public new async Task<IResult> ShowAsync() => DialogExtensions.ResultFromDialogOption((DialogOption)await base.ShowAsync());
4039

4140
private INavigationTarget GetTargetForTag(int tag)
4241
{
@@ -70,7 +69,7 @@ private void CloseButton_Click(object? sender, RoutedEventArgs e)
7069
private void SettingsDialog_Closing(ContentDialog sender, ContentDialogClosingEventArgs e)
7170
{
7271
// Remove the reference to the NavigationControl so the dialog can get properly garbage collected
73-
ViewModel.NavigationService.ResetNavigation<FrameNavigationControl>();
72+
ViewModel.NavigationService.ResetNavigation();
7473
}
7574
}
7675
}

SecureFolderFS.AvaloniaUI/Dialogs/VaultWizardDialog.axaml.cs

+10-6
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,22 @@
11
using Avalonia.Interactivity;
22
using Avalonia.Markup.Xaml;
33
using Avalonia.Styling;
4+
using Avalonia.Threading;
5+
using CommunityToolkit.Mvvm.Messaging;
46
using FluentAvalonia.UI.Controls;
7+
using SecureFolderFS.AvaloniaUI.Messages;
58
using SecureFolderFS.Sdk.Enums;
9+
using SecureFolderFS.Sdk.Extensions;
610
using SecureFolderFS.Sdk.Models;
711
using SecureFolderFS.Sdk.Services;
812
using SecureFolderFS.Sdk.ViewModels.Dialogs;
913
using SecureFolderFS.Sdk.ViewModels.Views.Wizard;
1014
using SecureFolderFS.Sdk.ViewModels.Views.Wizard.ExistingVault;
1115
using SecureFolderFS.Sdk.ViewModels.Views.Wizard.NewVault;
16+
using SecureFolderFS.Shared.Utils;
1217
using SecureFolderFS.UI.Helpers;
1318
using System;
1419
using System.Threading.Tasks;
15-
using Avalonia.Threading;
16-
using CommunityToolkit.Mvvm.Messaging;
17-
using SecureFolderFS.AvaloniaUI.Messages;
1820

1921
namespace SecureFolderFS.AvaloniaUI.Dialogs
2022
{
@@ -33,11 +35,13 @@ public VaultWizardDialogViewModel? ViewModel
3335
public Type StyleKey => typeof(ContentDialog);
3436

3537
/// <inheritdoc/>
36-
public async Task<DialogResult> ShowAsync()
38+
public new async Task<IResult> ShowAsync()
3739
{
38-
// Can't be in the constructor because ViewModel is set later
40+
// Can't initialize in the constructor because the ViewModel is set later
3941
await Dispatcher.UIThread.InvokeAsync(() => AvaloniaXamlLoader.Load(this));
40-
return (DialogResult)await base.ShowAsync();
42+
43+
// Show dialog
44+
return DialogExtensions.ResultFromDialogOption((DialogOption)await base.ShowAsync());
4145
}
4246

4347
private async Task CompleteAnimationAsync(BaseWizardPageViewModel? viewModel)

SecureFolderFS.AvaloniaUI/ServiceImplementation/ApplicationService.cs

+5-1
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,14 @@ namespace SecureFolderFS.AvaloniaUI.ServiceImplementation
1111
/// <inheritdoc cref="IApplicationService"/>
1212
internal sealed class ApplicationService : BaseApplicationService
1313
{
14+
/// <inheritdoc/>
15+
public override string Platform { get; } = "AvaloniaUI";
16+
1417
/// <inheritdoc/>
1518
public override AppVersion GetAppVersion()
1619
{
17-
return new(Assembly.GetExecutingAssembly().GetName().Version!, "AvaloniaUI");
20+
var version = Assembly.GetExecutingAssembly().GetName().Version!;
21+
return new(version, Platform);
1822
}
1923

2024
/// <inheritdoc/>

SecureFolderFS.AvaloniaUI/ServiceImplementation/AvaloniaNavigationService.cs

+39-17
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,17 @@
1-
using SecureFolderFS.AvaloniaUI.Animations.Transitions.NavigationTransitions;
1+
using Avalonia.Threading;
2+
using SecureFolderFS.AvaloniaUI.Animations.Transitions.NavigationTransitions;
23
using SecureFolderFS.AvaloniaUI.UserControls.Navigation;
34
using SecureFolderFS.Sdk.Enums;
45
using SecureFolderFS.Sdk.Services;
56
using SecureFolderFS.Shared.Extensions;
67
using SecureFolderFS.UI.ServiceImplementation;
78
using System.Linq;
89
using System.Threading.Tasks;
9-
using Avalonia.Threading;
1010

1111
namespace SecureFolderFS.AvaloniaUI.ServiceImplementation
1212
{
1313
/// <inheritdoc cref="INavigationService"/>
14-
internal sealed class AvaloniaNavigationService : BaseNavigationService<FrameNavigationControl>
14+
internal sealed class AvaloniaNavigationService : BaseNavigationService
1515
{
1616
/// <inheritdoc/>
1717
protected override async Task<bool> BeginNavigationAsync(INavigationTarget? target, NavigationType navigationType)
@@ -23,27 +23,49 @@ protected override async Task<bool> BeginNavigationAsync(INavigationTarget? targ
2323
{
2424
case NavigationType.Backward:
2525
{
26-
if (NavigationControl.ContentFrame.CanGoBack)
27-
{
28-
NavigationControl.ContentFrame.GoBack();
26+
if (NavigationControl is not FrameNavigationControl frameNavigation)
27+
return false;
28+
29+
if (!frameNavigation.ContentFrame.CanGoBack)
30+
return false;
2931

30-
var contentType = NavigationControl.ContentFrame.Content?.GetType();
31-
if (contentType is null)
32-
return false;
32+
// Navigate back
33+
frameNavigation.ContentFrame.GoBack();
3334

34-
var targetType = NavigationControl.TypeBinding.GetByKeyOrValue(contentType);
35-
var backTarget = Targets.FirstOrDefault(x => x.GetType() == targetType);
36-
if (backTarget is not null)
37-
CurrentTarget = backTarget;
35+
var contentType = frameNavigation.ContentFrame.Content?.GetType();
36+
if (contentType is null)
37+
return false;
3838

39-
return true;
40-
}
39+
var targetType = frameNavigation.TypeBinding.GetByKeyOrValue(contentType);
40+
var backTarget = Targets.FirstOrDefault(x => x.GetType() == targetType);
41+
if (backTarget is not null)
42+
CurrentTarget = backTarget;
4143

42-
return false;
44+
return true;
4345
}
4446

4547
case NavigationType.Forward:
46-
return false;
48+
{
49+
if (NavigationControl is not FrameNavigationControl frameNavigation)
50+
return false;
51+
52+
if (!frameNavigation.ContentFrame.CanGoForward)
53+
return false;
54+
55+
// Navigate forward
56+
frameNavigation.ContentFrame.GoForward();
57+
58+
var contentType = frameNavigation.ContentFrame.Content?.GetType();
59+
if (contentType is null)
60+
return false;
61+
62+
var targetType = frameNavigation.TypeBinding.GetByKeyOrValue(contentType);
63+
var forwardTarget = Targets.FirstOrDefault(x => x.GetType() == targetType);
64+
if (forwardTarget is not null)
65+
CurrentTarget = forwardTarget;
66+
67+
return true;
68+
}
4769

4870
default:
4971
case NavigationType.Detached:

SecureFolderFS.AvaloniaUI/ServiceImplementation/AvaloniaUpdateService.cs

+5-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
using SecureFolderFS.Sdk.Enums;
21
using SecureFolderFS.Sdk.Services;
2+
using SecureFolderFS.Shared.Utils;
33
using System;
44
using System.Threading;
55
using System.Threading.Tasks;
@@ -10,6 +10,9 @@ namespace SecureFolderFS.AvaloniaUI.ServiceImplementation
1010
/// <inheritdoc cref="IUpdateService"/>
1111
internal sealed class AvaloniaUpdateService : IUpdateService
1212
{
13+
/// <inheritdoc/>
14+
public event EventHandler<EventArgs>? StateChanged;
15+
1316
/// <inheritdoc/>
1417
public Task<bool> IsSupportedAsync()
1518
{
@@ -23,7 +26,7 @@ public Task<bool> IsUpdateAvailableAsync(CancellationToken cancellationToken = d
2326
}
2427

2528
/// <inheritdoc/>
26-
public Task<AppUpdateResultType> UpdateAsync(IProgress<double>? progress, CancellationToken cancellationToken = default)
29+
public Task<IResult> UpdateAsync(IProgress<double>? progress, CancellationToken cancellationToken = default)
2730
{
2831
throw new NotImplementedException();
2932
}
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using SecureFolderFS.Sdk.Services;
22
using System;
33
using System.IO;
4+
using System.Threading;
45
using System.Threading.Tasks;
56

67
namespace SecureFolderFS.AvaloniaUI.ServiceImplementation
@@ -9,7 +10,7 @@ namespace SecureFolderFS.AvaloniaUI.ServiceImplementation
910
internal sealed class ClipboardService : IClipboardService
1011
{
1112
/// <inheritdoc/>
12-
public Task<bool> IsClipboardAvailableAsync()
13+
public Task<bool> IsSupportedAsync()
1314
{
1415
// TODO handle macOS
1516
if (OperatingSystem.IsLinux())
@@ -19,9 +20,9 @@ public Task<bool> IsClipboardAvailableAsync()
1920
}
2021

2122
/// <inheritdoc/>
22-
public Task SetTextAsync(string text)
23+
public Task SetTextAsync(string text, CancellationToken cancellationToken = default)
2324
{
24-
return TextCopy.ClipboardService.SetTextAsync(text);
25+
return TextCopy.ClipboardService.SetTextAsync(text, cancellationToken);
2526
}
2627
}
2728
}

0 commit comments

Comments
 (0)