| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778 |
- import SwiftUI
- @MainActor
- @Observable
- final class SplashViewModel {
- private(set) var isVisible = true
- private(set) var loadingProgress: Double = 0
- private static var hasCompletedThisSession = false
- private var hasStarted = false
- private let minimumDisplayDuration: Duration = .seconds(1.35)
- init() {
- if Self.hasCompletedThisSession {
- isVisible = false
- loadingProgress = 1
- }
- }
- func runIfNeeded() async {
- guard !Self.hasCompletedThisSession, !hasStarted else { return }
- hasStarted = true
- await run()
- Self.hasCompletedThisSession = true
- }
- private func run() async {
- withAnimation(.easeOut(duration: 0.25)) {
- loadingProgress = 0.08
- }
- async let networkPrepare: Void = NetworkClient.prepare()
- async let minimumDelay: Void = waitMinimumDuration()
- async let progressAnimation: Void = animateLoadingProgress()
- _ = await (networkPrepare, minimumDelay, progressAnimation)
- withAnimation(.easeOut(duration: 0.22)) {
- loadingProgress = 1
- }
- try? await Task.sleep(for: .milliseconds(150))
- withAnimation(.easeInOut(duration: 0.45)) {
- isVisible = false
- }
- }
- private func waitMinimumDuration() async {
- try? await Task.sleep(for: minimumDisplayDuration)
- }
- private func animateLoadingProgress() async {
- let start: Double = 0.08
- let target: Double = 0.92
- let duration = 1.35
- let steps = 30
- let stepDuration = duration / Double(steps)
- for step in 1...steps {
- if Task.isCancelled { return }
- let fraction = Double(step) / Double(steps)
- let eased = start + (target - start) * easeOutCubic(fraction)
- withAnimation(.linear(duration: stepDuration)) {
- loadingProgress = eased
- }
- try? await Task.sleep(for: .seconds(stepDuration))
- }
- }
- private func easeOutCubic(_ value: Double) -> Double {
- 1 - pow(1 - value, 3)
- }
- }
|