Settings Feature Enhancement
Nucleic-Session: 89BFAB13-D559-43FC-BE28-0B2639BECF0F Co-authored-by: Nucleic <[email protected]>
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user