Files
noctalia-shell/Modules/Panels/Bluetooth/BluetoothPanel.qml
T

266 lines
8.7 KiB
QML

import QtQuick
import QtQuick.Controls
import QtQuick.Layouts
import Quickshell
import Quickshell.Bluetooth
import qs.Commons
import qs.Modules.MainScreen
import qs.Services.Networking
import qs.Services.UI
import qs.Widgets
SmartPanel {
id: root
preferredWidth: Math.round(440 * Style.uiScaleRatio)
preferredHeight: Math.round(500 * Style.uiScaleRatio)
panelContent: Rectangle {
color: Color.transparent
// Calculate content height based on header + devices list (or minimum for empty states)
property real headerHeight: headerRow.implicitHeight + Style.marginM * 2
property real devicesHeight: devicesList.implicitHeight
property real calculatedHeight: (devicesHeight !== 0) ? (headerHeight + devicesHeight + Style.marginL * 2 + Style.marginM) : (280 * Style.uiScaleRatio)
property real contentPreferredHeight: (BluetoothService.adapter && BluetoothService.adapter.enabled) ? Math.min(root.preferredHeight, calculatedHeight) : Math.min(root.preferredHeight, 280 * Style.uiScaleRatio)
ColumnLayout {
id: mainColumn
anchors.fill: parent
anchors.margins: Style.marginL
spacing: Style.marginM
// Header
NBox {
Layout.fillWidth: true
Layout.preferredHeight: headerRow.implicitHeight + Style.marginM * 2
RowLayout {
id: headerRow
anchors.fill: parent
anchors.margins: Style.marginM
spacing: Style.marginM
NIcon {
icon: "bluetooth"
pointSize: Style.fontSizeXXL
color: Color.mPrimary
}
NText {
text: I18n.tr("bluetooth.panel.title")
pointSize: Style.fontSizeL
font.weight: Style.fontWeightBold
color: Color.mOnSurface
Layout.fillWidth: true
}
NToggle {
id: bluetoothSwitch
checked: BluetoothService.enabled
onToggled: function(checked) { BluetoothService.setBluetoothEnabled(checked); }
baseSize: Style.baseWidgetSize * 0.65
}
// Discoverability toggle (advertising)
NIconButton {
enabled: BluetoothService.enabled
icon: BluetoothService.discoverable ? "broadcast" : "broadcast-off"
tooltipText: I18n.tr("bluetooth.panel.discoverable")
baseSize: Style.baseWidgetSize * 0.8
onClicked: {
BluetoothService.setDiscoverable(!BluetoothService.discoverable);
}
}
NIconButton {
enabled: BluetoothService.enabled
icon: BluetoothService.adapter && BluetoothService.adapter.discovering ? "stop" : "refresh"
tooltipText: I18n.tr("tooltips.refresh-devices")
baseSize: Style.baseWidgetSize * 0.8
onClicked: {
if (BluetoothService.adapter) {
BluetoothService.adapter.discovering = !BluetoothService.adapter.discovering;
}
}
}
NIconButton {
icon: "close"
tooltipText: I18n.tr("tooltips.close")
baseSize: Style.baseWidgetSize * 0.8
onClicked: {
root.close();
}
}
}
}
// Adapter not available of disabled
NBox {
id: disabledBox
visible: !(BluetoothService.adapter && BluetoothService.adapter.enabled)
Layout.fillWidth: true
Layout.fillHeight: true
// Center the content within this rectangle
ColumnLayout {
anchors.fill: parent
spacing: Style.marginM
Item {
Layout.fillHeight: true
}
NIcon {
icon: "bluetooth-off"
pointSize: 48
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
NText {
text: I18n.tr("bluetooth.panel.disabled")
pointSize: Style.fontSizeL
color: Color.mOnSurfaceVariant
Layout.alignment: Qt.AlignHCenter
}
NText {
text: I18n.tr("bluetooth.panel.enable-message")
pointSize: Style.fontSizeS
color: Color.mOnSurfaceVariant
horizontalAlignment: Text.AlignHCenter
Layout.fillWidth: true
wrapMode: Text.WordWrap
}
Item {
Layout.fillHeight: true
}
}
}
NScrollView {
visible: BluetoothService.adapter && BluetoothService.adapter.enabled
Layout.fillWidth: true
Layout.fillHeight: true
horizontalPolicy: ScrollBar.AlwaysOff
verticalPolicy: ScrollBar.AsNeeded
clip: true
contentWidth: availableWidth
ColumnLayout {
id: devicesList
width: parent.width
spacing: Style.marginM
// Connected devices
BluetoothDevicesList {
label: I18n.tr("bluetooth.panel.connected-devices")
property var items: {
if (!BluetoothService.adapter || !Bluetooth.devices)
return [];
var filtered = Bluetooth.devices.values.filter(function(dev) { return dev && !dev.blocked && dev.connected; });
filtered = BluetoothService.dedupeDevices(filtered);
return BluetoothService.sortDevices(filtered);
}
model: items
visible: items.length > 0
Layout.fillWidth: true
}
// Paired devices
BluetoothDevicesList {
label: I18n.tr("bluetooth.panel.paired-devices")
tooltipText: I18n.tr("tooltips.connect-disconnect-devices")
property var items: {
if (!BluetoothService.adapter || !Bluetooth.devices)
return [];
var filtered = Bluetooth.devices.values.filter(function(dev) { return dev && !dev.blocked && !dev.connected && (dev.paired || dev.trusted); });
filtered = BluetoothService.dedupeDevices(filtered);
return BluetoothService.sortDevices(filtered);
}
model: items
visible: items.length > 0
Layout.fillWidth: true
}
// Available devices (for pairing)
BluetoothDevicesList {
label: I18n.tr("bluetooth.panel.available-devices")
property var items: {
if (!BluetoothService.adapter || !Bluetooth.devices)
return [];
var filtered = Bluetooth.devices.values.filter(function(dev) { return dev && !dev.blocked && !dev.paired && !dev.trusted; });
filtered = BluetoothService.dedupeDevices(filtered);
return BluetoothService.sortDevices(filtered);
}
model: items
visible: items.length > 0
Layout.fillWidth: true
}
// Fallback - No devices, scanning
NBox {
Layout.fillWidth: true
Layout.preferredHeight: columnScanning.implicitHeight + Style.marginM * 2
visible: {
if (!BluetoothService.adapter || !BluetoothService.adapter.discovering || !Bluetooth.devices) {
return false;
}
var availableCount = Bluetooth.devices.values.filter(function(dev) {
return dev && !dev.paired && !dev.pairing && !dev.blocked && (dev.signalStrength === undefined || dev.signalStrength > 0);
}).length;
return (availableCount === 0);
}
ColumnLayout {
id: columnScanning
anchors.fill: parent
anchors.margins: Style.marginM
spacing: Style.marginM
RowLayout {
Layout.alignment: Qt.AlignHCenter
spacing: Style.marginXS
NIcon {
icon: "refresh"
pointSize: Style.fontSizeXXL * 1.5
color: Color.mPrimary
RotationAnimation on rotation {
running: true
loops: Animation.Infinite
from: 0
to: 360
duration: Style.animationSlow * 4
}
}
NText {
text: I18n.tr("bluetooth.panel.scanning")
pointSize: Style.fontSizeL
color: Color.mOnSurface
}
}
NText {
text: I18n.tr("bluetooth.panel.pairing-mode")
pointSize: Style.fontSizeM
color: Color.mOnSurfaceVariant
horizontalAlignment: Text.AlignHCenter
Layout.fillWidth: true
wrapMode: Text.WordWrap
}
}
}
}
}
}
}
}