import SwiftUI struct LoginView: View { @EnvironmentObject var store: ConnectionStore @StateObject private var vm = LoginViewModel() @State private var logoAppeared = false var body: some View { ZStack { AppTheme.background.ignoresSafeArea() VStack(spacing: 0) { Spacer() // Logo Image("nas_logo_clean") .resizable() .scaledToFit() .frame(width: 72, height: 72) .clipShape(RoundedRectangle(cornerRadius: 18, style: .continuous)) .shadow(color: .black.opacity(0.08), radius: 16, x: 0, y: 4) .scaleEffect(logoAppeared ? 1 : 0.8) .opacity(logoAppeared ? 1 : 0) .animation(.spring(response: 0.5, dampingFraction: 0.7).delay(0.1), value: logoAppeared) Spacer().frame(height: 32) // Title + account VStack(spacing: 5) { Text("Kisani") .font(.system(size: 22, weight: .semibold)) .foregroundStyle(AppTheme.ink) Text("Sign in to continue") .font(AppTheme.caption()) .foregroundStyle(AppTheme.inkTertiary) } .opacity(logoAppeared ? 1 : 0) .animation(.spring(response: 0.5, dampingFraction: 0.7).delay(0.2), value: logoAppeared) Spacer().frame(height: 40) // Face ID button Button(action: { vm.authenticateWithFaceID() }) { HStack(spacing: 8) { if vm.isAuthenticating { ProgressView() .tint(AppTheme.inkInverse) .scaleEffect(0.8) } else { Image(systemName: "faceid") .font(.system(size: 16, weight: .medium)) Text("Sign in with Face ID") } } } .buttonStyle(PrimaryButtonStyle()) .padding(.horizontal, AppTheme.hPad) .disabled(vm.isAuthenticating) .opacity(logoAppeared ? 1 : 0) .animation(.spring(response: 0.5, dampingFraction: 0.7).delay(0.3), value: logoAppeared) if let error = vm.authError { Text(error) .font(AppTheme.caption()) .foregroundStyle(AppTheme.destructive) .padding(.top, 12) .transition(.opacity.combined(with: .move(edge: .top))) } Spacer() // More Button("More options") { vm.showMoreSheet = true } .font(.system(size: 13, weight: .regular)) .foregroundStyle(AppTheme.inkTertiary) .padding(.bottom, 44) .opacity(logoAppeared ? 1 : 0) .animation(.spring(response: 0.5, dampingFraction: 0.7).delay(0.4), value: logoAppeared) } } .sheet(isPresented: $vm.showMoreSheet) { MoreOptionsSheet(isPresented: $vm.showMoreSheet) { vm.showMoreSheet = false store.isSessionActive = true } .presentationDetents([.height(200)]) .modifier(SheetCornerRadius(20)) } .onAppear { vm.onAuthenticated = { store.isSessionActive = true } logoAppeared = true } } } struct MoreOptionsSheet: View { @Binding var isPresented: Bool var onAuthenticated: (() -> Void)? var body: some View { VStack(spacing: 0) { Capsule() .fill(AppTheme.inkQuaternary) .frame(width: 32, height: 4) .padding(.top, 12) .padding(.bottom, 20) VStack(spacing: 0) { sheetRow("Password", weight: .medium) { } Divider().padding(.leading, 16) sheetRow("Another account") { isPresented = false onAuthenticated?() } Divider().padding(.leading, 16) sheetRow("Cancel", color: AppTheme.inkTertiary) { isPresented = false } } .background(AppTheme.surfaceRaised) .clipShape(RoundedRectangle(cornerRadius: AppTheme.radius, style: .continuous)) .overlay( RoundedRectangle(cornerRadius: AppTheme.radius, style: .continuous) .stroke(AppTheme.cardBorderColor, lineWidth: 0.5) ) .padding(.horizontal, AppTheme.hPad) .padding(.bottom, 16) } .background(AppTheme.background) } private func sheetRow( _ title: String, weight: Font.Weight = .regular, color: Color = AppTheme.ink, action: @escaping () -> Void ) -> some View { Button(action: action) { Text(title) .font(.system(size: 15, weight: weight)) .foregroundStyle(color) .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal, 16) .padding(.vertical, 14) } .buttonStyle(ScaleButtonStyle()) } } private struct SheetCornerRadius: ViewModifier { let radius: CGFloat init(_ radius: CGFloat) { self.radius = radius } func body(content: Content) -> some View { if #available(iOS 16.4, *) { content.presentationCornerRadius(radius) } else { content } } }