- Add ServerConfigView stub (referenced in SilverAppleApp.swift) - Add HardeningListView stub (referenced in DashboardView.swift) - Fix .accentColor → Color.accentColor (ShapeStyle removed member) - Fix withCheckedThrowingContinuation explicit type annotation - Make AppEnvironment.serverUrl internal for ServerConfigView access Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
116 lines
4.3 KiB
Swift
116 lines
4.3 KiB
Swift
import SwiftUI
|
|
import SafariServices
|
|
|
|
struct MDMEnrollmentView: View {
|
|
var onComplete: () -> Void
|
|
@EnvironmentObject var env: AppEnvironment
|
|
@State private var isLoading = false
|
|
@State private var showSafari = false
|
|
@State private var enrollUrl: URL?
|
|
@State private var error: String?
|
|
@State private var didEnroll = false
|
|
|
|
var body: some View {
|
|
ScrollView {
|
|
VStack(spacing: 32) {
|
|
Image(systemName: "iphone.badge.play")
|
|
.font(.system(size: 56))
|
|
.foregroundStyle(Color.accentColor)
|
|
|
|
VStack(spacing: 12) {
|
|
Text("Device Management")
|
|
.font(.title2.weight(.semibold))
|
|
|
|
Text("SilverApple uses Apple MDM to automatically configure your device — installing VPN, DNS, and account profiles without manual setup.")
|
|
.font(.subheadline)
|
|
.foregroundStyle(.secondary)
|
|
.multilineTextAlignment(.center)
|
|
|
|
Text("You remain in control: you can remove MDM enrollment at any time in Settings → General → VPN & Device Management.")
|
|
.font(.caption)
|
|
.foregroundStyle(.tertiary)
|
|
.multilineTextAlignment(.center)
|
|
.padding(.top, 4)
|
|
}
|
|
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
FeatureRow(icon: "lock.shield", text: "Privacy hardening profiles")
|
|
FeatureRow(icon: "network", text: "SilverVPN configuration")
|
|
FeatureRow(icon: "globe", text: "Encrypted DNS via Technitium")
|
|
FeatureRow(icon: "envelope", text: "Mailcow email account")
|
|
FeatureRow(icon: "calendar", text: "CalDAV calendar sync")
|
|
FeatureRow(icon: "person.crop.rectangle", text: "CardDAV contacts sync")
|
|
}
|
|
.padding()
|
|
.background(.secondary.opacity(0.08), in: RoundedRectangle(cornerRadius: 12))
|
|
|
|
if let error {
|
|
Text(error).font(.caption).foregroundStyle(.red).multilineTextAlignment(.center)
|
|
}
|
|
|
|
VStack(spacing: 12) {
|
|
Button {
|
|
Task { await startEnrollment() }
|
|
} label: {
|
|
Group {
|
|
if isLoading { ProgressView() }
|
|
else { Label("Enroll this Device", systemImage: "checkmark.shield.fill") }
|
|
}
|
|
.frame(maxWidth: .infinity).padding()
|
|
}
|
|
.buttonStyle(.borderedProminent)
|
|
.disabled(isLoading || didEnroll)
|
|
|
|
if didEnroll {
|
|
Button("Continue →", action: onComplete)
|
|
.buttonStyle(.bordered)
|
|
}
|
|
|
|
Button("Skip for now") { onComplete() }
|
|
.foregroundStyle(.secondary)
|
|
.font(.caption)
|
|
}
|
|
}
|
|
.padding(24)
|
|
}
|
|
.sheet(isPresented: $showSafari) {
|
|
if let url = enrollUrl {
|
|
SafariView(url: url)
|
|
.ignoresSafeArea()
|
|
.onDisappear { didEnroll = true }
|
|
}
|
|
}
|
|
}
|
|
|
|
private func startEnrollment() async {
|
|
isLoading = true; error = nil
|
|
do {
|
|
let response = try await env.apiClient.getEnrollmentToken()
|
|
enrollUrl = URL(string: response.enrollmentUrl)
|
|
showSafari = true
|
|
} catch {
|
|
self.error = error.localizedDescription
|
|
}
|
|
isLoading = false
|
|
}
|
|
}
|
|
|
|
struct FeatureRow: View {
|
|
let icon: String
|
|
let text: String
|
|
var body: some View {
|
|
Label(text, systemImage: icon)
|
|
.font(.subheadline)
|
|
.foregroundStyle(.primary)
|
|
}
|
|
}
|
|
|
|
/// UIViewControllerRepresentable wrapper for SFSafariViewController
|
|
struct SafariView: UIViewControllerRepresentable {
|
|
let url: URL
|
|
func makeUIViewController(context: Context) -> SFSafariViewController {
|
|
SFSafariViewController(url: url)
|
|
}
|
|
func updateUIViewController(_ vc: SFSafariViewController, context: Context) {}
|
|
}
|