Files
suixinkan_uikit/suixinkan/UI/Statistics/StatisticsViewController.swift

144 lines
4.5 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 = AppColor.pageBackgroundSoft
view.addSubview(summaryCardView)
view.addSubview(dailyListView)
}
override func setupConstraints() {
summaryCardView.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide).offset(AppSpacing.md)
make.leading.trailing.equalToSuperview().inset(AppSpacing.screenHorizontalInset)
}
dailyListView.snp.makeConstraints { make in
make.top.equalTo(summaryCardView.snp.bottom).offset(AppSpacing.md)
make.leading.trailing.equalToSuperview().inset(AppSpacing.screenHorizontalInset)
make.bottom.equalTo(view.safeAreaLayoutGuide).inset(AppSpacing.md)
}
}
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)
}
}