Integrate CYLTabBar center scan button and unify UIKit navigation.

Add CYLTabBarController with a global scan entry, promote MainTabBarController to the window root after login, replace RouterPath-based navigation with AppNavigator, and fix Profile diffable cell reconfiguration crashes.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-26 16:36:18 +08:00
parent 24a7339b68
commit a1c031c9b7
31 changed files with 838 additions and 517 deletions

View File

@ -7,12 +7,11 @@ import SnapKit
import UIKit
@MainActor
/// ViewController Overlay
/// window `MainTabBarController`
final class RootViewController: UIViewController {
private let services: AppServices
private var currentChild: UIViewController?
private let restoringView = UIView()
private var currentAuthChild: UIViewController?
///
init(services: AppServices = .shared) {
@ -29,21 +28,14 @@ final class RootViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .systemBackground
restoringView.backgroundColor = .systemBackground
services.appSession.onChange = { [weak self] in
self?.handleSessionPhaseChange()
}
mountChild(for: services.appSession.phase)
applyRoot(for: services.appSession.phase)
bootstrapSession()
configurePushNotifications()
}
/// Toast Loading window
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
attachGlobalOverlays()
services.configurePushNotifications()
}
///
@ -66,21 +58,9 @@ final class RootViewController: UIViewController {
}
}
///
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)
}
///
/// window
private func handleSessionPhaseChange() {
mountChild(for: services.appSession.phase)
applyRoot(for: services.appSession.phase)
switch services.appSession.phase {
case .loggedIn:
@ -93,7 +73,8 @@ final class RootViewController: UIViewController {
services.accountContext.reset()
services.permissionContext.reset()
services.scenicSpotContext.reset()
services.appRouter.reset()
services.appNavigator.resetAllStacks()
services.appNavigator.detach()
services.toastCenter.dismiss()
services.scenicQueueRuntime.stop()
case .restoring:
@ -113,31 +94,67 @@ final class RootViewController: UIViewController {
)
}
/// Tab
private func mountChild(for phase: AuthPhase) {
let nextChild: UIViewController
/// window
private func applyRoot(for phase: AuthPhase) {
switch phase {
case .loggedOut:
nextChild = LoginViewController(services: services)
case .restoring:
nextChild = restoringViewController()
case .loggedIn:
nextChild = MainTabBarController(services: services)
showMainTabBarAsWindowRoot()
case .loggedOut:
showAuthRoot(with: LoginViewController(services: services))
case .restoring:
showAuthRoot(with: restoringViewController())
}
}
/// `MainTabBarController` window CYLTabBarController
private func showMainTabBarAsWindowRoot() {
removeCurrentAuthChild()
guard let window = activeWindow else { return }
let mainTabBarController = MainTabBarController(services: services)
window.rootViewController = mainTabBarController
attachGlobalOverlays(to: window)
}
/// /
private func showAuthRoot(with child: UIViewController) {
guard let window = activeWindow else {
mountAuthChild(child)
return
}
guard currentChild !== nextChild else { return }
if window.rootViewController !== self {
window.rootViewController = self
}
currentChild?.willMove(toParent: nil)
currentChild?.view.removeFromSuperview()
currentChild?.removeFromParent()
mountAuthChild(child)
attachGlobalOverlays(to: window)
}
addChild(nextChild)
view.addSubview(nextChild.view)
nextChild.view.snp.makeConstraints { make in
///
private func mountAuthChild(_ child: UIViewController) {
guard currentAuthChild !== child else { return }
currentAuthChild?.willMove(toParent: nil)
currentAuthChild?.view.removeFromSuperview()
currentAuthChild?.removeFromParent()
addChild(child)
view.addSubview(child.view)
child.view.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
nextChild.didMove(toParent: self)
currentChild = nextChild
child.didMove(toParent: self)
currentAuthChild = child
}
///
private func removeCurrentAuthChild() {
currentAuthChild?.willMove(toParent: nil)
currentAuthChild?.view.removeFromSuperview()
currentAuthChild?.removeFromParent()
currentAuthChild = nil
}
///
@ -146,4 +163,18 @@ final class RootViewController: UIViewController {
controller.view.backgroundColor = .systemBackground
return controller
}
/// Toast Loading window
private func attachGlobalOverlays(to window: UIWindow) {
services.toastCenter.attachToWindow(window)
services.globalLoading.attachToWindow(window)
}
/// key window
private var activeWindow: UIWindow? {
view.window ?? UIApplication.shared.connectedScenes
.compactMap { $0 as? UIWindowScene }
.flatMap(\.windows)
.first(where: \.isKeyWindow)
}
}