// // RootView.swift // suixinkan // // Created by Codex on 2026/6/18. // import SwiftUI /// App 根视图,负责创建全局依赖并根据登录状态切换登录页和主界面。 struct RootView: View { @Environment(\.scenePhase) private var scenePhase @State private var appSession = AppSession() @State private var accountContext = AccountContext() @State private var permissionContext = PermissionContext() @State private var scenicSpotContext = ScenicSpotContext() @State private var appRouter = AppRouter() @State private var toastCenter = ToastCenter() @State private var globalLoading = GlobalLoadingCenter() @State private var snapshotStore: AccountSnapshotStore @State private var apiClient: APIClient @State private var authAPI: AuthAPI @State private var profileAPI: ProfileAPI @State private var uploadAPI: UploadAPI @State private var ossUploadService: OSSUploadService @State private var accountContextAPI: AccountContextAPI @State private var ordersAPI: OrdersAPI @State private var statisticsAPI: StatisticsAPI @State private var paymentAPI: PaymentAPI @State private var walletAPI: WalletAPI @State private var scenicPermissionAPI: ScenicPermissionAPI @State private var scenicSettlementAPI: ScenicSettlementAPI @State private var messageCenterAPI: MessageCenterAPI @State private var scenicQueueAPI: ScenicQueueAPI @State private var scenicQueueRuntime = ScenicQueueRuntime() @State private var liveAPI: LiveAPI @State private var taskAPI: TaskAPI @State private var projectAPI: ProjectAPI @State private var scheduleAPI: ScheduleAPI @State private var inviteAPI: InviteAPI @State private var assetsAPI: AssetsAPI @State private var punchPointAPI: PunchPointAPI @State private var locationReportAPI: LocationReportAPI @State private var authSessionCoordinator: AuthSessionCoordinator @State private var sessionBootstrapper: SessionBootstrapper /// 初始化网络客户端和业务 API,确保它们共享同一个 token provider。 init() { let apiClient = APIClient() let tokenStore = SessionTokenStore() let snapshotStore = AccountSnapshotStore() _snapshotStore = State(initialValue: snapshotStore) _apiClient = State(initialValue: apiClient) _authAPI = State(initialValue: AuthAPI(client: apiClient)) _profileAPI = State(initialValue: ProfileAPI(client: apiClient)) let uploadAPI = UploadAPI(client: apiClient) _uploadAPI = State(initialValue: uploadAPI) _ossUploadService = State(initialValue: OSSUploadService(configService: uploadAPI)) _accountContextAPI = State(initialValue: AccountContextAPI(client: apiClient)) _ordersAPI = State(initialValue: OrdersAPI(client: apiClient)) _statisticsAPI = State(initialValue: StatisticsAPI(client: apiClient)) _paymentAPI = State(initialValue: PaymentAPI(client: apiClient)) _walletAPI = State(initialValue: WalletAPI(client: apiClient)) _scenicPermissionAPI = State(initialValue: ScenicPermissionAPI(client: apiClient)) _scenicSettlementAPI = State(initialValue: ScenicSettlementAPI(client: apiClient)) _messageCenterAPI = State(initialValue: MessageCenterAPI(client: apiClient)) _scenicQueueAPI = State(initialValue: ScenicQueueAPI(client: apiClient)) _liveAPI = State(initialValue: LiveAPI(client: apiClient)) _taskAPI = State(initialValue: TaskAPI(client: apiClient)) _projectAPI = State(initialValue: ProjectAPI(client: apiClient)) _scheduleAPI = State(initialValue: ScheduleAPI(client: apiClient)) _inviteAPI = State(initialValue: InviteAPI(client: apiClient)) _assetsAPI = State(initialValue: AssetsAPI(client: apiClient)) _punchPointAPI = State(initialValue: PunchPointAPI(client: apiClient)) _locationReportAPI = State(initialValue: LocationReportAPI(client: apiClient)) _authSessionCoordinator = State( initialValue: AuthSessionCoordinator( tokenStore: tokenStore, snapshotStore: snapshotStore, preferencesStore: AppPreferencesStore() ) ) _sessionBootstrapper = State( initialValue: SessionBootstrapper( tokenStore: tokenStore, snapshotStore: snapshotStore ) ) } var body: some View { rootContent .globalToastOverlay() .globalLoadingOverlay(loadingCenter: globalLoading) .environment(appSession) .environment(accountContext) .environment(permissionContext) .environment(scenicSpotContext) .environment(appRouter) .environment(toastCenter) .environment(\.globalLoading, globalLoading) .environment(snapshotStore) .environment(apiClient) .environment(authAPI) .environment(profileAPI) .environment(uploadAPI) .environment(ossUploadService) .environment(accountContextAPI) .environment(ordersAPI) .environment(statisticsAPI) .environment(paymentAPI) .environment(walletAPI) .environment(scenicPermissionAPI) .environment(scenicSettlementAPI) .environment(messageCenterAPI) .environment(scenicQueueAPI) .environment(scenicQueueRuntime) .environment(liveAPI) .environment(taskAPI) .environment(projectAPI) .environment(scheduleAPI) .environment(inviteAPI) .environment(assetsAPI) .environment(punchPointAPI) .environment(locationReportAPI) .environment(authSessionCoordinator) .task { apiClient.bindAuthTokenProvider { appSession.token } await globalLoading.withLoading { await sessionBootstrapper.restore( appSession: appSession, accountContext: accountContext, permissionContext: permissionContext, profileAPI: profileAPI, accountContextAPI: accountContextAPI, toastCenter: toastCenter ) } } .task(id: scenicSpotTaskID) { guard appSession.isLoggedIn else { scenicSpotContext.reset() return } await scenicSpotContext.reload( scenicId: accountContext.currentScenic?.id, api: accountContextAPI ) } .onChange(of: scenePhase) { _, newPhase in if appSession.isLoggedIn { scenicQueueRuntime.update( api: scenicQueueAPI, userId: accountContext.profile?.userId, scenicId: accountContext.currentScenic?.id, scenePhase: newPhase ) } else { scenicQueueRuntime.stop() } } .onChange(of: accountContext.currentScenic?.id) { _, _ in if appSession.isLoggedIn { scenicQueueRuntime.update( api: scenicQueueAPI, userId: accountContext.profile?.userId, scenicId: accountContext.currentScenic?.id, scenePhase: scenePhase ) } } .onChange(of: appSession.phase) { _, phase in guard phase == .loggedOut else { return } accountContext.reset() permissionContext.reset() scenicSpotContext.reset() appRouter.reset() toastCenter.dismiss() } } @ViewBuilder private var rootContent: some View { switch appSession.phase { case .loggedOut: LoginView() case .restoring: Color(.systemBackground) .frame(maxWidth: .infinity, maxHeight: .infinity) case .loggedIn: MainTabsView() } } /// 景点加载任务的稳定标识,确保登录状态和当前景区变化都会触发任务。 private var scenicSpotTaskID: String { let phaseText: String switch appSession.phase { case .loggedOut: phaseText = "loggedOut" case .restoring: phaseText = "restoring" case .loggedIn: phaseText = "loggedIn" } return "\(phaseText)-\(accountContext.currentScenic?.id ?? 0)" } } #Preview { RootView() }