Files
suixinkan_uikit/suixinkan/UI/MainTab/MainTabBarController.swift

217 lines
7.7 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()
}
}
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()
}
}
}