Skip to content

Commit 4d85b88

Browse files
authored
Feature: Added new format for displaying Release Notes (#16563)
1 parent da88530 commit 4d85b88

File tree

16 files changed

+189
-165
lines changed

16 files changed

+189
-165
lines changed
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
// Copyright (c) 2024 Files Community
2+
// Licensed under the MIT License. See the LICENSE.
3+
4+
using CommunityToolkit.WinUI.Helpers;
5+
6+
namespace Files.App.Actions
7+
{
8+
internal sealed class OpenReleaseNotesAction : ObservableObject, IAction
9+
{
10+
private readonly IDialogService DialogService = Ioc.Default.GetRequiredService<IDialogService>();
11+
private readonly IUpdateService UpdateService = Ioc.Default.GetRequiredService<IUpdateService>();
12+
public string Label
13+
=> Strings.WhatsNew.GetLocalizedResource();
14+
15+
public string Description
16+
=> Strings.WhatsNewDescription.GetLocalizedResource();
17+
18+
public bool IsExecutable
19+
=> UpdateService.AreReleaseNotesAvailable;
20+
21+
public OpenReleaseNotesAction()
22+
{
23+
UpdateService.PropertyChanged += UpdateService_PropertyChanged;
24+
}
25+
26+
public Task ExecuteAsync(object? parameter = null)
27+
{
28+
var viewModel = new ReleaseNotesDialogViewModel(Constants.ExternalUrl.ReleaseNotesUrl);
29+
var dialog = DialogService.GetDialog(viewModel);
30+
31+
return dialog.TryShowAsync();
32+
}
33+
34+
private void UpdateService_PropertyChanged(object? sender, PropertyChangedEventArgs e)
35+
{
36+
switch (e.PropertyName)
37+
{
38+
case nameof(IUpdateService.AreReleaseNotesAvailable):
39+
OnPropertyChanged(nameof(IsExecutable));
40+
break;
41+
}
42+
}
43+
}
44+
}

src/Files.App/Constants.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright (c) 2024 Files Community
22
// Licensed under the MIT License. See the LICENSE.
33

4+
using CommunityToolkit.WinUI.Helpers;
5+
46
namespace Files.App
57
{
68
public static class Constants
@@ -205,6 +207,7 @@ public static class ExternalUrl
205207
public const string PrivacyPolicyUrl = @"https://files.community/privacy";
206208
public const string SupportUsUrl = @"https://github.com/sponsors/yaira2";
207209
public const string CrowdinUrl = @"https://crowdin.com/project/files-app";
210+
public static readonly string ReleaseNotesUrl= $"https://files.community/blog/posts/v{SystemInformation.Instance.ApplicationVersion.Major}-{SystemInformation.Instance.ApplicationVersion.Minor}-{SystemInformation.Instance.ApplicationVersion.Build}?minimal";
208211
}
209212

210213
public static class DocsPath

src/Files.App/Data/Commands/Manager/CommandCodes.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ public enum CommandCodes
114114
OpenInVSCode,
115115
OpenRepoInVSCode,
116116
OpenProperties,
117+
OpenReleaseNotes,
117118
OpenClassicProperties,
118119
OpenSettings,
119120
OpenStorageSense,

src/Files.App/Data/Commands/Manager/CommandManager.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ public IRichCommand this[HotKey hotKey]
115115
public IRichCommand OpenInVSCode => commands[CommandCodes.OpenInVSCode];
116116
public IRichCommand OpenRepoInVSCode => commands[CommandCodes.OpenRepoInVSCode];
117117
public IRichCommand OpenProperties => commands[CommandCodes.OpenProperties];
118+
public IRichCommand OpenReleaseNotes => commands[CommandCodes.OpenReleaseNotes];
118119
public IRichCommand OpenClassicProperties => commands[CommandCodes.OpenClassicProperties];
119120
public IRichCommand OpenStorageSense => commands[CommandCodes.OpenStorageSense];
120121
public IRichCommand OpenStorageSenseFromHome => commands[CommandCodes.OpenStorageSenseFromHome];
@@ -317,6 +318,7 @@ public IEnumerator<IRichCommand> GetEnumerator() =>
317318
[CommandCodes.OpenInVSCode] = new OpenInVSCodeAction(),
318319
[CommandCodes.OpenRepoInVSCode] = new OpenRepoInVSCodeAction(),
319320
[CommandCodes.OpenProperties] = new OpenPropertiesAction(),
321+
[CommandCodes.OpenReleaseNotes] = new OpenReleaseNotesAction(),
320322
[CommandCodes.OpenClassicProperties] = new OpenClassicPropertiesAction(),
321323
[CommandCodes.OpenStorageSense] = new OpenStorageSenseAction(),
322324
[CommandCodes.OpenStorageSenseFromHome] = new OpenStorageSenseFromHomeAction(),

src/Files.App/Data/Commands/Manager/ICommandManager.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ public interface ICommandManager : IEnumerable<IRichCommand>
103103
IRichCommand OpenInVSCode { get; }
104104
IRichCommand OpenRepoInVSCode { get; }
105105
IRichCommand OpenProperties { get; }
106+
IRichCommand OpenReleaseNotes { get; }
106107
IRichCommand OpenClassicProperties { get; }
107108
IRichCommand OpenStorageSense { get; }
108109
IRichCommand OpenStorageSenseFromHome { get; }

src/Files.App/Data/Contracts/IUpdateService.cs

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,27 +16,22 @@ public interface IUpdateService : INotifyPropertyChanged
1616
bool IsUpdating { get; }
1717

1818
/// <summary>
19-
/// Gets a value indicating if the apps being used the first time after an update.
19+
/// Gets a value indicating if the app is being used the first time after an update.
2020
/// </summary>
2121
bool IsAppUpdated { get; }
2222

2323
/// <summary>
24-
/// Gets a value indicating if release notes are available.
24+
/// Gets a value that indicates if there are release notes available for the current version of the app.
2525
/// </summary>
26-
bool IsReleaseNotesAvailable { get; }
26+
bool AreReleaseNotesAvailable { get; }
2727

2828
Task DownloadUpdatesAsync();
2929

3030
Task DownloadMandatoryUpdatesAsync();
3131

3232
Task CheckForUpdatesAsync();
3333

34-
Task CheckLatestReleaseNotesAsync(CancellationToken cancellationToken = default);
35-
36-
/// <summary>
37-
/// Gets release notes for the latest release
38-
/// </summary>
39-
Task<string?> GetLatestReleaseNotesAsync(CancellationToken cancellationToken = default);
34+
Task CheckForReleaseNotesAsync();
4035

4136
/// <summary>
4237
/// Replace Files.App.Launcher.exe if it is used and has been updated

src/Files.App/Dialogs/ReleaseNotesDialog.xaml

Lines changed: 8 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
<ContentDialog.Resources>
1818
<ResourceDictionary>
1919
<Thickness x:Key="ContentDialogPadding">0</Thickness>
20+
<x:Double x:Key="ContentDialogMaxWidth">1100</x:Double>
2021
</ResourceDictionary>
2122
</ContentDialog.Resources>
2223

@@ -60,21 +61,14 @@
6061
</Button>
6162
</Grid>
6263

63-
<!-- Markdown Content -->
64-
<ScrollViewer
64+
<!-- WebView -->
65+
<WebView2
66+
x:Name="BlogPostWebView"
6567
Grid.Row="1"
66-
MinWidth="440"
67-
MinHeight="200"
68-
Padding="20,0,20,20"
69-
HorizontalScrollMode="Auto"
70-
VerticalScrollMode="Auto">
71-
<controls:MarkdownTextBlock
72-
x:Name="ReleaseNotesMarkdownTextBlock"
73-
Background="Transparent"
74-
LinkClicked="ReleaseNotesMarkdownTextBlock_LinkClicked"
75-
ListGutterWidth="16"
76-
Text="{x:Bind ViewModel.ReleaseNotesMadrkdown, Mode=OneWay}" />
77-
</ScrollViewer>
68+
HorizontalAlignment="Stretch"
69+
VerticalAlignment="Stretch"
70+
CoreWebView2Initialized="BlogPostWebView_CoreWebView2Initialized"
71+
Source="{x:Bind ViewModel.BlogPostUrl, Mode=OneWay}" />
7872

7973
<!-- Footer -->
8074
<Border

src/Files.App/Dialogs/ReleaseNotesDialog.xaml.cs

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@
33

44
using Microsoft.UI.Xaml;
55
using Microsoft.UI.Xaml.Controls;
6+
using Microsoft.Web.WebView2.Core;
67
using Windows.System;
8+
using Windows.UI.WebUI;
79

810
namespace Files.App.Dialogs
911
{
@@ -28,7 +30,10 @@ public ReleaseNotesDialog()
2830

2931
private void UpdateDialogLayout()
3032
{
31-
ContainerGrid.MaxHeight = MainWindow.Instance.Bounds.Height - 70;
33+
var maxHeight = MainWindow.Instance.Bounds.Height - 70;
34+
var maxWidth = MainWindow.Instance.Bounds.Width;
35+
ContainerGrid.Height = maxHeight > 740 ? 740 : maxHeight;
36+
ContainerGrid.Width = maxWidth > 740 ? 740 : maxWidth;
3237
}
3338

3439
private void Current_SizeChanged(object sender, WindowSizeChangedEventArgs e)
@@ -60,10 +65,40 @@ private ContentDialog SetContentDialogRoot(ContentDialog contentDialog)
6065
return contentDialog;
6166
}
6267

63-
private async void ReleaseNotesMarkdownTextBlock_LinkClicked(object sender, CommunityToolkit.WinUI.UI.Controls.LinkClickedEventArgs e)
68+
private async void BlogPostWebView_CoreWebView2Initialized(WebView2 sender, CoreWebView2InitializedEventArgs args)
6469
{
65-
if (Uri.TryCreate(e.Link, UriKind.Absolute, out Uri? link))
66-
await Launcher.LaunchUriAsync(link);
70+
BlogPostWebView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false;
71+
BlogPostWebView.CoreWebView2.Settings.AreDevToolsEnabled = false;
72+
BlogPostWebView.CoreWebView2.Settings.AreBrowserAcceleratorKeysEnabled = false;
73+
BlogPostWebView.CoreWebView2.Settings.IsSwipeNavigationEnabled = false;
74+
75+
var script = @"
76+
document.addEventListener('click', function(event) {
77+
var target = event.target;
78+
while (target && target.tagName !== 'A') {
79+
target = target.parentElement;
80+
}
81+
if (target && target.href) {
82+
event.preventDefault();
83+
window.chrome.webview.postMessage(target.href);
84+
}
85+
});
86+
";
87+
88+
await sender.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(script);
89+
sender.WebMessageReceived += WebView_WebMessageReceived;
6790
}
91+
92+
private async void WebView_WebMessageReceived(WebView2 sender, CoreWebView2WebMessageReceivedEventArgs args)
93+
{
94+
// Open link in web browser
95+
if (Uri.TryCreate(args.TryGetWebMessageAsString(), UriKind.Absolute, out Uri? uri))
96+
await Launcher.LaunchUriAsync(uri);
97+
98+
// Navigate back to blog post
99+
if (sender.CoreWebView2.CanGoBack)
100+
sender.CoreWebView2.GoBack();
101+
}
102+
68103
}
69104
}

