Files
Panama/config/dot/quickshell/modules/settings/PhonePage.qml
T
Gabriel Brown 7578348db1 Merge Home & Phone into a three-tab Home that knows your house
Home is now Overview | My Home | Phone. Overview leads with quick-action
tiles (focus, Do Not Disturb, health, snapshots, storage), keeps the
findings card — updates fold in, the reclaim-space prompt is gone on
purpose — and adds glance cards, the next calendar event, and weather.

My Home groups every light by Home Assistant area: the helper gained an
`areas` command (one REST template render, no websocket), and the rooms
degrade to a flat list on setups without areas. The favorites editor and
connection card moved intact. Phone gains a vitals strip — battery and
cell signal read from KDE Connect's plugin D-Bus objects, where absence
is data, not an error — beside ring, clipboard, send-a-file, and the
BlueBubbles handoff.

The retired home-phone id resolves to my-home forever via a new alias
map in SettingsRoutes (with a hasOwnProperty guard so prototype names
cannot leak into settingsPage). Storage no longer claims 0 B free — the
old page read a field the disks helper never emitted.

Contracts updated alongside; per the new workflow, the full suite runs
once at the end of the redesign (see the test backlog note).

Claude-Session: https://claude.ai/code/session_01Ms2FbjQy31TVf3CEvQhGM8
2026-08-23 22:06:18 -04:00

259 lines
9.3 KiB
QML

// 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")
}
}
}