Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
signalapp
GitHub Repository: signalapp/Signal-iOS
Path: blob/main/SignalServiceKit/Messages/Interactions/PinnedMessages/TSInfoMessage+PinnedMessage.swift
1 views
//
// Copyright 2025 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

public import LibSignalClient

@objc
public class PersistablePinnedMessageItem: NSObject, NSCopying, NSSecureCoding {
    let pinnedMessageAuthorAci: Aci
    let originalMessageAuthorAci: Aci
    let timestamp: Int64

    public init(pinnedMessageAuthorAci: Aci, originalMessageAuthorAci: Aci, timestamp: Int64) {
        self.pinnedMessageAuthorAci = pinnedMessageAuthorAci
        self.originalMessageAuthorAci = originalMessageAuthorAci
        self.timestamp = timestamp
    }

    // MARK: - NSCopying

    public func copy(with zone: NSZone? = nil) -> Any {
        self
    }

    // MARK: - NSSecureCoding

    public static var supportsSecureCoding: Bool { true }

    public func encode(with coder: NSCoder) {
        coder.encode(pinnedMessageAuthorAci.serviceIdBinary, forKey: "pinnedMessageAuthor")
        coder.encode(originalMessageAuthorAci.serviceIdBinary, forKey: "originalMessageAuthor")
        coder.encode(timestamp, forKey: "timestamp")
    }

    public required init?(coder: NSCoder) {
        guard
            let pinnedMessageAuthorServiceIdBinary = coder.decodeObject(of: NSData.self, forKey: "pinnedMessageAuthor") as Data?,
            let originalMessageAuthorServiceIdBinary = coder.decodeObject(of: NSData.self, forKey: "originalMessageAuthor") as Data?,
            let _pinnedMessageAuthorAci = try? Aci.parseFrom(serviceIdBinary: pinnedMessageAuthorServiceIdBinary),
            let _originalMessageAuthorAci = try? Aci.parseFrom(serviceIdBinary: originalMessageAuthorServiceIdBinary)
        else {
            return nil
        }

        self.pinnedMessageAuthorAci = _pinnedMessageAuthorAci
        self.originalMessageAuthorAci = _originalMessageAuthorAci
        self.timestamp = coder.decodeInt64(forKey: "timestamp")
    }
}

extension TSInfoMessage {
    public func pinnedMessageUniqueId(threadUniqueId: String, transaction: DBReadTransaction) -> String? {
        guard let pinnedMessageItem: PersistablePinnedMessageItem = infoMessageValue(forKey: .pinnedMessage) else {
            return nil
        }

        guard let localAci = DependenciesBridge.shared.tsAccountManager.localIdentifiers(tx: transaction)?.aci else {
            Logger.error("User is not registered")
            return nil
        }

        guard
            let message = try? DependenciesBridge.shared.interactionStore.fetchMessage(
                timestamp: UInt64(pinnedMessageItem.timestamp),
                incomingMessageAuthor: localAci == pinnedMessageItem.originalMessageAuthorAci ? nil : pinnedMessageItem.originalMessageAuthorAci,
                threadUniqueId: threadUniqueId,
                transaction: transaction,
            )
        else {
            Logger.error("Can't find target message")
            return nil
        }

        if message.editState == .pastRevision {
            // Pin targeted an old edit revision, fetch the latest
            // version to ensure the view button goes to the right place.
            if
                let latestEdit = DependenciesBridge.shared.editMessageStore.findMessage(
                    fromEdit: message,
                    tx: transaction,
                )
            {
                return latestEdit.uniqueId
            }
            Logger.error("Can't find past revision")
            return nil
        }
        return message.uniqueId
    }

    func pinnedMessageDescription(transaction: DBReadTransaction) -> String? {
        guard let pinnedMessageItem: PersistablePinnedMessageItem = infoMessageValue(forKey: .pinnedMessage) else {
            return nil
        }

        guard let localAci = DependenciesBridge.shared.tsAccountManager.localIdentifiers(tx: transaction)?.aci else {
            Logger.error("Can't find local ACI")
            return nil
        }

        if localAci == pinnedMessageItem.pinnedMessageAuthorAci {
            return OWSLocalizedString(
                "PINNED_MESSAGE_CHAT_EVENT_SELF",
                comment: "Shown when the local user pins a message.",
            )
        }

        let displayName = SSKEnvironment.shared.contactManagerRef.displayName(
            for: SignalServiceAddress(pinnedMessageItem.pinnedMessageAuthorAci),
            tx: transaction,
        )

        let formatString = OWSLocalizedString(
            "PINNED_MESSAGE_CHAT_EVENT_OTHER",
            comment: "Shown when another user pins a message. Embeds {{ another user }}.",
        )

        return String.nonPluralLocalizedStringWithFormat(formatString, displayName.resolvedValue())
    }
}