Files
suixinkan_uikit/suixinkan/Features/WildPhotographerReport/Models/WildPhotographerReportModels.swift

1414 lines
47 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.

//
// WildPhotographerReportModels.swift
// suixinkan
//
import CoreLocation
import Foundation
import MapKit
import UIKit
///
enum WildReportPageState: String, CaseIterable, Hashable {
case normal
case loading
case empty
case error
var title: String {
switch self {
case .normal: return "正常"
case .loading: return "加载"
case .empty: return "空状态"
case .error: return "异常"
}
}
}
///
enum WildReportAttachmentKind: Hashable {
case image
case video
case payment
}
///
struct WildReportType: Decodable, Hashable {
let value: Int
let label: String
}
///
struct WildReportTypeListResponse: Decodable, Hashable {
let list: [WildReportType]
init(list: [WildReportType]) {
self.list = list
}
}
///
struct WildReportCopyResponse: Decodable, Equatable, Hashable {
let notice: [String]
let ruleGroups: [WildReportRuleGroup]
enum CodingKeys: String, CodingKey {
case notice
case ruleGroups = "rule_groups"
}
init(notice: [String] = [], ruleGroups: [WildReportRuleGroup] = []) {
self.notice = notice
self.ruleGroups = ruleGroups
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
notice = try container.decodeIfPresent([String].self, forKey: .notice) ?? []
ruleGroups = try container.decodeIfPresent([WildReportRuleGroup].self, forKey: .ruleGroups) ?? []
}
}
///
struct WildReportRuleGroup: Decodable, Equatable, Hashable {
let title: String
let items: [String]
enum CodingKeys: String, CodingKey {
case title
case items
}
init(title: String = "", items: [String] = []) {
self.title = title
self.items = items
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
title = try container.decodeIfPresent(String.self, forKey: .title) ?? ""
items = try container.decodeIfPresent([String].self, forKey: .items) ?? []
}
}
///
struct WildReportAttachment: Hashable {
let id: UUID
let kind: WildReportAttachmentKind
let title: String
let localFileURL: URL?
let fileName: String
let fileType: Int
let uploadedURL: String?
init(
id: UUID = UUID(),
kind: WildReportAttachmentKind,
title: String,
localFileURL: URL? = nil,
fileName: String? = nil,
uploadedURL: String? = nil
) {
self.id = id
self.kind = kind
self.title = title
self.localFileURL = localFileURL
self.fileName = fileName ?? "\(title).\(kind.defaultFileExtension)"
self.fileType = kind.reportFileType
self.uploadedURL = uploadedURL
}
}
extension WildReportAttachment {
///
func uploadData() throws -> Data {
guard let localFileURL else { throw OSSUploadError.emptyFile }
return try Data(contentsOf: localFileURL)
}
}
extension WildReportAttachmentKind {
/// 1 2
var reportFileType: Int {
switch self {
case .image, .payment: return 1
case .video: return 2
}
}
///
var defaultFileExtension: String {
switch self {
case .image, .payment: return "jpg"
case .video: return "mp4"
}
}
}
///
enum WildReportStatus: String, Hashable, CaseIterable {
case pending = "待处理"
case processing = "处理中"
case processed = "已处理"
case rejected = "驳回"
var displayColor: UIColor {
switch self {
case .pending: return AppColor.warning
case .processing: return AppColor.primary
case .processed: return AppColor.success
case .rejected: return AppColor.danger
}
}
var backgroundColor: UIColor {
switch self {
case .pending: return AppColor.warningBackground
case .processing: return AppColor.infoBackground
case .processed: return AppColor.successBackground
case .rejected: return AppColor.dangerBackground
}
}
}
extension WildReportStatus {
/// 0 1 2 3
init(handleStatus: Int, fallbackText: String = "") {
switch handleStatus {
case 0:
self = .pending
case 1:
self = .processing
case 2:
self = .processed
case 3:
self = .rejected
default:
switch fallbackText {
case WildReportStatus.processing.rawValue:
self = .processing
case WildReportStatus.processed.rawValue:
self = .processed
case WildReportStatus.rejected.rawValue:
self = .rejected
default:
self = .pending
}
}
}
}
///
enum WildReportListFilter: String, CaseIterable, Hashable {
case all = "全部"
case pending = "待处理"
case processing = "处理中"
case processed = "已处理"
}
extension WildReportListFilter {
/// 0 1 2
var handleStatusValue: Int? {
switch self {
case .all: return nil
case .pending: return 0
case .processing: return 1
case .processed: return 2
}
}
}
///
struct WildReportListRequest: Equatable {
let scenicId: Int?
let handleStatus: Int?
let page: Int
let pageSize: Int
init(
scenicId: Int?,
handleStatus: Int?,
page: Int = 1,
pageSize: Int = 10
) {
self.scenicId = scenicId
self.handleStatus = handleStatus
self.page = page
self.pageSize = pageSize
}
var queryItems: [URLQueryItem] {
var items: [URLQueryItem] = []
if let scenicId, scenicId > 0 {
items.append(URLQueryItem(name: "scenic_id", value: String(scenicId)))
}
if let handleStatus {
items.append(URLQueryItem(name: "handle_status", value: String(handleStatus)))
}
items.append(URLQueryItem(name: "page", value: String(max(1, page))))
items.append(URLQueryItem(name: "page_size", value: String(min(max(1, pageSize), 50))))
return items
}
}
///
struct WildReportListResponse: Decodable, Equatable {
let list: [WildReportListItem]
let total: Int
let page: Int
let pageSize: Int
enum CodingKeys: String, CodingKey {
case list
case total
case page
case pageSize = "page_size"
}
init(list: [WildReportListItem], total: Int, page: Int, pageSize: Int) {
self.list = list
self.total = total
self.page = page
self.pageSize = pageSize
}
}
///
struct WildReportListItem: Decodable, Equatable, Hashable {
let id: Int
let complaintNo: String
let title: String
let reportType: Int
let reportTypeText: String
let desc: String
let scenicId: Int
let scenicName: String
let locationName: String
let latitude: String
let longitude: String
let handleStatus: Int
let handleStatusText: String
let createdAt: String
enum CodingKeys: String, CodingKey {
case id
case complaintNo = "complaint_no"
case title
case reportType = "report_type"
case reportTypeText = "report_type_text"
case desc
case scenicId = "scenic_id"
case scenicName = "scenic_name"
case locationName = "location_name"
case latitude
case longitude
case handleStatus = "handle_status"
case handleStatusText = "handle_status_text"
case createdAt = "created_at"
}
init(
id: Int,
complaintNo: String,
title: String,
reportType: Int,
reportTypeText: String,
desc: String,
scenicId: Int,
scenicName: String,
locationName: String,
latitude: String,
longitude: String,
handleStatus: Int,
handleStatusText: String,
createdAt: String
) {
self.id = id
self.complaintNo = complaintNo
self.title = title
self.reportType = reportType
self.reportTypeText = reportTypeText
self.desc = desc
self.scenicId = scenicId
self.scenicName = scenicName
self.locationName = locationName
self.latitude = latitude
self.longitude = longitude
self.handleStatus = handleStatus
self.handleStatusText = handleStatusText
self.createdAt = createdAt
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeIfPresent(Int.self, forKey: .id) ?? 0
complaintNo = try container.decodeIfPresent(String.self, forKey: .complaintNo) ?? ""
title = try container.decodeIfPresent(String.self, forKey: .title) ?? ""
reportType = try container.decodeIfPresent(Int.self, forKey: .reportType) ?? 0
reportTypeText = try container.decodeIfPresent(String.self, forKey: .reportTypeText) ?? ""
desc = try container.decodeIfPresent(String.self, forKey: .desc) ?? ""
scenicId = try container.decodeIfPresent(Int.self, forKey: .scenicId) ?? 0
scenicName = try container.decodeIfPresent(String.self, forKey: .scenicName) ?? ""
locationName = try container.decodeIfPresent(String.self, forKey: .locationName) ?? ""
latitude = try container.decodeIfPresent(String.self, forKey: .latitude) ?? ""
longitude = try container.decodeIfPresent(String.self, forKey: .longitude) ?? ""
handleStatus = try container.decodeIfPresent(Int.self, forKey: .handleStatus) ?? 0
handleStatusText = try container.decodeIfPresent(String.self, forKey: .handleStatusText) ?? ""
createdAt = try container.decodeIfPresent(String.self, forKey: .createdAt) ?? ""
}
/// UIKit
func toRecord() -> WildReportRecord {
let scenic = scenicName.trimmingCharacters(in: .whitespacesAndNewlines)
let location = locationName.trimmingCharacters(in: .whitespacesAndNewlines)
let displayLocation: String
if !scenic.isEmpty, !location.isEmpty, scenic != location {
displayLocation = "\(scenic) · \(location)"
} else if !location.isEmpty {
displayLocation = location
} else {
displayLocation = scenic
}
return WildReportRecord(
serverID: id > 0 ? id : nil,
id: complaintNo,
title: title.isEmpty ? "\(reportTypeText)举报" : title,
reportType: WildReportType(value: reportType, label: reportTypeText.isEmpty ? "其他" : reportTypeText),
status: WildReportStatus(handleStatus: handleStatus, fallbackText: handleStatusText),
location: displayLocation,
submitTime: createdAt,
description: desc,
wechatID: "",
phoneNumber: "",
imageCount: 0,
videoCount: 0,
handlerInfo: nil
)
}
}
///
struct WildReportEvidenceSummary: Decodable, Equatable, Hashable {
let imageCount: Int
let videoCount: Int
enum CodingKeys: String, CodingKey {
case imageCount = "image_count"
case videoCount = "video_count"
}
init(imageCount: Int = 0, videoCount: Int = 0) {
self.imageCount = imageCount
self.videoCount = videoCount
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
imageCount = try container.decodeIfPresent(Int.self, forKey: .imageCount) ?? 0
videoCount = try container.decodeIfPresent(Int.self, forKey: .videoCount) ?? 0
}
}
///
struct WildReportDetailEvidence: Decodable, Equatable, Hashable {
let fileType: Int
let fileTypeText: String
let fileURL: String
let fileName: String
enum CodingKeys: String, CodingKey {
case fileType = "file_type"
case fileTypeText = "file_type_text"
case fileURL = "file_url"
case fileName = "file_name"
}
init(fileType: Int = 0, fileTypeText: String = "", fileURL: String = "", fileName: String = "") {
self.fileType = fileType
self.fileTypeText = fileTypeText
self.fileURL = fileURL
self.fileName = fileName
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
fileType = try container.decodeIfPresent(Int.self, forKey: .fileType) ?? 0
fileTypeText = try container.decodeIfPresent(String.self, forKey: .fileTypeText) ?? ""
fileURL = try container.decodeIfPresent(String.self, forKey: .fileURL) ?? ""
fileName = try container.decodeIfPresent(String.self, forKey: .fileName) ?? ""
}
var isVideo: Bool { fileType == 2 }
var isImage: Bool { fileType == 1 || fileType == 5 }
}
///
struct WildReportDetailContent: Decodable, Equatable, Hashable {
let type: String
let id: Int
let desc: String
let time: String
let timeLabel: String
let evidences: [WildReportDetailEvidence]
enum CodingKeys: String, CodingKey {
case type
case id
case desc
case time
case timeLabel = "time_label"
case evidences
}
init(
type: String = "",
id: Int = 0,
desc: String = "",
time: String = "",
timeLabel: String = "",
evidences: [WildReportDetailEvidence] = []
) {
self.type = type
self.id = id
self.desc = desc
self.time = time
self.timeLabel = timeLabel
self.evidences = evidences
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
type = try container.decodeIfPresent(String.self, forKey: .type) ?? ""
id = try container.decodeIfPresent(Int.self, forKey: .id) ?? 0
desc = try container.decodeIfPresent(String.self, forKey: .desc) ?? ""
time = try container.decodeIfPresent(String.self, forKey: .time) ?? ""
timeLabel = try container.decodeIfPresent(String.self, forKey: .timeLabel) ?? ""
evidences = try container.decodeIfPresent([WildReportDetailEvidence].self, forKey: .evidences) ?? []
}
var imageCount: Int { evidences.filter(\.isImage).count }
var videoCount: Int { evidences.filter(\.isVideo).count }
}
///
struct WildReportDetailTimelineItem: Decodable, Equatable, Hashable {
let time: String
let content: String
enum CodingKeys: String, CodingKey {
case time
case content
}
init(time: String = "", content: String = "") {
self.time = time
self.content = content
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
time = try container.decodeIfPresent(String.self, forKey: .time) ?? ""
content = try container.decodeIfPresent(String.self, forKey: .content) ?? ""
}
}
///
struct WildReportDetailData: Decodable, Equatable, Hashable {
let id: Int
let complaintNo: String
let title: String
let reportType: Int
let reportTypeText: String
let desc: String
let supplements: [WildReportDetailContent]
let reportContents: [WildReportDetailContent]
let scenicId: Int
let scenicName: String
let storeUserId: Int
let contactPhone: String
let locationName: String
let locationAddress: String
let latitude: String
let longitude: String
let handleStatus: Int
let handleStatusText: String
let handleRemark: String
let evidenceSummary: WildReportEvidenceSummary?
let evidences: [WildReportDetailEvidence]
let paymentScreenshots: [WildReportDetailEvidence]
let timeline: [WildReportDetailTimelineItem]
let createdAt: String
enum CodingKeys: String, CodingKey {
case id
case complaintNo = "complaint_no"
case title
case reportType = "report_type"
case reportTypeText = "report_type_text"
case desc
case supplements
case reportContents = "report_contents"
case scenicId = "scenic_id"
case scenicName = "scenic_name"
case storeUserId = "store_user_id"
case contactPhone = "contact_phone"
case locationName = "location_name"
case locationAddress = "location_address"
case latitude
case longitude
case handleStatus = "handle_status"
case handleStatusText = "handle_status_text"
case handleRemark = "handle_remark"
case evidenceSummary = "evidence_summary"
case evidences
case paymentScreenshots = "payment_screenshots"
case timeline
case createdAt = "created_at"
}
init(
id: Int = 0,
complaintNo: String = "",
title: String = "",
reportType: Int = 0,
reportTypeText: String = "",
desc: String = "",
supplements: [WildReportDetailContent] = [],
reportContents: [WildReportDetailContent] = [],
scenicId: Int = 0,
scenicName: String = "",
storeUserId: Int = 0,
contactPhone: String = "",
locationName: String = "",
locationAddress: String = "",
latitude: String = "",
longitude: String = "",
handleStatus: Int = 0,
handleStatusText: String = "",
handleRemark: String = "",
evidenceSummary: WildReportEvidenceSummary? = nil,
evidences: [WildReportDetailEvidence] = [],
paymentScreenshots: [WildReportDetailEvidence] = [],
timeline: [WildReportDetailTimelineItem] = [],
createdAt: String = ""
) {
self.id = id
self.complaintNo = complaintNo
self.title = title
self.reportType = reportType
self.reportTypeText = reportTypeText
self.desc = desc
self.supplements = supplements
self.reportContents = reportContents
self.scenicId = scenicId
self.scenicName = scenicName
self.storeUserId = storeUserId
self.contactPhone = contactPhone
self.locationName = locationName
self.locationAddress = locationAddress
self.latitude = latitude
self.longitude = longitude
self.handleStatus = handleStatus
self.handleStatusText = handleStatusText
self.handleRemark = handleRemark
self.evidenceSummary = evidenceSummary
self.evidences = evidences
self.paymentScreenshots = paymentScreenshots
self.timeline = timeline
self.createdAt = createdAt
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeIfPresent(Int.self, forKey: .id) ?? 0
complaintNo = try container.decodeIfPresent(String.self, forKey: .complaintNo) ?? ""
title = try container.decodeIfPresent(String.self, forKey: .title) ?? ""
reportType = try container.decodeIfPresent(Int.self, forKey: .reportType) ?? 0
reportTypeText = try container.decodeIfPresent(String.self, forKey: .reportTypeText) ?? ""
desc = try container.decodeIfPresent(String.self, forKey: .desc) ?? ""
supplements = try container.decodeIfPresent([WildReportDetailContent].self, forKey: .supplements) ?? []
reportContents = try container.decodeIfPresent([WildReportDetailContent].self, forKey: .reportContents) ?? []
scenicId = try container.decodeIfPresent(Int.self, forKey: .scenicId) ?? 0
scenicName = try container.decodeIfPresent(String.self, forKey: .scenicName) ?? ""
storeUserId = try container.decodeIfPresent(Int.self, forKey: .storeUserId) ?? 0
contactPhone = try container.decodeIfPresent(String.self, forKey: .contactPhone) ?? ""
locationName = try container.decodeIfPresent(String.self, forKey: .locationName) ?? ""
locationAddress = try container.decodeIfPresent(String.self, forKey: .locationAddress) ?? ""
latitude = try container.decodeIfPresent(String.self, forKey: .latitude) ?? ""
longitude = try container.decodeIfPresent(String.self, forKey: .longitude) ?? ""
handleStatus = try container.decodeIfPresent(Int.self, forKey: .handleStatus) ?? 0
handleStatusText = try container.decodeIfPresent(String.self, forKey: .handleStatusText) ?? ""
handleRemark = try container.decodeIfPresent(String.self, forKey: .handleRemark) ?? ""
evidenceSummary = try container.decodeIfPresent(WildReportEvidenceSummary.self, forKey: .evidenceSummary)
evidences = try container.decodeIfPresent([WildReportDetailEvidence].self, forKey: .evidences) ?? []
paymentScreenshots = try container.decodeIfPresent([WildReportDetailEvidence].self, forKey: .paymentScreenshots) ?? []
timeline = try container.decodeIfPresent([WildReportDetailTimelineItem].self, forKey: .timeline) ?? []
createdAt = try container.decodeIfPresent(String.self, forKey: .createdAt) ?? ""
}
var displayLocation: String {
let scenic = scenicName.trimmingCharacters(in: .whitespacesAndNewlines)
let detail = (locationAddress.nonEmpty ?? locationName).trimmingCharacters(in: .whitespacesAndNewlines)
if !scenic.isEmpty, !detail.isEmpty, scenic != detail {
return "\(scenic) · \(detail)"
}
return detail.nonEmpty ?? scenic
}
var displayStatusText: String {
handleStatusText.trimmingCharacters(in: .whitespacesAndNewlines).nonEmpty
?? WildReportStatus(handleStatus: handleStatus).rawValue
}
var imageCount: Int {
evidenceSummary?.imageCount ?? evidences.filter(\.isImage).count
}
var videoCount: Int {
evidenceSummary?.videoCount ?? evidences.filter(\.isVideo).count
}
var supplementRecords: [WildReportSupplementaryEvidence] {
let contents = supplements.isEmpty
? reportContents.filter { $0.type == "supplement" }
: supplements
return contents.map { content in
WildReportSupplementaryEvidence(
id: content.id > 0 ? String(content.id) : content.time,
submitTime: content.time,
text: content.desc,
imageCount: content.imageCount,
videoCount: content.videoCount
)
}
}
/// UIKit
func toRecord(fallback: WildReportRecord? = nil) -> WildReportRecord {
WildReportRecord(
serverID: id > 0 ? id : fallback?.serverID,
id: complaintNo.nonEmpty ?? fallback?.id ?? "",
title: title.nonEmpty ?? fallback?.title ?? "摄影师举报",
reportType: WildReportType(
value: reportType > 0 ? reportType : fallback?.reportType.value ?? 4,
label: reportTypeText.nonEmpty ?? fallback?.reportType.label ?? "其他"
),
status: WildReportStatus(handleStatus: handleStatus, fallbackText: handleStatusText),
location: displayLocation.nonEmpty ?? fallback?.location ?? "",
submitTime: createdAt.nonEmpty ?? fallback?.submitTime ?? "",
description: desc.nonEmpty ?? fallback?.description ?? "",
wechatID: fallback?.wechatID ?? "",
phoneNumber: contactPhone.nonEmpty ?? fallback?.phoneNumber ?? "",
imageCount: imageCount,
videoCount: videoCount,
handlerInfo: fallback?.handlerInfo
)
}
}
///
struct WildReportSupplementaryEvidence: Hashable {
let id: String
let submitTime: String
let text: String
let imageCount: Int
let videoCount: Int
var summaryText: String {
var parts: [String] = []
if !text.isEmpty { parts.append(text) }
if imageCount > 0 { parts.append("\(imageCount)张图片") }
if videoCount > 0 { parts.append("\(videoCount)段视频") }
return parts.isEmpty ? "补充证据" : parts.joined(separator: " · ")
}
}
///
enum WildReportLocationParser {
/// ·
static func split(_ location: String) -> (scenic: String, detail: String) {
let parts = location
.components(separatedBy: " · ")
.map { $0.trimmingCharacters(in: .whitespacesAndNewlines) }
.filter { !$0.isEmpty }
if parts.count >= 2 {
return (parts[0], parts[1])
}
return (location, location)
}
}
///
struct WildReportRiskPoint: Hashable {
let id: Int
let title: String
let location: String
let distance: String
let riskLevel: String
let evidenceCount: Int
let latestTime: String
let description: String
}
///
struct WildReportRecord: Hashable {
var serverID: Int? = nil
let id: String
let title: String
let reportType: WildReportType
let status: WildReportStatus
let location: String
let submitTime: String
let description: String
let wechatID: String
let phoneNumber: String
let imageCount: Int
let videoCount: Int
let handlerInfo: WildReportHandlerInfo?
var contactDisplayText: String {
switch (wechatID.isEmpty, phoneNumber.isEmpty) {
case (true, true): return "未填写"
case (false, false): return "\(wechatID) / \(phoneNumber)"
case (false, true): return wechatID
case (true, false): return phoneNumber
}
}
}
///
struct WildReportCompletionMediaItem: Hashable {
enum Kind: Hashable {
case image
case video
}
let id: String
let kind: Kind
let coverURL: String
let duration: String?
}
///
struct WildReportHandlerInfo: Hashable {
let handlerName: String
let handlerPhone: String
let processingAt: String
let processedAt: String
let processOpinion: String
let completionMedia: [WildReportCompletionMediaItem]
var maskedHandlerPhone: String {
guard handlerPhone.count == 11, handlerPhone.allSatisfy(\.isNumber) else { return handlerPhone }
return "\(handlerPhone.prefix(3))****\(handlerPhone.suffix(4))"
}
var completionMediaSummary: String {
let imageCount = completionMedia.filter { $0.kind == .image }.count
let videoCount = completionMedia.filter { $0.kind == .video }.count
switch (imageCount, videoCount) {
case (0, 0): return "0项"
case (_, 0): return "\(imageCount)"
case (0, _): return "\(videoCount)"
default: return "\(imageCount)\(videoCount)"
}
}
}
///
enum WildReportProgressStepState: Hashable {
case completed
case current
case pending
}
///
struct WildReportProgressStep: Hashable {
let id: String
let title: String
let timeText: String
let state: WildReportProgressStepState
}
///
enum WildReportReporterMediaItem: Hashable {
case image(index: Int, url: String?, fileName: String?, fileTypeText: String?)
case video(index: Int, url: String?, fileName: String?, fileTypeText: String?)
var id: String {
switch self {
case .image(let index, let url, _, _): return "reporter-image-\(index)-\(url ?? "")"
case .video(let index, let url, _, _): return "reporter-video-\(index)-\(url ?? "")"
}
}
var fileURL: String? {
switch self {
case .image(_, let url, _, _), .video(_, let url, _, _):
return url
}
}
var fileName: String? {
switch self {
case .image(_, _, let fileName, _), .video(_, _, let fileName, _):
return fileName
}
}
var fileTypeText: String? {
switch self {
case .image(_, _, _, let fileTypeText), .video(_, _, _, let fileTypeText):
return fileTypeText
}
}
}
///
struct WildReportSubmitResult: Hashable {
let record: WildReportRecord
let imageCount: Int
let videoCount: Int
}
///
enum WildReportMapMarkerKind: Hashable {
case selfLocation
case wildPhotographer
case processingClue
case peerPhotographer
var color: UIColor {
switch self {
case .selfLocation: return AppColor.primary
case .wildPhotographer: return AppColor.danger
case .processingClue: return AppColor.warning
case .peerPhotographer: return AppColor.success
}
}
var title: String {
switch self {
case .selfLocation: return "我的位置"
case .wildPhotographer: return "疑似打野"
case .processingClue: return "处理中线索"
case .peerPhotographer: return "内部摄影师"
}
}
}
///
enum WildReportRadarMarkerType: String, Hashable {
case blue
case green
case red
case yellow
}
///
enum WildReportRiskMarkerType: Hashable {
case blue
case green
case red
case yellow
case unknown(String)
init(rawValue: String) {
switch rawValue.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() {
case "blue", "self", "self_location":
self = .blue
case "green":
self = .green
case "red":
self = .red
case "yellow":
self = .yellow
default:
self = .unknown(rawValue)
}
}
var rawValue: String {
switch self {
case .blue: return "blue"
case .green: return "green"
case .red: return "red"
case .yellow: return "yellow"
case .unknown(let value): return value
}
}
var markerKind: WildReportMapMarkerKind {
switch self {
case .blue:
return .selfLocation
case .green:
return .peerPhotographer
case .yellow:
return .processingClue
case .red, .unknown:
return .wildPhotographer
}
}
var radarType: WildReportRadarMarkerType {
switch self {
case .blue:
return .blue
case .green:
return .green
case .yellow:
return .yellow
case .red, .unknown:
return .red
}
}
}
///
struct WildReportDecodedHandleStatus: Decodable, Equatable, Hashable {
let intValue: Int?
let textValue: String?
init(intValue: Int? = nil, textValue: String? = nil) {
self.intValue = intValue
self.textValue = textValue
}
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if let value = try? container.decode(Int.self) {
intValue = value
textValue = nil
} else if let value = try? container.decode(String.self) {
intValue = Int(value)
textValue = value
} else {
intValue = nil
textValue = nil
}
}
var displayText: String {
if let text = textValue?.trimmingCharacters(in: .whitespacesAndNewlines), !text.isEmpty {
switch text {
case "0": return WildReportStatus.pending.rawValue
case "1": return WildReportStatus.processing.rawValue
case "2": return WildReportStatus.processed.rawValue
case "3": return WildReportStatus.rejected.rawValue
default: return text
}
}
return WildReportStatus(handleStatus: intValue ?? 0).rawValue
}
}
///
struct WildReportRiskMapResponse: Decodable, Equatable {
let scenicId: Int
let scenicName: String
let legend: [WildReportRiskLegendItem]
let greenMarkerTip: String
let markers: [WildReportRiskMapMarkerDTO]
enum CodingKeys: String, CodingKey {
case scenicId = "scenic_id"
case scenicName = "scenic_name"
case legend
case greenMarkerTip = "green_marker_tip"
case markers
}
init(
scenicId: Int,
scenicName: String,
legend: [WildReportRiskLegendItem] = [],
greenMarkerTip: String = "",
markers: [WildReportRiskMapMarkerDTO] = []
) {
self.scenicId = scenicId
self.scenicName = scenicName
self.legend = legend
self.greenMarkerTip = greenMarkerTip
self.markers = markers
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
scenicId = try container.decodeIfPresent(Int.self, forKey: .scenicId) ?? 0
scenicName = try container.decodeIfPresent(String.self, forKey: .scenicName) ?? ""
legend = try container.decodeIfPresent([WildReportRiskLegendItem].self, forKey: .legend) ?? []
greenMarkerTip = try container.decodeIfPresent(String.self, forKey: .greenMarkerTip) ?? ""
markers = try container.decodeIfPresent([WildReportRiskMapMarkerDTO].self, forKey: .markers) ?? []
}
}
///
struct WildReportRiskLegendItem: Decodable, Equatable, Hashable {
let markerType: String?
let title: String?
let color: String?
enum CodingKeys: String, CodingKey {
case markerType = "marker_type"
case type
case title
case text
case name
case color
}
init(markerType: String? = nil, title: String? = nil, color: String? = nil) {
self.markerType = markerType
self.title = title
self.color = color
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
markerType = try container.decodeIfPresent(String.self, forKey: .markerType)
?? (try container.decodeIfPresent(String.self, forKey: .type))
title = try container.decodeIfPresent(String.self, forKey: .title)
?? (try container.decodeIfPresent(String.self, forKey: .text))
?? (try container.decodeIfPresent(String.self, forKey: .name))
color = try container.decodeIfPresent(String.self, forKey: .color)
}
}
///
struct WildReportRiskMapMarkerDTO: Decodable, Equatable, Hashable {
let markerType: String
let id: Int
let latitude: String
let longitude: String
let locationName: String
let title: String
let reportTypeText: String
let handleStatus: WildReportDecodedHandleStatus?
let handleStatusText: String?
let distanceM: Double
let createdAt: String
let tag: String?
let name: String?
let avatar: String?
let storeName: String?
let online: Bool?
let greenMarkerTip: String?
let activityText: String?
enum CodingKeys: String, CodingKey {
case markerType = "marker_type"
case id
case latitude
case longitude
case locationName = "location_name"
case title
case reportTypeText = "report_type_text"
case handleStatus = "handle_status"
case handleStatusText = "handle_status_text"
case distanceM = "distance_m"
case createdAt = "created_at"
case tag
case name
case avatar
case storeName = "store_name"
case online
case greenMarkerTip = "green_marker_tip"
case activityText = "activity_text"
}
init(
markerType: String,
id: Int,
latitude: String,
longitude: String,
locationName: String,
title: String,
reportTypeText: String = "",
handleStatus: WildReportDecodedHandleStatus? = nil,
handleStatusText: String? = nil,
distanceM: Double = 0,
createdAt: String = "",
tag: String? = nil,
name: String? = nil,
avatar: String? = nil,
storeName: String? = nil,
online: Bool? = nil,
greenMarkerTip: String? = nil,
activityText: String? = nil
) {
self.markerType = markerType
self.id = id
self.latitude = latitude
self.longitude = longitude
self.locationName = locationName
self.title = title
self.reportTypeText = reportTypeText
self.handleStatus = handleStatus
self.handleStatusText = handleStatusText
self.distanceM = distanceM
self.createdAt = createdAt
self.tag = tag
self.name = name
self.avatar = avatar
self.storeName = storeName
self.online = online
self.greenMarkerTip = greenMarkerTip
self.activityText = activityText
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
markerType = try container.decodeIfPresent(String.self, forKey: .markerType) ?? ""
id = try container.decodeIfPresent(Int.self, forKey: .id) ?? 0
latitude = try container.decodeIfPresent(String.self, forKey: .latitude) ?? ""
longitude = try container.decodeIfPresent(String.self, forKey: .longitude) ?? ""
locationName = try container.decodeIfPresent(String.self, forKey: .locationName) ?? ""
title = try container.decodeIfPresent(String.self, forKey: .title) ?? ""
reportTypeText = try container.decodeIfPresent(String.self, forKey: .reportTypeText) ?? ""
handleStatus = try container.decodeIfPresent(WildReportDecodedHandleStatus.self, forKey: .handleStatus)
handleStatusText = try container.decodeIfPresent(String.self, forKey: .handleStatusText)
distanceM = try container.decodeIfPresent(Double.self, forKey: .distanceM) ?? 0
createdAt = try container.decodeIfPresent(String.self, forKey: .createdAt) ?? ""
tag = try container.decodeIfPresent(String.self, forKey: .tag)
name = try container.decodeIfPresent(String.self, forKey: .name)
avatar = try container.decodeIfPresent(String.self, forKey: .avatar)
storeName = try container.decodeIfPresent(String.self, forKey: .storeName)
online = try container.decodeIfPresent(Bool.self, forKey: .online)
greenMarkerTip = try container.decodeIfPresent(String.self, forKey: .greenMarkerTip)
activityText = try container.decodeIfPresent(String.self, forKey: .activityText)
}
}
///
struct WildReportMarkerDetailResponse: Decodable, Equatable, Hashable {
let markerType: String
let id: Int
let clueId: String
let title: String
let statusTag: String
let tag: String
let name: String
let avatar: String
let storeName: String
let online: Bool
let locationName: String
let latitude: String
let longitude: String
let distanceM: Double
let greenMarkerTip: String
let activityText: String
let reportTypeText: String
let handleStatus: WildReportDecodedHandleStatus?
let handleStatusText: String
let desc: String
let reporterName: String
let reporterPhone: String
let createdAt: String
let reportContents: [WildReportDetailContent]
let evidences: [WildReportDetailEvidence]
enum CodingKeys: String, CodingKey {
case markerType = "marker_type"
case id
case clueId = "clue_id"
case title
case statusTag = "status_tag"
case tag
case name
case avatar
case storeName = "store_name"
case online
case locationName = "location_name"
case latitude
case longitude
case distanceM = "distance_m"
case greenMarkerTip = "green_marker_tip"
case activityText = "activity_text"
case reportTypeText = "report_type_text"
case handleStatus = "handle_status"
case handleStatusText = "handle_status_text"
case desc
case reporterName = "reporter_name"
case reporterPhone = "reporter_phone"
case contactPhone = "contact_phone"
case createdAt = "created_at"
case reportContents = "report_contents"
case evidences
}
init(
markerType: String,
id: Int,
clueId: String = "",
title: String = "",
statusTag: String = "",
tag: String = "",
name: String = "",
avatar: String = "",
storeName: String = "",
online: Bool = false,
locationName: String = "",
latitude: String = "",
longitude: String = "",
distanceM: Double = 0,
greenMarkerTip: String = "",
activityText: String = "",
reportTypeText: String = "",
handleStatus: WildReportDecodedHandleStatus? = nil,
handleStatusText: String = "",
desc: String = "",
reporterName: String = "",
reporterPhone: String = "",
createdAt: String = "",
reportContents: [WildReportDetailContent] = [],
evidences: [WildReportDetailEvidence] = []
) {
self.markerType = markerType
self.id = id
self.clueId = clueId
self.title = title
self.statusTag = statusTag
self.tag = tag
self.name = name
self.avatar = avatar
self.storeName = storeName
self.online = online
self.locationName = locationName
self.latitude = latitude
self.longitude = longitude
self.distanceM = distanceM
self.greenMarkerTip = greenMarkerTip
self.activityText = activityText
self.reportTypeText = reportTypeText
self.handleStatus = handleStatus
self.handleStatusText = handleStatusText
self.desc = desc
self.reporterName = reporterName
self.reporterPhone = reporterPhone
self.createdAt = createdAt
self.reportContents = reportContents
self.evidences = evidences
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
markerType = try container.decodeIfPresent(String.self, forKey: .markerType) ?? ""
id = try container.decodeIfPresent(Int.self, forKey: .id) ?? 0
clueId = try container.decodeIfPresent(String.self, forKey: .clueId) ?? ""
title = try container.decodeIfPresent(String.self, forKey: .title) ?? ""
statusTag = try container.decodeIfPresent(String.self, forKey: .statusTag) ?? ""
tag = try container.decodeIfPresent(String.self, forKey: .tag) ?? ""
name = try container.decodeIfPresent(String.self, forKey: .name) ?? ""
avatar = try container.decodeIfPresent(String.self, forKey: .avatar) ?? ""
storeName = try container.decodeIfPresent(String.self, forKey: .storeName) ?? ""
online = try container.decodeIfPresent(Bool.self, forKey: .online) ?? false
locationName = try container.decodeIfPresent(String.self, forKey: .locationName) ?? ""
latitude = try container.decodeIfPresent(String.self, forKey: .latitude) ?? ""
longitude = try container.decodeIfPresent(String.self, forKey: .longitude) ?? ""
distanceM = try container.decodeIfPresent(Double.self, forKey: .distanceM) ?? 0
greenMarkerTip = try container.decodeIfPresent(String.self, forKey: .greenMarkerTip) ?? ""
activityText = try container.decodeIfPresent(String.self, forKey: .activityText) ?? ""
reportTypeText = try container.decodeIfPresent(String.self, forKey: .reportTypeText) ?? ""
handleStatus = try container.decodeIfPresent(WildReportDecodedHandleStatus.self, forKey: .handleStatus)
handleStatusText = try container.decodeIfPresent(String.self, forKey: .handleStatusText) ?? ""
desc = try container.decodeIfPresent(String.self, forKey: .desc) ?? ""
reporterName = try container.decodeIfPresent(String.self, forKey: .reporterName) ?? ""
reporterPhone = try container.decodeIfPresent(String.self, forKey: .reporterPhone)
?? (try container.decodeIfPresent(String.self, forKey: .contactPhone))
?? ""
createdAt = try container.decodeIfPresent(String.self, forKey: .createdAt) ?? ""
reportContents = try container.decodeIfPresent([WildReportDetailContent].self, forKey: .reportContents) ?? []
evidences = try container.decodeIfPresent([WildReportDetailEvidence].self, forKey: .evidences) ?? []
}
}
/// 线
struct WildReportRadarReportContent: Hashable {
let time: String
let content: String
}
/// 线
struct WildReportRadarProcessRecord: Hashable {
let handlerName: String
let phone: String
let maskedPhone: String
let statusText: String
let processResult: String
let processedAt: String?
let processingAt: String?
}
/// 线
struct WildReportRadarProcessingTimelineItem: Hashable {
let title: String
let time: String
let status: String
}
/// 线
struct WildReportRadarActiveClue: Hashable {
let id: String
let type: WildReportRadarMarkerType
let displayTitle: String
let statusText: String
let actionText: String
let nearestAbnormalText: String?
let distance: String?
let direction: String?
let updatedAt: String?
let name: String?
let storeName: String?
let avatar: String?
let summary: String?
let detail: String?
let distanceText: String?
let reporterName: String?
let reporterPhone: String?
let maskedReporterPhone: String?
let reportContents: [WildReportRadarReportContent]
let images: [String]
let processingStarted: Bool
let completed: Bool
let record: WildReportRadarProcessRecord?
let processingTimeline: [WildReportRadarProcessingTimelineItem]
let completionPhoto: String?
}
///
enum WildReportMapMarkerDetail: Hashable {
case activeClue(WildReportRadarActiveClue)
}
///
struct WildReportMapMarker: Hashable {
let id: String
let title: String
let coordinate: CLLocationCoordinate2D
let kind: WildReportMapMarkerKind
let detail: WildReportMapMarkerDetail
static func == (lhs: WildReportMapMarker, rhs: WildReportMapMarker) -> Bool {
lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
extension CLLocationCoordinate2D: @retroactive Equatable {
public static func == (lhs: CLLocationCoordinate2D, rhs: CLLocationCoordinate2D) -> Bool {
lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude
}
}
private extension String {
var nonEmpty: String? {
let text = trimmingCharacters(in: .whitespacesAndNewlines)
return text.isEmpty ? nil : text
}
}