Files
汉秋 d99a5b1bf8 Advance UIKit rewrite with AMap integration and core UI modules.
Integrate高德 SDK with simulator-safe build flags, add map views for operating area and punch points, refactor main tabs and key feature screens to UIKit with Diffable lists, and document Swift concurrency defaults in AGENTS.md.

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

857 lines
32 KiB
Swift
Raw Permalink 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.

//
// OrderModels.swift
// suixinkan
//
// Created by Codex on 2026/6/22.
//
import Foundation
/// Tab
enum OrdersEntry: Hashable {
case storeOrders
case verificationOrders
}
///
struct OrderStatusFilter: Identifiable, Hashable {
let id: Int
let title: String
}
///
enum OrderFilters {
static let statusFilters: [OrderStatusFilter] = [
.init(id: 0, title: "全部"),
.init(id: 18, title: "已付定金"),
.init(id: 30, title: "已完成"),
.init(id: 50, title: "已退款"),
.init(id: -1, title: "需要精修"),
.init(id: -2, title: "无需精修")
]
}
///
struct OrderEntity: Decodable, Identifiable, Equatable, Hashable {
var id: String { orderNumber }
let photogUid: Int
let orderNumber: String
let remark: String
let scenicAreaId: Int
let payTime: String
let completeTime: String
let orderStatus: Int
let orderStatusName: String
let storeId: Int?
let userId: Int
let projectId: Int
let phone: String
let orderAmount: String
let actualPayAmount: String
let actualRefundAmount: String
let refundAmount: String
let payTypeName: String
let payType: Int
let projectName: String
let orderType: Int
let orderTypeLabel: String
let depositPayTime: String
let createdAt: String
let photoTravel: OrderPhotoTravel?
let isNeedEdit: Bool
let isRefined: Int
///
enum CodingKeys: String, CodingKey {
case photogUid = "photog_uid"
case orderNumber = "order_number"
case remark
case scenicAreaId = "scenic_area_id"
case payTime = "pay_time"
case completeTime = "complete_time"
case orderStatus = "order_status"
case orderStatusName = "order_status_name"
case storeId = "store_id"
case userId = "user_id"
case projectId = "project_id"
case phone
case orderAmount = "order_amount"
case actualPayAmount = "actual_pay_amount"
case actualRefundAmount = "actual_refund_amount"
case refundAmount = "refund_amount"
case payTypeName = "pay_type_name"
case payType = "pay_type"
case projectName = "project_name"
case orderType = "order_type"
case orderTypeLabel = "order_type_label"
case depositPayTime = "deposit_pay_time"
case createdAt = "created_at"
case photoTravel = "photo_travel"
case isNeedEdit
case isRefined = "is_refined"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
photogUid = try container.decodeLossyInt(forKey: .photogUid) ?? 0
orderNumber = try container.decodeLossyString(forKey: .orderNumber)
remark = try container.decodeLossyString(forKey: .remark)
scenicAreaId = try container.decodeLossyInt(forKey: .scenicAreaId) ?? 0
payTime = try container.decodeLossyString(forKey: .payTime)
completeTime = try container.decodeLossyString(forKey: .completeTime)
orderStatus = try container.decodeLossyInt(forKey: .orderStatus) ?? 0
orderStatusName = try container.decodeLossyString(forKey: .orderStatusName)
storeId = try container.decodeLossyInt(forKey: .storeId)
userId = try container.decodeLossyInt(forKey: .userId) ?? 0
projectId = try container.decodeLossyInt(forKey: .projectId) ?? 0
phone = try container.decodeLossyString(forKey: .phone)
orderAmount = try container.decodeLossyString(forKey: .orderAmount)
actualPayAmount = try container.decodeLossyString(forKey: .actualPayAmount)
actualRefundAmount = try container.decodeLossyString(forKey: .actualRefundAmount)
refundAmount = try container.decodeLossyString(forKey: .refundAmount)
payTypeName = try container.decodeLossyString(forKey: .payTypeName)
payType = try container.decodeLossyInt(forKey: .payType) ?? 0
projectName = try container.decodeLossyString(forKey: .projectName)
orderType = try container.decodeLossyInt(forKey: .orderType) ?? 0
orderTypeLabel = try container.decodeLossyString(forKey: .orderTypeLabel)
depositPayTime = try container.decodeLossyString(forKey: .depositPayTime)
createdAt = try container.decodeLossyString(forKey: .createdAt)
photoTravel = try container.decodeIfPresent(OrderPhotoTravel.self, forKey: .photoTravel)
isNeedEdit = try container.decodeLossyBool(forKey: .isNeedEdit) ?? true
isRefined = try container.decodeLossyInt(forKey: .isRefined) ?? 0
}
}
///
struct OrderPhotoTravel: Decodable, Equatable, Hashable {
let orderPhotoNum: Int
let orderVideoNum: Int
let checkInTime: String
let retouchGiftPhotoNum: Int
let retouchGiftVideoNum: Int
///
enum CodingKeys: String, CodingKey {
case orderPhotoNum = "order_photo_num"
case orderVideoNum = "order_video_num"
case checkInTime = "check_in_time"
case retouchGiftPhotoNum = "retouch_gift_photo_num"
case retouchGiftVideoNum = "retouch_gift_video_num"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
orderPhotoNum = try container.decodeLossyInt(forKey: .orderPhotoNum) ?? 0
orderVideoNum = try container.decodeLossyInt(forKey: .orderVideoNum) ?? 0
checkInTime = try container.decodeLossyString(forKey: .checkInTime)
retouchGiftPhotoNum = try container.decodeLossyInt(forKey: .retouchGiftPhotoNum) ?? 0
retouchGiftVideoNum = try container.decodeLossyInt(forKey: .retouchGiftVideoNum) ?? 0
}
}
///
struct WriteOffOrderItem: Decodable, Identifiable, Equatable, Hashable {
var id: String { orderNumber }
let orderNumber: String
let orderVerificationStatus: String
let payTime: String
let projectName: String
let userPhone: String
let orderAmount: String
let orderStatusName: String
let orderVerificationTime: String
///
enum CodingKeys: String, CodingKey {
case orderNumber = "order_number"
case orderVerificationStatus = "order_verification_status"
case payTime = "pay_time"
case projectName = "project_name"
case userPhone = "user_phone"
case orderAmount = "order_amount"
case orderStatusName = "order_status_name"
case orderVerificationTime = "order_verification_time"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
orderNumber = try container.decodeLossyString(forKey: .orderNumber)
orderVerificationStatus = try container.decodeLossyString(forKey: .orderVerificationStatus)
payTime = try container.decodeLossyString(forKey: .payTime)
projectName = try container.decodeLossyString(forKey: .projectName)
userPhone = try container.decodeLossyString(forKey: .userPhone)
orderAmount = try container.decodeLossyString(forKey: .orderAmount)
orderStatusName = try container.decodeLossyString(forKey: .orderStatusName)
orderVerificationTime = try container.decodeLossyString(forKey: .orderVerificationTime)
}
}
///
struct WriteOffRequest: Encodable {
let orderNumber: String
///
enum CodingKeys: String, CodingKey {
case orderNumber = "order_number"
}
}
///
struct DepositOrderListItem: Decodable, Identifiable, Equatable, Hashable {
let id: Int
let orderNumber: String
let userPhone: String
let amount: String
let status: Int
let statusName: String
let createdAt: String
///
enum CodingKeys: String, CodingKey {
case id
case orderNumber = "order_number"
case userPhone = "user_phone"
case amount
case status
case statusName = "status_name"
case createdAt = "created_at"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeLossyInt(forKey: .id) ?? 0
orderNumber = try container.decodeLossyString(forKey: .orderNumber)
userPhone = try container.decodeLossyString(forKey: .userPhone)
amount = try container.decodeLossyString(forKey: .amount)
status = try container.decodeLossyInt(forKey: .status) ?? 0
statusName = try container.decodeLossyString(forKey: .statusName)
createdAt = try container.decodeLossyString(forKey: .createdAt)
}
}
///
struct DepositOrderWriteOffRequest: Encodable, Equatable {
let orderNumber: String
///
enum CodingKeys: String, CodingKey {
case orderNumber = "order_number"
}
}
/// 退
struct DepositOrderRefundRequest: Encodable, Equatable {
let orderNumber: String
let refundReason: String
///
enum CodingKeys: String, CodingKey {
case orderNumber = "order_number"
case refundReason = "refund_reason"
}
}
/// 退退退
enum OrderRefundMode: Int, CaseIterable, Identifiable, Hashable {
case full = 1
case partial = 3
var id: Int { rawValue }
/// 退
var title: String {
switch self {
case .full:
return "全额退款"
case .partial:
return "部分退款"
}
}
}
/// 退
struct OrderRefundRequest: Encodable, Equatable {
let orderNumber: String
let refundType: Int
let refundAmount: String
let refundReason: String
///
enum CodingKeys: String, CodingKey {
case orderNumber = "order_number"
case refundType = "refund_type"
case refundAmount = "refund_amount"
case refundReason = "refund_reason"
}
}
/// push
enum OrdersRoute: Hashable {
case storeDetail(OrderEntity)
case writeOffDetail(WriteOffOrderItem)
case depositDetail(orderNumber: String)
case depositShootingInfo(orderNumber: String, scenicSpotId: Int, photogUid: Int)
case historicalShooting(orderNumber: String)
case multiTravelTaskUpload(orderNumber: String)
case orderTrailer(orderNumber: String, title: String)
}
///
struct StoreOrderDetailResponse: Decodable, Equatable, Hashable {
let orderNumber: String
let orderType: Int
let orderTypeLabel: String
let createdAt: String
let orderStatus: Int
let orderStatusName: String
let actualPayAmount: String
let actualRefundAmount: String
let payTypeName: String
let payTime: String
let completeTime: String
let userId: Int
let phone: String
let projectId: Int
let projectName: String
let remark: String
let multiTravel: StoreOrderMultiTravelInfo?
///
enum CodingKeys: String, CodingKey {
case orderNumber = "order_number"
case orderType = "order_type"
case orderTypeLabel = "order_type_label"
case createdAt = "created_at"
case orderStatus = "order_status"
case orderStatusName = "order_status_name"
case actualPayAmount = "actual_pay_amount"
case actualRefundAmount = "actual_refund_amount"
case payTypeName = "pay_type_name"
case payTime = "pay_time"
case completeTime = "complete_time"
case userId = "user_id"
case phone
case projectId = "project_id"
case projectName = "project_name"
case remark
case multiTravel = "multi_travel"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
orderNumber = try container.decodeLossyString(forKey: .orderNumber)
orderType = try container.decodeLossyInt(forKey: .orderType) ?? 0
orderTypeLabel = try container.decodeLossyString(forKey: .orderTypeLabel)
createdAt = try container.decodeLossyString(forKey: .createdAt)
orderStatus = try container.decodeLossyInt(forKey: .orderStatus) ?? 0
orderStatusName = try container.decodeLossyString(forKey: .orderStatusName)
actualPayAmount = try container.decodeLossyString(forKey: .actualPayAmount)
actualRefundAmount = try container.decodeLossyString(forKey: .actualRefundAmount)
payTypeName = try container.decodeLossyString(forKey: .payTypeName)
payTime = try container.decodeLossyString(forKey: .payTime)
completeTime = try container.decodeLossyString(forKey: .completeTime)
userId = try container.decodeLossyInt(forKey: .userId) ?? 0
phone = try container.decodeLossyString(forKey: .phone)
projectId = try container.decodeLossyInt(forKey: .projectId) ?? 0
projectName = try container.decodeLossyString(forKey: .projectName)
remark = try container.decodeLossyString(forKey: .remark)
multiTravel = try container.decodeIfPresent(StoreOrderMultiTravelInfo.self, forKey: .multiTravel)
}
}
///
struct StoreOrderMultiTravelInfo: Decodable, Equatable, Hashable {
let projectInfo: StoreOrderProjectInfo?
let shootingList: [StoreOrderShootingListItem]
///
enum CodingKeys: String, CodingKey {
case projectInfo = "project_info"
case shootingList = "shooting_list"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
projectInfo = try container.decodeIfPresent(StoreOrderProjectInfo.self, forKey: .projectInfo)
shootingList = try container.decodeIfPresent([StoreOrderShootingListItem].self, forKey: .shootingList) ?? []
}
}
///
struct StoreOrderProjectInfo: Decodable, Equatable, Hashable {
let settleSpotNum: Int
let singleSpotMaterialNum: Int
let singleSpotPhotoNum: Int
let singleSpotVideoNum: Int
///
enum CodingKeys: String, CodingKey {
case settleSpotNum = "settle_spot_num"
case singleSpotMaterialNum = "single_spot_material_num"
case singleSpotPhotoNum = "single_spot_photo_num"
case singleSpotVideoNum = "single_spot_video_num"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
settleSpotNum = try container.decodeLossyInt(forKey: .settleSpotNum) ?? 0
singleSpotMaterialNum = try container.decodeLossyInt(forKey: .singleSpotMaterialNum) ?? 0
singleSpotPhotoNum = try container.decodeLossyInt(forKey: .singleSpotPhotoNum) ?? 0
singleSpotVideoNum = try container.decodeLossyInt(forKey: .singleSpotVideoNum) ?? 0
}
}
///
struct StoreOrderShootingListItem: Decodable, Identifiable, Equatable, Hashable {
var id: String { "\(scenicSpotId)-\(photogUid)" }
let scenicSpotId: Int
let photogUid: Int
let scenicSpotName: String
let staffName: String
let photogName: String
let status: Int
let startAvg: Double
let start: Double
///
enum CodingKeys: String, CodingKey {
case scenicSpotId = "scenic_spot_id"
case photogUid = "photog_uid"
case scenicSpotName = "scenic_spot_name"
case staffName = "staff_name"
case photogName = "photog_name"
case status
case startAvg = "start_avg"
case start
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
scenicSpotId = try container.decodeLossyInt(forKey: .scenicSpotId) ?? 0
photogUid = try container.decodeLossyInt(forKey: .photogUid) ?? 0
scenicSpotName = try container.decodeLossyString(forKey: .scenicSpotName)
staffName = try container.decodeLossyString(forKey: .staffName)
photogName = try container.decodeLossyString(forKey: .photogName)
status = try container.decodeLossyInt(forKey: .status) ?? 0
startAvg = try container.decodeLossyDouble(forKey: .startAvg) ?? 0
start = try container.decodeLossyDouble(forKey: .start) ?? 0
}
}
///
struct StoreOrderShootingDetailResponse: Decodable, Equatable, Hashable {
let scenicSpotId: Int
let scenicSpotName: String
let orderType: Int
let orderTypeName: String
let orderComment: StoreOrderComment?
let materialList: [OrderMediaFile]
let completeList: [OrderMediaFile]
///
enum CodingKeys: String, CodingKey {
case scenicSpotId = "scenic_spot_id"
case scenicSpotName = "scenic_spot_name"
case orderType = "order_type"
case orderTypeName = "order_type_name"
case orderComment = "order_comment"
case materialList = "material_list"
case completeList = "complete_list"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
scenicSpotId = try container.decodeLossyInt(forKey: .scenicSpotId) ?? 0
scenicSpotName = try container.decodeLossyString(forKey: .scenicSpotName)
orderType = try container.decodeLossyInt(forKey: .orderType) ?? 0
orderTypeName = try container.decodeLossyString(forKey: .orderTypeName)
orderComment = try container.decodeIfPresent(StoreOrderComment.self, forKey: .orderComment)
materialList = try container.decodeIfPresent([OrderMediaFile].self, forKey: .materialList) ?? []
completeList = try container.decodeIfPresent([OrderMediaFile].self, forKey: .completeList) ?? []
}
}
///
struct StoreOrderComment: Decodable, Equatable, Hashable {
let starShooting: Int
let starRetouching: Int
let starScenery: Int
let starService: Int
let starCamera: Int
let content: String
let createdAt: String
///
enum CodingKeys: String, CodingKey {
case starShooting = "star_shooting"
case starRetouching = "star_retouching"
case starScenery = "star_scenery"
case starService = "star_service"
case starCamera = "star_camera"
case content
case createdAt = "created_at"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
starShooting = try container.decodeLossyInt(forKey: .starShooting) ?? 0
starRetouching = try container.decodeLossyInt(forKey: .starRetouching) ?? 0
starScenery = try container.decodeLossyInt(forKey: .starScenery) ?? 0
starService = try container.decodeLossyInt(forKey: .starService) ?? 0
starCamera = try container.decodeLossyInt(forKey: .starCamera) ?? 0
content = try container.decodeLossyString(forKey: .content)
createdAt = try container.decodeLossyString(forKey: .createdAt)
}
}
///
struct OrderMediaFile: Decodable, Identifiable, Equatable, Hashable {
var id: String { "\(fileUrl)-\(fileName)-\(uploadTime)" }
let fileName: String
let fileUrl: String
let fileType: Int
let fileSize: Int64
let coverUrl: String
let uploadTime: String
var isVideo: Bool {
fileType == 1
}
var previewURLString: String {
coverUrl.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? fileUrl : coverUrl
}
///
enum CodingKeys: String, CodingKey {
case fileName = "file_name"
case fileUrl = "file_url"
case fileType = "file_type"
case fileSize = "file_size"
case coverUrl = "cover_url"
case uploadTime = "upload_time"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
fileName = try container.decodeLossyString(forKey: .fileName)
fileUrl = try container.decodeLossyString(forKey: .fileUrl)
fileType = try container.decodeLossyInt(forKey: .fileType) ?? 0
fileSize = Int64(try container.decodeLossyInt(forKey: .fileSize) ?? 0)
coverUrl = try container.decodeLossyString(forKey: .coverUrl)
uploadTime = try container.decodeLossyString(forKey: .uploadTime)
}
}
///
struct MultiTravelShootHistoryResponse: Decodable, Equatable, Hashable {
let projectName: String
let projectType: Int
let projectTypeName: String
let photogSpotList: [PhotogSpotItem]
///
enum CodingKeys: String, CodingKey {
case projectName = "project_name"
case projectType = "project_type"
case projectTypeName = "project_type_name"
case photogSpotList = "photog_spot_list"
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
projectName = try container.decodeLossyString(forKey: .projectName)
projectType = try container.decodeLossyInt(forKey: .projectType) ?? 0
projectTypeName = try container.decodeLossyString(forKey: .projectTypeName)
photogSpotList = try container.decodeIfPresent([PhotogSpotItem].self, forKey: .photogSpotList) ?? []
}
}
///
struct PhotogSpotItem: Decodable, Identifiable, Equatable, Hashable {
var id: String { "\(scenicSpotId)-\(photogUid)" }
let scenicSpotId: Int
let photogUid: Int
let scenicSpotName: String
let photogNickname: String
let photogName: String
let files: [OrderMediaFile]
var photographerDisplayName: String {
let nickname = photogNickname.trimmingCharacters(in: .whitespacesAndNewlines)
return nickname.isEmpty ? photogName : nickname
}
///
enum CodingKeys: String, CodingKey {
case scenicSpotId = "scenic_spot_id"
case photogUid = "photog_uid"
case scenicSpotName = "scenic_spot_name"
case photogNickname = "photog_nickname"
case photogName = "photog_name"
case files
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
scenicSpotId = try container.decodeLossyInt(forKey: .scenicSpotId) ?? 0
photogUid = try container.decodeLossyInt(forKey: .photogUid) ?? 0
scenicSpotName = try container.decodeLossyString(forKey: .scenicSpotName)
photogNickname = try container.decodeLossyString(forKey: .photogNickname)
photogName = try container.decodeLossyString(forKey: .photogName)
files = try container.decodeIfPresent([OrderMediaFile].self, forKey: .files) ?? []
}
}
///
struct MultiTravelVerifiedScenicSpotItem: Decodable, Identifiable, Equatable, Hashable {
let id: Int
let name: String
///
enum CodingKeys: String, CodingKey {
case id
case name
}
///
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeLossyInt(forKey: .id) ?? 0
name = try container.decodeLossyString(forKey: .name)
}
}
///
struct MultiTravelUploadMaterialRequest: Encodable, Equatable {
let orderNumber: String
let scenicSpotId: Int
let cloudFile: [MultiTravelCloudFileItem]
let uploadFile: [MultiTravelUploadFileItem]
///
enum CodingKeys: String, CodingKey {
case orderNumber = "order_number"
case scenicSpotId = "scenic_spot_id"
case cloudFile = "cloud_file"
case uploadFile = "upload_file"
}
}
///
struct MultiTravelCloudFileItem: Encodable, Equatable {
let fileId: Int
///
enum CodingKeys: String, CodingKey {
case fileId = "file_id"
}
}
///
struct MultiTravelUploadFileItem: Encodable, Equatable {
let fileName: String
let fileUrl: String
///
enum CodingKeys: String, CodingKey {
case fileName = "file_name"
case fileUrl = "file_url"
}
}
///
struct StoreOrderDetailDisplay: Equatable {
let orderNumber: String
let orderTypeLabel: String
let createdAt: String
let orderStatusName: String
let actualPayAmount: String
let actualRefundAmount: String
let payTypeName: String
let payTime: String
let completeTime: String
let userId: Int
let phone: String
let projectId: Int
let projectName: String
let remark: String
/// 使
init(item: OrderEntity) {
orderNumber = item.orderNumber
orderTypeLabel = item.orderTypeLabel
createdAt = item.createdAt
orderStatusName = item.orderStatusName
actualPayAmount = item.actualPayAmount
actualRefundAmount = item.actualRefundAmount.isEmpty ? item.refundAmount : item.actualRefundAmount
payTypeName = item.payTypeName
payTime = item.payTime
completeTime = item.completeTime
userId = item.userId
phone = item.phone
projectId = item.projectId
projectName = item.projectName
remark = item.remark
}
/// 使
init(detail: StoreOrderDetailResponse) {
orderNumber = detail.orderNumber
orderTypeLabel = detail.orderTypeLabel
createdAt = detail.createdAt
orderStatusName = detail.orderStatusName
actualPayAmount = detail.actualPayAmount
actualRefundAmount = detail.actualRefundAmount
payTypeName = detail.payTypeName
payTime = detail.payTime
completeTime = detail.completeTime
userId = detail.userId
phone = detail.phone
projectId = detail.projectId
projectName = detail.projectName
remark = detail.remark
}
}
///
enum OrderNumberParser {
/// URL
static func parse(_ raw: String) -> String? {
let text = raw.trimmingCharacters(in: .whitespacesAndNewlines)
guard !text.isEmpty else { return nil }
if let url = URL(string: text),
let components = URLComponents(url: url, resolvingAgainstBaseURL: false),
let queryItems = components.queryItems,
let value = queryItems.first(where: { $0.name.lowercased() == "order_number" })?.value,
!value.isEmpty {
return value
}
let patterns = [
"order[_-]?number[=:]([A-Za-z0-9_-]+)",
"([A-Za-z0-9_-]{8,})"
]
for pattern in patterns {
if let match = firstCapture(in: text, pattern: pattern) {
return match
}
}
if !text.contains(" "), text.count >= 6 {
return text
}
return nil
}
///
private static func firstCapture(in text: String, pattern: String) -> String? {
guard let regex = try? NSRegularExpression(pattern: pattern, options: [.caseInsensitive]) else {
return nil
}
let range = NSRange(text.startIndex..<text.endIndex, in: text)
guard let match = regex.firstMatch(in: text, options: [], range: range),
match.numberOfRanges > 1,
let captureRange = Range(match.range(at: 1), in: text) else {
return nil
}
return String(text[captureRange])
}
}
private extension KeyedDecodingContainer {
/// JSON
func decodeLossyString(forKey key: Key) throws -> String {
if let value = try? decodeIfPresent(String.self, forKey: key) {
return value
}
if let value = try? decodeIfPresent(Int.self, forKey: key) {
return String(value)
}
if let value = try? decodeIfPresent(Double.self, forKey: key) {
return String(value)
}
if let value = try? decodeIfPresent(Bool.self, forKey: key) {
return value ? "1" : "0"
}
return ""
}
/// IntDoubleBool
func decodeLossyInt(forKey key: Key) throws -> Int? {
if let value = try? decodeIfPresent(Int.self, forKey: key) {
return value
}
if let value = try? decodeIfPresent(String.self, forKey: key) {
let text = value.trimmingCharacters(in: .whitespacesAndNewlines)
if let intValue = Int(text) {
return intValue
}
if let doubleValue = Double(text) {
return Int(doubleValue)
}
}
if let value = try? decodeIfPresent(Double.self, forKey: key) {
return Int(value)
}
if let value = try? decodeIfPresent(Bool.self, forKey: key) {
return value ? 1 : 0
}
return nil
}
/// BoolInt
func decodeLossyBool(forKey key: Key) throws -> Bool? {
if let value = try? decodeIfPresent(Bool.self, forKey: key) {
return value
}
if let value = try? decodeIfPresent(Int.self, forKey: key) {
return value != 0
}
if let value = try? decodeIfPresent(String.self, forKey: key) {
switch value.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() {
case "1", "true", "yes":
return true
case "0", "false", "no":
return false
default:
return nil
}
}
return nil
}
/// DoubleInt
func decodeLossyDouble(forKey key: Key) throws -> Double? {
if let value = try? decodeIfPresent(Double.self, forKey: key) {
return value
}
if let value = try? decodeIfPresent(Int.self, forKey: key) {
return Double(value)
}
if let value = try? decodeIfPresent(String.self, forKey: key) {
return Double(value.trimmingCharacters(in: .whitespacesAndNewlines))
}
return nil
}
}