// // OnboardingView.swift // Severed // import SwiftUI /// Two-step onboarding: a welcome screen, then the Screen Time permission /// request. Onboarding only completes once authorization is approved — the /// app cannot block anything without it. struct OnboardingView: View { @Environment(ScreenTimeAuthorization.self) private var authorization @Environment(\.openURL) private var openURL let onComplete: () -> Void private enum Step { case welcome case permission } @State private var step = Step.welcome @State private var isRequesting = false var body: some View { VStack(spacing: 0) { Spacer() switch step { case .welcome: welcome case .permission: permission } Spacer() footer } .padding(.horizontal, 28) .padding(.bottom, 24) .frame(maxWidth: .infinity, maxHeight: .infinity) .foregroundStyle(.white) .background(Theme.background) } private var welcome: some View { VStack(spacing: 18) { Image(systemName: "scissors") .font(.system(size: 56, weight: .semibold)) .foregroundStyle(Theme.accent) Text("Severed") .font(.system(size: 38, weight: .bold)) Text("Block your most distracting apps with rules that keep you honest — on a schedule, with no way out when you choose Hard Mode.") .font(.system(size: 16)) .foregroundStyle(Theme.textSecondary) .multilineTextAlignment(.center) } } private var permission: some View { VStack(spacing: 24) { Image(systemName: "hourglass") .font(.system(size: 48, weight: .semibold)) .foregroundStyle(Theme.accent) Text("Allow Screen Time Access") .font(.system(size: 28, weight: .bold)) .multilineTextAlignment(.center) VStack(alignment: .leading, spacing: 14) { bullet("shield.fill", "Severed uses Apple's Screen Time framework to block the apps you choose.") bullet("hand.raised.fill", "Your app activity stays on this device and is never collected.") bullet("gearshape.fill", "You can change this anytime in Settings.") } if authorization.status == .denied || authorization.lastRequestFailed { VStack(spacing: 10) { Text("Screen Time access was declined. Severed can't block apps without it.") .font(.system(size: 13)) .foregroundStyle(Theme.destructive) .multilineTextAlignment(.center) .accessibilityIdentifier("permissionDeniedLabel") Button("Open Settings") { if let url = URL(string: UIApplication.openSettingsURLString) { openURL(url) } } .font(.system(size: 15, weight: .semibold)) .foregroundStyle(Theme.accent) .accessibilityIdentifier("openSettingsButton") } } } } private func bullet(_ systemImage: String, _ text: String) -> some View { HStack(alignment: .top, spacing: 12) { Image(systemName: systemImage) .font(.system(size: 15, weight: .semibold)) .foregroundStyle(Theme.accent) .frame(width: 22) Text(text) .font(.system(size: 15)) .foregroundStyle(Theme.textSecondary) } } @ViewBuilder private var footer: some View { switch step { case .welcome: pillButton("Continue", identifier: "onboardingContinueButton") { step = .permission } case .permission: pillButton( isRequesting ? "Requesting…" : "Allow Screen Time Access", identifier: "allowScreenTimeButton" ) { guard !isRequesting else { return } isRequesting = true Task { await authorization.request() isRequesting = false if authorization.status == .approved { onComplete() } } } } } private func pillButton( _ title: String, identifier: String, action: @escaping () -> Void ) -> some View { Button(action: action) { Text(title) .font(.system(size: 17, weight: .semibold)) .foregroundStyle(.black) .frame(maxWidth: .infinity) .frame(height: 54) .background(.white, in: RoundedRectangle(cornerRadius: 27)) } .buttonStyle(.plain) .accessibilityIdentifier(identifier) } }