Files
suixinkan_ios_uikit/suixinkan_ios/Features/Invite/ViewControllers/InviteViewControllers.swift
汉秋 24a7339b68 Refactor AppServices into grouped bundles and document DI conventions.
Centralize dependency wiring with Session/Context/Network/UI/Runtime bundles, unify leaf ViewControllers on appServices, and add a test initializer with AppServicesTests.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-26 15:24:25 +08:00

160 lines
5.7 KiB
Swift

//
// InviteViewControllers.swift
// suixinkan
//
// Created by Codex on 2026/6/26.
//
import SnapKit
import UIKit
///
final class PhotographerInviteViewController: UIViewController {
private let viewModel = PhotographerInviteViewModel()
private let scrollView = UIScrollView()
private let contentStack = UIStackView()
private let codeLabel = UILabel()
private let urlLabel = UILabel()
private let qrImageView = UIImageView()
private let rulesLabel = UILabel()
private let activityIndicator = UIActivityIndicatorView(style: .medium)
/// UI
override func viewDidLoad() {
super.viewDidLoad()
title = "邀请摄影师"
view.backgroundColor = UIColor(hex: 0xF5F7FA)
setupUI()
viewModel.onChange = { [weak self] in self?.render() }
Task { await viewModel.reload(api: appServices.inviteAPI) }
}
/// UI UI
private func setupUI() {
navigationItem.rightBarButtonItems = [
UIBarButtonItem(title: "复制码", style: .plain, target: self, action: #selector(copyCode)),
UIBarButtonItem(title: "复制链接", style: .plain, target: self, action: #selector(copyURL))
]
contentStack.axis = .vertical
contentStack.spacing = 12
scrollView.addSubview(contentStack)
view.addSubview(scrollView)
view.addSubview(activityIndicator)
scrollView.snp.makeConstraints { $0.edges.equalToSuperview() }
contentStack.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(16)
make.width.equalTo(scrollView.snp.width).offset(-32)
}
activityIndicator.snp.makeConstraints { $0.center.equalToSuperview() }
codeLabel.font = .systemFont(ofSize: 22, weight: .bold)
urlLabel.font = .systemFont(ofSize: 14)
urlLabel.numberOfLines = 0
urlLabel.textColor = AppDesign.textSecondary
qrImageView.contentMode = .scaleAspectFit
qrImageView.snp.makeConstraints { $0.height.equalTo(220) }
rulesLabel.numberOfLines = 0
rulesLabel.font = .systemFont(ofSize: 14)
rulesLabel.textColor = AppDesign.textSecondary
contentStack.addArrangedSubview(codeLabel)
contentStack.addArrangedSubview(urlLabel)
contentStack.addArrangedSubview(qrImageView)
contentStack.addArrangedSubview(rulesLabel)
}
/// render
private func render() {
activityIndicator.isHidden = !viewModel.loading
if viewModel.loading { activityIndicator.startAnimating() } else { activityIndicator.stopAnimating() }
codeLabel.text = viewModel.inviteCode.isEmpty ? "邀请码加载中..." : "邀请码:\(viewModel.inviteCode)"
urlLabel.text = viewModel.inviteUrl
qrImageView.image = viewModel.qrImage
rulesLabel.text = viewModel.rules.joined(separator: "\n")
if let error = viewModel.errorMessage {
rulesLabel.text = (rulesLabel.text ?? "") + "\n\n" + error
}
}
///
@objc private func copyCode() { viewModel.copyInviteCode() }
///
@objc private func copyURL() { viewModel.copyInviteUrl() }
}
extension PhotographerInviteViewModel: ViewModelBindable {}
///
final class InviteRecordViewController: ModuleTableViewController {
private let viewModel = InviteRecordViewModel()
private let summaryLabel = UILabel()
/// UI
override func viewDidLoad() {
title = "邀请记录"
navigationItem.rightBarButtonItem = UIBarButtonItem(
title: "奖励明细",
style: .plain,
target: self,
action: #selector(toggleTab)
)
super.viewDidLoad()
setupHeader()
viewModel.onChange = { [weak self] in
self?.updateSummary()
self?.reloadTable()
}
}
/// Header UI
private func setupHeader() {
summaryLabel.font = .systemFont(ofSize: 14)
summaryLabel.textColor = AppDesign.textSecondary
summaryLabel.numberOfLines = 0
summaryLabel.textAlignment = .center
summaryLabel.frame = CGRect(x: 0, y: 0, width: view.bounds.width, height: 56)
tableView.tableHeaderView = summaryLabel
}
/// tableCount
override func tableRowCount() -> Int {
viewModel.displayRows.count
}
/// Cell
override func configureCell(_ cell: TitleSubtitleTableViewCell, at indexPath: IndexPath) {
let row = viewModel.displayRows[indexPath.row]
cell.configure(
title: row.title,
subtitle: row.subtitle,
detail: row.amount.isEmpty ? row.time : "\(row.amount) · \(row.time)"
)
}
/// Content
override func reloadContent() async {
await viewModel.reload(
inviteAPI: services.inviteAPI,
walletAPI: services.walletAPI,
refresh: true
)
updateSummary()
}
/// Summary
private func updateSummary() {
summaryLabel.text = "累计奖励 \(viewModel.totalRewardText) · 可提现 \(viewModel.withdrawableText)"
navigationItem.rightBarButtonItem?.title = viewModel.tab == .invite ? "奖励明细" : "邀请用户"
}
/// toggleTab
@objc private func toggleTab() {
Task {
let next: InviteRecordTab = viewModel.tab == .invite ? .reward : .invite
await viewModel.selectTab(next, inviteAPI: services.inviteAPI, walletAPI: services.walletAPI)
}
}
}