From 02188626155c1aa57d7e0d655d8c0f7396eacd9e Mon Sep 17 00:00:00 2001 From: S4NKALP Date: Sun, 27 Jul 2025 05:05:00 +0545 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20feat(dashboard):=20add=20back=20but?= =?UTF-8?q?ton=20and=20hover=20effects=20to=20bluetooth=20and=20network=20?= =?UTF-8?q?tiles,=20improve=20icon=20updates?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- modules/dashboard/bluetooth.py | 101 +++++++++++++++++++--------- modules/dashboard/network.py | 118 +++++++++++++++++++++++++++++++-- 2 files changed, 179 insertions(+), 40 deletions(-) diff --git a/modules/dashboard/bluetooth.py b/modules/dashboard/bluetooth.py index 64d8483..fdab532 100644 --- a/modules/dashboard/bluetooth.py +++ b/modules/dashboard/bluetooth.py @@ -5,12 +5,18 @@ 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 +from gi.repository import Gdk from modules.dashboard.tile import Tile import utils.icons as icons +def add_hover_cursor(widget): + widget.add_events(Gdk.EventMask.ENTER_NOTIFY_MASK | Gdk.EventMask.LEAVE_NOTIFY_MASK) + widget.connect("enter-notify-event", lambda w, e: w.get_window().set_cursor(Gdk.Cursor.new_from_name(w.get_display(), "pointer")) if w.get_window() else None) + widget.connect("leave-notify-event", lambda w, e: w.get_window().set_cursor(None) if w.get_window() else None) + + class BluetoothDeviceSlot(CenterBox): """A widget representing a single Bluetooth device in the dashboard.""" @@ -99,6 +105,8 @@ class Bluetooth(Tile): 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::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) # Initialize state self._update_tile_state() @@ -115,20 +123,44 @@ class Bluetooth(Tile): self.add_style_class("off") self.label.set_label("Off") + # Update tile icon based on Bluetooth state + self._update_tile_icon() + + def _update_tile_icon(self): + """Update the tile icon based on Bluetooth status and connected devices.""" + if not self.bluetooth_client.enabled: + # Bluetooth is disabled + self.icon.set_markup(icons.bluetooth_off) + return + + # Bluetooth is enabled - check for connected devices + connected_devices = self.bluetooth_client.connected_devices + if connected_devices: + # Has connected devices + self.icon.set_markup(icons.bluetooth_connected) + else: + # Enabled but no connected devices + if self.bluetooth_client.scanning: + # Currently scanning - could add animation here if desired + self.icon.set_markup(icons.bluetooth) + else: + # Idle state + self.icon.set_markup(icons.bluetooth) + def create_content(self): """Create the detailed bluetooth content for the dashboard.""" - # Create Bluetooth toggle switch (like notifications DND switch) - self.bluetooth_switch = Gtk.Switch(name="bluetooth-switch") - self.bluetooth_switch.set_vexpand(False) - self.bluetooth_switch.set_valign(Gtk.Align.CENTER) - self.bluetooth_switch.set_active(self.bluetooth_client.enabled) - self.bluetooth_switch.connect("notify::active", self._on_bluetooth_toggle) - - # Create Bluetooth status icon - self.bluetooth_status_icon = Label( - name="bluetooth-status-icon", - markup=icons.bluetooth if self.bluetooth_client.enabled else icons.bluetooth_off + # Create back button with chevron left icon + self.back_button_icon = Label( + 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 + ) + add_hover_cursor(self.back_button) # Create scan button with icon self.scan_label = Label(name="bluetooth-scan-label", markup=icons.radar) @@ -138,6 +170,7 @@ class Bluetooth(Tile): tooltip_text="Scan for Bluetooth devices", on_clicked=lambda *_: self.bluetooth_client.toggle_scan(), ) + add_hover_cursor(self.scan_button) # Create device containers self.paired_box = Box(spacing=2, orientation="v") @@ -149,6 +182,14 @@ class Bluetooth(Tile): content_box.add(Label(name="bluetooth-section", label="Available")) content_box.add(self.available_box) + # Create header + header_box = CenterBox( + name="bluetooth-header", + start_children=[self.back_button], + center_children=[Label(name="bluetooth-title", label="Bluetooth Devices")], + end_children=[self.scan_button] + ) + # Main container main_container = Box( name="bluetooth-content", @@ -159,12 +200,7 @@ class Bluetooth(Tile): h_align="fill", style_classes=["tile-content"], children=[ - CenterBox( - name="bluetooth-header", - start_children=[self.bluetooth_switch, self.bluetooth_status_icon], - center_children=Label(name="bluetooth-text", label="Bluetooth Devices"), - end_children=self.scan_button, - ), + header_box, ScrolledWindow( name="bluetooth-devices", min_content_size=(-1, -1), @@ -186,6 +222,10 @@ class Bluetooth(Tile): """Handle new device discovery.""" if not (device := client.get_device(address)): return + + # Connect to device changes to update tile icon when connection state changes + device.connect("changed", self._on_device_connection_changed) + slot = BluetoothDeviceSlot(device) if device.paired: @@ -203,25 +243,20 @@ class Bluetooth(Tile): self.scan_button.remove_style_class("scanning") self.scan_button.set_tooltip_text("Scan for Bluetooth devices") - def _on_bluetooth_toggle(self, switch, *_): - """Handle Bluetooth toggle switch.""" - enabled = switch.get_active() - self.bluetooth_client.enabled = enabled - self._update_content_state() + # Update tile icon when scanning state changes + self._update_tile_icon() + + def _on_back_clicked(self, *_): + """Handle back button click - return to default (notifications) view.""" + if self.dashboard_instance: + self.dashboard_instance.reset_to_default() def _on_bluetooth_state_changed(self, *_): """Handle bluetooth state change.""" self._update_tile_state() - self._update_content_state() - def _update_content_state(self): - """Update content area based on Bluetooth state.""" - if hasattr(self, 'bluetooth_switch'): - self.bluetooth_switch.set_active(self.bluetooth_client.enabled) - - if hasattr(self, 'bluetooth_status_icon'): - self.bluetooth_status_icon.set_markup( - icons.bluetooth if self.bluetooth_client.enabled else icons.bluetooth_off - ) + def _on_device_connection_changed(self, *_): + """Handle device connection/disconnection changes.""" + self._update_tile_icon() diff --git a/modules/dashboard/network.py b/modules/dashboard/network.py index 508e25c..6bc5e64 100644 --- a/modules/dashboard/network.py +++ b/modules/dashboard/network.py @@ -5,13 +5,19 @@ 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 +from gi.repository import Gtk, Gdk, GLib from modules.dashboard.tile import Tile from services.network import NetworkClient import utils.icons as icons +def add_hover_cursor(widget): + widget.add_events(Gdk.EventMask.ENTER_NOTIFY_MASK | Gdk.EventMask.LEAVE_NOTIFY_MASK) + widget.connect("enter-notify-event", lambda w, e: w.get_window().set_cursor(Gdk.Cursor.new_from_name(w.get_display(), "pointer")) if w.get_window() else None) + widget.connect("leave-notify-event", lambda w, e: w.get_window().set_cursor(None) if w.get_window() else None) + + class WifiAccessPointSlot(CenterBox): """A widget representing a single WiFi access point in the dashboard.""" @@ -37,10 +43,11 @@ class WifiAccessPointSlot(CenterBox): self.connect_button = Button( name="wifi-connect-button", label="Connected" if self.is_active else "Connect", - sensitive=not self.is_active, + sensitive=True, # Always clickable - can connect or disconnect on_clicked=self._on_connect_clicked, style_classes=["connected"] if self.is_active else None, ) + add_hover_cursor(self.connect_button) self.set_start_children([ Box(spacing=8, h_expand=True, h_align="fill", children=[ @@ -52,10 +59,16 @@ class WifiAccessPointSlot(CenterBox): def _on_connect_clicked(self, *_): """Handle connect button click.""" - if not self.is_active and self.access_point.bssid: + if self.is_active: + # Disconnect from current network + self.connect_button.set_label("Disconnecting...") + self.connect_button.set_sensitive(False) + self.wifi_service.disconnect_wifi() + elif self.access_point.bssid: + # Connect to this network self.connect_button.set_label("Connecting...") self.connect_button.set_sensitive(False) - self.network_service.connect_wifi_bssid(self.access_point.bssid) + self.wifi_service.connect_to_wifi(self.access_point) class Network(Tile): @@ -70,6 +83,11 @@ class Network(Tile): ) self.state = False + # Animation properties + self._animation_timeout_id = None + self._animation_step = 0 + self._animation_direction = 1 + super().__init__( label="Wi-Fi", props=self.label, @@ -98,15 +116,46 @@ class Network(Tile): """Handle network connection state changes.""" self._update_tile_state() + 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 = self.network_client.wifi_device + if not self.icon or not wifi or not wifi.wireless_enabled: + self._stop_animation() + return False + + if wifi.active_access_point: + self._stop_animation() + return False + + GLib.idle_add(self.icon.set_markup, wifi_icons[self._animation_step]) + + self._animation_step = (self._animation_step + 1) % len(wifi_icons) + + return True + + def _start_animation(self): + if self._animation_timeout_id is None: + self._animation_step = 0 + self._animation_direction = 1 + self._animation_timeout_id = GLib.timeout_add(500, self._animate_searching) + + def _stop_animation(self): + if self._animation_timeout_id is not None: + GLib.source_remove(self._animation_timeout_id) + self._animation_timeout_id = None + def _update_tile_state(self): """Update tile visual state based on WiFi status.""" connected = False con_label = "Disconnected" + wifi = self.network_client.wifi_device - if self.network_client.wifi_device and self.network_client.wifi_device.active_access_point: + if wifi and wifi.active_access_point: connected = True - con_label = self.network_client.wifi_device.active_access_point.ssid - elif self.network_client.wifi_device and not self.network_client.wifi_device.wireless_enabled: + con_label = wifi.active_access_point.ssid + elif wifi and not wifi.wireless_enabled: con_label = "Off" if self.state != connected: @@ -121,6 +170,55 @@ class Network(Tile): self.label.set_label(con_label) + # Update tile icon based on WiFi state + self._update_tile_icon() + + def _update_tile_icon(self): + """Update the tile icon based on network status""" + wifi = self.network_client.wifi_device + ethernet = self.network_client.ethernet_device + + if wifi and not wifi.wireless_enabled: + self._stop_animation() + self.icon.set_markup(icons.wifi_off) + return + + if wifi and wifi.wireless_enabled: + if wifi.active_access_point: + self._stop_animation() + # Show signal strength icon + if wifi.active_access_point.strength > 0: + strength = wifi.active_access_point.strength + if strength < 25: + self.icon.set_markup(icons.wifi_0) + elif strength < 50: + self.icon.set_markup(icons.wifi_1) + elif strength < 75: + self.icon.set_markup(icons.wifi_2) + else: + self.icon.set_markup(icons.wifi_3) + else: + self.icon.set_markup(icons.wifi_1) + else: + # WiFi is enabled but not connected - start animation + self._start_animation() + + try: + primary_device = self.network_client.primary_device + except AttributeError: + primary_device = "wireless" + + if primary_device == "wired": + self._stop_animation() + if ethernet and ethernet.internet == "activated": + self.icon.set_markup(icons.world) + else: + self.icon.set_markup(icons.world_off) + else: + if not wifi: + self._stop_animation() + self.icon.set_markup(icons.wifi_off) + def create_content(self): """Create the detailed network content for the dashboard.""" @@ -143,6 +241,7 @@ class Network(Tile): tooltip_text="Scan for Wi-Fi networks", on_clicked=self._refresh_access_points ) + add_hover_cursor(self.refresh_button) # Create back button with chevron left icon self.back_button_icon = Label( @@ -155,6 +254,7 @@ class Network(Tile): tooltip_text="Back to notifications", on_clicked=self._on_back_clicked ) + add_hover_cursor(self.back_button) # Create header header_box = CenterBox( @@ -212,10 +312,14 @@ class Network(Tile): 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) # Only update UI if content has been created 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'):