From 5bf1f06bfed8fb82839194faaa5b070040bba51a Mon Sep 17 00:00:00 2001 From: administrator Date: Sun, 17 May 2026 16:55:16 +0200 Subject: [PATCH] EPG-Erinnerungen: Rechtsklick auf Sendung, Glocke-Indikator, MessageBox-Benachrichtigung --- MainWindow.xaml.cs | 59 ++++++++++++++++++++++ Models/Reminder.cs | 16 ++++++ Services/AppPaths.cs | 1 + Services/ReminderService.cs | 99 +++++++++++++++++++++++++++++++++++++ 4 files changed, 175 insertions(+) create mode 100644 Models/Reminder.cs create mode 100644 Services/ReminderService.cs diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index 6d8f250..d611335 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -19,6 +19,7 @@ public partial class MainWindow : Window private Media? _currentMedia; private AppSettings _settings = AppSettings.Load(); private readonly EpgService _epgService = new(); + private readonly ReminderService _reminderService = new(); private readonly LogoService _logoService = new(); private readonly ObservableCollection _allChannels = new(); @@ -1050,6 +1051,64 @@ public partial class MainWindow : Window PlayChannel(target); }; + // Rechtsklick: Kontextmenu mit Erinnern-Option + box.MouseRightButtonDown += (_, args) => + { + args.Handled = true; + var hasReminder = _reminderService.HasReminder(ch.Name, ev.StartTime); + var menu = new ContextMenu(); + + var remind = new MenuItem + { + Header = hasReminder ? "\u23f0 Erinnerung entfernen" : "\u23f0 5 min vorher erinnern", + FontSize = 13 + }; + remind.Click += (_, _) => + { + if (hasReminder) + { + var existing = _reminderService.Reminders + .FirstOrDefault(r => r.ChannelName == ch.Name && r.StartTime == ev.StartTime); + if (existing != null) _reminderService.Remove(existing.Id); + } + else + { + _reminderService.Add(new Reminder + { + ChannelName = ch.Name, + Title = ev.Title, + Description = ev.Description ?? "", + StartTime = ev.StartTime, + EndTime = ev.EndTime, + MinutesBefore = 5 + }); + } + // Grid neu bauen damit Indikator aktualisiert wird + _ = BuildEpgGridAsync(); + }; + menu.Items.Add(remind); + box.ContextMenu = menu; + menu.IsOpen = true; + }; + + // Erinnerungs-Indikator anzeigen wenn Erinnerung gesetzt + if (_reminderService.HasReminder(ch.Name, ev.StartTime)) + { + var bell = new TextBlock + { + Text = "\u23f0", + FontSize = 11, + HorizontalAlignment = HorizontalAlignment.Right, + VerticalAlignment = VerticalAlignment.Top, + Margin = new Thickness(0, 3, 4, 0), + IsHitTestVisible = false + }; + var overlay = new Grid(); + overlay.Children.Add(content); + overlay.Children.Add(bell); + box.Child = overlay; + } + return box; } diff --git a/Models/Reminder.cs b/Models/Reminder.cs new file mode 100644 index 0000000..f465581 --- /dev/null +++ b/Models/Reminder.cs @@ -0,0 +1,16 @@ +namespace FritzTV.Models; + +public class Reminder +{ + public Guid Id { get; set; } = Guid.NewGuid(); + public required string ChannelName { get; set; } + public required string Title { get; set; } + public string Description { get; set; } = ""; + public DateTime StartTime { get; set; } + public DateTime EndTime { get; set; } + public int MinutesBefore { get; set; } = 5; + public bool Fired { get; set; } = false; + + /// Zeitpunkt zu dem die Benachrichtigung ausgelöst wird + public DateTime NotifyAt => StartTime.AddMinutes(-MinutesBefore); +} diff --git a/Services/AppPaths.cs b/Services/AppPaths.cs index 6e55dc1..bdb7dce 100644 --- a/Services/AppPaths.cs +++ b/Services/AppPaths.cs @@ -36,4 +36,5 @@ public static class AppPaths public static string Epg => Path.Combine(Root, "epg"); public static string CrashLog => Path.Combine(Root, "crash.log"); public static string WebView2Profile => Path.Combine(Root, "webview2"); + public static string Reminders => Path.Combine(Root, "reminders.json"); } diff --git a/Services/ReminderService.cs b/Services/ReminderService.cs new file mode 100644 index 0000000..05c6f0a --- /dev/null +++ b/Services/ReminderService.cs @@ -0,0 +1,99 @@ +using System.IO; +using System.Text.Json; +using System.Windows.Threading; +using FritzTV.Models; + +namespace FritzTV.Services; + +public class ReminderService +{ + private static readonly string RemindersPath = AppPaths.Reminders; + private readonly DispatcherTimer _checkTimer; + private List _reminders = new(); + + /// Feuert wenn eine Erinnerung faellig ist. Callback laeuft auf UI-Thread. + public event Action? ReminderDue; + + public IReadOnlyList Reminders => _reminders.AsReadOnly(); + + public ReminderService() + { + Load(); + _checkTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(30) }; + _checkTimer.Tick += (_, _) => CheckReminders(); + _checkTimer.Start(); + } + + public void Add(Reminder reminder) + { + _reminders.Add(reminder); + Save(); + } + + public void Remove(Guid id) + { + _reminders.RemoveAll(r => r.Id == id); + Save(); + } + + public bool HasReminder(string channelName, DateTime startTime) + => _reminders.Any(r => r.ChannelName == channelName && r.StartTime == startTime); + + private void CheckReminders() + { + var now = DateTime.Now; + var due = _reminders + .Where(r => !r.Fired && r.NotifyAt <= now && r.StartTime > now.AddMinutes(-30)) + .ToList(); + + foreach (var r in due) + { + r.Fired = true; + FireNotification(r); + ReminderDue?.Invoke(r); + } + + _reminders.RemoveAll(r => r.Fired && r.StartTime < now.AddHours(-1)); + if (due.Any()) Save(); + } + + private static void FireNotification(Reminder r) + { + try + { + var minLeft = (int)(r.StartTime - DateTime.Now).TotalMinutes; + var when = minLeft <= 0 ? "Jetzt" : $"In {minLeft} Minute{(minLeft == 1 ? "" : "n")}"; + var msg = $"{when}: {r.Title}\n{r.ChannelName} {r.StartTime:HH:mm}\u2013{r.EndTime:HH:mm}"; + + System.Windows.MessageBox.Show(msg, "\u23f0 HomeStream-Erinnerung", + System.Windows.MessageBoxButton.OK, + System.Windows.MessageBoxImage.Information); + } + catch { } + } + + private void Load() + { + try + { + if (File.Exists(RemindersPath)) + { + var json = File.ReadAllText(RemindersPath); + _reminders = JsonSerializer.Deserialize>(json) ?? new(); + _reminders.RemoveAll(r => r.StartTime < DateTime.Now.AddHours(-1)); + } + } + catch { _reminders = new(); } + } + + private void Save() + { + try + { + Directory.CreateDirectory(Path.GetDirectoryName(RemindersPath)!); + var json = JsonSerializer.Serialize(_reminders, new JsonSerializerOptions { WriteIndented = true }); + File.WriteAllText(RemindersPath, json); + } + catch { } + } +}