Build the Home accessory shelf
This commit is contained in:
@@ -0,0 +1,132 @@
|
|||||||
|
// A light dimmer that previews locally and sends one value when interaction
|
||||||
|
// ends. Home Assistant never sees the intermediate pointer positions.
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import qs.config
|
||||||
|
|
||||||
|
Item {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property int value: 0
|
||||||
|
property int previewValue: 0
|
||||||
|
|
||||||
|
readonly property bool pressed: drag.pressed
|
||||||
|
|
||||||
|
signal previewChanged(int value)
|
||||||
|
signal committed(int value)
|
||||||
|
|
||||||
|
implicitWidth: 160
|
||||||
|
implicitHeight: 32
|
||||||
|
activeFocusOnTab: root.enabled
|
||||||
|
opacity: root.enabled ? 1 : 0.42
|
||||||
|
|
||||||
|
onValueChanged: {
|
||||||
|
if (!drag.pressed)
|
||||||
|
root.updatePreview(root.value, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
Component.onCompleted: root.updatePreview(root.value, false)
|
||||||
|
|
||||||
|
Keys.onLeftPressed: root.commitStep(-5)
|
||||||
|
Keys.onDownPressed: root.commitStep(-5)
|
||||||
|
Keys.onRightPressed: root.commitStep(5)
|
||||||
|
Keys.onUpPressed: root.commitStep(5)
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: track
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
height: 10
|
||||||
|
radius: 5
|
||||||
|
color: Theme.alpha(Theme.fg, 0.105)
|
||||||
|
border.width: root.activeFocus ? 1 : 0
|
||||||
|
border.color: Theme.alpha(Theme.warn, 0.72)
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: fill
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
width: track.width * root.previewValue / 100
|
||||||
|
radius: track.radius
|
||||||
|
color: Theme.warn
|
||||||
|
|
||||||
|
Behavior on width {
|
||||||
|
enabled: !drag.pressed
|
||||||
|
NumberAnimation {
|
||||||
|
duration: Theme.durFast
|
||||||
|
easing.type: Easing.OutQuad
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: knob
|
||||||
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
|
x: Math.max(0, Math.min(track.width - width,
|
||||||
|
track.width * root.previewValue / 100 - width / 2))
|
||||||
|
width: 16
|
||||||
|
height: 16
|
||||||
|
radius: 8
|
||||||
|
color: Theme.fg
|
||||||
|
border.width: 1
|
||||||
|
border.color: Theme.alpha(Theme.bgDark, 0.38)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The 32 px interaction surface is intentionally much taller than the
|
||||||
|
// 10 px track, while remaining inside this component's layout bounds.
|
||||||
|
MouseArea {
|
||||||
|
id: drag
|
||||||
|
anchors.fill: parent
|
||||||
|
enabled: root.enabled
|
||||||
|
hoverEnabled: true
|
||||||
|
preventStealing: true
|
||||||
|
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
|
|
||||||
|
onPressed: event => {
|
||||||
|
root.forceActiveFocus();
|
||||||
|
root.previewAt(event.x);
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
onPositionChanged: event => {
|
||||||
|
if (pressed)
|
||||||
|
root.previewAt(event.x);
|
||||||
|
}
|
||||||
|
onReleased: event => {
|
||||||
|
root.committed(root.previewValue);
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
onCanceled: root.updatePreview(root.value, false)
|
||||||
|
onWheel: event => {
|
||||||
|
const direction = event.angleDelta.y >= 0 ? 5 : -5;
|
||||||
|
root.commitStep(direction);
|
||||||
|
event.accepted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clamp(candidate: real): int {
|
||||||
|
return Math.max(0, Math.min(100, Math.round(candidate)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function updatePreview(candidate: real, announce: bool): void {
|
||||||
|
const nextValue = root.clamp(candidate);
|
||||||
|
if (root.previewValue === nextValue)
|
||||||
|
return;
|
||||||
|
root.previewValue = nextValue;
|
||||||
|
if (announce)
|
||||||
|
root.previewChanged(nextValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
function previewAt(pointerX: real): void {
|
||||||
|
root.updatePreview(pointerX / Math.max(1, drag.width) * 100, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
function commitStep(delta: int): void {
|
||||||
|
if (!root.enabled)
|
||||||
|
return;
|
||||||
|
root.updatePreview(root.previewValue + delta, true);
|
||||||
|
root.committed(root.previewValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,9 @@ Item {
|
|||||||
property bool expanded: false
|
property bool expanded: false
|
||||||
signal toggleExpanded
|
signal toggleExpanded
|
||||||
|
|
||||||
|
readonly property bool hasSelection: HomeAssistant.selectedEntities.length > 0
|
||||||
|
readonly property bool showSetup: HomeAssistant.phase === "ready" && !root.hasSelection
|
||||||
|
|
||||||
implicitHeight: content.implicitHeight
|
implicitHeight: content.implicitHeight
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
@@ -18,46 +21,50 @@ Item {
|
|||||||
ControlSectionHeader {
|
ControlSectionHeader {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
label: "Home"
|
label: "Home"
|
||||||
action: HomeAssistant.entities.length > 0
|
action: root.headerAction()
|
||||||
? HomeAssistant.entities.length + " accessories " + (root.expanded ? "⌃" : "›")
|
|
||||||
: "Retry"
|
|
||||||
actionEnabled: HomeAssistant.phase !== "loading"
|
actionEnabled: HomeAssistant.phase !== "loading"
|
||||||
onActionTriggered: {
|
onActionTriggered: {
|
||||||
if (HomeAssistant.entities.length > 0)
|
if (root.hasSelection) {
|
||||||
root.toggleExpanded();
|
root.toggleExpanded();
|
||||||
else
|
} else if (root.showSetup) {
|
||||||
|
root.openHomeSettings();
|
||||||
|
} else {
|
||||||
HomeAssistant.refresh();
|
HomeAssistant.refresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: homeCard
|
id: restingCard
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: visible ? homeGrid.implicitHeight + 20 : 0
|
height: visible ? restingGrid.implicitHeight + 20 : 0
|
||||||
visible: HomeAssistant.entities.length > 0
|
visible: root.hasSelection && !root.expanded
|
||||||
radius: Theme.cardRadius + 2
|
radius: Theme.cardRadius + 2
|
||||||
color: Theme.alpha(Theme.accent, 0.045)
|
color: Theme.alpha(Theme.warn, 0.028)
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: Theme.alpha(Theme.accent, 0.12)
|
border.color: Theme.alpha(Theme.warn, 0.095)
|
||||||
|
|
||||||
Grid {
|
Grid {
|
||||||
id: homeGrid
|
id: restingGrid
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.top: parent.top
|
anchors.top: parent.top
|
||||||
anchors.margins: 10
|
anchors.margins: 10
|
||||||
columns: 4
|
columns: 2
|
||||||
spacing: 7
|
spacing: 8
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: HomeAssistant.visibleEntities
|
model: HomeAssistant.visibleEntities
|
||||||
|
|
||||||
HomeTile {
|
HomeTile {
|
||||||
required property var modelData
|
required property var modelData
|
||||||
width: (homeGrid.width - homeGrid.spacing * 3) / 4
|
width: (restingGrid.width - restingGrid.spacing) / 2
|
||||||
entity: modelData
|
entity: modelData
|
||||||
busy: HomeAssistant.busyEntityId === modelData.id
|
busy: HomeAssistant.isBusy(modelData.id)
|
||||||
onActivated: HomeAssistant.toggleEntity(modelData.id)
|
pendingBrightness: HomeAssistant.pendingFor(modelData.id)
|
||||||
|
actionError: HomeAssistant.errorFor(modelData.id)
|
||||||
|
onPowerRequested: HomeAssistant.toggleEntity(modelData.id)
|
||||||
|
onBrightnessRequested: value => HomeAssistant.setBrightness(modelData.id, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,28 +72,32 @@ Item {
|
|||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: visible ? 66 : 0
|
height: visible ? 72 : 0
|
||||||
visible: HomeAssistant.entities.length === 0
|
visible: !root.hasSelection
|
||||||
radius: Theme.cardRadius
|
radius: Theme.cardRadius
|
||||||
color: Theme.alpha(Theme.fg, 0.05)
|
color: root.showSetup
|
||||||
|
? Theme.alpha(Theme.accent, 0.055)
|
||||||
|
: Theme.alpha(Theme.fg, 0.045)
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: Theme.alpha(Theme.fg, 0.045)
|
border.color: root.showSetup
|
||||||
|
? Theme.alpha(Theme.accent, 0.12)
|
||||||
|
: Theme.alpha(Theme.fg, 0.055)
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
id: stateIcon
|
id: stateIcon
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: 12
|
anchors.leftMargin: 13
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: HomeAssistant.phase === "loading" ? "\u{F0772}" : "\u{F02DC}"
|
text: root.showSetup ? "\u{F0335}" : "\u{F02DC}"
|
||||||
color: HomeAssistant.phase === "loading" ? Theme.accent : Theme.fgDim
|
color: root.showSetup ? Theme.accent : Theme.fgDim
|
||||||
font.family: Theme.fontMono
|
font.family: Theme.fontMono
|
||||||
font.pixelSize: 16
|
font.pixelSize: 17
|
||||||
}
|
}
|
||||||
|
|
||||||
Column {
|
Column {
|
||||||
anchors.left: stateIcon.right
|
anchors.left: stateIcon.right
|
||||||
anchors.leftMargin: 11
|
anchors.leftMargin: 11
|
||||||
anchors.right: openHome.left
|
anchors.right: emptyAction.left
|
||||||
anchors.rightMargin: 10
|
anchors.rightMargin: 10
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
spacing: 3
|
spacing: 3
|
||||||
@@ -111,61 +122,66 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
id: openHome
|
id: emptyAction
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: 10
|
anchors.rightMargin: 10
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
width: 48
|
width: root.showSetup ? 68 : 52
|
||||||
height: 28
|
height: 30
|
||||||
radius: 9
|
radius: 10
|
||||||
visible: HomeAssistant.phase !== "loading"
|
visible: HomeAssistant.phase !== "loading"
|
||||||
color: openMouse.containsMouse
|
color: emptyActionMouse.containsMouse
|
||||||
? Theme.alpha(Theme.accent, 0.20)
|
? Theme.alpha(Theme.accent, 0.20)
|
||||||
: Theme.alpha(Theme.accent, 0.11)
|
: Theme.alpha(Theme.accent, 0.105)
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: "Open"
|
text: root.showSetup ? "Manage" : "Open"
|
||||||
color: Theme.accent
|
color: Theme.accent
|
||||||
font.family: Theme.fontFamily
|
font.family: Theme.fontFamily
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.DemiBold
|
||||||
}
|
}
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: openMouse
|
id: emptyActionMouse
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: HomeAssistant.open()
|
onClicked: {
|
||||||
|
if (root.showSetup)
|
||||||
|
root.openHomeSettings();
|
||||||
|
else
|
||||||
|
HomeAssistant.open();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
Rectangle {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
height: visible ? 30 : 0
|
height: visible ? 34 : 0
|
||||||
visible: HomeAssistant.stale
|
visible: HomeAssistant.stale
|
||||||
radius: 9
|
radius: 10
|
||||||
color: Theme.alpha(Theme.warn, 0.08)
|
color: Theme.alpha(Theme.warn, 0.075)
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
anchors.left: parent.left
|
anchors.left: parent.left
|
||||||
anchors.leftMargin: 10
|
anchors.leftMargin: 11
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: "Showing the last known state"
|
text: "Last known state · " + root.countSummary()
|
||||||
color: Theme.warn
|
color: Theme.warn
|
||||||
font.family: Theme.fontFamily
|
font.family: Theme.fontFamily
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
}
|
}
|
||||||
Text {
|
Text {
|
||||||
anchors.right: parent.right
|
anchors.right: parent.right
|
||||||
anchors.rightMargin: 10
|
anchors.rightMargin: 11
|
||||||
anchors.verticalCenter: parent.verticalCenter
|
anchors.verticalCenter: parent.verticalCenter
|
||||||
text: "Retry"
|
text: "Retry"
|
||||||
color: Theme.accent
|
color: Theme.accent
|
||||||
font.family: Theme.fontFamily
|
font.family: Theme.fontFamily
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
font.weight: Font.Medium
|
font.weight: Font.DemiBold
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: -6
|
anchors.margins: -6
|
||||||
@@ -177,88 +193,66 @@ Item {
|
|||||||
|
|
||||||
Section {
|
Section {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
expanded: root.expanded && HomeAssistant.entities.length > 0
|
expanded: root.expanded && root.hasSelection
|
||||||
|
|
||||||
ScrollColumn {
|
ScrollColumn {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
maxHeight: 270
|
maxHeight: 324
|
||||||
spacing: 2
|
spacing: 8
|
||||||
|
|
||||||
|
Grid {
|
||||||
|
id: expandedGrid
|
||||||
|
width: parent.width
|
||||||
|
columns: 2
|
||||||
|
spacing: 8
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: HomeAssistant.entities
|
model: HomeAssistant.selectedEntities
|
||||||
|
|
||||||
Rectangle {
|
HomeTile {
|
||||||
id: entityRow
|
|
||||||
required property var modelData
|
required property var modelData
|
||||||
width: parent.width
|
width: (expandedGrid.width - expandedGrid.spacing) / 2
|
||||||
height: 44
|
entity: modelData
|
||||||
radius: 10
|
busy: HomeAssistant.isBusy(modelData.id)
|
||||||
color: entityMouse.containsMouse
|
pendingBrightness: HomeAssistant.pendingFor(modelData.id)
|
||||||
? Theme.alpha(Theme.fg, 0.09)
|
actionError: HomeAssistant.errorFor(modelData.id)
|
||||||
: "transparent"
|
onPowerRequested: HomeAssistant.toggleEntity(modelData.id)
|
||||||
|
onBrightnessRequested: value => HomeAssistant.setBrightness(modelData.id, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Text {
|
RowButton {
|
||||||
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
|
width: parent.width
|
||||||
text: entityRow.modelData.name
|
icon: "preferences-system-symbolic"
|
||||||
color: Theme.fg
|
label: "Manage in Settings"
|
||||||
elide: Text.ElideRight
|
sublabel: root.countSummary()
|
||||||
font.family: Theme.fontFamily
|
onClicked: root.openHomeSettings()
|
||||||
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 headerAction(): string {
|
||||||
|
if (root.hasSelection) {
|
||||||
|
const noun = HomeAssistant.configuredCount === 1 ? "accessory" : "accessories";
|
||||||
|
return HomeAssistant.configuredCount + " " + noun + (root.expanded ? " ⌃" : " ›");
|
||||||
}
|
}
|
||||||
|
if (HomeAssistant.phase === "loading")
|
||||||
|
return "Loading";
|
||||||
|
if (root.showSetup)
|
||||||
|
return "Manage";
|
||||||
|
return "Retry";
|
||||||
|
}
|
||||||
|
|
||||||
|
function countSummary(): string {
|
||||||
|
return HomeAssistant.configuredCount + " selected · "
|
||||||
|
+ HomeAssistant.discoveredCount + " discovered";
|
||||||
}
|
}
|
||||||
|
|
||||||
function emptyTitle(): string {
|
function emptyTitle(): string {
|
||||||
|
if (root.showSetup)
|
||||||
|
return "Choose your accessories";
|
||||||
if (HomeAssistant.phase === "loading")
|
if (HomeAssistant.phase === "loading")
|
||||||
return "Loading your home";
|
return "Loading your home";
|
||||||
if (HomeAssistant.lastError === "authentication-required")
|
if (HomeAssistant.lastError === "authentication-required")
|
||||||
@@ -269,12 +263,23 @@ Item {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function emptyDetail(): string {
|
function emptyDetail(): string {
|
||||||
|
if (root.showSetup) {
|
||||||
|
if (HomeAssistant.discoveredCount === 0)
|
||||||
|
return "No lights discovered yet";
|
||||||
|
const noun = HomeAssistant.discoveredCount === 1 ? "light" : "lights";
|
||||||
|
return HomeAssistant.discoveredCount + " " + noun + " ready to add";
|
||||||
|
}
|
||||||
if (HomeAssistant.phase === "loading")
|
if (HomeAssistant.phase === "loading")
|
||||||
return "Reading configured favourites";
|
return "Finding your selected lights";
|
||||||
if (HomeAssistant.lastError === "authentication-required")
|
if (HomeAssistant.lastError === "authentication-required")
|
||||||
return "Update the long-lived access token";
|
return "Update the long-lived access token";
|
||||||
if (HomeAssistant.lastError === "not-configured")
|
if (HomeAssistant.lastError === "not-configured")
|
||||||
return "Add a URL, token and favourites";
|
return "Connect Home Assistant in Settings";
|
||||||
return "Check the connection and retry";
|
return "Check the connection, then retry";
|
||||||
|
}
|
||||||
|
|
||||||
|
function openHomeSettings(): void {
|
||||||
|
ShellState.close();
|
||||||
|
ShellState.openSettings("home-phone");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,76 +6,164 @@ Rectangle {
|
|||||||
|
|
||||||
required property var entity
|
required property var entity
|
||||||
property bool busy: false
|
property bool busy: false
|
||||||
signal activated
|
property int pendingBrightness: -1
|
||||||
|
property string actionError: ""
|
||||||
|
|
||||||
implicitHeight: 72
|
signal powerRequested
|
||||||
radius: Theme.cardRadius
|
signal brightnessRequested(int value)
|
||||||
|
|
||||||
|
readonly property int confirmedBrightness: root.clamp(root.entity.brightnessPct ?? 0)
|
||||||
|
readonly property int displayedBrightness: brightnessSlider.previewValue
|
||||||
|
readonly property bool powerEnabled: root.entity.available && !root.busy
|
||||||
|
readonly property bool dimmerEnabled: root.entity.available
|
||||||
|
&& root.entity.dimmable
|
||||||
|
&& !root.busy
|
||||||
|
|
||||||
|
implicitHeight: 124
|
||||||
|
radius: 14
|
||||||
color: {
|
color: {
|
||||||
if (root.entity.active)
|
if (root.entity.active)
|
||||||
return Theme.alpha(Theme.warn, tileMouse.containsMouse ? 0.20 : 0.14);
|
return Theme.alpha(Theme.warn, powerArea.containsMouse ? 0.145 : 0.095);
|
||||||
return Theme.alpha(Theme.fg, tileMouse.containsMouse ? 0.10 : 0.055);
|
return Theme.alpha(Theme.fg, powerArea.containsMouse ? 0.078 : 0.045);
|
||||||
}
|
}
|
||||||
border.width: 1
|
border.width: 1
|
||||||
border.color: root.entity.active
|
border.color: {
|
||||||
? Theme.alpha(Theme.warn, 0.20)
|
if (root.activeFocus)
|
||||||
: Theme.alpha(Theme.fg, 0.045)
|
return Theme.alpha(Theme.accent, 0.72);
|
||||||
|
if (root.entity.active)
|
||||||
|
return Theme.alpha(Theme.warn, 0.17);
|
||||||
|
return Theme.alpha(Theme.fg, 0.065);
|
||||||
|
}
|
||||||
|
activeFocusOnTab: root.powerEnabled
|
||||||
|
|
||||||
Behavior on color { ColorAnimation { duration: Theme.durFast } }
|
Behavior on color { ColorAnimation { duration: Theme.durFast } }
|
||||||
|
Behavior on border.color { ColorAnimation { duration: Theme.durFast } }
|
||||||
|
|
||||||
|
Keys.onSpacePressed: {
|
||||||
|
if (root.powerEnabled)
|
||||||
|
root.powerRequested();
|
||||||
|
}
|
||||||
|
Keys.onReturnPressed: {
|
||||||
|
if (root.powerEnabled)
|
||||||
|
root.powerRequested();
|
||||||
|
}
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
id: bulb
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 13
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.topMargin: 13
|
||||||
|
width: 30
|
||||||
|
height: 30
|
||||||
|
radius: 10
|
||||||
|
color: root.entity.active
|
||||||
|
? Theme.alpha(Theme.warn, 0.16)
|
||||||
|
: Theme.alpha(Theme.fg, 0.065)
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
anchors.left: parent.left
|
anchors.centerIn: parent
|
||||||
anchors.leftMargin: 10
|
text: root.busy ? "\u{F0772}" : "\u{F0335}"
|
||||||
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
|
color: root.entity.active ? Theme.warn : Theme.fgDim
|
||||||
font.family: Theme.fontMono
|
font.family: Theme.fontMono
|
||||||
font.pixelSize: 15
|
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 {
|
Text {
|
||||||
width: parent.width
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 13
|
||||||
|
anchors.verticalCenter: bulb.verticalCenter
|
||||||
|
text: root.entity.dimmable ? root.displayedBrightness + "%" : "—"
|
||||||
|
color: root.entity.active || root.pendingBrightness >= 0
|
||||||
|
? Theme.warn
|
||||||
|
: Theme.fgDim
|
||||||
|
font.family: Theme.fontFamily
|
||||||
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
|
font.weight: Font.DemiBold
|
||||||
|
font.features: Theme.tabularFigures
|
||||||
|
}
|
||||||
|
|
||||||
|
Text {
|
||||||
|
id: aliasLabel
|
||||||
|
anchors.left: parent.left
|
||||||
|
anchors.leftMargin: 13
|
||||||
|
anchors.right: parent.right
|
||||||
|
anchors.rightMargin: 13
|
||||||
|
anchors.top: bulb.bottom
|
||||||
|
anchors.topMargin: 8
|
||||||
text: root.entity.name
|
text: root.entity.name
|
||||||
color: Theme.fg
|
color: Theme.fg
|
||||||
elide: Text.ElideRight
|
elide: Text.ElideRight
|
||||||
font.family: Theme.fontFamily
|
font.family: Theme.fontFamily
|
||||||
font.pixelSize: Theme.fontSizeSmall
|
font.pixelSize: Theme.fontSize
|
||||||
font.weight: Font.DemiBold
|
font.weight: Font.DemiBold
|
||||||
}
|
}
|
||||||
|
|
||||||
Text {
|
Text {
|
||||||
width: parent.width
|
anchors.left: aliasLabel.left
|
||||||
text: !root.entity.available ? "Unavailable" : (root.entity.active ? "On" : "Off")
|
anchors.right: aliasLabel.right
|
||||||
color: root.entity.active ? Theme.warn : Theme.fgMuted
|
anchors.top: aliasLabel.bottom
|
||||||
|
anchors.topMargin: 2
|
||||||
|
text: root.secondaryText()
|
||||||
|
color: root.actionError !== ""
|
||||||
|
? Theme.warn
|
||||||
|
: (root.entity.active ? Theme.warn : Theme.fgMuted)
|
||||||
|
elide: Text.ElideRight
|
||||||
font.family: Theme.fontFamily
|
font.family: Theme.fontFamily
|
||||||
font.pixelSize: 9
|
font.pixelSize: Theme.fontSizeSmall
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
id: tileMouse
|
id: powerArea
|
||||||
anchors.fill: parent
|
anchors.left: parent.left
|
||||||
enabled: root.entity.available && !root.busy
|
anchors.right: parent.right
|
||||||
|
anchors.top: parent.top
|
||||||
|
anchors.bottom: brightnessSlider.top
|
||||||
|
anchors.bottomMargin: 1
|
||||||
|
enabled: root.powerEnabled
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
cursorShape: enabled ? Qt.PointingHandCursor : Qt.ArrowCursor
|
||||||
onClicked: root.activated()
|
onPressed: root.forceActiveFocus()
|
||||||
|
onClicked: root.powerRequested()
|
||||||
}
|
}
|
||||||
|
|
||||||
function glyphFor(domain: string): string {
|
HomeBrightnessSlider {
|
||||||
if (domain === "light")
|
id: brightnessSlider
|
||||||
return "\u{F0335}";
|
anchors.left: parent.left
|
||||||
if (domain === "switch")
|
anchors.leftMargin: 13
|
||||||
return "\u{F0521}";
|
anchors.right: parent.right
|
||||||
if (domain === "scene")
|
anchors.rightMargin: 13
|
||||||
return "\u{F0FCE}";
|
anchors.bottom: parent.bottom
|
||||||
return "\u{F02DC}";
|
anchors.bottomMargin: 5
|
||||||
|
value: root.pendingBrightness >= 0
|
||||||
|
? root.pendingBrightness
|
||||||
|
: root.confirmedBrightness
|
||||||
|
enabled: root.dimmerEnabled
|
||||||
|
onCommitted: value => root.brightnessRequested(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
function clamp(candidate: real): int {
|
||||||
|
return Math.max(0, Math.min(100, Math.round(candidate)));
|
||||||
|
}
|
||||||
|
|
||||||
|
function secondaryText(): string {
|
||||||
|
if (root.actionError !== "")
|
||||||
|
return root.errorText(root.actionError);
|
||||||
|
if (!root.entity.available)
|
||||||
|
return "Unavailable";
|
||||||
|
if (root.busy && root.pendingBrightness >= 0)
|
||||||
|
return "Setting " + root.pendingBrightness + "%";
|
||||||
|
if (root.busy)
|
||||||
|
return "Updating";
|
||||||
|
return root.entity.active ? "On" : "Off";
|
||||||
|
}
|
||||||
|
|
||||||
|
function errorText(code: string): string {
|
||||||
|
if (code === "authentication-required")
|
||||||
|
return "Authentication required";
|
||||||
|
if (code === "entity-not-discovered")
|
||||||
|
return "Light is unavailable";
|
||||||
|
return "Couldn’t update light";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
module qs.modules.quicksettings
|
||||||
|
AudioDeviceList 1.0 AudioDeviceList.qml
|
||||||
|
AudioSlider 1.0 AudioSlider.qml
|
||||||
|
BluetoothList 1.0 BluetoothList.qml
|
||||||
|
BrightnessControl 1.0 BrightnessControl.qml
|
||||||
|
ControlSectionHeader 1.0 ControlSectionHeader.qml
|
||||||
|
HomeBrightnessSlider 1.0 HomeBrightnessSlider.qml
|
||||||
|
HomeControls 1.0 HomeControls.qml
|
||||||
|
HomeTile 1.0 HomeTile.qml
|
||||||
|
IconButton 1.0 IconButton.qml
|
||||||
|
PhoneControls 1.0 PhoneControls.qml
|
||||||
|
QuickSettings 1.0 QuickSettings.qml
|
||||||
|
QuickSettingsPanel 1.0 QuickSettingsPanel.qml
|
||||||
|
RecentExchange 1.0 RecentExchange.qml
|
||||||
|
RowButton 1.0 RowButton.qml
|
||||||
|
ScrollColumn 1.0 ScrollColumn.qml
|
||||||
|
Section 1.0 Section.qml
|
||||||
|
WifiList 1.0 WifiList.qml
|
||||||
@@ -2,72 +2,181 @@
|
|||||||
|
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
|
project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||||
|
source_config_path="$project_root/config/dot/quickshell"
|
||||||
|
quicksettings_path="$source_config_path/modules/quicksettings"
|
||||||
|
state_home="$(mktemp -d /tmp/panama-control-center-ui.XXXXXX)"
|
||||||
|
config_path="$state_home/quickshell"
|
||||||
|
helper_log="$state_home/home-helper.log"
|
||||||
|
shell_log="$state_home/quickshell.log"
|
||||||
|
|
||||||
fail() {
|
fail() {
|
||||||
printf 'Control Center contract: %s\n' "$1" >&2
|
printf 'Control Center contract: %s\n' "$1" >&2
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
project_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
qs_for_test() {
|
||||||
|
QS_CONFIG_PATH="$config_path" XDG_STATE_HOME="$state_home" \
|
||||||
|
QS_DISABLE_CRASH_HANDLER=1 \
|
||||||
|
PANAMA_HOME_HELPER_LOG="$helper_log" \
|
||||||
|
qs -p "$config_path" "$@"
|
||||||
|
}
|
||||||
|
|
||||||
|
stop_test_shell() {
|
||||||
|
qs_for_test kill >/dev/null 2>&1 || true
|
||||||
|
for _ in $(seq 1 80); do
|
||||||
|
if ! qs_for_test list 2>/dev/null | rg '^Instance ' >/dev/null \
|
||||||
|
&& ! qs_for_test ipc show >/dev/null 2>&1; then
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
qs ipc call quicksettings close >/dev/null 2>&1 || true
|
qs_for_test ipc call quicksettings close >/dev/null 2>&1 || true
|
||||||
qs ipc call kdeconnect reset >/dev/null 2>&1 || true
|
qs_for_test ipc call kdeconnect reset >/dev/null 2>&1 || true
|
||||||
qs ipc call home-assistant reset >/dev/null 2>&1 || true
|
qs_for_test ipc call home-assistant reset >/dev/null 2>&1 || true
|
||||||
|
if stop_test_shell; then
|
||||||
|
rm -rf "$state_home"
|
||||||
|
else
|
||||||
|
printf 'Control Center contract: branch shell did not stop; retained %s\n' \
|
||||||
|
"$state_home" >&2
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
|
|
||||||
|
start_test_shell() {
|
||||||
|
stop_test_shell || fail 'pre-existing branch shell did not stop cleanly'
|
||||||
|
for _attempt in 1 2; do
|
||||||
|
qs_for_test --daemonize >"$shell_log" 2>&1
|
||||||
|
for _ in $(seq 1 80); do
|
||||||
|
if qs_for_test ipc show 2>/dev/null | rg '^target quicksettings$' >/dev/null; then
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
sleep 0.1
|
||||||
|
done
|
||||||
|
stop_test_shell || fail 'failed branch-shell attempt did not stop cleanly'
|
||||||
|
done
|
||||||
|
sed -n '1,200p' "$shell_log" >&2
|
||||||
|
fail 'isolated branch shell did not start'
|
||||||
|
}
|
||||||
|
|
||||||
rg -Fq 'readonly property int controlCenterWidth: 430' \
|
rg -Fq 'readonly property int controlCenterWidth: 430' \
|
||||||
"$project_root/config/dot/quickshell/config/Theme.qml" \
|
"$source_config_path/config/Theme.qml" \
|
||||||
|| fail 'approved Control Center width is missing'
|
|| fail 'approved Control Center width is missing'
|
||||||
rg -Fq 'readonly property int controlCenterTopGap: 2' \
|
rg -Fq 'readonly property int controlCenterTopGap: 2' \
|
||||||
"$project_root/config/dot/quickshell/config/Theme.qml" \
|
"$source_config_path/config/Theme.qml" \
|
||||||
|| fail 'approved top attachment is missing'
|
|| fail 'approved top attachment is missing'
|
||||||
rg -Fq 'margins.top: Theme.barHeight + Theme.controlCenterTopGap' \
|
rg -Fq 'margins.top: Theme.barHeight + Theme.controlCenterTopGap' \
|
||||||
"$project_root/config/dot/quickshell/modules/quicksettings/QuickSettings.qml" \
|
"$quicksettings_path/QuickSettings.qml" \
|
||||||
|| fail 'Control Center is not tightly attached to the bar'
|
|| fail 'Control Center is not tightly attached to the bar'
|
||||||
rg -Fq 'implicitWidth: Theme.controlCenterWidth' \
|
rg -Fq 'implicitWidth: Theme.controlCenterWidth' \
|
||||||
"$project_root/config/dot/quickshell/modules/quicksettings/QuickSettings.qml" \
|
"$quicksettings_path/QuickSettings.qml" \
|
||||||
|| fail 'Control Center window does not use its geometry token'
|
|| fail 'Control Center window does not use its geometry token'
|
||||||
rg -Fq 'HomeControls' \
|
rg -Fq 'HomeControls' \
|
||||||
"$project_root/config/dot/quickshell/modules/quicksettings/QuickSettingsPanel.qml" \
|
"$quicksettings_path/QuickSettingsPanel.qml" \
|
||||||
|| fail 'Home controls are not mounted'
|
|| fail 'Home controls are not mounted'
|
||||||
rg -Fq 'PhoneControls' \
|
rg -Fq 'PhoneControls' \
|
||||||
"$project_root/config/dot/quickshell/modules/quicksettings/QuickSettingsPanel.qml" \
|
"$quicksettings_path/QuickSettingsPanel.qml" \
|
||||||
|| fail 'Phone controls are not mounted'
|
|| fail 'Phone controls are not mounted'
|
||||||
rg -Fq 'visible: KdeConnect.phoneReachable' \
|
rg -Fq 'visible: KdeConnect.phoneReachable' \
|
||||||
"$project_root/config/dot/quickshell/modules/bar/StatusCluster.qml" \
|
"$source_config_path/modules/bar/StatusCluster.qml" \
|
||||||
|| fail 'reachable phone state is absent from the bar'
|
|| fail 'reachable phone state is absent from the bar'
|
||||||
|
|
||||||
for component in ControlSectionHeader HomeControls HomeTile PhoneControls RecentExchange; do
|
for component in ControlSectionHeader HomeBrightnessSlider HomeControls HomeTile PhoneControls RecentExchange; do
|
||||||
[[ -f "$project_root/config/dot/quickshell/modules/quicksettings/$component.qml" ]] \
|
[[ -f "$quicksettings_path/$component.qml" ]] \
|
||||||
|| fail "$component is missing"
|
|| fail "$component is missing"
|
||||||
done
|
done
|
||||||
|
|
||||||
qs ipc call home-assistant fixture ready >/dev/null
|
[[ -f "$quicksettings_path/qmldir" ]] \
|
||||||
qs ipc call kdeconnect fixture reachable >/dev/null
|
|| fail 'quick-settings module manifest is missing'
|
||||||
qs ipc call quicksettings open >/dev/null
|
rg -Fq 'HomeBrightnessSlider 1.0 HomeBrightnessSlider.qml' \
|
||||||
|
"$quicksettings_path/qmldir" \
|
||||||
|
|| fail 'brightness slider is not registered in the quick-settings module'
|
||||||
|
|
||||||
|
[[ "$(rg -c '^[[:space:]]*columns: 2$' "$quicksettings_path/HomeControls.qml")" -ge 2 ]] \
|
||||||
|
|| fail 'resting and expanded Home shelves are not both two-column grids'
|
||||||
|
rg -Fq 'model: HomeAssistant.visibleEntities' "$quicksettings_path/HomeControls.qml" \
|
||||||
|
|| fail 'resting Home shelf does not use the first four selected lights'
|
||||||
|
rg -Fq 'model: HomeAssistant.selectedEntities' "$quicksettings_path/HomeControls.qml" \
|
||||||
|
|| fail 'expanded Home shelf does not use every selected light'
|
||||||
|
rg -Fq 'HomeAssistant.pendingFor(' "$quicksettings_path/HomeControls.qml" \
|
||||||
|
|| fail 'Home tiles do not receive per-light pending brightness'
|
||||||
|
rg -Fq 'HomeAssistant.setBrightness(' "$quicksettings_path/HomeControls.qml" \
|
||||||
|
|| fail 'Home brightness commits are not wired to the service'
|
||||||
|
rg -Fq 'ShellState.openSettings("home-phone")' "$quicksettings_path/HomeControls.qml" \
|
||||||
|
|| fail 'Manage in Settings does not open Home & Phone'
|
||||||
|
|
||||||
|
rg -Fq 'signal previewChanged(int value)' "$quicksettings_path/HomeBrightnessSlider.qml" \
|
||||||
|
|| fail 'brightness slider has no preview contract'
|
||||||
|
rg -Fq 'signal committed(int value)' "$quicksettings_path/HomeBrightnessSlider.qml" \
|
||||||
|
|| fail 'brightness slider has no release-commit contract'
|
||||||
|
rg -Fq 'onReleased:' "$quicksettings_path/HomeBrightnessSlider.qml" \
|
||||||
|
|| fail 'brightness slider does not commit on pointer release'
|
||||||
|
rg -Fq 'onWheel:' "$quicksettings_path/HomeBrightnessSlider.qml" \
|
||||||
|
|| fail 'brightness slider has no wheel commit path'
|
||||||
|
rg -Fq 'onCommitted: value => root.brightnessRequested(value)' "$quicksettings_path/HomeTile.qml" \
|
||||||
|
|| fail 'Home tile does not forward the slider release commit'
|
||||||
|
|
||||||
|
cp -a "$source_config_path" "$config_path"
|
||||||
|
: >"$helper_log"
|
||||||
|
cat >"$config_path/scripts/panama-home-assistant" <<'EOF'
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
printf '%s\n' "$*" >>"$PANAMA_HOME_HELPER_LOG"
|
||||||
|
case "${1:-}" in
|
||||||
|
catalog)
|
||||||
|
printf '%s\n' '{"ok":false,"configured":false,"entities":[],"legacyEntityIds":[],"error":"test-helper"}'
|
||||||
|
;;
|
||||||
|
toggle|brightness)
|
||||||
|
printf '%s\n' '{"ok":false,"error":"contract-action-forbidden"}'
|
||||||
|
exit 73
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
EOF
|
||||||
|
chmod +x "$config_path/scripts/panama-home-assistant"
|
||||||
|
|
||||||
|
start_test_shell
|
||||||
|
|
||||||
|
qs_for_test ipc call home-assistant fixture ready >/dev/null
|
||||||
|
home_ready="$(qs_for_test ipc call home-assistant status)"
|
||||||
|
jq -e '.fixture == true and .visibleCount == 4 and .configuredCount == 7' \
|
||||||
|
<<<"$home_ready" >/dev/null \
|
||||||
|
|| fail 'Home fixture does not expose four resting and seven expanded accessories'
|
||||||
|
|
||||||
|
qs_for_test ipc call kdeconnect fixture reachable >/dev/null
|
||||||
|
qs_for_test ipc call quicksettings open >/dev/null
|
||||||
|
|
||||||
hyprctl layers | rg -q 'namespace: qs-popover-quicksettings' \
|
hyprctl layers | rg -q 'namespace: qs-popover-quicksettings' \
|
||||||
|| fail 'Control Center layer did not map'
|
|| fail 'Control Center layer did not map'
|
||||||
jq -e '.open == true and .expandedSection == ""' \
|
jq -e '.open == true and .expandedSection == ""' \
|
||||||
<<<"$(qs ipc call quicksettings status)" >/dev/null \
|
<<<"$(qs_for_test ipc call quicksettings status)" >/dev/null \
|
||||||
|| fail 'Control Center did not open in its resting state'
|
|| fail 'Control Center did not open in its resting state'
|
||||||
|
|
||||||
qs ipc call quicksettings section home >/dev/null
|
qs_for_test ipc call quicksettings section home >/dev/null
|
||||||
jq -e '.open == true and .expandedSection == "home"' \
|
jq -e '.open == true and .expandedSection == "home"' \
|
||||||
<<<"$(qs ipc call quicksettings status)" >/dev/null \
|
<<<"$(qs_for_test ipc call quicksettings status)" >/dev/null \
|
||||||
|| fail 'Home section did not expand'
|
|| fail 'Home section did not expand'
|
||||||
|
|
||||||
qs ipc call quicksettings section phone >/dev/null
|
qs_for_test ipc call quicksettings section phone >/dev/null
|
||||||
jq -e '.open == true and .expandedSection == "phone"' \
|
jq -e '.open == true and .expandedSection == "phone"' \
|
||||||
<<<"$(qs ipc call quicksettings status)" >/dev/null \
|
<<<"$(qs_for_test ipc call quicksettings status)" >/dev/null \
|
||||||
|| fail 'Phone did not replace Home as the expanded section'
|
|| fail 'Phone did not replace Home as the expanded section'
|
||||||
|
|
||||||
qs ipc call quicksettings section phone >/dev/null
|
qs_for_test ipc call quicksettings section phone >/dev/null
|
||||||
jq -e '.open == true and .expandedSection == ""' \
|
jq -e '.open == true and .expandedSection == ""' \
|
||||||
<<<"$(qs ipc call quicksettings status)" >/dev/null \
|
<<<"$(qs_for_test ipc call quicksettings status)" >/dev/null \
|
||||||
|| fail 'expanded Phone section did not collapse'
|
|| fail 'expanded Phone section did not collapse'
|
||||||
|
|
||||||
|
if rg '^(toggle|brightness)( |$)' "$helper_log" >&2; then
|
||||||
|
fail 'Control Center contract attempted a real Home action path'
|
||||||
|
fi
|
||||||
|
|
||||||
trap - EXIT
|
trap - EXIT
|
||||||
cleanup
|
cleanup
|
||||||
|
[[ ! -e "$state_home" ]] \
|
||||||
|
|| fail 'temporary Control Center state was not removed after shell exit'
|
||||||
printf 'Control Center contract: PASS\n'
|
printf 'Control Center contract: PASS\n'
|
||||||
|
|||||||
Reference in New Issue
Block a user