// Phone — the continuity half of the old Home & Phone page, promoted to a tab // of Home and given vitals. // // The vitals strip reads battery and cell signal from KDE Connect's plugin // D-Bus objects, which exist only while the phone is paired and reachable — a // missing datum renders as an em-dash, never an error. Actions mirror Control // Center's semantics exactly: KDE Connect actions need the phone nearby; // Messages needs only BlueBubbles. import QtQuick import QtQuick.Dialogs import qs.config import qs.services SettingsPage { id: root objectName: "phone-page" title: "Phone" lede: { if (!KdeConnect.available) return "KDE Connect is unavailable — open System Health for the service."; if (!KdeConnect.preferredPhone) return "Pair a phone with KDE Connect to reach it from here."; return String(KdeConnect.preferredPhone.name) + (KdeConnect.phoneReachable ? " · nearby on Wi-Fi" : " · not nearby"); } readonly property bool actionsReady: KdeConnect.phoneReachable && !KdeConnect.transferActive function localPath(selectedUrl: url): string { const value = String(selectedUrl); if (!value.startsWith("file://")) return ""; return decodeURIComponent(value.slice(7)); } FileDialog { id: fileDialog title: KdeConnect.preferredPhone ? "Send a file to " + KdeConnect.preferredPhone.name : "Send a file" fileMode: FileDialog.OpenFile nameFilters: ["All files (*)"] onAccepted: { const path = root.localPath(selectedFile); if (path !== "") KdeConnect.sendFile(path); } } // ── Vitals ────────────────────────────────────────────────────────────── Row { id: vitals width: parent.width spacing: 12 Repeater { model: [ { value: KdeConnect.phoneBattery ? KdeConnect.phoneBattery.charge + "%" : "—", caption: KdeConnect.phoneBattery ? (KdeConnect.phoneBattery.charging ? "Battery · charging" : "Battery") : "Battery" }, { value: KdeConnect.phoneSignal ? String(KdeConnect.phoneSignal.networkType) : "—", caption: KdeConnect.phoneSignal ? "Signal · " + KdeConnect.phoneSignal.strength + " of 4" : "Signal" }, { value: KdeConnect.phoneReachable ? "Nearby" : (KdeConnect.pairedCount > 0 ? "Away" : "Unpaired"), caption: KdeConnect.phoneReachable ? "Reachable on Wi-Fi" : (KdeConnect.pairedCount > 0 ? "Actions wait for the phone to reconnect" : "Pair a device to continue") } ] delegate: Rectangle { id: vital required property var modelData width: (vitals.width - vitals.spacing * 2) / 3 height: 66 radius: 12 color: Theme.alpha(Theme.fg, 0.045) border.width: 1 border.color: Theme.alpha(Theme.fg, 0.06) Column { anchors.left: parent.left anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter anchors.leftMargin: 14 anchors.rightMargin: 14 spacing: 3 Text { text: String(vital.modelData.value) color: Theme.fg font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeLarge + 3 font.weight: Font.DemiBold font.features: Theme.tabularFigures } Text { width: parent.width text: String(vital.modelData.caption) color: Theme.fgDim font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall elide: Text.ElideRight } } } } } // ── Reach it ──────────────────────────────────────────────────────────── SettingsCard { title: "Reach it" subtitle: KdeConnect.transferActive ? "Sending " + KdeConnect.transferFileName + "…" : "Everything you actually do weekly, one card." SettingRow { label: "Ring" detail: "Even when silenced" controlWidth: 110 SettingsButton { anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter text: "Ring" enabled: root.actionsReady && KdeConnect.supports("ring") onClicked: KdeConnect.ring() } } SettingRow { label: "Clipboard" detail: "Send what you just copied" controlWidth: 110 SettingsButton { anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter text: "Send" enabled: root.actionsReady && KdeConnect.supports("clipboard") onClicked: KdeConnect.sendClipboard() } } SettingRow { label: "Send a file" detail: KdeConnect.recentExchange ? "Last sent " + KdeConnect.recentExchange.fileName : "Lands in the phone's downloads" divider: KdeConnect.transferActive controlWidth: 110 SettingsButton { anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter text: "Choose…" enabled: root.actionsReady && KdeConnect.supports("share") onClicked: fileDialog.open() } } SettingRow { visible: KdeConnect.transferActive label: "Sending " + KdeConnect.transferFileName detail: "To " + KdeConnect.transferDeviceName divider: false controlWidth: 110 SettingsButton { anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter text: "Cancel" tone: "danger" onClicked: KdeConnect.cancelTransfer() } } } // ── Messages ──────────────────────────────────────────────────────────── SettingsCard { title: "Messages" subtitle: "Kept independent from phone connectivity." SettingRow { label: "iMessage" detail: "Opens BlueBubbles" divider: false controlWidth: 204 Row { anchors.right: parent.right anchors.verticalCenter: parent.verticalCenter spacing: 12 Text { anchors.verticalCenter: parent.verticalCenter text: SystemSettings.bluebubblesAvailable ? "Installed" : "Unavailable" color: SystemSettings.bluebubblesAvailable ? Theme.ok : Theme.fgMuted font.family: Theme.fontFamily font.pixelSize: Theme.fontSizeSmall } SettingsButton { id: openBlueBubblesButton text: "Open" enabled: SystemSettings.bluebubblesAvailable activeFocusOnTab: enabled border.width: activeFocus ? 2 : 1 border.color: activeFocus ? Theme.accent : Theme.alpha(Theme.fg, 0.08) onClicked: SystemSettings.openApplication("bluebubbles") Keys.onReturnPressed: if (enabled) SystemSettings.openApplication("bluebubbles") Keys.onSpacePressed: if (enabled) SystemSettings.openApplication("bluebubbles") } } } } // ── Device ────────────────────────────────────────────────────────────── SettingsCard { title: "Device" ActionRow { label: "Connection" detail: "Ask the service to look again" action: "Refresh" onTriggered: KdeConnect.refresh() } ActionRow { label: "Device settings" detail: "Pairing, plugins and service health" action: "Open" divider: false onTriggered: ShellState.openSettings("connectivity") } } }