Skip to content

Commit ce27c9c

Browse files
Show update download progress (#318)
Co-authored-by: Owen McGirr <o.a.mcgirr@gmail.com>
1 parent 0f6e89e commit ce27c9c

3 files changed

Lines changed: 158 additions & 1 deletion

File tree

src/SwitchifyPc.App/SettingsWindow.xaml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,14 @@
193193
<Setter Property="Width" Value="10" />
194194
<Setter Property="MinWidth" Value="10" />
195195
</Style>
196+
197+
<Style TargetType="ProgressBar">
198+
<Setter Property="Height" Value="10" />
199+
<Setter Property="Minimum" Value="0" />
200+
<Setter Property="Maximum" Value="100" />
201+
<Setter Property="Foreground" Value="{StaticResource BrandPrimary}" />
202+
<Setter Property="Background" Value="{StaticResource BorderColor}" />
203+
</Style>
196204
</Window.Resources>
197205

198206
<Grid Background="#F5F4F7" Margin="24">
@@ -533,6 +541,14 @@
533541
<TextBlock Margin="0,6,0,0"
534542
Text="{Binding UpdateDownloadMessage}"
535543
Style="{StaticResource MutedText}" />
544+
<StackPanel Margin="0,10,0,0"
545+
Visibility="{Binding IsUpdateDownloading, Converter={StaticResource BooleanToVisibilityConverter}}">
546+
<ProgressBar Value="{Binding UpdateDownloadPercent}"
547+
IsIndeterminate="{Binding IsUpdateDownloadIndeterminate}" />
548+
<TextBlock Margin="0,6,0,0"
549+
Text="{Binding UpdateDownloadProgressText}"
550+
Style="{StaticResource MutedText}" />
551+
</StackPanel>
536552
<WrapPanel Margin="0,14,0,0">
537553
<Button Content="Check for updates"
538554
Margin="0,0,10,8"

src/SwitchifyPc.Core/Ui/SettingsViewModel.cs

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.ComponentModel;
2+
using System.Globalization;
23
using System.Runtime.CompilerServices;
34
using SwitchifyPc.Core.Pairing;
45
using SwitchifyPc.Core.Settings;
@@ -131,6 +132,14 @@ public string StartWithSystemMessage
131132

132133
public string? UpdateDownloadMessage => DownloadMessage(updateState.Download);
133134

135+
public bool IsUpdateDownloading => updateState.Download.Status == UpdateDownloadStatus.Downloading;
136+
137+
public bool IsUpdateDownloadIndeterminate => IsUpdateDownloading && updateState.Download.Percent is null;
138+
139+
public int UpdateDownloadPercent => Math.Clamp(updateState.Download.Percent ?? 0, 0, 100);
140+
141+
public string UpdateDownloadProgressText => DownloadProgressText(updateState.Download);
142+
134143
public bool CanDownloadUpdate =>
135144
updateState.Info.Status == UpdateCheckStatus.UpdateAvailable &&
136145
updateState.Download.Status is UpdateDownloadStatus.Idle or UpdateDownloadStatus.DownloadFailed;
@@ -199,6 +208,10 @@ public void SetUpdateState(UpdateState state)
199208
updateState = state;
200209
OnPropertyChanged(nameof(UpdateStatusMessage));
201210
OnPropertyChanged(nameof(UpdateDownloadMessage));
211+
OnPropertyChanged(nameof(IsUpdateDownloading));
212+
OnPropertyChanged(nameof(IsUpdateDownloadIndeterminate));
213+
OnPropertyChanged(nameof(UpdateDownloadPercent));
214+
OnPropertyChanged(nameof(UpdateDownloadProgressText));
202215
OnPropertyChanged(nameof(CanDownloadUpdate));
203216
OnPropertyChanged(nameof(CanInstallUpdate));
204217
}
@@ -224,7 +237,7 @@ public static string UpdateCheckMessage(UpdateInfo info)
224237
if (download.Status == UpdateDownloadStatus.Idle) return null;
225238
if (download.Status == UpdateDownloadStatus.Downloading)
226239
{
227-
return download.Percent is null ? "Downloading..." : $"Downloading {download.Percent}%.";
240+
return "Downloading update...";
228241
}
229242

