// // SceneDelegate.swift // suixinkan // import UIKit class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? private var sessionExpiredDialog: SessionExpiredDialogViewController? private var deregistrationCoordinator: StoreAccountDeregistrationRootCoordinator? private var needsForegroundDeregistrationCheck = false func scene( _ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions ) { guard let windowScene = scene as? UIWindowScene else { return } AppNavigationBarAppearance.applyGlobalAppearance() let window = UIWindow(windowScene: windowScene) window.rootViewController = UIViewController() self.window = window (UIApplication.shared.delegate as? AppDelegate)?.window = window let appSession = AppStore.shared.session deregistrationCoordinator = StoreAccountDeregistrationRootCoordinator( window: window, session: appSession, makeAPI: { identity in StoreAccountDeregistrationAPI(client: NetworkServices.shared.apiClient, identity: identity) { identity.matches(session: appSession) } }, makeSubmissionStore: { StoreAccountDeregistrationSubmissionStore(storeUserID: $0) }, makeBusinessRoot: { MainTabBarController() }, setBindingSuspended: { PushNotificationManager.shared.setAccountBindingSuspended($0) }, onBusinessResumed: { PushNotificationManager.shared.handleLoginCompleted() PushNotificationManager.shared.routePendingNotificationIfPossible() } ) PushNotificationManager.shared.attach(window: window) registerNotifications() refreshRootForCurrentSession() window.makeKeyAndVisible() if let response = connectionOptions.notificationResponse { PushNotificationManager.shared.handleNotificationResponse(response) } } func sceneDidDisconnect(_ scene: UIScene) { deregistrationCoordinator?.cancelPendingCheck() deregistrationCoordinator = nil NotificationCenter.default.removeObserver(self) if (UIApplication.shared.delegate as? AppDelegate)?.window === window { (UIApplication.shared.delegate as? AppDelegate)?.window = nil } } func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { guard let url = URLContexts.first?.url else { return } _ = WeChatManager.shared.handleOpenURL(url) } func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { _ = WeChatManager.shared.handleContinueUserActivity(userActivity) } func sceneDidBecomeActive(_ scene: UIScene) { guard accessController == nil, deregistrationCoordinator?.isChecking != true else { return } PushNotificationManager.shared.retryPendingRegistrationUpload() } func sceneDidEnterBackground(_ scene: UIScene) { needsForegroundDeregistrationCheck = true } func sceneWillEnterForeground(_ scene: UIScene) { guard needsForegroundDeregistrationCheck else { return } needsForegroundDeregistrationCheck = false guard sessionExpiredDialog == nil else { return } deregistrationCoordinator?.resumeFromBackground() } private func registerNotifications() { NotificationCenter.default.addObserver( self, selector: #selector(handleStoreDeregistrationRestriction(_:)), name: NotificationName.storeAccountDeregistrationRestricted, object: nil ) NotificationCenter.default.addObserver( self, selector: #selector(handleStoreDeregistrationSubmitted(_:)), name: NotificationName.storeAccountDeregistrationSubmitted, object: nil ) NotificationCenter.default.addObserver( self, selector: #selector(handleSessionDidExpire), name: NotificationName.sessionDidExpire, object: nil ) NotificationCenter.default.addObserver( self, selector: #selector(handleUserDidLogout), name: NotificationName.userDidLogout, object: nil ) NotificationCenter.default.addObserver( self, selector: #selector(handleUserDidLogin), name: NotificationName.userDidLogin, object: nil ) NotificationCenter.default.addObserver( self, selector: #selector(handleAccountDidSwitch), name: NotificationName.accountDidSwitch, object: nil ) } @objc private func handleSessionDidExpire() { guard AppStore.shared.session.isLoggedIn else { return } guard sessionExpiredDialog == nil else { return } deregistrationCoordinator?.cancelPendingCheck() GlobalLoadingManager.shared.hideAll() let dialog = SessionExpiredDialogViewController { [weak self] in self?.transitionToLogin() } sessionExpiredDialog = dialog guard let presenter = topViewController(from: window?.rootViewController) else { sessionExpiredDialog = nil return } presenter.present(dialog, animated: true) } private func transitionToLogin() { clearAuthenticatedSession() AppRouter.setRoot(.login, on: window) } /// 统一清除当前会话及依赖登录态的后台能力,不执行页面跳转。 private func clearAuthenticatedSession() { deregistrationCoordinator?.cancelPendingCheck() sessionExpiredDialog?.dismiss(animated: false) sessionExpiredDialog = nil GlobalLoadingManager.shared.hideAll() PushNotificationManager.shared.handleLogout() PushNotificationManager.shared.setAccountBindingSuspended(false) AppStore.shared.logout() } @objc private func handleUserDidLogout() { transitionToLogin() } /// 申请明确受理后先退出,再展示只含“完成”的结果页;结果页不再依赖已清除的凭证。 @objc private func handleStoreDeregistrationSubmitted(_ notification: Notification) { let identityName = notification.userInfo?[NotificationUserInfoKey.deregistrationIdentityName] as? String ?? "当前门店身份" let coolingUntil = notification.userInfo?[NotificationUserInfoKey.deregistrationCoolingUntil] as? String clearAuthenticatedSession() guard !AppStore.shared.session.isLoggedIn else { AppRouter.setRoot(.login, on: window) return } let controller = StoreAccountDeregistrationSubmittedViewController( identityName: identityName, coolingUntil: coolingUntil ) { [weak self] in AppRouter.setRoot(.login, on: self?.window) } AppRouter.setRoot(UINavigationController(rootViewController: controller), on: window) } @objc private func handleUserDidLogin() { sessionExpiredDialog?.dismiss(animated: false) sessionExpiredDialog = nil // v9 登录响应中的 store_users[].status 是登录是否需要注销确认的唯一依据。 // 旧 account-deregister/status 仅供用户主动进入注销设置流程时查询,不能再拦截正常登录。 refreshRootForCurrentSession() } @objc private func handleAccountDidSwitch() { if AppStore.shared.session.accountType == .storeUser { refreshRootForCurrentSession() } else { deregistrationCoordinator?.cancelPendingCheck() PushNotificationManager.shared.setAccountBindingSuspended(false) PushNotificationManager.shared.handleAccountSwitched() } } /// 仅检查真实根控制器,避免被旧的异步查询或页面导航状态误判。 private var accessController: StoreAccountDeregistrationAccessViewController? { deregistrationCoordinator?.accessController } /// 冷启动和切换身份使用已有会话,不主动查询注销状态;新登录由独立入口核验。 private func refreshRootForCurrentSession() { let session = AppStore.shared.session guard session.isLoggedIn else { deregistrationCoordinator?.cancelPendingCheck() PushNotificationManager.shared.setAccountBindingSuspended(false) AppRouter.setRoot(.login, on: window, animated: false) return } deregistrationCoordinator?.restoreSession() } /// 150015 只限制原请求对应的当前门店身份;旧 Token 响应不会影响新身份。 @objc private func handleStoreDeregistrationRestriction(_ notification: Notification) { let token = notification.userInfo?[NotificationUserInfoKey.deregistrationRequestToken] as? String let session = AppStore.shared.session guard StoreAccountDeregistrationAccessViewModel.restrictionApplies(requestToken: token, session: session) else { return } deregistrationCoordinator?.recordRestriction(requestToken: token) } private func topViewController(from viewController: UIViewController?) -> UIViewController? { guard let viewController else { return nil } if let presented = viewController.presentedViewController { return topViewController(from: presented) } if let navigationController = viewController as? UINavigationController { return topViewController(from: navigationController.visibleViewController) } if let tabBarController = viewController as? UITabBarController { return topViewController(from: tabBarController.selectedViewController) } return viewController } }