Files
suixinkan_uikit/suixinkan/UI/Statistics/StatisticsViewController.swift
汉秋 39186cfe25 Add Lottie global loading overlay and fix stuck loading states.
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>
2026-07-06 17:43:13 +08:00

144 lines
4.4 KiB
Swift
Raw 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.

//
// 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)
}
}