230243
if (download.Status == UpdateDownloadStatus.Downloaded) return "Update downloaded and ready to install.";
@@ -233,6 +246,39 @@ public static string UpdateCheckMessage(UpdateInfo info)
233246
return "Could not download the update.";
234247
}
235248

249+
public static string DownloadProgressText(UpdateDownloadProgress download)
250+
{
251+
if (download.Status != UpdateDownloadStatus.Downloading) return string.Empty;
252+
253+
bool hasDownloadedBytes = download.DownloadedBytes > 0;
254+
bool hasTotalBytes = download.TotalBytes is > 0;
255+
string? percent = download.Percent is null
256+
? null
257+
: $"{Math.Clamp(download.Percent.Value, 0, 100).ToString(CultureInfo.InvariantCulture)}%";
258+
259+
if (percent is not null && hasDownloadedBytes && hasTotalBytes)
260+
{
261+
return $"Downloading {percent} ({FormatBytes(download.DownloadedBytes)} of {FormatBytes(download.TotalBytes!.Value)}).";
262+
}
263+
264+
if (percent is not null)
265+
{
266+
return $"Downloading {percent}.";
267+
}
268+
269+
if (hasDownloadedBytes && hasTotalBytes)
270+
{
271+
return $"Downloading {FormatBytes(download.DownloadedBytes)} of {FormatBytes(download.TotalBytes!.Value)}.";
272+
}
273+
274+
if (hasDownloadedBytes)
275+
{
276+
return $"Downloading {FormatBytes(download.DownloadedBytes)}.";
277+
}
278+
279+
return "Downloading...";
280+
}
281+
236282
public static string InstallMessage(UpdateInstallFailureReason? reason)
237283
{
238284
return reason switch
@@ -251,4 +297,21 @@ private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
251297
{
252298
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
253299
}
300+
301+
private static string FormatBytes(long bytes)
302+
{
303+
if (bytes < 1024)
304+
{
305+
return string.Create(CultureInfo.InvariantCulture, $"{bytes} B");
306+
}
307+
308+
double kilobytes = bytes / 1024d;
309+
if (kilobytes < 1024)
310+
{
311+
return string.Create(CultureInfo.InvariantCulture, $"{kilobytes:0.0} KB");
312+
}
313+
314+
double megabytes = kilobytes / 1024d;
315+
return string.Create(CultureInfo.InvariantCulture, $"{megabytes:0.0} MB");
316+
}
254317
}

src/SwitchifyPc.Tests/SettingsViewModelTests.cs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,76 @@ public void MapsUpdateMessagesAndActions()
200200
Assert.True(viewModel.CanInstallUpdate);
201201
}
202202

