396 lines
14 KiB
QML
396 lines
14 KiB
QML
import QtQuick
|
|
import QtQuick.Layouts
|
|
import QtQuick.Controls
|
|
import Quickshell
|
|
import Quickshell.Wayland
|
|
import qs.Commons
|
|
import qs.Services
|
|
import qs.Widgets
|
|
|
|
// Loader for Bluetooth menu
|
|
NLoader {
|
|
id: root
|
|
|
|
content: Component {
|
|
NPanel {
|
|
id: bluetoothPanel
|
|
|
|
function hide() {
|
|
bluetoothMenuRect.scaleValue = 0.8
|
|
bluetoothMenuRect.opacityValue = 0.0
|
|
hideTimer.start()
|
|
}
|
|
|
|
// Connect to NPanel's dismissed signal to handle external close events
|
|
Connections {
|
|
target: bluetoothPanel
|
|
ignoreUnknownSignals: true
|
|
function onDismissed() {
|
|
// Start hide animation
|
|
bluetoothMenuRect.scaleValue = 0.8
|
|
bluetoothMenuRect.opacityValue = 0.0
|
|
// Hide after animation completes
|
|
hideTimer.start()
|
|
}
|
|
}
|
|
|
|
// Also handle visibility changes from external sources
|
|
onVisibleChanged: {
|
|
if (visible && Settings.data.network.bluetoothEnabled) {
|
|
// Always refresh devices when menu opens to get fresh device objects
|
|
BluetoothService.refreshDevices()
|
|
} else if (bluetoothMenuRect.opacityValue > 0) {
|
|
// Start hide animation
|
|
bluetoothMenuRect.scaleValue = 0.8
|
|
bluetoothMenuRect.opacityValue = 0.0
|
|
// Hide after animation completes
|
|
hideTimer.start()
|
|
}
|
|
}
|
|
|
|
// Timer to hide panel after animation
|
|
Timer {
|
|
id: hideTimer
|
|
interval: Style.animationSlow
|
|
repeat: false
|
|
onTriggered: {
|
|
bluetoothPanel.visible = false
|
|
bluetoothPanel.dismissed()
|
|
}
|
|
}
|
|
|
|
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
|
|
|
Rectangle {
|
|
id: bluetoothMenuRect
|
|
color: Colors.mSurface
|
|
radius: Style.radiusLarge * scaling
|
|
border.color: Colors.mOutlineVariant
|
|
border.width: Math.max(1, Style.borderThin * scaling)
|
|
width: 340 * scaling
|
|
height: 500 * scaling
|
|
anchors.top: parent.top
|
|
anchors.right: parent.right
|
|
anchors.topMargin: Style.marginTiny * scaling
|
|
anchors.rightMargin: Style.marginTiny * scaling
|
|
|
|
// Animation properties
|
|
property real scaleValue: 0.8
|
|
property real opacityValue: 0.0
|
|
|
|
scale: scaleValue
|
|
opacity: opacityValue
|
|
|
|
// Animate in when component is completed
|
|
Component.onCompleted: {
|
|
scaleValue = 1.0
|
|
opacityValue = 1.0
|
|
}
|
|
|
|
// Animation behaviors
|
|
Behavior on scale {
|
|
NumberAnimation {
|
|
duration: Style.animationSlow
|
|
easing.type: Easing.OutExpo
|
|
}
|
|
}
|
|
|
|
Behavior on opacity {
|
|
NumberAnimation {
|
|
duration: Style.animationNormal
|
|
easing.type: Easing.OutQuad
|
|
}
|
|
}
|
|
|
|
ColumnLayout {
|
|
anchors.fill: parent
|
|
anchors.margins: Style.marginLarge * scaling
|
|
spacing: Style.marginMedium * scaling
|
|
|
|
RowLayout {
|
|
Layout.fillWidth: true
|
|
spacing: Style.marginMedium * scaling
|
|
|
|
NText {
|
|
text: "bluetooth"
|
|
font.family: "Material Symbols Outlined"
|
|
font.pointSize: Style.fontSizeXL * scaling
|
|
color: Colors.mPrimary
|
|
}
|
|
|
|
NText {
|
|
text: "Bluetooth"
|
|
font.pointSize: Style.fontSizeLarge * scaling
|
|
font.bold: true
|
|
color: Colors.mOnSurface
|
|
Layout.fillWidth: true
|
|
}
|
|
|
|
NIconButton {
|
|
icon: "refresh"
|
|
tooltipText: "Refresh Devices"
|
|
sizeMultiplier: 0.8
|
|
enabled: Settings.data.network.bluetoothEnabled && !BluetoothService.isDiscovering
|
|
onClicked: {
|
|
BluetoothService.refreshDevices()
|
|
}
|
|
}
|
|
|
|
NIconButton {
|
|
icon: "close"
|
|
tooltipText: "Close"
|
|
sizeMultiplier: 0.8
|
|
onClicked: {
|
|
bluetoothPanel.hide()
|
|
}
|
|
}
|
|
}
|
|
|
|
NDivider {}
|
|
|
|
Item {
|
|
Layout.fillWidth: true
|
|
Layout.fillHeight: true
|
|
|
|
// Loading indicator
|
|
ColumnLayout {
|
|
anchors.centerIn: parent
|
|
visible: Settings.data.network.bluetoothEnabled && BluetoothService.isDiscovering
|
|
spacing: Style.marginMedium * scaling
|
|
|
|
NBusyIndicator {
|
|
running: BluetoothService.isDiscovering
|
|
color: Colors.mPrimary
|
|
size: Style.baseWidgetSize * scaling
|
|
Layout.alignment: Qt.AlignHCenter
|
|
}
|
|
|
|
NText {
|
|
text: "Scanning for devices..."
|
|
font.pointSize: Style.fontSizeNormal * scaling
|
|
color: Colors.mOnSurfaceVariant
|
|
Layout.alignment: Qt.AlignHCenter
|
|
}
|
|
}
|
|
|
|
// Bluetooth disabled message
|
|
ColumnLayout {
|
|
anchors.centerIn: parent
|
|
visible: !Settings.data.network.bluetoothEnabled
|
|
spacing: Style.marginMedium * scaling
|
|
|
|
NText {
|
|
text: "bluetooth_disabled"
|
|
font.family: "Material Symbols Outlined"
|
|
font.pointSize: Style.fontSizeXXL * scaling
|
|
color: Colors.mOnSurfaceVariant
|
|
Layout.alignment: Qt.AlignHCenter
|
|
}
|
|
|
|
NText {
|
|
text: "Bluetooth is disabled"
|
|
font.pointSize: Style.fontSizeLarge * scaling
|
|
color: Colors.mOnSurfaceVariant
|
|
Layout.alignment: Qt.AlignHCenter
|
|
}
|
|
|
|
NText {
|
|
text: "Enable Bluetooth to see available devices"
|
|
font.pointSize: Style.fontSizeNormal * scaling
|
|
color: Colors.mOnSurfaceVariant
|
|
Layout.alignment: Qt.AlignHCenter
|
|
}
|
|
}
|
|
|
|
// Device list
|
|
ListView {
|
|
id: deviceList
|
|
anchors.fill: parent
|
|
visible: Settings.data.network.bluetoothEnabled && !BluetoothService.isDiscovering
|
|
model: []
|
|
spacing: Style.marginMedium * scaling
|
|
clip: true
|
|
|
|
// Combine all devices into a single list for the ListView
|
|
property var allDevices: {
|
|
const devices = []
|
|
|
|
// Add connected devices first
|
|
for (const device of BluetoothService.connectedDevices) {
|
|
devices.push({
|
|
"device": device,
|
|
"type": 'connected',
|
|
"section": 'Connected Devices'
|
|
})
|
|
}
|
|
|
|
// Add paired devices
|
|
for (const device of BluetoothService.pairedDevices) {
|
|
devices.push({
|
|
"device": device,
|
|
"type": 'paired',
|
|
"section": 'Paired Devices'
|
|
})
|
|
}
|
|
|
|
// Add available devices
|
|
for (const device of BluetoothService.availableDevices) {
|
|
devices.push({
|
|
"device": device,
|
|
"type": 'available',
|
|
"section": 'Available Devices'
|
|
})
|
|
}
|
|
|
|
return devices
|
|
}
|
|
|
|
// Update model when devices change
|
|
onAllDevicesChanged: {
|
|
deviceList.model = allDevices
|
|
}
|
|
|
|
// Also watch for changes in the service arrays
|
|
Connections {
|
|
target: BluetoothService
|
|
function onConnectedDevicesChanged() {
|
|
deviceList.model = deviceList.allDevices
|
|
}
|
|
function onPairedDevicesChanged() {
|
|
deviceList.model = deviceList.allDevices
|
|
}
|
|
function onAvailableDevicesChanged() {
|
|
deviceList.model = deviceList.allDevices
|
|
}
|
|
}
|
|
|
|
delegate: Item {
|
|
width: parent ? parent.width : 0
|
|
height: Style.baseWidgetSize * 1.5 * scaling
|
|
|
|
Rectangle {
|
|
anchors.fill: parent
|
|
radius: Style.radiusMedium * scaling
|
|
color: modelData.device.connected ? Colors.mPrimary : (deviceMouseArea.containsMouse ? Colors.mTertiary : "transparent")
|
|
|
|
RowLayout {
|
|
anchors.fill: parent
|
|
anchors.margins: Style.marginSmall * scaling
|
|
spacing: Style.marginSmall * scaling
|
|
|
|
NText {
|
|
text: BluetoothService.getDeviceIcon(modelData.device)
|
|
font.family: "Material Symbols Outlined"
|
|
font.pointSize: Style.fontSizeXL * scaling
|
|
color: modelData.device.connected ? Colors.mSurface : (deviceMouseArea.containsMouse ? Colors.mSurface : Colors.mOnSurface)
|
|
}
|
|
|
|
ColumnLayout {
|
|
Layout.fillWidth: true
|
|
spacing: Style.marginTiny * scaling
|
|
|
|
NText {
|
|
text: modelData.device.name || modelData.device.deviceName || "Unknown Device"
|
|
font.pointSize: Style.fontSizeNormal * scaling
|
|
elide: Text.ElideRight
|
|
Layout.fillWidth: true
|
|
color: modelData.device.connected ? Colors.mSurface : (deviceMouseArea.containsMouse ? Colors.mSurface : Colors.mOnSurface)
|
|
}
|
|
|
|
NText {
|
|
text: {
|
|
if (modelData.device.connected) {
|
|
return "Connected"
|
|
} else if (modelData.device.paired) {
|
|
return "Paired"
|
|
} else {
|
|
return "Available"
|
|
}
|
|
}
|
|
font.pointSize: Style.fontSizeSmall * scaling
|
|
color: modelData.device.connected ? Colors.mSurface : (deviceMouseArea.containsMouse ? Colors.mSurface : Colors.mOnSurfaceVariant)
|
|
}
|
|
|
|
NText {
|
|
text: BluetoothService.getBatteryText(modelData.device)
|
|
font.pointSize: Style.fontSizeSmall * scaling
|
|
color: modelData.device.connected ? Colors.mSurface : (deviceMouseArea.containsMouse ? Colors.mSurface : Colors.mOnSurfaceVariant)
|
|
visible: modelData.device.batteryAvailable
|
|
}
|
|
}
|
|
|
|
Item {
|
|
Layout.preferredWidth: Style.baseWidgetSize * 0.7 * scaling
|
|
Layout.preferredHeight: Style.baseWidgetSize * 0.7 * scaling
|
|
visible: modelData.device.pairing || modelData.device.state === 2 // Connecting state
|
|
|
|
NBusyIndicator {
|
|
visible: modelData.device.pairing || modelData.device.state === 2
|
|
running: modelData.device.pairing || modelData.device.state === 2
|
|
color: Colors.mPrimary
|
|
anchors.centerIn: parent
|
|
size: Style.baseWidgetSize * 0.7 * scaling
|
|
}
|
|
}
|
|
|
|
NText {
|
|
visible: modelData.device.connected
|
|
text: "connected"
|
|
font.pointSize: Style.fontSizeSmall * scaling
|
|
color: modelData.device.connected ? Colors.mSurface : (deviceMouseArea.containsMouse ? Colors.mSurface : Colors.mOnSurface)
|
|
}
|
|
}
|
|
|
|
MouseArea {
|
|
id: deviceMouseArea
|
|
anchors.fill: parent
|
|
hoverEnabled: true
|
|
onClicked: {
|
|
if (modelData.device.connected) {
|
|
BluetoothService.disconnectDevice(modelData.device)
|
|
} else if (modelData.device.paired) {
|
|
BluetoothService.connectDevice(modelData.device)
|
|
} else {
|
|
BluetoothService.pairDevice(modelData.device)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Empty state when no devices found
|
|
ColumnLayout {
|
|
anchors.centerIn: parent
|
|
visible: Settings.data.network.bluetoothEnabled && !BluetoothService.isDiscovering
|
|
&& deviceList.count === 0
|
|
spacing: Style.marginMedium * scaling
|
|
|
|
NText {
|
|
text: "bluetooth_disabled"
|
|
font.family: "Material Symbols Outlined"
|
|
font.pointSize: Style.fontSizeXXL * scaling
|
|
color: Colors.mOnSurfaceVariant
|
|
Layout.alignment: Qt.AlignHCenter
|
|
}
|
|
|
|
NText {
|
|
text: "No Bluetooth devices"
|
|
font.pointSize: Style.fontSizeLarge * scaling
|
|
color: Colors.mOnSurfaceVariant
|
|
Layout.alignment: Qt.AlignHCenter
|
|
}
|
|
|
|
NText {
|
|
text: "Click the refresh button to discover devices"
|
|
font.pointSize: Style.fontSizeNormal * scaling
|
|
color: Colors.mOnSurfaceVariant
|
|
Layout.alignment: Qt.AlignHCenter
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|