Replace per-VC activity indicators with a ref-counted GlobalLoadingManager, and resolve duplicate show/hide calls on Profile, Statistics, and Home tabs. Co-authored-by: Cursor <cursoragent@cursor.com>
144 lines
4.4 KiB
Swift
144 lines
4.4 KiB
Swift
//
|
||
// StatisticsViewController.swift
|
||
// suixinkan
|
||
//
|
||
|
||
import SnapKit
|
||
import UIKit
|
||
|
||
/// 数据 Tab 根页面,展示汇总指标与日明细列表。
|
||
final class StatisticsViewController: BaseViewController {
|
||
|
||
private let viewModel = StatisticsViewModel()
|
||
private let statisticsAPI = NetworkServices.shared.statisticsAPI
|
||
|
||
private let summaryCardView = StatisticsSummaryCardView()
|
||
private let dailyListView = StatisticsDailyListView()
|
||
|
||
private var hasInitialized = false
|
||
private var isLoadingMore = false
|
||
|
||
override func setupNavigationBar() {
|
||
title = "数据"
|
||
}
|
||
|
||
override func setupUI() {
|
||
view.backgroundColor = UIColor(hex: 0xF7FAFF)
|
||
|
||
view.addSubview(summaryCardView)
|
||
view.addSubview(dailyListView)
|
||
}
|
||
|
||
override func setupConstraints() {
|
||
summaryCardView.snp.makeConstraints { make in
|
||
make.top.equalTo(view.safeAreaLayoutGuide).offset(16)
|
||
make.leading.trailing.equalToSuperview().inset(16)
|
||
}
|
||
dailyListView.snp.makeConstraints { make in
|
||
make.top.equalTo(summaryCardView.snp.bottom).offset(16)
|
||
make.leading.trailing.equalToSuperview().inset(16)
|
||
make.bottom.equalTo(view.safeAreaLayoutGuide).inset(16)
|
||
}
|
||
}
|
||
|
||
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) }
|
||
}
|
||
|
||
summaryCardView.onPeriodSelected = { [weak self] period in
|
||
self?.selectPeriod(period)
|
||
}
|
||
dailyListView.onRangeTap = { [weak self] in
|
||
self?.presentDateRangeSheet()
|
||
}
|
||
dailyListView.onRefresh = { [weak self] in
|
||
self?.refreshList()
|
||
}
|
||
dailyListView.onLoadMore = { [weak self] in
|
||
self?.loadMoreIfNeeded()
|
||
}
|
||
}
|
||
|
||
override func viewDidAppear(_ animated: Bool) {
|
||
super.viewDidAppear(animated)
|
||
guard !hasInitialized else { return }
|
||
hasInitialized = true
|
||
Task { await initializeStatistics() }
|
||
}
|
||
|
||
private func applyViewModel() {
|
||
summaryCardView.apply(
|
||
selectedPeriod: viewModel.selectedPeriod,
|
||
selectedPeriodTime: viewModel.selectedPeriodTime,
|
||
summary: viewModel.statistics
|
||
)
|
||
dailyListView.apply(
|
||
rangeText: viewModel.statisticsRangeText,
|
||
items: viewModel.statisticsList,
|
||
isRefreshing: viewModel.isRefreshing,
|
||
canLoadMore: viewModel.canLoadMore
|
||
)
|
||
|
||
if viewModel.isLoadingSummary {
|
||
showLoading()
|
||
} else {
|
||
hideLoading()
|
||
}
|
||
}
|
||
|
||
private func initializeStatistics() async {
|
||
await viewModel.initStatistics(api: statisticsAPI)
|
||
applyViewModel()
|
||
}
|
||
|
||
private func selectPeriod(_ period: String) {
|
||
Task {
|
||
await viewModel.selectPeriod(period, api: statisticsAPI)
|
||
}
|
||
}
|
||
|
||
private func refreshList() {
|
||
Task { await viewModel.refreshStatistics(api: statisticsAPI) }
|
||
}
|
||
|
||
private func loadMoreIfNeeded() {
|
||
guard viewModel.canLoadMore, !isLoadingMore else { return }
|
||
isLoadingMore = true
|
||
Task {
|
||
await viewModel.loadMore(api: statisticsAPI)
|
||
isLoadingMore = false
|
||
}
|
||
}
|
||
|
||
private func presentDateRangeSheet() {
|
||
let controller = StatisticsDateRangeViewController(
|
||
rangeStart: viewModel.rangeStartDate,
|
||
rangeEnd: viewModel.rangeEndDate
|
||
)
|
||
controller.onShortcutSelected = { [weak self, weak controller] shortcut in
|
||
guard let self else { return }
|
||
Task {
|
||
await self.viewModel.updateRangeByShortcut(shortcut, api: self.statisticsAPI)
|
||
controller?.dismiss(animated: true)
|
||
}
|
||
}
|
||
controller.onRangeSelected = { [weak self, weak controller] start, end in
|
||
guard let self else { return }
|
||
Task {
|
||
await self.viewModel.updateRangeByCalendar(start: start, end: end, api: self.statisticsAPI)
|
||
controller?.dismiss(animated: true)
|
||
}
|
||
}
|
||
|
||
if let sheet = controller.sheetPresentationController {
|
||
sheet.detents = [.medium(), .large()]
|
||
sheet.prefersGrabberVisible = true
|
||
}
|
||
present(controller, animated: true)
|
||
}
|
||
}
|