Private
Public Access
1
0
Files
SilverApple/SilverApple/Onboarding/Views/MDMEnrollmentView.swift
SilverLABS 531a534c44 feat(ios): initial SilverApple Swift project scaffold
- App entry point, AppEnvironment dependency container
- PassportAuthService: PKCE OAuth 2.0 against SilverSHELL Passport
- TokenStore: Keychain-backed token storage
- SilverAPIClient: MDM enrollment + privacy score API calls
- MDMEnrollmentService: enrollment URL fetching + device listing
- Onboarding coordinator with MDM, account setup, and hardening steps
- Dashboard with privacy score ring and category breakdown
- WelcomeView with Passport sign-in flow

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 03:28:31 +01:00

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(.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) {}
}