✨ feat(dock): switch notification popup to click-based, load history in background

This commit is contained in:
sankalp committed 2025-07-21 10:26:22 +05:45
1 parent 2f7590c700
commit aebb974810
1 file changed
+120 -59
+120 -59
View File
@@ -5,7 +5,6 @@ from datetime import datetime
from fabric.widgets.box import Box from fabric.widgets.box import Box
from fabric.widgets.button import Button from fabric.widgets.button import Button
from fabric.widgets.centerbox import CenterBox from fabric.widgets.centerbox import CenterBox
from fabric.widgets.eventbox import EventBox
from fabric.widgets.label import Label from fabric.widgets.label import Label
from fabric.widgets.revealer import Revealer from fabric.widgets.revealer import Revealer
from fabric.widgets.scrolledwindow import ScrolledWindow from fabric.widgets.scrolledwindow import ScrolledWindow
@@ -32,7 +31,7 @@ from utils.wayland import WaylandWindow as Window
class NotificationHistoryPopup(Window): class NotificationHistoryPopup(Window):
"""Popup window that shows notification history with hover-based reveal""" """Popup window that shows notification history with click-based reveal"""
def __init__(self, dock_component=None, **kwargs): def __init__(self, dock_component=None, **kwargs):
# Get dock position for proper anchoring # Get dock position for proper anchoring
@@ -147,26 +146,28 @@ class NotificationHistoryPopup(Window):
return False return False
def on_button_press(self, widget, event): def on_button_press(self, widget, event):
"""Handle button press events""" """Handle button press events - close popup when clicking outside"""
# Close popup when clicking outside
if self.dock_component:
self.dock_component.popup_visible = False
self.hide_popup()
return False return False
def on_enter_notify(self, widget, event): def on_enter_notify(self, widget, event):
"""Handle mouse entering popup""" """Handle mouse entering popup"""
# Cancel any pending hide from dock component # No action needed for click-based popup
if self.dock_component and hasattr(self.dock_component, '_hover_timeout'):
if self.dock_component._hover_timeout:
GLib.source_remove(self.dock_component._hover_timeout)
self.dock_component._hover_timeout = None
return False return False
def on_leave_notify(self, widget, event): def on_leave_notify(self, widget, event):
"""Handle mouse leaving popup""" """Handle mouse leaving popup"""
# Hide popup when mouse leaves # No action needed for click-based popup
self.hide_popup()
return False return False
def show_popup(self): def show_popup(self):
"""Show the notification history popup""" """Show the notification history popup"""
# Ensure notification history is loaded before showing
self.notification_history._ensure_history_loaded()
self.set_visible(True) self.set_visible(True)
self.show_all() self.show_all()
# Use GLib.idle_add to ensure the window is shown before revealing # Use GLib.idle_add to ensure the window is shown before revealing
@@ -185,7 +186,7 @@ class NotificationHistoryPopup(Window):
GLib.timeout_add(350, lambda: self.set_visible(False)) GLib.timeout_add(350, lambda: self.set_visible(False))
class NotificationIndicator(EventBox): class NotificationIndicator(Box):
def __init__(self, **kwargs): def __init__(self, **kwargs):
self.notification_icon = Label( self.notification_icon = Label(
name="notification-icon", markup=icons.notifications name="notification-icon", markup=icons.notifications
@@ -195,77 +196,89 @@ class NotificationIndicator(EventBox):
self.icon_button = Button( self.icon_button = Button(
name="button-bar-notifications", name="button-bar-notifications",
child=self.notification_icon, child=self.notification_icon,
on_clicked=self.on_button_clicked,
) )
super().__init__( super().__init__(
name="notification-indicator", name="notification-indicator",
child=self.icon_button, children=[self.icon_button],
**kwargs **kwargs
) )
# Set up event box for proper event handling # Initialize popup as None, will be created in background
self.set_visible_window(False) self.popup = None
self.set_above_child(True) self.popup_visible = False
self.popup_creating = False
# Connect hover events
self.connect("enter-notify-event", self.on_enter_notify)
self.connect("leave-notify-event", self.on_leave_notify)
self.add_events(Gdk.EventMask.ENTER_NOTIFY_MASK | Gdk.EventMask.LEAVE_NOTIFY_MASK)
# Pre-create popup for immediate display on hover
self.popup = NotificationHistoryPopup(dock_component=self)
self._hover_timeout = None
self._connect_to_dnd_state() self._connect_to_dnd_state()
# Connect to destroy signal for cleanup # Connect to destroy signal for cleanup
self.connect("destroy", self.on_destroy) self.connect("destroy", self.on_destroy)
def on_destroy(self, widget): # Start background creation of popup after a short delay
"""Clean up popup when component is destroyed""" GLib.timeout_add(100, self._create_popup_in_background)
try:
# Remove hover timeout
if hasattr(self, "_hover_timeout") and self._hover_timeout:
GLib.source_remove(self._hover_timeout)
self._hover_timeout = None
# Clean up popup (popup is always created now) def on_destroy(self, widget):
if self.popup: """Hide popup when component is destroyed"""
try:
# Hide popup instead of destroying it (only if it was created)
if self.popup is not None:
try: try:
self.popup.destroy() self.popup.hide_popup()
self.popup = None self.popup_visible = False
except Exception: except Exception:
pass pass
except Exception: except Exception:
pass pass
def on_enter_notify(self, widget, event): def _create_popup_in_background(self):
"""Handle mouse entering the notification component""" """Create popup in background for instant access"""
print("Notification hover detected!") # Debug print if self.popup is None and not self.popup_creating:
# Cancel any pending hover timeout print("Creating notification popup in background...") # Debug print
if self._hover_timeout: self.popup_creating = True
GLib.source_remove(self._hover_timeout)
self._hover_timeout = None
# Show popup immediately on hover (popup is pre-created) # Use idle_add to create popup without blocking UI
if not self.popup.get_visible(): GLib.idle_add(self._do_background_creation)
self.popup.show_popup()
return False
def on_leave_notify(self, widget, event):
"""Handle mouse leaving the notification component"""
# Hide popup with a small delay to prevent flickering
self._hover_timeout = GLib.timeout_add(100, self._hide_popup_delayed)
return False
def _hide_popup_delayed(self):
"""Hide popup after delay"""
if self.popup and self.popup.get_visible():
self.popup.hide_popup()
self._hover_timeout = None
return False # Don't repeat timeout return False # Don't repeat timeout
def _do_background_creation(self):
"""Actually create the popup in background"""
try:
self.popup = NotificationHistoryPopup(dock_component=self)
print("Notification popup created in background!") # Debug print
except Exception as e:
print(f"Error creating popup in background: {e}")
finally:
self.popup_creating = False
return False # Don't repeat
def on_button_clicked(self, button):
"""Handle button click to toggle notification popup"""
print("Notification button clicked!") # Debug print
# If popup is still being created, create it synchronously
if self.popup is None:
if self.popup_creating:
print("Popup still creating, waiting...") # Debug print
# Force synchronous creation if user clicked while background creation is happening
self.popup = NotificationHistoryPopup(dock_component=self)
self.popup_creating = False
else:
print("Creating notification popup immediately...") # Debug print
self.popup = NotificationHistoryPopup(dock_component=self)
if self.popup_visible:
# Hide popup if currently visible
self.popup.hide_popup()
self.popup_visible = False
else:
# Show popup if currently hidden
self.popup.show_popup()
self.popup_visible = True
def _connect_to_dnd_state(self): def _connect_to_dnd_state(self):
try: try:
self.notification_history = get_shared_notification_history() self.notification_history = get_shared_notification_history()
@@ -365,14 +378,20 @@ class NotificationHistory(Box):
self.persistent_notifications = [] self.persistent_notifications = []
self.add(self.history_header) self.add(self.history_header)
self.add(self.scrolled_window) self.add(self.scrolled_window)
self._load_persistent_history()
cleanup_orphan_cached_images(self.persistent_notifications) # Initialize history loading state
self.history_loaded = False
self.history_loading = False
self.schedule_midnight_update() self.schedule_midnight_update()
self._connect_to_shared_history() self._connect_to_shared_history()
self._load_and_sync_dnd_state() self._load_and_sync_dnd_state()
# Start background loading of history after a short delay
GLib.timeout_add(200, self._load_history_in_background)
def _connect_to_shared_history(self): def _connect_to_shared_history(self):
try: try:
shared_history = get_shared_notification_history() shared_history = get_shared_notification_history()
@@ -422,6 +441,48 @@ class NotificationHistory(Box):
except Exception as e: except Exception as e:
print(f"Could not sync DND state: {e}") print(f"Could not sync DND state: {e}")
def _load_history_in_background(self):
"""Start loading notification history in background"""
if not self.history_loaded and not self.history_loading:
print("Starting background loading of notification history...") # Debug print
self.history_loading = True
# Use idle_add to load history without blocking UI
GLib.idle_add(self._do_background_history_loading)
return False # Don't repeat timeout
def _do_background_history_loading(self):
"""Actually load the history in background"""
try:
print("Loading notification history in background...") # Debug print
self._load_persistent_history()
cleanup_orphan_cached_images(self.persistent_notifications)
self.history_loaded = True
print("Notification history loaded in background!") # Debug print
except Exception as e:
print(f"Error loading history in background: {e}")
finally:
self.history_loading = False
return False # Don't repeat
def _ensure_history_loaded(self):
"""Ensure notification history is loaded (force if needed)"""
if not self.history_loaded:
if self.history_loading:
print("History still loading in background, forcing synchronous load...") # Debug print
# Force synchronous loading if user opened popup while background loading
self._load_persistent_history()
cleanup_orphan_cached_images(self.persistent_notifications)
self.history_loaded = True
self.history_loading = False
else:
print("Loading notification history synchronously...") # Debug print
self._load_persistent_history()
cleanup_orphan_cached_images(self.persistent_notifications)
self.history_loaded = True
def schedule_midnight_update(self): def schedule_midnight_update(self):
schedule_midnight_update(self.on_midnight) schedule_midnight_update(self.on_midnight)