// // RootViewController.swift // suixinkan // import SnapKit import UIKit @MainActor /// 应用根 ViewController,根据登录阶段切换子页面并挂载全局 Overlay。 final class RootViewController: UIViewController { private let services: AppServices private var currentChild: UIViewController? private let restoringView = UIView() /// 初始化实例。 init(services: AppServices = .shared) { self.services = services super.init(nibName: nil, bundle: nil) } @available(*, unavailable) required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } /// 初始化视图、监听登录阶段变化并启动会话恢复。 override func viewDidLoad() { super.viewDidLoad() view.backgroundColor = .systemBackground restoringView.backgroundColor = .systemBackground services.appSession.onChange = { [weak self] in self?.handleSessionPhaseChange() } mountChild(for: services.appSession.phase) bootstrapSession() configurePushNotifications() } /// 窗口展示后将 Toast 与全局 Loading 挂载到 window 层级。 override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) attachGlobalOverlays() } /// 冷启动时恢复本地会话,并在已登录时申请推送权限。 private func bootstrapSession() { Task { await services.globalLoading.withLoading { await services.sessionBootstrapper.restore( appSession: services.appSession, accountContext: services.accountContext, permissionContext: services.permissionContext, profileAPI: services.profileAPI, accountContextAPI: services.accountContextAPI, toastCenter: services.toastCenter ) } if services.appSession.isLoggedIn, !AppUITestLaunchState.isRunningUITests { PushNotificationManager.shared.requestAuthorizationAndRegister() } } } /// 配置推送管理器与路由跳转回调。 private func configurePushNotifications() { services.configurePushNotifications() } /// 将全局 Toast 与 Loading 视图附加到当前 window。 private func attachGlobalOverlays() { guard let window = view.window else { return } services.toastCenter.attachToWindow(window) services.globalLoading.attachToWindow(window) } /// 登录阶段变化时切换子页面,并在登出时清理各上下文状态。 private func handleSessionPhaseChange() { mountChild(for: services.appSession.phase) switch services.appSession.phase { case .loggedIn: if !AppUITestLaunchState.isRunningUITests { PushNotificationManager.shared.requestAuthorizationAndRegister() } Task { await reloadScenicSpotContextIfNeeded() } case .loggedOut: // 登出后重置账号、权限、路由与排队运行时,避免残留旧状态 services.accountContext.reset() services.permissionContext.reset() services.scenicSpotContext.reset() services.appRouter.reset() services.toastCenter.dismiss() services.scenicQueueRuntime.stop() case .restoring: break } } /// 登录成功后按当前景区 ID 刷新景区上下文。 private func reloadScenicSpotContextIfNeeded() async { guard services.appSession.isLoggedIn else { services.scenicSpotContext.reset() return } await services.scenicSpotContext.reload( scenicId: services.accountContext.currentScenic?.id, api: services.accountContextAPI ) } /// 按登录阶段挂载对应子控制器:登录页、恢复占位或主 Tab。 private func mountChild(for phase: AuthPhase) { let nextChild: UIViewController switch phase { case .loggedOut: nextChild = LoginViewController(services: services) case .restoring: nextChild = restoringViewController() case .loggedIn: nextChild = MainTabBarController(services: services) } guard currentChild !== nextChild else { return } currentChild?.willMove(toParent: nil) currentChild?.view.removeFromSuperview() currentChild?.removeFromParent() addChild(nextChild) view.addSubview(nextChild.view) nextChild.view.snp.makeConstraints { make in make.edges.equalToSuperview() } nextChild.didMove(toParent: self) currentChild = nextChild } /// 会话恢复阶段的空白占位页,避免闪烁登录页。 private func restoringViewController() -> UIViewController { let controller = UIViewController() controller.view.backgroundColor = .systemBackground return controller } }