Settings Feature Enhancement

Nucleic-Session: 89BFAB13-D559-43FC-BE28-0B2639BECF0F
Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
2026-07-05 22:05:31 -07:00
co-authored by Nucleic
parent cb8c1f68d1
commit 20afa55a7b
@@ -370,3 +370,118 @@ struct ManualPairingView: View {
dismiss()
}
}
/// "Add a device to this mesh": the phone can't mint a join code itself (it runs no listener), so
/// it asks a connected Mac to open a pairing window and relays back that Mac's code — shown here as
/// a QR to scan from a new iPhone/iPad, or a copyable link to send to a new Mac. Scanning/pasting it
/// joins the new device to the whole group. Dismissing the sheet closes the Mac's pairing window
/// (`RemoteStore.cancelPairingCode`, wired to the sheet's `onDismiss`).
struct AddDeviceView: View {
@EnvironmentObject var store: RemoteStore
@Environment(\.dismiss) private var dismiss
@State private var copied = false
@State private var copyResetTask: Task<Void, Never>?
var body: some View {
NavigationStack {
Form {
switch store.addDevice {
case .idle, .requesting:
Section {
HStack {
ProgressView()
Text("Asking a Mac in your mesh for a join code…")
.foregroundStyle(.secondary)
}
}
case .unavailable:
Section {
Label("Couldn't get a join code", systemImage: "exclamationmark.triangle.fill")
.foregroundStyle(.orange)
Text("This needs a Mac in your mesh that's connected right now. Make sure "
+ "one is reachable with its meshing service on, then try again.")
.font(.callout).foregroundStyle(.secondary)
Button("Try again") { store.requestPairingCode() }
}
case .ready(let code):
codeSection(code)
}
}
.navigationTitle("Add a device")
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .confirmationAction) { Button("Done") { dismiss() } }
}
}
// Fire the request as the sheet appears; `onDismiss` (on the presenter) tells the Mac to
// close its pairing window.
.onAppear { if case .ready = store.addDevice {} else { store.requestPairingCode() } }
.onDisappear { copyResetTask?.cancel() }
}
@ViewBuilder
private func codeSection(_ code: String) -> some View {
Section {
if let image = Self.qrImage(code) {
HStack {
Spacer()
Image(uiImage: image)
.interpolation(.none)
.resizable()
.frame(width: 220, height: 220)
.padding(10)
.background(.white, in: RoundedRectangle(cornerRadius: 12))
Spacer()
}
.listRowBackground(Color.clear)
}
} header: {
Text("Scan to join")
} footer: {
Text("On a new iPhone or iPad, open Nucleic Remote ▸ Join an Orbital mesh and scan this. "
+ "It joins the whole group — every Mac and device here.")
}
Section {
Text(code)
.font(.system(.caption, design: .monospaced))
.lineLimit(2)
.truncationMode(.middle)
.textSelection(.enabled)
Button {
copyCode(code)
} label: {
Label(copied ? "Copied" : "Copy join code",
systemImage: copied ? "checkmark" : "doc.on.doc")
}
} header: {
Text("Or send a code")
} footer: {
Text("Paste into “Join mesh…” on another Mac, or “Enter code manually” on an iPhone or "
+ "iPad. A Mac joining is confirmed on the Mac that shared the code.")
}
}
private func copyCode(_ code: String) {
UIPasteboard.general.string = code
withAnimation(.easeOut(duration: 0.12)) { copied = true }
copyResetTask?.cancel()
copyResetTask = Task {
try? await Task.sleep(for: .seconds(1.4))
guard !Task.isCancelled else { return }
withAnimation(.easeIn(duration: 0.2)) { copied = false }
}
}
/// Render the join string as a QR code (CoreImage), mirroring the Mac's pairing sheet.
private static func qrImage(_ string: String) -> UIImage? {
let filter = CIFilter.qrCodeGenerator()
filter.message = Data(string.utf8)
filter.correctionLevel = "M"
guard let output = filter.outputImage else { return nil }
let scaled = output.transformed(by: CGAffineTransform(scaleX: 10, y: 10))
let context = CIContext()
guard let cg = context.createCGImage(scaled, from: scaled.extent) else { return nil }
return UIImage(cgImage: cg)
}
}