Implement report supplement evidence flow
This commit is contained in:
@ -3,6 +3,7 @@
|
||||
// suixinkan
|
||||
//
|
||||
|
||||
import Kingfisher
|
||||
import SnapKit
|
||||
import UIKit
|
||||
|
||||
@ -136,7 +137,7 @@ final class WildPhotographerReportListViewController: BaseViewController {
|
||||
|
||||
private func openDetail(_ record: WildReportRecord) {
|
||||
navigationController?.pushViewController(
|
||||
WildPhotographerReportDetailViewController(record: record, homeViewModel: viewModel.homeViewModel),
|
||||
WildPhotographerReportDetailViewController(record: record, homeViewModel: viewModel.homeViewModel, api: api),
|
||||
animated: true
|
||||
)
|
||||
}
|
||||
@ -514,14 +515,20 @@ private final class WildReportListLoadingView: UIView {
|
||||
final class WildPhotographerReportDetailViewController: BaseViewController {
|
||||
private let viewModel: WildPhotographerReportDetailViewModel
|
||||
private let homeViewModel: WildPhotographerReportHomeViewModel
|
||||
private let api: any WildPhotographerReportServing
|
||||
private let scrollView = UIScrollView()
|
||||
private let stack = UIStackView()
|
||||
private let bottomBar = UIView()
|
||||
private let bottomButtonRow = UIStackView()
|
||||
|
||||
init(record: WildReportRecord, homeViewModel: WildPhotographerReportHomeViewModel) {
|
||||
init(
|
||||
record: WildReportRecord,
|
||||
homeViewModel: WildPhotographerReportHomeViewModel,
|
||||
api: any WildPhotographerReportServing = NetworkServices.shared.wildPhotographerReportAPI
|
||||
) {
|
||||
self.viewModel = WildPhotographerReportDetailViewModel(record: record, homeViewModel: homeViewModel)
|
||||
self.homeViewModel = homeViewModel
|
||||
self.api = api
|
||||
super.init(nibName: nil, bundle: nil)
|
||||
}
|
||||
|
||||
@ -593,7 +600,17 @@ final class WildPhotographerReportDetailViewController: BaseViewController {
|
||||
}
|
||||
}
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
Task {
|
||||
await viewModel.loadDetailIfNeeded(api: api)
|
||||
}
|
||||
}
|
||||
|
||||
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) }
|
||||
}
|
||||
@ -705,6 +722,10 @@ final class WildPhotographerReportDetailViewController: BaseViewController {
|
||||
card.stack.addArrangedSubview(WildReportDetailInfoRowView(title: "举报说明", value: viewModel.descriptionText, multiline: true))
|
||||
card.stack.addArrangedSubview(makeInsetDivider())
|
||||
card.stack.addArrangedSubview(WildReportDetailInfoRowView(title: "微信号/手机号", value: viewModel.contactText))
|
||||
if !viewModel.handleRemarkText.isEmpty {
|
||||
card.stack.addArrangedSubview(makeInsetDivider())
|
||||
card.stack.addArrangedSubview(WildReportDetailInfoRowView(title: "处理备注", value: viewModel.handleRemarkText, multiline: true))
|
||||
}
|
||||
return card
|
||||
}
|
||||
|
||||
@ -715,6 +736,10 @@ final class WildPhotographerReportDetailViewController: BaseViewController {
|
||||
card.stack.addArrangedSubview(makeReporterMaterialsSection())
|
||||
card.stack.addArrangedSubview(makeDivider())
|
||||
}
|
||||
if !viewModel.paymentScreenshotItems.isEmpty {
|
||||
card.stack.addArrangedSubview(makePaymentScreenshotSection())
|
||||
card.stack.addArrangedSubview(makeDivider())
|
||||
}
|
||||
card.stack.addArrangedSubview(makeLocationSection())
|
||||
return card
|
||||
}
|
||||
@ -819,6 +844,43 @@ final class WildPhotographerReportDetailViewController: BaseViewController {
|
||||
return section
|
||||
}
|
||||
|
||||
private func makePaymentScreenshotSection() -> UIView {
|
||||
let section = UIStackView()
|
||||
section.axis = .vertical
|
||||
section.spacing = 12
|
||||
section.addArrangedSubview(makeSectionHeader(
|
||||
iconName: "creditcard.fill",
|
||||
title: "支付截图",
|
||||
subtitle: viewModel.paymentScreenshotSummary,
|
||||
chevron: true,
|
||||
action: #selector(paymentPreviewTapped)
|
||||
))
|
||||
|
||||
let mediaScroll = UIScrollView()
|
||||
mediaScroll.showsHorizontalScrollIndicator = false
|
||||
let mediaStack = UIStackView()
|
||||
mediaStack.axis = .horizontal
|
||||
mediaStack.spacing = 10
|
||||
mediaScroll.addSubview(mediaStack)
|
||||
mediaStack.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
make.height.equalTo(mediaScroll.frameLayoutGuide)
|
||||
}
|
||||
viewModel.paymentScreenshotItems.forEach { item in
|
||||
let tile = WildReportDetailMediaThumbnailView(reporterItem: item)
|
||||
tile.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(reporterMediaTapped(_:))))
|
||||
tile.snp.makeConstraints { make in
|
||||
make.size.equalTo(108)
|
||||
}
|
||||
mediaStack.addArrangedSubview(tile)
|
||||
}
|
||||
mediaScroll.snp.makeConstraints { make in
|
||||
make.height.equalTo(108)
|
||||
}
|
||||
section.addArrangedSubview(mediaScroll)
|
||||
return section
|
||||
}
|
||||
|
||||
private func makeCompletionMediaSection(info: WildReportHandlerInfo) -> UIView {
|
||||
let section = UIStackView()
|
||||
section.axis = .vertical
|
||||
@ -996,6 +1058,7 @@ final class WildPhotographerReportDetailViewController: BaseViewController {
|
||||
|
||||
@objc private func shareTapped() { viewModel.shareReport() }
|
||||
@objc private func previewTapped() { viewModel.showMediaPreview(kind: "图片/视频") }
|
||||
@objc private func paymentPreviewTapped() { viewModel.showMediaPreview(kind: "支付截图") }
|
||||
@objc private func locationTapped() { viewModel.showLocationPreview() }
|
||||
|
||||
@objc private func reporterMediaTapped(_ recognizer: UITapGestureRecognizer) {
|
||||
@ -1010,7 +1073,17 @@ final class WildPhotographerReportDetailViewController: BaseViewController {
|
||||
|
||||
@objc private func supplementTapped() {
|
||||
navigationController?.pushViewController(
|
||||
WildReportSupplementEvidenceViewController(reportID: viewModel.record.id, homeViewModel: homeViewModel),
|
||||
WildReportSupplementEvidenceViewController(
|
||||
record: viewModel.record,
|
||||
homeViewModel: homeViewModel,
|
||||
api: api,
|
||||
onSupplementSuccess: { [weak self] in
|
||||
guard let self else { return }
|
||||
Task {
|
||||
await self.viewModel.loadDetailIfNeeded(api: self.api, force: true)
|
||||
}
|
||||
}
|
||||
),
|
||||
animated: true
|
||||
)
|
||||
}
|
||||
@ -1107,24 +1180,25 @@ private final class WildReportDetailInfoRowView: UIView {
|
||||
}
|
||||
}
|
||||
|
||||
/// 举报详情页材料缩略图,用渐变与系统图标模拟图片、视频和处理凭证。
|
||||
/// 举报详情页材料缩略图,展示远程图片、视频和处理凭证。
|
||||
private final class WildReportDetailMediaThumbnailView: UIView {
|
||||
let previewKindText: String
|
||||
private let gradient = CAGradientLayer()
|
||||
private let remoteImageView = UIImageView()
|
||||
private let iconView = UIImageView()
|
||||
private let footerLabel = PaddingLabel(insets: UIEdgeInsets(top: 3, left: 8, bottom: 3, right: 8))
|
||||
private let playView = UIImageView(image: UIImage(systemName: "play.circle.fill"))
|
||||
|
||||
init(reporterItem: WildReportReporterMediaItem) {
|
||||
switch reporterItem {
|
||||
case .image(let index):
|
||||
previewKindText = "第\(index + 1)张举报图片"
|
||||
case .image(let index, let url, let fileName, let fileTypeText):
|
||||
previewKindText = fileName?.isEmpty == false ? fileName! : "第\(index + 1)张举报图片"
|
||||
super.init(frame: .zero)
|
||||
configure(kind: .image(index: index), footerText: "图片 \(index + 1)")
|
||||
case .video(let index):
|
||||
previewKindText = "第\(index + 1)段举报视频"
|
||||
configure(kind: .image(index: index), footerText: fileTypeText ?? "图片 \(index + 1)", imageURL: url)
|
||||
case .video(let index, _, let fileName, let fileTypeText):
|
||||
previewKindText = fileName?.isEmpty == false ? fileName! : "第\(index + 1)段举报视频"
|
||||
super.init(frame: .zero)
|
||||
configure(kind: .video(index: index, duration: "00:32"), footerText: "视频 \(index + 1)")
|
||||
configure(kind: .video(index: index, duration: fileTypeText ?? "视频"), footerText: fileTypeText ?? "视频 \(index + 1)")
|
||||
}
|
||||
}
|
||||
|
||||
@ -1151,7 +1225,7 @@ private final class WildReportDetailMediaThumbnailView: UIView {
|
||||
gradient.frame = bounds
|
||||
}
|
||||
|
||||
private func configure(kind: Kind, footerText: String) {
|
||||
private func configure(kind: Kind, footerText: String, imageURL: String? = nil) {
|
||||
clipsToBounds = true
|
||||
layer.cornerRadius = 14
|
||||
gradient.colors = kind.gradientColors
|
||||
@ -1159,9 +1233,20 @@ private final class WildReportDetailMediaThumbnailView: UIView {
|
||||
gradient.endPoint = CGPoint(x: 1, y: 1)
|
||||
layer.insertSublayer(gradient, at: 0)
|
||||
|
||||
if let imageURL, let url = URL(string: imageURL) {
|
||||
remoteImageView.contentMode = .scaleAspectFill
|
||||
remoteImageView.clipsToBounds = true
|
||||
remoteImageView.kf.setImage(with: url, placeholder: UIImage(systemName: "photo"))
|
||||
addSubview(remoteImageView)
|
||||
remoteImageView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
}
|
||||
|
||||
iconView.image = UIImage(systemName: kind.iconName)
|
||||
iconView.tintColor = UIColor.white.withAlphaComponent(0.92)
|
||||
iconView.contentMode = .scaleAspectFit
|
||||
iconView.isHidden = imageURL != nil
|
||||
addSubview(iconView)
|
||||
|
||||
footerLabel.text = footerText
|
||||
|
||||
Reference in New Issue
Block a user