// // PigeonModel.swift // QuickLocation // import ObjectMapper import UIKit struct PigeonLocalTemplate: Equatable { let id: String let title: String let mediaType: String let coverAssetName: String let contentAssetName: String static let imageTemplates: [PigeonLocalTemplate] = [ PigeonLocalTemplate( id: "come_play", title: "出来玩", mediaType: "image", coverAssetName: "PigeonMessage/template_come_play_cover", contentAssetName: "PigeonMessage/template_come_play" ), PigeonLocalTemplate( id: "miss_you", title: "想你了", mediaType: "image", coverAssetName: "PigeonMessage/template_miss_you_cover", contentAssetName: "PigeonMessage/template_miss_you" ), PigeonLocalTemplate( id: "buy_for_me", title: "给我买", mediaType: "image", coverAssetName: "PigeonMessage/template_buy_for_me_cover", contentAssetName: "PigeonMessage/template_buy_for_me" ), PigeonLocalTemplate( id: "dislike_you", title: "讨厌你", mediaType: "image", coverAssetName: "PigeonMessage/template_dislike_you_cover", contentAssetName: "PigeonMessage/template_dislike_you" ), PigeonLocalTemplate( id: "who_upset_me", title: "谁惹我", mediaType: "image", coverAssetName: "PigeonMessage/template_who_upset_me_cover", contentAssetName: "PigeonMessage/template_who_upset_me" ), PigeonLocalTemplate( id: "apology", title: "道歉", mediaType: "image", coverAssetName: "PigeonMessage/template_apology_cover", contentAssetName: "PigeonMessage/template_apology" ), PigeonLocalTemplate( id: "arriving_soon", title: "马上到", mediaType: "image", coverAssetName: "PigeonMessage/template_arriving_soon_cover", contentAssetName: "PigeonMessage/template_arriving_soon" ), PigeonLocalTemplate( id: "proud", title: "骄傲", mediaType: "image", coverAssetName: "PigeonMessage/template_proud_cover", contentAssetName: "PigeonMessage/template_proud" ) ] var coverImage: UIImage? { UIImage(named: coverAssetName) } var contentImage: UIImage? { UIImage(named: contentAssetName) } static func resolve(templateId: String) -> PigeonLocalTemplate? { imageTemplates.first { $0.id == templateId } } } struct PigeonVoiceTemplate: Equatable { let id: String let title: String let duration: TimeInterval let audioAssetName: String static let templates: [PigeonVoiceTemplate] = [ PigeonVoiceTemplate(id: "sorry_im_right", title: "错不起,我对了", duration: 1.881), PigeonVoiceTemplate(id: "wake_up_sleepyhead", title: "小懒虫,起床啦", duration: 2.821), PigeonVoiceTemplate(id: "dont_you_need_to_work", title: "你们不用打工吗 一天......", duration: 4.049), PigeonVoiceTemplate(id: "ignoring_me", title: "不理我 是吧", duration: 3.527), PigeonVoiceTemplate(id: "who_can_save_me", title: "谁来救救我", duration: 1.228), PigeonVoiceTemplate(id: "baby_dont_be_angry", title: "宝宝,别生气了", duration: 3.056), PigeonVoiceTemplate(id: "okay", title: "好的", duration: 2.377), PigeonVoiceTemplate(id: "thank_you", title: "谢谢你", duration: 0.836), PigeonVoiceTemplate(id: "good_night_kiss", title: "我先睡了 晚安 么么哒", duration: 2.299), PigeonVoiceTemplate(id: "sleep_if_you_want", title: "如果你想睡觉就晚安", duration: 5.956), PigeonVoiceTemplate(id: "getting_bold", title: "我看你胆子真是肥嘟嘟", duration: 2.534), PigeonVoiceTemplate(id: "mosquito_violin", title: "再不理我我就派蚊子去你耳边拉小提琴", duration: 5.721), PigeonVoiceTemplate(id: "am_i_talkative", title: "我多嘴问一句我多嘴吗", duration: 6.949), PigeonVoiceTemplate(id: "is_my_mic_loud", title: "我的麦很炸吗", duration: 3.396), PigeonVoiceTemplate(id: "love_you", title: "爱你哟", duration: 1.019), PigeonVoiceTemplate(id: "what_can_you_do", title: "说白了 你有啥实力啊", duration: 3.056), PigeonVoiceTemplate(id: "save_me", title: "救我救我", duration: 1.489), PigeonVoiceTemplate(id: "dare_to_compete", title: "敢不敢和我比划比划", duration: 1.646), PigeonVoiceTemplate(id: "teasing_you_daily", title: "其实每天骚扰你就是我最大的乐趣", duration: 3.448) ] private init(id: String, title: String, duration: TimeInterval) { self.id = id self.title = title self.duration = duration audioAssetName = "PigeonMessage/voice_\(id)" } var audioData: Data? { NSDataAsset(name: audioAssetName)?.data } static func resolve(templateId: String) -> PigeonVoiceTemplate? { templates.first { $0.id == templateId } } } struct PigeonTemplateExtra: Mappable { var image_template_id: String = "" var voice_template_id: String = "" init?(map: Map) {} static func decode(_ value: Any?) -> PigeonTemplateExtra? { if let extra = value as? PigeonTemplateExtra { return extra } if let json = value as? [String: Any] { return PigeonTemplateExtra(JSON: json) } let data: Data? if let value = value as? String { data = value.data(using: .utf8) } else { data = value as? Data } guard let data, let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { return nil } return PigeonTemplateExtra(JSON: json) } mutating func mapping(map: Map) { image_template_id <- map["image_template_id"] voice_template_id <- map["voice_template_id"] var legacyMediaType = "" var legacyTemplateID = "" legacyMediaType <- map["media_type"] legacyTemplateID <- map["template_id"] let legacyID = legacyTemplateID.trimmingCharacters(in: .whitespacesAndNewlines) guard !legacyID.isEmpty else { return } switch legacyMediaType.trimmingCharacters(in: .whitespacesAndNewlines).lowercased() { case "voice": if voice_template_id.isEmpty { voice_template_id = legacyID } case "image": if image_template_id.isEmpty { image_template_id = legacyID } default: if image_template_id.isEmpty, PigeonLocalTemplate.resolve(templateId: legacyID) != nil { image_template_id = legacyID } else if voice_template_id.isEmpty, PigeonVoiceTemplate.resolve(templateId: legacyID) != nil { voice_template_id = legacyID } } } var normalizedImageTemplateID: String { image_template_id.trimmingCharacters(in: .whitespacesAndNewlines) } var normalizedVoiceTemplateID: String { voice_template_id.trimmingCharacters(in: .whitespacesAndNewlines) } var sendParameters: [String: Any]? { var parameters: [String: Any] = [:] if !normalizedImageTemplateID.isEmpty { parameters["image_template_id"] = normalizedImageTemplateID } if !normalizedVoiceTemplateID.isEmpty { parameters["voice_template_id"] = normalizedVoiceTemplateID } return parameters.isEmpty ? nil : parameters } } private let kPigeonTemplateExtraTransform = TransformOf(fromJSON: { value in PigeonTemplateExtra.decode(value) }, toJSON: { value in value?.sendParameters }) struct PigeonSentResponse: BaseModelProtocol { var code: String? var message: String? var list: [PigeonSentMessage] = [] init?(map: Map) {} mutating func mapping(map: Map) { code <- (map["code"], kIntTransformStr) message <- map["message"] list <- map["data"] } } struct PigeonSentMessage: Mappable { var message_uuid: String = "" var group_key: String = "" var from_user: PigeonSentUser? var to_user: [PigeonSentUser] = [] var msg_type: Int = 0 var msg: String = "" var text_msg: String = "" var bg_img: String = "" var extra: PigeonTemplateExtra? var expire_time: Int64 = 0 var send_time: Int64 = 0 init?(map: Map) {} mutating func mapping(map: Map) { message_uuid <- map["message_uuid"] group_key <- map["group_key"] from_user <- map["from_user"] to_user <- map["to_user"] msg_type <- map["msg_type"] msg <- map["msg"] text_msg <- map["text_msg"] bg_img <- map["bg_img"] extra <- (map["extra"], kPigeonTemplateExtraTransform) expire_time <- map["expire_time"] send_time <- map["send_time"] } var isVoice: Bool { msg_type == 2 } var localTemplate: PigeonLocalTemplate? { let templateId = extra?.normalizedImageTemplateID ?? "" return PigeonLocalTemplate.resolve(templateId: templateId) } var localTemplateImage: UIImage? { localTemplate?.contentImage } var localVoiceTemplate: PigeonVoiceTemplate? { let templateId = extra?.normalizedVoiceTemplateID ?? "" return PigeonVoiceTemplate.resolve(templateId: templateId) } var localVoiceData: Data? { localVoiceTemplate?.audioData } var captionText: String { if isVoice { return "" } let fromMsg = msg.trimmingCharacters(in: .whitespacesAndNewlines) if !fromMsg.isEmpty, !fromMsg.lowercased().hasPrefix("http") { return fromMsg } return text_msg } var backgroundURL: URL? { let background = bg_img.trimmingCharacters(in: .whitespacesAndNewlines) let legacyImage = isVoice ? "" : msg.trimmingCharacters(in: .whitespacesAndNewlines) let raw = background.lowercased().hasPrefix("http") ? background : legacyImage guard raw.lowercased().hasPrefix("http"), let url = URL(string: raw) else { return nil } return url } var audioURL: URL? { guard isVoice else { return nil } let raw = msg.trimmingCharacters(in: .whitespacesAndNewlines) guard raw.lowercased().hasPrefix("http"), let url = URL(string: raw) else { return nil } return url } var mediaURL: URL? { isVoice ? audioURL : backgroundURL } func toHistoryItem() -> PigeonHistoryItem { PigeonHistoryItem( id: message_uuid.isEmpty ? UUID().uuidString : message_uuid, dateText: Self.dateText(from: send_time), timeText: Self.timeText(from: send_time), kind: isVoice ? .voice : .image, caption: captionText, image: localTemplateImage, mediaURL: mediaURL, localAudioData: localVoiceData, senderAvatar: from_user?.avatarImage, receiverAvatars: to_user.map(\.avatarImage), duration: localVoiceTemplate?.duration ?? 0 ) } private static func date(from timestamp: Int64) -> Date { Date(timeIntervalSince1970: TimeInterval(timestamp)) } private static func dateText(from timestamp: Int64) -> String { let date = date(from: timestamp) let calendar = Calendar.current let month = calendar.component(.month, from: date) let day = calendar.component(.day, from: date) let weekdays = ["周日", "周一", "周二", "周三", "周四", "周五", "周六"] let weekday = weekdays[calendar.component(.weekday, from: date) - 1] return "\(month).\(day) \(weekday)" } private static func timeText(from timestamp: Int64) -> String { let date = date(from: timestamp) let calendar = Calendar.current let hour = calendar.component(.hour, from: date) let minute = calendar.component(.minute, from: date) let period = hour < 12 ? "上午" : "下午" let hour12 = hour % 12 == 0 ? 12 : hour % 12 return String(format: "%@ %d:%02d", period, hour12, minute) } } struct PigeonSentUser: Mappable { var user_id: String = "" var nick_name: String = "" var remark: String = "" var head_pic: String = "" var extra = MemberExtra() init?(map: Map) {} mutating func mapping(map: Map) { user_id <- map["user_id"] nick_name <- map["nick_name"] remark <- map["remark"] head_pic <- map["head_pic"] extra <- (map["extra"], kMemberExtraTransform) if extra.relation_idx.isEmpty { extra.relation_idx <- (map["relation_idx"], kMemberRelationIndexTransform) } } var displayName: String { let remarkName = remark.trimmingCharacters(in: .whitespacesAndNewlines) if !remarkName.isEmpty { return remarkName } return nick_name } var avatarImage: UIImage { let iconName = head_pic.trimmingCharacters(in: .whitespacesAndNewlines) guard !iconName.isEmpty, let image = UIImage(named: "UserIcon/\(iconName)") else { return UIImage(named: "UserIcon/1") ?? UIImage() } return image } }