using System.Collections.ObjectModel; using System.Windows; using XFEToolBox.Client.Models; namespace XFEToolBox.Client.Utilities; public static class ActivityCenterService { private const int MaximumCompletedItems = 8; private static readonly ObservableCollection MutableItems = []; public static ReadOnlyObservableCollection Items { get; } = new(MutableItems); public static event EventHandler? Changed; public static int ActiveCount => MutableItems.Count(item => item.State is ActivityState.Pending or ActivityState.Running); public static ActivityHandle Start(string title, ActivityKind kind = ActivityKind.General, bool canCancel = false) { var item = new ActivityItem(Guid.NewGuid().ToString("N"), kind, title, canCancel) { State = ActivityState.Running }; Dispatch(() => { MutableItems.Insert(0, item); TrimCompletedItems(); Changed?.Invoke(null, EventArgs.Empty); }); return new ActivityHandle(item); } public static IReadOnlyList GetSnapshot(int maximumCount = 8) => MutableItems.Take(Math.Max(0, maximumCount)).ToArray(); public static string DescribeActiveItems() => string.Join(",\n", MutableItems .Where(item => item.State is ActivityState.Pending or ActivityState.Running) .Select(item => $"{item.Title} · {item.ProgressText}")); public static void ClearCompleted() { Dispatch(() => { for (var index = MutableItems.Count - 1; index >= 0; index--) if (MutableItems[index].State is not (ActivityState.Pending or ActivityState.Running)) MutableItems.RemoveAt(index); Changed?.Invoke(null, EventArgs.Empty); }); } internal static void NotifyChanged() => Dispatch(() => { TrimCompletedItems(); Changed?.Invoke(null, EventArgs.Empty); }); internal static void Dispatch(Action action) { var dispatcher = Application.Current?.Dispatcher; if (dispatcher is null || dispatcher.CheckAccess()) action(); else dispatcher.Invoke(action); } private static void TrimCompletedItems() { var completed = 0; for (var index = 0; index < MutableItems.Count; index++) { if (MutableItems[index].State is ActivityState.Pending or ActivityState.Running) continue; completed++; if (completed <= MaximumCompletedItems) continue; MutableItems.RemoveAt(index--); } } } public sealed class ActivityHandle : IDisposable { private readonly ActivityItem item; private bool completed; internal ActivityHandle(ActivityItem item) => this.item = item; public ActivityItem Item => item; public CancellationToken CancellationToken => item.Token; public void Report(double? progress, string? message = null) => ActivityCenterService.Dispatch(() => { if (completed) return; item.Progress = progress; if (message is not null) item.Message = message; ActivityCenterService.NotifyChanged(); }); public void Succeed(string? message = null) => Finish(ActivityState.Succeeded, message); public void Fail(string message) => Finish(ActivityState.Failed, message); public void Cancel(string? message = null) => Finish(ActivityState.Cancelled, message ?? "任务已取消"); public void Dispose() { if (!completed) Succeed(); } private void Finish(ActivityState state, string? message) { ActivityCenterService.Dispatch(() => { if (completed) return; completed = true; item.State = state; item.Progress = state == ActivityState.Succeeded ? 100 : item.Progress; if (message is not null) item.Message = message; item.CompletedAt = DateTimeOffset.Now; item.CanCancel = false; ActivityCenterService.NotifyChanged(); }); } }