Files
suixinkan_uikit/suixinkan/UI/WildPhotographerReport/WildPhotographerReportSubmitViewController.swift

331 lines
15 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.

//
// WildPhotographerReportSubmitViewController.swift
// suixinkan
//
import SnapKit
import UIKit
///
final class WildPhotographerReportSubmitViewController: BaseViewController {
private let viewModel: WildPhotographerReportSubmitViewModel
private let homeViewModel: WildPhotographerReportHomeViewModel
private let scrollView = UIScrollView()
private let contentStack = UIStackView()
private let descriptionView = UITextView()
private let contactField = UITextField()
private let submitButton = AppButton(title: "提交举报")
init(
homeViewModel: WildPhotographerReportHomeViewModel,
locationProvider: any LocationProviding = LocationProvider.shared
) {
self.homeViewModel = homeViewModel
self.viewModel = WildPhotographerReportSubmitViewModel(
homeViewModel: homeViewModel,
locationProvider: locationProvider
)
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setupNavigationBar() {
title = "提交举报"
}
override func setupUI() {
view.backgroundColor = AppColor.pageBackgroundSoft
scrollView.keyboardDismissMode = .interactive
contentStack.axis = .vertical
contentStack.spacing = AppSpacing.sm
descriptionView.font = .app(.body)
descriptionView.textColor = AppColor.textPrimary
descriptionView.backgroundColor = AppColor.inputBackground
descriptionView.layer.cornerRadius = AppRadius.md
descriptionView.textContainerInset = UIEdgeInsets(top: 12, left: 10, bottom: 12, right: 10)
descriptionView.delegate = self
contactField.placeholder = "微信号或手机号(选填)"
contactField.font = .app(.body)
contactField.textColor = AppColor.textPrimary
contactField.backgroundColor = AppColor.inputBackground
contactField.layer.cornerRadius = AppRadius.md
contactField.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 12, height: 1))
contactField.leftViewMode = .always
contactField.addTarget(self, action: #selector(contactChanged), for: .editingChanged)
submitButton.addTarget(self, action: #selector(submitTapped), for: .touchUpInside)
view.addSubview(scrollView)
scrollView.addSubview(contentStack)
view.addSubview(submitButton)
rebuildContent()
}
override func setupConstraints() {
submitButton.snp.makeConstraints { make in
make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
make.bottom.equalTo(view.safeAreaLayoutGuide).inset(AppSpacing.sm)
}
scrollView.snp.makeConstraints { make in
make.top.leading.trailing.equalTo(view.safeAreaLayoutGuide)
make.bottom.equalTo(submitButton.snp.top).offset(-AppSpacing.sm)
}
contentStack.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(UIEdgeInsets(top: AppSpacing.sm, left: AppSpacing.md, bottom: AppSpacing.lg, right: AppSpacing.md))
make.width.equalTo(scrollView.snp.width).offset(-AppSpacing.md * 2)
}
}
override func bindActions() {
viewModel.onStateChange = { [weak self] in
Task { @MainActor in self?.rebuildContent() }
}
viewModel.onShowMessage = { [weak self] message in
Task { @MainActor in self?.showToast(message) }
}
viewModel.onSubmitSuccess = { [weak self] result in
Task { @MainActor in
guard let self else { return }
self.navigationController?.pushViewController(
WildPhotographerReportSuccessViewController(
result: result,
homeViewModel: self.homeViewModel
),
animated: true
)
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
viewModel.loadInitialLocationIfNeeded()
}
@MainActor
private func rebuildContent() {
contentStack.arrangedSubviews.forEach { view in
contentStack.removeArrangedSubview(view)
view.removeFromSuperview()
}
contentStack.addArrangedSubview(makeTipsCard())
contentStack.addArrangedSubview(makeTypeCard())
contentStack.addArrangedSubview(makeEvidenceCard())
contentStack.addArrangedSubview(makeDescriptionCard())
contentStack.addArrangedSubview(makeLocationCard())
contentStack.addArrangedSubview(makeContactCard())
contentStack.addArrangedSubview(makePaymentCard())
}
private func makeTipsCard() -> UIView {
let card = WildReportCardView(spacing: AppSpacing.xs)
card.stack.addArrangedSubview(WildReportUI.label("实名登录后提交,景区公安将接收并处理", font: .app(.bodyMedium), color: AppColor.textPrimary, lines: 0))
card.stack.addArrangedSubview(WildReportUI.label("请描述摄影师位置、外貌特征和具体行为。", font: .app(.caption), color: AppColor.textSecondary, lines: 0))
return card
}
private func makeTypeCard() -> UIView {
let card = sectionCard(index: 1, title: "选择举报类型")
let row = UIStackView()
row.axis = .vertical
row.spacing = AppSpacing.xs
WildReportType.allCases.forEach { type in
let button = UIButton(type: .system)
button.contentHorizontalAlignment = .left
button.titleLabel?.font = .app(.bodyMedium)
button.setTitle(" \(type.rawValue)", for: .normal)
button.setImage(UIImage(systemName: type == viewModel.selectedReportType ? "largecircle.fill.circle" : "circle"), for: .normal)
button.tintColor = AppColor.primary
button.setTitleColor(AppColor.textPrimary, for: .normal)
button.addAction(UIAction { [weak self] _ in self?.viewModel.selectReportType(type) }, for: .touchUpInside)
button.snp.makeConstraints { make in make.height.equalTo(38) }
row.addArrangedSubview(button)
}
card.stack.addArrangedSubview(row)
return card
}
private func makeEvidenceCard() -> UIView {
let card = sectionCard(index: 2, title: "上传现场证据")
let buttonRow = UIStackView()
buttonRow.axis = .horizontal
buttonRow.spacing = AppSpacing.sm
buttonRow.distribution = .fillEqually
let imageButton = WildReportUI.iconButton(title: "添加图片", imageName: "photo", style: .secondary)
imageButton.addTarget(self, action: #selector(addImageTapped), for: .touchUpInside)
let videoButton = WildReportUI.iconButton(title: "添加视频", imageName: "video", style: .secondary)
videoButton.addTarget(self, action: #selector(addVideoTapped), for: .touchUpInside)
[imageButton, videoButton].forEach { button in
button.snp.makeConstraints { make in make.height.equalTo(44) }
buttonRow.addArrangedSubview(button)
}
card.stack.addArrangedSubview(buttonRow)
addAttachments(viewModel.images + viewModel.videos, to: card)
return card
}
private func makeDescriptionCard() -> UIView {
let card = sectionCard(index: 3, title: "举报说明")
descriptionView.text = viewModel.description
card.stack.addArrangedSubview(descriptionView)
descriptionView.snp.makeConstraints { make in
make.height.equalTo(132)
}
let count = WildReportUI.label("\(viewModel.description.count)/500", font: .app(.caption), color: AppColor.textTertiary)
count.textAlignment = .right
card.stack.addArrangedSubview(count)
return card
}
private func makeLocationCard() -> UIView {
let card = sectionCard(index: 4, title: "举报位置")
let title = WildReportUI.label(viewModel.locationTitleText, font: .app(.bodyMedium), color: AppColor.textPrimary, lines: 0)
let subtitle = WildReportUI.label(viewModel.locationSubtitleText, font: .app(.caption), color: AppColor.textSecondary, lines: 0)
let button = WildReportUI.iconButton(title: viewModel.isUpdatingLocation ? "定位中" : "更新定位", imageName: "location.fill", style: .secondary)
button.isEnabled = !viewModel.isUpdatingLocation
button.addTarget(self, action: #selector(refreshLocationTapped), for: .touchUpInside)
button.snp.makeConstraints { make in make.height.equalTo(42) }
card.stack.addArrangedSubview(title)
card.stack.addArrangedSubview(subtitle)
card.stack.addArrangedSubview(button)
return card
}
private func makeContactCard() -> UIView {
let card = sectionCard(index: 5, title: "联系方式(选填)")
contactField.text = viewModel.contact
card.stack.addArrangedSubview(contactField)
contactField.snp.makeConstraints { make in make.height.equalTo(46) }
return card
}
private func makePaymentCard() -> UIView {
let card = sectionCard(index: 6, title: "支付截图(选填)")
card.stack.addArrangedSubview(WildReportUI.label("上传线下微信/支付宝支付截图,最多 3 张。", font: .app(.caption), color: AppColor.textSecondary, lines: 0))
let button = WildReportUI.iconButton(title: "添加截图", imageName: "creditcard", style: .secondary)
button.addTarget(self, action: #selector(addPaymentTapped), for: .touchUpInside)
button.snp.makeConstraints { make in make.height.equalTo(42) }
card.stack.addArrangedSubview(button)
addAttachments(viewModel.paymentImages, to: card)
return card
}
private func sectionCard(index: Int, title: String) -> WildReportCardView {
let card = WildReportCardView(spacing: AppSpacing.sm)
card.stack.addArrangedSubview(WildReportUI.label("\(index). \(title)", font: .app(.title), color: AppColor.textPrimary))
return card
}
private func addAttachments(_ attachments: [WildReportAttachment], to card: WildReportCardView) {
guard !attachments.isEmpty else { return }
attachments.forEach { attachment in
let chip = WildReportAttachmentChipView(attachment: attachment)
chip.onRemove = { [weak self] in
self?.viewModel.removeAttachment(attachment)
}
card.stack.addArrangedSubview(chip)
}
}
@objc private func addImageTapped() { viewModel.addImage() }
@objc private func addVideoTapped() { viewModel.addVideo() }
@objc private func addPaymentTapped() { viewModel.addPaymentImage() }
@objc private func refreshLocationTapped() { viewModel.refreshCurrentLocation() }
@objc private func submitTapped() { viewModel.submitReport() }
@objc private func contactChanged() { viewModel.updateContact(contactField.text ?? "") }
}
extension WildPhotographerReportSubmitViewController: UITextViewDelegate {
func textViewDidChange(_ textView: UITextView) {
viewModel.updateDescription(textView.text)
}
}
///
final class WildPhotographerReportSuccessViewController: BaseViewController {
private let viewModel: WildPhotographerReportSuccessViewModel
private let homeViewModel: WildPhotographerReportHomeViewModel
private let stack = UIStackView()
init(result: WildReportSubmitResult, homeViewModel: WildPhotographerReportHomeViewModel) {
self.viewModel = WildPhotographerReportSuccessViewModel(result: result)
self.homeViewModel = homeViewModel
super.init(nibName: nil, bundle: nil)
}
@available(*, unavailable)
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func setupNavigationBar() {
title = "举报成功"
}
override func setupUI() {
view.backgroundColor = AppColor.pageBackgroundSoft
stack.axis = .vertical
stack.spacing = AppSpacing.sm
view.addSubview(stack)
let hero = WildReportCardView(spacing: AppSpacing.md)
hero.stack.alignment = .center
let icon = UIImageView(image: UIImage(systemName: "checkmark.circle.fill"))
icon.tintColor = AppColor.success
icon.snp.makeConstraints { make in make.size.equalTo(58) }
hero.stack.addArrangedSubview(icon)
hero.stack.addArrangedSubview(WildReportUI.label("举报已提交", font: .app(.metric), color: AppColor.textPrimary))
let desc = WildReportUI.label("景区公安已收到线索,将尽快前往现场核实处理", font: .app(.body), color: AppColor.textSecondary, lines: 0)
desc.textAlignment = .center
hero.stack.addArrangedSubview(desc)
stack.addArrangedSubview(hero)
let info = WildReportCardView(spacing: AppSpacing.sm)
info.stack.addArrangedSubview(WildReportInfoRowView(title: "举报编号", value: viewModel.result.record.id))
info.stack.addArrangedSubview(WildReportInfoRowView(title: "上传材料", value: viewModel.uploadSummary))
info.stack.addArrangedSubview(WildReportInfoRowView(title: "提交时间", value: viewModel.result.record.submitTime))
stack.addArrangedSubview(info)
let progress = WildReportCardView(spacing: AppSpacing.sm)
progress.stack.addArrangedSubview(WildReportUI.label("处理进度", font: .app(.title), color: AppColor.textPrimary))
viewModel.progressSteps.enumerated().forEach { index, title in
progress.stack.addArrangedSubview(WildReportUI.label("\(index + 1). \(title)", font: .app(.body), color: AppColor.textSecondary))
}
stack.addArrangedSubview(progress)
let detailButton = AppButton(title: "查看处理进度")
detailButton.addTarget(self, action: #selector(detailTapped), for: .touchUpInside)
let homeButton = AppButton(title: "返回首页", style: .secondary)
homeButton.addTarget(self, action: #selector(homeTapped), for: .touchUpInside)
stack.addArrangedSubview(detailButton)
stack.addArrangedSubview(homeButton)
}
override func setupConstraints() {
stack.snp.makeConstraints { make in
make.top.equalTo(view.safeAreaLayoutGuide).offset(AppSpacing.md)
make.leading.trailing.equalToSuperview().inset(AppSpacing.md)
}
}
@objc private func detailTapped() {
navigationController?.pushViewController(
WildPhotographerReportDetailViewController(record: viewModel.result.record, homeViewModel: homeViewModel),
animated: true
)
}
@objc private func homeTapped() {
navigationController?.popToViewController(
navigationController?.viewControllers.first(where: { $0 is WildPhotographerReportHomeViewController }) ?? self,
animated: true
)
}
}