|
|
@@ -0,0 +1,152 @@
|
|
|
+import SwiftUI
|
|
|
+
|
|
|
+struct SplashScreenView: View {
|
|
|
+ @Bindable var viewModel: SplashViewModel
|
|
|
+ @Environment(AppearanceManager.self) private var appearanceManager
|
|
|
+ @State private var contentScale: CGFloat = 0.9
|
|
|
+ @State private var contentOpacity: Double = 0
|
|
|
+ @State private var detailsOpacity: Double = 0
|
|
|
+ @State private var glowPulse = false
|
|
|
+
|
|
|
+ var body: some View {
|
|
|
+ let isDark = appearanceManager.isDarkMode
|
|
|
+
|
|
|
+ ZStack {
|
|
|
+ background(isDark: isDark)
|
|
|
+ glowOrb(isDark: isDark)
|
|
|
+
|
|
|
+ VStack(spacing: 28) {
|
|
|
+ brandMark
|
|
|
+
|
|
|
+ VStack(spacing: 24) {
|
|
|
+ VStack(spacing: 10) {
|
|
|
+ title
|
|
|
+ Text("AI Assistant for Reddit")
|
|
|
+ .font(.system(size: 14, weight: .medium))
|
|
|
+ .foregroundStyle(AppTheme.textSecondary)
|
|
|
+ }
|
|
|
+
|
|
|
+ loadingBar
|
|
|
+ }
|
|
|
+ .opacity(detailsOpacity)
|
|
|
+ }
|
|
|
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
|
+ .scaleEffect(contentScale)
|
|
|
+ .opacity(contentOpacity)
|
|
|
+ }
|
|
|
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
|
+ .ignoresSafeArea()
|
|
|
+ .onAppear {
|
|
|
+ withAnimation(.spring(response: 0.62, dampingFraction: 0.78)) {
|
|
|
+ contentScale = 1
|
|
|
+ contentOpacity = 1
|
|
|
+ }
|
|
|
+ withAnimation(.easeOut(duration: 0.55).delay(0.12)) {
|
|
|
+ detailsOpacity = 1
|
|
|
+ }
|
|
|
+ withAnimation(.easeInOut(duration: 2.4).repeatForever(autoreverses: true)) {
|
|
|
+ glowPulse = true
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private func background(isDark: Bool) -> some View {
|
|
|
+ Group {
|
|
|
+ if isDark {
|
|
|
+ AppTheme.background
|
|
|
+ } else {
|
|
|
+ LinearGradient(
|
|
|
+ colors: [
|
|
|
+ Color(hex: 0xF3F0FF),
|
|
|
+ AppTheme.background
|
|
|
+ ],
|
|
|
+ startPoint: .top,
|
|
|
+ endPoint: .bottom
|
|
|
+ )
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .ignoresSafeArea()
|
|
|
+ }
|
|
|
+
|
|
|
+ private func glowOrb(isDark: Bool) -> some View {
|
|
|
+ Circle()
|
|
|
+ .fill(
|
|
|
+ RadialGradient(
|
|
|
+ colors: [
|
|
|
+ AppTheme.accentPurple.opacity(isDark ? 0.22 : 0.14),
|
|
|
+ Color.clear
|
|
|
+ ],
|
|
|
+ center: .center,
|
|
|
+ startRadius: 0,
|
|
|
+ endRadius: 280
|
|
|
+ )
|
|
|
+ )
|
|
|
+ .frame(width: 560, height: 560)
|
|
|
+ .scaleEffect(glowPulse ? 1.06 : 0.94)
|
|
|
+ .offset(x: 180, y: -220)
|
|
|
+ .allowsHitTesting(false)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var brandMark: some View {
|
|
|
+ ZStack {
|
|
|
+ RoundedRectangle(cornerRadius: 20, style: .continuous)
|
|
|
+ .fill(
|
|
|
+ LinearGradient(
|
|
|
+ colors: [AppTheme.accentPurpleLight, AppTheme.accentPurple],
|
|
|
+ startPoint: .topLeading,
|
|
|
+ endPoint: .bottomTrailing
|
|
|
+ )
|
|
|
+ )
|
|
|
+ .frame(width: 80, height: 80)
|
|
|
+ .shadow(color: AppTheme.accentPurple.opacity(0.38), radius: 28, y: 10)
|
|
|
+
|
|
|
+ Image(systemName: "brain.head.profile")
|
|
|
+ .font(.system(size: 36, weight: .semibold))
|
|
|
+ .foregroundStyle(.white)
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private var loadingBar: some View {
|
|
|
+ VStack(spacing: 12) {
|
|
|
+ GeometryReader { geometry in
|
|
|
+ ZStack(alignment: .leading) {
|
|
|
+ Capsule()
|
|
|
+ .fill(AppTheme.border)
|
|
|
+
|
|
|
+ Capsule()
|
|
|
+ .fill(
|
|
|
+ LinearGradient(
|
|
|
+ colors: [AppTheme.accentPurple, AppTheme.accentPurpleLight],
|
|
|
+ startPoint: .leading,
|
|
|
+ endPoint: .trailing
|
|
|
+ )
|
|
|
+ )
|
|
|
+ .frame(width: max(geometry.size.width * viewModel.loadingProgress, geometry.size.height))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ .frame(width: 300, height: 7)
|
|
|
+
|
|
|
+ Text("Loading…")
|
|
|
+ .font(.system(size: 12, weight: .medium))
|
|
|
+ .foregroundStyle(AppTheme.textTertiary)
|
|
|
+ }
|
|
|
+ .frame(maxWidth: .infinity)
|
|
|
+ }
|
|
|
+
|
|
|
+ private var title: some View {
|
|
|
+ HStack(spacing: 7) {
|
|
|
+ Text("Reddora")
|
|
|
+ .foregroundStyle(AppTheme.textPrimary)
|
|
|
+ Text("AI")
|
|
|
+ .foregroundStyle(
|
|
|
+ LinearGradient(
|
|
|
+ colors: [AppTheme.accentPurple, AppTheme.accentPurpleLight],
|
|
|
+ startPoint: .leading,
|
|
|
+ endPoint: .trailing
|
|
|
+ )
|
|
|
+ )
|
|
|
+ }
|
|
|
+ .font(.system(size: 34, weight: .bold))
|
|
|
+ .tracking(-0.6)
|
|
|
+ }
|
|
|
+}
|