Build the Panama Control Center
This commit is contained in:
@@ -78,6 +78,8 @@ Singleton {
|
||||
readonly property int popoverRadius: 18
|
||||
readonly property int popoverPadding: 14
|
||||
readonly property int popoverWidth: 380
|
||||
readonly property int controlCenterWidth: 430
|
||||
readonly property int controlCenterTopGap: 2
|
||||
|
||||
readonly property int cardRadius: 12
|
||||
readonly property int pillRadius: 999
|
||||
|
||||
@@ -10,6 +10,7 @@ import Quickshell.Networking
|
||||
import Quickshell.Services.Pipewire
|
||||
import QtQuick
|
||||
import qs.config
|
||||
import qs.services
|
||||
import qs.widgets
|
||||
|
||||
Pill {
|
||||
@@ -106,4 +107,10 @@ Pill {
|
||||
glyph: "\u{F0176}" // md-coffee
|
||||
color: Theme.warn
|
||||
}
|
||||
|
||||
StatusGlyph {
|
||||
visible: KdeConnect.phoneReachable
|
||||
glyph: "\u{F03F2}" // md-cellphone-link
|
||||
color: Theme.cyan
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import QtQuick
|
||||
import qs.config
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property string label: ""
|
||||
property string action: ""
|
||||
property bool actionEnabled: true
|
||||
signal actionTriggered
|
||||
|
||||
implicitHeight: 24
|
||||
|
||||
Text {
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: root.label.toUpperCase()
|
||||
color: Theme.fgMuted
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.DemiBold
|
||||
font.letterSpacing: 0.9
|
||||
}
|
||||
|
||||
Text {
|
||||
id: actionLabel
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
visible: root.action !== ""
|
||||
text: root.action
|
||||
color: root.actionEnabled
|
||||
? (actionMouse.containsMouse ? Theme.accentAlt : Theme.accent)
|
||||
: Theme.fgMuted
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
|
||||
Behavior on color { ColorAnimation { duration: Theme.durFast } }
|
||||
|
||||
MouseArea {
|
||||
id: actionMouse
|
||||
anchors.fill: parent
|
||||
anchors.margins: -6
|
||||
enabled: root.actionEnabled
|
||||
hoverEnabled: true
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: root.actionTriggered()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,280 @@
|
||||
import QtQuick
|
||||
import qs.config
|
||||
import qs.services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool expanded: false
|
||||
signal toggleExpanded
|
||||
|
||||
implicitHeight: content.implicitHeight
|
||||
|
||||
Column {
|
||||
id: content
|
||||
width: parent.width
|
||||
spacing: 6
|
||||
|
||||
ControlSectionHeader {
|
||||
width: parent.width
|
||||
label: "Home"
|
||||
action: HomeAssistant.entities.length > 0
|
||||
? HomeAssistant.entities.length + " accessories " + (root.expanded ? "⌃" : "›")
|
||||
: "Retry"
|
||||
actionEnabled: HomeAssistant.phase !== "loading"
|
||||
onActionTriggered: {
|
||||
if (HomeAssistant.entities.length > 0)
|
||||
root.toggleExpanded();
|
||||
else
|
||||
HomeAssistant.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: homeCard
|
||||
width: parent.width
|
||||
height: visible ? homeGrid.implicitHeight + 20 : 0
|
||||
visible: HomeAssistant.entities.length > 0
|
||||
radius: Theme.cardRadius + 2
|
||||
color: Theme.alpha(Theme.accent, 0.045)
|
||||
border.width: 1
|
||||
border.color: Theme.alpha(Theme.accent, 0.12)
|
||||
|
||||
Grid {
|
||||
id: homeGrid
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: 10
|
||||
columns: 4
|
||||
spacing: 7
|
||||
|
||||
Repeater {
|
||||
model: HomeAssistant.visibleEntities
|
||||
|
||||
HomeTile {
|
||||
required property var modelData
|
||||
width: (homeGrid.width - homeGrid.spacing * 3) / 4
|
||||
entity: modelData
|
||||
busy: HomeAssistant.busyEntityId === modelData.id
|
||||
onActivated: HomeAssistant.toggleEntity(modelData.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: visible ? 66 : 0
|
||||
visible: HomeAssistant.entities.length === 0
|
||||
radius: Theme.cardRadius
|
||||
color: Theme.alpha(Theme.fg, 0.05)
|
||||
border.width: 1
|
||||
border.color: Theme.alpha(Theme.fg, 0.045)
|
||||
|
||||
Text {
|
||||
id: stateIcon
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 12
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: HomeAssistant.phase === "loading" ? "\u{F0772}" : "\u{F02DC}"
|
||||
color: HomeAssistant.phase === "loading" ? Theme.accent : Theme.fgDim
|
||||
font.family: Theme.fontMono
|
||||
font.pixelSize: 16
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.left: stateIcon.right
|
||||
anchors.leftMargin: 11
|
||||
anchors.right: openHome.left
|
||||
anchors.rightMargin: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 3
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: root.emptyTitle()
|
||||
color: Theme.fg
|
||||
elide: Text.ElideRight
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSize
|
||||
font.weight: Font.DemiBold
|
||||
}
|
||||
Text {
|
||||
width: parent.width
|
||||
text: root.emptyDetail()
|
||||
color: Theme.fgDim
|
||||
elide: Text.ElideRight
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
id: openHome
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 48
|
||||
height: 28
|
||||
radius: 9
|
||||
visible: HomeAssistant.phase !== "loading"
|
||||
color: openMouse.containsMouse
|
||||
? Theme.alpha(Theme.accent, 0.20)
|
||||
: Theme.alpha(Theme.accent, 0.11)
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "Open"
|
||||
color: Theme.accent
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
MouseArea {
|
||||
id: openMouse
|
||||
anchors.fill: parent
|
||||
hoverEnabled: true
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: HomeAssistant.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: visible ? 30 : 0
|
||||
visible: HomeAssistant.stale
|
||||
radius: 9
|
||||
color: Theme.alpha(Theme.warn, 0.08)
|
||||
|
||||
Text {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Showing the last known state"
|
||||
color: Theme.warn
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
Text {
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 10
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Retry"
|
||||
color: Theme.accent
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.Medium
|
||||
MouseArea {
|
||||
anchors.fill: parent
|
||||
anchors.margins: -6
|
||||
cursorShape: Qt.PointingHandCursor
|
||||
onClicked: HomeAssistant.refresh()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
width: parent.width
|
||||
expanded: root.expanded && HomeAssistant.entities.length > 0
|
||||
|
||||
ScrollColumn {
|
||||
width: parent.width
|
||||
maxHeight: 270
|
||||
spacing: 2
|
||||
|
||||
Repeater {
|
||||
model: HomeAssistant.entities
|
||||
|
||||
Rectangle {
|
||||
id: entityRow
|
||||
required property var modelData
|
||||
width: parent.width
|
||||
height: 44
|
||||
radius: 10
|
||||
color: entityMouse.containsMouse
|
||||
? Theme.alpha(Theme.fg, 0.09)
|
||||
: "transparent"
|
||||
|
||||
Text {
|
||||
id: entityGlyph
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 11
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "\u{F0335}"
|
||||
color: entityRow.modelData.active ? Theme.warn : Theme.fgDim
|
||||
font.family: Theme.fontMono
|
||||
font.pixelSize: 14
|
||||
}
|
||||
Column {
|
||||
anchors.left: entityGlyph.right
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: entityState.left
|
||||
anchors.rightMargin: 8
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
Text {
|
||||
width: parent.width
|
||||
text: entityRow.modelData.name
|
||||
color: Theme.fg
|
||||
elide: Text.ElideRight
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSize
|
||||
}
|
||||
Text {
|
||||
width: parent.width
|
||||
text: !entityRow.modelData.available ? "Unavailable" : entityRow.modelData.state
|
||||
color: Theme.fgMuted
|
||||
elide: Text.ElideRight
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
}
|
||||
Text {
|
||||
id: entityState
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 11
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: HomeAssistant.busyEntityId === entityRow.modelData.id
|
||||
? "Working"
|
||||
: (entityRow.modelData.active ? "On" : "Off")
|
||||
color: entityRow.modelData.active ? Theme.warn : Theme.fgMuted
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.DemiBold
|
||||
}
|
||||
MouseArea {
|
||||
id: entityMouse
|
||||
anchors.fill: parent
|
||||
enabled: entityRow.modelData.available && HomeAssistant.busyEntityId === ""
|
||||
hoverEnabled: true
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: HomeAssistant.toggleEntity(entityRow.modelData.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function emptyTitle(): string {
|
||||
if (HomeAssistant.phase === "loading")
|
||||
return "Loading your home";
|
||||
if (HomeAssistant.lastError === "authentication-required")
|
||||
return "Authentication required";
|
||||
if (HomeAssistant.lastError === "not-configured")
|
||||
return "Home Assistant is not configured";
|
||||
return "Home Assistant is unavailable";
|
||||
}
|
||||
|
||||
function emptyDetail(): string {
|
||||
if (HomeAssistant.phase === "loading")
|
||||
return "Reading configured favourites";
|
||||
if (HomeAssistant.lastError === "authentication-required")
|
||||
return "Update the long-lived access token";
|
||||
if (HomeAssistant.lastError === "not-configured")
|
||||
return "Add a URL, token and favourites";
|
||||
return "Check the connection and retry";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
import QtQuick
|
||||
import qs.config
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
required property var entity
|
||||
property bool busy: false
|
||||
signal activated
|
||||
|
||||
implicitHeight: 72
|
||||
radius: Theme.cardRadius
|
||||
color: {
|
||||
if (root.entity.active)
|
||||
return Theme.alpha(Theme.warn, tileMouse.containsMouse ? 0.20 : 0.14);
|
||||
return Theme.alpha(Theme.fg, tileMouse.containsMouse ? 0.10 : 0.055);
|
||||
}
|
||||
border.width: 1
|
||||
border.color: root.entity.active
|
||||
? Theme.alpha(Theme.warn, 0.20)
|
||||
: Theme.alpha(Theme.fg, 0.045)
|
||||
|
||||
Behavior on color { ColorAnimation { duration: Theme.durFast } }
|
||||
|
||||
Text {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 9
|
||||
text: root.busy ? "\u{F0772}" : root.glyphFor(root.entity.domain)
|
||||
color: root.entity.active ? Theme.warn : Theme.fgDim
|
||||
font.family: Theme.fontMono
|
||||
font.pixelSize: 15
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 10
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: 8
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 8
|
||||
spacing: 1
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: root.entity.name
|
||||
color: Theme.fg
|
||||
elide: Text.ElideRight
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.DemiBold
|
||||
}
|
||||
Text {
|
||||
width: parent.width
|
||||
text: !root.entity.available ? "Unavailable" : (root.entity.active ? "On" : "Off")
|
||||
color: root.entity.active ? Theme.warn : Theme.fgMuted
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 9
|
||||
}
|
||||
}
|
||||
|
||||
MouseArea {
|
||||
id: tileMouse
|
||||
anchors.fill: parent
|
||||
enabled: root.entity.available && !root.busy
|
||||
hoverEnabled: true
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: root.activated()
|
||||
}
|
||||
|
||||
function glyphFor(domain: string): string {
|
||||
if (domain === "light")
|
||||
return "\u{F0335}";
|
||||
if (domain === "switch")
|
||||
return "\u{F0521}";
|
||||
if (domain === "scene")
|
||||
return "\u{F0FCE}";
|
||||
return "\u{F02DC}";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
import QtQuick
|
||||
import QtQuick.Dialogs
|
||||
import qs.config
|
||||
import qs.services
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
property bool expanded: false
|
||||
signal toggleExpanded
|
||||
|
||||
readonly property var phone: KdeConnect.preferredPhone
|
||||
readonly property var actionModels: [
|
||||
{ id: "share", glyph: "\u{F0142}", label: "Send file" },
|
||||
{ id: "clipboard", glyph: "\u{F014C}", label: "Clipboard" },
|
||||
{ id: "ring", glyph: "\u{F009A}", label: "Ring" }
|
||||
].filter(item => KdeConnect.supports(item.id))
|
||||
|
||||
implicitHeight: content.implicitHeight
|
||||
|
||||
Column {
|
||||
id: content
|
||||
width: parent.width
|
||||
spacing: 6
|
||||
|
||||
ControlSectionHeader {
|
||||
width: parent.width
|
||||
label: "Phone"
|
||||
action: root.phone ? (root.expanded ? "Details ⌃" : "Details ›") : "Refresh"
|
||||
onActionTriggered: {
|
||||
if (root.phone)
|
||||
root.toggleExpanded();
|
||||
else
|
||||
KdeConnect.refresh();
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: phoneContent.implicitHeight + 22
|
||||
radius: Theme.cardRadius + 2
|
||||
color: Theme.alpha(Theme.fg, 0.055)
|
||||
border.width: 1
|
||||
border.color: Theme.alpha(Theme.fg, 0.05)
|
||||
|
||||
Column {
|
||||
id: phoneContent
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: 11
|
||||
spacing: 9
|
||||
|
||||
Item {
|
||||
width: parent.width
|
||||
height: 46
|
||||
|
||||
Rectangle {
|
||||
id: phoneSilhouette
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
width: 29
|
||||
height: 43
|
||||
radius: 8
|
||||
color: Theme.alpha(Theme.accent, 0.07)
|
||||
border.width: 1
|
||||
border.color: Theme.alpha(Theme.fg, 0.42)
|
||||
|
||||
Rectangle {
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 4
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
width: 10
|
||||
height: 2
|
||||
radius: 1
|
||||
color: Theme.alpha(Theme.fg, 0.35)
|
||||
}
|
||||
Rectangle {
|
||||
anchors.right: parent.right
|
||||
anchors.rightMargin: -3
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 10
|
||||
width: 6
|
||||
height: 6
|
||||
radius: 3
|
||||
visible: KdeConnect.phoneReachable
|
||||
color: Theme.ok
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.left: phoneSilhouette.right
|
||||
anchors.leftMargin: 11
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 3
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: root.phone?.name ?? (KdeConnect.available ? "No paired phone" : "KDE Connect unavailable")
|
||||
color: Theme.fg
|
||||
elide: Text.ElideRight
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSize
|
||||
font.weight: Font.DemiBold
|
||||
}
|
||||
Text {
|
||||
width: parent.width
|
||||
text: root.phone
|
||||
? (KdeConnect.phoneReachable ? "● Nearby on Wi-Fi" : "Not nearby")
|
||||
: (KdeConnect.available ? "Pair a device to continue" : "Open settings to repair the service")
|
||||
color: KdeConnect.phoneReachable ? Theme.ok : Theme.fgDim
|
||||
elide: Text.ElideRight
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Grid {
|
||||
id: actionsGrid
|
||||
width: parent.width
|
||||
columns: Math.max(1, root.actionModels.length)
|
||||
spacing: 7
|
||||
visible: root.actionModels.length > 0
|
||||
|
||||
Repeater {
|
||||
model: root.actionModels
|
||||
|
||||
Rectangle {
|
||||
id: actionButton
|
||||
required property var modelData
|
||||
width: (actionsGrid.width - actionsGrid.spacing * Math.max(0, root.actionModels.length - 1)) / Math.max(1, root.actionModels.length)
|
||||
height: 48
|
||||
radius: 11
|
||||
color: actionMouse.containsMouse && actionMouse.enabled
|
||||
? Theme.alpha(Theme.accent, 0.15)
|
||||
: Theme.alpha(Theme.accent, 0.075)
|
||||
opacity: actionMouse.enabled ? 1 : 0.48
|
||||
|
||||
Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.top: parent.top
|
||||
anchors.topMargin: 7
|
||||
text: actionButton.modelData.glyph
|
||||
color: Theme.accent
|
||||
font.family: Theme.fontMono
|
||||
font.pixelSize: 13
|
||||
}
|
||||
Text {
|
||||
anchors.horizontalCenter: parent.horizontalCenter
|
||||
anchors.bottom: parent.bottom
|
||||
anchors.bottomMargin: 6
|
||||
text: actionButton.modelData.label
|
||||
color: Theme.fg
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 9
|
||||
font.weight: Font.Medium
|
||||
}
|
||||
MouseArea {
|
||||
id: actionMouse
|
||||
anchors.fill: parent
|
||||
enabled: KdeConnect.phoneReachable && !KdeConnect.transferActive
|
||||
hoverEnabled: true
|
||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||
onClicked: root.invoke(actionButton.modelData.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
visible: root.phone && !KdeConnect.phoneReachable && root.actionModels.length > 0
|
||||
text: "Actions become available when the iPhone reconnects"
|
||||
color: Theme.fgMuted
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
visible: KdeConnect.lastError !== ""
|
||||
text: root.errorText(KdeConnect.lastError)
|
||||
color: Theme.warn
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: parent.width
|
||||
height: 1
|
||||
visible: KdeConnect.recentExchange !== null
|
||||
color: Theme.alpha(Theme.fg, 0.07)
|
||||
}
|
||||
|
||||
RecentExchange {
|
||||
width: parent.width
|
||||
height: visible ? implicitHeight : 0
|
||||
visible: KdeConnect.recentExchange !== null
|
||||
exchange: KdeConnect.recentExchange ?? ({})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Section {
|
||||
width: parent.width
|
||||
expanded: root.expanded && root.phone !== null
|
||||
|
||||
Column {
|
||||
width: parent.width
|
||||
spacing: 2
|
||||
|
||||
RowButton {
|
||||
width: parent.width
|
||||
icon: "network-wireless-symbolic"
|
||||
label: "Connection"
|
||||
sublabel: KdeConnect.phoneReachable ? "Nearby on Wi-Fi" : "Waiting for the phone"
|
||||
selected: KdeConnect.phoneReachable
|
||||
onClicked: KdeConnect.refresh()
|
||||
}
|
||||
RowButton {
|
||||
width: parent.width
|
||||
icon: "preferences-system-symbolic"
|
||||
label: "Device settings"
|
||||
sublabel: "Pairing, plugins and service health"
|
||||
onClicked: {
|
||||
ShellState.close();
|
||||
ShellState.openSettings("connectivity");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FileDialog {
|
||||
id: fileDialog
|
||||
title: root.phone ? "Send a file to " + root.phone.name : "Send a file"
|
||||
fileMode: FileDialog.OpenFile
|
||||
nameFilters: ["All files (*)"]
|
||||
onAccepted: {
|
||||
const path = root.localPath(selectedFile);
|
||||
if (path !== "")
|
||||
KdeConnect.sendFile(path);
|
||||
}
|
||||
}
|
||||
|
||||
function invoke(action: string): void {
|
||||
if (action === "share")
|
||||
fileDialog.open();
|
||||
else if (action === "clipboard")
|
||||
KdeConnect.sendClipboard();
|
||||
else if (action === "ring")
|
||||
KdeConnect.ring();
|
||||
}
|
||||
|
||||
function localPath(selectedUrl: url): string {
|
||||
const value = String(selectedUrl);
|
||||
if (!value.startsWith("file://"))
|
||||
return "";
|
||||
return decodeURIComponent(value.slice(7));
|
||||
}
|
||||
|
||||
function errorText(code: string): string {
|
||||
if (code === "device-offline")
|
||||
return "The iPhone is not nearby";
|
||||
if (code === "unsupported-action")
|
||||
return "That action is not available on this iPhone";
|
||||
return "The phone action did not finish";
|
||||
}
|
||||
}
|
||||
@@ -18,15 +18,21 @@ PanelWindow {
|
||||
visible: ShellState.quickSettingsOpen
|
||||
color: "transparent"
|
||||
|
||||
// Hangs off the top-right corner, clear of the floating bar.
|
||||
// Hangs directly beneath the top-right status cluster. Two logical pixels
|
||||
// preserve the Prism edge without making the surface feel detached.
|
||||
anchors.top: true
|
||||
anchors.right: true
|
||||
margins.top: Theme.barHeight + Theme.barGap * 2
|
||||
margins.top: Theme.barHeight + Theme.controlCenterTopGap
|
||||
margins.right: Theme.barSideMargin
|
||||
exclusiveZone: 0
|
||||
|
||||
implicitWidth: Theme.popoverWidth
|
||||
implicitWidth: Theme.controlCenterWidth
|
||||
implicitHeight: panel.implicitHeight
|
||||
readonly property string expandedSection: panel.expandedSection
|
||||
|
||||
function expand(name: string): void {
|
||||
panel.expand(name);
|
||||
}
|
||||
|
||||
// The `qs-popover` prefix is matched by a layerrule in hypr/rules.lua.
|
||||
WlrLayershell.namespace: "qs-popover-quicksettings"
|
||||
@@ -36,10 +42,13 @@ PanelWindow {
|
||||
WlrLayershell.keyboardFocus: WlrKeyboardFocus.OnDemand
|
||||
|
||||
onVisibleChanged: {
|
||||
if (root.visible)
|
||||
if (root.visible) {
|
||||
KdeConnect.refresh();
|
||||
HomeAssistant.refresh();
|
||||
openAnim.restart();
|
||||
else
|
||||
} else {
|
||||
panel.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Closes the panel when a click lands anywhere else on the desktop.
|
||||
|
||||
@@ -18,7 +18,7 @@ import qs.widgets
|
||||
Item {
|
||||
id: root
|
||||
|
||||
implicitWidth: Theme.popoverWidth
|
||||
implicitWidth: Theme.controlCenterWidth
|
||||
implicitHeight: content.implicitHeight + Theme.popoverPadding * 2
|
||||
|
||||
// "" | "wifi" | "bluetooth" | "sink" | "source". Only one detail list is
|
||||
@@ -233,6 +233,19 @@ Item {
|
||||
width: content.width
|
||||
}
|
||||
|
||||
// ── Connected life ─────────────────────────────────────────────────
|
||||
HomeControls {
|
||||
width: content.width
|
||||
expanded: root.expandedSection === "home"
|
||||
onToggleExpanded: root.expand("home")
|
||||
}
|
||||
|
||||
PhoneControls {
|
||||
width: content.width
|
||||
expanded: root.expandedSection === "phone"
|
||||
onToggleExpanded: root.expand("phone")
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
width: content.width
|
||||
height: 1
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
import QtQuick
|
||||
import qs.config
|
||||
|
||||
Item {
|
||||
id: root
|
||||
|
||||
required property var exchange
|
||||
|
||||
implicitHeight: 40
|
||||
|
||||
Rectangle {
|
||||
width: 24
|
||||
height: 24
|
||||
radius: 8
|
||||
anchors.left: parent.left
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
color: Theme.alpha(Theme.ok, 0.10)
|
||||
|
||||
Text {
|
||||
anchors.centerIn: parent
|
||||
text: "\u{F012C}"
|
||||
color: Theme.ok
|
||||
font.family: Theme.fontMono
|
||||
font.pixelSize: 12
|
||||
}
|
||||
}
|
||||
|
||||
Column {
|
||||
anchors.left: parent.left
|
||||
anchors.leftMargin: 34
|
||||
anchors.right: timeLabel.left
|
||||
anchors.rightMargin: 8
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
spacing: 2
|
||||
|
||||
Text {
|
||||
width: parent.width
|
||||
text: root.exchange.fileName || "File"
|
||||
color: Theme.fg
|
||||
elide: Text.ElideMiddle
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: Theme.fontSizeSmall
|
||||
font.weight: Font.DemiBold
|
||||
}
|
||||
Text {
|
||||
width: parent.width
|
||||
text: "Sent to " + (root.exchange.deviceName || "phone")
|
||||
color: Theme.fgMuted
|
||||
elide: Text.ElideRight
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 9
|
||||
}
|
||||
}
|
||||
|
||||
Text {
|
||||
id: timeLabel
|
||||
anchors.right: parent.right
|
||||
anchors.verticalCenter: parent.verticalCenter
|
||||
text: "Now"
|
||||
color: Theme.fgMuted
|
||||
font.family: Theme.fontFamily
|
||||
font.pixelSize: 9
|
||||
}
|
||||
}
|
||||
@@ -78,7 +78,7 @@ ShellRoot {
|
||||
// overview or quick settings is instant rather than paying a load.
|
||||
Overview {}
|
||||
// QuickSettings is the window; QuickSettingsPanel is its content.
|
||||
QuickSettings {}
|
||||
QuickSettings { id: quickSettings }
|
||||
DateMenu { id: dateMenu }
|
||||
ClipboardPanel {}
|
||||
CaptureOverlay {}
|
||||
@@ -223,8 +223,12 @@ ShellRoot {
|
||||
function toggle(): void { ShellState.toggle("quicksettings"); }
|
||||
function open(): void { ShellState.open("quicksettings"); }
|
||||
function close(): void { ShellState.close(); }
|
||||
function section(name: string): void { quickSettings.expand(name); }
|
||||
function status(): string {
|
||||
return JSON.stringify({ open: ShellState.quickSettingsOpen });
|
||||
return JSON.stringify({
|
||||
open: ShellState.quickSettingsOpen,
|
||||
expandedSection: quickSettings.expandedSection
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user