1
0
mirror of https://github.com/AvengeMedia/DankMaterialShell.git synced 2026-01-27 06:52:50 -05:00

refactor: start trimming AI bullcrap patterns

This commit is contained in:
bbedward
2025-07-17 13:25:35 -04:00
parent cc9b575a1d
commit 77cc9c288b
21 changed files with 541 additions and 1069 deletions

View File

@@ -28,8 +28,6 @@ Singleton {
function onReadyChanged() { _rebuildModels() } function onReadyChanged() { _rebuildModels() }
function onDefaultAudioSinkChanged() { _rebuildModels() } function onDefaultAudioSinkChanged() { _rebuildModels() }
function onDefaultAudioSourceChanged() { _rebuildModels() } function onDefaultAudioSourceChanged() { _rebuildModels() }
function onNodeAdded() { _rebuildModels() }
function onNodeRemoved() { _rebuildModels() }
} }
Timer { Timer {

View File

@@ -7,7 +7,7 @@ import Quickshell.Services.UPower
Singleton { Singleton {
id: root id: root
readonly property var device: UPower.displayDevice readonly property UPowerDevice device: UPower.displayDevice
readonly property bool batteryAvailable: device && device.ready && device.isLaptopBattery readonly property bool batteryAvailable: device && device.ready && device.isLaptopBattery
readonly property int batteryLevel: batteryAvailable ? device.percentage * 100.0 : 0 readonly property int batteryLevel: batteryAvailable ? device.percentage * 100.0 : 0
readonly property bool isCharging: batteryAvailable && device.state === UPowerDeviceState.Charging readonly property bool isCharging: batteryAvailable && device.state === UPowerDeviceState.Charging

View File

@@ -7,477 +7,170 @@ import Quickshell.Bluetooth
Singleton { Singleton {
id: root id: root
property bool bluetoothEnabled: false readonly property BluetoothAdapter adapter: Bluetooth.defaultAdapter
property bool bluetoothAvailable: false readonly property bool available: adapter !== null
readonly property list<BluetoothDevice> bluetoothDevices: [] readonly property bool enabled: adapter?.enabled ?? false
readonly property list<BluetoothDevice> availableDevices: [] readonly property bool discovering: adapter?.discovering ?? false
property bool scanning: false
property bool discoverable: false
property var connectingDevices: ({}) readonly property var devices: {
var deviceList = []
if (!adapter) return deviceList
for (var i = 0; i < adapter.devices.count; i++) {
var dev = adapter.devices.get(i)
if (dev && dev.ready && _isValidDevice(dev)) {
deviceList.push({
address: dev.address,
name: dev.name || dev.deviceName,
paired: dev.paired,
connected: dev.connected,
iconName: _getDeviceIcon(dev),
type: _getDeviceType(dev),
batteryLevel: dev.batteryAvailable ? Math.round(dev.battery * 100) : -1,
batteryAvailable: dev.batteryAvailable,
native: dev
})
}
}
return deviceList
}
readonly property var pairedDevices: {
return devices.filter(dev => dev.paired)
}
readonly property var availableDevices: {
if (!discovering) return []
var availableList = []
if (Bluetooth.devices && Bluetooth.devices.values) {
for (var device of Bluetooth.devices.values) {
if (device && device.ready && !device.paired && _isValidDevice(device)) {
availableList.push({
address: device.address,
name: device.name || device.deviceName,
paired: false,
connected: false,
iconName: _getDeviceIcon(device),
type: _getDeviceType(device),
batteryLevel: -1,
batteryAvailable: false,
native: device
})
}
}
}
return availableList
}
readonly property var allDevicesWithBattery: {
return devices.filter(dev => dev.batteryAvailable && dev.batteryLevel >= 0)
}
Component.onCompleted: { Component.onCompleted: {
refreshBluetoothState() if (adapter && adapter.devices) {
updateDevices() adapter.devices.itemAdded.connect(devicesChanged)
adapter.devices.itemRemoved.connect(devicesChanged)
}
if (Bluetooth.devices) {
Bluetooth.devices.itemAdded.connect(devicesChanged)
Bluetooth.devices.itemRemoved.connect(devicesChanged)
}
} }
Connections { Connections {
target: Bluetooth target: Bluetooth
function onDefaultAdapterChanged() { function onDefaultAdapterChanged() {
console.log("BluetoothService: Default adapter changed") if (adapter && adapter.devices) {
refreshBluetoothState() adapter.devices.itemAdded.connect(devicesChanged)
updateDevices() adapter.devices.itemRemoved.connect(devicesChanged)
}
}
Connections {
target: Bluetooth.defaultAdapter
function onEnabledChanged() {
refreshBluetoothState()
updateDevices()
}
function onDiscoveringChanged() {
refreshBluetoothState()
updateDevices()
}
}
Connections {
target: Bluetooth.defaultAdapter ? Bluetooth.defaultAdapter.devices : null
function onModelReset() {
updateDevices()
}
function onItemAdded() {
updateDevices()
}
function onItemRemoved() {
updateDevices()
}
}
Connections {
target: Bluetooth.devices
function onModelReset() {
updateDevices()
}
function onItemAdded() {
updateDevices()
}
function onItemRemoved() {
updateDevices()
}
}
function refreshBluetoothState() {
root.bluetoothAvailable = Bluetooth.defaultAdapter !== null
root.bluetoothEnabled = Bluetooth.defaultAdapter ? Bluetooth.defaultAdapter.enabled : false
root.scanning = Bluetooth.defaultAdapter ? Bluetooth.defaultAdapter.discovering : false
root.discoverable = Bluetooth.defaultAdapter ? Bluetooth.defaultAdapter.discoverable : false
}
function updateDevices() {
if (!Bluetooth.defaultAdapter) {
clearDeviceList(root.bluetoothDevices)
clearDeviceList(root.availableDevices)
root.bluetoothDevices = []
root.availableDevices = []
return
}
let newPairedDevices = []
let newAvailableDevices = []
let allNativeDevices = []
let adapterDevices = Bluetooth.defaultAdapter.devices
if (adapterDevices.values) {
allNativeDevices = allNativeDevices.concat(adapterDevices.values)
}
if (Bluetooth.devices.values) {
for (let device of Bluetooth.devices.values) {
if (!allNativeDevices.some(d => d.address === device.address)) {
allNativeDevices.push(device)
}
}
}
for (let device of allNativeDevices) {
if (!device) continue
let deviceType = getDeviceType(device.name || device.deviceName, device.icon)
let displayName = device.name || device.deviceName
if (!displayName || displayName.startsWith('/org/bluez') || displayName.includes('hci0') || displayName.length < 2) {
continue
}
if (device.paired) {
let existingDevice = findDeviceInList(root.bluetoothDevices, device.address)
if (existingDevice) {
updateDeviceData(existingDevice, device, deviceType, displayName)
newPairedDevices.push(existingDevice)
} else {
let newDevice = createBluetoothDevice(device, deviceType, displayName)
newPairedDevices.push(newDevice)
}
} else {
if (Bluetooth.defaultAdapter.discovering && isDeviceDiscoverable(device)) {
let existingDevice = findDeviceInList(root.availableDevices, device.address)
if (existingDevice) {
updateDeviceData(existingDevice, device, deviceType, displayName)
newAvailableDevices.push(existingDevice)
} else {
let newDevice = createBluetoothDevice(device, deviceType, displayName)
newAvailableDevices.push(newDevice)
}
}
}
}
cleanupOldDevices(root.bluetoothDevices, newPairedDevices)
cleanupOldDevices(root.availableDevices, newAvailableDevices)
console.log("BluetoothService: Found", newPairedDevices.length, "paired devices and", newAvailableDevices.length, "available devices")
root.bluetoothDevices = newPairedDevices
root.availableDevices = newAvailableDevices
}
function createBluetoothDevice(nativeDevice, deviceType, displayName) {
return deviceComponent.createObject(root, {
mac: nativeDevice.address,
name: displayName,
type: deviceType,
paired: nativeDevice.paired,
connected: nativeDevice.connected,
battery: nativeDevice.batteryAvailable ? Math.round(nativeDevice.battery * 100) : -1,
signalStrength: nativeDevice.connected ? "excellent" : "unknown",
canPair: !nativeDevice.paired,
nativeDevice: nativeDevice,
connecting: false,
connectionFailed: false
})
}
function updateDeviceData(deviceObj, nativeDevice, deviceType, displayName) {
deviceObj.name = displayName
deviceObj.type = deviceType
deviceObj.paired = nativeDevice.paired
// If device connected state changed, clear connecting/failed states and refresh audio
if (deviceObj.connected !== nativeDevice.connected) {
deviceObj.connecting = false
deviceObj.connectionFailed = false
// Refresh audio devices when bluetooth audio device connects/disconnects
if (deviceType === "headset" || deviceType === "speaker") {
Qt.callLater(() => {
if (typeof AudioService !== 'undefined') {
AudioService.updateDevices()
}
})
}
}
deviceObj.connected = nativeDevice.connected
deviceObj.battery = nativeDevice.batteryAvailable ? Math.round(nativeDevice.battery * 100) : -1
deviceObj.signalStrength = nativeDevice.connected ? "excellent" : "unknown"
deviceObj.canPair = !nativeDevice.paired
deviceObj.nativeDevice = nativeDevice
}
function findDeviceInList(deviceList, address) {
for (let device of deviceList) {
if (device.mac === address) {
return device
}
}
return null
}
function cleanupOldDevices(oldList, newList) {
for (let oldDevice of oldList) {
if (!newList.includes(oldDevice)) {
oldDevice.destroy()
} }
} }
} }
function clearDeviceList(deviceList) { function _isValidDevice(device) {
for (let device of deviceList) { var displayName = device.name || device.deviceName
device.destroy()
}
}
function isDeviceDiscoverable(device) {
let displayName = device.name || device.deviceName
if (!displayName || displayName.length < 2) return false if (!displayName || displayName.length < 2) return false
if (displayName.startsWith('/org/bluez') || displayName.includes('hci0')) return false if (displayName.startsWith('/org/bluez') || displayName.includes('hci0')) return false
return displayName.length >= 3
let nameLower = displayName.toLowerCase()
if (nameLower.match(/^[0-9a-f]{2}[:-][0-9a-f]{2}[:-][0-9a-f]{2}/)) {
return false
}
if (displayName.length < 3) return false
if (nameLower.includes('iphone') || nameLower.includes('ipad') ||
nameLower.includes('airpods') || nameLower.includes('samsung') ||
nameLower.includes('galaxy') || nameLower.includes('pixel') ||
nameLower.includes('headphone') || nameLower.includes('speaker') ||
nameLower.includes('mouse') || nameLower.includes('keyboard') ||
nameLower.includes('watch') || nameLower.includes('buds') ||
nameLower.includes('android')) {
return true
}
return displayName.length >= 4 && !displayName.match(/^[A-Z0-9_-]+$/)
} }
function getDeviceType(name, icon) { function _getDeviceIcon(device) {
if (!name && !icon) return "bluetooth" var name = (device.name || device.deviceName || "").toLowerCase()
var icon = (device.icon || "").toLowerCase()
let nameLower = (name || "").toLowerCase()
let iconLower = (icon || "").toLowerCase()
if (iconLower.includes("audio") || iconLower.includes("headset") || iconLower.includes("headphone") ||
nameLower.includes("headphone") || nameLower.includes("airpod") || nameLower.includes("headset") ||
nameLower.includes("arctis") || nameLower.includes("audio")) return "headset"
else if (iconLower.includes("input-mouse") || nameLower.includes("mouse")) return "mouse"
else if (iconLower.includes("input-keyboard") || nameLower.includes("keyboard")) return "keyboard"
else if (iconLower.includes("phone") || nameLower.includes("phone") || nameLower.includes("iphone") ||
nameLower.includes("samsung") || nameLower.includes("android")) return "phone"
else if (iconLower.includes("watch") || nameLower.includes("watch")) return "watch"
else if (iconLower.includes("audio-speakers") || nameLower.includes("speaker")) return "speaker"
else if (iconLower.includes("video-display") || nameLower.includes("tv") || nameLower.includes("display")) return "tv"
if (icon.includes("headset") || icon.includes("audio") || name.includes("headphone") ||
name.includes("airpod") || name.includes("headset") || name.includes("arctis")) return "headset"
if (icon.includes("mouse") || name.includes("mouse")) return "mouse"
if (icon.includes("keyboard") || name.includes("keyboard")) return "keyboard"
if (icon.includes("phone") || name.includes("phone") || name.includes("iphone") ||
name.includes("android") || name.includes("samsung")) return "smartphone"
if (icon.includes("watch") || name.includes("watch")) return "watch"
if (icon.includes("speaker") || name.includes("speaker")) return "speaker"
if (icon.includes("display") || name.includes("tv")) return "tv"
return "bluetooth" return "bluetooth"
} }
function startDiscovery() { function _getDeviceType(device) {
if (Bluetooth.defaultAdapter && Bluetooth.defaultAdapter.enabled) { var name = (device.name || device.deviceName || "").toLowerCase()
Bluetooth.defaultAdapter.discovering = true var icon = (device.icon || "").toLowerCase()
updateDevices()
} if (icon.includes("headset") || icon.includes("audio") || name.includes("headphone") ||
name.includes("airpod") || name.includes("headset") || name.includes("arctis")) return "headset"
if (icon.includes("mouse") || name.includes("mouse")) return "mouse"
if (icon.includes("keyboard") || name.includes("keyboard")) return "keyboard"
if (icon.includes("phone") || name.includes("phone") || name.includes("iphone") ||
name.includes("android") || name.includes("samsung")) return "phone"
if (icon.includes("watch") || name.includes("watch")) return "watch"
if (icon.includes("speaker") || name.includes("speaker")) return "speaker"
if (icon.includes("display") || name.includes("tv")) return "tv"
return "bluetooth"
} }
function stopDiscovery() { function toggleAdapter() {
if (Bluetooth.defaultAdapter) { if (adapter) adapter.enabled = !adapter.enabled
Bluetooth.defaultAdapter.discovering = false
updateDevices()
}
} }
function pairDevice(mac) { function startScan() {
console.log("Pairing device:", mac) if (adapter) adapter.discovering = true
let device = findDeviceByMac(mac) }
function stopScan() {
if (adapter) adapter.discovering = false
}
function connect(address) {
var device = _findDevice(address)
if (device) device.connect()
}
function disconnect(address) {
var device = _findDevice(address)
if (device) device.disconnect()
}
function pair(address) {
var device = _findDevice(address)
if (device) device.pair()
}
function forget(address) {
var device = _findDevice(address)
if (device) device.forget()
}
function toggle(address) {
var device = _findDevice(address)
if (device) { if (device) {
device.pair() if (device.connected) device.disconnect()
else device.connect()
} }
} }
function connectDevice(mac) { function _findDevice(address) {
console.log("Connecting to device:", mac) if (!adapter) return null
let device = findDeviceByMac(mac) return adapter.devices.values.find(d => d.address === address) ||
if (device) { (Bluetooth.devices ? Bluetooth.devices.values.find(d => d.address === address) : null)
device.connect()
}
}
function removeDevice(mac) {
console.log("Removing device:", mac)
let device = findDeviceByMac(mac)
if (device) {
device.forget()
}
}
function toggleBluetoothDevice(mac) {
let typedDevice = findDeviceInList(root.bluetoothDevices, mac)
if (!typedDevice) {
typedDevice = findDeviceInList(root.availableDevices, mac)
}
if (typedDevice && typedDevice.nativeDevice) {
if (typedDevice.connected) {
console.log("Disconnecting device:", mac)
typedDevice.connecting = false
typedDevice.connectionFailed = false
typedDevice.nativeDevice.connected = false
} else {
console.log("Connecting to device:", mac)
typedDevice.connecting = true
typedDevice.connectionFailed = false
// Set a timeout to handle connection failure
Qt.callLater(() => {
connectionTimeout.deviceMac = mac
connectionTimeout.start()
})
typedDevice.nativeDevice.connected = true
}
}
}
function toggleBluetooth() {
if (Bluetooth.defaultAdapter) {
Bluetooth.defaultAdapter.enabled = !Bluetooth.defaultAdapter.enabled
}
}
function findDeviceByMac(mac) {
let typedDevice = findDeviceInList(root.bluetoothDevices, mac)
if (typedDevice && typedDevice.nativeDevice) {
return typedDevice.nativeDevice
}
typedDevice = findDeviceInList(root.availableDevices, mac)
if (typedDevice && typedDevice.nativeDevice) {
return typedDevice.nativeDevice
}
if (Bluetooth.defaultAdapter) {
let adapterDevices = Bluetooth.defaultAdapter.devices
if (adapterDevices.values) {
for (let device of adapterDevices.values) {
if (device && device.address === mac) {
return device
}
}
}
}
if (Bluetooth.devices.values) {
for (let device of Bluetooth.devices.values) {
if (device && device.address === mac) {
return device
}
}
}
return null
}
Timer {
id: bluetoothMonitorTimer
interval: 2000
running: false
repeat: true
onTriggered: {
updateDevices()
}
}
function enableMonitoring(enabled) {
bluetoothMonitorTimer.running = enabled
if (enabled) {
refreshBluetoothState()
updateDevices()
}
}
Timer {
id: bluetoothStateRefreshTimer
interval: 5000
running: true
repeat: true
onTriggered: {
refreshBluetoothState()
}
}
Timer {
id: connectionTimeout
interval: 10000 // 10 second timeout
running: false
repeat: false
property string deviceMac: ""
onTriggered: {
if (deviceMac) {
let typedDevice = findDeviceInList(root.bluetoothDevices, deviceMac)
if (!typedDevice) {
typedDevice = findDeviceInList(root.availableDevices, deviceMac)
}
if (typedDevice && typedDevice.connecting && !typedDevice.connected) {
console.log("Connection timeout for device:", deviceMac)
typedDevice.connecting = false
typedDevice.connectionFailed = true
// Clear failure state after 3 seconds
Qt.callLater(() => {
clearFailureTimer.deviceMac = deviceMac
clearFailureTimer.start()
})
}
deviceMac = ""
}
}
}
Timer {
id: clearFailureTimer
interval: 3000
running: false
repeat: false
property string deviceMac: ""
onTriggered: {
if (deviceMac) {
let typedDevice = findDeviceInList(root.bluetoothDevices, deviceMac)
if (!typedDevice) {
typedDevice = findDeviceInList(root.availableDevices, deviceMac)
}
if (typedDevice) {
typedDevice.connectionFailed = false
}
deviceMac = ""
}
}
}
component BluetoothDevice: QtObject {
required property string mac
required property string name
required property string type
required property bool paired
required property bool connected
required property int battery
required property string signalStrength
required property bool canPair
required property var nativeDevice // Reference to native Quickshell device
property bool connecting: false
property bool connectionFailed: false
readonly property string displayName: name
readonly property bool batteryAvailable: battery >= 0
readonly property string connectionStatus: {
if (connecting) return "Connecting..."
if (connectionFailed) return "Connection Failed"
if (connected) return "Connected"
return "Disconnected"
}
}
Component {
id: deviceComponent
BluetoothDevice {}
} }
} }

View File

@@ -1,159 +1,74 @@
import QtQuick import QtQuick
import QtQml.Models
import Quickshell import Quickshell
import Quickshell.Io import Quickshell.Io
import Quickshell.Services.Mpris import Quickshell.Services.Mpris
import Quickshell.Widgets
pragma Singleton pragma Singleton
pragma ComponentBehavior: Bound pragma ComponentBehavior: Bound
/**
* A service that provides easy access to the active Mpris player.
*/
Singleton { Singleton {
id: root id: root
property MprisPlayer trackedPlayer: null
property MprisPlayer activePlayer: trackedPlayer ?? Mpris.players.values[0] ?? null
signal trackChanged(reverse: bool)
property bool __reverse: false readonly property list<MprisPlayer> availablePlayers: Mpris.players.values
property var activeTrack property MprisPlayer activePlayer: null
property MprisPlayer _candidatePlayer: availablePlayers.find(p => p.isPlaying)
?? availablePlayers.find(p => p.canControl && p.canPlay)
?? null
Instantiator { Timer {
model: Mpris.players id: playerSwitchTimer
interval: 300
Connections { onTriggered: {
required property MprisPlayer modelData if (_candidatePlayer !== activePlayer) {
target: modelData activePlayer = _candidatePlayer
Component.onCompleted: {
if (root.trackedPlayer == null || modelData.isPlaying) {
root.trackedPlayer = modelData
}
}
Component.onDestruction: {
if (root.trackedPlayer == null || root.trackedPlayer.playbackState !== MprisPlaybackState.Playing) {
for (const player of Mpris.players.values) {
if (player.playbackState === MprisPlaybackState.Playing) {
root.trackedPlayer = player
break
}
}
if (trackedPlayer == null && Mpris.players.values.length != 0) {
trackedPlayer = Mpris.players.values[0]
}
}
}
function onPlaybackStateChanged() {
if (root.trackedPlayer !== modelData) root.trackedPlayer = modelData
} }
} }
} }
Connections { on_CandidatePlayerChanged: {
target: activePlayer if (_candidatePlayer === null && activePlayer !== null) {
playerSwitchTimer.restart()
function onPostTrackChanged() { } else if (_candidatePlayer !== null) {
root.updateTrack() playerSwitchTimer.stop()
} activePlayer = _candidatePlayer
function onTrackArtUrlChanged() {
if (root.activePlayer.uniqueId == root.activeTrack.uniqueId && root.activePlayer.trackArtUrl != root.activeTrack.artUrl) {
const r = root.__reverse
root.updateTrack()
root.__reverse = r
}
} }
} }
onActivePlayerChanged: this.updateTrack() IpcHandler {
target: "mpris"
function updateTrack() { function list(): string {
this.activeTrack = { return root.availablePlayers.map(p => p.identity).join("");
uniqueId: this.activePlayer?.uniqueId ?? 0,
artUrl: this.activePlayer?.trackArtUrl ?? "",
title: this.activePlayer?.trackTitle || "Unknown Title",
artist: this.activePlayer?.trackArtist || "Unknown Artist",
album: this.activePlayer?.trackAlbum || "Unknown Album",
} }
this.trackChanged(__reverse) function play(): void {
this.__reverse = false if (root.activePlayer?.canPlay)
} root.activePlayer.play();
property bool isPlaying: this.activePlayer && this.activePlayer.playbackState === MprisPlaybackState.Playing
property bool canTogglePlaying: this.activePlayer?.canTogglePlaying ?? false
function togglePlaying() {
if (this.canTogglePlaying) this.activePlayer.togglePlaying()
}
property bool canGoPrevious: this.activePlayer?.canGoPrevious ?? false
function previous() {
if (this.canGoPrevious) {
this.__reverse = true
this.activePlayer.previous()
}
}
property bool canGoNext: this.activePlayer?.canGoNext ?? false
function next() {
if (this.canGoNext) {
this.__reverse = false
this.activePlayer.next()
}
}
property bool canChangeVolume: this.activePlayer && this.activePlayer.volumeSupported && this.activePlayer.canControl
property bool loopSupported: this.activePlayer && this.activePlayer.loopSupported && this.activePlayer.canControl
property var loopState: this.activePlayer?.loopState ?? MprisLoopState.None
function setLoopState(loopState) {
if (this.loopSupported) {
this.activePlayer.loopState = loopState
}
}
property bool shuffleSupported: this.activePlayer && this.activePlayer.shuffleSupported && this.activePlayer.canControl
property bool hasShuffle: this.activePlayer?.shuffle ?? false
function setShuffle(shuffle) {
if (this.shuffleSupported) {
this.activePlayer.shuffle = shuffle
}
}
function setActivePlayer(player) {
const targetPlayer = player ?? Mpris.players[0]
if (targetPlayer && this.activePlayer) {
this.__reverse = Mpris.players.indexOf(targetPlayer) < Mpris.players.indexOf(this.activePlayer)
} else {
this.__reverse = false
} }
this.trackedPlayer = targetPlayer function pause(): void {
} if (root.activePlayer?.canPause)
root.activePlayer.pause();
}
// Seeking support function playPause(): void {
property bool canSeek: this.activePlayer?.canSeek ?? false if (root.activePlayer?.canTogglePlaying)
property real position: this.activePlayer?.position ?? 0 root.activePlayer.togglePlaying();
property real length: this.activePlayer?.length ?? 0 }
function seek(offsetUs) { function previous(): void {
if (this.canSeek && this.activePlayer) { if (root.activePlayer?.canGoPrevious)
this.activePlayer.seek(offsetUs) root.activePlayer.previous();
}
function next(): void {
if (root.activePlayer?.canGoNext)
root.activePlayer.next();
}
function stop(): void {
root.activePlayer?.stop();
} }
} }
function setPosition(trackId, positionUs) {
if (this.canSeek && this.activePlayer && typeof this.activePlayer.setPosition === "function") {
this.activePlayer.setPosition(trackId, positionUs)
} else if (this.canSeek && this.activePlayer) {
// Fallback to setting position property
this.activePlayer.position = positionUs
}
}
} }

View File

@@ -12,25 +12,7 @@ PanelWindow {
id: centerCommandCenter id: centerCommandCenter
property var theme: Theme property var theme: Theme
property bool hasActiveMedia: root.hasActiveMedia readonly property bool hasActiveMedia: MprisController.activePlayer !== null
property var weather: root.weather
property bool showMediaPlayer: hasActiveMedia || hideMediaTimer.running
Timer {
id: hideMediaTimer
interval: 3000
running: false
repeat: false
}
onHasActiveMediaChanged: {
if (hasActiveMedia) {
hideMediaTimer.stop()
} else {
hideMediaTimer.start()
}
}
visible: root.calendarVisible visible: root.calendarVisible
@@ -70,7 +52,7 @@ PanelWindow {
// Main row with widgets and calendar // Main row with widgets and calendar
let widgetHeight = 160 // Media widget always present let widgetHeight = 160 // Media widget always present
widgetHeight += (weather ? 140 : 80) + theme.spacingM // Weather widget always present widgetHeight += 140 + theme.spacingM // Weather widget always present
let calendarHeight = 300 let calendarHeight = 300
let mainRowHeight = Math.max(widgetHeight, calendarHeight) let mainRowHeight = Math.max(widgetHeight, calendarHeight)
@@ -177,7 +159,7 @@ PanelWindow {
width: parent.width width: parent.width
height: { height: {
let widgetHeight = 160 // Media widget always present let widgetHeight = 160 // Media widget always present
widgetHeight += (weather ? 140 : 80) + theme.spacingM // Weather widget always present widgetHeight += 140 + theme.spacingM // Weather widget always present
let calendarHeight = 300 let calendarHeight = 300
return Math.max(widgetHeight, calendarHeight) return Math.max(widgetHeight, calendarHeight)
} }
@@ -204,9 +186,8 @@ PanelWindow {
WeatherWidget { WeatherWidget {
visible: true // Always visible - shows placeholder when no weather visible: true // Always visible - shows placeholder when no weather
width: parent.width width: parent.width
height: weather ? 140 : 80 height: 140
theme: centerCommandCenter.theme theme: centerCommandCenter.theme
weather: centerCommandCenter.weather
} }
} }

View File

@@ -12,6 +12,32 @@ Rectangle {
property MprisPlayer activePlayer: MprisController.activePlayer property MprisPlayer activePlayer: MprisController.activePlayer
property var theme: Theme property var theme: Theme
property string lastValidTitle: ""
property string lastValidArtist: ""
property string lastValidAlbum: ""
property string lastValidArtUrl: ""
Timer {
id: clearCacheTimer
interval: 2000
onTriggered: {
if (!activePlayer) {
lastValidTitle = ""
lastValidArtist = ""
lastValidAlbum = ""
lastValidArtUrl = ""
}
}
}
onActivePlayerChanged: {
if (!activePlayer) {
clearCacheTimer.restart()
} else {
clearCacheTimer.stop()
}
}
width: parent.width width: parent.width
height: parent.height height: parent.height
radius: theme.cornerRadiusLarge radius: theme.cornerRadiusLarge
@@ -49,15 +75,6 @@ Rectangle {
} }
} }
// Initialize when player changes
onActivePlayerChanged: {
if (activePlayer) {
currentPosition = activePlayer.position || 0
} else {
currentPosition = 0
}
}
// Backend events // Backend events
Connections { Connections {
target: activePlayer target: activePlayer
@@ -85,7 +102,7 @@ Rectangle {
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
spacing: theme.spacingS spacing: theme.spacingS
visible: !activePlayer || !activePlayer.trackTitle || activePlayer.trackTitle === "" visible: (!activePlayer && !lastValidTitle) || (activePlayer && activePlayer.trackTitle === "" && lastValidTitle === "")
Text { Text {
text: "music_note" text: "music_note"
@@ -107,7 +124,7 @@ Rectangle {
Column { Column {
anchors.fill: parent anchors.fill: parent
spacing: theme.spacingS spacing: theme.spacingS
visible: activePlayer && activePlayer.trackTitle && activePlayer.trackTitle !== "" visible: activePlayer && activePlayer.trackTitle !== "" || lastValidTitle !== ""
// Normal media info when playing // Normal media info when playing
Row { Row {
@@ -129,7 +146,12 @@ Rectangle {
Image { Image {
id: albumArt id: albumArt
anchors.fill: parent anchors.fill: parent
source: activePlayer?.trackArtUrl || "" source: activePlayer?.trackArtUrl || lastValidArtUrl || ""
onSourceChanged: {
if (activePlayer?.trackArtUrl) {
lastValidArtUrl = activePlayer.trackArtUrl;
}
}
fillMode: Image.PreserveAspectCrop fillMode: Image.PreserveAspectCrop
smooth: true smooth: true
} }
@@ -157,7 +179,12 @@ Rectangle {
spacing: theme.spacingXS spacing: theme.spacingXS
Text { Text {
text: activePlayer?.trackTitle || "Unknown Track" text: activePlayer?.trackTitle || lastValidTitle || "Unknown Track"
onTextChanged: {
if (activePlayer?.trackTitle) {
lastValidTitle = activePlayer.trackTitle;
}
}
font.pixelSize: theme.fontSizeMedium font.pixelSize: theme.fontSizeMedium
font.weight: Font.Bold font.weight: Font.Bold
color: theme.surfaceText color: theme.surfaceText
@@ -166,7 +193,12 @@ Rectangle {
} }
Text { Text {
text: activePlayer?.trackArtist || "Unknown Artist" text: activePlayer?.trackArtist || lastValidArtist || "Unknown Artist"
onTextChanged: {
if (activePlayer?.trackArtist) {
lastValidArtist = activePlayer.trackArtist;
}
}
font.pixelSize: theme.fontSizeSmall font.pixelSize: theme.fontSizeSmall
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.8) color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.8)
width: parent.width width: parent.width
@@ -174,7 +206,12 @@ Rectangle {
} }
Text { Text {
text: activePlayer?.trackAlbum || "" text: activePlayer?.trackAlbum || lastValidAlbum || ""
onTextChanged: {
if (activePlayer?.trackAlbum) {
lastValidAlbum = activePlayer.trackAlbum;
}
}
font.pixelSize: theme.fontSizeSmall font.pixelSize: theme.fontSizeSmall
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.6) color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.6)
width: parent.width width: parent.width

View File

@@ -8,7 +8,6 @@ Rectangle {
id: weatherWidget id: weatherWidget
property var theme: Theme property var theme: Theme
property var weather
width: parent.width width: parent.width
height: parent.height height: parent.height
@@ -31,26 +30,18 @@ Rectangle {
Column { Column {
anchors.centerIn: parent anchors.centerIn: parent
spacing: theme.spacingS spacing: theme.spacingS
visible: !weather || !weather.available || weather.temp === 0 visible: !WeatherService.weather.available || WeatherService.weather.temp === 0
Text { Text {
text: weather && weather.loading ? "cloud_sync" : "cloud_off" text: "cloud_off"
font.family: theme.iconFont font.family: theme.iconFont
font.pixelSize: theme.iconSize + 8 font.pixelSize: theme.iconSize + 8
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.5) color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.5)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
RotationAnimation on rotation {
from: 0
to: 360
duration: 2000
running: weather && weather.loading
loops: Animation.Infinite
}
} }
Text { Text {
text: weather && weather.loading ? "Loading Weather..." : "No Weather Data" text: "No Weather Data"
font.pixelSize: theme.fontSizeMedium font.pixelSize: theme.fontSizeMedium
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.7) color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.7)
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -62,7 +53,7 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
anchors.margins: theme.spacingL anchors.margins: theme.spacingL
spacing: theme.spacingS spacing: theme.spacingS
visible: weather && weather.available && weather.temp !== 0 visible: WeatherService.weather.available && WeatherService.weather.temp !== 0
// Weather header info // Weather header info
Item { Item {
@@ -70,12 +61,12 @@ Rectangle {
height: 60 height: 60
Row { Row {
anchors.fill: parent anchors.centerIn: parent
spacing: theme.spacingL spacing: theme.spacingL
// Weather icon // Weather icon
Text { Text {
text: weather ? WeatherService.getWeatherIcon(weather.wCode) : "" text: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
font.family: theme.iconFont font.family: theme.iconFont
font.pixelSize: theme.iconSize + 8 font.pixelSize: theme.iconSize + 8
color: theme.primary color: theme.primary
@@ -87,7 +78,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Text { Text {
text: weather ? ((Prefs.useFahrenheit ? weather.tempF : weather.temp) + "°" + (Prefs.useFahrenheit ? "F" : "C")) : "" text: (Prefs.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp) + "°" + (Prefs.useFahrenheit ? "F" : "C")
font.pixelSize: theme.fontSizeXLarge font.pixelSize: theme.fontSizeXLarge
color: theme.surfaceText color: theme.surfaceText
font.weight: Font.Light font.weight: Font.Light
@@ -96,13 +87,13 @@ Rectangle {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: if (weather) Prefs.setTemperatureUnit(!Prefs.useFahrenheit) onClicked: if (WeatherService.weather.available) Prefs.setTemperatureUnit(!Prefs.useFahrenheit)
enabled: weather !== null enabled: WeatherService.weather.available
} }
} }
Text { Text {
text: weather ? weather.city : "" text: WeatherService.weather.city || ""
font.pixelSize: theme.fontSizeMedium font.pixelSize: theme.fontSizeMedium
color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.7) color: Qt.rgba(theme.surfaceText.r, theme.surfaceText.g, theme.surfaceText.b, 0.7)
visible: text.length > 0 visible: text.length > 0
@@ -127,7 +118,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
text: weather ? weather.humidity + "%" : "--" text: WeatherService.weather.humidity ? WeatherService.weather.humidity + "%" : "--"
font.pixelSize: theme.fontSizeSmall font.pixelSize: theme.fontSizeSmall
color: theme.surfaceText color: theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -144,7 +135,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
text: weather ? weather.wind : "--" text: WeatherService.weather.wind || "--"
font.pixelSize: theme.fontSizeSmall font.pixelSize: theme.fontSizeSmall
color: theme.surfaceText color: theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -161,7 +152,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
text: weather ? weather.sunrise : "--" text: WeatherService.weather.sunrise || "--"
font.pixelSize: theme.fontSizeSmall font.pixelSize: theme.fontSizeSmall
color: theme.surfaceText color: theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -178,7 +169,7 @@ Rectangle {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
Text { Text {
text: weather ? weather.sunset : "--" text: WeatherService.weather.sunset || "--"
font.pixelSize: theme.fontSizeSmall font.pixelSize: theme.fontSizeSmall
color: theme.surfaceText color: theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter

View File

@@ -9,9 +9,6 @@ import qs.Services
Item { Item {
id: bluetoothTab id: bluetoothTab
property bool bluetoothEnabled: false
property var bluetoothDevices: []
ScrollView { ScrollView {
anchors.fill: parent anchors.fill: parent
clip: true clip: true
@@ -28,8 +25,8 @@ Item {
height: 60 height: 60
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: bluetoothToggle.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : color: bluetoothToggle.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) :
(bluetoothTab.bluetoothEnabled ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12)) (BluetoothService.enabled ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.16) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.12))
border.color: bluetoothTab.bluetoothEnabled ? Theme.primary : "transparent" border.color: BluetoothService.enabled ? Theme.primary : "transparent"
border.width: 2 border.width: 2
Row { Row {
@@ -42,7 +39,7 @@ Item {
text: "bluetooth" text: "bluetooth"
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: Theme.iconSizeLarge font.pixelSize: Theme.iconSizeLarge
color: bluetoothTab.bluetoothEnabled ? Theme.primary : Theme.surfaceText color: BluetoothService.enabled ? Theme.primary : Theme.surfaceText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
} }
@@ -53,12 +50,12 @@ Item {
Text { Text {
text: "Bluetooth" text: "Bluetooth"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: bluetoothTab.bluetoothEnabled ? Theme.primary : Theme.surfaceText color: BluetoothService.enabled ? Theme.primary : Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
Text { Text {
text: bluetoothTab.bluetoothEnabled ? "Enabled" : "Disabled" text: BluetoothService.enabled ? "Enabled" : "Disabled"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
} }
@@ -72,7 +69,7 @@ Item {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
BluetoothService.toggleBluetooth() BluetoothService.toggleAdapter()
} }
} }
} }
@@ -80,7 +77,7 @@ Item {
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
visible: bluetoothTab.bluetoothEnabled visible: BluetoothService.enabled
Text { Text {
text: "Paired Devices" text: "Paired Devices"
@@ -90,7 +87,7 @@ Item {
} }
Repeater { Repeater {
model: bluetoothTab.bluetoothDevices model: BluetoothService.pairedDevices
Rectangle { Rectangle {
width: parent.width width: parent.width
@@ -108,33 +105,11 @@ Item {
spacing: Theme.spacingM spacing: Theme.spacingM
Text { Text {
text: { text: modelData.iconName
switch (modelData.type) {
case "headset": return "headset"
case "mouse": return "mouse"
case "keyboard": return "keyboard"
case "phone": return "smartphone"
default: return "bluetooth"
}
}
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: Theme.iconSize font.pixelSize: Theme.iconSize
color: { color: modelData.connected ? Theme.primary : Theme.surfaceText
if (modelData.connecting) return Theme.primary
if (modelData.connected) return Theme.primary
return Theme.surfaceText
}
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
opacity: modelData.connecting ? 0.6 : 1.0
Behavior on opacity {
SequentialAnimation {
running: modelData.connecting
loops: Animation.Infinite
NumberAnimation { from: 1.0; to: 0.3; duration: 800 }
NumberAnimation { from: 0.3; to: 1.0; duration: 800 }
}
}
} }
Column { Column {
@@ -152,20 +127,26 @@ Item {
spacing: Theme.spacingXS spacing: Theme.spacingXS
Text { Text {
text: modelData.connectionStatus text: modelData.connected ? "Connected" : "Disconnected"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: { color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
if (modelData.connecting) return Theme.primary
if (modelData.connectionFailed) return Theme.error
return Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
}
} }
Text { Text {
text: modelData.battery >= 0 ? "• " + modelData.battery + "%" : "" text: {
if (modelData.batteryAvailable && modelData.batteryLevel >= 0) {
return "• " + modelData.batteryLevel + "%"
}
var btBattery = BatteryService.bluetoothDevices.find(dev =>
dev.name === modelData.name ||
dev.name.toLowerCase().includes(modelData.name.toLowerCase()) ||
modelData.name.toLowerCase().includes(dev.name.toLowerCase())
)
return btBattery ? "• " + btBattery.percentage + "%" : ""
}
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
visible: modelData.battery >= 0 visible: text.length > 0
} }
} }
} }
@@ -196,16 +177,13 @@ Item {
MouseArea { MouseArea {
id: btMenuButtonArea id: btMenuButtonArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: !modelData.connecting hoverEnabled: true
enabled: !modelData.connecting cursorShape: Qt.PointingHandCursor
cursorShape: modelData.connecting ? Qt.ArrowCursor : Qt.PointingHandCursor
onClicked: { onClicked: {
if (!modelData.connecting) { bluetoothContextMenuWindow.deviceData = modelData
bluetoothContextMenuWindow.deviceData = modelData let localPos = btMenuButtonArea.mapToItem(bluetoothTab, btMenuButtonArea.width / 2, btMenuButtonArea.height)
let localPos = btMenuButtonArea.mapToItem(bluetoothTab, btMenuButtonArea.width / 2, btMenuButtonArea.height) bluetoothContextMenuWindow.show(localPos.x, localPos.y)
bluetoothContextMenuWindow.show(localPos.x, localPos.y)
}
} }
} }
@@ -218,14 +196,11 @@ Item {
id: btDeviceArea id: btDeviceArea
anchors.fill: parent anchors.fill: parent
anchors.rightMargin: 40 // Don't overlap with menu button anchors.rightMargin: 40 // Don't overlap with menu button
hoverEnabled: !modelData.connecting hoverEnabled: true
enabled: !modelData.connecting cursorShape: Qt.PointingHandCursor
cursorShape: modelData.connecting ? Qt.ArrowCursor : Qt.PointingHandCursor
onClicked: { onClicked: {
if (!modelData.connecting) { BluetoothService.toggle(modelData.address)
BluetoothService.toggleBluetoothDevice(modelData.mac)
}
} }
} }
} }
@@ -235,7 +210,7 @@ Item {
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
visible: bluetoothTab.bluetoothEnabled visible: BluetoothService.enabled
Row { Row {
width: parent.width width: parent.width
@@ -264,7 +239,7 @@ Item {
spacing: Theme.spacingXS spacing: Theme.spacingXS
Text { Text {
text: BluetoothService.scanning ? "stop" : "bluetooth_searching" text: BluetoothService.discovering ? "stop" : "bluetooth_searching"
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 4 font.pixelSize: Theme.iconSize - 4
color: Theme.primary color: Theme.primary
@@ -273,7 +248,7 @@ Item {
Text { Text {
id: scanText id: scanText
text: BluetoothService.scanning ? "Stop Scanning" : "Start Scanning" text: BluetoothService.discovering ? "Stop Scanning" : "Start Scanning"
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.primary color: Theme.primary
font.weight: Font.Medium font.weight: Font.Medium
@@ -288,10 +263,10 @@ Item {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (BluetoothService.scanning) { if (BluetoothService.discovering) {
BluetoothService.stopDiscovery() BluetoothService.stopScan()
} else { } else {
BluetoothService.startDiscovery() BluetoothService.startScan()
} }
} }
} }
@@ -317,18 +292,7 @@ Item {
spacing: Theme.spacingM spacing: Theme.spacingM
Text { Text {
text: { text: modelData.iconName
switch (modelData.type) {
case "headset": return "headset"
case "mouse": return "mouse"
case "keyboard": return "keyboard"
case "phone": return "smartphone"
case "watch": return "watch"
case "speaker": return "speaker"
case "tv": return "tv"
default: return "bluetooth"
}
}
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: Theme.iconSize font.pixelSize: Theme.iconSize
color: modelData.paired ? Theme.secondary : Theme.surfaceText color: modelData.paired ? Theme.secondary : Theme.surfaceText
@@ -350,21 +314,10 @@ Item {
spacing: Theme.spacingXS spacing: Theme.spacingXS
Text { Text {
text: { text: modelData.paired ? "Available" : "Not paired"
if (modelData.paired && modelData.connected) return "Connected"
if (modelData.paired) return "Paired"
return "Signal: " + modelData.signalStrength
}
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
} }
Text {
text: modelData.rssi !== 0 ? "• " + modelData.rssi + " dBm" : ""
font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
visible: modelData.rssi !== 0
}
} }
} }
} }
@@ -379,11 +332,11 @@ Item {
color: actionButtonArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" color: actionButtonArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
border.color: Theme.primary border.color: Theme.primary
border.width: 1 border.width: 1
visible: modelData.canPair || modelData.paired visible: true
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: modelData.paired ? (modelData.connected ? "Disconnect" : "Connect") : "Pair" text: modelData.paired ? "Connect" : "Pair"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.primary color: Theme.primary
font.weight: Font.Medium font.weight: Font.Medium
@@ -397,13 +350,9 @@ Item {
onClicked: { onClicked: {
if (modelData.paired) { if (modelData.paired) {
if (modelData.connected) { BluetoothService.connect(modelData.address)
BluetoothService.toggleBluetoothDevice(modelData.mac)
} else {
BluetoothService.connectDevice(modelData.mac)
}
} else { } else {
BluetoothService.pairDevice(modelData.mac) BluetoothService.pair(modelData.address)
} }
} }
} }
@@ -418,9 +367,9 @@ Item {
onClicked: { onClicked: {
if (modelData.paired) { if (modelData.paired) {
BluetoothService.toggleBluetoothDevice(modelData.mac) BluetoothService.connect(modelData.address)
} else { } else {
BluetoothService.pairDevice(modelData.mac) BluetoothService.pair(modelData.address)
} }
} }
} }
@@ -430,7 +379,7 @@ Item {
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
visible: BluetoothService.scanning && BluetoothService.availableDevices.length === 0 visible: BluetoothService.discovering && BluetoothService.availableDevices.length === 0
Row { Row {
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
@@ -473,7 +422,7 @@ Item {
text: "No devices found. Put your device in pairing mode and click Start Scanning." text: "No devices found. Put your device in pairing mode and click Start Scanning."
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
visible: BluetoothService.availableDevices.length === 0 && !BluetoothService.scanning visible: BluetoothService.availableDevices.length === 0 && !BluetoothService.discovering
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
width: parent.width width: parent.width
horizontalAlignment: Text.AlignHCenter horizontalAlignment: Text.AlignHCenter
@@ -568,7 +517,7 @@ Item {
onClicked: { onClicked: {
if (bluetoothContextMenuWindow.deviceData) { if (bluetoothContextMenuWindow.deviceData) {
BluetoothService.toggleBluetoothDevice(bluetoothContextMenuWindow.deviceData.mac) BluetoothService.toggle(bluetoothContextMenuWindow.deviceData.address)
} }
bluetoothContextMenuWindow.hide() bluetoothContextMenuWindow.hide()
} }
@@ -634,7 +583,7 @@ Item {
onClicked: { onClicked: {
if (bluetoothContextMenuWindow.deviceData) { if (bluetoothContextMenuWindow.deviceData) {
BluetoothService.removeDevice(bluetoothContextMenuWindow.deviceData.mac) BluetoothService.forget(bluetoothContextMenuWindow.deviceData.address)
} }
bluetoothContextMenuWindow.hide() bluetoothContextMenuWindow.hide()
} }

View File

@@ -33,9 +33,9 @@ PanelWindow {
property bool powerOptionsExpanded: false property bool powerOptionsExpanded: false
Rectangle { Rectangle {
width: Math.min(600, parent.width - Theme.spacingL * 2) width: Math.min(600, Screen.width - Theme.spacingL * 2)
height: controlCenterPopup.powerOptionsExpanded ? 570 : 500 height: controlCenterPopup.powerOptionsExpanded ? 570 : 500
x: Math.max(Theme.spacingL, parent.width - width - Theme.spacingL) x: Math.max(Theme.spacingL, Screen.width - width - Theme.spacingL)
y: Theme.barHeight + Theme.spacingXS y: Theme.barHeight + Theme.spacingXS
color: Theme.popupBackground() color: Theme.popupBackground()
radius: Theme.cornerRadiusLarge radius: Theme.cornerRadiusLarge
@@ -560,7 +560,7 @@ PanelWindow {
tabs.push({name: "Audio", icon: "volume_up", id: "audio", available: true}) tabs.push({name: "Audio", icon: "volume_up", id: "audio", available: true})
// Show Bluetooth only if available // Show Bluetooth only if available
if (root.bluetoothAvailable) { if (BluetoothService.available) {
tabs.push({name: "Bluetooth", icon: "bluetooth", id: "bluetooth", available: true}) tabs.push({name: "Bluetooth", icon: "bluetooth", id: "bluetooth", available: true})
} }
@@ -573,7 +573,7 @@ PanelWindow {
Rectangle { Rectangle {
property int tabCount: { property int tabCount: {
let count = 3 // Network + Audio + Display (always visible) let count = 3 // Network + Audio + Display (always visible)
if (root.bluetoothAvailable) count++ if (BluetoothService.available) count++
return count return count
} }
width: (parent.width - Theme.spacingXS * (tabCount - 1)) / tabCount width: (parent.width - Theme.spacingXS * (tabCount - 1)) / tabCount
@@ -646,26 +646,10 @@ PanelWindow {
anchors.margins: Theme.spacingM anchors.margins: Theme.spacingM
visible: controlCenterPopup.currentTab === "network" visible: controlCenterPopup.currentTab === "network"
// Bind properties from root
networkStatus: root.networkStatus
wifiAvailable: root.wifiAvailable
wifiEnabled: root.wifiEnabled
wifiToggling: root.wifiToggling
ethernetIP: root.ethernetIP
ethernetInterface: root.ethernetInterface
ethernetConnected: root.ethernetConnected
currentWifiSSID: root.currentWifiSSID
wifiIP: root.wifiIP
wifiSignalStrength: root.wifiSignalStrength
wifiNetworks: root.wifiNetworks
wifiScanning: root.wifiScanning
wifiConnectionStatus: root.wifiConnectionStatus
wifiPasswordSSID: root.wifiPasswordSSID wifiPasswordSSID: root.wifiPasswordSSID
wifiPasswordInput: root.wifiPasswordInput wifiPasswordInput: root.wifiPasswordInput
wifiPasswordDialogVisible: root.wifiPasswordDialogVisible wifiPasswordDialogVisible: root.wifiPasswordDialogVisible
changingNetworkPreference: root.changingNetworkPreference
// Bind the auto-refresh flag
onWifiAutoRefreshEnabledChanged: { onWifiAutoRefreshEnabledChanged: {
root.wifiAutoRefreshEnabled = wifiAutoRefreshEnabled root.wifiAutoRefreshEnabled = wifiAutoRefreshEnabled
} }
@@ -682,11 +666,7 @@ PanelWindow {
BluetoothTab { BluetoothTab {
anchors.fill: parent anchors.fill: parent
anchors.margins: Theme.spacingM anchors.margins: Theme.spacingM
visible: root.bluetoothAvailable && controlCenterPopup.currentTab === "bluetooth" visible: BluetoothService.available && controlCenterPopup.currentTab === "bluetooth"
// Bind properties from root
bluetoothEnabled: root.bluetoothEnabled
bluetoothDevices: root.bluetoothDevices
} }
// Display Tab // Display Tab

View File

@@ -11,32 +11,16 @@ Item {
property int networkSubTab: { property int networkSubTab: {
// Default to WiFi tab if WiFi is connected, otherwise Ethernet // Default to WiFi tab if WiFi is connected, otherwise Ethernet
if (networkStatus === "wifi") return 1 if (NetworkService.networkStatus === "wifi") return 1
else if (networkStatus === "ethernet") return 0 else if (NetworkService.networkStatus === "ethernet") return 0
else return 1 // Default to WiFi when nothing is connected else return 1 // Default to WiFi when nothing is connected
} }
// Expose properties that the parent needs to bind to // Expose properties that the parent needs to bind to
property bool wifiAutoRefreshEnabled: false property bool wifiAutoRefreshEnabled: false
// These should be bound from parent
property string networkStatus: ""
property bool wifiAvailable: false
property bool wifiEnabled: false
property bool wifiToggling: false
property string ethernetIP: ""
property string ethernetInterface: ""
property bool ethernetConnected: false
property string currentWifiSSID: ""
property string wifiIP: ""
property string wifiSignalStrength: ""
property var wifiNetworks: []
property bool wifiScanning: false
property string wifiConnectionStatus: ""
property string wifiPasswordSSID: "" property string wifiPasswordSSID: ""
property string wifiPasswordInput: "" property string wifiPasswordInput: ""
property bool wifiPasswordDialogVisible: false property bool wifiPasswordDialogVisible: false
property bool changingNetworkPreference: false
Column { Column {
anchors.fill: parent anchors.fill: parent
@@ -101,7 +85,7 @@ Item {
spacing: Theme.spacingXS spacing: Theme.spacingXS
Text { Text {
text: networkTab.wifiEnabled ? "wifi" : "wifi_off" text: NetworkService.wifiEnabled ? "wifi" : "wifi_off"
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 4 font.pixelSize: Theme.iconSize - 4
color: networkTab.networkSubTab === 1 ? Theme.primary : Theme.surfaceText color: networkTab.networkSubTab === 1 ? Theme.primary : Theme.surfaceText
@@ -160,8 +144,8 @@ Item {
height: 70 height: 70
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.5) color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.5)
border.color: networkTab.networkStatus === "ethernet" ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: NetworkService.networkStatus === "ethernet" ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: networkTab.networkStatus === "ethernet" ? 2 : 1 border.width: NetworkService.networkStatus === "ethernet" ? 2 : 1
visible: true visible: true
Row { Row {
@@ -190,7 +174,7 @@ Item {
} }
Text { Text {
text: networkTab.ethernetConnected ? (networkTab.ethernetIP || "Connected") : "Disconnected" text: NetworkService.ethernetConnected ? (NetworkService.ethernetIP || "Connected") : "Disconnected"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
} }
@@ -342,8 +326,8 @@ Item {
height: 50 height: 50
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: wifiToggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08) color: wifiToggleArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.08) : Qt.rgba(Theme.surfaceVariant.r, Theme.surfaceVariant.g, Theme.surfaceVariant.b, 0.08)
visible: networkTab.wifiAvailable visible: NetworkService.wifiAvailable
opacity: networkTab.wifiToggling ? 0.6 : 1.0 opacity: NetworkService.wifiToggling ? 0.6 : 1.0
Behavior on opacity { Behavior on opacity {
NumberAnimation { NumberAnimation {
@@ -360,17 +344,17 @@ Item {
Text { Text {
id: wifiToggleIcon id: wifiToggleIcon
text: networkTab.wifiToggling ? "sync" : "power_settings_new" text: NetworkService.wifiToggling ? "sync" : "power_settings_new"
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: Theme.iconSize font.pixelSize: Theme.iconSize
color: networkTab.wifiEnabled ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5) color: NetworkService.wifiEnabled ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
rotation: networkTab.wifiToggling ? wifiToggleIcon.rotation : 0 rotation: NetworkService.wifiToggling ? wifiToggleIcon.rotation : 0
RotationAnimation { RotationAnimation {
target: wifiToggleIcon target: wifiToggleIcon
property: "rotation" property: "rotation"
running: networkTab.wifiToggling running: NetworkService.wifiToggling
from: 0 from: 0
to: 360 to: 360
duration: 1000 duration: 1000
@@ -386,7 +370,7 @@ Item {
} }
Text { Text {
text: networkTab.wifiToggling ? "Switching WiFi..." : (networkTab.wifiEnabled ? "Turn WiFi Off" : "Turn WiFi On") text: NetworkService.wifiToggling ? "Switching WiFi..." : (NetworkService.wifiEnabled ? "Turn WiFi Off" : "Turn WiFi On")
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -413,7 +397,7 @@ Item {
color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.5) color: Qt.rgba(Theme.surfaceContainer.r, Theme.surfaceContainer.g, Theme.surfaceContainer.b, 0.5)
border.color: networkTab.networkStatus === "wifi" ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12) border.color: networkTab.networkStatus === "wifi" ? Theme.primary : Qt.rgba(Theme.outline.r, Theme.outline.g, Theme.outline.b, 0.12)
border.width: networkTab.networkStatus === "wifi" ? 2 : 1 border.width: networkTab.networkStatus === "wifi" ? 2 : 1
visible: networkTab.wifiAvailable && networkTab.wifiEnabled visible: NetworkService.wifiAvailable && NetworkService.wifiEnabled
Row { Row {
anchors.left: parent.left anchors.left: parent.left
@@ -423,10 +407,10 @@ Item {
Text { Text {
text: networkTab.networkStatus === "wifi" ? text: networkTab.networkStatus === "wifi" ?
(networkTab.wifiSignalStrength === "excellent" ? "wifi" : (WifiService.wifiSignalStrength === "excellent" ? "wifi" :
networkTab.wifiSignalStrength === "good" ? "wifi_2_bar" : WifiService.wifiSignalStrength === "good" ? "wifi_2_bar" :
networkTab.wifiSignalStrength === "fair" ? "wifi_1_bar" : WifiService.wifiSignalStrength === "fair" ? "wifi_1_bar" :
networkTab.wifiSignalStrength === "poor" ? "wifi_calling_3" : "wifi") : "wifi" WifiService.wifiSignalStrength === "poor" ? "wifi_calling_3" : "wifi") : "wifi"
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: Theme.iconSizeLarge font.pixelSize: Theme.iconSizeLarge
color: networkTab.networkStatus === "wifi" ? Theme.primary : Theme.surfaceText color: networkTab.networkStatus === "wifi" ? Theme.primary : Theme.surfaceText
@@ -438,14 +422,14 @@ Item {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
Text { Text {
text: networkTab.networkStatus === "wifi" ? (networkTab.currentWifiSSID || "Connected") : "Not Connected" text: NetworkService.networkStatus === "wifi" ? (WifiService.currentWifiSSID || "Connected") : "Not Connected"
font.pixelSize: Theme.fontSizeLarge font.pixelSize: Theme.fontSizeLarge
color: networkTab.networkStatus === "wifi" ? Theme.primary : Theme.surfaceText color: networkTab.networkStatus === "wifi" ? Theme.primary : Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
} }
Text { Text {
text: networkTab.networkStatus === "wifi" ? (networkTab.wifiIP || "Connected") : "Select a network below" text: NetworkService.networkStatus === "wifi" ? (NetworkService.wifiIP || "Connected") : "Select a network below"
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.7)
} }
@@ -496,10 +480,10 @@ Item {
} }
Text { Text {
text: networkTab.changingNetworkPreference ? "Switching..." : text: NetworkService.changingNetworkPreference ? "Switching..." :
(networkTab.networkStatus === "wifi" ? "" : "Prefer over Ethernet") (NetworkService.networkStatus === "wifi" ? "" : "Prefer over Ethernet")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: networkTab.networkStatus === "wifi" ? Theme.background : Theme.primary color: NetworkService.networkStatus === "wifi" ? Theme.background : Theme.primary
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
font.weight: Font.Medium font.weight: Font.Medium
} }
@@ -513,7 +497,7 @@ Item {
enabled: !networkTab.changingNetworkPreference enabled: !networkTab.changingNetworkPreference
onClicked: { onClicked: {
console.log("Force WiFi preference clicked") console.log("Force WiFi preference clicked")
if (networkTab.networkStatus !== "wifi") { if (NetworkService.networkStatus !== "wifi") {
NetworkService.setNetworkPreference("wifi") NetworkService.setNetworkPreference("wifi")
} else { } else {
NetworkService.setNetworkPreference("auto") NetworkService.setNetworkPreference("auto")
@@ -528,7 +512,7 @@ Item {
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
visible: networkTab.wifiEnabled visible: NetworkService.wifiEnabled
Row { Row {
width: parent.width width: parent.width
@@ -547,21 +531,21 @@ Item {
height: 32 height: 32
radius: 16 radius: 16
color: refreshArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : color: refreshArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) :
networkTab.wifiScanning ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06) : "transparent" WifiService.isScanning ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.06) : "transparent"
Text { Text {
id: refreshIcon id: refreshIcon
anchors.centerIn: parent anchors.centerIn: parent
text: networkTab.wifiScanning ? "sync" : "refresh" text: WifiService.isScanning ? "sync" : "refresh"
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 4 font.pixelSize: Theme.iconSize - 4
color: Theme.surfaceText color: Theme.surfaceText
rotation: networkTab.wifiScanning ? refreshIcon.rotation : 0 rotation: WifiService.isScanning ? refreshIcon.rotation : 0
RotationAnimation { RotationAnimation {
target: refreshIcon target: refreshIcon
property: "rotation" property: "rotation"
running: networkTab.wifiScanning running: WifiService.isScanning
from: 0 from: 0
to: 360 to: 360
duration: 1000 duration: 1000
@@ -581,7 +565,7 @@ Item {
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
enabled: !networkTab.wifiScanning enabled: !WifiService.isScanning
onClicked: { onClicked: {
if (NetworkService.wifiEnabled) { if (NetworkService.wifiEnabled) {
WifiService.scanWifi() WifiService.scanWifi()
@@ -598,27 +582,27 @@ Item {
height: 40 height: 40
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: { color: {
if (networkTab.wifiConnectionStatus === "connecting") { if (WifiService.connectionStatus === "connecting") {
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12) return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.12)
} else if (networkTab.wifiConnectionStatus === "failed") { } else if (WifiService.connectionStatus === "failed") {
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12) return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.12)
} else if (networkTab.wifiConnectionStatus === "connected") { } else if (WifiService.connectionStatus === "connected") {
return Qt.rgba(Theme.success.r, Theme.success.g, Theme.success.b, 0.12) return Qt.rgba(Theme.success.r, Theme.success.g, Theme.success.b, 0.12)
} }
return "transparent" return "transparent"
} }
border.color: { border.color: {
if (networkTab.wifiConnectionStatus === "connecting") { if (WifiService.connectionStatus === "connecting") {
return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.3) return Qt.rgba(Theme.warning.r, Theme.warning.g, Theme.warning.b, 0.3)
} else if (networkTab.wifiConnectionStatus === "failed") { } else if (WifiService.connectionStatus === "failed") {
return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.3) return Qt.rgba(Theme.error.r, Theme.error.g, Theme.error.b, 0.3)
} else if (networkTab.wifiConnectionStatus === "connected") { } else if (WifiService.connectionStatus === "connected") {
return Qt.rgba(Theme.success.r, Theme.success.g, Theme.success.b, 0.3) return Qt.rgba(Theme.success.r, Theme.success.g, Theme.success.b, 0.3)
} }
return "transparent" return "transparent"
} }
border.width: networkTab.wifiConnectionStatus !== "" ? 1 : 0 border.width: WifiService.connectionStatus !== "" ? 1 : 0
visible: networkTab.wifiConnectionStatus !== "" visible: WifiService.connectionStatus !== ""
Row { Row {
anchors.centerIn: parent anchors.centerIn: parent
@@ -627,26 +611,26 @@ Item {
Text { Text {
id: connectionIcon id: connectionIcon
text: { text: {
if (networkTab.wifiConnectionStatus === "connecting") return "sync" if (WifiService.connectionStatus === "connecting") return "sync"
if (networkTab.wifiConnectionStatus === "failed") return "error" if (WifiService.connectionStatus === "failed") return "error"
if (networkTab.wifiConnectionStatus === "connected") return "check_circle" if (WifiService.connectionStatus === "connected") return "check_circle"
return "" return ""
} }
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 6 font.pixelSize: Theme.iconSize - 6
color: { color: {
if (networkTab.wifiConnectionStatus === "connecting") return Theme.warning if (WifiService.connectionStatus === "connecting") return Theme.warning
if (networkTab.wifiConnectionStatus === "failed") return Theme.error if (WifiService.connectionStatus === "failed") return Theme.error
if (networkTab.wifiConnectionStatus === "connected") return Theme.success if (WifiService.connectionStatus === "connected") return Theme.success
return Theme.surfaceText return Theme.surfaceText
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
rotation: networkTab.wifiConnectionStatus === "connecting" ? connectionIcon.rotation : 0 rotation: WifiService.connectionStatus === "connecting" ? connectionIcon.rotation : 0
RotationAnimation { RotationAnimation {
target: connectionIcon target: connectionIcon
property: "rotation" property: "rotation"
running: networkTab.wifiConnectionStatus === "connecting" running: WifiService.connectionStatus === "connecting"
from: 0 from: 0
to: 360 to: 360
duration: 1000 duration: 1000
@@ -663,16 +647,16 @@ Item {
Text { Text {
text: { text: {
if (networkTab.wifiConnectionStatus === "connecting") return "Connecting to " + WifiService.connectingSSID if (WifiService.connectionStatus === "connecting") return "Connecting to " + WifiService.connectingSSID
if (networkTab.wifiConnectionStatus === "failed") return "Failed to connect to " + WifiService.connectingSSID if (WifiService.connectionStatus === "failed") return "Failed to connect to " + WifiService.connectingSSID
if (networkTab.wifiConnectionStatus === "connected") return "Connected to " + WifiService.connectingSSID if (WifiService.connectionStatus === "connected") return "Connected to " + WifiService.connectingSSID
return "" return ""
} }
font.pixelSize: Theme.fontSizeMedium font.pixelSize: Theme.fontSizeMedium
color: { color: {
if (networkTab.wifiConnectionStatus === "connecting") return Theme.warning if (WifiService.connectionStatus === "connecting") return Theme.warning
if (networkTab.wifiConnectionStatus === "failed") return Theme.error if (WifiService.connectionStatus === "failed") return Theme.error
if (networkTab.wifiConnectionStatus === "connected") return Theme.success if (WifiService.connectionStatus === "connected") return Theme.success
return Theme.surfaceText return Theme.surfaceText
} }
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
@@ -689,7 +673,7 @@ Item {
// WiFi networks list (only show if WiFi is available and enabled) // WiFi networks list (only show if WiFi is available and enabled)
Repeater { Repeater {
model: networkTab.wifiAvailable && networkTab.wifiEnabled ? networkTab.wifiNetworks : [] model: NetworkService.wifiAvailable && NetworkService.wifiEnabled ? WifiService.wifiNetworks : []
Rectangle { Rectangle {
width: parent.width width: parent.width
@@ -828,7 +812,7 @@ Item {
Column { Column {
width: parent.width width: parent.width
spacing: Theme.spacingM spacing: Theme.spacingM
visible: !networkTab.wifiEnabled visible: !NetworkService.wifiEnabled
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
Text { Text {

View File

@@ -163,7 +163,7 @@ Item {
// Global mouse area for drag tracking // Global mouse area for drag tracking
MouseArea { MouseArea {
id: sliderGlobalMouseArea id: sliderGlobalMouseArea
anchors.fill: slider.parent // Fill the entire settings popup anchors.fill: sliderContainer
enabled: sliderMouseArea.isDragging enabled: sliderMouseArea.isDragging
visible: false visible: false
preventStealing: true preventStealing: true

View File

@@ -42,7 +42,7 @@ PanelWindow {
Rectangle { Rectangle {
width: 400 width: 400
height: 500 height: 500
x: parent.width - width - Theme.spacingL x: Screen.width - width - Theme.spacingL
y: Theme.barHeight + Theme.spacingXS y: Theme.barHeight + Theme.spacingXS
color: Theme.popupBackground() color: Theme.popupBackground()
radius: Theme.cornerRadiusLarge radius: Theme.cornerRadiusLarge

View File

@@ -46,9 +46,9 @@ PanelWindow {
Rectangle { Rectangle {
id: dropdownContent id: dropdownContent
width: Math.min(600, parent.width - Theme.spacingL * 2) width: Math.min(600, Screen.width - Theme.spacingL * 2)
height: Math.min(600, parent.height - Theme.barHeight - Theme.spacingS * 2) height: Math.min(600, Screen.height - Theme.barHeight - Theme.spacingS * 2)
x: Math.max(Theme.spacingL, parent.width - width - Theme.spacingL) x: Math.max(Theme.spacingL, Screen.width - width - Theme.spacingL)
y: Theme.barHeight + Theme.spacingXS y: Theme.barHeight + Theme.spacingXS
radius: Theme.cornerRadiusLarge radius: Theme.cornerRadiusLarge

View File

@@ -3,13 +3,14 @@ import Quickshell
import Quickshell.Io import Quickshell.Io
import Quickshell.Services.Mpris import Quickshell.Services.Mpris
import qs.Common import qs.Common
import qs.Services
Item { Item {
id: root id: root
property list<real> audioLevels: [0, 0, 0, 0] property list<real> audioLevels: [0, 0, 0, 0]
property bool hasActiveMedia: false readonly property MprisPlayer activePlayer: MprisController.activePlayer
property var activePlayer: null readonly property bool hasActiveMedia: activePlayer !== null
property bool cavaAvailable: false property bool cavaAvailable: false
width: 20 width: 20

View File

@@ -5,12 +5,6 @@ import qs.Services
Rectangle { Rectangle {
id: root id: root
property string networkStatus: "disconnected"
property string wifiSignalStrength: "good"
property int volumeLevel: 50
property bool volumeMuted: false
property bool bluetoothAvailable: false
property bool bluetoothEnabled: false
property bool isActive: false property bool isActive: false
signal clicked() signal clicked()
@@ -30,9 +24,9 @@ Rectangle {
// Network Status Icon // Network Status Icon
Text { Text {
text: { text: {
if (root.networkStatus === "ethernet") return "lan" if (NetworkService.networkStatus === "ethernet") return "lan"
else if (root.networkStatus === "wifi") { else if (NetworkService.networkStatus === "wifi") {
switch (root.wifiSignalStrength) { switch (WifiService.wifiSignalStrength) {
case "excellent": return "wifi" case "excellent": return "wifi"
case "good": return "wifi_2_bar" case "good": return "wifi_2_bar"
case "fair": return "wifi_1_bar" case "fair": return "wifi_1_bar"
@@ -45,7 +39,7 @@ Rectangle {
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 8 font.pixelSize: Theme.iconSize - 8
font.weight: Theme.iconFontWeight font.weight: Theme.iconFontWeight
color: root.networkStatus !== "disconnected" ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5) color: NetworkService.networkStatus !== "disconnected" ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: true visible: true
} }
@@ -56,9 +50,9 @@ Rectangle {
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 8 font.pixelSize: Theme.iconSize - 8
font.weight: Theme.iconFontWeight font.weight: Theme.iconFontWeight
color: root.bluetoothEnabled ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5) color: BluetoothService.enabled ? Theme.primary : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.5)
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
visible: root.bluetoothAvailable && root.bluetoothEnabled visible: BluetoothService.available && BluetoothService.enabled
} }
// Audio Icon with scroll wheel support // Audio Icon with scroll wheel support
@@ -70,8 +64,8 @@ Rectangle {
Text { Text {
id: audioIcon id: audioIcon
text: root.volumeMuted ? "volume_off" : text: AudioService.sinkMuted ? "volume_off" :
root.volumeLevel < 33 ? "volume_down" : "volume_up" AudioService.volumeLevel < 33 ? "volume_down" : "volume_up"
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 8 font.pixelSize: Theme.iconSize - 8
font.weight: Theme.iconFontWeight font.weight: Theme.iconFontWeight
@@ -88,7 +82,7 @@ Rectangle {
onWheel: function(wheelEvent) { onWheel: function(wheelEvent) {
let delta = wheelEvent.angleDelta.y let delta = wheelEvent.angleDelta.y
let currentVolume = root.volumeLevel let currentVolume = AudioService.volumeLevel
let newVolume let newVolume
if (delta > 0) { if (delta > 0) {

View File

@@ -10,12 +10,10 @@ Rectangle {
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: launcherArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) color: launcherArea.containsMouse ? Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.12) : Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
property string osLogo: ""
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: root.osLogo || "apps" text: OSDetectorService.osLogo || "apps"
font.family: root.osLogo ? "NerdFont" : Theme.iconFont font.family: OSDetectorService.osLogo ? "NerdFont" : Theme.iconFont
font.pixelSize: Theme.iconSize - 6 font.pixelSize: Theme.iconSize - 6
font.weight: Theme.iconFontWeight font.weight: Theme.iconFontWeight
color: Theme.surfaceText color: Theme.surfaceText

View File

@@ -1,111 +1,92 @@
import QtQuick import QtQuick
import Quickshell.Services.Mpris import Quickshell.Services.Mpris
import qs.Common import qs.Common
import qs.Services
Rectangle { Rectangle {
id: root id: root
property var activePlayer: null readonly property MprisPlayer activePlayer: MprisController.activePlayer
property bool hasActiveMedia: false readonly property bool playerAvailable: activePlayer !== null
readonly property int contentWidth: Math.min(280, mediaRow.implicitWidth + Theme.spacingS * 2)
// Add a stable visibility property that doesn't flicker during track changes
property bool stableVisible: false
signal clicked() signal clicked()
// Use a timer to stabilize visibility during track changes
Timer {
id: visibilityTimer
interval: 1000 // 1 second delay before hiding
onTriggered: root.stableVisible = root.hasActiveMedia
}
onHasActiveMediaChanged: {
if (hasActiveMedia) {
// Show immediately when media becomes available
stableVisible = true
visibilityTimer.stop()
} else {
// Delay hiding to avoid flicker during track changes
visibilityTimer.restart()
}
}
visible: stableVisible
width: stableVisible ? Math.min(280, mediaRow.implicitWidth + Theme.spacingS * 2) : 0
height: 30 height: 30
radius: Theme.cornerRadius radius: Theme.cornerRadius
color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08) color: Qt.rgba(Theme.surfaceText.r, Theme.surfaceText.g, Theme.surfaceText.b, 0.08)
states: [
State {
name: "shown"
when: playerAvailable
Behavior on color { PropertyChanges {
ColorAnimation { target: root
duration: Theme.shortDuration opacity: 1
easing.type: Theme.standardEasing width: contentWidth
} }
}
},
State {
name: "hidden"
when: !playerAvailable
PropertyChanges {
target: root
opacity: 0
width: 0
}
Behavior on width {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
} }
} ]
Row { Row {
id: mediaRow id: mediaRow
anchors.centerIn: parent anchors.centerIn: parent
spacing: Theme.spacingXS spacing: Theme.spacingXS
// Media info section (clickable to open full player) // Media info section (clickable to open full player)
Row { Row {
id: mediaInfo id: mediaInfo
spacing: Theme.spacingXS spacing: Theme.spacingXS
AudioVisualization { AudioVisualization {
width: 20 width: 20
height: Theme.iconSize height: Theme.iconSize
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
hasActiveMedia: root.hasActiveMedia
activePlayer: root.activePlayer
} }
Text { Text {
id: mediaText id: mediaText
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
width: 140 width: 140
text: { text: {
// Handle the case when activePlayer is temporarily null during track changes
if (!activePlayer || !activePlayer.trackTitle) { if (!activePlayer || !activePlayer.trackTitle) {
return "Loading..." return "";
} }
// Check if it's web media by looking at player identity let identity = activePlayer.identity || "";
let identity = activePlayer.identity || "" let isWebMedia = identity.toLowerCase().includes("firefox") || identity.toLowerCase().includes("chrome") || identity.toLowerCase().includes("chromium") || identity.toLowerCase().includes("edge") || identity.toLowerCase().includes("safari");
let isWebMedia = identity.toLowerCase().includes("firefox") || let title = "";
identity.toLowerCase().includes("chrome") || let subtitle = "";
identity.toLowerCase().includes("chromium") ||
identity.toLowerCase().includes("edge") ||
identity.toLowerCase().includes("safari")
let title = ""
let subtitle = ""
if (isWebMedia && activePlayer.trackTitle) { if (isWebMedia && activePlayer.trackTitle) {
title = activePlayer.trackTitle title = activePlayer.trackTitle;
subtitle = activePlayer.trackArtist || identity subtitle = activePlayer.trackArtist || identity;
} else { } else {
title = activePlayer.trackTitle || "Unknown Track" title = activePlayer.trackTitle || "Unknown Track";
subtitle = activePlayer.trackArtist || "" subtitle = activePlayer.trackArtist || "";
} }
if (title.length > 20)
title = title.substring(0, 20) + "...";
// Truncate title and subtitle to fit in available space - more generous limits if (subtitle.length > 22)
if (title.length > 20) title = title.substring(0, 20) + "..." subtitle = subtitle.substring(0, 22) + "...";
if (subtitle.length > 22) subtitle = subtitle.substring(0, 22) + "..."
return subtitle.length > 0 ? title + " • " + subtitle : title return subtitle.length > 0 ? title + " • " + subtitle : title;
} }
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium
@@ -117,7 +98,9 @@ Rectangle {
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: root.clicked() onClicked: root.clicked()
} }
} }
} }
// Control buttons // Control buttons
@@ -132,8 +115,8 @@ Rectangle {
radius: 10 radius: 10
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: prevArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" color: prevArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
visible: stableVisible visible: root.playerAvailable
opacity: (activePlayer && activePlayer.canGoPrevious) ? 1.0 : 0.3 opacity: (activePlayer && activePlayer.canGoPrevious) ? 1 : 0.3
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
@@ -145,13 +128,17 @@ Rectangle {
MouseArea { MouseArea {
id: prevArea id: prevArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (activePlayer) activePlayer.previous() if (activePlayer)
activePlayer.previous();
} }
} }
} }
// Play/Pause button // Play/Pause button
@@ -160,16 +147,16 @@ Rectangle {
height: 24 height: 24
radius: 12 radius: 12
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: activePlayer?.playbackState === 1 ? Theme.primary : Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) color: activePlayer && activePlayer.playbackState === 1 ? Theme.primary : Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12)
visible: stableVisible visible: root.playerAvailable
opacity: activePlayer ? 1.0 : 0.3 opacity: activePlayer ? 1 : 0.3
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
text: activePlayer?.playbackState === 1 ? "pause" : "play_arrow" text: activePlayer && activePlayer.playbackState === 1 ? "pause" : "play_arrow"
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: 14 font.pixelSize: 14
color: activePlayer?.playbackState === 1 ? Theme.background : Theme.primary color: activePlayer && activePlayer.playbackState === 1 ? Theme.background : Theme.primary
} }
MouseArea { MouseArea {
@@ -177,9 +164,12 @@ Rectangle {
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (activePlayer) activePlayer.togglePlaying() if (activePlayer)
activePlayer.togglePlaying();
} }
} }
} }
// Next button // Next button
@@ -189,8 +179,8 @@ Rectangle {
radius: 10 radius: 10
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
color: nextArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent" color: nextArea.containsMouse ? Qt.rgba(Theme.primary.r, Theme.primary.g, Theme.primary.b, 0.12) : "transparent"
visible: stableVisible visible: playerAvailable
opacity: (activePlayer && activePlayer.canGoNext) ? 1.0 : 0.3 opacity: (activePlayer && activePlayer.canGoNext) ? 1 : 0.3
Text { Text {
anchors.centerIn: parent anchors.centerIn: parent
@@ -202,14 +192,69 @@ Rectangle {
MouseArea { MouseArea {
id: nextArea id: nextArea
anchors.fill: parent anchors.fill: parent
hoverEnabled: true hoverEnabled: true
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
onClicked: { onClicked: {
if (activePlayer) activePlayer.next() if (activePlayer)
activePlayer.next();
} }
} }
} }
} }
} }
transitions: [
Transition {
from: "shown"
to: "hidden"
SequentialAnimation {
PauseAnimation {
duration: 500
}
NumberAnimation {
properties: "opacity,width"
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
},
Transition {
from: "hidden"
to: "shown"
NumberAnimation {
properties: "opacity,width"
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
]
Behavior on color {
ColorAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
Behavior on width {
NumberAnimation {
duration: Theme.shortDuration
easing.type: Theme.standardEasing
}
}
} }

View File

@@ -31,19 +31,6 @@ PanelWindow {
} }
// Properties exposed to shell // Properties exposed to shell
property bool hasActiveMedia: false
property var activePlayer: null
property bool weatherAvailable: false
property string weatherCode: ""
property int weatherTemp: 0
property int weatherTempF: 0
property string osLogo: ""
property string networkStatus: "disconnected"
property string wifiSignalStrength: "good"
property int volumeLevel: 50
property bool volumeMuted: false
property bool bluetoothAvailable: false
property bool bluetoothEnabled: false
// Shell reference to access root properties directly // Shell reference to access root properties directly
property var shellRoot: null property var shellRoot: null
@@ -155,7 +142,6 @@ PanelWindow {
LauncherButton { LauncherButton {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
osLogo: topBar.osLogo
} }
WorkspaceSwitcher { WorkspaceSwitcher {
@@ -184,9 +170,7 @@ PanelWindow {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
anchors.right: clockWidget.left anchors.right: clockWidget.left
anchors.rightMargin: Theme.spacingS anchors.rightMargin: Theme.spacingS
activePlayer: topBar.activePlayer visible: Prefs.showMusic && MprisController.activePlayer
hasActiveMedia: topBar.hasActiveMedia
visible: Prefs.showMusic && topBar.hasActiveMedia
onClicked: { onClicked: {
if (topBar.shellRoot) { if (topBar.shellRoot) {
@@ -201,11 +185,7 @@ PanelWindow {
anchors.left: clockWidget.right anchors.left: clockWidget.right
anchors.leftMargin: Theme.spacingS anchors.leftMargin: Theme.spacingS
weatherAvailable: topBar.weatherAvailable visible: Prefs.showWeather && WeatherService.weather.available && WeatherService.weather.temp > 0 && WeatherService.weather.tempF > 0
weatherCode: topBar.weatherCode
weatherTemp: topBar.weatherTemp
weatherTempF: topBar.weatherTempF
visible: Prefs.showWeather && topBar.weatherAvailable && topBar.weatherTemp > 0 && topBar.weatherTempF > 0
onClicked: { onClicked: {
if (topBar.shellRoot) { if (topBar.shellRoot) {
@@ -307,12 +287,6 @@ PanelWindow {
ControlCenterButton { ControlCenterButton {
anchors.verticalCenter: parent.verticalCenter anchors.verticalCenter: parent.verticalCenter
networkStatus: topBar.networkStatus
wifiSignalStrength: topBar.wifiSignalStrength
volumeLevel: topBar.volumeLevel
volumeMuted: topBar.volumeMuted
bluetoothAvailable: topBar.bluetoothAvailable
bluetoothEnabled: topBar.bluetoothEnabled
isActive: topBar.shellRoot ? topBar.shellRoot.controlCenterVisible : false isActive: topBar.shellRoot ? topBar.shellRoot.controlCenterVisible : false
onClicked: { onClicked: {

View File

@@ -5,10 +5,6 @@ import qs.Services
Rectangle { Rectangle {
id: root id: root
property bool weatherAvailable: false
property string weatherCode: ""
property int weatherTemp: 0
property int weatherTempF: 0
signal clicked() signal clicked()
@@ -40,7 +36,7 @@ Rectangle {
spacing: Theme.spacingXS spacing: Theme.spacingXS
Text { Text {
text: WeatherService.getWeatherIcon(weatherCode) text: WeatherService.getWeatherIcon(WeatherService.weather.wCode)
font.family: Theme.iconFont font.family: Theme.iconFont
font.pixelSize: Theme.iconSize - 4 font.pixelSize: Theme.iconSize - 4
color: Theme.primary color: Theme.primary
@@ -48,7 +44,7 @@ Rectangle {
} }
Text { Text {
text: (Prefs.useFahrenheit ? weatherTempF : weatherTemp) + "°" + (Prefs.useFahrenheit ? "F" : "C") text: (Prefs.useFahrenheit ? WeatherService.weather.tempF : WeatherService.weather.temp) + "°" + (Prefs.useFahrenheit ? "F" : "C")
font.pixelSize: Theme.fontSizeSmall font.pixelSize: Theme.fontSizeSmall
color: Theme.surfaceText color: Theme.surfaceText
font.weight: Font.Medium font.weight: Font.Medium

View File

@@ -70,7 +70,7 @@ PanelWindow {
QsMenuOpener { QsMenuOpener {
id: menuOpener id: menuOpener
menu: root.currentTrayItem?.menu menu: root.currentTrayItem ? root.currentTrayItem.menu : null
} }
// Custom menu styling using ListView // Custom menu styling using ListView

View File

@@ -37,19 +37,11 @@ ShellRoot {
property real trayMenuY: 0 property real trayMenuY: 0
property var currentTrayMenu: null property var currentTrayMenu: null
property var currentTrayItem: null property var currentTrayItem: null
property string osLogo: OSDetectorService.osLogo
property string osName: OSDetectorService.osName
property bool notificationHistoryVisible: false property bool notificationHistoryVisible: false
property bool mediaPlayerVisible: false property bool mediaPlayerVisible: false
property MprisPlayer activePlayer: MprisController.activePlayer property bool hasActiveMedia: MprisController.active && (MprisController.active.trackTitle || MprisController.active.trackArtist)
property bool hasActiveMedia: activePlayer && (activePlayer.trackTitle || activePlayer.trackArtist)
property bool controlCenterVisible: false property bool controlCenterVisible: false
// Monitor control center visibility to enable/disable bluetooth scanning
onControlCenterVisibleChanged: {
console.log("Control center", controlCenterVisible ? "opened" : "closed")
BluetoothService.enableMonitoring(controlCenterVisible)
}
property bool batteryPopupVisible: false property bool batteryPopupVisible: false
property bool powerMenuVisible: false property bool powerMenuVisible: false
property bool powerConfirmVisible: false property bool powerConfirmVisible: false
@@ -58,52 +50,11 @@ ShellRoot {
property string powerConfirmMessage: "" property string powerConfirmMessage: ""
property bool settingsVisible: false property bool settingsVisible: false
// Network properties from NetworkService
property string networkStatus: NetworkService.networkStatus
property string ethernetIP: NetworkService.ethernetIP
property string ethernetInterface: NetworkService.ethernetInterface
property bool ethernetConnected: NetworkService.ethernetConnected
property string wifiIP: NetworkService.wifiIP
property bool bluetoothEnabled: BluetoothService.bluetoothEnabled
property bool bluetoothAvailable: BluetoothService.bluetoothAvailable
property bool wifiEnabled: NetworkService.wifiEnabled
property bool wifiAvailable: NetworkService.wifiAvailable
property bool wifiToggling: NetworkService.wifiToggling
property bool changingNetworkPreference: NetworkService.changingPreference
// WiFi properties from WifiService
property string wifiSignalStrength: WifiService.wifiSignalStrength
property string currentWifiSSID: WifiService.currentWifiSSID
property var wifiNetworks: WifiService.wifiNetworks
property var savedWifiNetworks: WifiService.savedWifiNetworks
property bool wifiScanning: WifiService.isScanning
// Audio properties from AudioService
property int volumeLevel: AudioService.volumeLevel
property bool volumeMuted: AudioService.sinkMuted
property var audioSinks: AudioService.audioSinks
property string currentAudioSink: AudioService.currentAudioSink
// Microphone properties from AudioService
property int micLevel: AudioService.micLevel
property var audioSources: AudioService.audioSources
property string currentAudioSource: AudioService.currentAudioSource
// Bluetooth properties from BluetoothService
property var bluetoothDevices: BluetoothService.bluetoothDevices
// Brightness properties from BrightnessService
property int brightnessLevel: BrightnessService.brightnessLevel
// Calendar properties from CalendarService
property bool calendarAvailable: CalendarService.khalAvailable
property var calendarEvents: CalendarService.eventsByDate
// WiFi password dialog // WiFi password dialog
property bool wifiPasswordDialogVisible: false property bool wifiPasswordDialogVisible: false
property string wifiPasswordSSID: "" property string wifiPasswordSSID: ""
property string wifiPasswordInput: "" property string wifiPasswordInput: ""
property string wifiConnectionStatus: WifiService.connectionStatus
property bool wifiAutoRefreshEnabled: false property bool wifiAutoRefreshEnabled: false
// Wallpaper error status // Wallpaper error status
@@ -116,8 +67,6 @@ ShellRoot {
property bool isMediumScreen: screenWidth >= 1200 && screenWidth < 1600 property bool isMediumScreen: screenWidth >= 1200 && screenWidth < 1600
property bool isLargeScreen: screenWidth >= 1600 property bool isLargeScreen: screenWidth >= 1600
// Weather data from WeatherService
property var weather: WeatherService.weather
// Weather configuration // Weather configuration
@@ -171,19 +120,6 @@ ShellRoot {
modelData: item modelData: item
// Connect shell properties // Connect shell properties
hasActiveMedia: root.hasActiveMedia
activePlayer: root.activePlayer
weatherAvailable: root.weather.available
weatherCode: root.weather.wCode
weatherTemp: root.weather.temp
weatherTempF: root.weather.tempF
osLogo: root.osLogo
networkStatus: root.networkStatus
wifiSignalStrength: root.wifiSignalStrength
volumeLevel: root.volumeLevel
volumeMuted: root.volumeMuted
bluetoothAvailable: root.bluetoothAvailable
bluetoothEnabled: root.bluetoothEnabled
shellRoot: root shellRoot: root
notificationCount: NotificationService.notifications.length notificationCount: NotificationService.notifications.length
processDropdown: processListDropdown processDropdown: processListDropdown