203+
[Fact]
204+
public void ShowsDeterminateDownloadProgress()
205+
{
206+
SettingsViewModel viewModel = new();
207+
208+
viewModel.SetUpdateState(UpdateState.CreateInitial("0.2.3") with
209+
{
210+
Download = new UpdateDownloadProgress(UpdateDownloadStatus.Downloading, 20_971_520, 52_428_800, 40)
211+
});
212+
213+
Assert.True(viewModel.IsUpdateDownloading);
214+
Assert.False(viewModel.IsUpdateDownloadIndeterminate);
215+
Assert.Equal(40, viewModel.UpdateDownloadPercent);
216+
Assert.Equal("Downloading update...", viewModel.UpdateDownloadMessage);
217+
Assert.Equal("Downloading 40% (20.0 MB of 50.0 MB).", viewModel.UpdateDownloadProgressText);
218+
}
219+
220+
[Fact]
221+
public void ShowsIndeterminateDownloadProgressWhenPercentIsUnknown()
222+
{
223+
SettingsViewModel viewModel = new();
224+
225+
viewModel.SetUpdateState(UpdateState.CreateInitial("0.2.3") with
226+
{
227+
Download = new UpdateDownloadProgress(UpdateDownloadStatus.Downloading, 20_971_520, null, null)
228+
});
229+
230+
Assert.True(viewModel.IsUpdateDownloading);
231+
Assert.True(viewModel.IsUpdateDownloadIndeterminate);
232+
Assert.Equal(0, viewModel.UpdateDownloadPercent);
233+
Assert.Equal("Downloading 20.0 MB.", viewModel.UpdateDownloadProgressText);
234+
}
235+
236+
[Fact]
237+
public void HidesDownloadProgressWhenDownloaded()
238+
{
239+
SettingsViewModel viewModel = new();
240+
241+
viewModel.SetUpdateState(UpdateState.CreateInitial("0.2.3") with
242+
{
243+
Download = new UpdateDownloadProgress(UpdateDownloadStatus.Downloaded, 52_428_800, 52_428_800, 100)
244+
});
245+
246+
Assert.False(viewModel.IsUpdateDownloading);
247+
Assert.False(viewModel.IsUpdateDownloadIndeterminate);
248+
Assert.Equal(100, viewModel.UpdateDownloadPercent);
249+
Assert.Equal("Update downloaded and ready to install.", viewModel.UpdateDownloadMessage);
250+
Assert.Equal(string.Empty, viewModel.UpdateDownloadProgressText);
251+
}
252+
253+
[Fact]
254+
public void HidesDownloadProgressWhenFailed()
255+
{
256+
SettingsViewModel viewModel = new();
257+
258+
viewModel.SetUpdateState(UpdateState.CreateInitial("0.2.3") with
259+
{
260+
Download = new UpdateDownloadProgress(
261+
UpdateDownloadStatus.DownloadFailed,
262+
20_971_520,
263+
52_428_800,
264+
40,
265+
UpdateFailureReason.NetworkError)
266+
});
267+
268+
Assert.False(viewModel.IsUpdateDownloading);
269+
Assert.Equal("Could not download the update.", viewModel.UpdateDownloadMessage);
270+
Assert.Equal(string.Empty, viewModel.UpdateDownloadProgressText);
271+
}
272+
203273
[Fact]
204274
public void MapsUpdateInstallFailureMessages()
205275
{
@@ -224,11 +294,19 @@ public void RaisesChangedProperties()
224294
viewModel.PropertyChanged += (_, eventArgs) => changed.Add(eventArgs.PropertyName);
225295

226296
viewModel.SetPointerMovementSettings(new PointerMovementSettings(50));
297+
viewModel.SetUpdateState(UpdateState.CreateInitial("0.2.3") with
298+
{
299+
Download = new UpdateDownloadProgress(UpdateDownloadStatus.Downloading, 1024, 2048, 50)
300+
});
227301

228302
Assert.Contains(nameof(SettingsViewModel.PointerScalePercent), changed);
229303
Assert.Contains(nameof(SettingsViewModel.IsPointerScale50), changed);
230304
Assert.Contains(nameof(SettingsViewModel.PointerSmall), changed);
231305
Assert.Contains(nameof(SettingsViewModel.PointerMedium), changed);
232306
Assert.Contains(nameof(SettingsViewModel.PointerLarge), changed);
307+
Assert.Contains(nameof(SettingsViewModel.IsUpdateDownloading), changed);
308+
Assert.Contains(nameof(SettingsViewModel.IsUpdateDownloadIndeterminate), changed);
309+
Assert.Contains(nameof(SettingsViewModel.UpdateDownloadPercent), changed);
310+
Assert.Contains(nameof(SettingsViewModel.UpdateDownloadProgressText), changed);
233311
}
234312
}

0 commit comments

Comments
 (0)