import SwiftUI import Photos struct PhotoPermissionView: View { @EnvironmentObject var store: ConnectionStore @State private var appeared = false @State private var isRequesting = false var body: some View { ZStack { AppTheme.background.ignoresSafeArea() VStack(spacing: 0) { // Step indicator stepIndicator .padding(.top, 20) .padding(.bottom, 36) .opacity(appeared ? 1 : 0) .animation(.spring(response: 0.4, dampingFraction: 0.8).delay(0.05), value: appeared) Spacer() // Icon ZStack { Circle() .fill(AppTheme.surfaceSunken) .frame(width: 96, height: 96) Image(systemName: "photo.stack") .font(.system(size: 38, weight: .light)) .foregroundStyle(AppTheme.ink) } .scaleEffect(appeared ? 1 : 0.85) .opacity(appeared ? 1 : 0) .animation(.spring(response: 0.5, dampingFraction: 0.7).delay(0.1), value: appeared) Spacer().frame(height: 32) // Title + description VStack(spacing: 10) { Text("Access your photos") .font(.system(size: 24, weight: .semibold)) .foregroundStyle(AppTheme.ink) Text("Kisani needs read access to your photo library to find and back up your photos and videos to the NAS.") .font(AppTheme.body()) .foregroundStyle(AppTheme.inkSecondary) .multilineTextAlignment(.center) .padding(.horizontal, 32) // Detail chips HStack(spacing: 8) { detailChip(icon: "eye.slash", label: "Read-only") detailChip(icon: "lock.shield", label: "Stays on-device") detailChip(icon: "icloud.slash", label: "No cloud upload") } .padding(.top, 8) } .opacity(appeared ? 1 : 0) .animation(.spring(response: 0.4, dampingFraction: 0.8).delay(0.18), value: appeared) Spacer() // Buttons VStack(spacing: 12) { Button(action: requestAccess) { Group { if isRequesting { ProgressView().tint(AppTheme.inkInverse) } else { Text("Allow Photo Access") } } } .buttonStyle(PrimaryButtonStyle()) .disabled(isRequesting) Button(action: skipAndContinue) { Text("Not Now") } .buttonStyle(GhostButtonStyle()) } .padding(.horizontal, AppTheme.hPad) .opacity(appeared ? 1 : 0) .animation(.spring(response: 0.4, dampingFraction: 0.8).delay(0.28), value: appeared) Spacer().frame(height: 48) } } .onAppear { appeared = true } } private var stepIndicator: some View { HStack(spacing: 6) { ForEach(1...3, id: \.self) { step in Capsule() .fill(step == 3 ? AppTheme.ink : AppTheme.inkQuaternary) .frame(width: step == 3 ? 20 : 6, height: 6) } } } private func detailChip(icon: String, label: String) -> some View { HStack(spacing: 4) { Image(systemName: icon) .font(.system(size: 10, weight: .medium)) Text(label) .font(.system(size: 11, weight: .medium)) } .foregroundStyle(AppTheme.inkSecondary) .padding(.horizontal, 10) .padding(.vertical, 5) .background(AppTheme.surfaceSunken) .clipShape(Capsule(style: .continuous)) } private func requestAccess() { isRequesting = true PHPhotoLibrary.requestAuthorization(for: .readWrite) { _ in DispatchQueue.main.async { isRequesting = false store.onboardingPhotoShown = true } } } private func skipAndContinue() { store.onboardingPhotoShown = true } }