✨ feat(dashboard): add back button and hover effects to bluetooth and network tiles, improve icon updates

This commit is contained in:
sankalp committed 2025-07-27 05:05:00 +05:45
1 parent 358b04bddc
commit 0218862615
2 files changed
+179 -40

No files matched your search

+68 -33
View File
@@ -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()
+111 -7
View File
@@ -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'):