feat: add interactive notifications with buttons using windows-toasts

This commit is contained in:
2026-03-06 21:03:04 +01:00
parent 692ea1b1ed
commit 5d655e4207

View File

@@ -1,45 +1,93 @@
import threading
from git_monitor.logger import logger from git_monitor.logger import logger
# Modern Windows 10/11 toasts with buttons
try:
from windows_toasts import WindowsToaster, ToastText2, ToastActivatedEventArgs, ToastButton
WINDOWS_TOASTS_AVAILABLE = True
toaster = WindowsToaster('Git Monitor')
except ImportError:
logger.warning("windows-toasts not found. Interactive buttons will not be available.")
WINDOWS_TOASTS_AVAILABLE = False
# Fallbacks
try: try:
from plyer import notification from plyer import notification
PLYER_AVAILABLE = True PLYER_AVAILABLE = True
except ImportError: except ImportError:
logger.warning("plyer not found. Will try win10toast.")
PLYER_AVAILABLE = False PLYER_AVAILABLE = False
try: try:
from win10toast import ToastNotifier from win10toast import ToastNotifier
WIN10TOAST_AVAILABLE = True WIN10TOAST_AVAILABLE = True
toaster = ToastNotifier() legacy_toaster = ToastNotifier()
except ImportError: except ImportError:
WIN10TOAST_AVAILABLE = False WIN10TOAST_AVAILABLE = False
class Notifier: class Notifier:
def notify(self, title, message): def notify(self, title, message):
"""Simple notification (no buttons)."""
logger.info(f"Notification: {title} - {message}") logger.info(f"Notification: {title} - {message}")
# Try plyer first if WINDOWS_TOASTS_AVAILABLE:
try:
toast = ToastText2()
toast.headline = title
toast.body = message
toaster.show_toast(toast)
return
except Exception as e:
logger.error(f"windows-toasts simple notification failed: {e}")
# Fallback to older libraries
if PLYER_AVAILABLE: if PLYER_AVAILABLE:
try: try:
notification.notify( notification.notify(title=title, message=message, app_name="Git Monitor")
title=title,
message=message,
app_name="Git Monitor"
)
return return
except Exception as e: except: pass
logger.error(f"Plyer notification failed: {e}. Trying fallback...")
# Fallback to win10toast
if WIN10TOAST_AVAILABLE: if WIN10TOAST_AVAILABLE:
try: try:
# threaded=True prevents blocking the app legacy_toaster.show_toast(title, message, duration=5, threaded=True)
toaster.show_toast(title, message, duration=5, threaded=True)
return return
except Exception as e: except: pass
logger.error(f"win10toast notification failed: {e}")
# Final fallback to stdout def notify_interactive(self, title, message, on_save, on_later):
print(f"[{title}] {message}") """Interactive notification with 'Save now' and 'Ask in 5 min' buttons."""
logger.info(f"Interactive Notification: {title} - {message}")
if not WINDOWS_TOASTS_AVAILABLE:
logger.warning("Interactive notifications not available, performing default action (Save).")
on_save()
return
try:
toast = ToastText2()
toast.headline = title
toast.body = message
# Action buttons
toast.AddAction(ToastButton("Zapisz teraz", "save"))
toast.AddAction(ToastButton("Zapytaj mnie za 5 min", "later"))
# Handler for button clicks
def on_activated(args: ToastActivatedEventArgs):
if args.arguments == "save":
logger.info("User clicked 'Zapisz teraz'")
on_save()
elif args.arguments == "later":
logger.info("User clicked 'Zapytaj mnie za 5 min'")
on_later()
else:
# Default click on toast body
on_save()
toast.on_activated = on_activated
toaster.show_toast(toast)
except Exception as e:
logger.error(f"Interactive notification error: {e}")
# Fallback to immediate save if notification fails
on_save()
notifier = Notifier() notifier = Notifier()