Files
suixinkan_uikit/suixinkan/Features/MessageCenter/Models/MessageCenterModels.swift

302 lines
11 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.

//
// MessageCenterModels.swift
// suixinkan
//
import Foundation
/// JSON `extra_data`
enum MessageJSONValue: Decodable, Hashable, Sendable {
case string(String)
case number(Double)
case bool(Bool)
case object([String: MessageJSONValue])
case array([MessageJSONValue])
case null
init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
if container.decodeNil() {
self = .null
} else if let value = try? container.decode(Bool.self) {
self = .bool(value)
} else if let value = try? container.decode(Double.self) {
self = .number(value)
} else if let value = try? container.decode(String.self) {
self = .string(value)
} else if let value = try? container.decode([MessageJSONValue].self) {
self = .array(value)
} else {
self = .object(try container.decode([String: MessageJSONValue].self))
}
}
}
/// Android `MsgResponse`
struct MessageListResponse: Decodable, Equatable, Sendable {
let hasMore: Bool
let lastId: Int
let items: [MessageItem]
let isRefresh: Bool
enum CodingKeys: String, CodingKey {
case hasMore = "has_more"
case lastId = "last_id"
case items
case isRefresh = "is_refresh"
}
init(hasMore: Bool = false, lastId: Int = 0, items: [MessageItem] = [], isRefresh: Bool = false) {
self.hasMore = hasMore
self.lastId = lastId
self.items = items
self.isRefresh = isRefresh
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
hasMore = try container.decodeLossyBool(forKey: .hasMore) ?? false
lastId = try container.decodeLossyInt(forKey: .lastId) ?? 0
items = (try? container.decodeIfPresent([MessageItem].self, forKey: .items)) ?? []
isRefresh = try container.decodeLossyBool(forKey: .isRefresh) ?? false
}
}
/// Android `MsgEntity`
struct MessageItem: Decodable, Hashable, Sendable {
let id: Int
let receiverId: Int
let receiverType: String
let type: Int
let typeName: String
let title: String
let content: String
let pushAt: String
let isRead: Bool
let readAt: String
let pushChannel: Int
let pushChannelName: String
let extraData: [String: MessageJSONValue]?
let createdAt: String
let updatedAt: String
enum CodingKeys: String, CodingKey {
case id
case receiverId = "receiver_id"
case receiverType = "receiver_type"
case type
case typeName = "type_name"
case title
case content
case pushAt = "push_at"
case isRead = "is_read"
case readAt = "read_at"
case pushChannel = "push_channel"
case pushChannelName = "push_channel_name"
case extraData = "extra_data"
case createdAt = "created_at"
case updatedAt = "updated_at"
}
init(
id: Int = 0,
receiverId: Int = 0,
receiverType: String = "",
type: Int = 0,
typeName: String = "",
title: String = "",
content: String = "",
pushAt: String = "",
isRead: Bool = false,
readAt: String = "",
pushChannel: Int = 0,
pushChannelName: String = "",
extraData: [String: MessageJSONValue]? = nil,
createdAt: String = "",
updatedAt: String = ""
) {
self.id = id
self.receiverId = receiverId
self.receiverType = receiverType
self.type = type
self.typeName = typeName
self.title = title
self.content = content
self.pushAt = pushAt
self.isRead = isRead
self.readAt = readAt
self.pushChannel = pushChannel
self.pushChannelName = pushChannelName
self.extraData = extraData
self.createdAt = createdAt
self.updatedAt = updatedAt
}
init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
id = try container.decodeLossyInt(forKey: .id) ?? 0
receiverId = try container.decodeLossyInt(forKey: .receiverId) ?? 0
receiverType = try container.decodeLossyString(forKey: .receiverType)
type = try container.decodeLossyInt(forKey: .type) ?? 0
typeName = try container.decodeLossyString(forKey: .typeName)
title = try container.decodeLossyString(forKey: .title)
content = try container.decodeLossyString(forKey: .content)
pushAt = try container.decodeLossyString(forKey: .pushAt)
isRead = try container.decodeLossyBool(forKey: .isRead) ?? false
readAt = try container.decodeLossyString(forKey: .readAt)
pushChannel = try container.decodeLossyInt(forKey: .pushChannel) ?? 0
pushChannelName = try container.decodeLossyString(forKey: .pushChannelName)
extraData = try? container.decodeIfPresent([String: MessageJSONValue].self, forKey: .extraData)
createdAt = try container.decodeLossyString(forKey: .createdAt)
updatedAt = try container.decodeLossyString(forKey: .updatedAt)
}
/// Android 使 `type_name`
var displayTitle: String {
typeName.trimmingCharacters(in: .whitespacesAndNewlines)
}
/// Android `push_at` `created_at`
var displayTime: String {
let pushTime = pushAt.trimmingCharacters(in: .whitespacesAndNewlines)
if !pushTime.isEmpty { return pushTime }
return MessageDateFormatter.formatDateTime(createdAt) ?? createdAt
}
///
func markedRead() -> MessageItem {
MessageItem(
id: id,
receiverId: receiverId,
receiverType: receiverType,
type: type,
typeName: typeName,
title: title,
content: content,
pushAt: pushAt,
isRead: true,
readAt: readAt,
pushChannel: pushChannel,
pushChannelName: pushChannelName,
extraData: extraData,
createdAt: createdAt,
updatedAt: updatedAt
)
}
}
/// Android `MsgDeleteRequest`
struct MessageDeleteRequest: Encodable, Sendable {
let id: Int
}
/// Android `DateUtils.formatDateTime`
enum MessageDateFormatter {
private static let calendar = Calendar.current
static func formatDateTime(_ input: String?) -> String? {
guard let input = input?.trimmingCharacters(in: .whitespacesAndNewlines), !input.isEmpty else { return nil }
guard let date = parseDate(input) else { return nil }
let today = Date()
let startOfInput = calendar.startOfDay(for: date)
let startOfToday = calendar.startOfDay(for: today)
let dayDifference = calendar.dateComponents([.day], from: startOfInput, to: startOfToday).day ?? 0
let inputComponents = calendar.dateComponents([.year, .month, .day, .weekday], from: date)
let todayComponents = calendar.dateComponents([.year, .month], from: today)
if dayDifference == 0 {
return "今天"
}
if (1 ... 6).contains(dayDifference), let weekday = inputComponents.weekday {
return weekdayName(weekday)
}
if inputComponents.year == todayComponents.year, inputComponents.month == todayComponents.month {
return "本月\(inputComponents.day ?? 0)"
}
if inputComponents.year == todayComponents.year {
return "\(inputComponents.month ?? 0)\(inputComponents.day ?? 0)"
}
return "\(inputComponents.year ?? 0)\(inputComponents.month ?? 0)\(inputComponents.day ?? 0)"
}
private static func parseDate(_ input: String) -> Date? {
if let timestamp = Double(input) {
let seconds = timestamp > 1_000_000_000_000 ? timestamp / 1000 : timestamp
return Date(timeIntervalSince1970: seconds)
}
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = .current
for format in ["yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd'T'HH:mm:ss", "yyyy-MM-dd"] {
formatter.dateFormat = format
if let date = formatter.date(from: input) {
return date
}
}
return nil
}
private static func weekdayName(_ weekday: Int) -> String {
switch weekday {
case 1: return "周日"
case 2: return "周一"
case 3: return "周二"
case 4: return "周三"
case 5: return "周四"
case 6: return "周五"
case 7: return "周六"
default: return ""
}
}
}
private extension KeyedDecodingContainer {
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 ? "true" : "false"
}
return ""
}
func decodeLossyInt(forKey key: Key) throws -> Int? {
if let value = try? decodeIfPresent(Int.self, forKey: key) {
return value
}
if let value = try? decodeIfPresent(Double.self, forKey: key) {
return Int(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) }
}
return nil
}
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) {
let text = value.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
if ["true", "1", "yes"].contains(text) { return true }
if ["false", "0", "no"].contains(text) { return false }
}
return nil
}
}