Files
suixinkan_uikit/suixinkan/UI/MainTab/MainTabBarController.swift
汉秋 1b2637f0ee fix: 推送点击仅按 type 路由到收款页或消息中心。
对齐后端消息类型约定,去掉 route/uri 等兼容字段,并补充联调日志与单测。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-22 18:20:36 +08:00

240 lines
8.5 KiB
Swift
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// MainTabBarController.swift
// suixinkan
//
import UIKit
/// Tab
final class MainTabBarController: UITabBarController {
private let badgeViewModel = MainTabBadgeViewModel()
private var tabNavigationControllers: [AppTab: TabNavigationController] = [:]
private var isSyncingTabSelection = false
private let scanHandler = MainTabScanHandler(api: NetworkServices.shared.orderAPI)
///
var onScanTabSelected: (() -> Void)?
private enum TabSlot: CaseIterable {
case home
case orders
case scan
case statistics
case profile
var appTab: AppTab? {
switch self {
case .home: return .home
case .orders: return .orders
case .scan: return nil
case .statistics: return .statistics
case .profile: return .profile
}
}
static func index(for tab: AppTab) -> Int? {
allCases.firstIndex { $0.appTab == tab }
}
static var scanIndex: Int? {
allCases.firstIndex(of: .scan)
}
}
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
configureTabBarAppearance()
configureTabs()
bindBadgeViewModel()
bindScanHandler()
badgeViewModel.refreshPendingWriteOffCount()
}
/// Tab
func selectTab(_ tab: AppTab) {
guard let index = TabSlot.index(for: tab) else { return }
guard selectedIndex != index else { return }
isSyncingTabSelection = true
selectedIndex = index
isSyncingTabSelection = false
if tab == .orders {
badgeViewModel.refreshPendingWriteOffCount()
}
}
/// Tab
func openPushDestination(_ destination: PushDestination) {
let targetTab = AppTab.home
selectTab(targetTab)
guard let navigationController = tabNavigationControllers[targetTab] else { return }
_ = navigationController.popToRootViewController(animated: false)
let controller: UIViewController?
switch destination {
case .paymentRecord:
controller = PaymentCollectionRecordViewController()
case .paymentDetails:
controller = PaymentCollectionDetailsViewController()
case .messageCenter:
controller = MessageCenterViewController()
}
if let controller {
navigationController.pushViewController(controller, animated: false)
}
}
private func configureTabBarAppearance() {
tabBar.tintColor = AppColor.primary
tabBar.unselectedItemTintColor = AppColor.textTabInactive
tabBar.backgroundColor = AppColor.cardBackground
tabBar.shadowImage = UIImage()
let normalAttributes: [NSAttributedString.Key: Any] = [
.foregroundColor: AppColor.textTabInactive,
.font: UIFont.app(.caption),
]
let selectedAttributes: [NSAttributedString.Key: Any] = [
.foregroundColor: AppColor.primary,
.font: UIFont.app(.captionMedium),
]
let appearance = UITabBarAppearance()
appearance.configureWithOpaqueBackground()
appearance.backgroundColor = AppColor.cardBackground
appearance.shadowColor = AppColor.tabBarShadow
appearance.stackedLayoutAppearance.normal.titleTextAttributes = normalAttributes
appearance.stackedLayoutAppearance.selected.titleTextAttributes = selectedAttributes
tabBar.standardAppearance = appearance
tabBar.scrollEdgeAppearance = appearance
}
private func configureTabs() {
tabNavigationControllers.removeAll()
viewControllers = TabSlot.allCases.map { slot in
if let tab = slot.appTab {
let navigationController = TabNavigationController(tab: tab)
navigationController.tabBarItem = tab.makeTabBarItem()
tabNavigationControllers[tab] = navigationController
return navigationController
}
let scannerPlaceholder = UIViewController()
scannerPlaceholder.tabBarItem = MainScanTabItem.makeTabBarItem()
return scannerPlaceholder
}
}
private func bindBadgeViewModel() {
badgeViewModel.onChange = { [weak self] in
self?.updateOrderBadge()
}
}
private func updateOrderBadge() {
let badgeValue: String?
if let count = badgeViewModel.pendingWriteOffCount, count > 0 {
badgeValue = "\(count)"
} else {
badgeValue = nil
}
tabNavigationControllers[.orders]?.tabBarItem.badgeValue = badgeValue
}
private func bindScanHandler() {
scanHandler.onShowMessage = { [weak self] message in
self?.showScanToast(message)
}
scanHandler.onShowSignIn = { [weak self] response in
guard let self else { return }
OrderSignInAlertController.present(from: self, response: response)
}
scanHandler.onShowWriteOff = { [weak self] response in
guard let self else { return }
WriteOffResultAlertController.present(from: self, response: response)
}
scanHandler.onShowMultiVerifySheet = { [weak self] spots, selected in
self?.presentMultiVerifySheet(spots: spots, selected: selected)
}
scanHandler.onNavigateBindAcquirer = { [weak self] saleUserId in
self?.presentBindAcquirerAfterScan(saleUserId: saleUserId)
}
}
private func presentBindAcquirerAfterScan(saleUserId: Int) {
dismiss(animated: true) { [weak self] in
guard let self else { return }
let controller = BindAcquirerViewController(saleUserId: saleUserId)
self.topNavigationController()?.pushViewController(controller, animated: true)
}
}
private func topNavigationController() -> UINavigationController? {
if let selected = selectedViewController as? UINavigationController {
return selected
}
return selectedViewController?.navigationController
}
private func showScanToast(_ message: String) {
let alert = UIAlertController(title: nil, message: message, preferredStyle: .alert)
present(alert, animated: true)
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
alert.dismiss(animated: true)
}
}
private func presentMultiVerifySheet(spots: [MultiTravelScenicSpotEntity], selected: MultiTravelScenicSpotEntity?) {
let controller = MultiVerifyScenicSpotViewController(spots: spots, selected: selected)
controller.onConfirm = { [weak self] spot in
guard let self, let pending = self.scanHandler.pendingMultiVerify else { return }
Task { await self.scanHandler.confirmMultiVerify(pending: pending, spot: spot) }
}
let nav = UINavigationController(rootViewController: controller)
nav.modalPresentationStyle = .pageSheet
present(nav, animated: true)
}
private func presentGlobalScanner() {
guard presentedViewController == nil else { return }
let scanner = QRCodeScannerViewController()
scanner.onScanResult = { [weak self] result in
guard let self else { return }
Task { await self.scanHandler.handleScanResult(result) }
}
let navigationController = UINavigationController(rootViewController: scanner)
navigationController.modalPresentationStyle = .fullScreen
present(navigationController, animated: true)
}
}
extension MainTabBarController: UITabBarControllerDelegate {
func tabBarController(_ tabBarController: UITabBarController, shouldSelect viewController: UIViewController) -> Bool {
guard viewControllers?.firstIndex(of: viewController) == TabSlot.scanIndex else {
return true
}
if let onScanTabSelected {
onScanTabSelected()
} else {
presentGlobalScanner()
}
return false
}
func tabBarController(_ tabBarController: UITabBarController, didSelect viewController: UIViewController) {
guard !isSyncingTabSelection,
let navigationController = viewController as? TabNavigationController else { return }
if navigationController.appTab == .orders {
badgeViewModel.refreshPendingWriteOffCount()
}
}
}