// // Copyright 2024 Signal Messenger, LLC // SPDX-License-Identifier: AGPL-3.0-only // import SignalCoreKit /// Represents a sync message an event related to the Calls Tab (also known on /// other platforms as the "call log"). /// /// - SeeAlso ``IncomingCallLogEventSyncMessageManager`` @objc(OutgoingCallLogEventSyncMessage) public class OutgoingCallLogEventSyncMessage: OWSOutgoingSyncMessage { /// The call log event. /// /// - Important /// The ObjC name must remain as-is for compatibility with Mantle. /// /// - Note /// Nullability here is intentional, since Mantle will set this property via /// its reflection-based `init(coder:)` when we call `super.init(coder:)`. @objc(callLogEvent) private(set) var callLogEvent: CallLogEvent! init( callLogEvent: CallLogEvent, thread: TSThread, tx: SDSAnyReadTransaction ) { self.callLogEvent = callLogEvent super.init(thread: thread, transaction: tx) } required public init?(coder: NSCoder) { super.init(coder: coder) } required public init(dictionary dictionaryValue: [String: Any]!) throws { try super.init(dictionary: dictionaryValue) } override public var isUrgent: Bool { false } override public func syncMessageBuilder(transaction: SDSAnyReadTransaction) -> SSKProtoSyncMessageBuilder? { let callLogEventBuilder = SSKProtoSyncMessageCallLogEvent.builder() callLogEventBuilder.setTimestamp(callLogEvent.timestamp) callLogEventBuilder.setType(callLogEvent.eventType.protoType) if let callId = callLogEvent.callId { callLogEventBuilder.setCallID(callId) } if let conversationId = callLogEvent.conversationId { callLogEventBuilder.setConversationID(conversationId.asData) } let builder = SSKProtoSyncMessage.builder() builder.setCallLogEvent(callLogEventBuilder.buildInfallibly()) return builder } } // MARK: - public extension OutgoingCallLogEventSyncMessage { @objc(OutgoingCallLogEvent) class CallLogEvent: NSObject, NSCoding { typealias ConversationId = CallSyncMessageConversationId public enum EventType: UInt, CaseIterable { /// Indicates we cleared our call log in its entirety. /// /// - SeeAlso /// ``OutgoingCallEvent/EventType/deleted``, which indicates that we /// deleted a singular individual call. /// /// That action is part of the `CallEvent` sync message for /// historical reasons, in that it predates the `CallLogEvent` sync /// message. case cleared = 0 /// Indicates we marked calls as read. case markedAsRead = 1 /// Indicates we marked calls as read in a particular conversation. case markedAsReadInConversation = 2 } let eventType: EventType let callId: UInt64? let conversationId: ConversationId? let timestamp: UInt64 init( eventType: EventType, callId: UInt64?, conversationId: ConversationId?, timestamp: UInt64 ) { self.eventType = eventType self.callId = callId self.conversationId = conversationId self.timestamp = timestamp } // MARK: NSCoding private enum Keys { static let eventType = "eventType" static let timestamp = "timestamp" static let callId = "callId" static let conversationId = "conversationId" } required public init?(coder: NSCoder) { guard let eventTypeRaw = coder.decodeObject(of: NSNumber.self, forKey: Keys.eventType) as? UInt, let eventType = EventType(rawValue: eventTypeRaw), let timestamp = coder.decodeObject(of: NSNumber.self, forKey: Keys.timestamp) as? UInt64 else { owsFailDebug("Missing or unrecognized fields!") return nil } self.eventType = eventType self.timestamp = timestamp if let callId = coder.decodeObject(of: NSNumber.self, forKey: Keys.callId) as? UInt64, let conversationId: ConversationId = coder.decodeObject( of: NSData.self, forKey: Keys.conversationId ).flatMap({ .from(data: $0 as Data) }) { self.callId = callId self.conversationId = conversationId } else { self.callId = nil self.conversationId = nil } } public func encode(with coder: NSCoder) { coder.encode(NSNumber(value: eventType.rawValue), forKey: Keys.eventType) coder.encode(NSNumber(value: timestamp), forKey: Keys.timestamp) if let callId, let conversationId { coder.encode(NSNumber(value: callId), forKey: Keys.callId) coder.encode(conversationId.asData as NSData, forKey: Keys.conversationId) } } } } // MARK: - private extension OutgoingCallLogEventSyncMessage.CallLogEvent.EventType { var protoType: SSKProtoSyncMessageCallLogEventType { switch self { case .cleared: return .cleared case .markedAsRead: return .markedAsRead case .markedAsReadInConversation: return .markedAsReadInConversation } } }