// // RootView.swift // suixinkan // // Created by Codex on 2026/6/18. // import SwiftUI /// App 根视图,负责创建全局依赖并根据登录状态切换登录页和主界面。 struct RootView: View { @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 apiClient: APIClient @State private var authAPI: AuthAPI @State private var profileAPI: ProfileAPI @State private var accountContextAPI: AccountContextAPI @State private var ordersAPI: OrdersAPI @State private var statisticsAPI: StatisticsAPI @State private var authSessionCoordinator: AuthSessionCoordinator @State private var sessionBootstrapper: SessionBootstrapper /// 初始化网络客户端和业务 API,确保它们共享同一个 token provider。 init() { let apiClient = APIClient() let tokenStore = SessionTokenStore() let snapshotStore = AccountSnapshotStore() _apiClient = State(initialValue: apiClient) _authAPI = State(initialValue: AuthAPI(client: apiClient)) _profileAPI = State(initialValue: ProfileAPI(client: apiClient)) _accountContextAPI = State(initialValue: AccountContextAPI(client: apiClient)) _ordersAPI = State(initialValue: OrdersAPI(client: apiClient)) _statisticsAPI = State(initialValue: StatisticsAPI(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() .environment(appSession) .environment(accountContext) .environment(permissionContext) .environment(scenicSpotContext) .environment(appRouter) .environment(toastCenter) .environment(apiClient) .environment(authAPI) .environment(profileAPI) .environment(accountContextAPI) .environment(ordersAPI) .environment(statisticsAPI) .environment(authSessionCoordinator) .task { apiClient.bindAuthTokenProvider { appSession.token } 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: 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: ProgressView() .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color(.systemBackground)) 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() }