Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions Magic Switch/Model/Store/BluetoothPeripheralStore.swift
Original file line number Diff line number Diff line change
Expand Up @@ -398,6 +398,11 @@ final class BluetoothPeripheralStore: NSObject, ObservableObject, BluetoothPerip

var newPeripheral = peripheral
peripherals.append(newPeripheral)
// Resolve the new row's live connection state (and register its disconnect
// observer) right away. Without this the row reads `.disconnected` until
// the next snapshot — i.e. the user has to leave and re-open the tab to
// see an already-connected peripheral show as connected.
fetchConnectedPeripherals()
}

/// Removes peripheral information from the system while maintaining it in the list
Expand Down
72 changes: 43 additions & 29 deletions Magic Switch/View/Settings/BluetoothPeripheralSettingsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -137,17 +137,20 @@ private struct PeripheralListView: View {
var secondaryAction: ((BluetoothPeripheral) -> Void)?

var body: some View {
List {
ForEach(peripherals) { peripheral in
PeripheralRowView(
peripheral: peripheral,
showConnectionStatus: showConnectionStatus,
primaryAction: { primaryAction(peripheral) },
secondaryAction: secondaryAction.map { action in
{ action(peripheral) }
}
)
}
// Rows go straight into the enclosing Form Section — a nested List here
// gives each row a taller default height than its content, which
// top-aligns short rows (e.g. the icon-only Available row) instead of
// centering them. Letting the Form own the row layout keeps content
// vertically centered.
ForEach(peripherals) { peripheral in
PeripheralRowView(
peripheral: peripheral,
showConnectionStatus: showConnectionStatus,
primaryAction: { primaryAction(peripheral) },
secondaryAction: secondaryAction.map { action in
{ action(peripheral) }
}
)
}
}
}
Expand All @@ -166,24 +169,35 @@ private struct PeripheralRowView: View {
}

var body: some View {
HStack {
Text(peripheral.name)
Spacer()
if showConnectionStatus {
connectionButton
Button(action: { secondaryAction?() }) {
Image(systemName: "minus.circle")
.foregroundColor(.red)
}
.help("Remove this peripheral from Magic Switch's list.")
.accessibilityLabel("Remove \(peripheral.name) from list")
} else {
Button(action: primaryAction) {
Image(systemName: "plus.circle")
.foregroundColor(.blue)
VStack(alignment: .leading, spacing: 4) {
HStack {
Text(peripheral.name)
Spacer()
if showConnectionStatus {
connectionButton
Button(action: { secondaryAction?() }) {
Image(systemName: "minus.circle")
.foregroundColor(.red)
}
.help("Remove this peripheral from Magic Switch's list.")
.accessibilityLabel("Remove \(peripheral.name) from list")
} else {
Button(action: primaryAction) {
Image(systemName: "plus.circle")
.foregroundColor(.blue)
}
.help("Add this peripheral to Magic Switch's list.")
.accessibilityLabel("Add \(peripheral.name) to list")
}
.help("Add this peripheral to Magic Switch's list.")
.accessibilityLabel("Add \(peripheral.name) to list")
}

// Mirrors the dropdown's inline failure line. The store auto-fades the
// message after 5s (and clears it on a fresh attempt), so this appears
// and disappears on its own — no extra state to manage here.
if let error = store.peripheralOperationError[peripheral.id] {
Text(error)
.font(.caption)
.foregroundColor(.red)
}
}
}
Expand All @@ -192,7 +206,7 @@ private struct PeripheralRowView: View {
private var connectionButton: some View {
switch connectionState {
case .connected:
Button("Disconnect", action: primaryAction)
Button("Release", action: primaryAction)
.help(
"Release this peripheral from this Mac. If a peer Mac is paired, it'll take ownership automatically."
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,11 @@ private struct NetworkDeviceListView: View {
}

var body: some View {
List(devices) { device in
// Rows go straight into the enclosing Form Section — a nested List here
// gives each row a taller default height than its content, which
// top-aligns the row contents instead of centering them in the pill.
// Letting the Form own the row layout keeps content vertically centered.
ForEach(devices) { device in
VStack(alignment: .leading, spacing: 6) {
HStack {
Text(device.name)
Expand Down