src/Files.App/Helpers/Application/AppLifecycleHelper.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ public static async Task CheckAppUpdate()
107107
await updateService.CheckForUpdatesAsync();
108108
await updateService.DownloadMandatoryUpdatesAsync();
109109
await updateService.CheckAndUpdateFilesLauncherAsync();
110-
await updateService.CheckLatestReleaseNotesAsync();
110+
await updateService.CheckForReleaseNotesAsync();
111111
}
112112

113113
/// <summary>
Lines changed: 25 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,25 @@
1-
using System;
2-
using System.Collections.Generic;
3-
using System.Linq;
4-
using System.Text;
5-
using System.Threading.Tasks;
1+
using CommunityToolkit.WinUI.Helpers;
2+
using System.Net.Http;
63

74
namespace Files.App.Services
85
{
9-
internal sealed class DummyUpdateService : IUpdateService
6+
internal sealed class DummyUpdateService : ObservableObject, IUpdateService
107
{
118
public bool IsUpdateAvailable => false;
129

1310
public bool IsUpdating => false;
1411

15-
public bool IsAppUpdated => true;
12+
public bool IsAppUpdated
13+
{
14+
get => SystemInformation.Instance.IsAppUpdated;
15+
}
1616

17-
public bool IsReleaseNotesAvailable => true;
17+
private bool _areReleaseNotesAvailable = false;
18+
public bool AreReleaseNotesAvailable
19+
{
20+
get => _areReleaseNotesAvailable;
21+
private set => SetProperty(ref _areReleaseNotesAvailable, value);
22+
}
1823

1924
public event PropertyChangedEventHandler? PropertyChanged { add { } remove { } }
2025

@@ -28,9 +33,19 @@ public Task CheckForUpdatesAsync()
2833
return Task.CompletedTask;
2934
}
3035

31-
public Task CheckLatestReleaseNotesAsync(CancellationToken cancellationToken = default)
36+
public async Task CheckForReleaseNotesAsync()
3237
{
33-
return Task.CompletedTask;
38+
using var client = new HttpClient();
39+
40+
try
41+
{
42+
var response = await client.GetAsync(Constants.ExternalUrl.ReleaseNotesUrl);
43+
AreReleaseNotesAvailable = response.IsSuccessStatusCode;
44+
}
45+
catch
46+
{
47+
AreReleaseNotesAvailable = false;
48+
}
3449
}
3550

3651
public Task DownloadMandatoryUpdatesAsync()
@@ -42,11 +57,5 @@ public Task DownloadUpdatesAsync()
4257
{
4358
return Task.CompletedTask;
4459
}
45-
46-
public Task<string?> GetLatestReleaseNotesAsync(CancellationToken cancellationToken = default)
47-
{
48-
// No localization for dev-only string
49-
return Task.FromResult((string?)"No release notes available for Dev build.");
50-
}
5160
}
5261
}

0 commit comments

Comments
 (0)