// // LockScreenView.swift // Reflect // // Lock screen shown when privacy lock is enabled and app needs authentication. // Supports multiple themed styles that match app themes. // import SwiftUI // MARK: - Lock Screen Style Protocol protocol LockScreenTheme { var backgroundColor: AnyView { get } var centralElement: AnyView { get } var titleText: String { get } var subtitleText: String { get } var taglineText: String { get } var titleFont: Font { get } var subtitleFont: Font { get } var taglineFont: Font { get } func titleColor(isDark: Bool) -> Color func subtitleColor(isDark: Bool) -> Color func taglineColor(isDark: Bool) -> Color func buttonStyle(isDark: Bool) -> LockButtonStyle } struct LockButtonStyle { let backgroundColor: Color let foregroundColor: Color let borderColor: Color let useMaterial: Bool } // MARK: - Floating Mood Particle struct MoodParticle: Identifiable { let id = UUID() var x: CGFloat var y: CGFloat let size: CGFloat let color: Color let duration: Double let delay: Double } // MARK: - Aurora Gradient Background struct AuroraBackground: View { @Environment(\.colorScheme) private var colorScheme @State private var animateGradient = false private let moodColors: [Color] = [ Color(hex: "31d158"), // great - green Color(hex: "ffd709"), // good - yellow Color(hex: "0b84ff"), // average - blue Color(hex: "ff9e0b"), // bad - orange Color(hex: "ff453a"), // horrible - red ] private var isDark: Bool { colorScheme == .dark } var body: some View { ZStack { // Base gradient - adapts to color scheme LinearGradient( colors: isDark ? [ Color(hex: "0a0a0f"), Color(hex: "12121a"), Color(hex: "0d0d14") ] : [ Color(hex: "f8f9fa"), Color(hex: "e9ecef"), Color(hex: "f1f3f5") ], startPoint: .topLeading, endPoint: .bottomTrailing ) // Aurora layer 1 - green/blue EllipticalGradient( colors: [ moodColors[0].opacity(isDark ? 0.3 : 0.2), moodColors[2].opacity(isDark ? 0.15 : 0.1), .clear ], center: .topLeading, startRadiusFraction: 0, endRadiusFraction: 0.8 ) .blur(radius: 60) .offset(x: animateGradient ? 20 : -20, y: animateGradient ? -30 : 30) // Aurora layer 2 - yellow/orange EllipticalGradient( colors: [ moodColors[1].opacity(isDark ? 0.2 : 0.15), moodColors[3].opacity(isDark ? 0.1 : 0.08), .clear ], center: .bottomTrailing, startRadiusFraction: 0, endRadiusFraction: 0.7 ) .blur(radius: 80) .offset(x: animateGradient ? -30 : 30, y: animateGradient ? 20 : -20) // Aurora layer 3 - subtle red accent RadialGradient( colors: [ moodColors[4].opacity(isDark ? 0.15 : 0.1), .clear ], center: UnitPoint(x: 0.8, y: 0.3), startRadius: 0, endRadius: 200 ) .blur(radius: 40) .offset(y: animateGradient ? -10 : 10) } .ignoresSafeArea() .onAppear { withAnimation( .easeInOut(duration: 8) .repeatForever(autoreverses: true) ) { animateGradient = true } } } } // MARK: - Floating Particles Layer struct FloatingParticlesView: View { @State private var particles: [MoodParticle] = [] private let moodColors: [Color] = [ Color(hex: "31d158"), Color(hex: "ffd709"), Color(hex: "0b84ff"), Color(hex: "ff9e0b"), Color(hex: "ff453a"), ] var body: some View { GeometryReader { geo in ZStack { ForEach(particles) { particle in Circle() .fill(particle.color) .frame(width: particle.size, height: particle.size) .blur(radius: particle.size * 0.3) .position(x: particle.x, y: particle.y) .modifier(FloatingAnimation( startY: particle.y, duration: particle.duration, delay: particle.delay )) } } .onAppear { generateParticles(in: geo.size) } } } private func generateParticles(in size: CGSize) { particles = (0..<15).map { _ in MoodParticle( x: CGFloat.random(in: 0...size.width), y: CGFloat.random(in: 0...size.height), size: CGFloat.random(in: 4...12), color: moodColors.randomElement()!.opacity(Double.random(in: 0.2...0.5)), duration: Double.random(in: 6...12), delay: Double.random(in: 0...3) ) } } } struct FloatingAnimation: ViewModifier { let startY: CGFloat let duration: Double let delay: Double @State private var offset: CGFloat = 0 func body(content: Content) -> some View { content .offset(y: offset) .onAppear { withAnimation( .easeInOut(duration: duration) .repeatForever(autoreverses: true) .delay(delay) ) { offset = CGFloat.random(in: -30...30) } } } } // MARK: - Central Breathing Orb struct BreathingOrb: View { @Environment(\.colorScheme) private var colorScheme @State private var breathe = false @State private var rotate = false private let moodColors: [Color] = [ Color(hex: "31d158"), Color(hex: "ffd709"), Color(hex: "0b84ff"), Color(hex: "ff9e0b"), Color(hex: "ff453a"), ] private var isDark: Bool { colorScheme == .dark } var body: some View { ZStack { // Outer glow Circle() .fill( AngularGradient( colors: moodColors + [moodColors[0]], center: .center, startAngle: .degrees(0), endAngle: .degrees(360) ) ) .frame(width: 180, height: 180) .blur(radius: 40) .opacity(isDark ? 0.6 : 0.5) .scaleEffect(breathe ? 1.2 : 0.9) .rotationEffect(.degrees(rotate ? 360 : 0)) // Middle ring Circle() .fill( AngularGradient( colors: moodColors.reversed() + [moodColors.last!], center: .center ) ) .frame(width: 120, height: 120) .blur(radius: 20) .opacity(isDark ? 0.8 : 0.6) .scaleEffect(breathe ? 1.1 : 0.95) .rotationEffect(.degrees(rotate ? -360 : 0)) // Inner core Circle() .fill( RadialGradient( colors: isDark ? [ .white.opacity(0.9), .white.opacity(0.3), .clear ] : [ .white, .white.opacity(0.6), .clear ], center: .center, startRadius: 0, endRadius: 40 ) ) .frame(width: 80, height: 80) .scaleEffect(breathe ? 1.05 : 0.98) .shadow(color: .black.opacity(isDark ? 0 : 0.1), radius: 10) // Glossy highlight Ellipse() .fill( LinearGradient( colors: [.white.opacity(isDark ? 0.4 : 0.8), .clear], startPoint: .top, endPoint: .center ) ) .frame(width: 50, height: 30) .offset(y: -15) .scaleEffect(breathe ? 1.05 : 0.98) } .onAppear { withAnimation( .easeInOut(duration: 4) .repeatForever(autoreverses: true) ) { breathe = true } withAnimation( .linear(duration: 20) .repeatForever(autoreverses: false) ) { rotate = true } } } } // MARK: - Zen Lock Screen Theme struct ZenLockBackground: View { @Environment(\.colorScheme) private var colorScheme @State private var breathe = false var body: some View { ZStack { // Warm paper background LinearGradient( colors: colorScheme == .dark ? [ Color(red: 0.12, green: 0.11, blue: 0.10), Color(red: 0.08, green: 0.07, blue: 0.06) ] : [ Color(red: 0.96, green: 0.94, blue: 0.90), Color(red: 0.92, green: 0.90, blue: 0.86) ], startPoint: .top, endPoint: .bottom ) // Subtle ink wash effect Circle() .fill( RadialGradient( colors: colorScheme == .dark ? [ Color(red: 0.4, green: 0.45, blue: 0.4).opacity(0.15), .clear ] : [ Color(red: 0.3, green: 0.35, blue: 0.3).opacity(0.08), .clear ], center: .center, startRadius: 0, endRadius: 300 ) ) .scaleEffect(breathe ? 1.1 : 1.0) .blur(radius: 80) } .ignoresSafeArea() .onAppear { withAnimation(.easeInOut(duration: 6).repeatForever(autoreverses: true)) { breathe = true } } .onDisappear { breathe = false } } } struct ZenEnsoOrb: View { @Environment(\.colorScheme) private var colorScheme @State private var drawProgress: CGFloat = 0 @State private var breathe = false var body: some View { ZStack { // Outer glow Circle() .stroke( Color(red: 0.35, green: 0.4, blue: 0.35).opacity(colorScheme == .dark ? 0.3 : 0.15), lineWidth: 4 ) .frame(width: 140, height: 140) .blur(radius: 15) .scaleEffect(breathe ? 1.1 : 0.95) // Enso circle - incomplete for zen aesthetics Circle() .trim(from: 0, to: 0.85) .stroke( Color(red: 0.3, green: 0.35, blue: 0.3), style: StrokeStyle(lineWidth: 5, lineCap: .round) ) .frame(width: 100, height: 100) .rotationEffect(.degrees(-90)) .scaleEffect(breathe ? 1.02 : 0.98) } .onAppear { withAnimation(.easeInOut(duration: 5).repeatForever(autoreverses: true)) { breathe = true } } .onDisappear { breathe = false } } } // MARK: - Neon Lock Screen Theme struct NeonLockBackground: View { @State private var pulse = false var body: some View { ZStack { // Deep black base Color(red: 0.02, green: 0.02, blue: 0.05) // Grid lines Canvas { context, size in let spacing: CGFloat = 30 for y in stride(from: 0, to: size.height, by: spacing) { var path = Path() path.move(to: CGPoint(x: 0, y: y)) path.addLine(to: CGPoint(x: size.width, y: y)) context.stroke(path, with: .color(Color.cyan.opacity(0.08)), lineWidth: 0.5) } for x in stride(from: 0, to: size.width, by: spacing) { var path = Path() path.move(to: CGPoint(x: x, y: 0)) path.addLine(to: CGPoint(x: x, y: size.height)) context.stroke(path, with: .color(Color.cyan.opacity(0.08)), lineWidth: 0.5) } } // Neon glow spots Circle() .fill(Color(red: 0, green: 1, blue: 0.82).opacity(pulse ? 0.3 : 0.15)) .frame(width: 300, height: 300) .blur(radius: 80) .offset(y: -100) Circle() .fill(Color(red: 1, green: 0, blue: 0.8).opacity(pulse ? 0.2 : 0.1)) .frame(width: 250, height: 250) .blur(radius: 70) .offset(x: 50, y: 150) } .ignoresSafeArea() .onAppear { withAnimation(.easeInOut(duration: 2).repeatForever(autoreverses: true)) { pulse = true } } .onDisappear { pulse = false } } } struct NeonRingOrb: View { @State private var rotate = false @State private var pulse = false var body: some View { ZStack { // Outer glow ring Circle() .stroke( LinearGradient( colors: [Color(red: 0, green: 1, blue: 0.82), Color(red: 1, green: 0, blue: 0.8)], startPoint: .topLeading, endPoint: .bottomTrailing ), lineWidth: 4 ) .frame(width: 140, height: 140) .blur(radius: 10) .opacity(pulse ? 0.9 : 0.5) .rotationEffect(.degrees(rotate ? 360 : 0)) // Inner ring Circle() .stroke( LinearGradient( colors: [Color(red: 0, green: 1, blue: 0.82), Color(red: 1, green: 0, blue: 0.8)], startPoint: .topLeading, endPoint: .bottomTrailing ), lineWidth: 3 ) .frame(width: 100, height: 100) .shadow(color: Color(red: 0, green: 1, blue: 0.82).opacity(0.6), radius: pulse ? 20 : 10) // Center core Circle() .fill(Color.white) .frame(width: 30, height: 30) .shadow(color: .white.opacity(0.8), radius: 15) } .onAppear { withAnimation(.linear(duration: 10).repeatForever(autoreverses: false)) { rotate = true } withAnimation(.easeInOut(duration: 1.5).repeatForever(autoreverses: true)) { pulse = true } } .onDisappear { rotate = false pulse = false } } } // MARK: - Celestial Lock Screen Theme struct CelestialLockBackground: View { @Environment(\.colorScheme) private var colorScheme @State private var twinkle = false var body: some View { ZStack { // Deep space gradient LinearGradient( colors: colorScheme == .dark ? [ Color(red: 0.05, green: 0.05, blue: 0.12), Color(red: 0.08, green: 0.06, blue: 0.15) ] : [ Color(red: 0.95, green: 0.94, blue: 0.98), Color(red: 0.92, green: 0.9, blue: 0.96) ], startPoint: .top, endPoint: .bottom ) // Star field (dark mode only) if colorScheme == .dark { Canvas { context, size in for _ in 0..<50 { let x = CGFloat.random(in: 0...size.width) let y = CGFloat.random(in: 0...size.height) let starSize = CGFloat.random(in: 1...3) context.fill( Path(ellipseIn: CGRect(x: x, y: y, width: starSize, height: starSize)), with: .color(.white.opacity(Double.random(in: 0.3...0.8))) ) } } .opacity(twinkle ? 0.8 : 1.0) } // Nebula glow Circle() .fill(Color(red: 1.0, green: 0.4, blue: 0.5).opacity(colorScheme == .dark ? 0.2 : 0.1)) .frame(width: 300, height: 300) .blur(radius: 80) .offset(y: -50) Circle() .fill(Color(red: 0.6, green: 0.4, blue: 0.9).opacity(colorScheme == .dark ? 0.15 : 0.08)) .frame(width: 250, height: 250) .blur(radius: 60) .offset(x: 80, y: 100) } .ignoresSafeArea() .onAppear { withAnimation(.easeInOut(duration: 3).repeatForever(autoreverses: true)) { twinkle = true } } .onDisappear { twinkle = false } } } struct CelestialOrbsElement: View { @Environment(\.colorScheme) private var colorScheme @State private var float = false @State private var rotate = false private let orbColors: [Color] = [ Color(red: 1.0, green: 0.8, blue: 0.3), // Gold Color(red: 1.0, green: 0.5, blue: 0.5), // Coral Color(red: 0.6, green: 0.5, blue: 0.9) // Lavender ] var body: some View { ZStack { // Orbit ring Circle() .stroke(Color.white.opacity(colorScheme == .dark ? 0.15 : 0.2), lineWidth: 1) .frame(width: 140, height: 140) .rotationEffect(.degrees(rotate ? 360 : 0)) // Orbiting orbs ForEach(0..<3, id: \.self) { i in Circle() .fill( RadialGradient( colors: [orbColors[i], orbColors[i].opacity(0.6)], center: .center, startRadius: 0, endRadius: 20 ) ) .frame(width: 28, height: 28) .shadow(color: orbColors[i].opacity(0.6), radius: 10) .offset(y: -70) .rotationEffect(.degrees(Double(i) * 120 + (rotate ? 360 : 0))) } // Center star Image(systemName: "sparkle") .font(.system(size: 40, weight: .light)) .foregroundStyle( LinearGradient( colors: [Color(red: 1.0, green: 0.9, blue: 0.7), Color(red: 1.0, green: 0.7, blue: 0.6)], startPoint: .top, endPoint: .bottom ) ) .shadow(color: Color(red: 1.0, green: 0.8, blue: 0.5).opacity(0.5), radius: 15) .scaleEffect(float ? 1.1 : 0.95) } .onAppear { withAnimation(.linear(duration: 20).repeatForever(autoreverses: false)) { rotate = true } withAnimation(.easeInOut(duration: 3).repeatForever(autoreverses: true)) { float = true } } .onDisappear { rotate = false float = false } } } // MARK: - Editorial Lock Screen Theme struct EditorialLockBackground: View { @Environment(\.colorScheme) private var colorScheme var body: some View { ZStack { // Solid elegant background Color(colorScheme == .dark ? .black : .white) // Subtle texture lines VStack(spacing: 0) { ForEach(0..<20, id: \.self) { _ in Rectangle() .fill(Color.primary.opacity(0.02)) .frame(height: 1) Spacer() } } .padding(.horizontal, 40) } .ignoresSafeArea() } } struct EditorialFrameElement: View { @Environment(\.colorScheme) private var colorScheme @State private var appear = false var body: some View { ZStack { // Elegant frame RoundedRectangle(cornerRadius: 2) .stroke(Color.primary.opacity(0.3), lineWidth: 1) .frame(width: 120, height: 150) // Inner accent line VStack { Rectangle() .fill(Color.primary) .frame(width: 40, height: 2) Spacer() Rectangle() .fill(Color.primary) .frame(width: 40, height: 2) } .frame(height: 130) .opacity(appear ? 1 : 0) // Center diamond Image(systemName: "diamond") .font(.system(size: 28, weight: .ultraLight)) .foregroundColor(.primary) .opacity(appear ? 1 : 0.5) .scaleEffect(appear ? 1 : 0.9) } .onAppear { withAnimation(.easeOut(duration: 1)) { appear = true } } } } // MARK: - Mixtape Lock Screen Theme struct MixtapeLockBackground: View { @State private var shift = false var body: some View { ZStack { // Warm retro gradient LinearGradient( colors: [ Color(red: 0.95, green: 0.45, blue: 0.35), Color(red: 0.95, green: 0.65, blue: 0.25) ], startPoint: shift ? .topLeading : .topTrailing, endPoint: shift ? .bottomTrailing : .bottomLeading ) } .ignoresSafeArea() .onAppear { withAnimation(.easeInOut(duration: 5).repeatForever(autoreverses: true)) { shift = true } } .onDisappear { shift = false } } } struct CassetteElement: View { @State private var spin = false var body: some View { ZStack { // Cassette body RoundedRectangle(cornerRadius: 8) .fill(Color.black.opacity(0.85)) .frame(width: 140, height: 90) .shadow(color: .black.opacity(0.3), radius: 10) // Label area RoundedRectangle(cornerRadius: 4) .fill(Color.white.opacity(0.9)) .frame(width: 100, height: 30) .offset(y: -15) // Reels HStack(spacing: 40) { Circle() .fill(Color.white.opacity(0.8)) .frame(width: 30, height: 30) .overlay( Circle() .fill(Color.black.opacity(0.6)) .frame(width: 10, height: 10) ) .rotationEffect(.degrees(spin ? 360 : 0)) Circle() .fill(Color.white.opacity(0.8)) .frame(width: 30, height: 30) .overlay( Circle() .fill(Color.black.opacity(0.6)) .frame(width: 10, height: 10) ) .rotationEffect(.degrees(spin ? 360 : 0)) } .offset(y: 18) } .onAppear { withAnimation(.linear(duration: 4).repeatForever(autoreverses: false)) { spin = true } } .onDisappear { spin = false } } } // MARK: - Bloom Lock Screen Theme struct BloomLockBackground: View { @Environment(\.colorScheme) private var colorScheme @State private var bloom = false var body: some View { ZStack { // Garden gradient LinearGradient( colors: colorScheme == .dark ? [ Color(red: 0.05, green: 0.12, blue: 0.08), Color(red: 0.08, green: 0.18, blue: 0.1) ] : [ Color(red: 0.95, green: 0.98, blue: 0.95), Color(red: 0.9, green: 0.96, blue: 0.92) ], startPoint: .top, endPoint: .bottom ) // Soft glows Circle() .fill(Color(red: 0.3, green: 0.7, blue: 0.4).opacity(colorScheme == .dark ? 0.2 : 0.1)) .frame(width: 300, height: 300) .blur(radius: 80) .offset(y: bloom ? -20 : 20) Circle() .fill(Color(red: 1.0, green: 0.6, blue: 0.7).opacity(colorScheme == .dark ? 0.15 : 0.08)) .frame(width: 200, height: 200) .blur(radius: 60) .offset(x: -50, y: bloom ? 80 : 120) } .ignoresSafeArea() .onAppear { withAnimation(.easeInOut(duration: 6).repeatForever(autoreverses: true)) { bloom = true } } .onDisappear { bloom = false } } } struct FlowerElement: View { @Environment(\.colorScheme) private var colorScheme @State private var bloom = false var body: some View { ZStack { // Petals ForEach(0..<6, id: \.self) { i in Ellipse() .fill( LinearGradient( colors: [ Color(red: 1.0, green: 0.6, blue: 0.7), Color(red: 1.0, green: 0.5, blue: 0.6) ], startPoint: .top, endPoint: .bottom ) ) .frame(width: 30, height: bloom ? 60 : 45) .offset(y: bloom ? -45 : -35) .rotationEffect(.degrees(Double(i) * 60)) .shadow(color: Color(red: 1.0, green: 0.5, blue: 0.6).opacity(0.3), radius: 8) } // Center Circle() .fill( RadialGradient( colors: [ Color(red: 1.0, green: 0.9, blue: 0.6), Color(red: 1.0, green: 0.85, blue: 0.4) ], center: .center, startRadius: 0, endRadius: 25 ) ) .frame(width: 40, height: 40) .shadow(color: Color(red: 1.0, green: 0.9, blue: 0.6).opacity(0.5), radius: 10) } .onAppear { withAnimation(.easeInOut(duration: 4).repeatForever(autoreverses: true)) { bloom = true } } .onDisappear { bloom = false } } } // MARK: - Heartfelt Lock Screen Theme struct HeartfeltLockBackground: View { @Environment(\.colorScheme) private var colorScheme @State private var pulse = false var body: some View { ZStack { // Soft pink gradient LinearGradient( colors: colorScheme == .dark ? [ Color(red: 0.15, green: 0.08, blue: 0.1), Color(red: 0.1, green: 0.05, blue: 0.08) ] : [ Color(red: 1.0, green: 0.95, blue: 0.96), Color(red: 0.98, green: 0.92, blue: 0.94) ], startPoint: .top, endPoint: .bottom ) // Heart glow Circle() .fill(Color(red: 0.9, green: 0.45, blue: 0.55).opacity(colorScheme == .dark ? 0.2 : 0.1)) .frame(width: 300, height: 300) .blur(radius: 80) .scaleEffect(pulse ? 1.1 : 0.95) } .ignoresSafeArea() .onAppear { withAnimation(.easeInOut(duration: 1.5).repeatForever(autoreverses: true)) { pulse = true } } .onDisappear { pulse = false } } } struct HeartElement: View { @State private var beat = false var body: some View { ZStack { // Glow Image(systemName: "heart.fill") .font(.system(size: 80)) .foregroundColor(Color(red: 0.9, green: 0.45, blue: 0.55)) .blur(radius: 20) .scaleEffect(beat ? 1.15 : 0.9) // Main heart Image(systemName: "heart.fill") .font(.system(size: 70)) .foregroundStyle( LinearGradient( colors: [ Color(red: 0.95, green: 0.55, blue: 0.6), Color(red: 0.85, green: 0.35, blue: 0.45) ], startPoint: .top, endPoint: .bottom ) ) .shadow(color: Color(red: 0.9, green: 0.45, blue: 0.55).opacity(0.4), radius: 15) .scaleEffect(beat ? 1.08 : 0.95) } .onAppear { withAnimation(.easeInOut(duration: 0.8).repeatForever(autoreverses: true)) { beat = true } } .onDisappear { beat = false } } } // MARK: - Minimal Lock Screen Theme struct MinimalLockBackground: View { @Environment(\.colorScheme) private var colorScheme var body: some View { ZStack { // Clean gradient LinearGradient( colors: colorScheme == .dark ? [ Color(red: 0.1, green: 0.1, blue: 0.1), Color(red: 0.08, green: 0.08, blue: 0.08) ] : [ Color(red: 0.98, green: 0.96, blue: 0.94), Color(red: 0.95, green: 0.93, blue: 0.9) ], startPoint: .top, endPoint: .bottom ) // Subtle warm accent Circle() .fill(Color(red: 0.95, green: 0.6, blue: 0.5).opacity(colorScheme == .dark ? 0.08 : 0.05)) .frame(width: 400, height: 400) .blur(radius: 100) } .ignoresSafeArea() } } struct MinimalCircleElement: View { @Environment(\.colorScheme) private var colorScheme @State private var breathe = false var body: some View { ZStack { // Outer ring Circle() .stroke(Color.primary.opacity(0.15), lineWidth: 1) .frame(width: breathe ? 130 : 120, height: breathe ? 130 : 120) // Middle ring Circle() .stroke(Color.primary.opacity(0.25), lineWidth: 1) .frame(width: breathe ? 90 : 85, height: breathe ? 90 : 85) // Inner circle Circle() .fill(Color(red: 0.95, green: 0.6, blue: 0.5).opacity(colorScheme == .dark ? 0.6 : 0.4)) .frame(width: 50, height: 50) .scaleEffect(breathe ? 1.05 : 0.95) } .onAppear { withAnimation(.easeInOut(duration: 4).repeatForever(autoreverses: true)) { breathe = true } } .onDisappear { breathe = false } } } // MARK: - Luxe Lock Screen Theme struct LuxeLockBackground: View { @State private var shimmer = false var body: some View { ZStack { // Rich dark background LinearGradient( colors: [ Color(red: 0.1, green: 0.08, blue: 0.06), Color(red: 0.06, green: 0.04, blue: 0.02) ], startPoint: .top, endPoint: .bottom ) // Gold shimmer LinearGradient( colors: [ .clear, Color(red: 0.85, green: 0.7, blue: 0.45).opacity(0.1), .clear ], startPoint: shimmer ? .topLeading : .bottomTrailing, endPoint: shimmer ? .bottomTrailing : .topLeading ) } .ignoresSafeArea() .onAppear { withAnimation(.easeInOut(duration: 4).repeatForever(autoreverses: true)) { shimmer = true } } .onDisappear { shimmer = false } } } struct DiamondElement: View { @State private var rotate = false @State private var shimmer = false var body: some View { ZStack { // Glow Image(systemName: "diamond.fill") .font(.system(size: 70)) .foregroundColor(Color(red: 0.85, green: 0.7, blue: 0.45)) .blur(radius: 25) .opacity(shimmer ? 0.6 : 0.3) // Diamond Image(systemName: "diamond.fill") .font(.system(size: 60)) .foregroundStyle( LinearGradient( colors: [ Color(red: 0.95, green: 0.85, blue: 0.6), Color(red: 0.75, green: 0.6, blue: 0.35), Color(red: 0.55, green: 0.45, blue: 0.25) ], startPoint: shimmer ? .topLeading : .bottomTrailing, endPoint: shimmer ? .bottomTrailing : .topLeading ) ) .shadow(color: Color(red: 0.85, green: 0.7, blue: 0.45).opacity(0.5), radius: 20) .rotationEffect(.degrees(rotate ? 5 : -5)) } .onAppear { withAnimation(.easeInOut(duration: 3).repeatForever(autoreverses: true)) { rotate = true } withAnimation(.easeInOut(duration: 2).repeatForever(autoreverses: true)) { shimmer = true } } .onDisappear { rotate = false shimmer = false } } } // MARK: - Forecast Lock Screen Theme struct ForecastLockBackground: View { @State private var drift = false var body: some View { ZStack { // Sky gradient LinearGradient( colors: [ Color(red: 0.55, green: 0.75, blue: 0.95), Color(red: 0.4, green: 0.6, blue: 0.85), Color(red: 0.3, green: 0.5, blue: 0.75) ], startPoint: .top, endPoint: .bottom ) // Floating clouds ForEach(0..<4, id: \.self) { i in Image(systemName: "cloud.fill") .font(.system(size: CGFloat.random(in: 40...80))) .foregroundColor(.white.opacity(Double.random(in: 0.3...0.6))) .offset( x: CGFloat(i * 100 - 150) + (drift ? 20 : -20), y: CGFloat(i * 80 - 200) ) .blur(radius: CGFloat.random(in: 2...5)) } } .ignoresSafeArea() .onAppear { withAnimation(.easeInOut(duration: 8).repeatForever(autoreverses: true)) { drift = true } } .onDisappear { drift = false } } } struct WeatherElement: View { @State private var shine = false var body: some View { ZStack { // Sun rays ForEach(0..<8, id: \.self) { i in Rectangle() .fill(Color(red: 1.0, green: 0.9, blue: 0.5)) .frame(width: 3, height: 30) .offset(y: -60) .rotationEffect(.degrees(Double(i) * 45)) .opacity(shine ? 0.8 : 0.4) } // Sun glow Circle() .fill(Color(red: 1.0, green: 0.9, blue: 0.5).opacity(0.4)) .frame(width: 100, height: 100) .blur(radius: 25) // Sun Circle() .fill( RadialGradient( colors: [ Color(red: 1.0, green: 0.95, blue: 0.7), Color(red: 1.0, green: 0.85, blue: 0.4) ], center: .center, startRadius: 0, endRadius: 35 ) ) .frame(width: 70, height: 70) .shadow(color: Color(red: 1.0, green: 0.85, blue: 0.4).opacity(0.6), radius: 20) // Cloud accent Image(systemName: "cloud.fill") .font(.system(size: 35)) .foregroundColor(.white) .offset(x: 40, y: 25) .shadow(color: .black.opacity(0.1), radius: 5) } .onAppear { withAnimation(.easeInOut(duration: 2).repeatForever(autoreverses: true)) { shine = true } } .onDisappear { shine = false } } } // MARK: - Playful Lock Screen Theme struct PlayfulLockBackground: View { @Environment(\.colorScheme) private var colorScheme var body: some View { ZStack { // Warm cream gradient LinearGradient( colors: colorScheme == .dark ? [ Color(red: 0.15, green: 0.12, blue: 0.1), Color(red: 0.1, green: 0.08, blue: 0.06) ] : [ Color(red: 1.0, green: 0.98, blue: 0.95), Color(red: 0.98, green: 0.96, blue: 0.92) ], startPoint: .top, endPoint: .bottom ) // Colorful accents Circle() .fill(Color(red: 0.95, green: 0.55, blue: 0.35).opacity(colorScheme == .dark ? 0.15 : 0.08)) .frame(width: 200, height: 200) .blur(radius: 60) .offset(x: -80, y: -150) Circle() .fill(Color(red: 0.95, green: 0.75, blue: 0.35).opacity(colorScheme == .dark ? 0.12 : 0.06)) .frame(width: 180, height: 180) .blur(radius: 50) .offset(x: 100, y: 150) } .ignoresSafeArea() } } struct PlayfulEmojiElement: View { @State private var bounce = false @State private var wiggle = false var body: some View { ZStack { // Background circle Circle() .fill(Color(red: 0.95, green: 0.55, blue: 0.35).opacity(0.15)) .frame(width: 140, height: 140) .scaleEffect(bounce ? 1.05 : 0.98) // Main emoji Text("😊") .font(.system(size: 80)) .rotationEffect(.degrees(wiggle ? 8 : -8)) .scaleEffect(bounce ? 1.1 : 0.95) } .onAppear { withAnimation(.easeInOut(duration: 0.6).repeatForever(autoreverses: true)) { wiggle = true } withAnimation(.easeInOut(duration: 1.2).repeatForever(autoreverses: true)) { bounce = true } } .onDisappear { wiggle = false bounce = false } } } // MARK: - Journal Lock Screen Theme struct JournalLockBackground: View { @Environment(\.colorScheme) private var colorScheme var body: some View { ZStack { // Warm paper background Color(colorScheme == .dark ? Color(red: 0.12, green: 0.1, blue: 0.08) : Color(red: 0.95, green: 0.92, blue: 0.88) ) // Paper texture lines VStack(spacing: 28) { ForEach(0..<25, id: \.self) { _ in Rectangle() .fill(Color.primary.opacity(colorScheme == .dark ? 0.08 : 0.06)) .frame(height: 1) } } .padding(.horizontal, 50) // Margin line Rectangle() .fill(Color(red: 0.85, green: 0.55, blue: 0.55).opacity(colorScheme == .dark ? 0.3 : 0.2)) .frame(width: 1) .offset(x: -120) } .ignoresSafeArea() } } struct JournalBookElement: View { @Environment(\.colorScheme) private var colorScheme @State private var open = false var body: some View { ZStack { // Book shadow RoundedRectangle(cornerRadius: 8) .fill(Color.black.opacity(0.2)) .frame(width: 110, height: 140) .offset(x: 5, y: 5) .blur(radius: 8) // Book cover RoundedRectangle(cornerRadius: 8) .fill( LinearGradient( colors: [ Color(red: 0.55, green: 0.45, blue: 0.35), Color(red: 0.45, green: 0.35, blue: 0.28) ], startPoint: .top, endPoint: .bottom ) ) .frame(width: 100, height: 130) .overlay( RoundedRectangle(cornerRadius: 8) .stroke(Color(red: 0.65, green: 0.55, blue: 0.45), lineWidth: 2) ) // Spine Rectangle() .fill(Color(red: 0.4, green: 0.32, blue: 0.25)) .frame(width: 8, height: 130) .offset(x: -46) // Title area VStack(spacing: 8) { Rectangle() .fill(Color(red: 0.95, green: 0.9, blue: 0.82)) .frame(width: 60, height: 30) .cornerRadius(2) Image(systemName: "heart.fill") .font(.system(size: 20)) .foregroundColor(Color(red: 0.85, green: 0.55, blue: 0.55)) } .scaleEffect(open ? 1.02 : 1.0) } .onAppear { withAnimation(.easeInOut(duration: 2).repeatForever(autoreverses: true)) { open = true } } .onDisappear { open = false } } } // MARK: - Glassmorphic Button struct GlassButton: View { @Environment(\.colorScheme) private var colorScheme let icon: String let title: String let action: () -> Void @State private var isPressed = false @State private var pulse = false private var isDark: Bool { colorScheme == .dark } private var foregroundColor: Color { isDark ? .white : .primary } private var accentOpacity: Double { isDark ? 0.15 : 0.1 } var body: some View { Button(action: action) { HStack(spacing: 14) { ZStack { // Pulse ring Circle() .stroke(lineWidth: 2) .foregroundColor(foregroundColor.opacity(0.3)) .frame(width: 44, height: 44) .scaleEffect(pulse ? 1.3 : 1) .opacity(pulse ? 0 : 0.6) // Icon background Circle() .fill(foregroundColor.opacity(accentOpacity)) .frame(width: 44, height: 44) Image(systemName: icon) .font(.system(size: 20, weight: .medium)) .foregroundColor(foregroundColor) } Text(title) .font(.system(size: 17, weight: .semibold, design: .rounded)) .foregroundColor(foregroundColor) } .frame(maxWidth: .infinity) .padding(.vertical, 18) .padding(.horizontal, 24) .background( ZStack { // Glass effect RoundedRectangle(cornerRadius: 20) .fill(.ultraThinMaterial) // Border gradient RoundedRectangle(cornerRadius: 20) .stroke( LinearGradient( colors: [ foregroundColor.opacity(0.3), foregroundColor.opacity(0.1), foregroundColor.opacity(0.15) ], startPoint: .topLeading, endPoint: .bottomTrailing ), lineWidth: 1 ) // Inner highlight RoundedRectangle(cornerRadius: 20) .fill( LinearGradient( colors: [foregroundColor.opacity(0.08), .clear], startPoint: .top, endPoint: .bottom ) ) } ) .scaleEffect(isPressed ? 0.97 : 1) } .buttonStyle(PlainButtonStyle()) .simultaneousGesture( DragGesture(minimumDistance: 0) .onChanged { _ in withAnimation(.easeInOut(duration: 0.1)) { isPressed = true } } .onEnded { _ in withAnimation(.easeInOut(duration: 0.1)) { isPressed = false } } ) .onAppear { withAnimation( .easeInOut(duration: 2) .repeatForever(autoreverses: false) ) { pulse = true } } } } // MARK: - Main Lock Screen View struct LockScreenView: View { private enum AnimationConstants { static let contentAppearDuration: TimeInterval = 0.8 static let contentAppearDelay: TimeInterval = 0.2 static let authenticationDelay: Int = 800 // milliseconds } @Environment(\.colorScheme) private var colorScheme @ObservedObject var authManager: BiometricAuthManager @State private var showError = false @State private var showContent = false // Read from AppStorage to match current theme, with optional override for previews @AppStorage(UserDefaultsStore.Keys.lockScreenStyle.rawValue, store: GroupUserDefaults.groupDefaults) private var lockScreenStyleRaw: Int = 0 var style: LockScreenStyle? private var lockScreenStyle: LockScreenStyle { if let override = style { return override } return LockScreenStyle(rawValue: lockScreenStyleRaw) ?? .aurora } private var isDark: Bool { colorScheme == .dark } // Style-dependent properties private var primaryTextColor: Color { switch lockScreenStyle { case .neon: return Color(red: 0, green: 1, blue: 0.82) case .editorial: return isDark ? .white : .black case .mixtape: return .white case .luxe: return Color(red: 0.95, green: 0.85, blue: 0.6) case .forecast: return .white default: return isDark ? .white : .primary } } private var secondaryTextColor: Color { switch lockScreenStyle { case .neon: return Color(red: 1, green: 0, blue: 0.8) case .editorial: return isDark ? .white.opacity(0.7) : .black.opacity(0.7) case .mixtape: return .white.opacity(0.9) case .luxe: return Color(red: 0.75, green: 0.6, blue: 0.35) case .forecast: return .white.opacity(0.9) default: return isDark ? .white.opacity(0.7) : .secondary } } private var tertiaryTextColor: Color { switch lockScreenStyle { case .neon: return Color.white.opacity(0.6) case .editorial: return isDark ? .white.opacity(0.5) : .black.opacity(0.5) case .mixtape: return .white.opacity(0.7) case .luxe: return Color(red: 0.65, green: 0.55, blue: 0.4).opacity(0.8) case .forecast: return .white.opacity(0.7) default: return isDark ? .white.opacity(0.5) : .secondary.opacity(0.8) } } private var titleFont: Font { switch lockScreenStyle { case .neon: return .system(size: 28, weight: .black, design: .monospaced) case .editorial: return .system(size: 30, weight: .ultraLight, design: .serif) case .mixtape: return .system(size: 28, weight: .black, design: .rounded) case .zen: return .system(size: 30, weight: .thin, design: .serif) case .luxe: return .system(size: 28, weight: .light, design: .serif) case .playful: return .system(size: 28, weight: .bold, design: .rounded) case .journal: return .system(size: 26, weight: .medium, design: .serif) default: return .system(size: 32, weight: .light, design: .serif) } } private var titleText: String { switch lockScreenStyle { case .neon: return "UNLOCK YOUR" case .editorial: return "Your Story" case .mixtape: return "PRESS PLAY" case .zen: return "Find Your" case .heartfelt: return "Feel With" case .luxe: return "Your Sanctuary" case .forecast: return "Your Forecast" case .playful: return "Hey There!" case .journal: return "Your Journal" case .bloom: return "Time to" case .celestial: return "Your Feelings" case .minimal: return "Simply" default: return "Your Feelings" } } private var subtitleText: String { switch lockScreenStyle { case .neon: return "FULL SIGNAL" case .editorial: return "Awaits" case .mixtape: return "ON YOUR MOODS" case .zen: return "Inner Peace" case .heartfelt: return "All Your Heart" case .luxe: return "Awaits" case .forecast: return "Is Ready" case .playful: return "Let's Check In!" case .journal: return "Is Private" case .bloom: return "Bloom" case .celestial: return "are safe here" case .minimal: return "Know Yourself" default: return "are safe here" } } private var taglineText: String { switch lockScreenStyle { case .neon: return "Authenticate to sync your vibes" case .editorial: return "Authenticate to continue" case .mixtape: return "Authenticate to spin your tracks" case .zen: return "Authenticate to begin your practice" case .heartfelt: return "Authenticate to open your heart" case .luxe: return "Authenticate for exclusive access" case .forecast: return "Authenticate to check the weather" case .playful: return "Authenticate to start the fun!" case .journal: return "Authenticate to continue writing" case .bloom: return "Authenticate to tend your garden" case .celestial: return "Authenticate to explore the cosmos" case .minimal: return "Authenticate to continue" default: return "Authenticate to continue your journey" } } var body: some View { ZStack { // Themed background backgroundView .accessibilityHidden(true) // Floating particles (Aurora only) if lockScreenStyle == .aurora { FloatingParticlesView() .accessibilityHidden(true) } // Main content VStack(spacing: 0) { Spacer() // Central element centralElement .opacity(showContent ? 1 : 0) .scaleEffect(showContent ? 1 : 0.8) .accessibilityHidden(true) Spacer() .frame(height: 50) // Text content VStack(spacing: 12) { Text(titleText) .font(titleFont) .foregroundColor(primaryTextColor) .tracking(lockScreenStyle == .neon || lockScreenStyle == .mixtape ? 2 : 0) Text(subtitleText) .font(lockScreenStyle == .neon ? .system(size: 24, weight: .bold, design: .monospaced) : titleFont) .foregroundColor(secondaryTextColor) .tracking(lockScreenStyle == .neon || lockScreenStyle == .mixtape ? 2 : 0) } .multilineTextAlignment(.center) .opacity(showContent ? 1 : 0) .offset(y: showContent ? 0 : 20) .accessibilityElement(children: .combine) Spacer() .frame(height: 16) Text(taglineText) .font(.system(size: 14, weight: .regular, design: .rounded)) .foregroundColor(tertiaryTextColor) .opacity(showContent ? 1 : 0) Spacer() // Unlock button themedButton .disabled(authManager.isAuthenticating) .opacity(showContent ? 1 : 0) .offset(y: showContent ? 0 : 30) .padding(.horizontal, 32) .accessibilityIdentifier(AccessibilityID.LockScreen.unlockButton) .accessibilityLabel("Unlock") .accessibilityHint("Double tap to authenticate with \(authManager.biometricName)") // Passcode button if authManager.canUseDevicePasscode { Button { Task { let success = await authManager.authenticate() if !success { showError = true } } } label: { Text("Or use your device passcode") .font(.system(size: 13, weight: .medium, design: .rounded)) .foregroundColor(passcodeButtonColor) } .disabled(authManager.isAuthenticating) .padding(.top, 16) .opacity(showContent ? 1 : 0) .accessibilityIdentifier(AccessibilityID.LockScreen.passcodeUnlockButton) .accessibilityLabel("Use device passcode") .accessibilityHint("Double tap to authenticate with your device passcode") } Spacer() .frame(height: 50) } .padding() } .alert("Authentication Failed", isPresented: $showError) { Button("Try Again") { Task { await authManager.authenticate() } } .accessibilityIdentifier(AccessibilityID.LockScreen.tryAgainButton) Button("Cancel", role: .cancel) { } .accessibilityIdentifier(AccessibilityID.LockScreen.cancelButton) } message: { Text("Unable to verify your identity. Please try again.") } .onAppear { withAnimation(.easeOut(duration: AnimationConstants.contentAppearDuration).delay(AnimationConstants.contentAppearDelay)) { showContent = true } if !authManager.isUnlocked && !authManager.isAuthenticating { Task { try? await Task.sleep(for: .milliseconds(AnimationConstants.authenticationDelay)) await authManager.authenticate() } } } } // MARK: - Themed Components @ViewBuilder private var backgroundView: some View { switch lockScreenStyle { case .aurora: AuroraBackground() case .zen: ZenLockBackground() case .neon: NeonLockBackground() case .celestial: CelestialLockBackground() case .editorial: EditorialLockBackground() case .mixtape: MixtapeLockBackground() case .bloom: BloomLockBackground() case .heartfelt: HeartfeltLockBackground() case .minimal: MinimalLockBackground() case .luxe: LuxeLockBackground() case .forecast: ForecastLockBackground() case .playful: PlayfulLockBackground() case .journal: JournalLockBackground() } } @ViewBuilder private var centralElement: some View { switch lockScreenStyle { case .aurora: BreathingOrb() case .zen: ZenEnsoOrb() case .neon: NeonRingOrb() case .celestial: CelestialOrbsElement() case .editorial: EditorialFrameElement() case .mixtape: CassetteElement() case .bloom: FlowerElement() case .heartfelt: HeartElement() case .minimal: MinimalCircleElement() case .luxe: DiamondElement() case .forecast: WeatherElement() case .playful: PlayfulEmojiElement() case .journal: JournalBookElement() } } @ViewBuilder private var themedButton: some View { switch lockScreenStyle { case .neon: NeonUnlockButton( icon: authManager.biometricIcon, title: "Unlock with \(authManager.biometricName)" ) { Task { let success = await authManager.authenticate() if !success { showError = true } } } case .luxe: LuxeUnlockButton( icon: authManager.biometricIcon, title: "Unlock with \(authManager.biometricName)" ) { Task { let success = await authManager.authenticate() if !success { showError = true } } } case .mixtape: MixtapeUnlockButton( icon: authManager.biometricIcon, title: "Unlock with \(authManager.biometricName)" ) { Task { let success = await authManager.authenticate() if !success { showError = true } } } default: GlassButton( icon: authManager.biometricIcon, title: "Unlock with \(authManager.biometricName)" ) { Task { let success = await authManager.authenticate() if !success { showError = true } } } } } private var passcodeButtonColor: Color { switch lockScreenStyle { case .neon: return Color(red: 0, green: 1, blue: 0.82).opacity(0.7) case .luxe: return Color(red: 0.85, green: 0.7, blue: 0.45).opacity(0.7) case .mixtape, .forecast: return .white.opacity(0.6) case .editorial: return isDark ? .white.opacity(0.5) : .black.opacity(0.5) default: return isDark ? .white.opacity(0.5) : .accentColor } } } // MARK: - Themed Unlock Buttons struct NeonUnlockButton: View { let icon: String let title: String let action: () -> Void @State private var pulse = false var body: some View { Button(action: action) { HStack(spacing: 14) { Image(systemName: icon) .font(.system(size: 20, weight: .bold)) Text(title) .font(.system(size: 15, weight: .bold, design: .monospaced)) } .foregroundStyle( LinearGradient( colors: [Color(red: 0, green: 1, blue: 0.82), Color(red: 1, green: 0, blue: 0.8)], startPoint: .leading, endPoint: .trailing ) ) .frame(maxWidth: .infinity) .padding(.vertical, 18) .padding(.horizontal, 24) .background( RoundedRectangle(cornerRadius: 12) .fill(Color.black) .overlay( RoundedRectangle(cornerRadius: 12) .stroke( LinearGradient( colors: [Color(red: 0, green: 1, blue: 0.82), Color(red: 1, green: 0, blue: 0.8)], startPoint: .leading, endPoint: .trailing ), lineWidth: 2 ) ) .shadow(color: Color(red: 0, green: 1, blue: 0.82).opacity(pulse ? 0.5 : 0.2), radius: pulse ? 15 : 8) ) } .buttonStyle(PlainButtonStyle()) .onAppear { withAnimation(.easeInOut(duration: 1.5).repeatForever(autoreverses: true)) { pulse = true } } .onDisappear { pulse = false } } } struct LuxeUnlockButton: View { let icon: String let title: String let action: () -> Void var body: some View { Button(action: action) { HStack(spacing: 14) { Image(systemName: icon) .font(.system(size: 20, weight: .medium)) Text(title) .font(.system(size: 16, weight: .medium, design: .serif)) } .foregroundColor(Color(red: 0.95, green: 0.9, blue: 0.75)) .frame(maxWidth: .infinity) .padding(.vertical, 18) .padding(.horizontal, 24) .background( RoundedRectangle(cornerRadius: 14) .fill( LinearGradient( colors: [ Color(red: 0.55, green: 0.45, blue: 0.25), Color(red: 0.4, green: 0.32, blue: 0.18) ], startPoint: .top, endPoint: .bottom ) ) .overlay( RoundedRectangle(cornerRadius: 14) .stroke( LinearGradient( colors: [ Color(red: 0.85, green: 0.7, blue: 0.45), Color(red: 0.65, green: 0.52, blue: 0.3) ], startPoint: .topLeading, endPoint: .bottomTrailing ), lineWidth: 1 ) ) .shadow(color: Color(red: 0.85, green: 0.7, blue: 0.45).opacity(0.3), radius: 10) ) } .buttonStyle(PlainButtonStyle()) } } struct MixtapeUnlockButton: View { let icon: String let title: String let action: () -> Void var body: some View { Button(action: action) { HStack(spacing: 14) { Image(systemName: icon) .font(.system(size: 20, weight: .bold)) Text(title) .font(.system(size: 15, weight: .bold, design: .rounded)) } .foregroundColor(.white) .frame(maxWidth: .infinity) .padding(.vertical, 18) .padding(.horizontal, 24) .background( RoundedRectangle(cornerRadius: 14) .fill(Color.black.opacity(0.7)) .overlay( RoundedRectangle(cornerRadius: 14) .stroke(Color.white.opacity(0.3), lineWidth: 1) ) ) } .buttonStyle(PlainButtonStyle()) } } // MARK: - Previews #Preview("Aurora") { LockScreenView(authManager: BiometricAuthManager(), style: .aurora) } #Preview("Zen") { LockScreenView(authManager: BiometricAuthManager(), style: .zen) } #Preview("Neon") { LockScreenView(authManager: BiometricAuthManager(), style: .neon) } #Preview("Celestial") { LockScreenView(authManager: BiometricAuthManager(), style: .celestial) } #Preview("Editorial") { LockScreenView(authManager: BiometricAuthManager(), style: .editorial) } #Preview("Mixtape") { LockScreenView(authManager: BiometricAuthManager(), style: .mixtape) } #Preview("Bloom") { LockScreenView(authManager: BiometricAuthManager(), style: .bloom) } #Preview("Heartfelt") { LockScreenView(authManager: BiometricAuthManager(), style: .heartfelt) } #Preview("Minimal") { LockScreenView(authManager: BiometricAuthManager(), style: .minimal) } #Preview("Luxe") { LockScreenView(authManager: BiometricAuthManager(), style: .luxe) } #Preview("Forecast") { LockScreenView(authManager: BiometricAuthManager(), style: .forecast) } #Preview("Playful") { LockScreenView(authManager: BiometricAuthManager(), style: .playful) } #Preview("Journal") { LockScreenView(authManager: BiometricAuthManager(), style: .journal) }