stremio/apps/ios/VMC/UI/LinkScreen.swift
Jos Vooges | STH 303383ea4e Add server URL choice, email login, and login lock controls.
Clients can use VMC or a custom server with email/password or device code; API locks after 3 failed logins with admin unlock.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-10 01:32:35 +02:00

121 lines
4.5 KiB
Swift

import SwiftUI
struct LinkScreen: View {
@EnvironmentObject private var app: AppModel
@State private var code: String?
@State private var linkUrl = ""
@State private var status = "Verbinden…"
@State private var error: String?
var body: some View {
ZStack {
LinearGradient(
colors: [
Color(red: 0.12, green: 0.04, blue: 0.06),
McColors.bg,
Color(red: 0.04, green: 0.05, blue: 0.1),
],
startPoint: .topLeading,
endPoint: .bottomTrailing
)
.ignoresSafeArea()
VStack(spacing: 18) {
Text("VMC")
.font(McFonts.brand)
.foregroundStyle(McColors.accent)
.shadow(color: McColors.accent.opacity(0.4), radius: 18)
Text("Koppel dit apparaat")
.font(McFonts.title)
.foregroundStyle(McColors.text)
Text("Open je Vonas-account, voer deze code in en bevestig.")
.font(McFonts.body)
.foregroundStyle(McColors.textDim)
.multilineTextAlignment(.center)
.padding(.horizontal, 28)
if let code {
Text(code)
.font(.system(size: 42, weight: .bold, design: .monospaced))
.foregroundStyle(McColors.text)
.padding(.vertical, 10)
.padding(.horizontal, 22)
.background(McColors.surface.opacity(0.9), in: RoundedRectangle(cornerRadius: 16, style: .continuous))
.overlay(
RoundedRectangle(cornerRadius: 16, style: .continuous)
.stroke(McColors.accent.opacity(0.35), lineWidth: 1)
)
} else if error == nil {
ProgressView().tint(.white)
}
if !linkUrl.isEmpty {
Text(linkUrl)
.font(McFonts.caption)
.foregroundStyle(McColors.textDim)
}
Text(status)
.font(McFonts.caption)
.foregroundStyle(McColors.textDim)
if let error {
Text(error)
.font(McFonts.body)
.foregroundStyle(McColors.danger)
.multilineTextAlignment(.center)
.padding(.horizontal)
Button("Opnieuw") { Task { await startLink() } }
.buttonStyle(McPrimaryButtonStyle())
}
Button("Change server") { app.phase = .server }
.buttonStyle(McSecondaryButtonStyle())
Button("Back") { app.phase = .auth }
.buttonStyle(McSecondaryButtonStyle())
}
.padding()
.frame(maxWidth: 420)
}
.task { await startLink() }
}
private func startLink() async {
error = nil
code = nil
status = "Verbinden…"
await app.api.configure(baseUrl: app.store.apiBase, token: nil)
do {
let created = try await app.api.createDeviceCode(deviceName: AppModel.deviceName)
code = created.code
linkUrl = created.linkUrl ?? ""
status = "Wachten tot je de code bevestigt…"
let interval = max(created.pollIntervalSeconds ?? 2, 2)
while true {
try await Task.sleep(nanoseconds: UInt64(interval) * 1_000_000_000)
switch try await app.api.pollDeviceCode(code: created.code) {
case .linked(let token, let name):
status = "Welkom, \(name)"
try await Task.sleep(nanoseconds: 700_000_000)
await app.didLink(token: token)
return
case .expired:
status = "Code verlopen"
error = "Code verlopen — probeer opnieuw"
return
case .alreadyLinked:
status = "Al gekoppeld"
error = "Al gekoppeld — herstart de app"
return
case .pending:
break
}
}
} catch {
self.error = error.localizedDescription
status = "Mislukt"
}
}
}