Use ACIs for mention candidates

This commit is contained in:
Max Radermacher 2025-10-30 12:36:34 -05:00 committed by GitHub
parent bc608a6165
commit 3cd06fcfeb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 98 additions and 85 deletions

View File

@ -3,6 +3,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
//
public import LibSignalClient
public import SignalServiceKit
public import SignalUI
@ -21,8 +22,8 @@ extension ConversationViewController: BodyRangesTextViewDelegate {
bottomBarContainer
}
public func textViewMentionPickerPossibleAddresses(_ textView: BodyRangesTextView, tx: DBReadTransaction) -> [SignalServiceAddress] {
supportsMentions ? thread.recipientAddresses(with: SDSDB.shimOnlyBridge(tx)) : []
public func textViewMentionPickerPossibleAcis(_ textView: BodyRangesTextView, tx: DBReadTransaction) -> [Aci] {
supportsMentions ? thread.recipientAddresses(with: tx).compactMap(\.aci) : []
}
public func textViewMentionCacheInvalidationKey(_ textView: BodyRangesTextView) -> String {

View File

@ -4,6 +4,7 @@
//
import CoreServices
import LibSignalClient
public import Photos
public import SignalServiceKit
public import SignalUI
@ -991,8 +992,8 @@ extension ConversationViewController: SendMediaNavDataSource {
return [displayName]
}
func sendMediaNavMentionableAddresses(tx: DBReadTransaction) -> [SignalServiceAddress] {
supportsMentions ? thread.recipientAddresses(with: SDSDB.shimOnlyBridge(tx)) : []
func sendMediaNavMentionableAcis(tx: DBReadTransaction) -> [Aci] {
supportsMentions ? thread.recipientAddresses(with: tx).compactMap(\.aci) : []
}
func sendMediaNavMentionCacheInvalidationKey() -> String {

View File

@ -5,6 +5,7 @@
import AVFoundation
import Foundation
public import LibSignalClient
public import SignalServiceKit
public import SignalUI
@ -60,8 +61,8 @@ extension ConversationViewController: AttachmentApprovalViewControllerDataSource
return [displayName]
}
public func attachmentApprovalMentionableAddresses(tx: DBReadTransaction) -> [SignalServiceAddress] {
supportsMentions ? thread.recipientAddresses(with: SDSDB.shimOnlyBridge(tx)) : []
public func attachmentApprovalMentionableAcis(tx: DBReadTransaction) -> [Aci] {
supportsMentions ? thread.recipientAddresses(with: tx).compactMap(\.aci) : []
}
public func attachmentApprovalMentionCacheInvalidationKey() -> String {

View File

@ -3,6 +3,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
//
import LibSignalClient
import SignalServiceKit
import SignalUI
@ -19,7 +20,7 @@ class CameraFirstCaptureSendFlow {
private var approvalMessageBody: MessageBody?
private var textAttachment: UnsentTextAttachment?
private var mentionCandidates: [SignalServiceAddress] = []
private var mentionCandidates: [Aci] = []
private let selection = ConversationPickerSelection()
private var selectedConversations: [ConversationItem] { selection.conversations }
@ -35,21 +36,23 @@ class CameraFirstCaptureSendFlow {
private func updateMentionCandidates() {
AssertIsOnMainThread()
guard selectedConversations.count == 1,
case .group(let groupThreadId) = selectedConversations.first?.messageRecipient else {
guard
selectedConversations.count == 1,
case .group(let groupThreadId) = selectedConversations.first?.messageRecipient
else {
mentionCandidates = []
return
}
let groupThread = SSKEnvironment.shared.databaseStorageRef.read { readTx in
TSGroupThread.anyFetchGroupThread(uniqueId: groupThreadId, transaction: readTx)
}
owsAssertDebug(groupThread != nil)
if let groupThread = groupThread, groupThread.allowsMentionSend {
mentionCandidates = groupThread.recipientAddressesWithSneakyTransaction
} else {
mentionCandidates = []
let databaseStorage = SSKEnvironment.shared.databaseStorageRef
self.mentionCandidates = databaseStorage.read { tx in
let groupThread = TSGroupThread.anyFetchGroupThread(uniqueId: groupThreadId, transaction: tx)
owsAssertDebug(groupThread != nil)
if let groupThread, groupThread.allowsMentionSend {
return groupThread.recipientAddresses(with: tx).compactMap(\.aci)
} else {
return []
}
}
}
}
@ -118,7 +121,7 @@ extension CameraFirstCaptureSendFlow: SendMediaNavDataSource {
selectedConversations.map { $0.titleWithSneakyTransaction }
}
func sendMediaNavMentionableAddresses(tx: DBReadTransaction) -> [SignalServiceAddress] {
func sendMediaNavMentionableAcis(tx: DBReadTransaction) -> [Aci] {
return mentionCandidates
}

View File

@ -4,6 +4,7 @@
//
import Foundation
import LibSignalClient
import SignalServiceKit
import SignalUI
@ -54,43 +55,40 @@ extension GifPickerNavigationViewController: GifPickerViewControllerDelegate {
extension GifPickerNavigationViewController: AttachmentApprovalViewControllerDelegate {
public func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController,
didApproveAttachments attachments: [SignalAttachment],
messageBody: MessageBody?) {
func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didApproveAttachments attachments: [SignalAttachment], messageBody: MessageBody?) {
approvalDelegate?.attachmentApproval(attachmentApproval, didApproveAttachments: attachments, messageBody: messageBody)
}
public func attachmentApprovalDidCancel() {
func attachmentApprovalDidCancel() {
approvalDelegate?.attachmentApprovalDidCancel()
}
public func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController,
didChangeMessageBody newMessageBody: MessageBody?) {
func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didChangeMessageBody newMessageBody: MessageBody?) {
approvalDelegate?.attachmentApproval(attachmentApproval, didChangeMessageBody: newMessageBody)
}
public func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didRemoveAttachment attachment: SignalAttachment) { }
func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didRemoveAttachment attachment: SignalAttachment) { }
public func attachmentApprovalDidTapAddMore(_ attachmentApproval: AttachmentApprovalViewController) { }
func attachmentApprovalDidTapAddMore(_ attachmentApproval: AttachmentApprovalViewController) { }
public func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didChangeViewOnceState isViewOnce: Bool) { }
func attachmentApproval(_ attachmentApproval: AttachmentApprovalViewController, didChangeViewOnceState isViewOnce: Bool) { }
}
extension GifPickerNavigationViewController: AttachmentApprovalViewControllerDataSource {
public var attachmentApprovalTextInputContextIdentifier: String? {
var attachmentApprovalTextInputContextIdentifier: String? {
return approvalDataSource?.attachmentApprovalTextInputContextIdentifier
}
public var attachmentApprovalRecipientNames: [String] {
var attachmentApprovalRecipientNames: [String] {
approvalDataSource?.attachmentApprovalRecipientNames ?? []
}
public func attachmentApprovalMentionableAddresses(tx: DBReadTransaction) -> [SignalServiceAddress] {
return approvalDataSource?.attachmentApprovalMentionableAddresses(tx: tx) ?? []
func attachmentApprovalMentionableAcis(tx: DBReadTransaction) -> [Aci] {
return approvalDataSource?.attachmentApprovalMentionableAcis(tx: tx) ?? []
}
public func attachmentApprovalMentionCacheInvalidationKey() -> String {
func attachmentApprovalMentionCacheInvalidationKey() -> String {
return approvalDataSource?.attachmentApprovalMentionCacheInvalidationKey() ?? UUID().uuidString
}
}

View File

@ -3,6 +3,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
//
import LibSignalClient
import SignalServiceKit
import SignalUI
import UIKit
@ -14,7 +15,7 @@ protocol StoryReplyInputToolbarDelegate: MessageReactionPickerDelegate {
func storyReplyInputToolbarDidTapSend(_ storyReplyInputToolbar: StoryReplyInputToolbar) async throws
func storyReplyInputToolbarDidBeginEditing(_ storyReplyInputToolbar: StoryReplyInputToolbar)
func storyReplyInputToolbarHeightDidChange(_ storyReplyInputToolbar: StoryReplyInputToolbar)
func storyReplyInputToolbarMentionPickerPossibleAddresses(_ storyReplyInputToolbar: StoryReplyInputToolbar, tx: DBReadTransaction) -> [SignalServiceAddress]
func storyReplyInputToolbarMentionPickerPossibleAcis(_ storyReplyInputToolbar: StoryReplyInputToolbar, tx: DBReadTransaction) -> [Aci]
func storyReplyInputToolbarMentionCacheInvalidationKey() -> String
func storyReplyInputToolbarMentionPickerReferenceView(_ storyReplyInputToolbar: StoryReplyInputToolbar) -> UIView?
func storyReplyInputToolbarMentionPickerParentView(_ storyReplyInputToolbar: StoryReplyInputToolbar) -> UIView?
@ -365,8 +366,8 @@ extension StoryReplyInputToolbar: BodyRangesTextViewDelegate {
delegate?.storyReplyInputToolbarMentionPickerReferenceView(self)
}
func textViewMentionPickerPossibleAddresses(_ textView: BodyRangesTextView, tx: DBReadTransaction) -> [SignalServiceAddress] {
delegate?.storyReplyInputToolbarMentionPickerPossibleAddresses(self, tx: tx) ?? []
func textViewMentionPickerPossibleAcis(_ textView: BodyRangesTextView, tx: DBReadTransaction) -> [Aci] {
delegate?.storyReplyInputToolbarMentionPickerPossibleAcis(self, tx: tx) ?? []
}
func textViewMentionCacheInvalidationKey(_ textView: BodyRangesTextView) -> String {

View File

@ -4,6 +4,7 @@
//
import Foundation
import LibSignalClient
import SignalServiceKit
import SignalUI
import UIKit
@ -172,9 +173,9 @@ extension StoryReplySheet {
func storyReplyInputToolbarDidBeginEditing(_ storyReplyInputToolbar: StoryReplyInputToolbar) {}
func storyReplyInputToolbarHeightDidChange(_ storyReplyInputToolbar: StoryReplyInputToolbar) {}
func storyReplyInputToolbarMentionPickerPossibleAddresses(_ storyReplyInputToolbar: StoryReplyInputToolbar, tx: DBReadTransaction) -> [SignalServiceAddress] {
func storyReplyInputToolbarMentionPickerPossibleAcis(_ storyReplyInputToolbar: StoryReplyInputToolbar, tx: DBReadTransaction) -> [Aci] {
guard let thread = thread, thread.isGroupThread else { return [] }
return thread.recipientAddresses(with: SDSDB.shimOnlyBridge(tx))
return thread.recipientAddresses(with: tx).compactMap(\.aci)
}
func storyReplyInputToolbarMentionCacheInvalidationKey() -> String {

View File

@ -3,10 +3,11 @@
// SPDX-License-Identifier: AGPL-3.0-only
//
import LibSignalClient
import Photos
import PhotosUI
import SignalServiceKit
import SignalUI
import PhotosUI
protocol SendMediaNavDelegate: AnyObject {
@ -29,7 +30,7 @@ protocol SendMediaNavDataSource: AnyObject {
var sendMediaNavRecipientNames: [String] { get }
func sendMediaNavMentionableAddresses(tx: DBReadTransaction) -> [SignalServiceAddress]
func sendMediaNavMentionableAcis(tx: DBReadTransaction) -> [Aci]
func sendMediaNavMentionCacheInvalidationKey() -> String
}
@ -619,8 +620,8 @@ extension SendMediaNavigationController: AttachmentApprovalViewControllerDataSou
sendMediaNavDataSource?.sendMediaNavRecipientNames ?? []
}
func attachmentApprovalMentionableAddresses(tx: DBReadTransaction) -> [SignalServiceAddress] {
sendMediaNavDataSource?.sendMediaNavMentionableAddresses(tx: tx) ?? []
func attachmentApprovalMentionableAcis(tx: DBReadTransaction) -> [Aci] {
sendMediaNavDataSource?.sendMediaNavMentionableAcis(tx: tx) ?? []
}
func attachmentApprovalMentionCacheInvalidationKey() -> String {

View File

@ -195,7 +195,7 @@ extension MessageBody {
/// This context is not necessarily one single thread; we could be pasting into a composer
/// for sending to multiple threads. So the input array is _all_ valid addresses.
public func forPasting(
intoContextWithPossibleAddresses possibleAddresses: [SignalServiceAddress],
intoContextWithPossibleAcis possibleAcis: [Aci],
transaction: DBReadTransaction,
isRTL: Bool = CurrentAppContext().isRTL
) -> MessageBody {
@ -203,7 +203,7 @@ extension MessageBody {
return self
}
let mentionHydrator = ContactsMentionHydrator.mentionHydrator(
excludedAcis: Set(possibleAddresses.compactMap(\.aci)),
excludedAcis: Set(possibleAcis),
transaction: transaction
)
return hydrating(mentionHydrator: mentionHydrator, isRTL: isRTL).asMessageBodyForForwarding()

View File

@ -3,10 +3,11 @@
// SPDX-License-Identifier: AGPL-3.0-only
//
import UIKit
import Foundation
import LibSignalClient
import SignalServiceKit
import SignalUI
import UIKit
class SharingThreadPickerViewController: ConversationPickerViewController {
@ -48,7 +49,7 @@ class SharingThreadPickerViewController: ConversationPickerViewController {
var approvalMessageBody: MessageBody?
var approvalLinkPreviewDraft: OWSLinkPreviewDraft?
var mentionCandidates: [SignalServiceAddress] = []
var mentionCandidates: [Aci] = []
var selectedConversations: [ConversationItem] { selection.conversations }
@ -76,21 +77,23 @@ class SharingThreadPickerViewController: ConversationPickerViewController {
private func updateMentionCandidates() {
AssertIsOnMainThread()
guard selectedConversations.count == 1,
case .group(let groupThreadId) = selectedConversations.first?.messageRecipient else {
guard
selectedConversations.count == 1,
case .group(let groupThreadId) = selectedConversations.first?.messageRecipient else
{
mentionCandidates = []
return
}
let groupThread = SSKEnvironment.shared.databaseStorageRef.read { readTx in
TSGroupThread.anyFetchGroupThread(uniqueId: groupThreadId, transaction: readTx)
}
owsAssertDebug(groupThread != nil)
if let groupThread = groupThread, groupThread.allowsMentionSend {
mentionCandidates = groupThread.recipientAddressesWithSneakyTransaction
} else {
mentionCandidates = []
let databaseStorage = SSKEnvironment.shared.databaseStorageRef
self.mentionCandidates = databaseStorage.read { tx in
let groupThread = TSGroupThread.anyFetchGroupThread(uniqueId: groupThreadId, transaction: tx)
owsAssertDebug(groupThread != nil)
if let groupThread, groupThread.allowsMentionSend {
return groupThread.recipientAddresses(with: tx).compactMap(\.aci)
} else {
return []
}
}
}
@ -732,7 +735,7 @@ extension SharingThreadPickerViewController: AttachmentApprovalViewControllerDat
selectedConversations.map { $0.titleWithSneakyTransaction }
}
func attachmentApprovalMentionableAddresses(tx: DBReadTransaction) -> [SignalServiceAddress] {
func attachmentApprovalMentionableAcis(tx: DBReadTransaction) -> [Aci] {
mentionCandidates
}

View File

@ -3,11 +3,12 @@
// SPDX-License-Identifier: AGPL-3.0-only
//
import Foundation
import AVFoundation
import CoreServices
import Foundation
public import LibSignalClient
import MediaPlayer
import Photos
import CoreServices
public import SignalServiceKit
public protocol AttachmentApprovalViewControllerDelegate: AnyObject {
@ -37,7 +38,7 @@ public protocol AttachmentApprovalViewControllerDataSource: AnyObject {
var attachmentApprovalRecipientNames: [String] { get }
func attachmentApprovalMentionableAddresses(tx: DBReadTransaction) -> [SignalServiceAddress]
func attachmentApprovalMentionableAcis(tx: DBReadTransaction) -> [Aci]
func attachmentApprovalMentionCacheInvalidationKey() -> String
}
@ -1294,8 +1295,8 @@ extension AttachmentApprovalViewController: BodyRangesTextViewDelegate {
return bottomToolView.attachmentTextToolbar
}
public func textViewMentionPickerPossibleAddresses(_ textView: BodyRangesTextView, tx: DBReadTransaction) -> [SignalServiceAddress] {
return approvalDataSource?.attachmentApprovalMentionableAddresses(tx: tx) ?? []
public func textViewMentionPickerPossibleAcis(_ textView: BodyRangesTextView, tx: DBReadTransaction) -> [Aci] {
return approvalDataSource?.attachmentApprovalMentionableAcis(tx: tx) ?? []
}
public func textViewDisplayConfiguration(_ textView: BodyRangesTextView) -> HydratedMessageBody.DisplayConfiguration {

View File

@ -4,8 +4,9 @@
//
import Foundation
import UIKit
import LibSignalClient
import SignalServiceKit
import UIKit
// Coincides with Android's max text message length
let kMaxMessageBodyCharacterCount = 2000
@ -341,8 +342,8 @@ extension AttachmentTextToolbar: BodyRangesTextViewDelegate {
return mentionTextViewDelegate?.textViewMentionPickerReferenceView(textView)
}
func textViewMentionPickerPossibleAddresses(_ textView: BodyRangesTextView, tx: DBReadTransaction) -> [SignalServiceAddress] {
return mentionTextViewDelegate?.textViewMentionPickerPossibleAddresses(textView, tx: tx) ?? []
func textViewMentionPickerPossibleAcis(_ textView: BodyRangesTextView, tx: DBReadTransaction) -> [Aci] {
return mentionTextViewDelegate?.textViewMentionPickerPossibleAcis(textView, tx: tx) ?? []
}
public func textViewDisplayConfiguration(_ textView: BodyRangesTextView) -> HydratedMessageBody.DisplayConfiguration {

View File

@ -3,6 +3,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
//
public import LibSignalClient
public import SignalServiceKit
public protocol TextApprovalViewControllerDelegate: AnyObject {
@ -176,7 +177,7 @@ public class TextApprovalViewController: OWSViewController, BodyRangesTextViewDe
return nil
}
public func textViewMentionPickerPossibleAddresses(_ textView: BodyRangesTextView, tx: DBReadTransaction) -> [SignalServiceAddress] {
public func textViewMentionPickerPossibleAcis(_ textView: BodyRangesTextView, tx: DBReadTransaction) -> [Aci] {
return []
}

View File

@ -3,7 +3,7 @@
// SPDX-License-Identifier: AGPL-3.0-only
//
import LibSignalClient
public import LibSignalClient
public import SignalServiceKit
public protocol BodyRangesTextViewDelegate: UITextViewDelegate {
@ -15,7 +15,7 @@ public protocol BodyRangesTextViewDelegate: UITextViewDelegate {
// It doesn't matter what this key is; but when it changes cached mention names will be discarded.
// Typically, we want this to change in new thread contexts and such.
func textViewMentionCacheInvalidationKey(_ textView: BodyRangesTextView) -> String
func textViewMentionPickerPossibleAddresses(_ textView: BodyRangesTextView, tx: DBReadTransaction) -> [SignalServiceAddress]
func textViewMentionPickerPossibleAcis(_ textView: BodyRangesTextView, tx: DBReadTransaction) -> [Aci]
func textViewDisplayConfiguration(_ textView: BodyRangesTextView) -> HydratedMessageBody.DisplayConfiguration
func mentionPickerStyle(_ textView: BodyRangesTextView) -> MentionPickerStyle
@ -104,15 +104,15 @@ open class BodyRangesTextView: OWSTextView, EditableMessageBodyDelegate {
text: "@",
ranges: MessageBodyRanges(mentions: [NSRange(location: 0, length: 1): mentionAci], styles: [])
)
let (hydrated, possibleAddresses) = DependenciesBridge.shared.db.read { tx in
let (hydrated, possibleAcis) = DependenciesBridge.shared.db.read { tx in
return (
body.hydrating(mentionHydrator: ContactsMentionHydrator.mentionHydrator(transaction: tx)),
bodyRangesDelegate.textViewMentionPickerPossibleAddresses(self, tx: tx)
bodyRangesDelegate.textViewMentionPickerPossibleAcis(self, tx: tx)
)
}
let hydratedPlaintext = hydrated.asPlaintext()
if possibleAddresses.contains(mentionAddress) {
if possibleAcis.contains(mentionAci) {
editableBody.beginEditing()
editableBody.replaceCharacters(in: range, withMentionAci: mentionAci, txProvider: DependenciesBridge.shared.db.readTxProvider)
editableBody.endEditing()
@ -242,17 +242,17 @@ open class BodyRangesTextView: OWSTextView, EditableMessageBodyDelegate {
pickerView?.removeFromSuperview()
let mentionableAddresses = SSKEnvironment.shared.databaseStorageRef.read { tx in
return bodyRangesDelegate.textViewMentionPickerPossibleAddresses(self, tx: tx)
let mentionableAcis = SSKEnvironment.shared.databaseStorageRef.read { tx in
return bodyRangesDelegate.textViewMentionPickerPossibleAcis(self, tx: tx)
}
guard !mentionableAddresses.isEmpty else { return }
guard !mentionableAcis.isEmpty else { return }
guard let pickerReferenceView = bodyRangesDelegate.textViewMentionPickerReferenceView(self),
let pickerParentView = bodyRangesDelegate.textViewMentionPickerParentView(self) else { return }
let pickerView = MentionPicker(
mentionableAddresses: mentionableAddresses,
mentionableAcis: mentionableAcis,
style: bodyRangesDelegate.mentionPickerStyle(self)
) { [weak self] selectedAddress in
self?.insertTypedMention(address: selectedAddress)
@ -712,10 +712,8 @@ open class BodyRangesTextView: OWSTextView, EditableMessageBodyDelegate {
public func editableMessageBodyHydrator(tx: DBReadTransaction) -> MentionHydrator {
var possibleMentionAcis = Set<Aci>()
bodyRangesDelegate?.textViewMentionPickerPossibleAddresses(self, tx: tx).forEach {
if let aci = $0.aci {
possibleMentionAcis.insert(aci)
}
bodyRangesDelegate?.textViewMentionPickerPossibleAcis(self, tx: tx).forEach {
possibleMentionAcis.insert($0)
}
let hydrator = ContactsMentionHydrator.mentionHydrator(transaction: tx)
return { aci in
@ -847,8 +845,8 @@ extension BodyRangesTextView {
var messageBody = try? NSKeyedUnarchiver.unarchivedObject(ofClass: MessageBody.self, from: encodedMessageBody) {
editableBody.beginEditing()
DependenciesBridge.shared.db.read { tx in
if let possibleAddresses = bodyRangesDelegate?.textViewMentionPickerPossibleAddresses(self, tx: tx) {
messageBody = messageBody.forPasting(intoContextWithPossibleAddresses: possibleAddresses, transaction: tx)
if let possibleAcis = bodyRangesDelegate?.textViewMentionPickerPossibleAcis(self, tx: tx) {
messageBody = messageBody.forPasting(intoContextWithPossibleAcis: possibleAcis, transaction: tx)
}
editableBody.replaceCharacters(in: selectedRange, withPastedMessageBody: messageBody, txProvider: { $0(tx) })
}

View File

@ -4,6 +4,7 @@
//
import Foundation
import LibSignalClient
import SignalServiceKit
public enum MentionPickerStyle {
@ -32,13 +33,14 @@ class MentionPicker: UIView {
let selectedAddressCallback: (SignalServiceAddress) -> Void
init(
mentionableAddresses: [SignalServiceAddress],
mentionableAcis: [Aci],
style: Style,
selectedAddressCallback: @escaping (SignalServiceAddress) -> Void
) {
mentionableUsers = SSKEnvironment.shared.databaseStorageRef.read { transaction in
let databaseStorage = SSKEnvironment.shared.databaseStorageRef
mentionableUsers = databaseStorage.read { transaction in
let sortedAddresses = SSKEnvironment.shared.contactManagerImplRef.sortSignalServiceAddresses(
mentionableAddresses,
mentionableAcis.map({ SignalServiceAddress($0) }),
transaction: transaction
)