Files
Luma/Sources/Shared/Models/Conversation.swift
T
2026-07-30 20:07:51 +07:00

123 lines
4.3 KiB
Swift

import Foundation
struct Conversation: Codable, Identifiable, Hashable, Sendable {
enum Kind: String, Codable, Sendable {
case direct
case group
}
let id: String
var jid: String
var displayName: String
var lastMessage: String
var lastActivity: Date
var unreadCount: Int
var isOnline: Bool
var isPinned: Bool
var encryptionPreference: EncryptionPreference
var kind: Kind
var groupNickname: String?
var isGroupJoined: Bool
var shouldAutojoin: Bool
var occupantCount: Int
var invitedBy: String?
init(
jid: String,
displayName: String? = nil,
lastMessage: String = "",
lastActivity: Date = .distantPast,
unreadCount: Int = 0,
isOnline: Bool = false,
isPinned: Bool = false,
encryptionPreference: EncryptionPreference = .inheritGlobal,
kind: Kind = .direct,
groupNickname: String? = nil,
isGroupJoined: Bool = false,
shouldAutojoin: Bool = false,
occupantCount: Int = 0,
invitedBy: String? = nil
) {
let normalized = jid.trimmingCharacters(in: .whitespacesAndNewlines).lowercased()
self.id = normalized
self.jid = normalized
self.displayName = displayName?.nilIfBlank ?? normalized
self.lastMessage = lastMessage
self.lastActivity = lastActivity
self.unreadCount = unreadCount
self.isOnline = isOnline
self.isPinned = isPinned
self.encryptionPreference = encryptionPreference
self.kind = kind
self.groupNickname = groupNickname?.nilIfBlank
self.isGroupJoined = isGroupJoined
self.shouldAutojoin = shouldAutojoin
self.occupantCount = max(0, occupantCount)
self.invitedBy = invitedBy?.nilIfBlank
}
private enum CodingKeys: String, CodingKey {
case id
case jid
case displayName
case lastMessage
case lastActivity
case unreadCount
case isOnline
case isPinned
case encryptionPreference
case kind
case groupNickname
case isGroupJoined
case shouldAutojoin
case occupantCount
case invitedBy
}
init(from decoder: Decoder) throws {
let values = try decoder.container(keyedBy: CodingKeys.self)
jid = try values.decode(String.self, forKey: .jid)
id = try values.decodeIfPresent(String.self, forKey: .id) ?? jid.lowercased()
displayName = try values.decode(String.self, forKey: .displayName)
lastMessage = try values.decode(String.self, forKey: .lastMessage)
lastActivity = try values.decode(Date.self, forKey: .lastActivity)
unreadCount = try values.decode(Int.self, forKey: .unreadCount)
isOnline = try values.decode(Bool.self, forKey: .isOnline)
isPinned = try values.decode(Bool.self, forKey: .isPinned)
encryptionPreference = try values.decodeIfPresent(
EncryptionPreference.self,
forKey: .encryptionPreference
) ?? .inheritGlobal
kind = try values.decodeIfPresent(Kind.self, forKey: .kind) ?? .direct
groupNickname = try values.decodeIfPresent(String.self, forKey: .groupNickname)
isGroupJoined = try values.decodeIfPresent(Bool.self, forKey: .isGroupJoined) ?? false
shouldAutojoin = try values.decodeIfPresent(Bool.self, forKey: .shouldAutojoin) ?? false
occupantCount = max(0, try values.decodeIfPresent(Int.self, forKey: .occupantCount) ?? 0)
invitedBy = try values.decodeIfPresent(String.self, forKey: .invitedBy)
}
var isGroup: Bool { kind == .group }
var initials: String {
let source = displayName == jid ? (jid.split(separator: "@").first.map(String.init) ?? jid) : displayName
let words = source.split(whereSeparator: { $0.isWhitespace || $0 == "-" || $0 == "_" })
let letters = words.prefix(2).compactMap(\.first)
if letters.isEmpty {
return String(source.prefix(1)).uppercased()
}
return String(letters).uppercased()
}
var colorSeed: UInt64 {
jid.utf8.reduce(14_695_981_039_346_656_037) { partial, byte in
(partial ^ UInt64(byte)) &* 1_099_511_628_211
}
}
}
private extension String {
var nilIfBlank: String? {
trimmingCharacters(in: .whitespacesAndNewlines).isEmpty ? nil : self
}
}