SplashScreenView.swift 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. import SwiftUI
  2. struct SplashScreenView: View {
  3. @Bindable var viewModel: SplashViewModel
  4. @Environment(AppearanceManager.self) private var appearanceManager
  5. @State private var contentScale: CGFloat = 0.9
  6. @State private var contentOpacity: Double = 0
  7. @State private var detailsOpacity: Double = 0
  8. @State private var glowPulse = false
  9. var body: some View {
  10. let isDark = appearanceManager.isDarkMode
  11. ZStack {
  12. background(isDark: isDark)
  13. glowOrb(isDark: isDark)
  14. VStack(spacing: 28) {
  15. brandMark
  16. VStack(spacing: 24) {
  17. VStack(spacing: 10) {
  18. title
  19. Text("AI Assistant for Reddit")
  20. .font(.system(size: 14, weight: .medium))
  21. .foregroundStyle(AppTheme.textSecondary)
  22. }
  23. loadingBar
  24. }
  25. .opacity(detailsOpacity)
  26. }
  27. .frame(maxWidth: .infinity, maxHeight: .infinity)
  28. .scaleEffect(contentScale)
  29. .opacity(contentOpacity)
  30. }
  31. .frame(maxWidth: .infinity, maxHeight: .infinity)
  32. .ignoresSafeArea()
  33. .onAppear {
  34. withAnimation(.spring(response: 0.62, dampingFraction: 0.78)) {
  35. contentScale = 1
  36. contentOpacity = 1
  37. }
  38. withAnimation(.easeOut(duration: 0.55).delay(0.12)) {
  39. detailsOpacity = 1
  40. }
  41. withAnimation(.easeInOut(duration: 2.4).repeatForever(autoreverses: true)) {
  42. glowPulse = true
  43. }
  44. }
  45. }
  46. private func background(isDark: Bool) -> some View {
  47. Group {
  48. if isDark {
  49. AppTheme.background
  50. } else {
  51. LinearGradient(
  52. colors: [
  53. Color(hex: 0xF3F0FF),
  54. AppTheme.background
  55. ],
  56. startPoint: .top,
  57. endPoint: .bottom
  58. )
  59. }
  60. }
  61. .ignoresSafeArea()
  62. }
  63. private func glowOrb(isDark: Bool) -> some View {
  64. Circle()
  65. .fill(
  66. RadialGradient(
  67. colors: [
  68. AppTheme.accentPurple.opacity(isDark ? 0.22 : 0.14),
  69. Color.clear
  70. ],
  71. center: .center,
  72. startRadius: 0,
  73. endRadius: 280
  74. )
  75. )
  76. .frame(width: 560, height: 560)
  77. .scaleEffect(glowPulse ? 1.06 : 0.94)
  78. .offset(x: 180, y: -220)
  79. .allowsHitTesting(false)
  80. }
  81. private var brandMark: some View {
  82. ZStack {
  83. RoundedRectangle(cornerRadius: 20, style: .continuous)
  84. .fill(
  85. LinearGradient(
  86. colors: [AppTheme.accentPurpleLight, AppTheme.accentPurple],
  87. startPoint: .topLeading,
  88. endPoint: .bottomTrailing
  89. )
  90. )
  91. .frame(width: 80, height: 80)
  92. .shadow(color: AppTheme.accentPurple.opacity(0.38), radius: 28, y: 10)
  93. Image(systemName: "brain.head.profile")
  94. .font(.system(size: 36, weight: .semibold))
  95. .foregroundStyle(.white)
  96. }
  97. }
  98. private var loadingBar: some View {
  99. VStack(spacing: 12) {
  100. GeometryReader { geometry in
  101. ZStack(alignment: .leading) {
  102. Capsule()
  103. .fill(AppTheme.border)
  104. Capsule()
  105. .fill(
  106. LinearGradient(
  107. colors: [AppTheme.accentPurple, AppTheme.accentPurpleLight],
  108. startPoint: .leading,
  109. endPoint: .trailing
  110. )
  111. )
  112. .frame(width: max(geometry.size.width * viewModel.loadingProgress, geometry.size.height))
  113. }
  114. }
  115. .frame(width: 300, height: 7)
  116. Text("Loading…")
  117. .font(.system(size: 12, weight: .medium))
  118. .foregroundStyle(AppTheme.textTertiary)
  119. }
  120. .frame(maxWidth: .infinity)
  121. }
  122. private var title: some View {
  123. HStack(spacing: 7) {
  124. Text("Reddora")
  125. .foregroundStyle(AppTheme.textPrimary)
  126. Text("AI")
  127. .foregroundStyle(
  128. LinearGradient(
  129. colors: [AppTheme.accentPurple, AppTheme.accentPurpleLight],
  130. startPoint: .leading,
  131. endPoint: .trailing
  132. )
  133. )
  134. }
  135. .font(.system(size: 34, weight: .bold))
  136. .tracking(-0.6)
  137. }
  138. }