// // HomeViewController.swift // suixinkan // import SnapKit import UIKit /// 首页 Tab 根页面,按身份与权限展示信息与入口。 final class HomeViewController: BaseViewController { private let viewModel = HomeViewModel() private let homeAPI = NetworkServices.shared.homeAPI private let scenicHeaderView = HomeScenicHeaderView() private let scrollView = UIScrollView() private let contentStack = UIStackView() private let workStatusCardView = HomeWorkStatusCardView() private let locationReportCardView = HomeLocationReportCardView() private let quickActionsView = HomeQuickActionsView() private let storeCardView = HomeStoreCardView() private let commonAppsGridView = HomeCommonAppsGridView() private var hasInitialized = false private var countdownTimer: Timer? private var activeDialog: HomeDialogKind? override func setupNavigationBar() { navigationController?.setNavigationBarHidden(true, animated: false) } override func setupUI() { view.backgroundColor = UIColor(hex: 0xF5F5F5) contentStack.axis = .vertical contentStack.spacing = 12 scrollView.showsVerticalScrollIndicator = false view.addSubview(scenicHeaderView) view.addSubview(scrollView) scrollView.addSubview(contentStack) contentStack.addArrangedSubview(workStatusCardView) contentStack.addArrangedSubview(locationReportCardView) contentStack.addArrangedSubview(quickActionsView) contentStack.addArrangedSubview(storeCardView) contentStack.addArrangedSubview(commonAppsGridView) } override func setupConstraints() { scenicHeaderView.snp.makeConstraints { make in make.top.equalTo(view.safeAreaLayoutGuide) make.leading.trailing.equalToSuperview() make.height.equalTo(52) } scrollView.snp.makeConstraints { make in make.top.equalTo(scenicHeaderView.snp.bottom) make.leading.trailing.bottom.equalToSuperview() } contentStack.snp.makeConstraints { make in make.edges.equalToSuperview().inset(16) make.width.equalTo(scrollView.snp.width).offset(-32) } } override func bindActions() { viewModel.onStateChange = { [weak self] in Task { @MainActor in self?.applyViewModel() } } viewModel.onShowMessage = { [weak self] message in Task { @MainActor in self?.showToast(message) } } scenicHeaderView.onTap = { [weak self] in self?.presentScenicSelection() } workStatusCardView.onOnlineTap = { [weak self] in self?.viewModel.showOnlineStatusSwitchDialog() } workStatusCardView.onReminderTap = { [weak self] in self?.presentReminderPicker() } locationReportCardView.onReportTap = { [weak self] in Task { await self?.viewModel.manualReportLocation(api: self?.homeAPI ?? NetworkServices.shared.homeAPI) } } quickActionsView.onCollectPayment = { [weak self] in self?.showToast("功能开发中") } quickActionsView.onSubmitTask = { [weak self] in self?.showToast("功能开发中") } quickActionsView.onToggleOnline = { [weak self] in self?.viewModel.showOnlineStatusSwitchDialog() } commonAppsGridView.onMenuSelected = { [weak self] menu in guard let self else { return } HomeRouteHandler.open(uri: menu.uri, from: self, storeItem: self.viewModel.storeItem) } NotificationCenter.default.addObserver( self, selector: #selector(handleAccountDidSwitch), name: NotificationName.accountDidSwitch, object: nil ) NotificationCenter.default.addObserver( self, selector: #selector(handleScenicDidChange), name: NotificationName.scenicDidChange, object: nil ) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) if !hasInitialized { hasInitialized = true Task { await initializeHome() } } else { Task { await viewModel.reloadIfNeeded(api: homeAPI) } } startCountdownTimer() } override func viewWillDisappear(_ animated: Bool) { super.viewWillDisappear(animated) countdownTimer?.invalidate() countdownTimer = nil } deinit { NotificationCenter.default.removeObserver(self) } private func initializeHome() async { showLoading() defer { hideLoading() } await viewModel.initialize(api: homeAPI) applyViewModel() await evaluateDialogsWithDelay() await viewModel.refreshLocationInfo() applyViewModel() } private func evaluateDialogsWithDelay() async { try? await Task.sleep(nanoseconds: 100_000_000) await viewModel.evaluateDialogs(api: homeAPI) applyViewModel() presentDialogsIfNeeded() } @MainActor private func applyViewModel() { scenicHeaderView.apply(scenicName: viewModel.currentScenicName) let showWorkBlock = !viewModel.isMinimalTopRole workStatusCardView.isHidden = !showWorkBlock locationReportCardView.isHidden = !showWorkBlock quickActionsView.isHidden = !showWorkBlock if showWorkBlock { workStatusCardView.apply( isOnline: viewModel.isOnline, countdownText: viewModel.countdownDisplayText, reminderMinutes: viewModel.reminderMinutes ) locationReportCardView.apply( address: viewModel.locationInfoText, isReporting: viewModel.isLocationReporting ) quickActionsView.apply(isOnline: viewModel.isOnline) } let showStore = viewModel.currentAppRole == .storeAdmin && viewModel.storeItem != nil storeCardView.isHidden = !showStore if showStore, let store = viewModel.storeItem { storeCardView.apply(store: store) } commonAppsGridView.apply(menus: viewModel.commonMenus) presentDialogsIfNeeded() } private func presentDialogsIfNeeded() { if viewModel.showPermissionDialog { presentDialog(.permission) return } if viewModel.showScenicDialog { presentDialog(.scenic) return } if viewModel.showOnlineStatusDialog { presentDialog(.onlineStatus) return } if viewModel.showLocationTimeoutDialog { presentDialog(.locationTimeout) return } if viewModel.showLocationReportSuccessDialog { presentDialog(.locationSuccess) return } activeDialog = nil } private enum HomeDialogKind { case permission case scenic case onlineStatus case locationTimeout case locationSuccess } private func presentDialog(_ kind: HomeDialogKind) { guard activeDialog != kind, presentedViewController == nil else { return } activeDialog = kind switch kind { case .permission: presentPermissionDialog() case .scenic: presentScenicDialog() case .onlineStatus: presentOnlineStatusDialog() case .locationTimeout: presentLocationTimeoutDialog() case .locationSuccess: presentLocationSuccessDialog() } } private func presentPermissionDialog() { let alert = UIAlertController( title: "权限提示", message: "您还没有权限,请先申请权限", preferredStyle: .alert ) alert.addAction(UIAlertAction(title: "取消", style: .cancel) { [weak self] _ in self?.viewModel.hidePermissionDialog() self?.activeDialog = nil }) alert.addAction(UIAlertAction(title: "去申请", style: .default) { [weak self] _ in self?.showToast("功能开发中") self?.activeDialog = nil }) present(alert, animated: true) } private func presentScenicDialog() { let alert = UIAlertController( title: "请选择景区", message: "使用首页功能前需要先选择当前景区", preferredStyle: .alert ) alert.addAction(UIAlertAction(title: "稍后", style: .cancel) { [weak self] _ in self?.viewModel.hideScenicDialog() self?.activeDialog = nil }) alert.addAction(UIAlertAction(title: "去选择", style: .default) { [weak self] _ in self?.viewModel.hideScenicDialog() self?.activeDialog = nil self?.presentScenicSelection() }) present(alert, animated: true) } private func presentOnlineStatusDialog() { let goingOnline = !viewModel.isOnline let alert = UIAlertController( title: goingOnline ? "切换在线" : "切换离线", message: goingOnline ? "确认切换到在线状态并上报位置?" : "确认切换到离线状态?", preferredStyle: .alert ) alert.addAction(UIAlertAction(title: "取消", style: .cancel) { [weak self] _ in self?.viewModel.hideOnlineStatusSwitchDialog() self?.activeDialog = nil }) alert.addAction(UIAlertAction(title: "确定", style: .default) { [weak self] _ in guard let self else { return } self.activeDialog = nil Task { await self.viewModel.switchOnlineStatus(api: self.homeAPI) } }) present(alert, animated: true) } private func presentLocationTimeoutDialog() { let alert = UIAlertController( title: "位置上报提醒", message: "您的位置上报即将超时,请立即上报", preferredStyle: .alert ) alert.addAction(UIAlertAction(title: "稍后", style: .cancel) { [weak self] _ in self?.viewModel.hideLocationTimeoutDialog() self?.activeDialog = nil }) alert.addAction(UIAlertAction(title: "立即上报", style: .default) { [weak self] _ in guard let self else { return } self.activeDialog = nil Task { await self.viewModel.confirmLocationTimeoutReport(api: self.homeAPI) } }) present(alert, animated: true) } private func presentLocationSuccessDialog() { let alert = UIAlertController( title: "上报成功", message: "上报时间:\(viewModel.reportTimeText)", preferredStyle: .alert ) alert.addAction(UIAlertAction(title: "确定", style: .default) { [weak self] _ in self?.viewModel.hideLocationReportSuccessDialog() self?.activeDialog = nil }) present(alert, animated: true) } private func presentReminderPicker() { let alert = UIAlertController(title: "提前提醒", message: "选择位置超时提前提醒时间", preferredStyle: .actionSheet) [0, 5, 10, 15, 30].forEach { minutes in let title = minutes == 0 ? "不提醒" : "提前\(minutes)分钟" alert.addAction(UIAlertAction(title: title, style: .default) { [weak self] _ in self?.viewModel.updateReminderMinutes(minutes) }) } alert.addAction(UIAlertAction(title: "取消", style: .cancel)) present(alert, animated: true) } private func presentScenicSelection() { let scenicList = AppStore.shared.roleScenicList() guard !scenicList.isEmpty else { showToast("暂无可选景区") return } let controller = ScenicSelectionViewController(scenicList: scenicList) controller.onSelected = { [weak self] in guard let self else { return } Task { await self.viewModel.reloadIfNeeded(api: self.homeAPI) await self.viewModel.loadStoreListIfNeeded(api: self.homeAPI) self.applyViewModel() await self.evaluateDialogsWithDelay() } } let nav = UINavigationController(rootViewController: controller) nav.modalPresentationStyle = .pageSheet present(nav, animated: true) } private func startCountdownTimer() { countdownTimer?.invalidate() countdownTimer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in guard let self else { return } self.viewModel.triggerLocationTimeoutIfNeeded() if !self.viewModel.isMinimalTopRole { self.workStatusCardView.apply( isOnline: self.viewModel.isOnline, countdownText: self.viewModel.countdownDisplayText, reminderMinutes: self.viewModel.reminderMinutes ) } } } @objc private func handleAccountDidSwitch() { viewModel.markNeedsPermissionReload() Task { await viewModel.reloadIfNeeded(api: homeAPI) applyViewModel() await evaluateDialogsWithDelay() } } @objc private func handleScenicDidChange() { viewModel.refreshLocalDisplayState() Task { await viewModel.loadStoreListIfNeeded(api: homeAPI) applyViewModel() } } }