// // 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() } 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() } 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 } } 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 ) } 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 } }