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

@ -65,51 +65,98 @@ final class PushNotificationTests: XCTestCase {
}
func testPushManagerRoutesHomeDestinationsThroughHomeNavigationStack() {
let router = AppRouter()
let manager = pushManager(router: router)
let services = AppServices(apiClient: APIClient(session: PushRecordingURLSession()))
let host = PushNavigationHost(services: services)
let manager = pushManager(navigator: services.appNavigator)
services.appNavigator.attach(host: host)
manager.handleRemoteNotification(PushPayload(userInfo: ["route": "payment_qr"]))
XCTAssertEqual(router.selectedTab, .home)
XCTAssertEqual(router.router(for: .home).path, [.home(.paymentCollection)])
XCTAssertEqual(host.selectedTab, .home)
XCTAssertTrue(host.navigationController(for: .home)?.viewControllers.last is PaymentCollectionViewController)
router.router(for: .home).reset()
manager.handleRemoteNotification(PushPayload(userInfo: ["route": "task_management"]))
XCTAssertEqual(router.router(for: .home).path, [.home(.taskManagement)])
XCTAssertTrue(host.navigationController(for: .home)?.viewControllers.last is TaskManagementViewController)
XCTAssertEqual(host.navigationController(for: .home)?.viewControllers.count, 2)
router.router(for: .home).reset()
manager.handleRemoteNotification(PushPayload(userInfo: ["uri": "/scenic-queue"]))
XCTAssertEqual(router.router(for: .home).path, [.home(.queueManagement)])
XCTAssertTrue(host.navigationController(for: .home)?.viewControllers.last is QueueManagementViewController)
XCTAssertEqual(host.navigationController(for: .home)?.viewControllers.count, 2)
router.router(for: .home).reset()
manager.handleRemoteNotification(PushPayload(userInfo: ["title": "未知通知"]))
XCTAssertEqual(router.router(for: .home).path, [.home(.messageCenter)])
XCTAssertTrue(host.navigationController(for: .home)?.viewControllers.last is MessageCenterViewController)
XCTAssertEqual(host.navigationController(for: .home)?.viewControllers.count, 2)
}
func testPushManagerRoutesOrdersEntry() {
let router = AppRouter()
let manager = pushManager(router: router)
let services = AppServices(apiClient: APIClient(session: PushRecordingURLSession()))
let host = PushNavigationHost(services: services)
let manager = pushManager(navigator: services.appNavigator)
services.appNavigator.attach(host: host)
manager.handleRemoteNotification(PushPayload(userInfo: ["route": "photographer_orders"]))
XCTAssertEqual(router.selectedTab, .orders)
XCTAssertEqual(router.selectedOrdersEntry, .storeOrders)
XCTAssertEqual(host.selectedTab, .orders)
XCTAssertEqual(host.appliedOrdersEntry, .storeOrders)
manager.handleRemoteNotification(PushPayload(userInfo: ["route": "verification_order"]))
XCTAssertEqual(router.selectedTab, .orders)
XCTAssertEqual(router.selectedOrdersEntry, .verificationOrders)
XCTAssertEqual(host.selectedTab, .orders)
XCTAssertEqual(host.appliedOrdersEntry, .verificationOrders)
}
private func pushManager(router: AppRouter) -> PushNotificationManager {
private func pushManager(navigator: AppNavigator) -> PushNotificationManager {
let session = AppSession()
let manager = PushNotificationManager()
manager.configure(
api: PushAPI(client: APIClient(session: PushRecordingURLSession())),
session: session,
router: router
navigator: navigator
)
return manager
}
}
/// 宿
@MainActor
private final class PushNavigationHost: AppNavigationHost {
private var controllers: [AppTab: TabNavigationController] = [:]
var selectedTab: AppTab = .home
var appliedOrdersEntry: OrdersEntry?
/// Tab
init(services: AppServices) {
for tab in AppTab.allCases {
controllers[tab] = TabNavigationController(tab: tab, services: services)
}
}
/// Tab
func selectTab(_ tab: AppTab) {
selectedTab = tab
}
/// Tab
func navigationController(for tab: AppTab) -> TabNavigationController? {
controllers[tab]
}
/// Tab
func selectedNavigationController() -> TabNavigationController? {
controllers[selectedTab]
}
/// Tab
func resetAllStacks() {
for controller in controllers.values {
controller.popToRootViewController(animated: false)
}
selectedTab = .home
}
///
func applyOrdersEntry(_ entry: OrdersEntry, scannedCode: String?) {
appliedOrdersEntry = entry
}
}
private final class PushRecordingURLSession: URLSessionProtocol {
private(set) var requests: [URLRequest] = []