From 1e2bfe938088e4d88319c2b10935596376fb3b83 Mon Sep 17 00:00:00 2001 From: cheykrym Date: Wed, 1 Apr 2026 00:32:33 +0300 Subject: [PATCH] bl new design --- screens/setting/bluetooth_screen.py | 222 ++++++++++++++++++++-------- services/bluetooth_service.py | 10 ++ themes/day.py | 23 ++- themes/night.py | 22 ++- 4 files changed, 213 insertions(+), 64 deletions(-) diff --git a/screens/setting/bluetooth_screen.py b/screens/setting/bluetooth_screen.py index 2706dbb..82585cc 100644 --- a/screens/setting/bluetooth_screen.py +++ b/screens/setting/bluetooth_screen.py @@ -1,6 +1,6 @@ from __future__ import annotations -from PySide6.QtCore import Qt, QTimer, QSettings, QEvent +from PySide6.QtCore import Qt, QTimer, QSettings, QEvent, Signal from PySide6.QtGui import QFont from PySide6.QtWidgets import ( QWidget, @@ -11,12 +11,102 @@ from PySide6.QtWidgets import ( QListWidget, QListWidgetItem, QScroller, + QFrame, ) import build_info from services.bluetooth_service import BluetoothService, BluetoothDevice +class BluetoothDeviceCard(QFrame): + """Карточка Bluetooth устройства.""" + + connect_clicked = Signal(str) # mac + disconnect_clicked = Signal(str) # mac + remove_clicked = Signal(str) # mac + + def __init__(self, device: BluetoothDevice, connected: bool, parent=None): + super().__init__(parent) + self._device = device + self._connected = connected + + self.setObjectName("BluetoothDeviceCard") + self.setFrameShape(QFrame.StyledPanel) + self.setCursor(Qt.PointingHandCursor) + + root = QHBoxLayout(self) + root.setContentsMargins(14, 10, 14, 10) + root.setSpacing(12) + + # Индикатор статуса (цветная точка) + self.status_indicator = QLabel("●") + self.status_indicator.setObjectName("BluetoothStatusIndicator") + self.status_indicator.setFont(QFont("", 18)) + self.status_indicator.setAlignment(Qt.AlignCenter) + self.status_indicator.setFixedSize(24, 24) + self._update_status_indicator() + + # Информация об устройстве + info_col = QVBoxLayout() + info_col.setContentsMargins(0, 0, 0, 0) + info_col.setSpacing(2) + + self.name_label = QLabel(device.name if device.name else "Неизвестное устройство") + self.name_label.setObjectName("BluetoothDeviceName") + self.name_label.setFont(QFont("", 15, 600)) + + self.mac_label = QLabel(device.mac) + self.mac_label.setObjectName("BluetoothDeviceMac") + self.mac_label.setFont(QFont("", 11)) + + self.status_label = QLabel("Подключено" if connected else "Не подключено") + self.status_label.setObjectName("BluetoothDeviceStatus") + self.status_label.setFont(QFont("", 12)) + + info_col.addWidget(self.name_label) + info_col.addWidget(self.mac_label) + info_col.addWidget(self.status_label) + + # Кнопка удаления (крестик) + self.remove_btn = QPushButton("✕") + self.remove_btn.setObjectName("BluetoothRemoveBtn") + self.remove_btn.setFixedSize(36, 36) + self.remove_btn.setCursor(Qt.PointingHandCursor) + self.remove_btn.clicked.connect(self._on_remove) + + root.addWidget(self.status_indicator) + root.addLayout(info_col, 1) + root.addWidget(self.remove_btn) + + def _update_status_indicator(self): + """Обновить цвет индикатора.""" + if self._connected: + self.status_indicator.setStyleSheet("color: #4ade80;") # зелёный + self.status_label.setStyleSheet("color: #4ade80;") + self.status_label.setText("Подключено") + else: + self.status_indicator.setStyleSheet("color: #9ca3af;") # серый + self.status_label.setStyleSheet("color: #9ca3af;") + self.status_label.setText("Не подключено") + + def update_connected(self, connected: bool): + """Обновить статус подключения.""" + self._connected = connected + self._update_status_indicator() + + def _on_remove(self): + """Обработчик нажатия кнопки удаления.""" + self.remove_clicked.emit(self._device.mac) + + def mousePressEvent(self, event): + """Обработка клика по карточке.""" + if self._connected: + self.disconnect_clicked.emit(self._device.mac) + else: + self.connect_clicked.emit(self._device.mac) + super().mousePressEvent(event) + + class BluetoothScreen(QWidget): def __init__(self, on_back): super().__init__() @@ -38,9 +128,8 @@ class BluetoothScreen(QWidget): self.list = QListWidget() self.list.setObjectName("BluetoothList") - self.list.setSpacing(6) - self.list.setSelectionMode(QListWidget.SingleSelection) - self.list.itemSelectionChanged.connect(self._on_select) + self.list.setSpacing(8) + self.list.setSelectionMode(QListWidget.NoSelection) # убираем выделение, т.к. карточки кликабельны QScroller.scroller(self.list.viewport()).grabGesture( self.list.viewport(), @@ -67,14 +156,12 @@ class BluetoothScreen(QWidget): self.btn_connect = QPushButton("Подключить") self.btn_connect.setObjectName("BluetoothActionBtnPrimary") self.btn_connect.setMinimumHeight(56) - self.btn_connect.setEnabled(False) - self.btn_connect.clicked.connect(self._connect_selected) + self.btn_connect.clicked.connect(self._connect_last_selected) self.btn_disconnect = QPushButton("Отключить") self.btn_disconnect.setObjectName("BluetoothActionBtn") self.btn_disconnect.setMinimumHeight(56) - self.btn_disconnect.setEnabled(False) - self.btn_disconnect.clicked.connect(self._disconnect_selected) + self.btn_disconnect.clicked.connect(self._disconnect_last_selected) actions.addWidget(self.btn_visible, 1) actions.addWidget(self.btn_refresh, 1) @@ -92,35 +179,73 @@ class BluetoothScreen(QWidget): """Обновить список сопряженных устройств.""" devices = self._bt_service.get_paired_devices() self.list.clear() - + self._cards: dict[str, BluetoothDeviceCard] = {} + for dev in devices: - label = f"{dev.name} ({dev.mac})" if dev.name else dev.mac - item = QListWidgetItem(label) - item.setData(Qt.UserRole, dev.mac) + connected = self._bt_service.is_connected(dev.mac) + card = BluetoothDeviceCard(dev, connected, self) + card.connect_clicked.connect(self._connect_device) + card.disconnect_clicked.connect(self._disconnect_device) + card.remove_clicked.connect(self._remove_device) + + item = QListWidgetItem() + item.setSizeHint(card.sizeHint()) self.list.addItem(item) - + self.list.setItemWidget(item, card) + self._cards[dev.mac] = card + self._update_status() - def _on_select(self): - """Обработчик выбора устройства.""" - has_selection = self._selected_mac() is not None - self.btn_connect.setEnabled(has_selection) - self.btn_disconnect.setEnabled(has_selection) - self._update_status() + def _connect_last_selected(self): + """Подключить последнее выбранное устройство.""" + if self._cards: + last_mac = self._settings.value("bluetooth/last_mac", "") + if last_mac and last_mac in self._cards: + self._connect_device(last_mac) - def _selected_mac(self) -> str | None: - """Получить MAC-адрес выбранного устройства.""" - items = self.list.selectedItems() - if not items: - return None - return items[0].data(Qt.UserRole) + def _disconnect_last_selected(self): + """Отключить последнее выбранное устройство.""" + if self._cards: + last_mac = self._settings.value("bluetooth/last_mac", "") + if last_mac and last_mac in self._cards: + self._disconnect_device(last_mac) - def _auto_connect_last(self): - """Автоподключение к последнему устройству.""" - last_mac = self._settings.value("bluetooth/last_mac", "") - if not last_mac: + def _connect_device(self, mac: str): + """Подключиться к устройству по MAC.""" + if mac not in self._cards: return - self._connect_device(last_mac, silent=True) + success = self._bt_service.connect_device(mac) + if not success: + self.status.setText(f"Статус: ошибка ({self._bt_service.last_error})") + self._settings.setValue("bluetooth/last_mac", mac) + QTimer.singleShot(300, self._update_card_status, mac) + + def _disconnect_device(self, mac: str): + """Отключить устройство по MAC.""" + if mac not in self._cards: + return + success = self._bt_service.disconnect_device(mac) + if not success: + self.status.setText(f"Статус: ошибка ({self._bt_service.last_error})") + else: + self.status.setText(f"Статус: отключено от {mac}") + QTimer.singleShot(300, self._update_card_status, mac) + + def _remove_device(self, mac: str): + """Удалить устройство из списка сопряженных.""" + success = self._bt_service.remove_device(mac) + if success: + self.status.setText(f"Статус: устройство {mac} удалено") + QTimer.singleShot(300, self.refresh_list) + else: + self.status.setText(f"Статус: ошибка удаления ({self._bt_service.last_error})") + + def _update_card_status(self, mac: str): + """Обновить статус на карточке устройства.""" + if mac in self._cards: + connected = self._bt_service.is_connected(mac) + self._cards[mac].update_connected(connected) + self._update_status() def _make_visible(self): """Сделать устройство видимым для сопряжения.""" @@ -142,40 +267,13 @@ class BluetoothScreen(QWidget): self._bt_service.make_discoverable() self._discoverable_timer.start(9000) # 9 секунд - def _connect_selected(self): - """Подключить выбранное устройство.""" - mac = self._selected_mac() - if not mac: - return - self._connect_device(mac, silent=False) - - def _connect_device(self, mac: str, silent: bool): - """Подключиться к устройству по MAC.""" - success = self._bt_service.connect_device(mac) - if not silent: - if not success: - self.status.setText(f"Статус: ошибка ({self._bt_service.last_error})") - else: - self.status.setText(f"Статус: подключаемся к {mac}") - self._settings.setValue("bluetooth/last_mac", mac) - QTimer.singleShot(300, self._update_status) - - def _disconnect_selected(self): - """Отключить выбранное устройство.""" - mac = self._selected_mac() - if not mac: - return - success = self._bt_service.disconnect_device(mac) - if not success: - self.status.setText(f"Статус: ошибка ({self._bt_service.last_error})") - else: - self.status.setText(f"Статус: отключено от {mac}") - QTimer.singleShot(300, self._update_status) - def _update_status(self): """Обновить текстовый статус.""" - mac = self._selected_mac() - self.status.setText(self._bt_service.get_status_text(mac)) + last_mac = self._settings.value("bluetooth/last_mac", "") + if not last_mac or last_mac not in self._cards: + self.status.setText("Статус: —") + return + self.status.setText(self._bt_service.get_status_text(last_mac)) def showEvent(self, event): """Экран показан — запускаем режим сопряжения и обновление списка.""" diff --git a/services/bluetooth_service.py b/services/bluetooth_service.py index fc8d461..fe39b31 100644 --- a/services/bluetooth_service.py +++ b/services/bluetooth_service.py @@ -81,6 +81,16 @@ class BluetoothService(QObject): self.connected_changed.emit(mac, False) return success + def remove_device(self, mac: str) -> bool: + """Удалить устройство из списка сопряженных.""" + self._last_error = "" + # Сначала отключаем, если подключено + if self.is_connected(mac): + self._run_cmd(["bluetoothctl", "disconnect", mac]) + result = self._run_cmd(["bluetoothctl", "remove", mac]) + success = not self._last_error + return success + def make_discoverable(self, timeout_sec: int = 10) -> bool: """Сделать устройство видимым для сопряжения. diff --git a/themes/day.py b/themes/day.py index 5f63b29..71cb1f4 100644 --- a/themes/day.py +++ b/themes/day.py @@ -140,10 +140,31 @@ QScrollArea > QWidget > QWidget { background: transparent; } padding: 6px; } #BluetoothList::item { - padding: 12px 10px; + padding: 0; border-radius: 10px; } #BluetoothList::item:selected { background: #F3F4F6; } +#BluetoothDeviceCard { + background: #FFFFFF; + border-radius: 14px; + border: 1px solid #E5E7EB; +} +#BluetoothDeviceCard:hover { background: #F9FAFB; } +#BluetoothDeviceName { color: #111827; } +#BluetoothDeviceMac { color: rgba(107,114,128,0.95); } +#BluetoothDeviceStatus { color: rgba(107,114,128,0.95); } +#BluetoothStatusIndicator { background: transparent; } +#BluetoothRemoveBtn { + background: transparent; + color: rgba(107,114,128,0.95); + border-radius: 18px; + font-size: 18px; + font-weight: 700; +} +#BluetoothRemoveBtn:hover { + background: #FEE2E2; + color: #DC2626; +} #BluetoothActionBtn { background: #FFFFFF; border-radius: 12px; diff --git a/themes/night.py b/themes/night.py index 17205ff..44d083b 100644 --- a/themes/night.py +++ b/themes/night.py @@ -130,10 +130,30 @@ QScrollArea > QWidget > QWidget { background: transparent; } padding: 6px; } #BluetoothList::item { - padding: 12px 10px; + padding: 0; border-radius: 10px; } #BluetoothList::item:selected { background: #1B2330; } +#BluetoothDeviceCard { + background: #141A22; + border-radius: 14px; +} +#BluetoothDeviceCard:hover { background: #1B2330; } +#BluetoothDeviceName { color: #E6EAF0; } +#BluetoothDeviceMac { color: rgba(138,147,166,0.95); } +#BluetoothDeviceStatus { color: rgba(138,147,166,0.95); } +#BluetoothStatusIndicator { background: transparent; } +#BluetoothRemoveBtn { + background: transparent; + color: rgba(138,147,166,0.95); + border-radius: 18px; + font-size: 18px; + font-weight: 700; +} +#BluetoothRemoveBtn:hover { + background: #B91C1C; + color: #FEE2E2; +} #BluetoothActionBtn { background: #141A22; border-radius: 12px;