Skip to content

Commit eb904f1

Browse files
committed
feat: extract mock service.
1 parent cf20497 commit eb904f1

File tree

7 files changed

+194
-133
lines changed

7 files changed

+194
-133
lines changed

src/Client.Avalonia/App.axaml.cs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1+
using System.Linq;
12
using Avalonia;
23
using Avalonia.Controls.ApplicationLifetimes;
3-
using Avalonia.Data.Core;
44
using Avalonia.Data.Core.Plugins;
5-
using System.Linq;
65
using Avalonia.Markup.Xaml;
6+
using Client.Avalonia.Services;
77
using Client.Avalonia.ViewModels;
88
using Client.Avalonia.Views;
9+
using Microsoft.Extensions.DependencyInjection;
910

1011
namespace Client.Avalonia;
1112

@@ -18,14 +19,19 @@ public override void Initialize()
1819

1920
public override void OnFrameworkInitializationCompleted()
2021
{
22+
var services = new ServiceCollection();
23+
services.AddSingleton<IDownloadService, MockDownloadService>();
24+
services.AddTransient<MainWindowViewModel>();
25+
var serviceProvider = new DefaultServiceProviderFactory().CreateServiceProvider(services);
26+
2127
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
2228
{
2329
// Avoid duplicate validations from both Avalonia and the CommunityToolkit.
2430
// More info: https://docs.avaloniaui.net/docs/guides/development-guides/data-validation#manage-validationplugins
2531
DisableAvaloniaDataAnnotationValidation();
2632
desktop.MainWindow = new MainWindow
2733
{
28-
DataContext = new MainWindowViewModel(),
34+
DataContext = serviceProvider.GetRequiredService<MainWindowViewModel>()
2935
};
3036
}
3137

src/Client.Avalonia/Client.Avalonia.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99
</PropertyGroup>
1010

1111
<ItemGroup>
12-
<Folder Include="Models\"/>
1312
<AvaloniaResource Include="Assets\**"/>
1413
</ItemGroup>
1514

@@ -23,6 +22,7 @@
2322
</PackageReference>
2423
<PackageReference Include="Avalonia.Svg" Version="11.2.0.2" />
2524
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.3.2"/>
25+
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.0" />
2626
<PackageReference Include="Semi.Avalonia" Version="11.2.1.1" />
2727
</ItemGroup>
2828
</Project>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
using System.ComponentModel;
3+
using CommunityToolkit.Mvvm.ComponentModel;
4+
5+
namespace Client.Avalonia.Models;
6+
7+
public partial class DownloadStatistics : ObservableObject
8+
{
9+
[ObservableProperty] [Description("当前下载版本")]
10+
private object? _version;
11+
12+
[ObservableProperty] [Description("下载速度")]
13+
private double _speed;
14+
15+
[ObservableProperty] [Description("剩余下载时间")]
16+
private TimeSpan _remaining;
17+
18+
[ObservableProperty] [Description("总大小")] [NotifyPropertyChangedFor(nameof(TotalBytesToReceiveInMB))]
19+
private long _totalBytesToReceive;
20+
21+
[ObservableProperty] [Description("已下载大小")] [NotifyPropertyChangedFor(nameof(BytesReceivedInMB))]
22+
private long _bytesReceived;
23+
24+
[ObservableProperty] [Description("进度百分比")]
25+
private double _progressPercentage;
26+
27+
public double BytesReceivedInMB => (double)BytesReceived / 1024 / 1024;
28+
public double TotalBytesToReceiveInMB => (double)TotalBytesToReceive / 1024 / 1024;
29+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using System;
2+
using System.ComponentModel;
3+
using Client.Avalonia.Models;
4+
5+
namespace Client.Avalonia.Services;
6+
7+
public interface IDownloadService
8+
{
9+
event Action<DownloadStatistics> ProgressChanged;
10+
event Action<DownloadStatus> StatusChanged;
11+
12+
DownloadStatistics CurrentStatistics { get; }
13+
DownloadStatus Status { get; }
14+
15+
void Start();
16+
void Pause();
17+
void Stop();
18+
void Restart();
19+
}
20+
21+
[Flags]
22+
public enum DownloadStatus
23+
{
24+
[Description("未开始")] NotStarted,
25+
[Description("下载中")] Downloading,
26+
[Description("已暂停")] Paused,
27+
[Description("已完成")] Completed
28+
}
Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
using System;
2+
using Avalonia.Threading;
3+
using Client.Avalonia.Models;
4+
5+
namespace Client.Avalonia.Services;
6+
7+
public class MockDownloadService : IDownloadService
8+
{
9+
public event Action<DownloadStatistics>? ProgressChanged;
10+
public event Action<DownloadStatus>? StatusChanged;
11+
12+
private readonly DispatcherTimer _timer;
13+
private readonly Random _random;
14+
15+
public DownloadStatistics CurrentStatistics { get; private set; }
16+
public DownloadStatus Status { get; private set; }
17+
18+
public MockDownloadService()
19+
{
20+
CurrentStatistics = new DownloadStatistics
21+
{
22+
Version = "11.2.2",
23+
Speed = 0,
24+
Remaining = TimeSpan.Zero,
25+
TotalBytesToReceive = 1024 * 1024 * 10,
26+
BytesReceived = 0,
27+
ProgressPercentage = 0
28+
};
29+
Status = DownloadStatus.NotStarted;
30+
31+
_random = new Random();
32+
_timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(1000) };
33+
_timer.Tick += UpdateProgress;
34+
}
35+
36+
public void Start()
37+
{
38+
if (Status is DownloadStatus.Downloading)
39+
{
40+
Pause();
41+
return;
42+
}
43+
44+
_timer.Start();
45+
UpdateStatus(DownloadStatus.Downloading);
46+
}
47+
48+
public void Pause()
49+
{
50+
if (Status is DownloadStatus.Downloading)
51+
{
52+
_timer.Stop();
53+
UpdateStatus(DownloadStatus.Paused);
54+
CurrentStatistics.Speed = 0;
55+
ProgressChanged?.Invoke(CurrentStatistics);
56+
}
57+
}
58+
59+
public void Stop()
60+
{
61+
_timer.Stop();
62+
UpdateStatus(DownloadStatus.NotStarted);
63+
64+
CurrentStatistics.BytesReceived = 0;
65+
CurrentStatistics.ProgressPercentage = 0;
66+
CurrentStatistics.Speed = 0;
67+
CurrentStatistics.Remaining = TimeSpan.Zero;
68+
69+
ProgressChanged?.Invoke(CurrentStatistics);
70+
}
71+
72+
public void Restart()
73+
{
74+
Stop();
75+
Start();
76+
}
77+
78+
private void UpdateProgress(object? sender, EventArgs e)
79+
{
80+
var stats = CurrentStatistics;
81+
var increment = _random.Next(1024 * 1024 * 2, 1024 * 1024 * 3);
82+
stats.BytesReceived = Math.Min(stats.BytesReceived + increment, stats.TotalBytesToReceive);
83+
84+
stats.ProgressPercentage = (double)stats.BytesReceived / stats.TotalBytesToReceive * 100;
85+
stats.Speed = increment / _timer.Interval.TotalSeconds / 1024 / 1024;
86+
stats.Remaining = stats.BytesReceived >= stats.TotalBytesToReceive
87+
? TimeSpan.Zero
88+
: TimeSpan.FromSeconds((stats.TotalBytesToReceive - stats.BytesReceived) / (stats.Speed * 1024 * 1024));
89+
90+
ProgressChanged?.Invoke(stats);
91+
92+
if (stats.BytesReceived >= stats.TotalBytesToReceive)
93+
{
94+
_timer.Stop();
95+
UpdateStatus(DownloadStatus.Completed);
96+
}
97+
}
98+
99+
private void UpdateStatus(DownloadStatus newStatus)
100+
{
101+
if (Status != newStatus)
102+
{
103+
Status = newStatus;
104+
StatusChanged?.Invoke(newStatus);
105+
}
106+
}
107+
}
Lines changed: 13 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -1,153 +1,43 @@
1-
using System;
2-
using System.ComponentModel;
3-
using Avalonia.Threading;
1+
using Client.Avalonia.Models;
2+
using Client.Avalonia.Services;
43
using CommunityToolkit.Mvvm.ComponentModel;
54
using CommunityToolkit.Mvvm.Input;
65

76
namespace Client.Avalonia.ViewModels;
87

98
public partial class MainWindowViewModel : ViewModelBase
109
{
10+
private readonly IDownloadService _downloadService;
11+
1112
[ObservableProperty] private DownloadStatistics _statistics;
1213

1314
[ObservableProperty] [NotifyCanExecuteChangedFor(nameof(StartCommand), nameof(StopCommand))]
1415
private DownloadStatus _status;
1516

16-
private readonly DispatcherTimer _timer;
17-
private readonly Random _random;
18-
19-
public MainWindowViewModel()
17+
public MainWindowViewModel(IDownloadService downloadService)
2018
{
21-
Statistics = new DownloadStatistics
22-
{
23-
Version = "11.2.2",
24-
Speed = 0,
25-
Remaining = TimeSpan.Zero,
26-
TotalBytesToReceive = 1024 * 1024 * 10,
27-
BytesReceived = 1024 * 1024 * 0,
28-
ProgressPercentage = 0
29-
};
30-
Status = DownloadStatus.NotStarted;
19+
_downloadService = downloadService;
20+
_downloadService.ProgressChanged += stats => Statistics = stats;
21+
_downloadService.StatusChanged += status => Status = status;
3122

32-
_timer = new DispatcherTimer
33-
{
34-
Interval = TimeSpan.FromMilliseconds(1000)
35-
};
36-
_timer.Tick += UpdateProgress;
37-
38-
_random = new Random();
23+
Statistics = _downloadService.CurrentStatistics;
24+
Status = _downloadService.Status;
3925
}
4026

4127
#region Buttons
4228

4329
private bool CanStart => Status is DownloadStatus.NotStarted or DownloadStatus.Downloading or DownloadStatus.Paused;
4430

4531
[RelayCommand(CanExecute = nameof(CanStart))]
46-
private void Start()
47-
{
48-
if (!_timer.IsEnabled)
49-
{
50-
_timer.Start();
51-
Status = DownloadStatus.Downloading;
52-
}
53-
else
54-
{
55-
_timer.Stop();
56-
Status = DownloadStatus.Paused;
57-
Statistics.Speed = 0;
58-
Statistics.Remaining = TimeSpan.Zero;
59-
}
60-
}
32+
private void Start() => _downloadService.Start();
6133

6234
private bool CanStop => Status is DownloadStatus.Downloading or DownloadStatus.Paused;
6335

6436
[RelayCommand(CanExecute = nameof(CanStop))]
65-
private void Stop()
66-
{
67-
_timer.Stop();
68-
Status = DownloadStatus.NotStarted;
69-
Statistics.BytesReceived = 0;
70-
Statistics.ProgressPercentage = 0;
71-
Statistics.Speed = 0;
72-
Statistics.Remaining = TimeSpan.Zero;
73-
OnPropertyChanged(nameof(Statistics));
74-
}
37+
private void Stop() => _downloadService.Stop();
7538

7639
[RelayCommand]
77-
private void Restart()
78-
{
79-
Stop();
80-
Start();
81-
}
40+
private void Restart() => _downloadService.Restart();
8241

8342
#endregion
84-
85-
86-
private void UpdateProgress(object? sender, EventArgs e)
87-
{
88-
var received = Statistics.BytesReceived;
89-
var total = Statistics.TotalBytesToReceive;
90-
if (received < total)
91-
{
92-
var increment = _random.Next(1024 * 1024 * 2, 1024 * 1024 * 3);
93-
received += increment;
94-
95-
if (received > total)
96-
{
97-
received = total;
98-
}
99-
100-
Statistics.BytesReceived = received;
101-
Statistics.ProgressPercentage = (double)received / total * 100;
102-
103-
var currentSpeed = increment / _timer.Interval.TotalSeconds;
104-
Statistics.Speed = currentSpeed / 1024 / 1024;
105-
106-
var remainingBytes = total - received;
107-
Statistics.Remaining = TimeSpan.FromSeconds(remainingBytes / currentSpeed);
108-
109-
OnPropertyChanged(nameof(Statistics));
110-
111-
if (received >= total)
112-
{
113-
_timer.Stop();
114-
Status = DownloadStatus.Completed;
115-
Statistics.Speed = 0;
116-
Statistics.Remaining = TimeSpan.Zero;
117-
}
118-
}
119-
}
120-
}
121-
122-
public partial class DownloadStatistics : ObservableObject
123-
{
124-
[ObservableProperty] [Description("当前下载版本")]
125-
private object? _version;
126-
127-
[ObservableProperty] [Description("下载速度")]
128-
private double _speed;
129-
130-
[ObservableProperty] [Description("剩余下载时间")]
131-
private TimeSpan _remaining;
132-
133-
[ObservableProperty] [Description("总大小")] [NotifyPropertyChangedFor(nameof(TotalBytesToReceiveInMB))]
134-
private long _totalBytesToReceive;
135-
136-
[ObservableProperty] [Description("已下载大小")] [NotifyPropertyChangedFor(nameof(BytesReceivedInMB))]
137-
private long _bytesReceived;
138-
139-
[ObservableProperty] [Description("进度百分比")]
140-
private double _progressPercentage;
141-
142-
public double BytesReceivedInMB => (double)BytesReceived / 1024 / 1024;
143-
public double TotalBytesToReceiveInMB => (double)TotalBytesToReceive / 1024 / 1024;
144-
}
145-
146-
[Flags]
147-
public enum DownloadStatus
148-
{
149-
[Description("未开始")] NotStarted,
150-
[Description("下载中")] Downloading,
151-
[Description("已暂停")] Paused,
152-
[Description("已完成")] Completed
15343
}

0 commit comments

Comments
 (0)