312 lines
12 KiB
QML
312 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
|
||
readonly property var actionModels: phoneActions.actionModels
|
||
|
||
PhoneActions {
|
||
id: phoneActions
|
||
}
|
||
|
||
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)
|
||
border.width: actionMouse.activeFocus ? 2 : 0
|
||
border.color: Theme.accent
|
||
opacity: actionMouse.enabled ? 1 : 0.48
|
||
|
||
Accessible.role: Accessible.Button
|
||
Accessible.name: actionButton.modelData.label
|
||
Accessible.description: root.actionAccessibleDescription(actionButton.modelData.id)
|
||
Accessible.focusable: actionMouse.enabled
|
||
Accessible.focused: actionMouse.activeFocus
|
||
Accessible.onPressAction: {
|
||
if (actionMouse.enabled)
|
||
root.invoke(actionButton.modelData.id);
|
||
}
|
||
|
||
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: root.actionEnabled(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: "KDE Connect actions are unavailable until 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 actionEnabled(action: string): bool {
|
||
return phoneActions.actionEnabled(action);
|
||
}
|
||
|
||
function actionAccessibleDescription(action: string): string {
|
||
if (action === "messages")
|
||
return SystemSettings.bluebubblesAvailable ? "Opens BlueBubbles" : "BlueBubbles is not installed";
|
||
return root.actionEnabled(action) ? "Available through KDE Connect" : "Unavailable through KDE Connect";
|
||
}
|
||
|
||
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";
|
||
}
|
||
}
|