SplashViewModel.swift 2.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778
  1. import SwiftUI
  2. @MainActor
  3. @Observable
  4. final class SplashViewModel {
  5. private(set) var isVisible = true
  6. private(set) var loadingProgress: Double = 0
  7. private static var hasCompletedThisSession = false
  8. private var hasStarted = false
  9. private let minimumDisplayDuration: Duration = .seconds(1.35)
  10. init() {
  11. if Self.hasCompletedThisSession {
  12. isVisible = false
  13. loadingProgress = 1
  14. }
  15. }
  16. func runIfNeeded() async {
  17. guard !Self.hasCompletedThisSession, !hasStarted else { return }
  18. hasStarted = true
  19. await run()
  20. Self.hasCompletedThisSession = true
  21. }
  22. private func run() async {
  23. withAnimation(.easeOut(duration: 0.25)) {
  24. loadingProgress = 0.08
  25. }
  26. async let networkPrepare: Void = NetworkClient.prepare()
  27. async let minimumDelay: Void = waitMinimumDuration()
  28. async let progressAnimation: Void = animateLoadingProgress()
  29. _ = await (networkPrepare, minimumDelay, progressAnimation)
  30. withAnimation(.easeOut(duration: 0.22)) {
  31. loadingProgress = 1
  32. }
  33. try? await Task.sleep(for: .milliseconds(150))
  34. withAnimation(.easeInOut(duration: 0.45)) {
  35. isVisible = false
  36. }
  37. }
  38. private func waitMinimumDuration() async {
  39. try? await Task.sleep(for: minimumDisplayDuration)
  40. }
  41. private func animateLoadingProgress() async {
  42. let start: Double = 0.08
  43. let target: Double = 0.92
  44. let duration = 1.35
  45. let steps = 30
  46. let stepDuration = duration / Double(steps)
  47. for step in 1...steps {
  48. if Task.isCancelled { return }
  49. let fraction = Double(step) / Double(steps)
  50. let eased = start + (target - start) * easeOutCubic(fraction)
  51. withAnimation(.linear(duration: stepDuration)) {
  52. loadingProgress = eased
  53. }
  54. try? await Task.sleep(for: .seconds(stepDuration))
  55. }
  56. }
  57. private func easeOutCubic(_ value: Double) -> Double {
  58. 1 - pow(1 - value, 3)
  59. }
  60. }