RunnerApp.swift 3.34 KB
//
//  RunnerApp.swift
//  Runner
//
//  Created by 权海 on 2026/6/11.
//

import SwiftUI
import UIKit

@main
struct RunnerApp: App {
    @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
    @Environment(\.scenePhase) private var scenePhase
    
    var body: some Scene {
        WindowGroup {
            RunnerLaunchView(appDelegate: appDelegate)
        }
        .onChange(of: scenePhase, initial: true) { _, phase in
            guard phase == .active else { return }
            appDelegate.setup()
            HealthKitService.shared.startBackgroundObserversIfNeeded()
            Task {
                await HealthKitService.shared.uploadActivityTargetAfterForeground()
            }
        }
    }
}

private struct RunnerLaunchView: View {
    let appDelegate: AppDelegate

    @State private var showFlutter = false
    @State private var didStart = false

    var body: some View {
        Group {
            if showFlutter {
                if let flutterEngine = appDelegate.flutterEngine{
                    FlutterRootView(engine: flutterEngine)
                        .ignoresSafeArea()
                }else{
                    VStack{
                        Text("Engin init error: \(appDelegate.enginInitError ?? "-")")
                        Button {
                            appDelegate.warmUpFlutterEngineIfNeeded()
                        } label: {
                            Text("Engin 重载")
                        }
                    }
                }
            } else {
                SplashView()
                    .onAppear(perform: startIfNeeded)
            }
        }
        .onAppear {
            appDelegate.setup()
        }.onOpenURL { url in
            appDelegate.handleFlutterUrl(url.absoluteString)
        }
    }

    private func startIfNeeded() {
        guard !didStart else {
            return
        }
        didStart = true

        DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
            appDelegate.warmUpFlutterEngineIfNeeded()
            withAnimation(.easeInOut(duration: 0.2)) {
                showFlutter = true
            }
        }
    }
}

private struct SplashView: View {
    var body: some View {
        ZStack(alignment: .top) {
            Color.white
                .ignoresSafeArea()

            AppLogoView()
                .padding(.top, 96)
        }
    }
}

private struct AppLogoView: View {
    var body: some View {
        if let image = Bundle.main.appIconImage {
            Image(uiImage: image)
                .resizable()
                .scaledToFit()
                .frame(width: 96, height: 96)
                .clipShape(RoundedRectangle(cornerRadius: 22, style: .continuous))
        } else {
            Text("DoubleFeel")
                .font(.system(size: 28, weight: .semibold))
                .foregroundStyle(.primary)
        }
    }
}

private extension Bundle {
    var appIconImage: UIImage? {
        if let image = UIImage(named: "AppIcon") {
            return image
        }

        guard let icons = infoDictionary?["CFBundleIcons"] as? [String: Any],
              let primaryIcon = icons["CFBundlePrimaryIcon"] as? [String: Any],
              let iconFiles = primaryIcon["CFBundleIconFiles"] as? [String],
              let iconName = iconFiles.last else {
            return nil
        }

        return UIImage(named: iconName)
    }
}