♻️ refactor(dashboard): improve dashboard module structure and components

This commit is contained in:
sankalp committed 2025-07-27 09:30:16 +05:45
1 parent 232518f2e6
commit a85e606022
8 files changed
+143 -137

No files matched your search

+1 -1
View File
@@ -12,7 +12,7 @@ from modules.launcher import Launcher
from modules.notification_popup import NotificationPopup
from modules.osd import OSD
from modules.switcher import ApplicationSwitcher
from modules.dashboard import DashboardWindow
from modules.dashboard.main import DashboardWindow
for log in [
-9
View File
@@ -1,9 +0,0 @@
"""
Dashboard module for Modus.
Provides a dashboard interface with tiles and notifications.
"""
from .main import DashboardWindow
from .notifications import DashboardNotifications, DashboardNotificationItem
__all__ = ["DashboardWindow", "DashboardNotifications", "DashboardNotificationItem"]
+16 -14
View File
@@ -6,8 +6,8 @@ from fabric.widgets.image import Image
from fabric.widgets.label import Label
from fabric.widgets.scrolledwindow import ScrolledWindow
from modules.dashboard.tile import Tile, add_hover_cursor
import utils.icons as icons
from modules.dashboard.tile import Tile, add_hover_cursor
class BluetoothDeviceSlot(CenterBox):
@@ -22,8 +22,7 @@ class BluetoothDeviceSlot(CenterBox):
)
self.connection_label = Label(
name="bluetooth-connection",
markup=icons.bluetooth_disconnected
name="bluetooth-connection", markup=icons.bluetooth_disconnected
)
self.connect_button = Button(
name="bluetooth-connect",
@@ -96,10 +95,16 @@ class Bluetooth(Tile):
# Initialize bluetooth client with device handling
self.bluetooth_client = BluetoothClient(on_device_added=self.on_device_added)
self.bluetooth_client.connect("notify::enabled", self._on_bluetooth_state_changed)
self.bluetooth_client.connect(
"notify::enabled", self._on_bluetooth_state_changed
)
self.bluetooth_client.connect("notify::scanning", self._update_scan_label)
self.bluetooth_client.connect("device-added", self._on_device_connection_changed)
self.bluetooth_client.connect("device-removed", self._on_device_connection_changed)
self.bluetooth_client.connect(
"device-added", self._on_device_connection_changed
)
self.bluetooth_client.connect(
"device-removed", self._on_device_connection_changed
)
# Initialize state
self._update_tile_state()
@@ -124,7 +129,7 @@ class Bluetooth(Tile):
def _setup_content_button(self):
"""Setup the content button to toggle Bluetooth instead of showing menu."""
if hasattr(self, 'type_box'):
if hasattr(self, "type_box"):
# Disconnect the existing click handler
self.type_box.disconnect_by_func(self.handle_click)
# Connect to Bluetooth toggle handler
@@ -160,14 +165,13 @@ class Bluetooth(Tile):
"""Create the detailed bluetooth content for the dashboard."""
# Create back button with chevron left icon
self.back_button_icon = Label(
name="bluetooth-back-label",
markup=icons.chevron_left
name="bluetooth-back-label", markup=icons.chevron_left
)
self.back_button = Button(
name="bluetooth-back",
child=self.back_button_icon,
tooltip_text="Back to notifications",
on_clicked=self._on_back_clicked
on_clicked=self._on_back_clicked,
)
add_hover_cursor(self.back_button)
@@ -196,7 +200,7 @@ class Bluetooth(Tile):
name="bluetooth-header",
start_children=[self.back_button],
center_children=[Label(name="bluetooth-title", label="Bluetooth Devices")],
end_children=[self.scan_button]
end_children=[self.scan_button],
)
# Main container
@@ -218,7 +222,7 @@ class Bluetooth(Tile):
propagate_width=False,
propagate_height=False,
),
]
],
)
# Initialize notifications
@@ -267,5 +271,3 @@ class Bluetooth(Tile):
def _on_device_connection_changed(self, *_):
"""Handle device connection/disconnection changes."""
self._update_tile_icon()
+3 -4
View File
@@ -1,13 +1,12 @@
from fabric.core.service import Property
from fabric.widgets.box import Box
from fabric.widgets.stack import Stack
from fabric.core.service import Property
from modules.dashboard.bluetooth import Bluetooth
from modules.dashboard.network import Network
from modules.dashboard.tile import TileSpecial
from modules.dashboard.player_mini import PlayerContainerMini
from modules.dashboard.notifications import DashboardNotifications
from modules.dashboard.player_mini import PlayerContainerMini
from modules.dashboard.tile import TileSpecial
from utils.wayland import WaylandWindow as Window
+57 -40
View File
@@ -1,21 +1,22 @@
from fabric.utils import bulk_connect
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.centerbox import CenterBox
from fabric.widgets.image import Image
from fabric.widgets.label import Label
from fabric.widgets.scrolledwindow import ScrolledWindow
from gi.repository import Gtk, GLib
from gi.repository import GLib
import utils.icons as icons
from modules.dashboard.tile import Tile, add_hover_cursor
from services.network import NetworkClient
import utils.icons as icons
class WifiAccessPointSlot(CenterBox):
"""A widget representing a single WiFi access point in the dashboard."""
def __init__(self, access_point, network_service: NetworkClient, wifi_service, **kwargs):
def __init__(
self, access_point, network_service: NetworkClient, wifi_service, **kwargs
):
super().__init__(name="wifi-ap-slot", **kwargs)
self.access_point = access_point
self.network_service = network_service
@@ -28,10 +29,7 @@ class WifiAccessPointSlot(CenterBox):
self.ap_icon = Image(icon_name=icon_name, size=24)
self.ap_label = Label(
label=ssid,
h_expand=True,
h_align="start",
ellipsization="end"
label=ssid, h_expand=True, h_align="start", ellipsization="end"
)
self.connect_button = Button(
@@ -43,12 +41,19 @@ class WifiAccessPointSlot(CenterBox):
)
add_hover_cursor(self.connect_button)
self.set_start_children([
Box(spacing=8, h_expand=True, h_align="fill", children=[
self.ap_icon,
self.ap_label,
])
])
self.set_start_children(
[
Box(
spacing=8,
h_expand=True,
h_align="fill",
children=[
self.ap_icon,
self.ap_label,
],
)
]
)
self.set_end_children([self.connect_button])
def _on_connect_clicked(self, *_):
@@ -115,7 +120,14 @@ class Network(Tile):
def _animate_searching(self):
"""Animate wifi icon when searching for networks"""
wifi_icons = [icons.wifi_0, icons.wifi_1, icons.wifi_2, icons.wifi_3, icons.wifi_2, icons.wifi_1]
wifi_icons = [
icons.wifi_0,
icons.wifi_1,
icons.wifi_2,
icons.wifi_3,
icons.wifi_2,
icons.wifi_1,
]
wifi = self.network_client.wifi_device
if not self.icon or not wifi or not wifi.wireless_enabled:
@@ -218,7 +230,7 @@ class Network(Tile):
def _setup_content_button(self):
"""Setup the content button to toggle WiFi instead of showing menu."""
if hasattr(self, 'type_box'):
if hasattr(self, "type_box"):
# Disconnect the existing click handler
self.type_box.disconnect_by_func(self.handle_click)
# Connect to WiFi toggle handler
@@ -235,35 +247,30 @@ class Network(Tile):
"""Create the detailed network content for the dashboard."""
# Create status label
self.status_label = Label(
label="Initializing ",
h_expand=True,
visible=False,
h_align="center"
label="Initializing ", h_expand=True, visible=False, h_align="center"
)
# Create refresh button with icon
self.refresh_button_icon = Label(
name="network-refresh-label",
markup=icons.reload
name="network-refresh-label", markup=icons.reload
)
self.refresh_button = Button(
name="network-refresh",
child=self.refresh_button_icon,
tooltip_text="Scan for Wi-Fi networks",
on_clicked=self._refresh_access_points
on_clicked=self._refresh_access_points,
)
add_hover_cursor(self.refresh_button)
# Create back button with chevron left icon
self.back_button_icon = Label(
name="network-back-label",
markup=icons.chevron_left
name="network-back-label", markup=icons.chevron_left
)
self.back_button = Button(
name="network-back",
child=self.back_button_icon,
tooltip_text="Back to notifications",
on_clicked=self._on_back_clicked
on_clicked=self._on_back_clicked,
)
add_hover_cursor(self.back_button)
@@ -272,7 +279,7 @@ class Network(Tile):
name="network-header",
start_children=[self.back_button],
center_children=[Label(name="network-title", label="Wi-Fi Networks")],
end_children=[self.refresh_button]
end_children=[self.refresh_button],
)
# Create access points list
@@ -299,7 +306,7 @@ class Network(Tile):
header_box,
self.status_label,
scrolled_window,
]
],
)
# Initialize button states
@@ -322,26 +329,30 @@ class Network(Tile):
"""Setup WiFi device connections and initial state."""
if self.network_client.wifi_device:
self.network_client.wifi_device.connect("changed", self._load_access_points)
self.network_client.wifi_device.connect("notify::wireless-enabled", self._update_wifi_status_ui)
self.network_client.wifi_device.connect("notify::active-access-point", self._update_tile_state)
self.network_client.wifi_device.connect(
"notify::wireless-enabled", self._update_wifi_status_ui
)
self.network_client.wifi_device.connect(
"notify::active-access-point", self._update_tile_state
)
# Only update UI if content has been created
if self._content_created and hasattr(self, 'refresh_button'):
if self._content_created and hasattr(self, "refresh_button"):
self._update_wifi_status_ui()
# Update tile icon immediately
self._update_tile_icon()
else:
# Only show error if content has been created
if self._content_created and hasattr(self, 'status_label'):
if self._content_created and hasattr(self, "status_label"):
self.status_label.set_label("Wi-Fi device not available.")
self.status_label.set_visible(True)
if hasattr(self, 'refresh_button'):
if hasattr(self, "refresh_button"):
self.refresh_button.set_sensitive(False)
def _update_wifi_status_ui(self, *_):
"""Update WiFi status UI elements."""
if not hasattr(self, 'refresh_button') or not self._content_created:
if not hasattr(self, "refresh_button") or not self._content_created:
return # Content not created yet
if self.network_client.wifi_device:
@@ -362,7 +373,10 @@ class Network(Tile):
def _refresh_access_points(self, *_):
"""Refresh access points list."""
if self.network_client.wifi_device and self.network_client.wifi_device.wireless_enabled:
if (
self.network_client.wifi_device
and self.network_client.wifi_device.wireless_enabled
):
self.status_label.set_label("Scanning for Wi-Fi networks...")
self.status_label.set_visible(True)
self._clear_ap_list()
@@ -376,10 +390,13 @@ class Network(Tile):
def _load_access_points(self, *_):
"""Load and display access points."""
if not hasattr(self, 'ap_list_box'):
if not hasattr(self, "ap_list_box"):
return # Content not created yet
if not self.network_client.wifi_device or not self.network_client.wifi_device.wireless_enabled:
if (
not self.network_client.wifi_device
or not self.network_client.wifi_device.wireless_enabled
):
self._clear_ap_list()
self.status_label.set_label("Wi-Fi disabled.")
self.status_label.set_visible(True)
@@ -395,12 +412,12 @@ class Network(Tile):
else:
self.status_label.set_visible(False)
# Sort access points by connection status (connected first) then by signal strength
sorted_aps = sorted(access_points, key=lambda x: (not x.is_active, -x.strength))
sorted_aps = sorted(
access_points, key=lambda x: (not x.is_active, -x.strength)
)
for access_point in sorted_aps:
slot = WifiAccessPointSlot(
access_point, self.network_client, self.network_client.wifi_device
)
self.ap_list_box.add(slot)
self.ap_list_box.show_all()
+56 -56
View File
@@ -3,49 +3,49 @@ Dashboard notification components for Modus.
Provides notification display functionality for the dashboard.
"""
from fabric.widgets.box import Box
from fabric.widgets.label import Label
from fabric.widgets.button import Button
from fabric.widgets.centerbox import CenterBox
from fabric.widgets.scrolledwindow import ScrolledWindow
from gi.repository import GLib, Gtk
import os
import json
import os
from datetime import datetime
from utils.notification_utils import (
get_shared_notification_history,
PERSISTENT_HISTORY_FILE,
create_historical_notification_from_data,
compute_time_label,
)
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.centerbox import CenterBox
from fabric.widgets.label import Label
from fabric.widgets.scrolledwindow import ScrolledWindow
from gi.repository import GLib, Gtk
import utils.icons as icons
from utils.notification_utils import (
PERSISTENT_HISTORY_FILE,
compute_time_label,
create_historical_notification_from_data,
get_shared_notification_history,
)
class DashboardNotificationItem(Box):
"""Individual notification item for dashboard"""
def __init__(self, notification_data, **kwargs):
super().__init__(
name="notification-container",
orientation="v",
h_align="fill",
h_expand=True,
**kwargs
**kwargs,
)
self.notification_data = notification_data
# Create historical notification from data
hist_notif = create_historical_notification_from_data(notification_data)
# Create timestamp
try:
arrival_time = datetime.fromisoformat(hist_notif.timestamp)
except Exception:
arrival_time = datetime.now()
# Create time label
self.time_label = Label(
name="notification-timestamp",
@@ -53,7 +53,7 @@ class DashboardNotificationItem(Box):
h_align="start",
ellipsization="end",
)
# Create notification image box
# For dashboard, we'll use a simple icon instead of trying to load cached images
self.notif_image_box = Box(
@@ -64,7 +64,7 @@ class DashboardNotificationItem(Box):
Box(v_expand=True),
],
)
# Create summary label
self.notif_summary_label = Label(
name="notification-summary",
@@ -72,7 +72,7 @@ class DashboardNotificationItem(Box):
h_align="start",
ellipsization="end",
)
# Create app name label
self.notif_app_name_label = Label(
name="notification-app-name",
@@ -80,7 +80,7 @@ class DashboardNotificationItem(Box):
h_align="start",
ellipsization="end",
)
# Create body label
self.notif_body_label = (
Label(
@@ -95,7 +95,7 @@ class DashboardNotificationItem(Box):
)
if hist_notif.body:
self.notif_body_label.set_single_line_mode(True)
# Create summary box with separators
self.notif_summary_box = Box(
name="notification-summary-box",
@@ -120,7 +120,7 @@ class DashboardNotificationItem(Box):
self.time_label,
],
)
# Create text box
self.notif_text_box = Box(
name="notification-text",
@@ -132,14 +132,14 @@ class DashboardNotificationItem(Box):
self.notif_body_label,
],
)
# Create close button
self.notif_close_button = Button(
name="notif-close-button",
child=Label(name="notif-close-label", markup=icons.cancel),
on_clicked=lambda *_: self.remove_notification(),
)
self.notif_close_button_box = Box(
orientation="v",
children=[
@@ -147,7 +147,7 @@ class DashboardNotificationItem(Box):
Box(v_expand=True),
],
)
# Create main content box
content_box = Box(
name="notification-box-hist",
@@ -158,9 +158,9 @@ class DashboardNotificationItem(Box):
self.notif_close_button_box,
],
)
self.add(content_box)
def remove_notification(self):
"""Remove this notification from the dashboard"""
if self.get_parent():
@@ -170,17 +170,13 @@ class DashboardNotificationItem(Box):
class DashboardNotifications(Box):
"""Dashboard notifications container that shows real notifications"""
def __init__(self, **kwargs):
super().__init__(
name="notification-history",
orientation="v",
**kwargs
)
super().__init__(name="notification-history", orientation="v", **kwargs)
self.notification_items = []
self.max_notifications = 5 # Limit to 5 notifications in dashboard
# Create header components (matching dock notifications)
self.header_label = Label(
name="nhh",
@@ -208,7 +204,7 @@ class DashboardNotifications(Box):
center_children=[self.header_label],
end_children=[self.header_clean],
)
# Create notifications list
self.notifications_list = Box(
name="notifications-list",
@@ -219,7 +215,7 @@ class DashboardNotifications(Box):
h_align="fill",
v_align="fill",
)
# Create "no notifications" label
self.no_notifications_label = Label(
name="no-notif",
@@ -238,7 +234,7 @@ class DashboardNotifications(Box):
h_expand=True,
children=[self.no_notifications_label],
)
# Create scrolled window for notifications list only
self.notifications_scrolled = ScrolledWindow(
name="notification-history-scrolled-window",
@@ -261,51 +257,55 @@ class DashboardNotifications(Box):
# Add the no notifications box initially to the notifications list
self.notifications_list.add(self.no_notifications_box)
# Connect to shared notification history
self._connect_to_notifications()
# Load and sync DND state
self._load_and_sync_dnd_state()
# Load initial notifications
self._load_recent_notifications()
def _connect_to_notifications(self):
"""Connect to the shared notification history for real-time updates"""
try:
self.shared_history = get_shared_notification_history()
self.shared_history.connect("notification-added", self._on_notification_added)
self.shared_history.connect(
"notification-added", self._on_notification_added
)
self.shared_history.connect("dnd-state-changed", self._on_dnd_state_changed)
except Exception as e:
print(f"Could not connect to notification history: {e}")
def _on_notification_added(self, shared_history):
"""Handle new notification added"""
GLib.idle_add(self._refresh_notifications)
def _on_dnd_state_changed(self, notification_history, dnd_enabled):
"""Handle DND state change"""
self.do_not_disturb_enabled = dnd_enabled
self.header_switch.set_active(dnd_enabled)
def _load_and_sync_dnd_state(self):
"""Load and sync DND state from shared history"""
try:
if hasattr(self, 'shared_history'):
if hasattr(self, "shared_history"):
dnd_enabled = self.shared_history.do_not_disturb_enabled
self.header_switch.set_active(dnd_enabled)
self.do_not_disturb_enabled = dnd_enabled
except Exception as e:
print(f"Could not sync DND state: {e}")
def on_do_not_disturb_changed(self, switch, pspec):
"""Handle DND switch toggle"""
self.do_not_disturb_enabled = switch.get_active()
try:
if hasattr(self, 'shared_history'):
self.shared_history.set_do_not_disturb_enabled(self.do_not_disturb_enabled)
if hasattr(self, "shared_history"):
self.shared_history.set_do_not_disturb_enabled(
self.do_not_disturb_enabled
)
except Exception as e:
print(f"Could not update shared DND state: {e}")
@@ -319,7 +319,7 @@ class DashboardNotifications(Box):
# Clear persistent history
try:
if hasattr(self, 'shared_history'):
if hasattr(self, "shared_history"):
self.shared_history.clear_history()
except Exception as e:
print(f"Could not clear shared history: {e}")
@@ -335,7 +335,7 @@ class DashboardNotifications(Box):
notifications = json.load(f)
# Get the most recent notifications (up to max_notifications)
recent_notifications = notifications[-self.max_notifications:]
recent_notifications = notifications[-self.max_notifications :]
for notif_data in reversed(recent_notifications):
self._add_notification_item(notif_data)
+8 -12
View File
@@ -1,20 +1,16 @@
from gi.repository import Gdk
import gi
from fabric.widgets.box import Box
from fabric.widgets.label import Label
from fabric.widgets.centerbox import CenterBox
from fabric.widgets.button import Button
from fabric.widgets.stack import Stack
from fabric.widgets.centerbox import CenterBox
from fabric.widgets.eventbox import EventBox
from services.mpris import PlayerManager, PlayerService
from utils.wiggle_bar import WigglyWidget
import utils.icons as icons
import config.data as data
from fabric.widgets.label import Label
from fabric.widgets.stack import Stack
from loguru import logger
import gi
import config.data as data
import utils.icons as icons
from services.mpris import PlayerManager, PlayerService
from utils.wiggle_bar import WigglyWidget
gi.require_version("Gtk", "3.0")
+2 -1
View File
@@ -23,9 +23,10 @@ Usage:
from fabric.widgets.box import Box
from fabric.widgets.button import Button
from fabric.widgets.label import Label
from fabric.widgets.stack import Stack
from fabric.widgets.revealer import Revealer
from fabric.widgets.stack import Stack
from gi.repository import Gdk
import utils.icons as icons