Add camera QR pairing, Bonjour discovery and pinned address recovery
This commit is contained in:
@@ -11,7 +11,7 @@ struct ContentView: View {
|
||||
Label(store.status, systemImage: store.connected ? "wifi" : "wifi.slash")
|
||||
.font(.subheadline).foregroundStyle(.secondary)
|
||||
if store.connection == nil {
|
||||
PairingView(store: store)
|
||||
PairingView(store: store, discovery: store.discovery)
|
||||
} else {
|
||||
PlayerView(store: store)
|
||||
Text(store.connection?.endpoint.host ?? "")
|
||||
@@ -40,10 +40,12 @@ struct ContentView: View {
|
||||
|
||||
private struct PairingView: View {
|
||||
@ObservedObject var store: PlayerStore
|
||||
@ObservedObject var discovery: DiscoveryStore
|
||||
@State private var endpoint = "https://"
|
||||
@State private var fingerprint = ""
|
||||
@State private var code = ""
|
||||
@State private var invitation = ""
|
||||
@State private var showScanner = false
|
||||
|
||||
var body: some View {
|
||||
VStack(alignment: .leading, spacing: 18) {
|
||||
@@ -53,6 +55,36 @@ private struct PairingView: View {
|
||||
Text("Музыка на ПК. Управление здесь.").font(.title2.bold())
|
||||
Text("Откройте сопряжение в MusicBridge на Windows. Подключите iPhone к той же домашней сети.")
|
||||
.foregroundStyle(.secondary)
|
||||
Button { showScanner = true } label: {
|
||||
Label("Сканировать QR с ПК", systemImage: "qrcode.viewfinder")
|
||||
.frame(maxWidth: .infinity)
|
||||
}.buttonStyle(.borderedProminent).controlSize(.large).disabled(store.pairing)
|
||||
Text("QR заполнит адрес, отпечаток и код. Затем нажмите «Подключить компьютер».")
|
||||
.font(.caption).foregroundStyle(.secondary)
|
||||
DisclosureGroup("Найти ПК в сети") {
|
||||
VStack(alignment: .leading, spacing: 12) {
|
||||
Text(discovery.message).font(.footnote).foregroundStyle(.secondary)
|
||||
ForEach(discovery.computers) { computer in
|
||||
Button {
|
||||
endpoint = computer.endpoint
|
||||
fingerprint = computer.fingerprint
|
||||
code = ""
|
||||
store.error = nil
|
||||
} label: {
|
||||
VStack(alignment: .leading) {
|
||||
Label(computer.name, systemImage: "desktopcomputer")
|
||||
Text(computer.endpoint).font(.caption)
|
||||
}
|
||||
}
|
||||
}
|
||||
Text("Выберите ПК, сверьте весь отпечаток с его экраном и введите свежий код сопряжения.")
|
||||
.font(.caption).foregroundStyle(.secondary)
|
||||
Button("Повторить поиск") { discovery.restart() }
|
||||
Button("Настройки доступа к сети") {
|
||||
if let url = URL(string: UIApplication.openSettingsURLString) { UIApplication.shared.open(url) }
|
||||
}
|
||||
}.padding(.top, 8)
|
||||
}.disabled(store.pairing)
|
||||
Group {
|
||||
TextField("Адрес ПК: https://192.168.1.10:8765", text: $endpoint).keyboardType(.URL)
|
||||
TextField("SHA-256 сертификата с экрана ПК", text: $fingerprint)
|
||||
@@ -74,17 +106,30 @@ private struct PairingView: View {
|
||||
TextField("musicbridge://pair?data=…", text: $invitation, axis: .vertical)
|
||||
.lineLimit(3...5).textInputAutocapitalization(.never).autocorrectionDisabled()
|
||||
Button("Заполнить из приглашения") {
|
||||
do {
|
||||
let parsed = try Invitation.parse(invitation.trimmingCharacters(in: .whitespacesAndNewlines))
|
||||
endpoint = parsed.endpoint
|
||||
fingerprint = parsed.certificateSha256
|
||||
code = parsed.code
|
||||
store.error = nil
|
||||
} catch { store.error = error.localizedDescription }
|
||||
importText(invitation)
|
||||
}
|
||||
}.padding(.top, 12)
|
||||
}.disabled(store.pairing)
|
||||
}
|
||||
.sheet(isPresented: $showScanner) { QRScannerSheet(receive: importText) }
|
||||
.onAppear { consumeIncoming() }
|
||||
.onChange(of: store.incomingInvitation) { _, _ in consumeIncoming() }
|
||||
}
|
||||
|
||||
private func consumeIncoming() {
|
||||
guard let text = store.incomingInvitation else { return }
|
||||
store.incomingInvitation = nil
|
||||
importText(text)
|
||||
}
|
||||
|
||||
private func importText(_ text: String) {
|
||||
do {
|
||||
let parsed = try Invitation.parse(text.trimmingCharacters(in: .whitespacesAndNewlines))
|
||||
endpoint = parsed.endpoint
|
||||
fingerprint = parsed.certificateSha256
|
||||
code = parsed.code
|
||||
store.error = nil
|
||||
} catch { store.error = error.localizedDescription }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user