feat: 更新素材管理和举报风险地图
This commit is contained in:
@ -4,6 +4,7 @@
|
||||
//
|
||||
|
||||
import Kingfisher
|
||||
import MAMapKit
|
||||
import SnapKit
|
||||
import UIKit
|
||||
|
||||
@ -928,7 +929,7 @@ final class WildPhotographerReportDetailViewController: BaseViewController {
|
||||
}
|
||||
viewModel.paymentScreenshotItems.forEach { item in
|
||||
let tile = WildReportDetailMediaThumbnailView(reporterItem: item)
|
||||
tile.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(reporterMediaTapped(_:))))
|
||||
tile.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(paymentMediaTapped(_:))))
|
||||
tile.snp.makeConstraints { make in
|
||||
make.size.equalTo(108)
|
||||
}
|
||||
@ -987,7 +988,8 @@ final class WildPhotographerReportDetailViewController: BaseViewController {
|
||||
section.addArrangedSubview(makeLocationHeader())
|
||||
let mapPreview = WildReportDetailMapPreviewView(
|
||||
scenicName: viewModel.scenicAreaName,
|
||||
detailAddress: viewModel.detailAddress
|
||||
detailAddress: viewModel.detailAddress,
|
||||
coordinate: viewModel.mapCoordinate
|
||||
)
|
||||
section.addArrangedSubview(mapPreview)
|
||||
mapPreview.snp.makeConstraints { make in
|
||||
@ -1198,18 +1200,63 @@ 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 previewTapped() { presentReporterMediaPreview(selectedKey: nil) }
|
||||
@objc private func paymentPreviewTapped() { presentPaymentMediaPreview(selectedKey: nil) }
|
||||
@objc private func locationTapped() { viewModel.showLocationPreview() }
|
||||
|
||||
@objc private func reporterMediaTapped(_ recognizer: UITapGestureRecognizer) {
|
||||
guard let tile = recognizer.view as? WildReportDetailMediaThumbnailView else { return }
|
||||
viewModel.showMediaPreview(kind: tile.previewKindText)
|
||||
presentReporterMediaPreview(selectedKey: tile.previewKey)
|
||||
}
|
||||
|
||||
@objc private func paymentMediaTapped(_ recognizer: UITapGestureRecognizer) {
|
||||
guard let tile = recognizer.view as? WildReportDetailMediaThumbnailView else { return }
|
||||
presentPaymentMediaPreview(selectedKey: tile.previewKey)
|
||||
}
|
||||
|
||||
@objc private func completionMediaTapped(_ recognizer: UITapGestureRecognizer) {
|
||||
guard let tile = recognizer.view as? WildReportDetailMediaThumbnailView else { return }
|
||||
viewModel.showMediaPreview(kind: tile.previewKindText)
|
||||
presentCompletionMediaPreview(selectedKey: tile.previewKey)
|
||||
}
|
||||
|
||||
private func presentReporterMediaPreview(selectedKey: String?) {
|
||||
presentMediaPreview(entries: previewEntries(for: viewModel.reporterMediaItems), selectedKey: selectedKey, emptyMessage: "当前举报材料暂无可预览文件")
|
||||
}
|
||||
|
||||
private func presentPaymentMediaPreview(selectedKey: String?) {
|
||||
presentMediaPreview(entries: previewEntries(for: viewModel.paymentScreenshotItems), selectedKey: selectedKey, emptyMessage: "当前支付截图暂无可预览文件")
|
||||
}
|
||||
|
||||
private func presentCompletionMediaPreview(selectedKey: String?) {
|
||||
let entries = viewModel.handlerInfo?.completionMedia.compactMap { item -> (key: String, item: MediaPreviewItem)? in
|
||||
guard let key = item.previewKey,
|
||||
let previewItem = MediaPreviewItem(wildReportCompletionItem: item)
|
||||
else { return nil }
|
||||
return (key, previewItem)
|
||||
} ?? []
|
||||
presentMediaPreview(entries: entries, selectedKey: selectedKey, emptyMessage: "当前处理凭证暂无可预览文件")
|
||||
}
|
||||
|
||||
private func previewEntries(for mediaItems: [WildReportReporterMediaItem]) -> [(key: String, item: MediaPreviewItem)] {
|
||||
mediaItems.compactMap { mediaItem in
|
||||
guard let key = mediaItem.previewKey,
|
||||
let previewItem = MediaPreviewItem(wildReportReporterItem: mediaItem)
|
||||
else { return nil }
|
||||
return (key, previewItem)
|
||||
}
|
||||
}
|
||||
|
||||
private func presentMediaPreview(
|
||||
entries: [(key: String, item: MediaPreviewItem)],
|
||||
selectedKey: String?,
|
||||
emptyMessage: String
|
||||
) {
|
||||
guard !entries.isEmpty else {
|
||||
showToast(emptyMessage)
|
||||
return
|
||||
}
|
||||
let startIndex = selectedKey.flatMap { key in entries.firstIndex { $0.key == key } } ?? 0
|
||||
presentMediaPreview(items: entries.map(\.item), startIndex: startIndex)
|
||||
}
|
||||
|
||||
@objc private func supplementTapped() {
|
||||
@ -1320,15 +1367,57 @@ private final class WildReportDetailInfoRowView: UIView {
|
||||
}
|
||||
}
|
||||
|
||||
private extension MediaPreviewItem {
|
||||
init?(wildReportReporterItem item: WildReportReporterMediaItem) {
|
||||
guard let urlText = item.previewKey else { return nil }
|
||||
switch item {
|
||||
case .image:
|
||||
guard let previewItem = MediaPreviewItem.remoteImage(urlText) else { return nil }
|
||||
self = previewItem
|
||||
case .video:
|
||||
guard let previewItem = MediaPreviewItem.remoteVideo(urlText) else { return nil }
|
||||
self = previewItem
|
||||
}
|
||||
}
|
||||
|
||||
init?(wildReportCompletionItem item: WildReportCompletionMediaItem) {
|
||||
guard let urlText = item.previewKey else { return nil }
|
||||
switch item.kind {
|
||||
case .image:
|
||||
guard let previewItem = MediaPreviewItem.remoteImage(urlText) else { return nil }
|
||||
self = previewItem
|
||||
case .video:
|
||||
guard let previewItem = MediaPreviewItem.remoteVideo(urlText) else { return nil }
|
||||
self = previewItem
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension WildReportReporterMediaItem {
|
||||
var previewKey: String? {
|
||||
guard let text = fileURL?.trimmingCharacters(in: .whitespacesAndNewlines), !text.isEmpty else { return nil }
|
||||
return text
|
||||
}
|
||||
}
|
||||
|
||||
private extension WildReportCompletionMediaItem {
|
||||
var previewKey: String? {
|
||||
let text = coverURL.trimmingCharacters(in: .whitespacesAndNewlines)
|
||||
return text.isEmpty ? nil : text
|
||||
}
|
||||
}
|
||||
|
||||
/// 举报详情页材料缩略图,展示远程图片、视频和处理凭证。
|
||||
private final class WildReportDetailMediaThumbnailView: UIView {
|
||||
let previewKindText: String
|
||||
let previewKey: String?
|
||||
private let gradient = CAGradientLayer()
|
||||
private let remoteImageView = 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) {
|
||||
previewKey = reporterItem.previewKey
|
||||
switch reporterItem {
|
||||
case .image(let index, let url, let fileName, let fileTypeText):
|
||||
previewKindText = fileName?.isEmpty == false ? fileName! : "第\(index + 1)张举报图片"
|
||||
@ -1342,11 +1431,12 @@ private final class WildReportDetailMediaThumbnailView: UIView {
|
||||
}
|
||||
|
||||
init(completionItem: WildReportCompletionMediaItem) {
|
||||
previewKey = completionItem.previewKey
|
||||
switch completionItem.kind {
|
||||
case .image:
|
||||
previewKindText = "处理凭证图片"
|
||||
super.init(frame: .zero)
|
||||
configure(kind: .image(index: 0), footerText: "凭证")
|
||||
configure(kind: .image(index: 0), footerText: "凭证", imageURL: completionItem.coverURL)
|
||||
case .video:
|
||||
previewKindText = "处理凭证视频"
|
||||
super.init(frame: .zero)
|
||||
@ -1500,107 +1590,41 @@ private final class WildReportDetailMediaThumbnailView: UIView {
|
||||
|
||||
/// 举报详情页地图预览,绘制简化道路、路径和定位点。
|
||||
private final class WildReportDetailMapPreviewView: UIView {
|
||||
private let scenicName: String
|
||||
private let detailAddress: String
|
||||
private let detailLabel = UILabel()
|
||||
private let scenicLabel = UILabel()
|
||||
private let pinView = UIImageView(image: UIImage(systemName: "mappin.circle.fill"))
|
||||
private let mapView: MAMapView
|
||||
|
||||
init(scenicName: String, detailAddress: String) {
|
||||
self.scenicName = scenicName
|
||||
self.detailAddress = detailAddress
|
||||
init(scenicName: String, detailAddress: String, coordinate: CLLocationCoordinate2D?) {
|
||||
_ = AMapBootstrap.configureIfNeeded()
|
||||
mapView = MAMapView(frame: .zero)
|
||||
super.init(frame: .zero)
|
||||
backgroundColor = UIColor(hex: 0xE1F4D8)
|
||||
backgroundColor = AppColor.pageBackgroundSoft
|
||||
layer.cornerRadius = 10
|
||||
clipsToBounds = true
|
||||
|
||||
detailLabel.text = detailAddress.replacingOccurrences(of: "附近", with: "")
|
||||
detailLabel.font = .systemFont(ofSize: 13, weight: .semibold)
|
||||
detailLabel.textColor = UIColor(hex: 0x64748B)
|
||||
detailLabel.textAlignment = .center
|
||||
|
||||
scenicLabel.text = scenicName
|
||||
scenicLabel.font = .systemFont(ofSize: 13, weight: .semibold)
|
||||
scenicLabel.textColor = UIColor(hex: 0x418C48)
|
||||
scenicLabel.textAlignment = .center
|
||||
|
||||
pinView.tintColor = AppColor.danger
|
||||
pinView.contentMode = .scaleAspectFit
|
||||
|
||||
addSubview(detailLabel)
|
||||
addSubview(scenicLabel)
|
||||
addSubview(pinView)
|
||||
detailLabel.snp.makeConstraints { make in
|
||||
make.centerX.equalToSuperview().multipliedBy(0.94)
|
||||
make.centerY.equalToSuperview().multipliedBy(0.64)
|
||||
make.leading.greaterThanOrEqualToSuperview().offset(12)
|
||||
make.trailing.lessThanOrEqualToSuperview().inset(12)
|
||||
}
|
||||
scenicLabel.snp.makeConstraints { make in
|
||||
make.centerX.equalToSuperview().multipliedBy(1.56)
|
||||
make.centerY.equalToSuperview().multipliedBy(1.16)
|
||||
make.leading.greaterThanOrEqualToSuperview().offset(12)
|
||||
make.trailing.lessThanOrEqualToSuperview().inset(12)
|
||||
}
|
||||
pinView.snp.makeConstraints { make in
|
||||
make.centerX.equalToSuperview().multipliedBy(1.06)
|
||||
make.centerY.equalToSuperview().multipliedBy(1.08)
|
||||
make.size.equalTo(36)
|
||||
mapView.showsCompass = false
|
||||
mapView.showsScale = false
|
||||
mapView.isScrollEnabled = false
|
||||
mapView.isZoomEnabled = false
|
||||
mapView.isRotateEnabled = false
|
||||
mapView.isRotateCameraEnabled = false
|
||||
mapView.zoomLevel = 16
|
||||
addSubview(mapView)
|
||||
mapView.snp.makeConstraints { make in
|
||||
make.edges.equalToSuperview()
|
||||
}
|
||||
|
||||
guard let coordinate else { return }
|
||||
let annotation = MAPointAnnotation()
|
||||
annotation.coordinate = coordinate
|
||||
annotation.title = scenicName
|
||||
annotation.subtitle = detailAddress
|
||||
mapView.addAnnotation(annotation)
|
||||
mapView.setCenter(coordinate, animated: false)
|
||||
}
|
||||
|
||||
@available(*, unavailable)
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override func draw(_ rect: CGRect) {
|
||||
super.draw(rect)
|
||||
guard let context = UIGraphicsGetCurrentContext() else { return }
|
||||
|
||||
let colors = [UIColor(hex: 0xE1F4D8).cgColor, UIColor(hex: 0xBDE6C7).cgColor] as CFArray
|
||||
let colorSpace = CGColorSpaceCreateDeviceRGB()
|
||||
if let gradient = CGGradient(colorsSpace: colorSpace, colors: colors, locations: [0, 1]) {
|
||||
context.drawLinearGradient(
|
||||
gradient,
|
||||
start: CGPoint(x: 0, y: 0),
|
||||
end: CGPoint(x: rect.width, y: rect.height),
|
||||
options: []
|
||||
)
|
||||
}
|
||||
|
||||
let dashedPath = UIBezierPath()
|
||||
dashedPath.move(to: CGPoint(x: rect.width * 0.05, y: rect.height * 0.32))
|
||||
dashedPath.addCurve(
|
||||
to: CGPoint(x: rect.width * 0.95, y: rect.height * 0.22),
|
||||
controlPoint1: CGPoint(x: rect.width * 0.28, y: rect.height * 0.62),
|
||||
controlPoint2: CGPoint(x: rect.width * 0.56, y: rect.height * 0.02)
|
||||
)
|
||||
UIColor(hex: 0x65A6D7).withAlphaComponent(0.75).setStroke()
|
||||
dashedPath.lineWidth = 1.4
|
||||
dashedPath.setLineDash([5, 5], count: 2, phase: 0)
|
||||
dashedPath.stroke()
|
||||
|
||||
let road = UIBezierPath()
|
||||
road.move(to: CGPoint(x: rect.width * 0.00, y: rect.height * 0.74))
|
||||
road.addCurve(
|
||||
to: CGPoint(x: rect.width * 1.00, y: rect.height * 0.62),
|
||||
controlPoint1: CGPoint(x: rect.width * 0.26, y: rect.height * 0.20),
|
||||
controlPoint2: CGPoint(x: rect.width * 0.68, y: rect.height * 1.00)
|
||||
)
|
||||
UIColor.white.withAlphaComponent(0.88).setStroke()
|
||||
road.lineWidth = 5
|
||||
road.stroke()
|
||||
UIColor(hex: 0x99C59C).setStroke()
|
||||
road.lineWidth = 1
|
||||
road.stroke()
|
||||
|
||||
let pinCenter = CGPoint(x: rect.width * 0.53, y: rect.height * 0.54)
|
||||
UIColor(hex: 0xEF4444).withAlphaComponent(0.18).setFill()
|
||||
context.fillEllipse(in: CGRect(x: pinCenter.x - 32, y: pinCenter.y - 32, width: 64, height: 64))
|
||||
UIColor(hex: 0xEF4444).withAlphaComponent(0.22).setFill()
|
||||
context.fillEllipse(in: CGRect(x: pinCenter.x - 22, y: pinCenter.y - 22, width: 44, height: 44))
|
||||
}
|
||||
}
|
||||
|
||||
/// 举报详情页处理进度时间线单行。
|
||||
|
||||
Reference in New Issue
Block a user