297 lines
12 KiB
QML
297 lines
12 KiB
QML
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
|
||
// Keep the four slots stable. The KDE actions can become unavailable when
|
||
// the phone disconnects, but the layout should not shift beneath Messages.
|
||
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" },
|
||
{ id: "messages", glyph: "\u{F0365}", label: "Messages" }
|
||
]
|
||
|
||
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: 4
|
||
spacing: 7
|
||
|
||
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: actionButton.modelData.id === "messages"
|
||
? SystemSettings.bluebubblesAvailable
|
||
: KdeConnect.phoneReachable
|
||
&& !KdeConnect.transferActive
|
||
&& KdeConnect.supports(actionButton.modelData.id)
|
||
hoverEnabled: true
|
||
activeFocusOnTab: enabled
|
||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||
onClicked: root.invoke(actionButton.modelData.id)
|
||
Keys.onReturnPressed: root.invoke(actionButton.modelData.id)
|
||
Keys.onSpacePressed: 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: !SystemSettings.bluebubblesAvailable
|
||
text: "BlueBubbles is not installed"
|
||
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();
|
||
else if (action === "messages") {
|
||
if (SystemSettings.bluebubblesAvailable && SystemSettings.openApplication("bluebubbles"))
|
||
ShellState.close();
|
||
}
|
||
}
|
||
|
||
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";
|
||
}
|
||
}
|