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;
///
/// UC-D11/UC-N02 토스트 알림 — 지정한 호스트 패널(우하단)에 카드를 띄우고
/// 설정된 시간 후 사라지게 한다. 방해 금지(DoNotDisturb)면 표시를 억제하고 알림센터에만 쌓는다.
/// UI 스레드 DispatcherTimer 기반이라 별도 스레드가 없다.
///
public sealed class ToastNotifier
{
private readonly Panel _host;
private readonly DispatcherTimer _timer;
private readonly List _history = new();
/// 토스트 표시 시간. AppConfig.ToastDurationMs 와 연동.
public TimeSpan Duration { get; set; } = TimeSpan.FromSeconds(4);
/// UC-N15 방해 금지 — true 면 토스트를 띄우지 않고 기록만.
public bool DoNotDisturb { get; set; }
/// UC-N14 알림 센터용 최근 알림 이력(최신 우선, 상한 100).
public IReadOnlyList History => _history.ToList();
public ToastNotifier(Panel host)
{
_host = host;
_timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(250) };
_timer.Tick += (_, _) => Expire();
}
/// 토스트 표시. kind 에 따라 아이콘·테두리 색이 달라진다(success/error/info).
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().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);
}
}