114 lines
4.4 KiB
C#
114 lines
4.4 KiB
C#
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Media;
|
|
using System.Windows.Media.Animation;
|
|
using System.Windows.Media.Effects;
|
|
using System.Windows.Threading;
|
|
|
|
namespace VideoDownloader.App.Services;
|
|
|
|
/// <summary>
|
|
/// UC-D11/UC-N02 토스트 알림 — 지정한 호스트 패널(우하단)에 카드를 띄우고
|
|
/// 설정된 시간 후 사라지게 한다. 방해 금지(DoNotDisturb)면 표시를 억제하고 알림센터에만 쌓는다.
|
|
/// UI 스레드 DispatcherTimer 기반이라 별도 스레드가 없다.
|
|
/// </summary>
|
|
public sealed class ToastNotifier
|
|
{
|
|
private readonly Panel _host;
|
|
private readonly DispatcherTimer _timer;
|
|
private readonly List<string> _history = new();
|
|
|
|
/// <summary>토스트 표시 시간. AppConfig.ToastDurationMs 와 연동.</summary>
|
|
public TimeSpan Duration { get; set; } = TimeSpan.FromSeconds(4);
|
|
|
|
/// <summary>UC-N15 방해 금지 — true 면 토스트를 띄우지 않고 기록만.</summary>
|
|
public bool DoNotDisturb { get; set; }
|
|
|
|
/// <summary>UC-N14 알림 센터용 최근 알림 이력(최신 우선, 상한 100).</summary>
|
|
public IReadOnlyList<string> History => _history.ToList();
|
|
|
|
public ToastNotifier(Panel host)
|
|
{
|
|
_host = host;
|
|
_timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(250) };
|
|
_timer.Tick += (_, _) => Expire();
|
|
}
|
|
|
|
/// <summary>토스트 표시. kind 에 따라 아이콘·테두리 색이 달라진다(success/error/info).</summary>
|
|
public void Show(string message, string kind = "info")
|
|
{
|
|
_history.Insert(0, $"[{DateTime.Now:HH:mm}] {message}");
|
|
if (_history.Count > 100) _history.RemoveAt(_history.Count - 100);
|
|
if (DoNotDisturb) return;
|
|
|
|
var border = new Border
|
|
{
|
|
Background = (Brush)Application.Current.FindResource("CardBg"),
|
|
BorderBrush = kind switch
|
|
{
|
|
"success" => (Brush)Application.Current.FindResource("SuccessBrush"),
|
|
"error" => (Brush)Application.Current.FindResource("ErrorBrush"),
|
|
_ => (Brush)Application.Current.FindResource("AccentBrush"),
|
|
},
|
|
BorderThickness = new Thickness(1),
|
|
CornerRadius = new CornerRadius(10),
|
|
Padding = new Thickness(14, 10, 14, 10),
|
|
Margin = new Thickness(0, 8, 0, 0),
|
|
MaxWidth = 340,
|
|
Effect = new DropShadowEffect { Opacity = 0.4, BlurRadius = 16, ShadowDepth = 4 },
|
|
Tag = DateTime.UtcNow.Add(Duration),
|
|
};
|
|
var icon = kind switch
|
|
{
|
|
"success" => "\uE73E",
|
|
"error" => "\uE7BA",
|
|
_ => "\uE946",
|
|
};
|
|
var panel = new StackPanel { Orientation = Orientation.Horizontal };
|
|
panel.Children.Add(new TextBlock
|
|
{
|
|
Text = icon,
|
|
FontFamily = (FontFamily)Application.Current.FindResource("IconFont"),
|
|
FontSize = 15,
|
|
Foreground = border.BorderBrush,
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
Margin = new Thickness(0, 0, 9, 0),
|
|
});
|
|
panel.Children.Add(new TextBlock
|
|
{
|
|
Text = message,
|
|
Foreground = (Brush)Application.Current.FindResource("TextPrimary"),
|
|
FontSize = 12.5,
|
|
TextWrapping = TextWrapping.Wrap,
|
|
VerticalAlignment = VerticalAlignment.Center,
|
|
});
|
|
border.Child = panel;
|
|
border.MouseLeftButtonUp += (_, _) => Collapse(border);
|
|
border.Opacity = 0;
|
|
_host.Children.Add(border);
|
|
|
|
border.Loaded += (_, _) =>
|
|
{
|
|
var anim = new DoubleAnimation(0, 1, (System.Windows.Duration)Application.Current.FindResource("Duration.Fast"));
|
|
border.BeginAnimation(UIElement.OpacityProperty, anim);
|
|
};
|
|
if (!_timer.IsEnabled) _timer.Start();
|
|
}
|
|
|
|
private void Expire()
|
|
{
|
|
var now = DateTime.UtcNow;
|
|
foreach (var child in _host.Children.OfType<Border>().ToList())
|
|
{
|
|
if (child.Tag is DateTime until && now >= until) Collapse(child);
|
|
}
|
|
if (_host.Children.Count == 0) _timer.Stop();
|
|
}
|
|
|
|
private void Collapse(Border border)
|
|
{
|
|
var anim = new DoubleAnimation(1, 0, (System.Windows.Duration)Application.Current.FindResource("Duration.Fast"));
|
|
anim.Completed += (_, _) => _host.Children.Remove(border);
|
|
border.BeginAnimation(UIElement.OpacityProperty, anim);
|
|
}
|
|
}
|