Skip to content

Commit 5189187

Browse files
nickustinovclaude
andcommitted
Fix device groups UI and blinds position tracking
- Groups settings: match padding and section header style with other tabs - Groups settings: add drag handles and drag-to-reorder functionality - GroupMenuItem: start toggles OFF and sliders at 0 (like individual devices) - GroupMenuItem: properly track and sync blind positions across group - GroupMenuItem: use blue slider color for blinds groups Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 6c71bfb commit 5189187

2 files changed

Lines changed: 261 additions & 126 deletions

File tree

macOSBridge/MenuItems/GroupMenuItem.swift

Lines changed: 49 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,12 @@ class GroupMenuItem: NSMenuItem, CharacteristicUpdatable, LocalChangeNotifiable
2727
private var deviceStates: [UUID: Bool] = [:]
2828
private var characteristicToService: [UUID: String] = [:] // characteristicId -> serviceId
2929

30+
// For blinds: track positions separately
31+
private var positionStates: [UUID: Int] = [:] // currentPositionId -> position
32+
private var targetPositionIds: [UUID] = [] // targetPositionIds for writing
33+
3034
var characteristicIdentifiers: [UUID] {
31-
return Array(deviceStates.keys)
35+
return Array(deviceStates.keys) + Array(positionStates.keys)
3236
}
3337

3438
init(group: DeviceGroup, menuData: MenuData, bridge: Mac2iOS?) {
@@ -84,14 +88,23 @@ class GroupMenuItem: NSMenuItem, CharacteristicUpdatable, LocalChangeNotifiable
8488
private func initializeDeviceStates() {
8589
let services = group.resolveServices(in: menuData)
8690
for service in services {
87-
// For power-based devices (lights, switches, etc.)
88-
if let idString = service.powerStateId, let id = UUID(uuidString: idString) {
89-
let isOn = bridge?.getCharacteristicValue(identifier: id) as? Bool ?? false
90-
deviceStates[id] = isOn
91+
// For blinds: track position (start at 0, will update from characteristic updates)
92+
if service.serviceType == ServiceTypes.windowCovering {
93+
if let currentIdString = service.currentPositionId,
94+
let currentId = UUID(uuidString: currentIdString) {
95+
positionStates[currentId] = 0
96+
}
97+
if let targetIdString = service.targetPositionId,
98+
let targetId = UUID(uuidString: targetIdString) {
99+
targetPositionIds.append(targetId)
100+
}
101+
}
102+
// For power-based devices (lights, switches, etc.) - start OFF, will update from characteristic updates
103+
else if let idString = service.powerStateId, let id = UUID(uuidString: idString) {
104+
deviceStates[id] = false
91105
characteristicToService[id] = service.uniqueIdentifier
92106
} else if let idString = service.activeId, let id = UUID(uuidString: idString) {
93-
let value = bridge?.getCharacteristicValue(identifier: id) as? Int ?? 0
94-
deviceStates[id] = value != 0
107+
deviceStates[id] = false
95108
characteristicToService[id] = service.uniqueIdentifier
96109
}
97110
}
@@ -107,7 +120,8 @@ class GroupMenuItem: NSMenuItem, CharacteristicUpdatable, LocalChangeNotifiable
107120

108121
let slider = ModernSlider(minValue: 0, maxValue: 100)
109122
slider.frame = NSRect(x: sliderX, y: sliderY, width: sliderWidth, height: 12)
110-
slider.doubleValue = 0
123+
slider.doubleValue = 0 // Start at 0, will update from characteristic updates
124+
slider.progressTintColor = DS.Colors.sliderBlind
111125
slider.isContinuous = false
112126
slider.target = self
113127
slider.action = #selector(sliderChanged(_:))
@@ -125,6 +139,7 @@ class GroupMenuItem: NSMenuItem, CharacteristicUpdatable, LocalChangeNotifiable
125139

126140
let toggle = ToggleSwitch()
127141
toggle.frame = NSRect(x: switchX, y: switchY, width: DS.ControlSize.switchWidth, height: DS.ControlSize.switchHeight)
142+
toggle.setOn(false, animated: false) // Start OFF, will update from bridge values
128143
toggle.target = self
129144
toggle.action = #selector(toggleChanged(_:))
130145
containerView.addSubview(toggle)
@@ -138,7 +153,19 @@ class GroupMenuItem: NSMenuItem, CharacteristicUpdatable, LocalChangeNotifiable
138153

139154
// Called when characteristic values change
140155
func updateValue(for characteristicId: UUID, value: Any, isLocalChange: Bool) {
141-
// Update our local state if this characteristic belongs to us
156+
// Update position state for blinds
157+
if positionStates.keys.contains(characteristicId) {
158+
if let pos = ValueConversion.toInt(value) {
159+
positionStates[characteristicId] = pos
160+
// Update slider with average position
161+
let avgPosition = positionStates.values.reduce(0, +) / positionStates.count
162+
positionSlider?.doubleValue = Double(avgPosition)
163+
updateBlindsIcon(position: avgPosition)
164+
}
165+
return
166+
}
167+
168+
// Update power state for other devices
142169
if deviceStates.keys.contains(characteristicId) {
143170
if let boolValue = ValueConversion.toBool(value) {
144171
deviceStates[characteristicId] = boolValue
@@ -208,16 +235,25 @@ class GroupMenuItem: NSMenuItem, CharacteristicUpdatable, LocalChangeNotifiable
208235
let services = group.resolveServices(in: menuData)
209236
guard let bridge = bridge else { return }
210237

238+
// Update local position states and notify
239+
for (currentId, _) in positionStates {
240+
positionStates[currentId] = position
241+
notifyLocalChange(characteristicId: currentId, value: position)
242+
}
243+
244+
// Write to all target positions
211245
for service in services {
212246
if let idString = service.targetPositionId, let id = UUID(uuidString: idString) {
213247
bridge.writeCharacteristic(identifier: id, value: position)
214-
notifyLocalChange(characteristicId: id, value: position)
215248
}
216249
}
217250

218-
// Update icon
219-
let isOpen = position > 50
251+
updateBlindsIcon(position: position)
252+
}
253+
254+
private func updateBlindsIcon(position: Int) {
255+
let isOpen = position > 0
220256
iconView.image = NSImage(systemSymbolName: isOpen ? "blinds.horizontal.open" : "blinds.horizontal.closed", accessibilityDescription: nil)
221-
iconView.contentTintColor = isOpen ? DS.Colors.success : DS.Colors.mutedForeground
257+
iconView.contentTintColor = isOpen ? DS.Colors.info : DS.Colors.mutedForeground
222258
}
223259
}

0 commit comments

Comments
 (0)