249 lines
8.8 KiB
Swift
249 lines
8.8 KiB
Swift
//
|
||
// 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 = switch destination {
|
||
case .orders, .verificationOrders:
|
||
.orders
|
||
case .payment, .task, .queue, .messageCenter:
|
||
.home
|
||
}
|
||
|
||
selectTab(targetTab)
|
||
guard let navigationController = tabNavigationControllers[targetTab] else { return }
|
||
_ = navigationController.popToRootViewController(animated: false)
|
||
|
||
let controller: UIViewController?
|
||
switch destination {
|
||
case .payment:
|
||
controller = PaymentCollectionDetailsViewController()
|
||
case .task:
|
||
controller = TaskAddViewController()
|
||
case .queue:
|
||
controller = ScenicQueueViewController()
|
||
case .messageCenter:
|
||
controller = MessageCenterViewController()
|
||
case .orders, .verificationOrders:
|
||
controller = nil
|
||
}
|
||
|
||
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()
|
||
}
|
||
}
|
||
}
|